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

loresoft / FluentCommand / 26618517770

29 May 2026 04:45AM UTC coverage: 55.778% (+0.03%) from 55.746%
26618517770

push

github

pwelter34
Handle nullable props and required JSON readers

1390 of 3261 branches covered (42.62%)

Branch coverage included in aggregate %.

47 of 54 new or added lines in 4 files covered. (87.04%)

1 existing line in 1 file now uncovered.

4455 of 7218 relevant lines covered (61.72%)

310.18 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;
×
232
        var enumInfo = GetEnumInfo(propertySymbol.Type);
×
NEW
233
        var isNullable = IsNullableType(propertySymbol.Type);
×
UNCOV
234
        var isNotMapped = (classIgnored?.Contains(propertyName) == true) || (!isJsonColumn && !IsSupportedType(propertySymbol.Type));
×
235

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

256
        var columnName = GetColumnName(attributes) ?? propertyName;
×
257
        var converterName = GetConverterName(attributes);
×
258

259
        var isKey = HasDataAnnotationAttribute(attributes, "KeyAttribute");
×
260

261
        isNotMapped = isNotMapped
×
262
            || IsNotMapped(attributes)
×
263
            || HasIgnorePropertyAttribute(attributes);
×
264

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

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

308
    private static (bool IsEnum, bool IsNullableEnum, string? UnderlyingType) GetEnumInfo(ITypeSymbol type)
309
    {
310
        var isNullableEnum = false;
×
311
        var enumType = type;
×
312

313
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
×
314
        {
315
            isNullableEnum = true;
×
316
            enumType = namedType.TypeArguments[0];
×
317
        }
318

319
        if (enumType is not INamedTypeSymbol { TypeKind: TypeKind.Enum } namedEnum)
×
320
            return (false, false, null);
×
321

322
        return (true, isNullableEnum, namedEnum.EnumUnderlyingType?.ToDisplayString());
×
323
    }
324

325
    private static bool IsNullableType(ITypeSymbol type)
326
    {
NEW
327
        return type.NullableAnnotation == NullableAnnotation.Annotated
×
NEW
328
            || type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T };
×
329
    }
330

331
    private static AttributeData? GetJsonColumn(ImmutableArray<AttributeData> attributes)
332
    {
333
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
334
        {
×
335
            Name: "JsonColumnAttribute",
×
336
            ContainingNamespace:
×
337
            {
×
338
                Name: "Attributes",
×
339
                ContainingNamespace.Name: "FluentCommand"
×
340
            }
×
341
        });
×
342
    }
343

344
    private static string? GetJsonOptionsProviderName(AttributeData? attribute)
345
    {
346
        if (attribute?.ConstructorArguments.Length == 1 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol providerSymbol)
×
347
            return providerSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
348

349
        return null;
×
350
    }
351

352
    private static string? GetJsonContextName(AttributeData? attribute)
353
    {
354
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol contextSymbol)
×
355
            return contextSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
356

357
        return null;
×
358
    }
359

360
    private static string? GetJsonTypeInfoPropertyName(AttributeData? attribute)
361
    {
362
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[1].Value is string propertyName)
×
363
            return propertyName;
×
364

365
        return null;
×
366
    }
367

368
    private static string? GetColumnName(ImmutableArray<AttributeData> attributes)
369
    {
370
        var columnAttribute = FindSchemaAttribute(attributes, "ColumnAttribute");
×
371

372
        if (columnAttribute == null)
×
373
            return null;
×
374

375
        // attribute constructor [Column("Name")]
376
        var converterType = columnAttribute.ConstructorArguments.FirstOrDefault();
×
377
        if (converterType.Value is string stringValue)
×
378
            return stringValue;
×
379

380
        return null;
×
381
    }
382

383

384
    private static string? GetConverterName(ImmutableArray<AttributeData> attributes)
385
    {
386
        var converter = attributes
×
387
            .FirstOrDefault(a => a.AttributeClass is
×
388
            {
×
389
                Name: "DataFieldConverterAttribute",
×
390
                ContainingNamespace.Name: "FluentCommand"
×
391
            });
×
392

393
        if (converter == null)
×
394
            return null;
×
395

396
        // attribute constructor
397
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
398
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
399
            return converterSymbol.ToDisplayString();
×
400

401
        // generic attribute
402
        var attributeClass = converter.AttributeClass;
×
403
        if (attributeClass is { IsGenericType: true }
×
404
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
405
            && attributeClass.TypeArguments.Length == 1)
×
406
        {
407
            var typeArgument = attributeClass.TypeArguments[0];
×
408
            return typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
409
        }
410

411
        return null;
×
412
    }
413

414
    private static AttributeData? FindDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
415
    {
416
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
417
        {
×
418
            ContainingNamespace:
×
419
            {
×
420
                Name: "DataAnnotations",
×
421
                ContainingNamespace:
×
422
                {
×
423
                    Name: "ComponentModel",
×
424
                    ContainingNamespace.Name: "System"
×
425
                }
×
426
            }
×
427
        } && a.AttributeClass.Name == name);
×
428
    }
429

430
    private static bool HasDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
431
    {
432
        return FindDataAnnotationAttribute(attributes, name) != null;
×
433
    }
434

435
    private static AttributeData? FindSchemaAttribute(ImmutableArray<AttributeData> attributes, string name)
436
    {
437
        return attributes.FirstOrDefault(a =>
×
438
            a.AttributeClass is
×
439
            {
×
440
                ContainingNamespace:
×
441
                {
×
442
                    Name: "Schema",
×
443
                    ContainingNamespace:
×
444
                    {
×
445
                        Name: "DataAnnotations",
×
446
                        ContainingNamespace:
×
447
                        {
×
448
                            Name: "ComponentModel",
×
449
                            ContainingNamespace.Name: "System"
×
450
                        }
×
451
                    }
×
452
                }
×
453
            }
×
454
            && a.AttributeClass.Name == name
×
455
        );
×
456
    }
457

458
    private static string? GetSchemaAttributeConstructorStringArg(ImmutableArray<AttributeData> attributes, string name)
459
    {
460
        var attribute = FindSchemaAttribute(attributes, name);
×
461
        if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string value)
×
462
            return value;
×
463

464
        return null;
×
465
    }
466

467
    private static bool GetIsDatabaseGenerated(ImmutableArray<AttributeData> attributes)
468
    {
469
        var attribute = FindSchemaAttribute(attributes, "DatabaseGeneratedAttribute");
×
470
        if (attribute == null)
×
471
            return false;
×
472

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

476
        return false;
×
477
    }
478

479
    private static string? GetNamedString(AttributeData? attribute, string argName)
480
    {
481
        if (attribute == null)
×
482
            return null;
×
483

484
        foreach (var namedArg in attribute.NamedArguments)
×
485
        {
486
            if (namedArg.Key == argName && namedArg.Value.Value is string value)
×
487
                return value;
×
488
        }
489

490
        return null;
×
491
    }
492

493
    private static int? GetNamedNumber(AttributeData? attribute, string argName)
494
    {
495
        if (attribute == null)
×
496
            return null;
×
497

498
        foreach (var namedArg in attribute.NamedArguments)
×
499
        {
500
            if (namedArg.Key == argName && namedArg.Value.Value is int value)
×
501
                return value;
×
502
        }
503

504
        return null;
×
505
    }
506

507
    private static bool IsIncluded(IPropertySymbol propertySymbol)
508
    {
509
        return !propertySymbol.IsIndexer
×
510
            && !propertySymbol.IsAbstract
×
511
            && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
512
    }
513

514
    private static bool IsSupportedType(ITypeSymbol type)
515
    {
516
        // handle nullable value types
517
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
×
518
            return IsSupportedType(namedType.TypeArguments[0]);
×
519

520
        // enums are stored as their underlying integer type
521
        if (type.TypeKind == TypeKind.Enum)
×
522
            return true;
×
523

524
        // primitives and string
525
        switch (type.SpecialType)
×
526
        {
527
            case SpecialType.System_Boolean:
528
            case SpecialType.System_Byte:
529
            case SpecialType.System_Char:
530
            case SpecialType.System_Decimal:
531
            case SpecialType.System_Double:
532
            case SpecialType.System_Single:
533
            case SpecialType.System_Int16:
534
            case SpecialType.System_Int32:
535
            case SpecialType.System_Int64:
536
            case SpecialType.System_String:
537
                return true;
×
538
        }
539

540
        // byte[]
541
        if (type is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte })
×
542
            return true;
×
543

544
        // well-known struct types and FluentCommand.ConcurrencyToken
545
        var fullName = type.ToDisplayString();
×
546
        return fullName is
×
547
            "System.DateTime" or
×
548
            "System.DateTimeOffset" or
×
549
            "System.Guid" or
×
550
            "System.TimeSpan" or
×
551
            "System.DateOnly" or
×
552
            "System.TimeOnly" or
×
553
            "FluentCommand.ConcurrencyToken";
×
554
    }
555

556
    private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
557
    {
558
        return attributes.Any(
×
559
            a => a.AttributeClass is
×
560
            {
×
561
                Name: "NotMappedAttribute",
×
562
                ContainingNamespace:
×
563
                {
×
564
                    Name: "Schema",
×
565
                    ContainingNamespace:
×
566
                    {
×
567
                        Name: "DataAnnotations",
×
568
                        ContainingNamespace:
×
569
                        {
×
570
                            Name: "ComponentModel",
×
571
                            ContainingNamespace.Name: "System"
×
572
                        }
×
573
                    }
×
574
                }
×
575
            });
×
576
    }
577

578
    private static bool HasIgnorePropertyAttribute(ImmutableArray<AttributeData> attributes)
579
    {
580
        return attributes.Any(a => a.AttributeClass is
×
581
        {
×
582
            Name: "IgnorePropertyAttribute",
×
583
            ContainingNamespace:
×
584
            {
×
585
                Name: "Attributes",
×
586
                ContainingNamespace.Name: "FluentCommand"
×
587
            }
×
588
        });
×
589
    }
590

591
    private static bool IsMappableProperty(IPropertySymbol propertySymbol, HashSet<string> classIgnored)
592
    {
593
        var attributes = propertySymbol.GetAttributes();
×
594
        if (classIgnored.Contains(propertySymbol.Name) || HasIgnorePropertyAttribute(attributes) || IsNotMapped(attributes))
×
595
            return false;
×
596

597
        return GetJsonColumn(attributes) != null || IsSupportedType(propertySymbol.Type);
×
598
    }
599

600
    private static HashSet<string> GetClassIgnoredProperties(ImmutableArray<AttributeData> attributes)
601
    {
602
        var ignored = new HashSet<string>(StringComparer.Ordinal);
×
603

604
        foreach (var attr in attributes)
×
605
        {
606
            if (attr.AttributeClass is not
×
607
                {
×
608
                    Name: "IgnorePropertyAttribute",
×
609
                    ContainingNamespace:
×
610
                    {
×
611
                        Name: "Attributes",
×
612
                        ContainingNamespace.Name: "FluentCommand"
×
613
                    }
×
614
                })
×
615
            {
616
                continue;
617
            }
618

619
            // constructor argument: [IgnoreProperty("Name")] or [IgnoreProperty(nameof(T.Name))]
620
            if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string ctorName)
×
621
            {
622
                ignored.Add(ctorName);
×
623
                continue;
×
624
            }
625

626
            // named argument: [IgnoreProperty(PropertyName = "Name")]
627
            foreach (var namedArg in attr.NamedArguments)
×
628
            {
629
                if (namedArg.Key == "PropertyName" && namedArg.Value.Value is string namedValue)
×
630
                    ignored.Add(namedValue);
×
631
            }
632
        }
633

634
        return ignored;
×
635
    }
636
}
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