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

loresoft / FluentCommand / 26594986582

28 May 2026 06:28PM UTC coverage: 55.553%. First build
26594986582

push

github

pwelter34
Move JSON support, add docs and examples

1358 of 3215 branches covered (42.24%)

Branch coverage included in aggregate %.

103 of 234 new or added lines in 9 files covered. (44.02%)

4389 of 7130 relevant lines covered (61.56%)

312.88 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

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

160
        // constructor initialization
161

162
        // constructor with same number of parameters as mappable properties
163
        var mappableCount = propertySymbols
×
NEW
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

NEW
185
        return new EntityClass
×
NEW
186
        {
×
NEW
187
            InitializationMode = mode,
×
NEW
188
            FullyQualified = fullyQualified,
×
NEW
189
            EntityNamespace = classNamespace,
×
NEW
190
            EntityName = className,
×
NEW
191
            Properties = properties,
×
NEW
192
            TableName = tableName,
×
NEW
193
            TableSchema = tableSchema
×
NEW
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();
×
NEW
230
        var jsonColumn = GetJsonColumn(attributes);
×
NEW
231
        var isJsonColumn = jsonColumn != null;
×
NEW
232
        var isNotMapped = (classIgnored?.Contains(propertyName) == true) || (!isJsonColumn && !IsSupportedType(propertySymbol.Type));
×
233

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

250
        var columnName = GetColumnName(attributes) ?? propertyName;
×
251
        var converterName = GetConverterName(attributes);
×
252

253
        var isKey = HasDataAnnotationAttribute(attributes, "KeyAttribute");
×
254

255
        isNotMapped = isNotMapped
×
256
            || IsNotMapped(attributes)
×
257
            || HasIgnorePropertyAttribute(attributes);
×
258

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

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

298
    private static AttributeData? GetJsonColumn(ImmutableArray<AttributeData> attributes)
299
    {
NEW
300
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
NEW
301
        {
×
NEW
302
            Name: "JsonColumnAttribute",
×
NEW
303
            ContainingNamespace:
×
NEW
304
            {
×
NEW
305
                Name: "Attributes",
×
NEW
306
                ContainingNamespace.Name: "FluentCommand"
×
NEW
307
            }
×
NEW
308
        });
×
309
    }
310

311
    private static string? GetJsonOptionsProviderName(AttributeData? attribute)
312
    {
NEW
313
        if (attribute?.ConstructorArguments.Length == 1 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol providerSymbol)
×
NEW
314
            return providerSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
315

NEW
316
        return null;
×
317
    }
318

319
    private static string? GetJsonContextName(AttributeData? attribute)
320
    {
NEW
321
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[0].Value is INamedTypeSymbol contextSymbol)
×
NEW
322
            return contextSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
323

NEW
324
        return null;
×
325
    }
326

327
    private static string? GetJsonTypeInfoPropertyName(AttributeData? attribute)
328
    {
NEW
329
        if (attribute?.ConstructorArguments.Length == 2 && attribute.ConstructorArguments[1].Value is string propertyName)
×
NEW
330
            return propertyName;
×
331

NEW
332
        return null;
×
333
    }
334

335
    private static string? GetColumnName(ImmutableArray<AttributeData> attributes)
336
    {
337
        var columnAttribute = FindSchemaAttribute(attributes, "ColumnAttribute");
×
338

339
        if (columnAttribute == null)
×
340
            return null;
×
341

342
        // attribute constructor [Column("Name")]
343
        var converterType = columnAttribute.ConstructorArguments.FirstOrDefault();
×
344
        if (converterType.Value is string stringValue)
×
345
            return stringValue;
×
346

347
        return null;
×
348
    }
349

350

351
    private static string? GetConverterName(ImmutableArray<AttributeData> attributes)
352
    {
353
        var converter = attributes
×
354
            .FirstOrDefault(a => a.AttributeClass is
×
355
            {
×
356
                Name: "DataFieldConverterAttribute",
×
357
                ContainingNamespace.Name: "FluentCommand"
×
358
            });
×
359

360
        if (converter == null)
×
361
            return null;
×
362

363
        // attribute constructor
364
        var converterType = converter.ConstructorArguments.FirstOrDefault();
×
365
        if (converterType.Value is INamedTypeSymbol converterSymbol)
×
366
            return converterSymbol.ToDisplayString();
×
367

368
        // generic attribute
369
        var attributeClass = converter.AttributeClass;
×
370
        if (attributeClass is { IsGenericType: true }
×
371
            && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length
×
372
            && attributeClass.TypeArguments.Length == 1)
×
373
        {
374
            var typeArgument = attributeClass.TypeArguments[0];
×
375
            return typeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
×
376
        }
377

378
        return null;
×
379
    }
380

381
    private static AttributeData? FindDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
382
    {
383
        return attributes.FirstOrDefault(a => a.AttributeClass is
×
384
        {
×
385
            ContainingNamespace:
×
386
            {
×
387
                Name: "DataAnnotations",
×
388
                ContainingNamespace:
×
389
                {
×
390
                    Name: "ComponentModel",
×
391
                    ContainingNamespace.Name: "System"
×
392
                }
×
393
            }
×
394
        } && a.AttributeClass.Name == name);
×
395
    }
396

397
    private static bool HasDataAnnotationAttribute(ImmutableArray<AttributeData> attributes, string name)
398
    {
399
        return FindDataAnnotationAttribute(attributes, name) != null;
×
400
    }
401

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

425
    private static string? GetSchemaAttributeConstructorStringArg(ImmutableArray<AttributeData> attributes, string name)
426
    {
427
        var attribute = FindSchemaAttribute(attributes, name);
×
428
        if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string value)
×
429
            return value;
×
430

431
        return null;
×
432
    }
433

434
    private static bool GetIsDatabaseGenerated(ImmutableArray<AttributeData> attributes)
435
    {
436
        var attribute = FindSchemaAttribute(attributes, "DatabaseGeneratedAttribute");
×
437
        if (attribute == null)
×
438
            return false;
×
439

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

443
        return false;
×
444
    }
445

446
    private static string? GetNamedString(AttributeData? attribute, string argName)
447
    {
448
        if (attribute == null)
×
449
            return null;
×
450

451
        foreach (var namedArg in attribute.NamedArguments)
×
452
        {
453
            if (namedArg.Key == argName && namedArg.Value.Value is string value)
×
454
                return value;
×
455
        }
456

457
        return null;
×
458
    }
459

460
    private static int? GetNamedNumber(AttributeData? attribute, string argName)
461
    {
462
        if (attribute == null)
×
463
            return null;
×
464

465
        foreach (var namedArg in attribute.NamedArguments)
×
466
        {
467
            if (namedArg.Key == argName && namedArg.Value.Value is int value)
×
468
                return value;
×
469
        }
470

471
        return null;
×
472
    }
473

474
    private static bool IsIncluded(IPropertySymbol propertySymbol)
475
    {
476
        return !propertySymbol.IsIndexer
×
477
            && !propertySymbol.IsAbstract
×
478
            && propertySymbol.DeclaredAccessibility == Accessibility.Public;
×
479
    }
480

481
    private static bool IsSupportedType(ITypeSymbol type)
482
    {
483
        // handle nullable value types
484
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
×
485
            return IsSupportedType(namedType.TypeArguments[0]);
×
486

487
        // enums are stored as their underlying integer type
488
        if (type.TypeKind == TypeKind.Enum)
×
489
            return true;
×
490

491
        // primitives and string
492
        switch (type.SpecialType)
×
493
        {
494
            case SpecialType.System_Boolean:
495
            case SpecialType.System_Byte:
496
            case SpecialType.System_Char:
497
            case SpecialType.System_Decimal:
498
            case SpecialType.System_Double:
499
            case SpecialType.System_Single:
500
            case SpecialType.System_Int16:
501
            case SpecialType.System_Int32:
502
            case SpecialType.System_Int64:
503
            case SpecialType.System_String:
504
                return true;
×
505
        }
506

507
        // byte[]
508
        if (type is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte })
×
509
            return true;
×
510

511
        // well-known struct types and FluentCommand.ConcurrencyToken
512
        var fullName = type.ToDisplayString();
×
513
        return fullName is
×
514
            "System.DateTime" or
×
515
            "System.DateTimeOffset" or
×
516
            "System.Guid" or
×
517
            "System.TimeSpan" or
×
518
            "System.DateOnly" or
×
519
            "System.TimeOnly" or
×
520
            "FluentCommand.ConcurrencyToken";
×
521
    }
522

523
    private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
524
    {
525
        return attributes.Any(
×
526
            a => a.AttributeClass is
×
527
            {
×
528
                Name: "NotMappedAttribute",
×
529
                ContainingNamespace:
×
530
                {
×
531
                    Name: "Schema",
×
532
                    ContainingNamespace:
×
533
                    {
×
534
                        Name: "DataAnnotations",
×
535
                        ContainingNamespace:
×
536
                        {
×
537
                            Name: "ComponentModel",
×
538
                            ContainingNamespace.Name: "System"
×
539
                        }
×
540
                    }
×
541
                }
×
542
            });
×
543
    }
544

545
    private static bool HasIgnorePropertyAttribute(ImmutableArray<AttributeData> attributes)
546
    {
547
        return attributes.Any(a => a.AttributeClass is
×
548
        {
×
549
            Name: "IgnorePropertyAttribute",
×
550
            ContainingNamespace:
×
551
            {
×
552
                Name: "Attributes",
×
553
                ContainingNamespace.Name: "FluentCommand"
×
554
            }
×
555
        });
×
556
    }
557

558
    private static bool IsMappableProperty(IPropertySymbol propertySymbol, HashSet<string> classIgnored)
559
    {
NEW
560
        var attributes = propertySymbol.GetAttributes();
×
NEW
561
        if (classIgnored.Contains(propertySymbol.Name) || HasIgnorePropertyAttribute(attributes) || IsNotMapped(attributes))
×
NEW
562
            return false;
×
563

NEW
564
        return GetJsonColumn(attributes) != null || IsSupportedType(propertySymbol.Type);
×
565
    }
566

567
    private static HashSet<string> GetClassIgnoredProperties(ImmutableArray<AttributeData> attributes)
568
    {
569
        var ignored = new HashSet<string>(StringComparer.Ordinal);
×
570

571
        foreach (var attr in attributes)
×
572
        {
573
            if (attr.AttributeClass is not
×
574
                {
×
575
                    Name: "IgnorePropertyAttribute",
×
576
                    ContainingNamespace:
×
577
                    {
×
578
                        Name: "Attributes",
×
579
                        ContainingNamespace.Name: "FluentCommand"
×
580
                    }
×
581
                })
×
582
            {
583
                continue;
584
            }
585

586
            // constructor argument: [IgnoreProperty("Name")] or [IgnoreProperty(nameof(T.Name))]
587
            if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string ctorName)
×
588
            {
589
                ignored.Add(ctorName);
×
590
                continue;
×
591
            }
592

593
            // named argument: [IgnoreProperty(PropertyName = "Name")]
594
            foreach (var namedArg in attr.NamedArguments)
×
595
            {
596
                if (namedArg.Key == "PropertyName" && namedArg.Value.Value is string namedValue)
×
597
                    ignored.Add(namedValue);
×
598
            }
599
        }
600

601
        return ignored;
×
602
    }
603
}
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