I have following Attribute:
[AttributeUsage(AttributeTargets.Class)]
public class EventApplyAttribute : Attribute
{
public string Aggregate { get; }
public EventApplyAttribute(string aggregate)
{
Aggregate = aggregate;
}
}
I use the Attribute like this:
[EventApplyAttribute(nameof(BaseClass))]
public class Test : BaseEvent{}
I use this source generator:
[Generator]
public class SourceGeneration : IIncrementalGenerator
{
private const string EventApplyAttribute = "DomainLibrary.EventApplyAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
EventApplyAttribute,
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, ct) => GetSemanticTargetForGeneration(ctx, ct))
.Where(static m => m is not null);
IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilationAndClasses
= context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Item1, source.Item2, spc));
}
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes, SourceProductionContext context)
{
}
private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct)
{
string fullName = context.Attributes.First().AttributeClass.ToDisplayString();
if (fullName == EventApplyAttribute)
{
if (context.TargetNode is not ClassDeclarationSyntax classDeclaration)
return null;
return classDeclaration;
}
return null;
}
}
The classes array in the Execute method has the expected classes. If I loop through the array I get a lot of information. Is there a way to access also attribute property value. In this case it would be "BaseClass"? I can't find anything...
You could extract the attribute from the ClassDeclarationSyntax
object using the AttributeList
property, which contains the syntax of the attribute list. But that's tedious and very low-level.
To make it easier and make the generator more efficient, you should do as @Youssef13 says and transform the value in GetSemanticTargetForGeneration
. The GeneratorAttributeSyntaxContext
already has the attribute:
private static (ClassDeclarationSyntax, AttributeData) GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct)
{
if (!(context.TargetNode is ClassDeclarationSyntax classDeclaration))
return (null, null);
AttributeData attribute = context.Attributes
.FirstOrDefault(a => a.AttributeClass.Name == "EventApplyAttribute");
return (classDeclaration, attribute);
}
Now we're returning pairs of the class declaration and the attribute data. You can process that in the Execute
method; I don't know what your goal is, so here I'm just generating a file containing a comment with the class name you're looking for, to show that it works:
private static void Execute(Compilation compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)> classes, SourceProductionContext context)
{
foreach (var (x, i) in classes.Select((x, i) => (x, i)))
{
TypedConstant aggregateParam = x.Item2.ConstructorArguments[0];
if (aggregateParam.Kind == TypedConstantKind.Primitive &&
aggregateParam.Value is string value)
{
context.AddSource(
$"generated_{i}.g.cs",
$"// <auto-generated/> using System; // Found param value '{value}'");
}
}
}
For completeness, you need to change the main Initialize
method thusly:
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<(ClassDeclarationSyntax, AttributeData)> classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
EventApplyAttribute,
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, ct) => GetSemanticTargetForGeneration(ctx, ct))
.Where(m => m.Item1 is not null && m.Item2 is not null);
IncrementalValueProvider<(Compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)>)> compilationAndClasses
= context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Item1, source.Item2, spc));
}