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

loresoft / FluentCommand / 23280088634

19 Mar 2026 04:39AM UTC coverage: 57.402% (+0.004%) from 57.398%
23280088634

push

github

pwelter34
Include MemberTypeName to support nullable types

1405 of 3071 branches covered (45.75%)

Branch coverage included in aggregate %.

5 of 8 new or added lines in 4 files covered. (62.5%)

1 existing line in 1 file now uncovered.

4291 of 6852 relevant lines covered (62.62%)

330.75 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
    {
13
        var qualifiedName = entityClass.EntityNamespace is null
×
14
            ? entityClass.EntityName
×
15
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
16

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

19
        context.AddSource($"{qualifiedName}DataReaderExtensions.g.cs", source);
×
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

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

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
44
        var typeAttributes = targetSymbol.GetAttributes();
×
45
        var tableAttribute = FindSchemaAttribute(typeAttributes, "TableAttribute");
×
46

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

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

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

70
            return new EntityClass(
×
71
                InitializationMode: mode,
×
72
                FullyQualified: fullyQualified,
×
73
                EntityNamespace: classNamespace,
×
74
                EntityName: className,
×
75
                Properties: propertyArray,
×
76
                TableName: tableName,
×
77
                TableSchema: tableSchema
×
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)
×
86
            return null;
×
87

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

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

103
        return new EntityClass(
×
104
            InitializationMode: mode,
×
105
            FullyQualified: fullyQualified,
×
106
            EntityNamespace: classNamespace,
×
107
            EntityName: className,
×
108
            Properties: properties,
×
109
            TableName: tableName,
×
110
            TableSchema: tableSchema
×
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();
×
NEW
142
        var memberTypeName = propertySymbol.Type.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToDisplayString();
×
143
        var propertyName = propertySymbol.Name;
×
144
        var hasGetter = propertySymbol.GetMethod != null;
×
145
        var hasSetter = propertySymbol.SetMethod != null && !propertySymbol.SetMethod.IsInitOnly;
×
146

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

161
        var columnName = GetColumnName(attributes) ?? propertyName;
×
162
        var converterName = GetConverterName(attributes);
×
163

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

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

197
    protected static string? GetColumnName(ImmutableArray<AttributeData> attributes)
198
    {
199
        var columnAttribute = FindSchemaAttribute(attributes, "ColumnAttribute");
×
200

201
        if (columnAttribute == null)
×
202
            return null;
×
203

204
        // attribute constructor [Column("Name")]
205
        var converterType = columnAttribute.ConstructorArguments.FirstOrDefault();
×
206
        if (converterType.Value is string stringValue)
×
207
            return stringValue;
×
208

209
        return null;
×
210
    }
211

212

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

222
        if (converter == null)
×
223
            return null;
×
224

225
        // attribute constructor
226
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
227
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
228
            return converterSymbol.ToDisplayString();
×
229

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

240
        return null;
×
241
    }
242

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

259
    private static bool HasDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
260
    {
261
        return FindDataAnnotationAttribute(attributes, name) != null;
×
262
    }
263

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

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

293
        return null;
×
294
    }
295

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

302
        if (attribute.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is int option)
×
303
            return option != 0; // DatabaseGeneratedOption.None = 0
×
304

305
        return false;
×
306
    }
307

308
    private static string? GetNamedString(AttributeData? attribute, string argName)
309
    {
310
        if (attribute == null)
×
311
            return null;
×
312

313
        foreach (var namedArg in attribute.NamedArguments)
×
314
        {
315
            if (namedArg.Key == argName && namedArg.Value.Value is string value)
×
316
                return value;
×
317
        }
318

319
        return null;
×
320
    }
321

322
    private static int? GetNamedNumber(AttributeData? attribute, string argName)
323
    {
324
        if (attribute == null)
×
325
            return null;
×
326

327
        foreach (var namedArg in attribute.NamedArguments)
×
328
        {
329
            if (namedArg.Key == argName && namedArg.Value.Value is int value)
×
330
                return value;
×
331
        }
332

333
        return null;
×
334
    }
335

336
    protected static bool IsIncluded(IPropertySymbol propertySymbol)
337
    {
338
        return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
339
    }
340

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