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

loresoft / FluentCommand / 26601445198

28 May 2026 08:50PM UTC coverage: 55.738% (+0.1%) from 55.595%
26601445198

push

github

pwelter34
Normalize enums and add enum data readers

1381 of 3245 branches covered (42.56%)

Branch coverage included in aggregate %.

38 of 58 new or added lines in 7 files covered. (65.52%)

3 existing lines in 1 file now uncovered.

4438 of 7195 relevant lines covered (61.68%)

311.1 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
using Microsoft.CodeAnalysis.CSharp;
7
using Microsoft.CodeAnalysis.CSharp.Syntax;
8

9
namespace FluentCommand.Generators;
10

11
[Generator(LanguageNames.CSharp)]
12
public sealed class DataReaderFactoryGenerator : IIncrementalGenerator
13
{
14
    public void Initialize(IncrementalGeneratorInitializationContext context)
15
    {
16
        // Pipeline for [GenerateReader(typeof(T))] attribute
17
        var generateAttributeClasses = context.SyntaxProvider.ForAttributeWithMetadataName(
×
18
            fullyQualifiedMetadataName: "FluentCommand.Attributes.GenerateReaderAttribute",
×
19
            predicate: static (_, __) => true,
×
20
            transform: static (context, _) =>
×
21
            {
×
22
                if (context.Attributes.Length == 0)
×
23
                    return [];
×
24

×
25
                var classes = new List<EntityClass>();
×
26

×
27
                foreach (var attribute in context.Attributes)
×
28
                {
×
29
                    if (attribute == null)
×
30
                        return [];
×
31

×
32
                    if (attribute.ConstructorArguments.Length != 1)
×
33
                        return [];
×
34

×
35
                    var comparerArgument = attribute.ConstructorArguments[0];
×
36
                    if (comparerArgument.Value is not INamedTypeSymbol targetSymbol)
×
37
                        return [];
×
38

×
39
                    var entityClass = CreateClass(targetSymbol);
×
40
                    if (entityClass != null)
×
41
                        classes.Add(entityClass);
×
42
                }
×
43

×
44
                return new EquatableArray<EntityClass>(classes);
×
45
            }
×
46
        )
×
47
        .Where(static context => context.Count > 0)
×
48
        .SelectMany(static (item, _) => item)
×
49
        .WithTrackingName("GenerateAttributeGenerator");
×
50

51
        context.RegisterSourceOutput(generateAttributeClasses, WriteDataReaderSource);
×
52
        context.RegisterSourceOutput(generateAttributeClasses, WriteTypeAccessorSource);
×
53

54
        // Pipeline for [Table] attribute
55
        var tableAttributeClasses = context.SyntaxProvider.ForAttributeWithMetadataName(
×
56
            fullyQualifiedMetadataName: "System.ComponentModel.DataAnnotations.Schema.TableAttribute",
×
57
            predicate: static (syntaxNode, _) =>
×
58
            {
×
59
                return
×
60
                    (
×
61
                        syntaxNode is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDeclaration
×
62
                            && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
×
63
                            && !classDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)
×
64
                    )
×
65
                    ||
×
66
                    (
×
67
                        syntaxNode is RecordDeclarationSyntax { AttributeLists.Count: > 0 } recordDeclaration
×
68
                            && !recordDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
×
69
                            && !recordDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)
×
70
                    );
×
71
            },
×
72
            transform: static (context, _) =>
×
73
            {
×
74
                if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
×
75
                    return null;
×
76

×
77
                return CreateClass(targetSymbol);
×
78
            }
×
79
        )
×
80
        .Where(static context => context is not null)
×
81
        .Select(static (context, _) => context!)
×
82
        .WithTrackingName("TableAttributeGenerator");
×
83

84
        context.RegisterSourceOutput(tableAttributeClasses, WriteDataReaderSource);
×
85
        context.RegisterSourceOutput(tableAttributeClasses, WriteTypeAccessorSource);
×
86
    }
×
87

88
    private static void WriteDataReaderSource(SourceProductionContext context, EntityClass entityClass)
89
    {
90
        var qualifiedName = entityClass.EntityNamespace is null
×
91
            ? entityClass.EntityName
×
92
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
93

94
        var source = DataReaderFactoryWriter.Generate(entityClass);
×
95

96
        context.AddSource($"{qualifiedName}DataReaderExtensions.g.cs", source);
×
97
    }
×
98

99
    private static void WriteTypeAccessorSource(SourceProductionContext context, EntityClass entityClass)
100
    {
101
        var qualifiedName = entityClass.EntityNamespace is null
×
102
            ? entityClass.EntityName
×
103
            : $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
×
104

105
        var source = TypeAccessorWriter.Generate(entityClass);
×
106

107
        context.AddSource($"{qualifiedName}TypeAccessor.g.cs", source);
×
108
    }
×
109

110

111
    private static EntityClass? CreateClass(INamedTypeSymbol targetSymbol)
112
    {
113
        if (targetSymbol == null)
×
114
            return null;
×
115

116
        var fullyQualified = targetSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
117
        var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
×
118
        var className = targetSymbol.Name;
×
119

120
        // extract table mapping info
121
        var typeAttributes = targetSymbol.GetAttributes();
×
122
        var tableAttribute = FindSchemaAttribute(typeAttributes, "TableAttribute");
×
123

124
        string? tableName = null;
×
125
        string? tableSchema = null;
×
126

127
        if (tableAttribute != null)
×
128
        {
129
            if (tableAttribute.ConstructorArguments.Length > 0 && tableAttribute.ConstructorArguments[0].Value is string name)
×
130
                tableName = name;
×
131

132
            tableSchema = GetNamedString(tableAttribute, "Schema");
×
133
        }
134

135
        var mode = targetSymbol.Constructors.Any(c => c.Parameters.Length == 0)
×
136
            ? InitializationMode.ObjectInitializer
×
137
            : InitializationMode.Constructor;
×
138

139
        var classIgnored = GetClassIgnoredProperties(typeAttributes);
×
140
        var propertySymbols = GetProperties(targetSymbol);
×
141

142
        if (mode == InitializationMode.ObjectInitializer)
×
143
        {
144
            var propertyArray = propertySymbols
×
145
                .Select(p => CreateProperty(p, classIgnored: classIgnored))
×
146
                .ToArray();
×
147

148
            return new EntityClass
×
149
            {
×
150
                InitializationMode = mode,
×
151
                FullyQualified = fullyQualified,
×
152
                EntityNamespace = classNamespace,
×
153
                EntityName = className,
×
154
                Properties = propertyArray,
×
155
                TableName = tableName,
×
156
                TableSchema = tableSchema
×
157
            };
×
158
        }
159

160
        // constructor initialization
161

162
        // constructor with same number of parameters as mappable properties
163
        var mappableCount = propertySymbols
×
164
            .Count(p => IsMappableProperty(p, classIgnored));
×
165

166
        var constructor = targetSymbol.Constructors.FirstOrDefault(c => c.Parameters.Length == mappableCount);
×
167
        if (constructor == null)
×
168
            return null;
×
169

170
        var properties = new List<EntityProperty>();
×
171
        foreach (var propertySymbol in propertySymbols)
×
172
        {
173
            // find matching constructor name
174
            var parameter = constructor
×
175
                .Parameters
×
176
                .FirstOrDefault(p => string.Equals(p.Name, propertySymbol.Name, StringComparison.InvariantCultureIgnoreCase));
×
177

178
            if (parameter == null)
×
179
                continue;
180

181
            var property = CreateProperty(propertySymbol, parameter.Name, classIgnored: classIgnored);
×
182
            properties.Add(property);
×
183
        }
184

185
        return new EntityClass
×
186
        {
×
187
            InitializationMode = mode,
×
188
            FullyQualified = fullyQualified,
×
189
            EntityNamespace = classNamespace,
×
190
            EntityName = className,
×
191
            Properties = properties,
×
192
            TableName = tableName,
×
193
            TableSchema = tableSchema
×
194
        };
×
195
    }
196

197
    private static List<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymbol)
198
    {
199
        var properties = new Dictionary<string, IPropertySymbol>();
×
200

201
        var currentSymbol = targetSymbol;
×
202

203
        // get nested properties
204
        while (currentSymbol != null)
×
205
        {
206
            var propertySymbols = currentSymbol
×
207
                .GetMembers()
×
208
                .Where(m => m.Kind == SymbolKind.Property)
×
209
                .OfType<IPropertySymbol>()
×
210
                .Where(IsIncluded)
×
211
                .Where(p => !properties.ContainsKey(p.Name));
×
212

213
            foreach (var propertySymbol in propertySymbols)
×
214
                properties.Add(propertySymbol.Name, propertySymbol);
×
215

216
            currentSymbol = currentSymbol.BaseType;
×
217
        }
218

219
        return [.. properties.Values];
×
220
    }
221

222
    private static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string? parameterName = null, HashSet<string>? classIgnored = null)
223
    {
224
        var propertyType = propertySymbol.Type.ToDisplayString();
×
225
        var memberTypeName = propertySymbol.Type.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToDisplayString();
×
226
        var propertyName = propertySymbol.Name;
×
227
        var hasGetter = propertySymbol.GetMethod != null;
×
228
        var hasSetter = propertySymbol.SetMethod?.IsInitOnly == false;
×
229
        var attributes = propertySymbol.GetAttributes();
×
230
        var jsonColumn = GetJsonColumn(attributes);
×
231
        var isJsonColumn = jsonColumn != null;
×
NEW
232
        var enumInfo = GetEnumInfo(propertySymbol.Type);
×
UNCOV
233
        var isNotMapped = (classIgnored?.Contains(propertyName) == true) || (!isJsonColumn && !IsSupportedType(propertySymbol.Type));
×
234

235
        if (attributes == default || attributes.Length == 0)
×
236
        {
237
            return new EntityProperty
×
238
            {
×
239
                PropertyName = propertyName,
×
240
                ColumnName = propertyName,
×
241
                PropertyType = propertyType,
×
242
                MemberTypeName = memberTypeName,
×
243
                ParameterName = parameterName,
×
244
                IsNotMapped = isNotMapped,
×
245
                HasGetter = hasGetter,
×
246
                HasSetter = hasSetter,
×
NEW
247
                IsJsonColumn = isJsonColumn,
×
NEW
248
                IsEnum = enumInfo.IsEnum,
×
NEW
249
                IsNullableEnum = enumInfo.IsNullableEnum,
×
NEW
250
                EnumUnderlyingType = enumInfo.UnderlyingType
×
UNCOV
251
            };
×
252
        }
253

254
        var columnName = GetColumnName(attributes) ?? propertyName;
×
255
        var converterName = GetConverterName(attributes);
×
256

257
        var isKey = HasDataAnnotationAttribute(attributes, "KeyAttribute");
×
258

259
        isNotMapped = isNotMapped
×
260
            || IsNotMapped(attributes)
×
261
            || HasIgnorePropertyAttribute(attributes);
×
262

263
        var isDatabaseGenerated = GetIsDatabaseGenerated(attributes);
×
264
        var isConcurrencyCheck = HasDataAnnotationAttribute(attributes, "ConcurrencyCheckAttribute");
×
265
        var foreignKey = GetSchemaAttributeConstructorStringArg(attributes, "ForeignKeyAttribute");
×
266
        var isRequired = HasDataAnnotationAttribute(attributes, "RequiredAttribute");
×
267
        var displayName = GetNamedString(FindDataAnnotationAttribute(attributes, "DisplayAttribute"), "Name");
×
268
        var dataFormatString = GetNamedString(FindDataAnnotationAttribute(attributes, "DisplayFormatAttribute"), "DataFormatString");
×
269
        var columnType = GetNamedString(FindSchemaAttribute(attributes, "ColumnAttribute"), "TypeName");
×
270
        var columnOrder = GetNamedNumber(FindSchemaAttribute(attributes, "ColumnAttribute"), "Order");
×
271
        var jsonOptionsProviderName = GetJsonOptionsProviderName(jsonColumn);
×
272
        var jsonContextName = GetJsonContextName(jsonColumn);
×
273
        var jsonTypeInfoPropertyName = GetJsonTypeInfoPropertyName(jsonColumn);
×
274

275
        return new EntityProperty
×
276
        {
×
277
            PropertyName = propertyName,
×
278
            ColumnName = columnName,
×
279
            PropertyType = propertyType,
×
280
            MemberTypeName = memberTypeName,
×
281
            ParameterName = parameterName,
×
282
            ConverterName = converterName,
×
283
            IsKey = isKey,
×
284
            IsNotMapped = isNotMapped,
×
285
            IsDatabaseGenerated = isDatabaseGenerated,
×
286
            IsConcurrencyCheck = isConcurrencyCheck,
×
287
            ForeignKey = foreignKey,
×
288
            IsRequired = isRequired,
×
289
            HasGetter = hasGetter,
×
290
            HasSetter = hasSetter,
×
291
            DisplayName = displayName,
×
292
            DataFormatString = dataFormatString,
×
293
            ColumnType = columnType,
×
294
            ColumnOrder = columnOrder,
×
295
            IsJsonColumn = isJsonColumn,
×
296
            JsonOptionsProviderName = jsonOptionsProviderName,
×
297
            JsonContextName = jsonContextName,
×
NEW
298
            JsonTypeInfoPropertyName = jsonTypeInfoPropertyName,
×
NEW
299
            IsEnum = enumInfo.IsEnum,
×
NEW
300
            IsNullableEnum = enumInfo.IsNullableEnum,
×
NEW
301
            EnumUnderlyingType = enumInfo.UnderlyingType
×
UNCOV
302
        };
×
303
    }
304

305
    private static (bool IsEnum, bool IsNullableEnum, string? UnderlyingType) GetEnumInfo(ITypeSymbol type)
306
    {
NEW
307
        var isNullableEnum = false;
×
NEW
308
        var enumType = type;
×
309

NEW
310
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
×
311
        {
NEW
312
            isNullableEnum = true;
×
NEW
313
            enumType = namedType.TypeArguments[0];
×
314
        }
315

NEW
316
        if (enumType is not INamedTypeSymbol { TypeKind: TypeKind.Enum } namedEnum)
×
NEW
317
            return (false, false, null);
×
318

NEW
319
        return (true, isNullableEnum, namedEnum.EnumUnderlyingType?.ToDisplayString());
×
320
    }
321

322
    private static AttributeData? GetJsonColumn(ImmutableArray<AttributeData> attributes)
323
    {
324
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
325
        {
×
326
            Name: "JsonColumnAttribute",
×
327
            ContainingNamespace:
×
328
            {
×
329
                Name: "Attributes",
×
330
                ContainingNamespace.Name: "FluentCommand"
×
331
            }
×
332
        });
×
333
    }
334

335
    private static string? GetJsonOptionsProviderName(AttributeData? attribute)
336
    {
337
        if (attribute?.ConstructorArguments.Length == 1 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol providerSymbol)
×
338
            return providerSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
339

340
        return null;
×
341
    }
342

343
    private static string? GetJsonContextName(AttributeData? attribute)
344
    {
345
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol contextSymbol)
×
346
            return contextSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
347

348
        return null;
×
349
    }
350

351
    private static string? GetJsonTypeInfoPropertyName(AttributeData? attribute)
352
    {
353
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[1].Value is string propertyName)
×
354
            return propertyName;
×
355

356
        return null;
×
357
    }
358

359
    private static string? GetColumnName(ImmutableArray<AttributeData> attributes)
360
    {
361
        var columnAttribute = FindSchemaAttribute(attributes, "ColumnAttribute");
×
362

363
        if (columnAttribute == null)
×
364
            return null;
×
365

366
        // attribute constructor [Column("Name")]
367
        var converterType = columnAttribute.ConstructorArguments.FirstOrDefault();
×
368
        if (converterType.Value is string stringValue)
×
369
            return stringValue;
×
370

371
        return null;
×
372
    }
373

374

375
    private static string? GetConverterName(ImmutableArray<AttributeData> attributes)
376
    {
377
        var converter = attributes
×
378
            .FirstOrDefault(a => a.AttributeClass is
×
379
            {
×
380
                Name: "DataFieldConverterAttribute",
×
381
                ContainingNamespace.Name: "FluentCommand"
×
382
            });
×
383

384
        if (converter == null)
×
385
            return null;
×
386

387
        // attribute constructor
388
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
389
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
390
            return converterSymbol.ToDisplayString();
×
391

392
        // generic attribute
393
        var attributeClass = converter.AttributeClass;
×
394
        if (attributeClass is { IsGenericType: true }
×
395
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
396
            && attributeClass.TypeArguments.Length == 1)
×
397
        {
398
            var typeArgument = attributeClass.TypeArguments[0];
×
399
            return typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
400
        }
401

402
        return null;
×
403
    }
404

405
    private static AttributeData? FindDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
406
    {
407
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
408
        {
×
409
            ContainingNamespace:
×
410
            {
×
411
                Name: "DataAnnotations",
×
412
                ContainingNamespace:
×
413
                {
×
414
                    Name: "ComponentModel",
×
415
                    ContainingNamespace.Name: "System"
×
416
                }
×
417
            }
×
418
        } && a.AttributeClass.Name == name);
×
419
    }
420

421
    private static bool HasDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
422
    {
423
        return FindDataAnnotationAttribute(attributes, name) != null;
×
424
    }
425

426
    private static AttributeData? FindSchemaAttribute(ImmutableArray<AttributeData> attributes, string name)
427
    {
428
        return attributes.FirstOrDefault(a =>
×
429
            a.AttributeClass is
×
430
            {
×
431
                ContainingNamespace:
×
432
                {
×
433
                    Name: "Schema",
×
434
                    ContainingNamespace:
×
435
                    {
×
436
                        Name: "DataAnnotations",
×
437
                        ContainingNamespace:
×
438
                        {
×
439
                            Name: "ComponentModel",
×
440
                            ContainingNamespace.Name: "System"
×
441
                        }
×
442
                    }
×
443
                }
×
444
            }
×
445
            && a.AttributeClass.Name == name
×
446
        );
×
447
    }
448

449
    private static string? GetSchemaAttributeConstructorStringArg(ImmutableArray<AttributeData> attributes, string name)
450
    {
451
        var attribute = FindSchemaAttribute(attributes, name);
×
452
        if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string value)
×
453
            return value;
×
454

455
        return null;
×
456
    }
457

458
    private static bool GetIsDatabaseGenerated(ImmutableArray<AttributeData> attributes)
459
    {
460
        var attribute = FindSchemaAttribute(attributes, "DatabaseGeneratedAttribute");
×
461
        if (attribute == null)
×
462
            return false;
×
463

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

467
        return false;
×
468
    }
469

470
    private static string? GetNamedString(AttributeData? attribute, string argName)
471
    {
472
        if (attribute == null)
×
473
            return null;
×
474

475
        foreach (var namedArg in attribute.NamedArguments)
×
476
        {
477
            if (namedArg.Key == argName && namedArg.Value.Value is string value)
×
478
                return value;
×
479
        }
480

481
        return null;
×
482
    }
483

484
    private static int? GetNamedNumber(AttributeData? attribute, string argName)
485
    {
486
        if (attribute == null)
×
487
            return null;
×
488

489
        foreach (var namedArg in attribute.NamedArguments)
×
490
        {
491
            if (namedArg.Key == argName && namedArg.Value.Value is int value)
×
492
                return value;
×
493
        }
494

495
        return null;
×
496
    }
497

498
    private static bool IsIncluded(IPropertySymbol propertySymbol)
499
    {
500
        return !propertySymbol.IsIndexer
×
501
            && !propertySymbol.IsAbstract
×
502
            && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
503
    }
504

505
    private static bool IsSupportedType(ITypeSymbol type)
506
    {
507
        // handle nullable value types
508
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
×
509
            return IsSupportedType(namedType.TypeArguments[0]);
×
510

511
        // enums are stored as their underlying integer type
512
        if (type.TypeKind == TypeKind.Enum)
×
513
            return true;
×
514

515
        // primitives and string
516
        switch (type.SpecialType)
×
517
        {
518
            case SpecialType.System_Boolean:
519
            case SpecialType.System_Byte:
520
            case SpecialType.System_Char:
521
            case SpecialType.System_Decimal:
522
            case SpecialType.System_Double:
523
            case SpecialType.System_Single:
524
            case SpecialType.System_Int16:
525
            case SpecialType.System_Int32:
526
            case SpecialType.System_Int64:
527
            case SpecialType.System_String:
528
                return true;
×
529
        }
530

531
        // byte[]
532
        if (type is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte })
×
533
            return true;
×
534

535
        // well-known struct types and FluentCommand.ConcurrencyToken
536
        var fullName = type.ToDisplayString();
×
537
        return fullName is
×
538
            "System.DateTime" or
×
539
            "System.DateTimeOffset" or
×
540
            "System.Guid" or
×
541
            "System.TimeSpan" or
×
542
            "System.DateOnly" or
×
543
            "System.TimeOnly" or
×
544
            "FluentCommand.ConcurrencyToken";
×
545
    }
546

547
    private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
548
    {
549
        return attributes.Any(
×
550
            a => a.AttributeClass is
×
551
            {
×
552
                Name: "NotMappedAttribute",
×
553
                ContainingNamespace:
×
554
                {
×
555
                    Name: "Schema",
×
556
                    ContainingNamespace:
×
557
                    {
×
558
                        Name: "DataAnnotations",
×
559
                        ContainingNamespace:
×
560
                        {
×
561
                            Name: "ComponentModel",
×
562
                            ContainingNamespace.Name: "System"
×
563
                        }
×
564
                    }
×
565
                }
×
566
            });
×
567
    }
568

569
    private static bool HasIgnorePropertyAttribute(ImmutableArray<AttributeData> attributes)
570
    {
571
        return attributes.Any(a => a.AttributeClass is
×
572
        {
×
573
            Name: "IgnorePropertyAttribute",
×
574
            ContainingNamespace:
×
575
            {
×
576
                Name: "Attributes",
×
577
                ContainingNamespace.Name: "FluentCommand"
×
578
            }
×
579
        });
×
580
    }
581

582
    private static bool IsMappableProperty(IPropertySymbol propertySymbol, HashSet<string> classIgnored)
583
    {
584
        var attributes = propertySymbol.GetAttributes();
×
585
        if (classIgnored.Contains(propertySymbol.Name) || HasIgnorePropertyAttribute(attributes) || IsNotMapped(attributes))
×
586
            return false;
×
587

588
        return GetJsonColumn(attributes) != null || IsSupportedType(propertySymbol.Type);
×
589
    }
590

591
    private static HashSet<string> GetClassIgnoredProperties(ImmutableArray<AttributeData> attributes)
592
    {
593
        var ignored = new HashSet<string>(StringComparer.Ordinal);
×
594

595
        foreach (var attr in attributes)
×
596
        {
597
            if (attr.AttributeClass is not
×
598
                {
×
599
                    Name: "IgnorePropertyAttribute",
×
600
                    ContainingNamespace:
×
601
                    {
×
602
                        Name: "Attributes",
×
603
                        ContainingNamespace.Name: "FluentCommand"
×
604
                    }
×
605
                })
×
606
            {
607
                continue;
608
            }
609

610
            // constructor argument: [IgnoreProperty("Name")] or [IgnoreProperty(nameof(T.Name))]
611
            if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string ctorName)
×
612
            {
613
                ignored.Add(ctorName);
×
614
                continue;
×
615
            }
616

617
            // named argument: [IgnoreProperty(PropertyName = "Name")]
618
            foreach (var namedArg in attr.NamedArguments)
×
619
            {
620
                if (namedArg.Key == "PropertyName" && namedArg.Value.Value is string namedValue)
×
621
                    ignored.Add(namedValue);
×
622
            }
623
        }
624

625
        return ignored;
×
626
    }
627
}
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