diff --git a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs
index cb6b674..f70e61c 100644
--- a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs
+++ b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorBase.cs
@@ -1,125 +1,129 @@
using System.Text;
using AttributeSourceGenerator.Common;
+using AttributeSourceGenerator.Models;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
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.
public abstract class AttributeIncrementalGeneratorBase : IIncrementalGenerator
{
- protected abstract string AttributeFullName { get; }
- protected abstract string AttributeSource { get; }
- protected abstract FilterType AttributeFilter { get; }
- protected abstract Func GenerateSourceForSymbol { get; }
+ private readonly AttributeIncrementalGeneratorConfiguration _configuration;
- public void Initialize(IncrementalGeneratorInitializationContext context)
+ /// Initializes a new instance of the class with the given configuration initializer.
+ /// The configuration for the generator.
+ protected AttributeIncrementalGeneratorBase(AttributeIncrementalGeneratorConfiguration configuration)
{
- context.RegisterPostInitializationOutput(AddSource);
-
- var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName(AttributeFullName, Filter, Transform);
-
- context.RegisterSourceOutput(pipeline, GenerateSource);
+ _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
- private void AddSource(IncrementalGeneratorPostInitializationContext ctx)
+ /// Initializes a new instance of the class with the given configuration initializer.
+ /// A function that provides the configuration for the generator.
+ protected AttributeIncrementalGeneratorBase(Func initializer)
{
- ctx.AddSource($"{AttributeFullName}.g.cs", SourceText.From(AttributeSource, Encoding.UTF8));
- }
+ if (initializer is null)
+ throw new ArgumentNullException(nameof(initializer));
- private bool Filter(SyntaxNode syntaxNode, CancellationToken _)
- {
- return AttributeFilter switch
- {
- FilterType.Interface => syntaxNode is InterfaceDeclarationSyntax,
- FilterType.Class => syntaxNode is ClassDeclarationSyntax,
- FilterType.Record => syntaxNode is RecordDeclarationSyntax syntax && syntax.Kind() == SyntaxKind.ClassDeclaration,
- FilterType.Struct => syntaxNode is StructDeclarationSyntax,
- FilterType.RecordStruct => syntaxNode is RecordDeclarationSyntax syntax && syntax.Kind() == SyntaxKind.StructDeclaration,
- FilterType.Method => syntaxNode is MethodDeclarationSyntax,
- _ => true,
- };
+ _configuration = initializer();
}
- private static Symbol Transform(GeneratorAttributeSyntaxContext context, CancellationToken _)
+ /// Initializes the incremental generator.
+ /// The initialization context.
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- var symbol = context.TargetSymbol;
- var containingDeclarations = BuildHierarchy(symbol);
- var symbolName = symbol.Name;
- return new Symbol(symbolName, containingDeclarations);
- }
+ context.RegisterPostInitializationOutput(initializationContext => AddSource(initializationContext, _configuration.AttributeFullyQualifiedName, _configuration.AttributeSource));
- private void GenerateSource(SourceProductionContext context, Symbol symbol)
- {
- var sourceText = GenerateSourceForSymbol(symbol);
- context.AddSource($"{symbol.FullName}.g.cs", sourceText);
+ var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName(_configuration.AttributeFullyQualifiedName, (syntaxNode, _) => Filter(syntaxNode, _configuration.SymbolFilter), (syntaxContext, _) => Transform(syntaxContext));
+
+ context.RegisterSourceOutput(pipeline, (productionContext, symbol) => GenerateSourceForSymbol(productionContext, symbol, _configuration.SourceGenerator));
}
- private static EquatableReadOnlyList BuildHierarchy(ISymbol symbol)
+ /// Adds a source file 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)
{
- var declarations = new Stack();
- BuildContainingSymbolHierarchy(symbol, in declarations);
- return declarations.ToEquatableReadOnlyList();
+ if (source?.Length > 0)
+ context.AddSource($"{name}.g.cs", SourceText.From(source, Encoding.UTF8));
}
- private static void BuildContainingSymbolHierarchy(ISymbol symbol, in Stack declarations)
+ /// Determines whether a syntax node should be included based on the filter settings.
+ /// The syntax node to filter.
+ /// The filter configuration.
+ /// if the syntax node should be included, otherwise .
+ private static bool Filter(SyntaxNode syntaxNode, FilterType filterType)
{
- if (symbol.ContainingType is not null)
- BuildTypeHierarchy(symbol.ContainingType, in declarations);
- else if (symbol.ContainingNamespace is not null)
- BuildNamespaceHierarchy(symbol.ContainingNamespace, declarations);
+ var filter = filterType == FilterType.None ? FilterType.All : filterType;
+
+ if (filter.HasFlag(FilterType.Interface) && syntaxNode is InterfaceDeclarationSyntax)
+ return true;
+ if (filter.HasFlag(FilterType.Class) && syntaxNode is ClassDeclarationSyntax)
+ return true;
+ if (filter.HasFlag(FilterType.Record) && syntaxNode is RecordDeclarationSyntax recordDeclaration && recordDeclaration.Kind() == SyntaxKind.ClassDeclaration)
+ return true;
+ if (filter.HasFlag(FilterType.Struct) && syntaxNode is StructDeclarationSyntax)
+ return true;
+ if (filter.HasFlag(FilterType.RecordStruct) && syntaxNode is RecordDeclarationSyntax recordStructDeclaration && recordStructDeclaration.Kind() == SyntaxKind.StructDeclaration)
+ return true;
+ if (filter.HasFlag(FilterType.Method) && syntaxNode is MethodDeclarationSyntax)
+ return true;
+
+ return false;
}
- private static void BuildTypeHierarchy(INamedTypeSymbol symbol, in Stack declarations)
+ /// Transforms a generator attribute syntax context into a symbol for source generation.
+ /// The generator attribute syntax context.
+ /// The transformed symbol.
+ private static Symbol Transform(GeneratorAttributeSyntaxContext context)
{
- DeclarationType? declarationType = null;
-
-
- if (symbol.IsReferenceType)
- {
- if (symbol.TypeKind == TypeKind.Interface)
- declarationType = DeclarationType.Interface;
- else if (symbol.IsRecord)
- declarationType = DeclarationType.Record;
- else
- declarationType = DeclarationType.Class;
- }
- else if (symbol.IsValueType)
+ 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 symbolName = targetSymbol.Name;
+ EquatableReadOnlyList genericTypeParameters;
+ EquatableReadOnlyList constructorParameters;
+ string returnType;
+ switch (targetSymbol)
{
- if (symbol.IsRecord)
- declarationType = DeclarationType.RecordStruct;
- else
- declarationType = DeclarationType.Struct;
+ case INamedTypeSymbol namedTypeSymbol:
+ genericTypeParameters = namedTypeSymbol.GetGenericTypeParameters();
+ constructorParameters = EquatableReadOnlyList.Empty;
+ returnType = "";
+ break;
+ case IMethodSymbol methodSymbol:
+ genericTypeParameters = methodSymbol.GetGenericTypeParameters();
+ constructorParameters = methodSymbol.GetConstructorParameters();
+ returnType = methodSymbol.ReturnType.ToDisplayString();
+ break;
+ default:
+ genericTypeParameters = EquatableReadOnlyList.Empty;
+ constructorParameters = EquatableReadOnlyList.Empty;
+ returnType = "";
+ break;
}
- if (declarationType is null)
- return;
+ var symbol = new Symbol(markerAttribute, containingDeclarations, symbolType, symbolName, genericTypeParameters, constructorParameters, returnType);
- var genericParameters = EquatableReadOnlyList.Empty;
- if (symbol.IsGenericType)
- {
- var typeParameters = new List();
- foreach (var typeParameter in symbol.TypeParameters)
- typeParameters.Add(typeParameter.Name);
- genericParameters = new EquatableReadOnlyList(typeParameters);
- }
-
- var typeDeclaration = new Declaration(declarationType.Value, symbol.Name, genericParameters);
- declarations.Push(typeDeclaration);
-
- BuildContainingSymbolHierarchy(symbol, declarations);
+ return symbol;
}
- private static void BuildNamespaceHierarchy(INamespaceSymbol symbol, in Stack declarations)
+ /// 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)
{
- if (!symbol.IsGlobalNamespace)
- {
- var namespaceDeclaration = new Declaration(DeclarationType.Namespace, symbol.Name, EquatableReadOnlyList.Empty);
- declarations.Push(namespaceDeclaration);
- }
-
- if (symbol.ContainingNamespace is not null && !symbol.ContainingNamespace.IsGlobalNamespace)
- BuildNamespaceHierarchy(symbol.ContainingNamespace, declarations);
+ var sourceText = generate(symbol);
+ context.AddSource($"{symbol.FullyQualifiedName}.g.cs", sourceText);
}
-}
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs
new file mode 100644
index 0000000..a808fe7
--- /dev/null
+++ b/src/AttributeSourceGenerator/AttributeIncrementalGeneratorConfiguration.cs
@@ -0,0 +1,24 @@
+// ReSharper disable CheckNamespace
+
+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 source for the attribute.
+ public string? AttributeSource { 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; }
+
+ /// Initializes a new instance of the class
+ public AttributeIncrementalGeneratorConfiguration()
+ {
+ }
+}
diff --git a/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj b/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj
index c97af2f..452b571 100644
--- a/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj
+++ b/src/AttributeSourceGenerator/AttributeSourceGenerator.csproj
@@ -6,7 +6,7 @@
enable
AttributeSourceGenerator
latest
- 8.0.1
+ 8.0.2
AttributeSourceGenerator
Jean-Sebastien Carle
A simple attribute-based Roslyn incremental source generator base class for .NET.
@@ -18,8 +18,8 @@
https://github.com/jscarle/AttributeSourceGenerator
git
attribute source-generator attribute-based source-generators
- 8.0.1.0
- 8.0.1.0
+ 8.0.2.0
+ 8.0.2.0
en-US
true
snupkg
@@ -31,12 +31,12 @@
-
-
+
+
-
+
True
\
diff --git a/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs b/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs
new file mode 100644
index 0000000..8505b9d
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/DeclarationExtensions.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using AttributeSourceGenerator.Models;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with declarations.
+internal static class DeclarationExtensions
+{
+ /// Converts a list of declarations to their corresponding namespace.
+ /// The list of declarations to convert.
+ /// The namespace represented by the declarations.
+ public static string ToNamespace(this EquatableReadOnlyList declarations)
+ {
+ var builder = new StringBuilder();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (var index = 0; index < declarations.Count; index++)
+ {
+ var declaration = declarations[index];
+ if (declaration.DeclarationType != DeclarationType.Namespace)
+ continue;
+
+ if (builder.Length > 0)
+ builder.Append('.');
+ builder.Append(declaration.Name);
+ }
+
+ return builder.ToString();
+ }
+
+ /// Converts a list of declarations to their fully qualified name.
+ /// The list of declarations to convert.
+ /// The fully qualified name represented by the declarations.
+ public static string ToFullyQualifiedName(this EquatableReadOnlyList declarations)
+ {
+ var builder = new StringBuilder();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (var index = 0; index < declarations.Count; index++)
+ {
+ var declaration = declarations[index];
+
+ if (builder.Length > 0)
+ builder.Append('.');
+ builder.Append(declaration.Name);
+
+ if (declaration.GenericParameters.Count <= 0)
+ continue;
+
+ builder.Append('`');
+ builder.Append(declaration.GenericParameters.Count);
+ }
+
+ return builder.ToString();
+ }
+}
diff --git a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList.cs b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList.cs
index 7a2cff2..0f4f242 100644
--- a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList.cs
+++ b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList.cs
@@ -1,7 +1,9 @@
-namespace AttributeSourceGenerator.Common;
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
/// Provides extension methods to convert various collections to an .
-public static class EquatableReadOnlyList
+internal static class EquatableReadOnlyList
{
/// Converts an to an .
/// The to convert.
diff --git a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs
index ac08110..71ed855 100644
--- a/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs
+++ b/src/AttributeSourceGenerator/Common/EquatableReadOnlyList`1.cs
@@ -1,5 +1,7 @@
using System.Collections;
+// ReSharper disable CheckNamespace
+
namespace AttributeSourceGenerator.Common;
/// A read-only list that implements for value-based equality comparisons.
@@ -7,16 +9,17 @@ namespace AttributeSourceGenerator.Common;
public readonly struct EquatableReadOnlyList : IEquatable>, IReadOnlyList
{
/// Gets an empty .
- public static EquatableReadOnlyList Empty { get; } = new([]);
+ internal static EquatableReadOnlyList Empty { get; } = new([]);
+
+ /// Gets the number of elements in the list.
+ public int Count => Collection.Count;
/// Gets the element at the specified index.
/// The index of the element to get.
/// The element at the specified index.
public T this[int index] => Collection[index];
- /// Gets the number of elements in the list.
- public int Count => Collection.Count;
-
+ private IReadOnlyList Collection => _collection ?? [];
private readonly IReadOnlyList? _collection;
/// Creates a new from an existing .
@@ -26,8 +29,6 @@ internal EquatableReadOnlyList(IReadOnlyList? collection)
_collection = collection;
}
- private IReadOnlyList Collection => _collection ?? [];
-
/// Determines whether this instance and another object are equal.
/// The object to compare with this instance.
/// True if the objects are equal, false otherwise.
@@ -68,7 +69,8 @@ public override int GetHashCode()
{
var hashCode = new HashCode();
- foreach (var item in Collection) hashCode.Add(item);
+ foreach (var item in Collection)
+ hashCode.Add(item);
return hashCode.ToHashCode();
}
diff --git a/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs b/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs
new file mode 100644
index 0000000..f9fdcb5
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/GeneratorAttributeSyntaxContextExtensions.cs
@@ -0,0 +1,120 @@
+using AttributeSourceGenerator.Models;
+using Microsoft.CodeAnalysis;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with GeneratorAttributeSyntaxContext.
+internal static class GeneratorAttributeSyntaxContextExtensions
+{
+ /// Gets the marker attribute data for the given context.
+ /// The to get the marker attribute for.
+ /// The representing the marker attribute.
+ public static MarkerAttributeData GetMarkerAttribute(this GeneratorAttributeSyntaxContext context)
+ {
+ var attribute = context.Attributes.First();
+
+ var attributeClass = attribute.AttributeClass;
+ if (attributeClass is null)
+ throw new InvalidOperationException(
+ $"{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 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.
+ /// A list of representing the generic type arguments.
+ private static EquatableReadOnlyList GetGenericTypeArguments(this AttributeData attribute)
+ {
+ var attributeClass = attribute.AttributeClass;
+ if (attributeClass is null)
+ throw new InvalidOperationException(
+ $"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly found that {nameof(AttributeData.AttributeClass)} was null while transforming a {nameof(GeneratorAttributeSyntaxContext.TargetSymbol)}.");
+
+ if (!attributeClass.IsGenericType)
+ return EquatableReadOnlyList.Empty;
+
+ var genericTypeArguments = new List();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < attributeClass.TypeParameters.Length; index++)
+ {
+ var typeParameterSymbol = attributeClass.TypeParameters[index];
+ var name = typeParameterSymbol.Name;
+ var value = attributeClass.TypeArguments[index].ToDisplayString();
+ var genericTypeArgument = new GenericTypeArgument(name, value);
+
+ genericTypeArguments.Add(genericTypeArgument);
+ }
+
+ return genericTypeArguments.ToEquatableReadOnlyList();
+ }
+
+ /// Gets a list of constructor arguments for the given attribute.
+ /// The to get the constructor arguments for.
+ /// A list of representing the constructor arguments.
+ private static EquatableReadOnlyList GetConstructorArguments(this AttributeData attribute)
+ {
+ var attributeConstructor = attribute.AttributeConstructor;
+ if (attributeConstructor is null)
+ throw new InvalidOperationException(
+ $"{nameof(AttributeIncrementalGeneratorBase)} unexpectedly found that {nameof(AttributeData.AttributeConstructor)} was null while transforming a {nameof(GeneratorAttributeSyntaxContext.TargetSymbol)}.");
+
+ if (attributeConstructor.Parameters.Length <= 0)
+ return EquatableReadOnlyList.Empty;
+
+ var constructorArguments = new List();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < attributeConstructor.Parameters.Length; index++)
+ {
+ var constructorParameterSymbol = attributeConstructor.Parameters[index];
+ var constructorArgumentSymbol = attribute.ConstructorArguments[index];
+ var type = constructorParameterSymbol.Type.ToDisplayString();
+ var name = constructorParameterSymbol.Name;
+ var value = constructorArgumentSymbol.Value?.ToString();
+ var constructorArgument = new ConstructorArgument(type, name, value);
+
+ constructorArguments.Add(constructorArgument);
+ }
+
+ return constructorArguments.ToEquatableReadOnlyList();
+ }
+
+ /// Gets a list of named arguments for the given attribute.
+ /// The to get the named arguments for.
+ /// A list of representing the named arguments.
+ private static EquatableReadOnlyList GetNamedArguments(this AttributeData attribute)
+ {
+ if (attribute.NamedArguments.Length <= 0)
+ return EquatableReadOnlyList.Empty;
+
+ var namedArguments = new List();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < attribute.NamedArguments.Length; index++)
+ {
+ var namedArgumentSymbol = attribute.NamedArguments[index].Value;
+ var type = namedArgumentSymbol.Type?.ToDisplayString() ?? "";
+ var name = attribute.NamedArguments[index].Key;
+ var value = namedArgumentSymbol.Value?.ToString();
+ var namedArgument = new NamedArgument(type, name, value);
+
+ namedArguments.Add(namedArgument);
+ }
+
+ return namedArguments.ToEquatableReadOnlyList();
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs
new file mode 100644
index 0000000..23bc87a
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/MethodSymbolExtensions.cs
@@ -0,0 +1,56 @@
+using AttributeSourceGenerator.Models;
+using Microsoft.CodeAnalysis;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with method symbols.
+internal static class MethodSymbolExtensions
+{
+ /// Gets the generic type parameters for the given method symbol.
+ /// The to get the type parameters for.
+ /// An of strings representing the generic type parameter names.
+ public static EquatableReadOnlyList GetGenericTypeParameters(this IMethodSymbol symbol)
+ {
+ if (!symbol.IsGenericMethod)
+ return EquatableReadOnlyList.Empty;
+
+ var genericTypeParameters = new List();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < symbol.TypeParameters.Length; index++)
+ {
+ var typeParameter = symbol.TypeParameters[index];
+ genericTypeParameters.Add(typeParameter.Name);
+ }
+
+ return genericTypeParameters.ToEquatableReadOnlyList();
+ }
+
+ /// Gets a list of constructor 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)
+ {
+ if (symbol.Parameters.Length <= 0)
+ return EquatableReadOnlyList.Empty;
+
+ var constructorParameters = 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);
+
+ constructorParameters.Add(constructorParameter);
+ }
+
+ return constructorParameters.ToEquatableReadOnlyList();
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs
new file mode 100644
index 0000000..a711ee8
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/NamedSymbolExtensions.cs
@@ -0,0 +1,30 @@
+using Microsoft.CodeAnalysis;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with named symbols.
+internal static class NamedSymbolExtensions
+{
+ /// Gets the generic type parameters for the given named type symbol.
+ /// The to get the type parameters for.
+ /// An of strings representing the generic type parameter names.
+ public static EquatableReadOnlyList GetGenericTypeParameters(this INamedTypeSymbol symbol)
+ {
+ if (!symbol.IsGenericType)
+ return EquatableReadOnlyList.Empty;
+
+ var genericTypeParameters = new List();
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < symbol.TypeParameters.Length; index++)
+ {
+ var typeParameter = symbol.TypeParameters[index];
+ genericTypeParameters.Add(typeParameter.Name);
+ }
+
+ return genericTypeParameters.ToEquatableReadOnlyList();
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Common/SymbolExtensions.cs b/src/AttributeSourceGenerator/Common/SymbolExtensions.cs
new file mode 100644
index 0000000..e441f9b
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/SymbolExtensions.cs
@@ -0,0 +1,98 @@
+using AttributeSourceGenerator.Models;
+using Microsoft.CodeAnalysis;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with symbols.
+internal static class SymbolExtensions
+{
+ /// Gets the for the given based on its type kind.
+ /// The to get the for.
+ /// A if the symbol can be mapped to a symbol type, otherwise null.
+ public static SymbolType GetSymbolType(this ISymbol symbol)
+ {
+ switch (symbol)
+ {
+ case ITypeSymbol { IsReferenceType: true } typeSymbol:
+ {
+ 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.");
+ }
+ }
+
+ /// Gets a list of declarations representing the hierarchy containing the given symbol.
+ /// The to get the containing declarations for.
+ /// An of objects representing the hierarchy.
+ public static EquatableReadOnlyList GetContainingDeclarations(this ISymbol symbol)
+ {
+ var declarations = new Stack();
+
+ BuildContainingSymbolHierarchy(symbol, in declarations);
+
+ return declarations.ToEquatableReadOnlyList();
+ }
+
+ /// 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)
+ {
+ switch (symbol.ContainingSymbol)
+ {
+ case INamespaceSymbol namespaceSymbol:
+ BuildNamespaceHierarchy(namespaceSymbol, declarations);
+ break;
+ case INamedTypeSymbol namedTypeSymbol:
+ BuildTypeHierarchy(namedTypeSymbol, in declarations);
+ break;
+ }
+ }
+
+ /// Builds 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)
+ {
+ if (!symbol.IsGlobalNamespace)
+ {
+ var namespaceDeclaration = new Declaration(DeclarationType.Namespace, symbol.Name, EquatableReadOnlyList.Empty);
+ declarations.Push(namespaceDeclaration);
+ }
+
+ if (symbol.ContainingNamespace is not null && !symbol.ContainingNamespace.IsGlobalNamespace)
+ BuildNamespaceHierarchy(symbol.ContainingNamespace, declarations);
+ }
+
+ /// Builds 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)
+ {
+ var declarationType = symbol.GetDeclarationType();
+ if (declarationType is null)
+ return;
+
+ var genericTypeParameters = symbol.GetGenericTypeParameters();
+
+ var typeDeclaration = new Declaration(declarationType.Value, symbol.Name, genericTypeParameters);
+ declarations.Push(typeDeclaration);
+
+ BuildContainingSymbolHierarchy(symbol, declarations);
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs b/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs
new file mode 100644
index 0000000..e14b6ae
--- /dev/null
+++ b/src/AttributeSourceGenerator/Common/TypeSymbolExtensions.cs
@@ -0,0 +1,33 @@
+using Microsoft.CodeAnalysis;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Common;
+
+/// Provides extension methods for working with type symbols.
+internal static class TypeSymbolExtensions
+{
+ /// Gets the for the given based on its type kind.
+ /// The to get the for.
+ /// A if the symbol can be mapped to a declaration type, otherwise null.
+ public static DeclarationType? GetDeclarationType(this ITypeSymbol symbol)
+ {
+ if (symbol.IsReferenceType)
+ {
+ if (symbol.TypeKind == TypeKind.Interface)
+ return DeclarationType.Interface;
+ if (symbol.IsRecord)
+ return DeclarationType.Record;
+ return DeclarationType.Class;
+ }
+
+ if (symbol.IsValueType)
+ {
+ if (symbol.IsRecord)
+ return DeclarationType.RecordStruct;
+ return DeclarationType.Struct;
+ }
+
+ return null;
+ }
+}
diff --git a/src/AttributeSourceGenerator/DeclarationType.cs b/src/AttributeSourceGenerator/DeclarationType.cs
index b5c00d5..d9462a7 100644
--- a/src/AttributeSourceGenerator/DeclarationType.cs
+++ b/src/AttributeSourceGenerator/DeclarationType.cs
@@ -1,4 +1,6 @@
-namespace AttributeSourceGenerator;
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator;
/// Specifies the kind of declaration.
public enum DeclarationType
@@ -20,4 +22,4 @@ public enum DeclarationType
/// Represents a record struct declaration.
RecordStruct = 5
-}
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/FilterType.cs b/src/AttributeSourceGenerator/FilterType.cs
index 35b037b..78d5f3e 100644
--- a/src/AttributeSourceGenerator/FilterType.cs
+++ b/src/AttributeSourceGenerator/FilterType.cs
@@ -1,26 +1,32 @@
-namespace AttributeSourceGenerator;
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator;
/// Specifies the kind of filter.
+[Flags]
public enum FilterType
{
/// Do not filter.
None = 0,
- /// Filter for interfaces only.
+ /// Only filter for attributes that are attached to interfaces.
Interface = 1,
- /// Filter for classes only.
+ /// Only filter for attributes that are attached to classes.
Class = 2,
- /// Filter for records only.
- Record = 3,
+ /// Only filter for attributes that are attached to records.
+ Record = 4,
- /// Filter for structs only.
- Struct = 4,
+ /// Only filter for attributes that are attached to structs.
+ Struct = 8,
- /// Filter for record structs only.
- RecordStruct = 5,
+ /// Only filter for attributes that are attached to record structs.
+ RecordStruct = 16,
- /// Filter for methods only.
- Method = 6
+ /// Only filter for attributes that are attached to methods.
+ Method = 32,
+
+ /// Filter for all supported attributes.
+ All = Interface | Class | Record | Struct | RecordStruct | Method
}
diff --git a/src/AttributeSourceGenerator/Models/ConstructorArgument.cs b/src/AttributeSourceGenerator/Models/ConstructorArgument.cs
new file mode 100644
index 0000000..b1903f5
--- /dev/null
+++ b/src/AttributeSourceGenerator/Models/ConstructorArgument.cs
@@ -0,0 +1,27 @@
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Models;
+
+/// Represents a constructor argument.
+public readonly record struct ConstructorArgument
+{
+ /// Gets the type of the argument.
+ public string Type { get; }
+
+ /// Gets the name of the argument.
+ public string Name { get; }
+
+ /// Gets the value of the argument.
+ public string? Value { get; }
+
+ /// Initializes a new instance of the record with the specified type, name, and value.
+ /// The type of the argument.
+ /// The name of the argument.
+ /// The value of the argument.
+ internal ConstructorArgument(string type, string name, string? value)
+ {
+ Type = type;
+ Name = name;
+ Value = value;
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Models/ConstructorParameter.cs b/src/AttributeSourceGenerator/Models/ConstructorParameter.cs
new file mode 100644
index 0000000..e6bc3b6
--- /dev/null
+++ b/src/AttributeSourceGenerator/Models/ConstructorParameter.cs
@@ -0,0 +1,22 @@
+// 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/Declaration.cs b/src/AttributeSourceGenerator/Models/Declaration.cs
similarity index 94%
rename from src/AttributeSourceGenerator/Declaration.cs
rename to src/AttributeSourceGenerator/Models/Declaration.cs
index b954a5c..23ffa13 100644
--- a/src/AttributeSourceGenerator/Declaration.cs
+++ b/src/AttributeSourceGenerator/Models/Declaration.cs
@@ -1,8 +1,10 @@
using AttributeSourceGenerator.Common;
-namespace AttributeSourceGenerator;
+// ReSharper disable CheckNamespace
-/// Represents a declaration with information about its type, name, and generic parameters.
+namespace AttributeSourceGenerator.Models;
+
+/// Represents a declaration.
public readonly record struct Declaration
{
/// Gets the type of declaration.
@@ -56,4 +58,4 @@ public void Deconstruct(out DeclarationType declarationType, out string name, ou
name = Name;
genericParameters = GenericParameters;
}
-}
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs b/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs
new file mode 100644
index 0000000..6a50892
--- /dev/null
+++ b/src/AttributeSourceGenerator/Models/GenericTypeArgument.cs
@@ -0,0 +1,22 @@
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Models;
+
+/// Represents a generic type argument.
+public readonly record struct GenericTypeArgument
+{
+ /// Gets the name of the argument.
+ public string Name { get; }
+
+ /// Gets the value of the argument.
+ public string? Value { get; }
+
+ /// Initializes a new instance of the record with the specified name and value.
+ /// The name of the argument.
+ /// The value of the argument.
+ internal GenericTypeArgument(string name, string? value)
+ {
+ Name = name;
+ Value = value;
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs b/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs
new file mode 100644
index 0000000..73440f8
--- /dev/null
+++ b/src/AttributeSourceGenerator/Models/MarkerAttributeData.cs
@@ -0,0 +1,47 @@
+using AttributeSourceGenerator.Common;
+
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Models;
+
+/// Represents a marker attribute.
+public readonly record struct MarkerAttributeData
+{
+ /// Gets the name of the attribute.
+ public string Name { get; }
+
+ /// Gets a read-only list of the generic type arguments for the attribute, or an empty list otherwise.
+ public EquatableReadOnlyList GenericTypeArguments { get; }
+
+ /// Gets a read-only list of the constructor arguments for the attribute, or an empty list otherwise.
+ public EquatableReadOnlyList ConstructorArguments { get; }
+
+ /// Gets a read-only list of the named arguments for the attribute, or an empty list otherwise.
+ public EquatableReadOnlyList NamedArguments { get; }
+
+ /// Initializes a new instance of the record with the specified name, generic type arguments, constructor arguments, and named arguments.
+ /// The name of the attribute.
+ /// The list of generic type arguments for the attribute.
+ /// The list of constructor arguments for the attribute.
+ /// The list of named arguments for the attribute.
+ internal MarkerAttributeData(string name, EquatableReadOnlyList genericTypeArguments, EquatableReadOnlyList constructorArguments, EquatableReadOnlyList namedArguments)
+ {
+ Name = name;
+ GenericTypeArguments = genericTypeArguments;
+ ConstructorArguments = constructorArguments;
+ NamedArguments = namedArguments;
+ }
+
+ /// Deconstructs the attribute into its constituent parts.
+ /// Receives the name of the attribute.
+ /// The list of generic type arguments for the attribute.
+ /// The list of constructor arguments for the attribute.
+ /// The list of named arguments for the attribute.
+ public void Deconstruct(out string name, out EquatableReadOnlyList genericTypeArguments, out EquatableReadOnlyList constructorArguments, out EquatableReadOnlyList namedArguments)
+ {
+ name = Name;
+ genericTypeArguments = GenericTypeArguments;
+ constructorArguments = ConstructorArguments;
+ namedArguments = NamedArguments;
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Models/NamedArgument.cs b/src/AttributeSourceGenerator/Models/NamedArgument.cs
new file mode 100644
index 0000000..f7f6d58
--- /dev/null
+++ b/src/AttributeSourceGenerator/Models/NamedArgument.cs
@@ -0,0 +1,27 @@
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator.Models;
+
+/// Represents a named argument.
+public readonly record struct NamedArgument
+{
+ /// Gets the type of the argument.
+ public string Type { get; }
+
+ /// Gets the name of the argument.
+ public string Name { get; }
+
+ /// Gets the value of the argument.
+ public string? Value { get; }
+
+ /// Initializes a new instance of the record with the specified type, name, and value.
+ /// The type of the argument.
+ /// The name of the argument.
+ /// The value of the argument.
+ internal NamedArgument(string type, string name, string? value)
+ {
+ Type = type;
+ Name = name;
+ Value = value;
+ }
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/Source.cs b/src/AttributeSourceGenerator/Source.cs
deleted file mode 100644
index 6dc66f2..0000000
--- a/src/AttributeSourceGenerator/Source.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-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;
- }
-}
diff --git a/src/AttributeSourceGenerator/Symbol.cs b/src/AttributeSourceGenerator/Symbol.cs
index 9b3b283..748d0f1 100644
--- a/src/AttributeSourceGenerator/Symbol.cs
+++ b/src/AttributeSourceGenerator/Symbol.cs
@@ -1,46 +1,101 @@
using AttributeSourceGenerator.Common;
+using AttributeSourceGenerator.Models;
+
+// ReSharper disable CheckNamespace
namespace AttributeSourceGenerator;
-/// Represents a symbol in a codebase.
+/// Represents a symbol.
public readonly record struct Symbol
{
+ /// Gets the marker attribute attached to this symbol.
+ public MarkerAttributeData MarkerAttribute { get; }
+
+ /// 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; }
+
+ /// Gets the type of symbol.
+ public SymbolType SymbolType { get; }
+
/// Gets the name of the symbol.
public string Name { get; }
+ /// 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 the return type for method symbols, or an empty string otherwise.
+ public string ReturnType { get; }
+
/// Gets the full name of the symbol.
- public string FullName
+ public string FullyQualifiedName
{
get
{
+ var name = GenericTypeParameters.Count <= 0 ? Name : $"{Name}`{GenericTypeParameters.Count}";
+
if (ContainingDeclarations.Count <= 0)
- return Name;
+ return name;
- var names = ContainingDeclarations.Select(declaration => declaration.Name).ToArray();
- return $"{string.Join(".", names)}.{Name}";
+ var path = ContainingDeclarations.ToFullyQualifiedName();
+ return $"{path}.{name}";
}
}
- /// 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; }
+ /// Gets the namespace of the symbol.
+ public string Namespace
+ {
+ get
+ {
+ if (ContainingDeclarations.Count <= 0)
+ return "";
+
+ var ns = ContainingDeclarations.ToNamespace();
+ return ns;
+ }
+ }
- /// Initializes a new instance of the record with the specified name and containing declarations.
+ /// Initializes a new instance of the record with the specified marker attribute, containing declarations, symbol type, name, and generic parameters.
+ /// The marker attribute attached to this symbol.
+ /// The read-only list of the declarations that contain this symbol.
+ /// The type of symbol.
/// The name of the symbol.
- /// The declarations that contain this symbol.
- internal Symbol(string name, EquatableReadOnlyList containingDeclarations)
+ /// The list of the generic parameters for the symbol.
+ /// The list of the constructor 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)
{
- Name = name;
+ MarkerAttribute = markerAttribute;
ContainingDeclarations = containingDeclarations;
+ SymbolType = symbolType;
+ Name = name;
+ GenericTypeParameters = genericTypeParameters;
+ ConstructorParameters = constructorParameters;
+ ReturnType = returnType;
}
/// Deconstructs the symbol into its constituent parts.
+ /// Receives the marker attribute attached to this symbol.
+ /// Receives the read-only list of the declarations that contain this symbol.
+ /// The type of symbol.
/// Receives the name of the symbol.
- /// Receives the read-only list of the containing declarations.
- public void Deconstruct(out string name, out EquatableReadOnlyList containingDeclarations)
+ /// 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 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)
{
+ markerAttribute = MarkerAttribute;
containingDeclarations = ContainingDeclarations;
+ symbolType = SymbolType;
name = Name;
+ genericParameters = GenericTypeParameters;
+ constructorParameters = ConstructorParameters;
+ returnType = ReturnType;
}
-}
+}
\ No newline at end of file
diff --git a/src/AttributeSourceGenerator/SymbolType.cs b/src/AttributeSourceGenerator/SymbolType.cs
new file mode 100644
index 0000000..1a5b5eb
--- /dev/null
+++ b/src/AttributeSourceGenerator/SymbolType.cs
@@ -0,0 +1,25 @@
+// ReSharper disable CheckNamespace
+
+namespace AttributeSourceGenerator;
+
+/// Specifies the type of symbol.
+public enum SymbolType
+{
+ /// Represents an interface symbol.
+ Interface = 0,
+
+ /// Represents a class symbol.
+ Class = 1,
+
+ /// Represents a record symbol.
+ Record = 2,
+
+ /// Represents a struct symbol.
+ Struct = 3,
+
+ /// Represents a record struct symbol.
+ RecordStruct = 4,
+
+ /// Represents a method symbol.
+ Method = 5
+}
\ No newline at end of file