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

loresoft / FluentCommand / 23278216331

19 Mar 2026 03:19AM UTC coverage: 57.398% (+0.7%) from 56.658%
23278216331

push

github

pwelter34
Enable nullable and improve source generators

1403 of 3069 branches covered (45.72%)

Branch coverage included in aggregate %.

527 of 907 new or added lines in 58 files covered. (58.1%)

22 existing lines in 10 files now uncovered.

4288 of 6846 relevant lines covered (62.64%)

330.58 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.Collections.Immutable;
2

3
using FluentCommand.Generators.Models;
4

5
using Microsoft.CodeAnalysis;
6

7
namespace FluentCommand.Generators;
8

9
public abstract class DataReaderFactoryGenerator
10
{
11
    protected static void WriteDataReaderSource(SourceProductionContext context, EntityClass entityClass)
12
    {
NEW
13
        var qualifiedName = entityClass.EntityNamespace is null
×
NEW
14
            ? entityClass.EntityName
×
NEW
15
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
16

NEW
17
        var source = DataReaderFactoryWriter.Generate(entityClass);
×
18

NEW
19
        context.AddSource($"{qualifiedName}DataReaderExtensions.g.cs", source);
×
UNCOV
20
    }
×
21

22
    protected static void WriteTypeAccessorSource(SourceProductionContext context, EntityClass entityClass)
23
    {
24
        var qualifiedName = entityClass.EntityNamespace is null
×
25
            ? entityClass.EntityName
×
26
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
27

NEW
28
        var source = TypeAccessorWriter.Generate(entityClass);
×
29

NEW
30
        context.AddSource($"{qualifiedName}TypeAccessor.g.cs", source);
×
31
    }
×
32

33

34
    protected static EntityClass? CreateClass(INamedTypeSymbol targetSymbol)
35
    {
36
        if (targetSymbol == null)
×
37
            return null;
×
38

39
        var fullyQualified = targetSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
40
        var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
×
41
        var className = targetSymbol.Name;
×
42

43
        // extract table mapping info
NEW
44
        var typeAttributes = targetSymbol.GetAttributes();
×
NEW
45
        var tableAttribute = FindSchemaAttribute(typeAttributes, "TableAttribute");
×
46

NEW
47
        string? tableName = null;
×
NEW
48
        string? tableSchema = null;
×
49

NEW
50
        if (tableAttribute != null)
×
51
        {
NEW
52
            if (tableAttribute.ConstructorArguments.Length > 0 && tableAttribute.ConstructorArguments[0].Value is string name)
×
NEW
53
                tableName = name;
×
54

NEW
55
            tableSchema = GetNamedString(tableAttribute, "Schema");
×
56
        }
57

58
        var mode = targetSymbol.Constructors.Any(c => c.Parameters.Length == 0)
×
59
            ? InitializationMode.ObjectInitializer
×
60
            : InitializationMode.Constructor;
×
61

62
        var propertySymbols = GetProperties(targetSymbol);
×
63

64
        if (mode == InitializationMode.ObjectInitializer)
×
65
        {
66
            var propertyArray = propertySymbols
×
67
                .Select(p => CreateProperty(p))
×
68
                .ToArray();
×
69

NEW
70
            return new EntityClass(
×
NEW
71
                InitializationMode: mode,
×
NEW
72
                FullyQualified: fullyQualified,
×
NEW
73
                EntityNamespace: classNamespace,
×
NEW
74
                EntityName: className,
×
NEW
75
                Properties: propertyArray,
×
NEW
76
                TableName: tableName,
×
NEW
77
                TableSchema: tableSchema
×
NEW
78
            );
×
79
        }
80

81
        // constructor initialization
82

83
        // constructor with same number of parameters as properties
84
        var constructor = targetSymbol.Constructors.FirstOrDefault(c => c.Parameters.Length == propertySymbols.Count);
×
85
        if (constructor == null)
×
UNCOV
86
            return null;
×
87

UNCOV
88
        var properties = new List<EntityProperty>();
×
89
        foreach (var propertySymbol in propertySymbols)
×
90
        {
91
            // find matching constructor name
92
            var parameter = constructor
×
93
                .Parameters
×
94
                .FirstOrDefault(p => string.Equals(p.Name, propertySymbol.Name, StringComparison.InvariantCultureIgnoreCase));
×
95

96
            if (parameter == null)
×
97
                continue;
98

UNCOV
99
            var property = CreateProperty(propertySymbol, parameter.Name);
×
100
            properties.Add(property);
×
101
        }
102

NEW
103
        return new EntityClass(
×
NEW
104
            InitializationMode: mode,
×
NEW
105
            FullyQualified: fullyQualified,
×
NEW
106
            EntityNamespace: classNamespace,
×
NEW
107
            EntityName: className,
×
NEW
108
            Properties: properties,
×
NEW
109
            TableName: tableName,
×
NEW
110
            TableSchema: tableSchema
×
NEW
111
        );
×
112
    }
113

114
    protected static List<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymbol)
115
    {
116
        var properties = new Dictionary<string, IPropertySymbol>();
×
117

118
        var currentSymbol = targetSymbol;
×
119

120
        // get nested properties
121
        while (currentSymbol != null)
×
122
        {
123
            var propertySymbols = currentSymbol
×
124
                .GetMembers()
×
125
                .Where(m => m.Kind == SymbolKind.Property)
×
126
                .OfType<IPropertySymbol>()
×
127
                .Where(IsIncluded)
×
128
                .Where(p => !properties.ContainsKey(p.Name));
×
129

130
            foreach (var propertySymbol in propertySymbols)
×
131
                properties.Add(propertySymbol.Name, propertySymbol);
×
132

133
            currentSymbol = currentSymbol.BaseType;
×
134
        }
135

136
        return properties.Values.ToList();
×
137
    }
138

139
    protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string? parameterName = null)
140
    {
141
        var propertyType = propertySymbol.Type.ToDisplayString();
×
142
        var propertyName = propertySymbol.Name;
×
NEW
143
        var hasGetter = propertySymbol.GetMethod != null;
×
NEW
144
        var hasSetter = propertySymbol.SetMethod != null && !propertySymbol.SetMethod.IsInitOnly;
×
145

UNCOV
146
        var attributes = propertySymbol.GetAttributes();
×
NEW
147
        if (attributes == default || attributes.Length == 0)
×
148
        {
149
            return new EntityProperty(
×
NEW
150
                PropertyName: propertyName,
×
NEW
151
                ColumnName: propertyName,
×
NEW
152
                PropertyType: propertyType,
×
NEW
153
                ParameterName: parameterName,
×
NEW
154
                HasGetter: hasGetter,
×
NEW
155
                HasSetter: hasSetter
×
NEW
156
            );
×
157
        }
158

159
        var columnName = GetColumnName(attributes) ?? propertyName;
×
NEW
160
        var converterName = GetConverterName(attributes);
×
161

NEW
162
        var isKey = HasDataAnnotationAttribute(attributes, "KeyAttribute");
×
NEW
163
        var isNotMapped = IsNotMapped(attributes);
×
NEW
164
        var isDatabaseGenerated = GetIsDatabaseGenerated(attributes);
×
NEW
165
        var isConcurrencyCheck = HasDataAnnotationAttribute(attributes, "ConcurrencyCheckAttribute");
×
NEW
166
        var foreignKey = GetSchemaAttributeConstructorStringArg(attributes, "ForeignKeyAttribute");
×
NEW
167
        var isRequired = HasDataAnnotationAttribute(attributes, "RequiredAttribute");
×
NEW
168
        var displayName = GetNamedString(FindDataAnnotationAttribute(attributes, "DisplayAttribute"), "Name");
×
NEW
169
        var dataFormatString = GetNamedString(FindDataAnnotationAttribute(attributes, "DisplayFormatAttribute"), "DataFormatString");
×
NEW
170
        var columnType = GetNamedString(FindSchemaAttribute(attributes, "ColumnAttribute"), "TypeName");
×
NEW
171
        var columnOrder = GetNamedNumber(FindSchemaAttribute(attributes, "ColumnAttribute"), "Order");
×
172

NEW
173
        return new EntityProperty(
×
NEW
174
            PropertyName: propertyName,
×
NEW
175
            ColumnName: columnName,
×
NEW
176
            PropertyType: propertyType,
×
NEW
177
            ParameterName: parameterName,
×
NEW
178
            ConverterName: converterName,
×
NEW
179
            IsKey: isKey,
×
NEW
180
            IsNotMapped: isNotMapped,
×
NEW
181
            IsDatabaseGenerated: isDatabaseGenerated,
×
NEW
182
            IsConcurrencyCheck: isConcurrencyCheck,
×
NEW
183
            ForeignKey: foreignKey,
×
NEW
184
            IsRequired: isRequired,
×
NEW
185
            HasGetter: hasGetter,
×
NEW
186
            HasSetter: hasSetter,
×
NEW
187
            DisplayName: displayName,
×
NEW
188
            DataFormatString: dataFormatString,
×
NEW
189
            ColumnType: columnType,
×
NEW
190
            ColumnOrder: columnOrder
×
NEW
191
        );
×
192
    }
193

194
    protected static string? GetColumnName(ImmutableArray<AttributeData> attributes)
195
    {
NEW
196
        var columnAttribute = FindSchemaAttribute(attributes, "ColumnAttribute");
×
197

NEW
198
        if (columnAttribute == null)
×
NEW
199
            return null;
×
200

201
        // attribute constructor [Column("Name")]
NEW
202
        var converterType = columnAttribute.ConstructorArguments.FirstOrDefault();
×
NEW
203
        if (converterType.Value is string stringValue)
×
NEW
204
            return stringValue;
×
205

NEW
206
        return null;
×
207
    }
208

209

210
    private static string? GetConverterName(ImmutableArray<AttributeData> attributes)
211
    {
212
        var converter = attributes
×
213
            .FirstOrDefault(a => a.AttributeClass is
×
214
            {
×
215
                Name: "DataFieldConverterAttribute",
×
216
                ContainingNamespace.Name: "FluentCommand"
×
217
            });
×
218

219
        if (converter == null)
×
NEW
220
            return null;
×
221

222
        // attribute constructor
223
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
224
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
NEW
225
            return converterSymbol.ToDisplayString();
×
226

227
        // generic attribute
228
        var attributeClass = converter.AttributeClass;
×
229
        if (attributeClass is { IsGenericType: true }
×
230
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
231
            && attributeClass.TypeArguments.Length == 1)
×
232
        {
233
            var typeArgument = attributeClass.TypeArguments[0];
×
NEW
234
            return typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
235
        }
236

NEW
237
        return null;
×
238
    }
239

240
    private static AttributeData? FindDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
241
    {
NEW
242
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
NEW
243
        {
×
NEW
244
            ContainingNamespace:
×
NEW
245
            {
×
NEW
246
                Name: "DataAnnotations",
×
NEW
247
                ContainingNamespace:
×
NEW
248
                {
×
NEW
249
                    Name: "ComponentModel",
×
NEW
250
                    ContainingNamespace.Name: "System"
×
NEW
251
                }
×
NEW
252
            }
×
NEW
253
        } && a.AttributeClass.Name == name);
×
254
    }
255

256
    private static bool HasDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
257
    {
NEW
258
        return FindDataAnnotationAttribute(attributes, name) != null;
×
259
    }
260

261
    protected static AttributeData? FindSchemaAttribute(ImmutableArray<AttributeData> attributes, string name)
262
    {
NEW
263
        return attributes.FirstOrDefault(a =>
×
NEW
264
            a.AttributeClass is
×
NEW
265
            {
×
NEW
266
                ContainingNamespace:
×
NEW
267
                {
×
NEW
268
                    Name: "Schema",
×
NEW
269
                    ContainingNamespace:
×
NEW
270
                    {
×
NEW
271
                        Name: "DataAnnotations",
×
NEW
272
                        ContainingNamespace:
×
NEW
273
                        {
×
NEW
274
                            Name: "ComponentModel",
×
NEW
275
                            ContainingNamespace.Name: "System"
×
NEW
276
                        }
×
NEW
277
                    }
×
NEW
278
                }
×
NEW
279
            }
×
NEW
280
            && a.AttributeClass.Name == name
×
NEW
281
        );
×
282
    }
283

284
    private static string? GetSchemaAttributeConstructorStringArg(ImmutableArray<AttributeData> attributes, string name)
285
    {
NEW
286
        var attribute = FindSchemaAttribute(attributes, name);
×
NEW
287
        if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string value)
×
NEW
288
            return value;
×
289

NEW
290
        return null;
×
291
    }
292

293
    private static bool GetIsDatabaseGenerated(ImmutableArray<AttributeData> attributes)
294
    {
NEW
295
        var attribute = FindSchemaAttribute(attributes, "DatabaseGeneratedAttribute");
×
NEW
296
        if (attribute == null)
×
NEW
297
            return false;
×
298

NEW
299
        if (attribute.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is int option)
×
NEW
300
            return option != 0; // DatabaseGeneratedOption.None = 0
×
301

NEW
302
        return false;
×
303
    }
304

305
    private static string? GetNamedString(AttributeData? attribute, string argName)
306
    {
NEW
307
        if (attribute == null)
×
NEW
308
            return null;
×
309

NEW
310
        foreach (var namedArg in attribute.NamedArguments)
×
311
        {
NEW
312
            if (namedArg.Key == argName && namedArg.Value.Value is string value)
×
NEW
313
                return value;
×
314
        }
315

NEW
316
        return null;
×
317
    }
318

319
    private static int? GetNamedNumber(AttributeData? attribute, string argName)
320
    {
NEW
321
        if (attribute == null)
×
UNCOV
322
            return null;
×
323

NEW
324
        foreach (var namedArg in attribute.NamedArguments)
×
325
        {
NEW
326
            if (namedArg.Key == argName && namedArg.Value.Value is int value)
×
NEW
327
                return value;
×
328
        }
329

330
        return null;
×
331
    }
332

333
    protected static bool IsIncluded(IPropertySymbol propertySymbol)
334
    {
NEW
335
        return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
336
    }
337

338
    private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
339
    {
NEW
340
        return attributes.Any(
×
NEW
341
            a => a.AttributeClass is
×
NEW
342
            {
×
NEW
343
                Name: "NotMappedAttribute",
×
NEW
344
                ContainingNamespace:
×
345
                {
×
NEW
346
                    Name: "Schema",
×
347
                    ContainingNamespace:
×
348
                    {
×
NEW
349
                        Name: "DataAnnotations",
×
350
                        ContainingNamespace:
×
351
                        {
×
NEW
352
                            Name: "ComponentModel",
×
NEW
353
                            ContainingNamespace.Name: "System"
×
354
                        }
×
355
                    }
×
NEW
356
                }
×
NEW
357
            });
×
358
    }
359
}
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