• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 5874771988

16 Aug 2023 04:44AM UTC coverage: 43.028% (+2.5%) from 40.495%
5874771988

push

github

web-flow
Merge pull request #277 from loresoft/feature/method-injectors

Feature/method injectors

765 of 2171 branches covered (35.24%)

Branch coverage included in aggregate %.

126 of 126 new or added lines in 6 files covered. (100.0%)

2361 of 5094 relevant lines covered (46.35%)

119.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/FluentCommand.Generators/DataReaderFactoryGenerator.cs
1
using System;
2
using System.Collections.Immutable;
3
using System.Reflection;
4

5
using FluentCommand.Generators.Internal;
6
using FluentCommand.Generators.Models;
7

8
using Microsoft.CodeAnalysis;
9
using Microsoft.CodeAnalysis.CSharp;
10
using Microsoft.CodeAnalysis.CSharp.Syntax;
11

12
namespace FluentCommand.Generators;
13

14
[Generator(LanguageNames.CSharp)]
15
public class DataReaderFactoryGenerator : IIncrementalGenerator
16
{
17
    public void Initialize(IncrementalGeneratorInitializationContext context)
18
    {
19
        var provider = context.SyntaxProvider.ForAttributeWithMetadataName(
×
20
            fullyQualifiedMetadataName: "System.ComponentModel.DataAnnotations.Schema.TableAttribute",
×
21
            predicate: SyntacticPredicate,
×
22
            transform: SemanticTransform
×
23
        )
×
24
        .Where(static context => context is not null);
×
25

26
        // Emit the diagnostics, if needed
27
        var diagnostics = provider
×
28
            .Select(static (item, _) => item.Diagnostics)
×
29
            .Where(static item => item.Count > 0);
×
30

31
        context.RegisterSourceOutput(diagnostics, ReportDiagnostic);
×
32

33
        var entityClasses = provider
×
34
            .Select(static (item, _) => item.EntityClass)
×
35
            .Where(static item => item is not null);
×
36

37
        context.RegisterSourceOutput(entityClasses, Execute);
×
38
    }
×
39

40
    private static void ReportDiagnostic(SourceProductionContext context, EquatableArray<Diagnostic> diagnostics)
41
    {
42
        foreach (var diagnostic in diagnostics)
×
43
            context.ReportDiagnostic(diagnostic);
×
44
    }
×
45

46
    private static void Execute(SourceProductionContext context, EntityClass entityClass)
47
    {
48
        var qualifiedName = entityClass.EntityNamespace is null
×
49
            ? entityClass.EntityName
×
50
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
51

52
        var source = DataReaderFactoryWriter.Generate(entityClass);
×
53

54
        context.AddSource($"{qualifiedName}DataReaderExtensions.g.cs", source);
×
55
    }
×
56

57
    private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken)
58
    {
59
        return syntaxNode is ClassDeclarationSyntax
×
60
               { AttributeLists.Count: > 0 } classDeclaration
×
61
               && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
×
62
               && !classDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)
×
63
            || syntaxNode is RecordDeclarationSyntax
×
64
               { AttributeLists.Count: > 0 } recordDeclaration
×
65
               && !recordDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
×
66
               && !recordDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword);
×
67
    }
68

69
    private static EntityContext SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
70
    {
71
        if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
×
72
            return null;
×
73

74
        var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
×
75
        var className = targetSymbol.Name;
×
76

77
        var mode = targetSymbol.Constructors.Any(c => c.Parameters.Length == 0)
×
78
            ? InitializationMode.ObjectInitializer
×
79
            : InitializationMode.Constructor;
×
80

81
        var propertySymbols = targetSymbol
×
82
            .GetMembers()
×
83
            .Where(m => m.Kind == SymbolKind.Property)
×
84
            .OfType<IPropertySymbol>()
×
85
            .Where(IsIncluded)
×
86
            .ToList();
×
87

88
        if (mode == InitializationMode.ObjectInitializer)
×
89
        {
90
            var propertyArray = propertySymbols
×
91
                .Select(p => CreateProperty(p));
×
92

93
            var entity = new EntityClass(mode, classNamespace, className, propertyArray);
×
94
            return new EntityContext(entity, Enumerable.Empty<Diagnostic>());
×
95
        }
96

97
        // constructor initialization
98
        var diagnostics = new List<Diagnostic>();
×
99

100
        // constructor with same number of parameters as properties
101
        var constructor = targetSymbol.Constructors.FirstOrDefault(c => c.Parameters.Length == propertySymbols.Count);
×
102
        if (constructor == null)
×
103
        {
104
            var constructorDiagnostic = Diagnostic.Create(
×
105
                DiagnosticDescriptors.InvalidConstructor,
×
106
                context.TargetNode.GetLocation(),
×
107
                propertySymbols.Count,
×
108
                className
×
109
            );
×
110

111
            diagnostics.Add(constructorDiagnostic);
×
112

113
            return new EntityContext(null, diagnostics);
×
114
        }
115

116
        var properties = new List<EntityProperty>();
×
117
        foreach (var propertySymbol in propertySymbols)
×
118
        {
119
            // find matching constructor name
120
            var parameter = constructor
×
121
                .Parameters
×
122
                .FirstOrDefault(p => string.Equals(p.Name, propertySymbol.Name, StringComparison.InvariantCultureIgnoreCase));
×
123

124
            if (parameter == null)
×
125
            {
126
                var constructorDiagnostic = Diagnostic.Create(
×
127
                    DiagnosticDescriptors.InvalidConstructorParameter,
×
128
                    context.TargetNode.GetLocation(),
×
129
                    propertySymbol.Name,
×
130
                    className
×
131
                );
×
132

133
                diagnostics.Add(constructorDiagnostic);
×
134

135
                continue;
×
136
            }
137

138
            var property = CreateProperty(propertySymbol, parameter.Name);
×
139
            properties.Add(property);
×
140
        }
141

142
        var entityClass = new EntityClass(mode, classNamespace, className, properties);
×
143
        return new EntityContext(entityClass, diagnostics);
×
144
    }
145

146
    private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string parameterName = null)
147
    {
148
        var propertyType = propertySymbol.Type.ToDisplayString();
×
149
        var propertyName = propertySymbol.Name;
×
150

151
        // look for custom field converter
152
        var attributes = propertySymbol.GetAttributes();
×
153
        if (attributes == null || attributes.Length == 0)
×
154
            return new EntityProperty(propertyName, propertyType, parameterName);
×
155

156
        var converter = attributes
×
157
            .FirstOrDefault(a => a.AttributeClass is
×
158
            {
×
159
                Name: "DataFieldConverterAttribute",
×
160
                ContainingNamespace.Name: "FluentCommand"
×
161
            });
×
162

163
        if (converter == null)
×
164
            return new EntityProperty(propertyName, propertyType, parameterName);
×
165

166
        // attribute contructor
167
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
168
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
169
        {
170
            return new EntityProperty(
×
171
                propertyName,
×
172
                propertyType,
×
173
                parameterName,
×
174
                converterSymbol.ToDisplayString());
×
175
        }
176

177
        // generic attribute
178
        var attributeClass = converter.AttributeClass;
×
179
        if (attributeClass is { IsGenericType: true }
×
180
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
181
            && attributeClass.TypeArguments.Length == 1)
×
182
        {
183
            var typeArgument = attributeClass.TypeArguments[0];
×
184
            var converterString = typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
185

186
            return new EntityProperty(
×
187
                propertyName,
×
188
                propertyType,
×
189
                parameterName,
×
190
                converterString);
×
191
        }
192

193
        return new EntityProperty(
×
194
            propertyName,
×
195
            propertyType,
×
196
            parameterName);
×
197
    }
198

199
    private static bool IsIncluded(IPropertySymbol propertySymbol)
200
    {
201
        var attributes = propertySymbol.GetAttributes();
×
202
        if (attributes.Length > 0 && attributes.Any(
×
203
                a => a.AttributeClass is
×
204
                {
×
205
                    Name: "NotMappedAttribute",
×
206
                    ContainingNamespace:
×
207
                    {
×
208
                        Name: "Schema",
×
209
                        ContainingNamespace:
×
210
                        {
×
211
                            Name: "DataAnnotations",
×
212
                            ContainingNamespace:
×
213
                            {
×
214
                                Name: "ComponentModel",
×
215
                                ContainingNamespace.Name: "System"
×
216
                            }
×
217
                        }
×
218
                    }
×
219
                }))
×
220
        {
221
            return false;
×
222
        }
223

224
        return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
225
    }
226
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc