From e3ebc2078b2535728f9727a70faaa826f63c5463 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:06:32 -0500 Subject: [PATCH] Updated implementation. (#5) * Updated namespaces. * Updated readme. * Added cancellation tokens. * Added source generator options. * Removed options. * Added support for multiple source files. * Updated README. --- AttributeSourceGenerator.sln | 14 ++- README.md | 97 +++++++++++++++++++ .../AttributeIncrementalGeneratorBase.cs | 72 ++++++++------ ...ributeIncrementalGeneratorConfiguration.cs | 16 ++- .../AttributeSourceGenerator.csproj | 5 + .../AttributeSourceGenerator.projitems | 35 +++++++ .../AttributeSourceGenerator.shproj | 13 +++ .../Common/DeclarationExtensions.cs | 4 +- .../Common/EquatableReadOnlyList.cs | 6 +- .../Common/EquatableReadOnlyList`1.cs | 4 +- ...neratorAttributeSyntaxContextExtensions.cs | 29 ++++-- .../Common/MethodSymbolExtensions.cs | 32 +++--- .../Common/NamedSymbolExtensions.cs | 7 +- .../Common/SymbolExtensions.cs | 72 +++++++------- .../Common/TypeSymbolExtensions.cs | 31 +++--- .../DeclarationType.cs | 4 +- src/AttributeSourceGenerator/FilterType.cs | 8 +- .../Models/ConstructorArgument.cs | 4 +- .../Models/ConstructorParameter.cs | 22 ----- .../Models/Declaration.cs | 2 - .../Models/GenericTypeArgument.cs | 4 +- .../Models/MarkerAttributeData.cs | 2 - .../Models/MethodParameter.cs | 20 ++++ .../Models/NamedArgument.cs | 4 +- src/AttributeSourceGenerator/Source.cs | 22 +++++ src/AttributeSourceGenerator/Symbol.cs | 22 ++--- src/AttributeSourceGenerator/SymbolType.cs | 4 +- 27 files changed, 369 insertions(+), 186 deletions(-) create mode 100644 src/AttributeSourceGenerator/AttributeSourceGenerator.projitems create mode 100644 src/AttributeSourceGenerator/AttributeSourceGenerator.shproj delete mode 100644 src/AttributeSourceGenerator/Models/ConstructorParameter.cs create mode 100644 src/AttributeSourceGenerator/Models/MethodParameter.cs create mode 100644 src/AttributeSourceGenerator/Source.cs diff --git a/AttributeSourceGenerator.sln b/AttributeSourceGenerator.sln index 4598c09..d38187e 100644 --- a/AttributeSourceGenerator.sln +++ b/AttributeSourceGenerator.sln @@ -1,6 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeSourceGenerator", "src\AttributeSourceGenerator\AttributeSourceGenerator.csproj", "{ED92CE9D-902E-445D-8E52-4679C575863E}" +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34616.47 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttributeSourceGenerator", "src\AttributeSourceGenerator\AttributeSourceGenerator.csproj", "{ED92CE9D-902E-445D-8E52-4679C575863E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -13,4 +16,13 @@ Global {ED92CE9D-902E-445D-8E52-4679C575863E}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED92CE9D-902E-445D-8E52-4679C575863E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0F4E21C8-19DB-41A2-A79A-FA5DCF0B08B6} + EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\AttributeSourceGenerator\AttributeSourceGeneratorShared.projitems*{dfd4aa47-b465-4c4b-930c-ec98d65bedc3}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 0ae11b4..82e4394 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,99 @@ # AttributeSourceGenerator + A simple attribute-based Roslyn incremental source generator base class for .NET. + +### Example generator + +```csharp +using AttributeSourceGenerator; +using Microsoft.CodeAnalysis; + +namespace SourceGenerators; + +[Generator] +public sealed class IdentifierSourceGenerator : AttributeIncrementalGeneratorBase +{ + public IdentifierSourceGenerator() + : base(() => new AttributeIncrementalGeneratorConfiguration() + { + MarkerAttributeName = MarkerAttributeName, + MarkerAttributeSource = MarkerAttributeSource, + SymbolFilter = FilterType.Struct, + SourceGenerator = GenerateIdentifier + }) + { + } + + private const string MarkerAttributeNamespace = "Domain.Common.Attributes"; + + private const string MarkerAttributeName = $"{MarkerAttributeNamespace}.GeneratedIdentifierAttribute`1"; + + private static source MarkerAttributeSource = new Source("GeneratedIdentifierAttribute`1", $$""" + namespace {{MarkerAttributeNamespace}}; + + [AttributeUsage(AttributeTargets.Struct)] + public sealed class GeneratedIdentifierAttribute : Attribute; + """; + + private static IEnumerable GenerateIdentifier(Symbol symbol) + { + return [new Source(symbol.Name, $$""" + // + + #nullable enable + + namespace {{symbol.Namespace}}; + + partial struct {{symbol.Name}} : IEquatable<{{symbol.Name}}>, IComparable<{{symbol.Name}}>, IComparable + { + // Implementation details + } + """)]; + } +} +``` + +### Typical .csproj + +```xml + + + + netstandard2.0 + enable + true + latest + latest-All + true + true + true + + + + + + + + + true + false + true + false + $(NoWarn);NU5128 + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + + + + +``` diff --git a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs index f70e61c..eeaaf84 100644 --- a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs +++ b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs @@ -6,8 +6,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator; /// Provides a base class for incremental source generators that generate source using marker attributes. @@ -36,30 +34,29 @@ protected AttributeIncrementalGeneratorBase(FuncThe initialization context. public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterPostInitializationOutput(initializationContext => AddSource(initializationContext, _configuration.AttributeFullyQualifiedName, _configuration.AttributeSource)); - - var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName(_configuration.AttributeFullyQualifiedName, (syntaxNode, _) => Filter(syntaxNode, _configuration.SymbolFilter), (syntaxContext, _) => Transform(syntaxContext)); + context.RegisterPostInitializationOutput(AddMarkerAttributeSource); - context.RegisterSourceOutput(pipeline, (productionContext, symbol) => GenerateSourceForSymbol(productionContext, symbol, _configuration.SourceGenerator)); + var syntaxProvider = context.SyntaxProvider.ForAttributeWithMetadataName(_configuration.MarkerAttributeName, Filter, Transform); + context.RegisterSourceOutput(syntaxProvider, GenerateSourceForSymbol); } - /// Adds a source file to the output. + /// Adds the marker attribute source to the output. /// The post-initialization context. - /// The name of the source file. - /// The source code for the file. - private static void AddSource(IncrementalGeneratorPostInitializationContext context, string name, string? source) + private void AddMarkerAttributeSource(IncrementalGeneratorPostInitializationContext context) { - if (source?.Length > 0) - context.AddSource($"{name}.g.cs", SourceText.From(source, Encoding.UTF8)); + if (_configuration.MarkerAttributeSource?.Text.Length > 0) + context.AddSource($"{_configuration.MarkerAttributeSource.Value.Name}.g.cs", SourceText.From(_configuration.MarkerAttributeSource.Value.Text, Encoding.UTF8)); } /// Determines whether a syntax node should be included based on the filter settings. /// The syntax node to filter. - /// The filter configuration. + /// The cancellation token. /// if the syntax node should be included, otherwise . - private static bool Filter(SyntaxNode syntaxNode, FilterType filterType) + private bool Filter(SyntaxNode syntaxNode, CancellationToken cancellationToken) { - var filter = filterType == FilterType.None ? FilterType.All : filterType; + cancellationToken.ThrowIfCancellationRequested(); + + var filter = _configuration.SymbolFilter == FilterType.None ? FilterType.All : _configuration.SymbolFilter; if (filter.HasFlag(FilterType.Interface) && syntaxNode is InterfaceDeclarationSyntax) return true; @@ -79,35 +76,50 @@ private static bool Filter(SyntaxNode syntaxNode, FilterType filterType) /// Transforms a generator attribute syntax context into a symbol for source generation. /// The generator attribute syntax context. + /// The cancellation token. /// The transformed symbol. - private static Symbol Transform(GeneratorAttributeSyntaxContext context) + private static Symbol Transform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + var targetSymbol = context.TargetSymbol; if (targetSymbol is not INamedTypeSymbol && targetSymbol is not IMethodSymbol) throw new InvalidOperationException($"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly tried to transform a {nameof(context.TargetSymbol)} that was not an {nameof(INamedTypeSymbol)} or a {nameof(IMethodSymbol)}."); - - var markerAttribute = context.GetMarkerAttribute(); - var containingDeclarations = targetSymbol.GetContainingDeclarations(); - var symbolType = targetSymbol.GetSymbolType(); + var markerAttribute = context.GetMarkerAttribute(cancellationToken); + var containingDeclarations = targetSymbol.GetContainingDeclarations(cancellationToken); + var symbolType = targetSymbol.GetSymbolType(cancellationToken); var symbolName = targetSymbol.Name; + EquatableReadOnlyList genericTypeParameters; - EquatableReadOnlyList constructorParameters; + EquatableReadOnlyList constructorParameters; string returnType; switch (targetSymbol) { case INamedTypeSymbol namedTypeSymbol: - genericTypeParameters = namedTypeSymbol.GetGenericTypeParameters(); - constructorParameters = EquatableReadOnlyList.Empty; + genericTypeParameters = namedTypeSymbol.GetGenericTypeParameters(cancellationToken); + constructorParameters = EquatableReadOnlyList.Empty; returnType = ""; + /* + TODO: Analyze members. + var members = namedTypeSymbol.GetMembers(); + foreach (var member in members) + switch (member) + { + case IPropertySymbol propertySymbol: + break; + case IMethodSymbol methodSymbol: + break; + } + */ break; case IMethodSymbol methodSymbol: - genericTypeParameters = methodSymbol.GetGenericTypeParameters(); - constructorParameters = methodSymbol.GetConstructorParameters(); + genericTypeParameters = methodSymbol.GetGenericTypeParameters(cancellationToken); + constructorParameters = methodSymbol.GetMethodParameters(cancellationToken); returnType = methodSymbol.ReturnType.ToDisplayString(); break; default: genericTypeParameters = EquatableReadOnlyList.Empty; - constructorParameters = EquatableReadOnlyList.Empty; + constructorParameters = EquatableReadOnlyList.Empty; returnType = ""; break; } @@ -120,10 +132,10 @@ private static Symbol Transform(GeneratorAttributeSyntaxContext context) /// Generates source code for a given symbol. /// The source production context. /// The symbol to generate source for. - /// A function that generates the source code for a symbol. - private static void GenerateSourceForSymbol(SourceProductionContext context, Symbol symbol, Func generate) + private void GenerateSourceForSymbol(SourceProductionContext context, Symbol symbol) { - var sourceText = generate(symbol); - context.AddSource($"{symbol.FullyQualifiedName}.g.cs", sourceText); + var sources = _configuration.SourceGenerator(symbol); + foreach (var source in sources) + context.AddSource($"{source.Name}.g.cs", source.Text); } } \ No newline at end of file diff --git a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs index a808fe7..06425cf 100644 --- a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs +++ b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs @@ -1,24 +1,22 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator; +namespace AttributeSourceGenerator; /// Defines the configuration for an incremental attribute generator. public sealed class AttributeIncrementalGeneratorConfiguration { - /// The fully qualified name of the attribute. - public required string AttributeFullyQualifiedName { get; init; } + /// The fully qualified name of the marker attribute. + public required string MarkerAttributeName { get; init; } - /// The source for the attribute. - public string? AttributeSource { get; init; } + /// The source for the marker attribute. + public Source? MarkerAttributeSource { get; init; } /// The filter to apply to symbols. public FilterType SymbolFilter { get; init; } = FilterType.All; /// The function that generates the source code for the attribute. - public required Func SourceGenerator { get; init; } + public required Func> SourceGenerator { get; init; } /// Initializes a new instance of the class public AttributeIncrementalGeneratorConfiguration() { } -} +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj b/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj index 452b571..cb57182 100644 --- a/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj +++ b/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj @@ -30,6 +30,11 @@ true + + + + + diff --git a/src/AttributeSourceGenerator/AttributeSourceGenerator.projitems b/src/AttributeSourceGenerator/AttributeSourceGenerator.projitems new file mode 100644 index 0000000..a708fdb --- /dev/null +++ b/src/AttributeSourceGenerator/AttributeSourceGenerator.projitems @@ -0,0 +1,35 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + dfd4aa47-b465-4c4b-930c-ec98d65bedc3 + + + AttributeSourceGenerator + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AttributeSourceGenerator/AttributeSourceGenerator.shproj b/src/AttributeSourceGenerator/AttributeSourceGenerator.shproj new file mode 100644 index 0000000..f82ad69 --- /dev/null +++ b/src/AttributeSourceGenerator/AttributeSourceGenerator.shproj @@ -0,0 +1,13 @@ + + + + dfd4aa47-b465-4c4b-930c-ec98d65bedc3 + 14.0 + + + + + + + + diff --git a/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs b/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs index 8505b9d..cb7c3c0 100644 --- a/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs +++ b/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs @@ -1,8 +1,6 @@ using System.Text; using AttributeSourceGenerator.Models; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with declarations. @@ -55,4 +53,4 @@ public static string ToFullyQualifiedName(this EquatableReadOnlyListProvides extension methods to convert various collections to an . internal static class EquatableReadOnlyList @@ -20,4 +18,4 @@ public static EquatableReadOnlyList ToEquatableReadOnlyList(this IEnumerab { return new EquatableReadOnlyList(enumerable.ToArray()); } -} +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs index 71ed855..28a2c0a 100644 --- a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs +++ b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs @@ -1,7 +1,5 @@ using System.Collections; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// A read-only list that implements for value-based equality comparisons. @@ -88,4 +86,4 @@ IEnumerator IEnumerable.GetEnumerator() { return Collection.GetEnumerator(); } -} +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs b/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs index f9fdcb5..57e8344 100644 --- a/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs +++ b/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs @@ -1,8 +1,6 @@ using AttributeSourceGenerator.Models; using Microsoft.CodeAnalysis; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with GeneratorAttributeSyntaxContext. @@ -10,9 +8,12 @@ internal static class GeneratorAttributeSyntaxContextExtensions { /// Gets the marker attribute data for the given context. /// The to get the marker attribute for. + /// The cancellation token. /// The representing the marker attribute. - public static MarkerAttributeData GetMarkerAttribute(this GeneratorAttributeSyntaxContext context) + public static MarkerAttributeData GetMarkerAttribute(this GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + var attribute = context.Attributes.First(); var attributeClass = attribute.AttributeClass; @@ -21,20 +22,22 @@ public static MarkerAttributeData GetMarkerAttribute(this GeneratorAttributeSynt $"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly found that {nameof(AttributeData.AttributeClass)} was null while transforming a {nameof(GeneratorAttributeSyntaxContext.TargetSymbol)}."); var attributeName = attributeClass.Name; - var genericTypeArguments = attribute.GetGenericTypeArguments(); - var constructorArguments = attribute.GetConstructorArguments(); - var namedArguments = attribute.GetNamedArguments(); + var genericTypeArguments = attribute.GetGenericTypeArguments(cancellationToken); + var constructorArguments = attribute.GetConstructorArguments(cancellationToken); + var namedArguments = attribute.GetNamedArguments(cancellationToken); var markerAttributeData = new MarkerAttributeData(attributeName, genericTypeArguments, constructorArguments, namedArguments); return markerAttributeData; } - /// Gets a list of generic type arguments for the given attribute. /// The to get the generic type arguments for. + /// The cancellation token. /// A list of representing the generic type arguments. - private static EquatableReadOnlyList GetGenericTypeArguments(this AttributeData attribute) + private static EquatableReadOnlyList GetGenericTypeArguments(this AttributeData attribute, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + var attributeClass = attribute.AttributeClass; if (attributeClass is null) throw new InvalidOperationException( @@ -62,9 +65,12 @@ private static EquatableReadOnlyList GetGenericTypeArgument /// Gets a list of constructor arguments for the given attribute. /// The to get the constructor arguments for. + /// The cancellation token. /// A list of representing the constructor arguments. - private static EquatableReadOnlyList GetConstructorArguments(this AttributeData attribute) + private static EquatableReadOnlyList GetConstructorArguments(this AttributeData attribute, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + var attributeConstructor = attribute.AttributeConstructor; if (attributeConstructor is null) throw new InvalidOperationException( @@ -94,9 +100,12 @@ private static EquatableReadOnlyList GetConstructorArgument /// Gets a list of named arguments for the given attribute. /// The to get the named arguments for. + /// The cancellation token. /// A list of representing the named arguments. - private static EquatableReadOnlyList GetNamedArguments(this AttributeData attribute) + private static EquatableReadOnlyList GetNamedArguments(this AttributeData attribute, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (attribute.NamedArguments.Length <= 0) return EquatableReadOnlyList.Empty; diff --git a/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs index 23bc87a..ca7f061 100644 --- a/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs +++ b/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs @@ -1,8 +1,6 @@ using AttributeSourceGenerator.Models; using Microsoft.CodeAnalysis; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with method symbols. @@ -10,9 +8,12 @@ internal static class MethodSymbolExtensions { /// Gets the generic type parameters for the given method symbol. /// The to get the type parameters for. + /// The cancellation token. /// An of strings representing the generic type parameter names. - public static EquatableReadOnlyList GetGenericTypeParameters(this IMethodSymbol symbol) + public static EquatableReadOnlyList GetGenericTypeParameters(this IMethodSymbol symbol, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (!symbol.IsGenericMethod) return EquatableReadOnlyList.Empty; @@ -29,28 +30,31 @@ public static EquatableReadOnlyList GetGenericTypeParameters(this IMetho return genericTypeParameters.ToEquatableReadOnlyList(); } - /// Gets a list of constructor parameters for the given method symbol. + /// Gets a list of method parameters for the given method symbol. /// The to get the type parameters for. - /// An of strings representing the constructor parameters. - public static EquatableReadOnlyList GetConstructorParameters(this IMethodSymbol symbol) + /// The cancellation token. + /// An of strings representing the method parameters. + public static EquatableReadOnlyList GetMethodParameters(this IMethodSymbol symbol, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (symbol.Parameters.Length <= 0) - return EquatableReadOnlyList.Empty; + return EquatableReadOnlyList.Empty; - var constructorParameters = new List(); + var methodParameters = new List(); // ReSharper disable once ForCanBeConvertedToForeach // ReSharper disable once LoopCanBeConvertedToQuery for (var index = 0; index < symbol.Parameters.Length; index++) { - var constructorParameterSymbol = symbol.Parameters[index]; - var type = constructorParameterSymbol.Type.ToDisplayString(); - var name = constructorParameterSymbol.Name; - var constructorParameter = new ConstructorParameter(type, name); + var methodParameterSymbol = symbol.Parameters[index]; + var type = methodParameterSymbol.Type.ToDisplayString(); + var name = methodParameterSymbol.Name; + var methodParameter = new MethodParameter(type, name); - constructorParameters.Add(constructorParameter); + methodParameters.Add(methodParameter); } - return constructorParameters.ToEquatableReadOnlyList(); + return methodParameters.ToEquatableReadOnlyList(); } } \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs index a711ee8..643ccc9 100644 --- a/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs +++ b/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs @@ -1,7 +1,5 @@ using Microsoft.CodeAnalysis; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with named symbols. @@ -9,9 +7,12 @@ internal static class NamedSymbolExtensions { /// Gets the generic type parameters for the given named type symbol. /// The to get the type parameters for. + /// The cancellation token. /// An of strings representing the generic type parameter names. - public static EquatableReadOnlyList GetGenericTypeParameters(this INamedTypeSymbol symbol) + public static EquatableReadOnlyList GetGenericTypeParameters(this INamedTypeSymbol symbol, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (!symbol.IsGenericType) return EquatableReadOnlyList.Empty; diff --git a/src/AttributeSourceGenerator/Common/SymbolExtensions.cs b/src/AttributeSourceGenerator/Common/SymbolExtensions.cs index e441f9b..16c31f7 100644 --- a/src/AttributeSourceGenerator/Common/SymbolExtensions.cs +++ b/src/AttributeSourceGenerator/Common/SymbolExtensions.cs @@ -1,8 +1,6 @@ using AttributeSourceGenerator.Models; using Microsoft.CodeAnalysis; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with symbols. @@ -10,40 +8,39 @@ internal static class SymbolExtensions { /// Gets the for the given based on its type kind. /// The to get the for. + /// The cancellation token. /// A if the symbol can be mapped to a symbol type, otherwise null. - public static SymbolType GetSymbolType(this ISymbol symbol) + public static SymbolType GetSymbolType(this ISymbol symbol, CancellationToken cancellationToken) { - switch (symbol) + cancellationToken.ThrowIfCancellationRequested(); + + return symbol switch { - case ITypeSymbol { IsReferenceType: true } typeSymbol: + ITypeSymbol typeSymbol => typeSymbol switch { - if (typeSymbol.TypeKind == TypeKind.Interface) - return SymbolType.Interface; - if (typeSymbol.IsRecord) - return SymbolType.Record; - return SymbolType.Class; - } - case ITypeSymbol { IsValueType: true } typeSymbol: - { - if (typeSymbol.IsRecord) - return SymbolType.RecordStruct; - return SymbolType.Struct; - } - case IMethodSymbol: - return SymbolType.Method; - default: - throw new InvalidOperationException($"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly received an {nameof(ISymbol)} that was unsupported."); - } + { IsReferenceType: true, TypeKind: TypeKind.Interface } => SymbolType.Interface, + { IsReferenceType: true, IsRecord: true } => SymbolType.Record, + { IsReferenceType: true } => SymbolType.Class, + { IsValueType: true, IsRecord: true } => SymbolType.RecordStruct, + { IsValueType: true } => SymbolType.Struct, + _ => throw new InvalidOperationException($"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly received an {nameof(ISymbol)} that was unsupported.") + }, + IMethodSymbol => SymbolType.Method, + _ => throw new InvalidOperationException($"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly received an {nameof(ISymbol)} that was unsupported.") + }; } /// Gets a list of declarations representing the hierarchy containing the given symbol. /// The to get the containing declarations for. + /// The cancellation token. /// An of objects representing the hierarchy. - public static EquatableReadOnlyList GetContainingDeclarations(this ISymbol symbol) + public static EquatableReadOnlyList GetContainingDeclarations(this ISymbol symbol, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + var declarations = new Stack(); - BuildContainingSymbolHierarchy(symbol, in declarations); + BuildContainingSymbolHierarchy(symbol, in declarations, cancellationToken); return declarations.ToEquatableReadOnlyList(); } @@ -51,15 +48,18 @@ public static EquatableReadOnlyList GetContainingDeclarations(this /// Builds the hierarchy of containing symbols starting from the given symbol. /// The to start building the hierarchy from. /// A of objects to store the hierarchy. - private static void BuildContainingSymbolHierarchy(ISymbol symbol, in Stack declarations) + /// The cancellation token. + private static void BuildContainingSymbolHierarchy(ISymbol symbol, in Stack declarations, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + switch (symbol.ContainingSymbol) { case INamespaceSymbol namespaceSymbol: - BuildNamespaceHierarchy(namespaceSymbol, declarations); + BuildNamespaceHierarchy(namespaceSymbol, declarations, cancellationToken); break; case INamedTypeSymbol namedTypeSymbol: - BuildTypeHierarchy(namedTypeSymbol, in declarations); + BuildTypeHierarchy(namedTypeSymbol, declarations, cancellationToken); break; } } @@ -67,8 +67,11 @@ private static void BuildContainingSymbolHierarchy(ISymbol symbol, in StackBuilds the hierarchy of containing namespaces starting from the given namespace symbol. /// The to start building the hierarchy from. /// A of objects to store the hierarchy. - private static void BuildNamespaceHierarchy(INamespaceSymbol symbol, in Stack declarations) + /// The cancellation token. + private static void BuildNamespaceHierarchy(INamespaceSymbol symbol, in Stack declarations, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (!symbol.IsGlobalNamespace) { var namespaceDeclaration = new Declaration(DeclarationType.Namespace, symbol.Name, EquatableReadOnlyList.Empty); @@ -76,23 +79,26 @@ private static void BuildNamespaceHierarchy(INamespaceSymbol symbol, in StackBuilds the hierarchy of containing types starting from the given type symbol. /// The to start building the hierarchy from. /// A of objects to store the hierarchy. - private static void BuildTypeHierarchy(INamedTypeSymbol symbol, in Stack declarations) + /// The cancellation token. + private static void BuildTypeHierarchy(INamedTypeSymbol symbol, in Stack declarations, CancellationToken cancellationToken) { - var declarationType = symbol.GetDeclarationType(); + cancellationToken.ThrowIfCancellationRequested(); + + var declarationType = symbol.GetDeclarationType(cancellationToken); if (declarationType is null) return; - var genericTypeParameters = symbol.GetGenericTypeParameters(); + var genericTypeParameters = symbol.GetGenericTypeParameters(cancellationToken); var typeDeclaration = new Declaration(declarationType.Value, symbol.Name, genericTypeParameters); declarations.Push(typeDeclaration); - BuildContainingSymbolHierarchy(symbol, declarations); + BuildContainingSymbolHierarchy(symbol, declarations, cancellationToken); } } \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs index e14b6ae..8ab2138 100644 --- a/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs +++ b/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs @@ -1,7 +1,5 @@ using Microsoft.CodeAnalysis; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Common; /// Provides extension methods for working with type symbols. @@ -9,25 +7,20 @@ internal static class TypeSymbolExtensions { /// Gets the for the given based on its type kind. /// The to get the for. + /// The cancellation token. /// A if the symbol can be mapped to a declaration type, otherwise null. - public static DeclarationType? GetDeclarationType(this ITypeSymbol symbol) + public static DeclarationType? GetDeclarationType(this ITypeSymbol symbol, CancellationToken cancellationToken) { - if (symbol.IsReferenceType) - { - if (symbol.TypeKind == TypeKind.Interface) - return DeclarationType.Interface; - if (symbol.IsRecord) - return DeclarationType.Record; - return DeclarationType.Class; - } + cancellationToken.ThrowIfCancellationRequested(); - if (symbol.IsValueType) + return symbol switch { - if (symbol.IsRecord) - return DeclarationType.RecordStruct; - return DeclarationType.Struct; - } - - return null; + { IsReferenceType: true, TypeKind: TypeKind.Interface } => DeclarationType.Interface, + { IsReferenceType: true, IsRecord: true } => DeclarationType.Record, + { IsReferenceType: true } => DeclarationType.Class, + { IsValueType: true, IsRecord: true } => DeclarationType.RecordStruct, + { IsValueType: true } => DeclarationType.Struct, + _ => null + }; } -} +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/DeclarationType.cs b/src/AttributeSourceGenerator/DeclarationType.cs index d9462a7..b7c1920 100644 --- a/src/AttributeSourceGenerator/DeclarationType.cs +++ b/src/AttributeSourceGenerator/DeclarationType.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator; +namespace AttributeSourceGenerator; /// Specifies the kind of declaration. public enum DeclarationType diff --git a/src/AttributeSourceGenerator/FilterType.cs b/src/AttributeSourceGenerator/FilterType.cs index 78d5f3e..1e6f827 100644 --- a/src/AttributeSourceGenerator/FilterType.cs +++ b/src/AttributeSourceGenerator/FilterType.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator; +namespace AttributeSourceGenerator; /// Specifies the kind of filter. [Flags] @@ -26,7 +24,7 @@ public enum FilterType /// Only filter for attributes that are attached to methods. Method = 32, - + /// Filter for all supported attributes. All = Interface | Class | Record | Struct | RecordStruct | Method -} +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Models/ConstructorArgument.cs b/src/AttributeSourceGenerator/Models/ConstructorArgument.cs index b1903f5..446a2a7 100644 --- a/src/AttributeSourceGenerator/Models/ConstructorArgument.cs +++ b/src/AttributeSourceGenerator/Models/ConstructorArgument.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator.Models; +namespace AttributeSourceGenerator.Models; /// Represents a constructor argument. public readonly record struct ConstructorArgument diff --git a/src/AttributeSourceGenerator/Models/ConstructorParameter.cs b/src/AttributeSourceGenerator/Models/ConstructorParameter.cs deleted file mode 100644 index e6bc3b6..0000000 --- a/src/AttributeSourceGenerator/Models/ConstructorParameter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator.Models; - -/// Represents a constructor parameter -public readonly record struct ConstructorParameter -{ - /// Gets the type of the parameter. - public string Type { get; } - - /// Gets the name of the parameter. - public string Name { get; } - - /// Initializes a new instance of the record with the specified type and name. - /// The type of the argument. - /// The name of the argument. - internal ConstructorParameter(string type, string name) - { - Type = type; - Name = name; - } -} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Models/Declaration.cs b/src/AttributeSourceGenerator/Models/Declaration.cs index 23ffa13..4cf65b2 100644 --- a/src/AttributeSourceGenerator/Models/Declaration.cs +++ b/src/AttributeSourceGenerator/Models/Declaration.cs @@ -1,7 +1,5 @@ using AttributeSourceGenerator.Common; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Models; /// Represents a declaration. diff --git a/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs b/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs index 6a50892..3e80232 100644 --- a/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs +++ b/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator.Models; +namespace AttributeSourceGenerator.Models; /// Represents a generic type argument. public readonly record struct GenericTypeArgument diff --git a/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs b/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs index 73440f8..7cc259c 100644 --- a/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs +++ b/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs @@ -1,7 +1,5 @@ using AttributeSourceGenerator.Common; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator.Models; /// Represents a marker attribute. diff --git a/src/AttributeSourceGenerator/Models/MethodParameter.cs b/src/AttributeSourceGenerator/Models/MethodParameter.cs new file mode 100644 index 0000000..e8a6259 --- /dev/null +++ b/src/AttributeSourceGenerator/Models/MethodParameter.cs @@ -0,0 +1,20 @@ +namespace AttributeSourceGenerator.Models; + +/// Represents a method parameter +public readonly record struct MethodParameter +{ + /// Gets the type of the parameter. + public string Type { get; } + + /// Gets the name of the parameter. + public string Name { get; } + + /// Initializes a new instance of the record with the specified type and name. + /// The type of the argument. + /// The name of the argument. + internal MethodParameter(string type, string name) + { + Type = type; + Name = name; + } +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Models/NamedArgument.cs b/src/AttributeSourceGenerator/Models/NamedArgument.cs index f7f6d58..0baa5df 100644 --- a/src/AttributeSourceGenerator/Models/NamedArgument.cs +++ b/src/AttributeSourceGenerator/Models/NamedArgument.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator.Models; +namespace AttributeSourceGenerator.Models; /// Represents a named argument. public readonly record struct NamedArgument diff --git a/src/AttributeSourceGenerator/Source.cs b/src/AttributeSourceGenerator/Source.cs new file mode 100644 index 0000000..14703a9 --- /dev/null +++ b/src/AttributeSourceGenerator/Source.cs @@ -0,0 +1,22 @@ +namespace AttributeSourceGenerator; + +/// Represents source. +public readonly record struct Source +{ + /// Gets the name of the source. + /// '.g.cs' will be appended to this name. + /// + public string Name { get; } + + /// Gets the text of the source. + public string Text { get; } + + /// Initializes a new instance of the record with the specified name and text. + /// The name of the source. + /// The text of the source. + public Source(string name, string text) + { + Name = name; + Text = text; + } +} \ No newline at end of file diff --git a/src/AttributeSourceGenerator/Symbol.cs b/src/AttributeSourceGenerator/Symbol.cs index 748d0f1..60c7658 100644 --- a/src/AttributeSourceGenerator/Symbol.cs +++ b/src/AttributeSourceGenerator/Symbol.cs @@ -1,8 +1,6 @@ using AttributeSourceGenerator.Common; using AttributeSourceGenerator.Models; -// ReSharper disable CheckNamespace - namespace AttributeSourceGenerator; /// Represents a symbol. @@ -14,7 +12,7 @@ public readonly record struct Symbol /// Gets a read-only list of the declarations that contain this symbol. /// The list is populated in order, from the outer declaration furthest away to the symbol towards the inner declaration closest to the symbol. /// - public EquatableReadOnlyList ContainingDeclarations { get; init; } + public EquatableReadOnlyList ContainingDeclarations { get; } /// Gets the type of symbol. public SymbolType SymbolType { get; } @@ -25,8 +23,8 @@ public readonly record struct Symbol /// Gets a read-only list of generic parameters for generic symbols, or an empty list otherwise. public EquatableReadOnlyList GenericTypeParameters { get; } - /// Gets a read-only list of constructor parameters for method symbols, or an empty list otherwise. - public EquatableReadOnlyList ConstructorParameters { get; } + /// Gets a read-only list of method parameters for method symbols, or an empty list otherwise. + public EquatableReadOnlyList MethodParameters { get; } /// Gets the return type for method symbols, or an empty string otherwise. public string ReturnType { get; } @@ -42,6 +40,7 @@ public string FullyQualifiedName return name; var path = ContainingDeclarations.ToFullyQualifiedName(); + return $"{path}.{name}"; } } @@ -55,6 +54,7 @@ public string Namespace return ""; var ns = ContainingDeclarations.ToNamespace(); + return ns; } } @@ -65,17 +65,17 @@ public string Namespace /// The type of symbol. /// The name of the symbol. /// The list of the generic parameters for the symbol. - /// The list of the constructor parameters for the symbol. + /// The list of the method parameters for the symbol. /// The return type for the symbol. internal Symbol(MarkerAttributeData markerAttribute, EquatableReadOnlyList containingDeclarations, SymbolType symbolType, string name, EquatableReadOnlyList genericTypeParameters, - EquatableReadOnlyList constructorParameters, string returnType) + EquatableReadOnlyList methodParameters, string returnType) { MarkerAttribute = markerAttribute; ContainingDeclarations = containingDeclarations; SymbolType = symbolType; Name = name; GenericTypeParameters = genericTypeParameters; - ConstructorParameters = constructorParameters; + MethodParameters = methodParameters; ReturnType = returnType; } @@ -85,17 +85,17 @@ internal Symbol(MarkerAttributeData markerAttribute, EquatableReadOnlyListThe type of symbol. /// Receives the name of the symbol. /// Receives the read-only list of the generic parameters for the symbol. - /// Receives the read-only list of the constructor parameters for the symbol. + /// Receives the read-only list of the method parameters for the symbol. /// Receives the return type for the symbol. public void Deconstruct(out MarkerAttributeData markerAttribute, out EquatableReadOnlyList containingDeclarations, out SymbolType symbolType, out string name, out EquatableReadOnlyList genericParameters, - out EquatableReadOnlyList constructorParameters, out string returnType) + out EquatableReadOnlyList methodParameters, out string returnType) { markerAttribute = MarkerAttribute; containingDeclarations = ContainingDeclarations; symbolType = SymbolType; name = Name; genericParameters = GenericTypeParameters; - constructorParameters = ConstructorParameters; + methodParameters = MethodParameters; returnType = ReturnType; } } \ No newline at end of file diff --git a/src/AttributeSourceGenerator/SymbolType.cs b/src/AttributeSourceGenerator/SymbolType.cs index 1a5b5eb..e672650 100644 --- a/src/AttributeSourceGenerator/SymbolType.cs +++ b/src/AttributeSourceGenerator/SymbolType.cs @@ -1,6 +1,4 @@ -// ReSharper disable CheckNamespace - -namespace AttributeSourceGenerator; +namespace AttributeSourceGenerator; /// Specifies the type of symbol. public enum SymbolType