diff --git a/mROA.Codegen/Properties/launchSettings.json b/mROA.Codegen/Properties/launchSettings.json new file mode 100644 index 0000000..f923004 --- /dev/null +++ b/mROA.Codegen/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../mROA.Example/mROA.Example.csproj" + } + } +} \ No newline at end of file diff --git a/mROA.Codegen/Readme.md b/mROA.Codegen/Readme.md new file mode 100644 index 0000000..a665add --- /dev/null +++ b/mROA.Codegen/Readme.md @@ -0,0 +1,29 @@ +# Roslyn Source Generators Sample + +A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs. + +## Content +### mROA.Codegen +A .NET Standard project with implementations of sample source generators. +**You must build this project to see the result (generated code) in the IDE.** + +- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry). +- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute. + +### mROA.Codegen.Sample +A project that references source generators. Note the parameters of `ProjectReference` in [mROA.Codegen.Sample.csproj](../mROA.Codegen.Sample/mROA.Codegen.Sample.csproj), they make sure that the project is referenced as a set of source generators. + +### mROA.Codegen.Tests +Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests. + +## How To? +### How to debug? +- Use the [launchSettings.json](Properties/launchSettings.json) profile. +- Debug tests. + +### How can I determine which syntax nodes I should expect? +Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/). + +### How to learn more about wiring source generators? +Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI) +The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md). \ No newline at end of file diff --git a/mROA.Codegen/SampleIncrementalSourceGenerator.cs b/mROA.Codegen/SampleIncrementalSourceGenerator.cs new file mode 100644 index 0000000..109639b --- /dev/null +++ b/mROA.Codegen/SampleIncrementalSourceGenerator.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + + +namespace mROA.Codegen; + +/// +/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute. +/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead. +/// +[Generator] +public class SampleIncrementalSourceGenerator : IIncrementalGenerator +{ + private const string Namespace = "mROA.Implementation"; + private const string AttributeName = "SharedObjectInterafceAttribute"; + + private const string AttributeSourceCode = $@"// + +namespace {Namespace} +{{ + [System.AttributeUsage(System.AttributeTargets.Class)] + public class {AttributeName} : System.Attribute + {{ + }} +}}"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + + // Filter classes annotated with the [Report] attribute. Only filtered Syntax Nodes can trigger code generation. + var provider = context.SyntaxProvider + .CreateSyntaxProvider( + (s, _) => s is InterfaceDeclarationSyntax, + (ctx, _) => GetClassDeclarationForSourceGen(ctx)) + .Where(t => t.reportAttributeFound) + .Select((t, _) => t.Item1); + + // Generate the source code. + context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()), + ((ctx, t) => GenerateCode(ctx, t.Left, t.Right))); + } + + /// + /// Checks whether the Node is annotated with the [Report] attribute and maps syntax context to the specific node type (ClassDeclarationSyntax). + /// + /// Syntax context, based on CreateSyntaxProvider predicate + /// The specific cast and whether the attribute was found. + private static (InterfaceDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen( + GeneratorSyntaxContext context) + { + var classDeclarationSyntax = (InterfaceDeclarationSyntax)context.Node; + + // Go through all attributes of the class. + foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) + continue; // if we can't get the symbol, ignore it + + string attributeName = attributeSymbol.ContainingType.ToDisplayString(); + + // Check the full name of the [Report] attribute. + if (attributeName == "mROA.Implementation.SharedObjectInterfaceAttribute") + return (classDeclarationSyntax, true); + } + + return (classDeclarationSyntax, false); + } + + /// + /// Generate code action. + /// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user. + /// + /// Source generation context used to add source files. + /// Compilation used to provide access to the Semantic Model. + /// Nodes annotated with the [Report] attribute that trigger the generate action. + private void GenerateCode(SourceProductionContext context, Compilation compilation, + ImmutableArray classDeclarations) + { + // Go through all filtered class declarations. + foreach (var classDeclarationSyntax in classDeclarations) + { + // We need to get semantic model of the class to retrieve metadata. + var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree); + + // Symbols allow us to get the compile-time information. + if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol) + continue; + + var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + + // 'Identifier' means the token of the node. Get class name from the syntax node. + var className = classDeclarationSyntax.Identifier.Text; + + // Go through all class members with a particular type (property) to generate method lines. + var methodBody = classSymbol.GetMembers() + .OfType(); + Console.WriteLine(methodBody.Select(i => i.ToDisplayString())); + + // Build up the source code + className = className.TrimStart('I') + "RemoteEndpoint"; + var code = $@"// + +using System; +using System.Collections.Generic; + +namespace {namespaceName}; + +partial class {className} (int id, ISerialisationModule.IFrontendSerialisationModule serialisationModule) : ITestParameter, IRemoteObject +{{ + public int Id => id; +}} +"; + + // Add the source code to the compilation. + context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8)); + } + } +} \ No newline at end of file diff --git a/mROA.Codegen/mROA.Codegen.csproj b/mROA.Codegen/mROA.Codegen.csproj new file mode 100644 index 0000000..4a0e1a1 --- /dev/null +++ b/mROA.Codegen/mROA.Codegen.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + false + enable + latest + + true + true + + mROA.Codegen + mROA.Codegen + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/mROA.Codegen/obj/Debug/netstandard2.0/mROA.Codegen.AssemblyInfo.cs b/mROA.Codegen/obj/Debug/netstandard2.0/mROA.Codegen.AssemblyInfo.cs new file mode 100644 index 0000000..9ad4294 --- /dev/null +++ b/mROA.Codegen/obj/Debug/netstandard2.0/mROA.Codegen.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("mROA.Codegen")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1888db3522e2880c8ef7146ee78cc413905212c4")] +[assembly: System.Reflection.AssemblyProductAttribute("mROA.Codegen")] +[assembly: System.Reflection.AssemblyTitleAttribute("mROA.Codegen")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.props b/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.props new file mode 100644 index 0000000..353ed1c --- /dev/null +++ b/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.props @@ -0,0 +1,22 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Mikhail\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.12.0 + + + + + + + + + + C:\Users\Mikhail\.nuget\packages\microsoft.codeanalysis.analyzers\3.3.4 + + \ No newline at end of file diff --git a/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.targets b/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.targets new file mode 100644 index 0000000..022a5dd --- /dev/null +++ b/mROA.Codegen/obj/mROA.Codegen.csproj.nuget.g.targets @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/mROA.Example/mROA.Example.csproj b/mROA.Example/mROA.Example.csproj new file mode 100644 index 0000000..95ea4a5 --- /dev/null +++ b/mROA.Example/mROA.Example.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + enable + enable + Exe + + + + + + + + diff --git a/mROA.Example/obj/Debug/net9.0/mROA.Example.AssemblyInfo.cs b/mROA.Example/obj/Debug/net9.0/mROA.Example.AssemblyInfo.cs new file mode 100644 index 0000000..1a0d397 --- /dev/null +++ b/mROA.Example/obj/Debug/net9.0/mROA.Example.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("mROA.Example")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1888db3522e2880c8ef7146ee78cc413905212c4")] +[assembly: System.Reflection.AssemblyProductAttribute("mROA.Example")] +[assembly: System.Reflection.AssemblyTitleAttribute("mROA.Example")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/mROA.Example/obj/Debug/net9.0/mROA.Example.GlobalUsings.g.cs b/mROA.Example/obj/Debug/net9.0/mROA.Example.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/mROA.Example/obj/Debug/net9.0/mROA.Example.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/mROA.Example/obj/mROA.Example.csproj.nuget.g.props b/mROA.Example/obj/mROA.Example.csproj.nuget.g.props new file mode 100644 index 0000000..721d876 --- /dev/null +++ b/mROA.Example/obj/mROA.Example.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Mikhail\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.12.0 + + + + + + \ No newline at end of file diff --git a/mROA.Example/obj/mROA.Example.csproj.nuget.g.targets b/mROA.Example/obj/mROA.Example.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/mROA.Example/obj/mROA.Example.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/mROA.Test/obj/Debug/net9.0/mROA.Test.AssemblyInfo.cs b/mROA.Test/obj/Debug/net9.0/mROA.Test.AssemblyInfo.cs new file mode 100644 index 0000000..3d55e76 --- /dev/null +++ b/mROA.Test/obj/Debug/net9.0/mROA.Test.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("mROA.Test")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1888db3522e2880c8ef7146ee78cc413905212c4")] +[assembly: System.Reflection.AssemblyProductAttribute("mROA.Test")] +[assembly: System.Reflection.AssemblyTitleAttribute("mROA.Test")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/mROA.Test/obj/Debug/net9.0/mROA.Test.GlobalUsings.g.cs b/mROA.Test/obj/Debug/net9.0/mROA.Test.GlobalUsings.g.cs new file mode 100644 index 0000000..d32bf35 --- /dev/null +++ b/mROA.Test/obj/Debug/net9.0/mROA.Test.GlobalUsings.g.cs @@ -0,0 +1,9 @@ +// +global using global::NUnit.Framework; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/mROA/obj/Debug/net9.0/mROA.AssemblyInfo.cs b/mROA/obj/Debug/net9.0/mROA.AssemblyInfo.cs new file mode 100644 index 0000000..1e63963 --- /dev/null +++ b/mROA/obj/Debug/net9.0/mROA.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("mROA")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1888db3522e2880c8ef7146ee78cc413905212c4")] +[assembly: System.Reflection.AssemblyProductAttribute("mROA")] +[assembly: System.Reflection.AssemblyTitleAttribute("mROA")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/mROA/obj/Debug/net9.0/mROA.GeneratedMSBuildEditorConfig.editorconfig b/mROA/obj/Debug/net9.0/mROA.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..6ec64ed --- /dev/null +++ b/mROA/obj/Debug/net9.0/mROA.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = mROA +build_property.ProjectDir = C:\Users\Mikhail\Projects\VisualStudioProjects\mROA\mROA\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/mROA/obj/Debug/net9.0/mROA.GlobalUsings.g.cs b/mROA/obj/Debug/net9.0/mROA.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/mROA/obj/Debug/net9.0/mROA.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks;