diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 2a7eb28d9b..95c0c2b7b2 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -54,6 +54,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
WARNING
WARNING
WARNING
+ WARNING
DO_NOT_SHOW
HINT
SUGGESTION
diff --git a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
index 65800bba82..89a511b08e 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Linq;
using System.Text;
using Humanizer;
using Microsoft.CodeAnalysis;
@@ -11,166 +8,163 @@
#pragma warning disable RS2008 // Enable analyzer release tracking
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+// To debug in Visual Studio (requires v17.2 or higher):
+// - Set JsonApiDotNetCore.SourceGenerators as startup project
+// - Add a breakpoint at the start of the Initialize or Execute method
+// - Optional: change targetProject in Properties\launchSettings.json
+// - Press F5
+
+[Generator(LanguageNames.CSharp)]
+public sealed class ControllerSourceGenerator : ISourceGenerator
{
- // To debug in Visual Studio (requires v17.2 or higher):
- // - Set JsonApiDotNetCore.SourceGenerators as startup project
- // - Add a breakpoint at the start of the Initialize or Execute method
- // - Optional: change targetProject in Properties\launchSettings.json
- // - Press F5
-
- [Generator(LanguageNames.CSharp)]
- public sealed class ControllerSourceGenerator : ISourceGenerator
+ private const string Category = "JsonApiDotNetCore";
+
+ private static readonly DiagnosticDescriptor MissingInterfaceWarning = new("JADNC001", "Resource type does not implement IIdentifiable",
+ "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category, DiagnosticSeverity.Warning,
+ true);
+
+ private static readonly DiagnosticDescriptor MissingIndentInTableError = new("JADNC900", "Internal error: Insufficient entries in IndentTable",
+ "Internal error: Missing entry in IndentTable for depth {0}", Category, DiagnosticSeverity.Warning, true);
+
+ // PERF: Heap-allocate the delegate only once, instead of per compilation.
+ private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = static () => new TypeWithAttributeSyntaxReceiver();
+
+ public void Initialize(GeneratorInitializationContext context)
{
- private const string Category = "JsonApiDotNetCore";
+ context.RegisterForSyntaxNotifications(CreateSyntaxReceiver);
+ }
- private static readonly DiagnosticDescriptor MissingInterfaceWarning = new DiagnosticDescriptor("JADNC001",
- "Resource type does not implement IIdentifiable",
- "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category,
- DiagnosticSeverity.Warning, true);
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var receiver = (TypeWithAttributeSyntaxReceiver?)context.SyntaxReceiver;
- private static readonly DiagnosticDescriptor MissingIndentInTableError = new DiagnosticDescriptor("JADNC900",
- "Internal error: Insufficient entries in IndentTable", "Internal error: Missing entry in IndentTable for depth {0}", Category,
- DiagnosticSeverity.Warning, true);
+ if (receiver == null)
+ {
+ return;
+ }
- // PERF: Heap-allocate the delegate only once, instead of per compilation.
- private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = () => new TypeWithAttributeSyntaxReceiver();
+ INamedTypeSymbol? resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute");
+ INamedTypeSymbol? identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1");
+ INamedTypeSymbol? loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory");
- public void Initialize(GeneratorInitializationContext context)
+ if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null)
{
- context.RegisterForSyntaxNotifications(CreateSyntaxReceiver);
+ return;
}
- public void Execute(GeneratorExecutionContext context)
+ var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var writer = new SourceCodeWriter(context, MissingIndentInTableError);
+
+ foreach (TypeDeclarationSyntax? typeDeclarationSyntax in receiver.TypeDeclarations)
{
- var receiver = (TypeWithAttributeSyntaxReceiver)context.SyntaxReceiver;
+ // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance.
+ // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing.
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree);
+ INamedTypeSymbol? resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken);
- if (receiver == null)
+ if (resourceType == null)
{
- return;
+ continue;
}
- INamedTypeSymbol resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute");
- INamedTypeSymbol identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1");
- INamedTypeSymbol loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory");
+ AttributeData? resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType,
+ static (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type));
- if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null)
+ if (resourceAttributeData == null)
{
- return;
+ continue;
}
- var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase);
- var writer = new SourceCodeWriter(context, MissingIndentInTableError);
+ TypedConstant endpointsArgument =
+ resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "GenerateControllerEndpoints").Value;
- foreach (TypeDeclarationSyntax typeDeclarationSyntax in receiver.TypeDeclarations)
+ if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None)
{
- // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance.
- // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing.
- context.CancellationToken.ThrowIfCancellationRequested();
-
- SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree);
- INamedTypeSymbol resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken);
-
- if (resourceType == null)
- {
- continue;
- }
-
- AttributeData resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType,
- (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type));
-
- if (resourceAttributeData == null)
- {
- continue;
- }
-
- TypedConstant endpointsArgument = resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "GenerateControllerEndpoints").Value;
-
- if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None)
- {
- continue;
- }
-
- TypedConstant controllerNamespaceArgument =
- resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "ControllerNamespace").Value;
-
- string controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType);
-
- INamedTypeSymbol identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface,
- (@interface, openInterface) => @interface.IsGenericType &&
- SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface));
+ continue;
+ }
- if (identifiableClosedInterface == null)
- {
- var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name);
- context.ReportDiagnostic(diagnostic);
- continue;
- }
+ TypedConstant controllerNamespaceArgument =
+ resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "ControllerNamespace").Value;
- ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0];
- string controllerName = $"{resourceType.Name.Pluralize()}Controller";
- JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All;
+ string? controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType);
- string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface);
- SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8);
+ INamedTypeSymbol? identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface,
+ static (@interface, openInterface) =>
+ @interface.IsGenericType && SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface));
- string fileName = GetUniqueFileName(controllerName, controllerNamesInUse);
- context.AddSource(fileName, sourceText);
+ if (identifiableClosedInterface == null)
+ {
+ var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name);
+ context.ReportDiagnostic(diagnostic);
+ continue;
}
- }
- private static TElement FirstOrDefault(ImmutableArray source, TContext context, Func predicate)
- {
- // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression.
- // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions.
+ ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0];
+ string controllerName = $"{resourceType.Name.Pluralize()}Controller";
+ JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All;
- foreach (TElement element in source)
- {
- if (predicate(element, context))
- {
- return element;
- }
- }
+ string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface);
+ SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8);
- return default;
+ string fileName = GetUniqueFileName(controllerName, controllerNamesInUse);
+ context.AddSource(fileName, sourceText);
}
+ }
- private static string GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType)
+ private static TElement? FirstOrDefault(ImmutableArray source, TContext context, Func predicate)
+ {
+ // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression.
+ // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions.
+
+ foreach (TElement element in source)
{
- if (!controllerNamespaceArgument.IsNull)
+ if (predicate(element, context))
{
- return (string)controllerNamespaceArgument.Value;
+ return element;
}
+ }
- if (resourceType.ContainingNamespace.IsGlobalNamespace)
- {
- return null;
- }
+ return default;
+ }
- if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace)
- {
- return "Controllers";
- }
+ private static string? GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType)
+ {
+ if (!controllerNamespaceArgument.IsNull)
+ {
+ return (string?)controllerNamespaceArgument.Value;
+ }
- return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers";
+ if (resourceType.ContainingNamespace.IsGlobalNamespace)
+ {
+ return null;
}
- private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse)
+ if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace)
{
- // We emit unique file names to prevent a failure in the source generator, but also because our test suite
- // may contain two resources with the same class name in different namespaces. That works, as long as only
- // one of its controllers gets registered.
+ return "Controllers";
+ }
- if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex))
- {
- lastIndex++;
- controllerNamesInUse[controllerName] = lastIndex;
+ return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers";
+ }
- return $"{controllerName}{lastIndex}.g.cs";
- }
+ private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse)
+ {
+ // We emit unique file names to prevent a failure in the source generator, but also because our test suite
+ // may contain two resources with the same class name in different namespaces. That works, as long as only
+ // one of its controllers gets registered.
- controllerNamesInUse[controllerName] = 1;
- return $"{controllerName}.g.cs";
+ if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex))
+ {
+ lastIndex++;
+ controllerNamesInUse[controllerName] = lastIndex;
+
+ return $"{controllerName}{lastIndex}.g.cs";
}
+
+ controllerNamesInUse[controllerName] = 1;
+ return $"{controllerName}.g.cs";
}
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
index bcd8c06b0a..8bf3e90cf6 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
+++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
@@ -5,8 +5,7 @@
true
false
$(NoWarn);NU5128
- disable
- disable
+ latest
true
diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
index 14134adcfd..911be3f359 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
@@ -1,26 +1,23 @@
-using System;
+namespace JsonApiDotNetCore.SourceGenerators;
-namespace JsonApiDotNetCore.SourceGenerators
+// IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes.
+[Flags]
+public enum JsonApiEndpointsCopy
{
- // IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes.
- [Flags]
- public enum JsonApiEndpointsCopy
- {
- None = 0,
- GetCollection = 1,
- GetSingle = 1 << 1,
- GetSecondary = 1 << 2,
- GetRelationship = 1 << 3,
- Post = 1 << 4,
- PostRelationship = 1 << 5,
- Patch = 1 << 6,
- PatchRelationship = 1 << 7,
- Delete = 1 << 8,
- DeleteRelationship = 1 << 9,
+ None = 0,
+ GetCollection = 1,
+ GetSingle = 1 << 1,
+ GetSecondary = 1 << 2,
+ GetRelationship = 1 << 3,
+ Post = 1 << 4,
+ PostRelationship = 1 << 5,
+ Patch = 1 << 6,
+ PatchRelationship = 1 << 7,
+ Delete = 1 << 8,
+ DeleteRelationship = 1 << 9,
- Query = GetCollection | GetSingle | GetSecondary | GetRelationship,
- Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship,
+ Query = GetCollection | GetSingle | GetSecondary | GetRelationship,
+ Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship,
- All = Query | Command
- }
+ All = Query | Command
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
index e03e3cbad2..13dc91a836 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
@@ -1,266 +1,271 @@
-using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+
+///
+/// Writes the source code for an ASP.NET controller for a JSON:API resource.
+///
+internal sealed class SourceCodeWriter
{
- ///
- /// Writes the source code for an ASP.NET controller for a JSON:API resource.
- ///
- internal sealed class SourceCodeWriter
- {
- private const int SpacesPerIndent = 4;
+ private const int SpacesPerIndent = 4;
- private static readonly IDictionary IndentTable = new Dictionary
+ private static readonly IDictionary IndentTable = new Dictionary
+ {
+ [0] = string.Empty,
+ [1] = new(' ', 1 * SpacesPerIndent),
+ [2] = new(' ', 2 * SpacesPerIndent),
+ [3] = new(' ', 3 * SpacesPerIndent)
+ };
+
+ private static readonly IDictionary AggregateEndpointToServiceNameMap =
+ new Dictionary
{
- [0] = string.Empty,
- [1] = new string(' ', 1 * SpacesPerIndent),
- [2] = new string(' ', 2 * SpacesPerIndent),
- [3] = new string(' ', 3 * SpacesPerIndent)
+ [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"),
+ [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"),
+ [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService")
};
- private static readonly IDictionary AggregateEndpointToServiceNameMap =
- new Dictionary
- {
- [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"),
- [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"),
- [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService")
- };
-
- private static readonly IDictionary EndpointToServiceNameMap =
- new Dictionary
- {
- [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"),
- [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"),
- [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"),
- [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"),
- [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"),
- [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"),
- [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"),
- [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"),
- [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"),
- [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship")
- };
-
- private readonly GeneratorExecutionContext _context;
- private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor;
-
- private readonly StringBuilder _sourceBuilder = new StringBuilder();
- private int _depth;
-
- public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor)
+ private static readonly IDictionary EndpointToServiceNameMap =
+ new Dictionary
{
- _context = context;
- _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor;
- }
-
- public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string controllerNamespace,
- string controllerName, INamedTypeSymbol loggerFactoryInterface)
- {
- _sourceBuilder.Clear();
- _depth = 0;
-
- if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated)
- {
- WriteNullableEnable();
- }
+ [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"),
+ [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"),
+ [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"),
+ [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"),
+ [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"),
+ [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"),
+ [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"),
+ [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"),
+ [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"),
+ [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship")
+ };
- WriteNamespaceImports(loggerFactoryInterface, resourceType);
+ private readonly GeneratorExecutionContext _context;
+ private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor;
- if (controllerNamespace != null)
- {
- WriteNamespaceDeclaration(controllerNamespace);
- }
+ private readonly StringBuilder _sourceBuilder = new();
+ private int _depth;
- WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType);
- _depth++;
+ public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor)
+ {
+ _context = context;
+ _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor;
+ }
- WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType);
+ public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string? controllerNamespace,
+ string controllerName, INamedTypeSymbol loggerFactoryInterface)
+ {
+ _sourceBuilder.Clear();
+ _depth = 0;
- _depth--;
- WriteCloseCurly();
+ WriteAutoGeneratedComment();
- return _sourceBuilder.ToString();
+ if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated)
+ {
+ WriteNullableEnable();
}
- private void WriteNullableEnable()
+ WriteNamespaceImports(loggerFactoryInterface, resourceType);
+
+ if (controllerNamespace != null)
{
- _sourceBuilder.AppendLine("#nullable enable");
- _sourceBuilder.AppendLine();
+ WriteNamespaceDeclaration(controllerNamespace);
}
- private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType)
- {
- _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};");
+ WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType);
+ _depth++;
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;");
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;");
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;");
+ WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType);
- if (!resourceType.ContainingNamespace.IsGlobalNamespace)
- {
- _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};");
- }
+ _depth--;
+ WriteCloseCurly();
- _sourceBuilder.AppendLine();
- }
+ return _sourceBuilder.ToString();
+ }
- private void WriteNamespaceDeclaration(string controllerNamespace)
- {
- _sourceBuilder.AppendLine($"namespace {controllerNamespace};");
- _sourceBuilder.AppendLine();
- }
+ private void WriteAutoGeneratedComment()
+ {
+ _sourceBuilder.AppendLine("// ");
+ _sourceBuilder.AppendLine();
+ }
- private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType,
- ITypeSymbol idType)
- {
- string baseClassName = GetControllerBaseClassName(endpointsToGenerate);
+ private void WriteNullableEnable()
+ {
+ _sourceBuilder.AppendLine("#nullable enable");
+ _sourceBuilder.AppendLine();
+ }
- WriteIndent();
- _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>");
+ private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType)
+ {
+ _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};");
- WriteOpenCurly();
- }
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;");
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;");
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;");
- private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate)
+ if (!resourceType.ContainingNamespace.IsGlobalNamespace)
{
- switch (endpointsToGenerate)
- {
- case JsonApiEndpointsCopy.Query:
- {
- return "JsonApiQueryController";
- }
- case JsonApiEndpointsCopy.Command:
- {
- return "JsonApiCommandController";
- }
- default:
- {
- return "JsonApiController";
- }
- }
+ _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};");
}
- private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate,
- INamedTypeSymbol resourceType, ITypeSymbol idType)
- {
- string loggerName = loggerFactoryInterface.Name;
+ _sourceBuilder.AppendLine();
+ }
+
+ private void WriteNamespaceDeclaration(string controllerNamespace)
+ {
+ _sourceBuilder.AppendLine($"namespace {controllerNamespace};");
+ _sourceBuilder.AppendLine();
+ }
+
+ private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ string baseClassName = GetControllerBaseClassName(endpointsToGenerate);
- WriteIndent();
- _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,");
+ WriteIndent();
+ _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>");
- _depth++;
+ WriteOpenCurly();
+ }
- if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value))
+ private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate)
+ {
+ switch (endpointsToGenerate)
+ {
+ case JsonApiEndpointsCopy.Query:
+ {
+ return "JsonApiQueryController";
+ }
+ case JsonApiEndpointsCopy.Command:
{
- WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType);
+ return "JsonApiCommandController";
}
- else
+ default:
{
- WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType);
+ return "JsonApiController";
}
+ }
+ }
- _depth--;
+ private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate,
+ INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ string loggerName = loggerFactoryInterface.Name;
- WriteOpenCurly();
- WriteCloseCurly();
- }
+ WriteIndent();
+ _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,");
- private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType)
- {
- WriteIndent();
- _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})");
+ _depth++;
- WriteIndent();
- _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})");
+ if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value))
+ {
+ WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType);
}
-
- private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ else
{
- bool isFirstEntry = true;
+ WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType);
+ }
+
+ _depth--;
+
+ WriteOpenCurly();
+ WriteCloseCurly();
+ }
+
+ private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})");
+
+ WriteIndent();
+ _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})");
+ }
+
+ private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ bool isFirstEntry = true;
- foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ {
+ if ((endpointsToGenerate & entry.Key) == entry.Key)
{
- if ((endpointsToGenerate & entry.Key) == entry.Key)
+ if (isFirstEntry)
+ {
+ isFirstEntry = false;
+ }
+ else
{
- if (isFirstEntry)
- {
- isFirstEntry = false;
- }
- else
- {
- _sourceBuilder.AppendLine(Tokens.Comma);
- }
-
- WriteIndent();
- _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}");
+ _sourceBuilder.AppendLine(Tokens.Comma);
}
+
+ WriteIndent();
+ _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}");
}
+ }
- _sourceBuilder.AppendLine(Tokens.CloseParen);
+ _sourceBuilder.AppendLine(Tokens.CloseParen);
- WriteIndent();
- _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,");
+ WriteIndent();
+ _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,");
- isFirstEntry = true;
- _depth++;
+ isFirstEntry = true;
+ _depth++;
- foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ {
+ if ((endpointsToGenerate & entry.Key) == entry.Key)
{
- if ((endpointsToGenerate & entry.Key) == entry.Key)
+ if (isFirstEntry)
{
- if (isFirstEntry)
- {
- isFirstEntry = false;
- }
- else
- {
- _sourceBuilder.AppendLine(Tokens.Comma);
- }
-
- WriteIndent();
- _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}");
+ isFirstEntry = false;
+ }
+ else
+ {
+ _sourceBuilder.AppendLine(Tokens.Comma);
}
- }
- _sourceBuilder.AppendLine(Tokens.CloseParen);
- _depth--;
+ WriteIndent();
+ _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}");
+ }
}
- private void WriteOpenCurly()
- {
- WriteIndent();
- _sourceBuilder.AppendLine(Tokens.OpenCurly);
- }
+ _sourceBuilder.AppendLine(Tokens.CloseParen);
+ _depth--;
+ }
- private void WriteCloseCurly()
- {
- WriteIndent();
- _sourceBuilder.AppendLine(Tokens.CloseCurly);
- }
+ private void WriteOpenCurly()
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine(Tokens.OpenCurly);
+ }
- private void WriteIndent()
- {
- // PERF: Reuse pre-calculated indents instead of allocating a new string each time.
- if (!IndentTable.TryGetValue(_depth, out string indent))
- {
- var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString());
- _context.ReportDiagnostic(diagnostic);
+ private void WriteCloseCurly()
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine(Tokens.CloseCurly);
+ }
- indent = new string(' ', _depth * SpacesPerIndent);
- }
+ private void WriteIndent()
+ {
+ // PERF: Reuse pre-calculated indents instead of allocating a new string each time.
+ if (!IndentTable.TryGetValue(_depth, out string? indent))
+ {
+ var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString());
+ _context.ReportDiagnostic(diagnostic);
- _sourceBuilder.Append(indent);
+ indent = new string(' ', _depth * SpacesPerIndent);
}
+ _sourceBuilder.Append(indent);
+ }
+
#pragma warning disable AV1008 // Class should not be static
- private static class Tokens
- {
- public const string OpenCurly = "{";
- public const string CloseCurly = "}";
- public const string CloseParen = ")";
- public const string Comma = ",";
- }
-#pragma warning restore AV1008 // Class should not be static
+ private static class Tokens
+ {
+ public const string OpenCurly = "{";
+ public const string CloseCurly = "}";
+ public const string CloseParen = ")";
+ public const string Comma = ",";
}
+#pragma warning restore AV1008 // Class should not be static
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
index 0fbc18a758..b23de19cc9 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
@@ -1,41 +1,39 @@
-using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+
+///
+/// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot
+/// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these:
+/// { }
+///
+/// [ResourceAttribute]
+/// public class ExampleResource2 : Identifiable { }
+///
+/// [AlternateNamespaceName.Annotations.Resource]
+/// public class ExampleResource3 : Identifiable { }
+///
+/// [AlternateTypeName]
+/// public class ExampleResource4 : Identifiable { }
+/// ]]>
+///
+///
+internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver
{
- ///
- /// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot
- /// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these:
- /// { }
- ///
- /// [ResourceAttribute]
- /// public class ExampleResource2 : Identifiable { }
- ///
- /// [AlternateNamespaceName.Annotations.Resource]
- /// public class ExampleResource3 : Identifiable { }
- ///
- /// [AlternateTypeName]
- /// public class ExampleResource4 : Identifiable { }
- /// ]]>
- ///
- ///
- internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver
- {
- public readonly ISet TypeDeclarations = new HashSet();
+ public readonly ISet TypeDeclarations = new HashSet();
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any())
{
- if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any())
- {
- TypeDeclarations.Add(typeDeclarationSyntax);
- }
+ TypeDeclarations.Add(typeDeclarationSyntax);
}
}
}
diff --git a/test/SourceGeneratorTests/ControllerGenerationTests.cs b/test/SourceGeneratorTests/ControllerGenerationTests.cs
index 614b4d316c..9f5f9f83d3 100644
--- a/test/SourceGeneratorTests/ControllerGenerationTests.cs
+++ b/test/SourceGeneratorTests/ControllerGenerationTests.cs
@@ -51,7 +51,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -111,7 +113,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -171,7 +175,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -234,7 +240,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -574,7 +582,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -633,7 +643,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
@@ -691,7 +703,9 @@ public sealed class Item : Identifiable
GeneratorDriverRunResult runResult = driver.GetRunResult();
runResult.Should().NotHaveDiagnostics();
- runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging;
+ runResult.Should().HaveProducedSourceCode(@"//
+
+using Microsoft.Extensions.Logging;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;