Skip to content

Commit 86374ac

Browse files
authored
feat: Support partial classes (#18)
* improve(SourceGenerator): comments * feat: support partial classes * test: PartialClass * refactor(TestClassCodeGenerator): improve naming * improve(FluentLambdaTestClass): Address class in separate file * fix(M31.FluentApi.Tests.csproj): remove item group * fix(FluentLambdaClassInDifferentNamespace): move Address to separate file * fix(FluentLambdaNullablePropertyClass): move Address into separate file * fix(FluentLambdaSingleStepClass): move address to separate file * fix: address Resharper warnings * test(FluentApiAnalyzer): structs, records, and record structs * test: DuplicateMethodPartialClass * chore: increase nuget version to 1.5.0 * fix: minor improvements * improve(ListAndDictionary): NotSupportedException instead of NotImplementedException * refactor: minor change
1 parent 9d36808 commit 86374ac

File tree

44 files changed

+599
-238
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+599
-238
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ PM> Install-Package M31.FluentApi
2525
A package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentApi`. Therefore, it is recommended to use the `PrivateAssets` metadata tag:
2626

2727
```xml
28-
<PackageReference Include="M31.FluentApi" Version="1.4.0" PrivateAssets="all"/>
28+
<PackageReference Include="M31.FluentApi" Version="1.5.0" PrivateAssets="all"/>
2929
```
3030

3131
If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:

src/M31.FluentApi.Generator/AnalyzerReleases.Shipped.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,13 @@ M31FA018 | M31.Usage | Error | Generic types are not supported
5050
Rule ID | Category | Severity | Notes
5151
--------|----------|----------|-------
5252
M31FA021 | M31.Usage | Error | Reserved method name
53-
M31FA022 | M31.Usage | Error | Fluent lambda member without Fluent API
53+
M31FA022 | M31.Usage | Error | Fluent lambda member without Fluent API
54+
55+
56+
## Release 1.5.0
57+
58+
### Removed Rules
59+
60+
Rule ID | Category | Severity | Notes
61+
--------|----------|----------|-------
62+
M31FA007 | M31.Usage | Error | Partial types are not supported

src/M31.FluentApi.Generator/Commons/SyntaxNodeExtensions.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ namespace M31.FluentApi.Generator.Commons;
55

66
internal static class SyntaxNodeExtensions
77
{
8-
internal static bool IsTypeDeclarationOfInterest(
8+
internal static bool IsClassStructOrRecordSyntax(
99
this SyntaxNode? syntaxNode, out TypeDeclarationSyntax typeDeclaration)
1010
{
11-
if (syntaxNode.IsTypeDeclarationOfInterest())
11+
if (syntaxNode.IsClassStructOrRecordSyntax())
1212
{
1313
typeDeclaration = (TypeDeclarationSyntax)syntaxNode!;
1414
return true;
@@ -18,8 +18,26 @@ internal static bool IsTypeDeclarationOfInterest(
1818
return false;
1919
}
2020

21-
internal static bool IsTypeDeclarationOfInterest(this SyntaxNode? syntaxNode)
21+
internal static bool IsClassStructOrRecordSyntax(this SyntaxNode? syntaxNode)
2222
{
2323
return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax;
2424
}
25+
26+
internal static bool IsFluentApiAttributeSyntax(this AttributeSyntax attributeSyntax)
27+
{
28+
string? name = ExtractName(attributeSyntax.Name);
29+
30+
// Note that we drop alias support for better performance.
31+
return name is "FluentApi" or "FluentApiAttribute";
32+
33+
string? ExtractName(NameSyntax nameSyntax)
34+
{
35+
return nameSyntax switch
36+
{
37+
SimpleNameSyntax simpleNameSyntax => simpleNameSyntax.Identifier.Text, // without namespace
38+
QualifiedNameSyntax qualifiedNameSyntax => qualifiedNameSyntax.Right.Identifier.Text, // fully qualified
39+
_ => null
40+
};
41+
}
42+
}
2543
}

src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1212
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1313
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
14-
<PackageVersion>1.4.0</PackageVersion>
14+
<PackageVersion>1.5.0</PackageVersion>
1515
<Authors>Kevin Schaal</Authors>
1616
<Description>The generator package for M31.FluentAPI. Don't install this package explicitly, install M31.FluentAPI instead.</Description>
1717
<PackageTags>fluentapi fluentbuilder fluentinterface fluentdesign fluent codegeneration</PackageTags>

src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiAnalyzer.cs

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ public override void Initialize(AnalysisContext context)
2020
{
2121
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
2222
context.EnableConcurrentExecution();
23-
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);
23+
context.RegisterSyntaxNodeAction(
24+
AnalyzeNode,
25+
SyntaxKind.ClassDeclaration,
26+
SyntaxKind.StructDeclaration,
27+
SyntaxKind.RecordDeclaration,
28+
SyntaxKind.RecordStructDeclaration);
2429
}
2530

2631
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
@@ -51,21 +56,7 @@ private void AnalyzeNodeInternal(SyntaxNodeAnalysisContext context)
5156
return;
5257
}
5358

54-
ImmutableArray<SyntaxReference> syntaxReferences = symbol.DeclaringSyntaxReferences;
55-
56-
if (syntaxReferences.Length == 0)
57-
{
58-
return;
59-
}
60-
61-
SyntaxNode syntaxNode = syntaxReferences.First().GetSyntax();
62-
63-
if (!syntaxNode.IsTypeDeclarationOfInterest(out TypeDeclarationSyntax typeDeclaration))
64-
{
65-
return;
66-
}
67-
68-
if (ReportErrorDiagnosticForPartialKeyword(context, typeDeclaration, symbol))
59+
if (context.Node is not TypeDeclarationSyntax typeDeclaration)
6960
{
7061
return;
7162
}
@@ -92,23 +83,4 @@ private void AnalyzeNodeInternal(SyntaxNodeAnalysisContext context)
9283
context.ReportDiagnostic(diagnostic);
9384
}
9485
}
95-
96-
private bool ReportErrorDiagnosticForPartialKeyword(
97-
SyntaxNodeAnalysisContext context,
98-
TypeDeclarationSyntax typeDeclaration,
99-
INamedTypeSymbol symbol)
100-
{
101-
SyntaxToken partialKeyword = typeDeclaration.Modifiers.FirstOrDefault(
102-
m => m.IsKind(SyntaxKind.PartialKeyword));
103-
104-
if (partialKeyword != default)
105-
{
106-
context.ReportDiagnostic(UnsupportedPartialType.CreateDiagnostic(
107-
partialKeyword,
108-
symbol.Name));
109-
return true;
110-
}
111-
112-
return false;
113-
}
11486
}

src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiDiagnostics.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal static class FluentApiDiagnostics
1818
DuplicateAttribute.Descriptor,
1919
OrthogonalAttributeMisused.Descriptor,
2020
DuplicateMainAttribute.Descriptor,
21-
UnsupportedPartialType.Descriptor,
2221
InvalidFluentPredicateType.Descriptor,
2322
InvalidFluentNullableType.Descriptor,
2423
FluentNullableTypeWithoutNullableAnnotation.Descriptor,
@@ -144,22 +143,6 @@ internal static Diagnostic CreateDiagnostic(AttributeDataExtended attributeData)
144143
}
145144
}
146145

147-
internal static class UnsupportedPartialType
148-
{
149-
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
150-
id: "M31FA007",
151-
title: "Partial types are not supported",
152-
messageFormat: "Partial types are not supported. Remove partial keyword from type '{0}'.",
153-
category: "M31.Usage",
154-
defaultSeverity: DiagnosticSeverity.Error,
155-
isEnabledByDefault: true);
156-
157-
internal static Diagnostic CreateDiagnostic(SyntaxToken partialKeyword, string typeName)
158-
{
159-
return Diagnostic.Create(Descriptor, partialKeyword.GetLocation(), typeName);
160-
}
161-
}
162-
163146
internal static class InvalidFluentPredicateType
164147
{
165148
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(

src/M31.FluentApi.Generator/SourceGenerators/SourceGenerator.cs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
1616
var infos = context.SyntaxProvider
1717
.CreateSyntaxProvider(CanBeFluentApiClass, GetFluentApiClassInfo)
1818
.Where(info => info is not null)
19-
.Collect() // Handle partial classes, get an array of related info objects into GenerateCode.
19+
.Collect() // Handle partial classes, get an array of related info objects.
2020
.SelectMany((infos, _) =>
2121
infos.Distinct()); // Want to have every fluent API info object only once.
2222

@@ -73,7 +73,7 @@ private void SourceOutputAction(SourceProductionContext ctx, FluentApiClassInfo
7373
{
7474
SyntaxNode? syntaxNode = ctx.Node.Parent?.Parent;
7575

76-
if (!syntaxNode.IsTypeDeclarationOfInterest(out TypeDeclarationSyntax typeDeclaration))
76+
if (!syntaxNode.IsClassStructOrRecordSyntax(out TypeDeclarationSyntax typeDeclaration))
7777
{
7878
return null;
7979
}
@@ -106,22 +106,11 @@ private bool CanBeFluentApiClass(SyntaxNode node, CancellationToken cancellation
106106
// The parent of the attribute is a list of attributes, the parent of the parent is the class.
107107
SyntaxNode? syntaxNode = attributeSyntax.Parent?.Parent;
108108

109-
if (!syntaxNode.IsTypeDeclarationOfInterest())
109+
if (!syntaxNode.IsClassStructOrRecordSyntax())
110110
{
111111
return false;
112112
}
113113

114-
string? name = ExtractName(attributeSyntax.Name);
115-
return name is "FluentApi" or "FluentApiAttribute";
116-
}
117-
118-
private string? ExtractName(NameSyntax nameSyntax)
119-
{
120-
return nameSyntax switch
121-
{
122-
SimpleNameSyntax simpleNameSyntax => simpleNameSyntax.Identifier.Text, // without namespace
123-
QualifiedNameSyntax qualifiedNameSyntax => qualifiedNameSyntax.Right.Identifier.Text, // fully qualified
124-
_ => null
125-
};
114+
return attributeSyntax.IsFluentApiAttributeSyntax();
126115
}
127116
}

0 commit comments

Comments
 (0)