From fc633192e40cc63d76482f3ce9d6117107ab12ec Mon Sep 17 00:00:00 2001 From: CodingFlow <3643313+CodingFlow@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:20:56 -0500 Subject: [PATCH 1/3] Update Main.cs to use and implement IIncrementalGenerator interface. --- DecoratorGenerator/DecoratorGenerator.csproj | 8 +-- DecoratorGenerator/Main.cs | 72 ++++++-------------- 2 files changed, 25 insertions(+), 55 deletions(-) diff --git a/DecoratorGenerator/DecoratorGenerator.csproj b/DecoratorGenerator/DecoratorGenerator.csproj index 391a46f..d9eae25 100644 --- a/DecoratorGenerator/DecoratorGenerator.csproj +++ b/DecoratorGenerator/DecoratorGenerator.csproj @@ -54,13 +54,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DecoratorGenerator/Main.cs b/DecoratorGenerator/Main.cs index e76bdb0..c4123d5 100644 --- a/DecoratorGenerator/Main.cs +++ b/DecoratorGenerator/Main.cs @@ -1,71 +1,41 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; -using System.Linq; using System.Text; +using System.Threading; namespace DecoratorGenerator { [Generator] - public class Main : ISourceGenerator + public class Main : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) { - // No initialization required for this one + public void Initialize(IncrementalGeneratorInitializationContext context) { + var types = context.SyntaxProvider.ForAttributeWithMetadataName( + $"DecoratorGenerator.{nameof(DecorateAttribute)}", + predicate: IsSyntaxTargetForGeneration, + transform: GetSemanticTargetForGeneration + ); + + context.RegisterSourceOutput(types, Execute); } - public void Execute(GeneratorExecutionContext context) { - var types = GetAllDecoratedTypes(context.Compilation.Assembly.GlobalNamespace); - - var wrapperSymbolWithoutNamespace = context.Compilation.Assembly.GetTypeByMetadataName("WrapperList"); - var wrapperListTypes = - (wrapperSymbolWithoutNamespace == null) - ? GetAllTypes(context.Compilation.Assembly.GlobalNamespace, x => x.Name == "WrapperList") - : new[] { wrapperSymbolWithoutNamespace }; - var thirdPartyTypes = - wrapperListTypes.SelectMany(x => x.GetMembers() - .Where(m => m.Name != ".ctor") - .Select(m => m as IFieldSymbol) - .Select(f => f.Type) - .Select(t => t as INamedTypeSymbol)); - - types = types.Concat(thirdPartyTypes); - - var outputs = types.Select(OutputGenerator.GenerateOutputs); - - foreach (var (source, className) in outputs) { - // Add the source code to the compilation - context.AddSource($"{className}.generated.cs", SourceText.From(source, Encoding.UTF8, SourceHashAlgorithm.Sha256)); - } + private static bool IsSyntaxTargetForGeneration(SyntaxNode syntaxNode, CancellationToken token) { + return syntaxNode is InterfaceDeclarationSyntax; } - /// - /// Gets all Types inside the namespace matching the predicate including nested namespaces. - /// - /// - /// - /// - private IEnumerable GetAllTypes(INamespaceSymbol input, Func predicate) { - foreach (var space in input.GetNamespaceMembers()) { - foreach (var item in space.GetTypeMembers()) { - if (predicate(item)) { - yield return item; - } - } + private static INamedTypeSymbol GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { + var intefaceSyntax = context.TargetNode as InterfaceDeclarationSyntax; + var symbol = context.SemanticModel.GetDeclaredSymbol(intefaceSyntax, cancellationToken: cancellationToken) as INamedTypeSymbol; - foreach (var nestedItem in GetAllTypes(space, predicate)) { - yield return nestedItem; - } - } + return symbol; } - /// - /// Gets all Types in the namespace decorated with the including nested namespaces. - /// - /// - /// - private IEnumerable GetAllDecoratedTypes(INamespaceSymbol input) { - return GetAllTypes(input, (x) => x.GetAttributes().Any(att => att.AttributeClass.Name == nameof(DecorateAttribute))); + private static void Execute(SourceProductionContext context, INamedTypeSymbol typeSymbol) { + var (source, className) = OutputGenerator.GenerateOutputs(typeSymbol); + + context.AddSource($"{className}.generated.cs", SourceText.From(source, Encoding.UTF8, SourceHashAlgorithm.Sha256)); } } } \ No newline at end of file From d4a94476d34808dcb76a3f2af15fbed142ad67fa Mon Sep 17 00:00:00 2001 From: CodingFlow <3643313+CodingFlow@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:31:55 -0500 Subject: [PATCH 2/3] - Remove deprecated NUnit specific testing nuget package. - Add generic testing nuget package. - Uncomment tests. --- .../CSharpSourceGeneratorVerifier.cs | 2 +- .../DecoratorGenerator.UnitTests.csproj | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DecoratorGenerator.UnitTests/CSharpSourceGeneratorVerifier.cs b/DecoratorGenerator.UnitTests/CSharpSourceGeneratorVerifier.cs index 27020e2..8afac95 100644 --- a/DecoratorGenerator.UnitTests/CSharpSourceGeneratorVerifier.cs +++ b/DecoratorGenerator.UnitTests/CSharpSourceGeneratorVerifier.cs @@ -6,7 +6,7 @@ namespace DecoratorGenerator.UnitTests; -public static class CSharpSourceGeneratorVerifier where TSourceGenerator : ISourceGenerator, new() +public static class CSharpSourceGeneratorVerifier where TSourceGenerator : IIncrementalGenerator, new() { public class Test : CSharpSourceGeneratorTest { diff --git a/DecoratorGenerator.UnitTests/DecoratorGenerator.UnitTests.csproj b/DecoratorGenerator.UnitTests/DecoratorGenerator.UnitTests.csproj index e097ae4..5ca0807 100644 --- a/DecoratorGenerator.UnitTests/DecoratorGenerator.UnitTests.csproj +++ b/DecoratorGenerator.UnitTests/DecoratorGenerator.UnitTests.csproj @@ -11,17 +11,17 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6fb917fe422638d2ab96fd33aee4c598faed2064 Mon Sep 17 00:00:00 2001 From: CodingFlow <3643313+CodingFlow@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:31:16 -0500 Subject: [PATCH 3/3] - Reintroduce support for WrapperList third party types. - Small update to readme to indicate this library is an incremental source generator. --- DecoratorGenerator/Main.cs | 40 ++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/DecoratorGenerator/Main.cs b/DecoratorGenerator/Main.cs index c4123d5..7f947c9 100644 --- a/DecoratorGenerator/Main.cs +++ b/DecoratorGenerator/Main.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading; @@ -18,7 +19,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { transform: GetSemanticTargetForGeneration ); + var thirdPartyTypes = context.CompilationProvider.SelectMany(GetThirdPartySemanticTargetsForGeneration); + context.RegisterSourceOutput(types, Execute); + context.RegisterSourceOutput(thirdPartyTypes, Execute); } private static bool IsSyntaxTargetForGeneration(SyntaxNode syntaxNode, CancellationToken token) { @@ -37,5 +41,41 @@ private static void Execute(SourceProductionContext context, INamedTypeSymbol ty context.AddSource($"{className}.generated.cs", SourceText.From(source, Encoding.UTF8, SourceHashAlgorithm.Sha256)); } + + private IEnumerable GetThirdPartySemanticTargetsForGeneration(Compilation compilation, CancellationToken _) { + var wrapperSymbolWithoutNamespace = compilation.Assembly.GetTypeByMetadataName("WrapperList"); + + var wrapperListTypes = + (wrapperSymbolWithoutNamespace == null) + ? GetAllTypes(compilation.Assembly.GlobalNamespace, x => x.Name == "WrapperList") + : new[] { wrapperSymbolWithoutNamespace }; + + return + wrapperListTypes.SelectMany(x => x.GetMembers() + .Where(m => m.Name != ".ctor") + .Select(m => m as IFieldSymbol) + .Select(f => f.Type) + .Select(t => t as INamedTypeSymbol)); + } + + /// + /// Gets all Types inside the namespace matching the predicate including nested namespaces. + /// + /// + /// + /// + private IEnumerable GetAllTypes(INamespaceSymbol input, Func predicate) { + foreach (var space in input.GetNamespaceMembers()) { + foreach (var item in space.GetTypeMembers()) { + if (predicate(item)) { + yield return item; + } + } + + foreach (var nestedItem in GetAllTypes(space, predicate)) { + yield return nestedItem; + } + } + } } } \ No newline at end of file diff --git a/README.md b/README.md index 0e2cdde..c78b639 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ public interface ICat } ``` -Build the project so the abstract class is generated for the interface. The generated class will be named after the interface, but without the `I` prefix. In this case, since the interface is `ICat` the class will be `CatDecorator`. Then create your decorator class as usual: +Since this library is an [incremental source generator](https://github.com/dotnet/roslyn/blob/d8c21d64ca958840bdaa2898cb2324397dc57bbb/docs/features/incremental-generators.md), the abstract class should be generated after saving the changes to interface's file. The generated class will be named after the interface, but without the `I` prefix. In this case, since the interface is `ICat` the class will be `CatDecorator`. Then create your decorator class as usual: ```c# namespace SampleLibrary;