Skip to content

Commit 9ff41df

Browse files
committed
Support IgnoreProperty and generator improvements
1 parent f6726f9 commit 9ff41df

19 files changed

Lines changed: 368 additions & 99 deletions

src/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<DefaultLanguage>en-US</DefaultLanguage>
2626
<LangVersion>latest</LangVersion>
2727
<ImplicitUsings>enable</ImplicitUsings>
28+
<Nullable>enable</Nullable>
2829
<NoWarn>1591</NoWarn>
2930
</PropertyGroup>
3031
<PropertyGroup>

src/FluentCommand.Caching/FluentCommand.Caching.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>
4-
<ImplicitUsings>enable</ImplicitUsings>
5-
<Nullable>enable</Nullable>
64
<Description>Fluent Wrapper for DbCommand with distributed caching support using Microsoft.Extensions.Caching and MessagePack serialization</Description>
75
</PropertyGroup>
86
<ItemGroup>

src/FluentCommand.Csv/FluentCommand.Csv.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
44
<RootNamespace>FluentCommand</RootNamespace>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
75
<Description>Fluent Wrapper for DbCommand with CSV export support for query results</Description>
86
</PropertyGroup>
97
<ItemGroup>

src/FluentCommand.Generators/DataReaderFactoryGenerator.cs

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ protected static void WriteTypeAccessorSource(SourceProductionContext context, E
5959
? InitializationMode.ObjectInitializer
6060
: InitializationMode.Constructor;
6161

62+
var classIgnored = GetClassIgnoredProperties(typeAttributes);
6263
var propertySymbols = GetProperties(targetSymbol);
6364

6465
if (mode == InitializationMode.ObjectInitializer)
6566
{
6667
var propertyArray = propertySymbols
67-
.Select(p => CreateProperty(p))
68+
.Select(p => CreateProperty(p, classIgnored: classIgnored))
6869
.ToArray();
6970

7071
return new EntityClass(
@@ -80,8 +81,11 @@ protected static void WriteTypeAccessorSource(SourceProductionContext context, E
8081

8182
// constructor initialization
8283

83-
// constructor with same number of parameters as properties
84-
var constructor = targetSymbol.Constructors.FirstOrDefault(c => c.Parameters.Length == propertySymbols.Count);
84+
// constructor with same number of parameters as mappable properties
85+
var mappableCount = propertySymbols
86+
.Count(p => !classIgnored.Contains(p.Name) && !HasIgnorePropertyAttribute(p.GetAttributes()));
87+
88+
var constructor = targetSymbol.Constructors.FirstOrDefault(c => c.Parameters.Length == mappableCount);
8589
if (constructor == null)
8690
return null;
8791

@@ -96,7 +100,7 @@ protected static void WriteTypeAccessorSource(SourceProductionContext context, E
96100
if (parameter == null)
97101
continue;
98102

99-
var property = CreateProperty(propertySymbol, parameter.Name);
103+
var property = CreateProperty(propertySymbol, parameter.Name, classIgnored: classIgnored);
100104
properties.Add(property);
101105
}
102106

@@ -133,16 +137,17 @@ protected static List<IPropertySymbol> GetProperties(INamedTypeSymbol targetSymb
133137
currentSymbol = currentSymbol.BaseType;
134138
}
135139

136-
return properties.Values.ToList();
140+
return [.. properties.Values];
137141
}
138142

139-
protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string? parameterName = null)
143+
protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, string? parameterName = null, HashSet<string>? classIgnored = null)
140144
{
141145
var propertyType = propertySymbol.Type.ToDisplayString();
142146
var memberTypeName = propertySymbol.Type.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToDisplayString();
143147
var propertyName = propertySymbol.Name;
144148
var hasGetter = propertySymbol.GetMethod != null;
145-
var hasSetter = propertySymbol.SetMethod != null && !propertySymbol.SetMethod.IsInitOnly;
149+
var hasSetter = propertySymbol.SetMethod?.IsInitOnly == false;
150+
var isNotMapped = (classIgnored?.Contains(propertyName) == true) || !IsSupportedType(propertySymbol.Type);
146151

147152
var attributes = propertySymbol.GetAttributes();
148153
if (attributes == default || attributes.Length == 0)
@@ -153,6 +158,7 @@ protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, s
153158
PropertyType: propertyType,
154159
MemberTypeName: memberTypeName,
155160
ParameterName: parameterName,
161+
IsNotMapped: isNotMapped,
156162
HasGetter: hasGetter,
157163
HasSetter: hasSetter
158164
);
@@ -162,7 +168,11 @@ protected static EntityProperty CreateProperty(IPropertySymbol propertySymbol, s
162168
var converterName = GetConverterName(attributes);
163169

164170
var isKey = HasDataAnnotationAttribute(attributes, "KeyAttribute");
165-
var isNotMapped = IsNotMapped(attributes);
171+
172+
isNotMapped = isNotMapped
173+
|| IsNotMapped(attributes)
174+
|| HasIgnorePropertyAttribute(attributes);
175+
166176
var isDatabaseGenerated = GetIsDatabaseGenerated(attributes);
167177
var isConcurrencyCheck = HasDataAnnotationAttribute(attributes, "ConcurrencyCheckAttribute");
168178
var foreignKey = GetSchemaAttributeConstructorStringArg(attributes, "ForeignKeyAttribute");
@@ -335,7 +345,51 @@ private static bool GetIsDatabaseGenerated(ImmutableArray<AttributeData> attribu
335345

336346
protected static bool IsIncluded(IPropertySymbol propertySymbol)
337347
{
338-
return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract && propertySymbol.DeclaredAccessibility == Accessibility.Public;
348+
return !propertySymbol.IsIndexer
349+
&& !propertySymbol.IsAbstract
350+
&& propertySymbol.DeclaredAccessibility == Accessibility.Public;
351+
}
352+
353+
private static bool IsSupportedType(ITypeSymbol type)
354+
{
355+
// handle nullable value types
356+
if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedType)
357+
return IsSupportedType(namedType.TypeArguments[0]);
358+
359+
// enums are stored as their underlying integer type
360+
if (type.TypeKind == TypeKind.Enum)
361+
return true;
362+
363+
// primitives and string
364+
switch (type.SpecialType)
365+
{
366+
case SpecialType.System_Boolean:
367+
case SpecialType.System_Byte:
368+
case SpecialType.System_Char:
369+
case SpecialType.System_Decimal:
370+
case SpecialType.System_Double:
371+
case SpecialType.System_Single:
372+
case SpecialType.System_Int16:
373+
case SpecialType.System_Int32:
374+
case SpecialType.System_Int64:
375+
case SpecialType.System_String:
376+
return true;
377+
}
378+
379+
// byte[]
380+
if (type is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte })
381+
return true;
382+
383+
// well-known struct types and FluentCommand.ConcurrencyToken
384+
var fullName = type.ToDisplayString();
385+
return fullName is
386+
"System.DateTime" or
387+
"System.DateTimeOffset" or
388+
"System.Guid" or
389+
"System.TimeSpan" or
390+
"System.DateOnly" or
391+
"System.TimeOnly" or
392+
"FluentCommand.ConcurrencyToken";
339393
}
340394

341395
private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
@@ -359,4 +413,54 @@ private static bool IsNotMapped(ImmutableArray<AttributeData> attributes)
359413
}
360414
});
361415
}
416+
417+
private static bool HasIgnorePropertyAttribute(ImmutableArray<AttributeData> attributes)
418+
{
419+
return attributes.Any(a => a.AttributeClass is
420+
{
421+
Name: "IgnorePropertyAttribute",
422+
ContainingNamespace:
423+
{
424+
Name: "Attributes",
425+
ContainingNamespace.Name: "FluentCommand"
426+
}
427+
});
428+
}
429+
430+
private static HashSet<string> GetClassIgnoredProperties(ImmutableArray<AttributeData> attributes)
431+
{
432+
var ignored = new HashSet<string>(StringComparer.Ordinal);
433+
434+
foreach (var attr in attributes)
435+
{
436+
if (attr.AttributeClass is not
437+
{
438+
Name: "IgnorePropertyAttribute",
439+
ContainingNamespace:
440+
{
441+
Name: "Attributes",
442+
ContainingNamespace.Name: "FluentCommand"
443+
}
444+
})
445+
{
446+
continue;
447+
}
448+
449+
// constructor argument: [IgnoreProperty("Name")] or [IgnoreProperty(nameof(T.Name))]
450+
if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string ctorName)
451+
{
452+
ignored.Add(ctorName);
453+
continue;
454+
}
455+
456+
// named argument: [IgnoreProperty(PropertyName = "Name")]
457+
foreach (var namedArg in attr.NamedArguments)
458+
{
459+
if (namedArg.Key == "PropertyName" && namedArg.Value.Value is string namedValue)
460+
ignored.Add(namedValue);
461+
}
462+
}
463+
464+
return ignored;
465+
}
362466
}

src/FluentCommand.Generators/DataReaderFactoryWriter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text;
2+
13
using FluentCommand.Generators.Models;
24

35
namespace FluentCommand.Generators;

src/FluentCommand.Generators/FluentCommand.Generators.csproj

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>netstandard2.0</TargetFramework>
4-
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
54
<IsRoslynComponent>true</IsRoslynComponent>
6-
<NoPackageAnalysis>true</NoPackageAnalysis>
7-
<IncludeBuildOutput>false</IncludeBuildOutput>
8-
<DevelopmentDependency>true</DevelopmentDependency>
95
<IsPackable>false</IsPackable>
106
<AnalyzerLanguage>cs</AnalyzerLanguage>
11-
<AnalyzerRoslynVersion>4.4</AnalyzerRoslynVersion>
127
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
13-
<Nullable>enable</Nullable>
14-
<Description>Source generators for FluentCommand that create DataReader factory methods and extension methods for entity classes</Description>
8+
<GenerateDocumentationFile>false</GenerateDocumentationFile>
159
</PropertyGroup>
1610
<ItemGroup>
1711
<!-- https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support -->
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma warning disable IDE0130 // Namespace does not match folder structure
2+
3+
using System.ComponentModel;
4+
5+
namespace System.Runtime.CompilerServices;
6+
7+
[EditorBrowsable(EditorBrowsableState.Never)]
8+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
9+
internal sealed class CollectionBuilderAttribute(Type builderType, string methodName) : Attribute
10+
{
11+
public Type BuilderType { get; } = builderType;
12+
13+
public string MethodName { get; } = methodName;
14+
}

0 commit comments

Comments
 (0)