Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support partial classes #18

Merged
merged 16 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ PM> Install-Package M31.FluentApi
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:

```xml
<PackageReference Include="M31.FluentApi" Version="1.4.0" PrivateAssets="all"/>
<PackageReference Include="M31.FluentApi" Version="1.5.0" PrivateAssets="all"/>
```

If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:
Expand Down
11 changes: 10 additions & 1 deletion src/M31.FluentApi.Generator/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,13 @@ M31FA018 | M31.Usage | Error | Generic types are not supported
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
M31FA021 | M31.Usage | Error | Reserved method name
M31FA022 | M31.Usage | Error | Fluent lambda member without Fluent API
M31FA022 | M31.Usage | Error | Fluent lambda member without Fluent API


## Release 1.5.0

### Removed Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
M31FA007 | M31.Usage | Error | Partial types are not supported
24 changes: 21 additions & 3 deletions src/M31.FluentApi.Generator/Commons/SyntaxNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace M31.FluentApi.Generator.Commons;

internal static class SyntaxNodeExtensions
{
internal static bool IsTypeDeclarationOfInterest(
internal static bool IsClassStructOrRecordSyntax(
this SyntaxNode? syntaxNode, out TypeDeclarationSyntax typeDeclaration)
{
if (syntaxNode.IsTypeDeclarationOfInterest())
if (syntaxNode.IsClassStructOrRecordSyntax())
{
typeDeclaration = (TypeDeclarationSyntax)syntaxNode!;
return true;
Expand All @@ -18,8 +18,26 @@ internal static bool IsTypeDeclarationOfInterest(
return false;
}

internal static bool IsTypeDeclarationOfInterest(this SyntaxNode? syntaxNode)
internal static bool IsClassStructOrRecordSyntax(this SyntaxNode? syntaxNode)
{
return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax;
}

internal static bool IsFluentApiAttributeSyntax(this AttributeSyntax attributeSyntax)
{
string? name = ExtractName(attributeSyntax.Name);

// Note that we drop alias support for better performance.
return name is "FluentApi" or "FluentApiAttribute";

string? ExtractName(NameSyntax nameSyntax)
{
return nameSyntax switch
{
SimpleNameSyntax simpleNameSyntax => simpleNameSyntax.Identifier.Text, // without namespace
QualifiedNameSyntax qualifiedNameSyntax => qualifiedNameSyntax.Right.Identifier.Text, // fully qualified
_ => null
};
}
}
}
2 changes: 1 addition & 1 deletion src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<PackageVersion>1.4.0</PackageVersion>
<PackageVersion>1.5.0</PackageVersion>
<Authors>Kevin Schaal</Authors>
<Description>The generator package for M31.FluentAPI. Don't install this package explicitly, install M31.FluentAPI instead.</Description>
<PackageTags>fluentapi fluentbuilder fluentinterface fluentdesign fluent codegeneration</PackageTags>
Expand Down
42 changes: 7 additions & 35 deletions src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeAction(
AnalyzeNode,
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.RecordDeclaration,
SyntaxKind.RecordStructDeclaration);
}

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
Expand Down Expand Up @@ -51,21 +56,7 @@ private void AnalyzeNodeInternal(SyntaxNodeAnalysisContext context)
return;
}

ImmutableArray<SyntaxReference> syntaxReferences = symbol.DeclaringSyntaxReferences;

if (syntaxReferences.Length == 0)
{
return;
}

SyntaxNode syntaxNode = syntaxReferences.First().GetSyntax();

if (!syntaxNode.IsTypeDeclarationOfInterest(out TypeDeclarationSyntax typeDeclaration))
{
return;
}

if (ReportErrorDiagnosticForPartialKeyword(context, typeDeclaration, symbol))
if (context.Node is not TypeDeclarationSyntax typeDeclaration)
{
return;
}
Expand All @@ -92,23 +83,4 @@ private void AnalyzeNodeInternal(SyntaxNodeAnalysisContext context)
context.ReportDiagnostic(diagnostic);
}
}

private bool ReportErrorDiagnosticForPartialKeyword(
SyntaxNodeAnalysisContext context,
TypeDeclarationSyntax typeDeclaration,
INamedTypeSymbol symbol)
{
SyntaxToken partialKeyword = typeDeclaration.Modifiers.FirstOrDefault(
m => m.IsKind(SyntaxKind.PartialKeyword));

if (partialKeyword != default)
{
context.ReportDiagnostic(UnsupportedPartialType.CreateDiagnostic(
partialKeyword,
symbol.Name));
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ internal static class FluentApiDiagnostics
DuplicateAttribute.Descriptor,
OrthogonalAttributeMisused.Descriptor,
DuplicateMainAttribute.Descriptor,
UnsupportedPartialType.Descriptor,
InvalidFluentPredicateType.Descriptor,
InvalidFluentNullableType.Descriptor,
FluentNullableTypeWithoutNullableAnnotation.Descriptor,
Expand Down Expand Up @@ -144,22 +143,6 @@ internal static Diagnostic CreateDiagnostic(AttributeDataExtended attributeData)
}
}

internal static class UnsupportedPartialType
{
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
id: "M31FA007",
title: "Partial types are not supported",
messageFormat: "Partial types are not supported. Remove partial keyword from type '{0}'.",
category: "M31.Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

internal static Diagnostic CreateDiagnostic(SyntaxToken partialKeyword, string typeName)
{
return Diagnostic.Create(Descriptor, partialKeyword.GetLocation(), typeName);
}
}

internal static class InvalidFluentPredicateType
{
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
Expand Down
19 changes: 4 additions & 15 deletions src/M31.FluentApi.Generator/SourceGenerators/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var infos = context.SyntaxProvider
.CreateSyntaxProvider(CanBeFluentApiClass, GetFluentApiClassInfo)
.Where(info => info is not null)
.Collect() // Handle partial classes, get an array of related info objects into GenerateCode.
.Collect() // Handle partial classes, get an array of related info objects.
.SelectMany((infos, _) =>
infos.Distinct()); // Want to have every fluent API info object only once.

Expand Down Expand Up @@ -73,7 +73,7 @@ private void SourceOutputAction(SourceProductionContext ctx, FluentApiClassInfo
{
SyntaxNode? syntaxNode = ctx.Node.Parent?.Parent;

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

if (!syntaxNode.IsTypeDeclarationOfInterest())
if (!syntaxNode.IsClassStructOrRecordSyntax())
{
return false;
}

string? name = ExtractName(attributeSyntax.Name);
return name is "FluentApi" or "FluentApiAttribute";
}

private string? ExtractName(NameSyntax nameSyntax)
{
return nameSyntax switch
{
SimpleNameSyntax simpleNameSyntax => simpleNameSyntax.Identifier.Text, // without namespace
QualifiedNameSyntax qualifiedNameSyntax => qualifiedNameSyntax.Right.Identifier.Text, // fully qualified
_ => null
};
return attributeSyntax.IsFluentApiAttributeSyntax();
}
}
Loading
Loading