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

loresoft / FluentCommand / 6187991024

14 Sep 2023 04:00PM UTC coverage: 50.964% (-0.1%) from 51.061%
6187991024

push

github

pwelter34
fix bug with reader generation

952 of 2406 branches covered (0.0%)

Branch coverage included in aggregate %.

16 of 16 new or added lines in 1 file covered. (100.0%)

2777 of 4911 relevant lines covered (56.55%)

161.07 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 = GetProperties(targetSymbol);
×
82

83
        if (mode == InitializationMode.ObjectInitializer)
×
84
        {
85
            var propertyArray = propertySymbols
×
86
                .Select(p => CreateProperty(p));
×
87

88
            var entity = new EntityClass(mode, classNamespace, className, propertyArray);
×
89
            return new EntityContext(entity, Enumerable.Empty<Diagnostic>());
×
90
        }
91

92
        // constructor initialization
93
        var diagnostics = new List<Diagnostic>();
×
94

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

106
            diagnostics.Add(constructorDiagnostic);
×
107

108
            return new EntityContext(null, diagnostics);
×
109
        }
110

111
        var properties = new List<EntityProperty>();
×
112
        foreach (var propertySymbol in propertySymbols)
×
113
        {
114
            // find matching constructor name
115
            var parameter = constructor
×
116
                .Parameters
×
117
                .FirstOrDefault(p => string.Equals(p.Name, propertySymbol.Name, StringComparison.InvariantCultureIgnoreCase));
×
118

119
            if (parameter == null)
×
120
            {
121
                var constructorDiagnostic = Diagnostic.Create(
×
122
                    DiagnosticDescriptors.InvalidConstructorParameter,
×
123
                    context.TargetNode.GetLocation(),
×
124
                    propertySymbol.Name,
×
125
                    className
×
126
                );
×
127

128
                diagnostics.Add(constructorDiagnostic);
×
129

130
                continue;
×
131
            }
132

133
            var property = CreateProperty(propertySymbol, parameter.Name);
×
134
            properties.Add(property);
×
135
        }
136

137
        var entityClass = new EntityClass(mode, classNamespace, className, properties);
×
138
        return new EntityContext(entityClass, diagnostics);
×
139
    }
140

141
    private static List<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymbol)
142
    {
143
        var properties = new Dictionary<string, IPropertySymbol>();
×
144

145
        var currentSymbol = targetSymbol;
×
146

147
        // get nested properties
148
        while (currentSymbol != null)
×
149
        {
150
            var propertySymbols = currentSymbol
×
151
                .GetMembers()
×
152
                .Where(m => m.Kind == SymbolKind.Property)
×
153
                .OfType<IPropertySymbol>()
×
154
                .Where(IsIncluded)
×
155
                .Where(p => !properties.ContainsKey(p.Name));
×
156

157
            foreach (var propertySymbol in propertySymbols)
×
158
                properties.Add(propertySymbol.Name, propertySymbol);
×
159

160
            currentSymbol = currentSymbol.BaseType;
×
161
        }
162

163
        return properties.Values.ToList();
×
164
    }
165

166
    private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string parameterName = null)
167
    {
168
        var propertyType = propertySymbol.Type.ToDisplayString();
×
169
        var propertyName = propertySymbol.Name;
×
170

171
        // look for custom field converter
172
        var attributes = propertySymbol.GetAttributes();
×
173
        if (attributes == null || attributes.Length == 0)
×
174
            return new EntityProperty(propertyName, propertyType, parameterName);
×
175

176
        var converter = attributes
×
177
            .FirstOrDefault(a => a.AttributeClass is
×
178
            {
×
179
                Name: "DataFieldConverterAttribute",
×
180
                ContainingNamespace.Name: "FluentCommand"
×
181
            });
×
182

183
        if (converter == null)
×
184
            return new EntityProperty(propertyName, propertyType, parameterName);
×
185

186
        // attribute contructor
187
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
188
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
189
        {
190
            return new EntityProperty(
×
191
                propertyName,
×
192
                propertyType,
×
193
                parameterName,
×
194
                converterSymbol.ToDisplayString());
×
195
        }
196

197
        // generic attribute
198
        var attributeClass = converter.AttributeClass;
×
199
        if (attributeClass is { IsGenericType: true }
×
200
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
201
            && attributeClass.TypeArguments.Length == 1)
×
202
        {
203
            var typeArgument = attributeClass.TypeArguments[0];
×
204
            var converterString = typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
205

206
            return new EntityProperty(
×
207
                propertyName,
×
208
                propertyType,
×
209
                parameterName,
×
210
                converterString);
×
211
        }
212

213
        return new EntityProperty(
×
214
            propertyName,
×
215
            propertyType,
×
216
            parameterName);
×
217
    }
218

219
    private static bool IsIncluded(IPropertySymbol propertySymbol)
220
    {
221
        var attributes = propertySymbol.GetAttributes();
×
222
        if (attributes.Length > 0 && attributes.Any(
×
223
                a => a.AttributeClass is
×
224
                {
×
225
                    Name: "NotMappedAttribute",
×
226
                    ContainingNamespace:
×
227
                    {
×
228
                        Name: "Schema",
×
229
                        ContainingNamespace:
×
230
                        {
×
231
                            Name: "DataAnnotations",
×
232
                            ContainingNamespace:
×
233
                            {
×
234
                                Name: "ComponentModel",
×
235
                                ContainingNamespace.Name: "System"
×
236
                            }
×
237
                        }
×
238
                    }
×
239
                }))
×
240
        {
241
            return false;
×
242
        }
243

244
        return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
245
    }
246
}
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