diff --git a/Avalonia.sln b/Avalonia.sln
index cd7e56e9d44..9aec50be47f 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -302,8 +302,15 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.RenderTests.WpfCompare", "tests\Avalonia.RenderTests.WpfCompare\Avalonia.RenderTests.WpfCompare.csproj", "{9AE1B827-21AC-4063-AB22-C8804B7F931E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Automation", "src\Windows\Avalonia.Win32.Automation\Avalonia.Win32.Automation.csproj", "{0097673D-DBCE-476E-82FE-E78A56E58AA2}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XEmbedSample", "samples\XEmbedSample\XEmbedSample.csproj", "{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.CssSrcGen", "src\Avalonia.CssSrcGen\Avalonia.CssSrcGen\Avalonia.CssSrcGen.csproj", "{F7797895-7400-4F70-BDFC-05FDF4B3020D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.CssSrcGen.Sample", "src\Avalonia.CssSrcGen\Avalonia.CssSrcGen.Sample\Avalonia.CssSrcGen.Sample.csproj", "{3D8D69F6-79FC-45C2-B7EA-6213E4AC260E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.CssSrcGen.Tests", "src\Avalonia.CssSrcGen\Avalonia.CssSrcGen.Tests\Avalonia.CssSrcGen.Tests.csproj", "{B9C3D58D-94D9-4B9D-8E61-E5A452F747CB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -712,6 +719,18 @@ Global
{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F7797895-7400-4F70-BDFC-05FDF4B3020D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7797895-7400-4F70-BDFC-05FDF4B3020D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7797895-7400-4F70-BDFC-05FDF4B3020D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7797895-7400-4F70-BDFC-05FDF4B3020D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D8D69F6-79FC-45C2-B7EA-6213E4AC260E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D8D69F6-79FC-45C2-B7EA-6213E4AC260E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D8D69F6-79FC-45C2-B7EA-6213E4AC260E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D8D69F6-79FC-45C2-B7EA-6213E4AC260E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9C3D58D-94D9-4B9D-8E61-E5A452F747CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9C3D58D-94D9-4B9D-8E61-E5A452F747CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9C3D58D-94D9-4B9D-8E61-E5A452F747CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9C3D58D-94D9-4B9D-8E61-E5A452F747CB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -801,6 +820,9 @@ Global
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{0097673D-DBCE-476E-82FE-E78A56E58AA2} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {F7797895-7400-4F70-BDFC-05FDF4B3020D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {3D8D69F6-79FC-45C2-B7EA-6213E4AC260E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {B9C3D58D-94D9-4B9D-8E61-E5A452F747CB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index edcbcbe988d..ccb6dd26b4d 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -35,6 +35,10 @@
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml b/samples/ControlCatalog/Pages/ButtonsPage.xaml
index d0503c44ae6..5497b063bac 100644
--- a/samples/ControlCatalog/Pages/ButtonsPage.xaml
+++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml
@@ -1,6 +1,7 @@
+ x:Class="ControlCatalog.Pages.ButtonsPage"
+ Styles="{}">
@@ -17,8 +18,8 @@
-
-
+
+
diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
index 2d63f1fee99..64da58a2547 100644
--- a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
@@ -1,16 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Generators;
namespace ControlCatalog.Pages
{
- public class ButtonsPage : UserControl
+ [CssGenerator]
+ public partial class ButtonsPage : UserControl
{
private int repeatButtonClickCount = 0;
public ButtonsPage()
{
InitializeComponent();
-
this.Get("RepeatButton").Click += OnRepeatButtonClick;
}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj
new file mode 100644
index 00000000000..1697c2ed1f9
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ Avalonia.CssSrcGen.Sample
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/DDD.UbiquitousLanguageRegistry.txt b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/DDD.UbiquitousLanguageRegistry.txt
new file mode 100644
index 00000000000..aea323f2486
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/DDD.UbiquitousLanguageRegistry.txt
@@ -0,0 +1,5 @@
+Customer
+Product
+Stock
+Shop
+Employee
\ No newline at end of file
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Examples.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Examples.cs
new file mode 100644
index 00000000000..c7e7e49e346
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Examples.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using Entities;
+
+namespace Avalonia.CssSrcGen.Sample;
+
+// This code will not compile until you build the project with the Source Generators
+
+public class Examples
+{
+ // Create generated entities, based on DDD.UbiquitousLanguageRegistry.txt
+ public object[] CreateEntities()
+ {
+ return new object[] { new Customer(), new Employee(), new Product(), new Shop(), new Stock() };
+ }
+
+ // Execute generated method Report
+ public IEnumerable CreateEntityReport(SampleEntity entity)
+ {
+ return entity.Report();
+ }
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/SampleEntity.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/SampleEntity.cs
new file mode 100644
index 00000000000..742e5af8062
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/SampleEntity.cs
@@ -0,0 +1,12 @@
+using Generators;
+
+namespace Avalonia.CssSrcGen.Sample;
+
+// This code will not compile until you build the project with the Source Generators
+
+[Report]
+public partial class SampleEntity
+{
+ public int Id { get; } = 42;
+ public string? Name { get; } = "Sample";
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Avalonia.CssSrcGen.Tests.csproj b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Avalonia.CssSrcGen.Tests.csproj
new file mode 100644
index 00000000000..26b1c707021
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Avalonia.CssSrcGen.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+
+ false
+
+ Avalonia.CssSrcGen.Tests
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAdditionalFilesTests.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAdditionalFilesTests.cs
new file mode 100644
index 00000000000..eedf580f602
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAdditionalFilesTests.cs
@@ -0,0 +1,46 @@
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Avalonia.CssSrcGen.Tests.Utils;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace Avalonia.CssSrcGen.Tests;
+
+public class SourceGeneratorWithAdditionalFilesTests
+{
+ private const string DddRegistryText = @"User
+Document
+Customer";
+
+ [Fact]
+ public void GenerateClassesBasedOnDDDRegistry()
+ {
+ // Create an instance of the source generator.
+ var generator = new SourceGeneratorWithAdditionalFiles();
+
+ // Source generators should be tested using 'GeneratorDriver'.
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
+
+ // Add the additional file separately from the compilation.
+ driver = driver.AddAdditionalTexts(
+ ImmutableArray.Create(
+ new TestAdditionalFile("./DDD.UbiquitousLanguageRegistry.txt", DddRegistryText))
+ );
+
+ // To run generators, we can use an empty compilation.
+ var compilation = CSharpCompilation.Create(nameof(SourceGeneratorWithAdditionalFilesTests));
+
+ // Run generators. Don't forget to use the new compilation rather than the previous one.
+ driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
+
+ // Retrieve all files in the compilation.
+ var generatedFiles = newCompilation.SyntaxTrees
+ .Select(t => Path.GetFileName(t.FilePath))
+ .ToArray();
+
+ // In this case, it is enough to check the file name.
+ Assert.Equivalent(new[] { "User.g.cs", "Document.g.cs", "Customer.g.cs" }, generatedFiles);
+ }
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAttributesTests.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAttributesTests.cs
new file mode 100644
index 00000000000..77d6343a6d3
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/SourceGeneratorWithAttributesTests.cs
@@ -0,0 +1,67 @@
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace Avalonia.CssSrcGen.Tests;
+
+public class SourceGeneratorWithAttributesTests
+{
+ private const string VectorClassText = @"
+namespace TestNamespace;
+
+[Generators.Report]
+public partial class Vector3
+{
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float Z { get; set; }
+}";
+
+ private const string ExpectedGeneratedClassText = @"//
+
+using System;
+using System.Collections.Generic;
+
+namespace TestNamespace;
+
+partial class Vector3
+{
+ public IEnumerable Report()
+ {
+ yield return $""X:{this.X}"";
+ yield return $""Y:{this.Y}"";
+ yield return $""Z:{this.Z}"";
+ }
+}
+";
+
+ [Fact]
+ public void GenerateReportMethod()
+ {
+ // Create an instance of the source generator.
+ var generator = new SourceGeneratorWithAttributes();
+
+ // Source generators should be tested using 'GeneratorDriver'.
+ var driver = CSharpGeneratorDriver.Create(generator);
+
+ // We need to create a compilation with the required source code.
+ var compilation = CSharpCompilation.Create(nameof(SourceGeneratorWithAdditionalFilesTests),
+ new[] { CSharpSyntaxTree.ParseText(VectorClassText) },
+ new[]
+ {
+ // To support 'System.Attribute' inheritance, add reference to 'System.Private.CoreLib'.
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
+ });
+
+ // Run generators and retrieve all results.
+ var runResult = driver.RunGenerators(compilation).GetRunResult();
+
+ // All generated files can be found in 'RunResults.GeneratedTrees'.
+ var generatedFileSyntax = runResult.GeneratedTrees.Single(t => t.FilePath.EndsWith("Vector3.g.cs"));
+
+ // Complex generators should be tested using text comparison.
+ Assert.Equal(ExpectedGeneratedClassText, generatedFileSyntax.GetText().ToString(),
+ ignoreLineEndingDifferences: true);
+ }
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Utils/TestAdditionalFile.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Utils/TestAdditionalFile.cs
new file mode 100644
index 00000000000..291da2e0b54
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Tests/Utils/TestAdditionalFile.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Avalonia.CssSrcGen.Tests.Utils;
+
+public class TestAdditionalFile : AdditionalText
+{
+ private readonly SourceText _text;
+
+ public TestAdditionalFile(string path, string text)
+ {
+ Path = path;
+ _text = SourceText.From(text);
+ }
+
+ public override SourceText GetText(CancellationToken cancellationToken = new()) => _text;
+
+ public override string Path { get; }
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Avalonia.CssSrcGen.csproj b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Avalonia.CssSrcGen.csproj
new file mode 100644
index 00000000000..b48a749386a
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Avalonia.CssSrcGen.csproj
@@ -0,0 +1,26 @@
+
+
+
+ netstandard2.0
+ false
+ enable
+ latest
+
+ true
+ true
+
+ Avalonia.CssSrcGen
+ Avalonia.CssSrcGen
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Properties/launchSettings.json b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Properties/launchSettings.json
new file mode 100644
index 00000000000..6347fa5898f
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "DebugRoslynSourceGenerator": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Readme.md b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/Readme.md
new file mode 100644
index 00000000000..1d00f674163
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/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
+### Avalonia.CssSrcGen
+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.
+
+### Avalonia.CssSrcGen.Sample
+A project that references source generators. Note the parameters of `ProjectReference` in [Avalonia.CssSrcGen.Sample.csproj](../Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj), they make sure that the project is referenced as a set of source generators.
+
+### Avalonia.CssSrcGen.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/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAdditionalFiles.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAdditionalFiles.cs
new file mode 100644
index 00000000000..876008adbc4
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAdditionalFiles.cs
@@ -0,0 +1,51 @@
+using System.Collections.Immutable;
+using System.IO;
+using Microsoft.CodeAnalysis;
+
+namespace Avalonia.CssSrcGen;
+
+///
+/// A sample source generator that creates C# classes based on the text file (in this case, Domain Driven Design ubiquitous language registry).
+/// When using a simple text file as a baseline, we can create a non-incremental source generator.
+///
+[Generator]
+public class SourceGeneratorWithAdditionalFiles : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var provider = context.AdditionalTextsProvider
+ .Where(f => Path.GetFileName(f.Path) == "DDD.UbiquitousLanguageRegistry.txt")
+ .Collect();
+
+ context.RegisterSourceOutput(provider, GenerateCode);
+ }
+
+ private void GenerateCode(SourceProductionContext context, ImmutableArray files)
+ {
+ foreach (var file in files)
+ {
+ // Get the text of the file.
+ var lines = file.GetText(context.CancellationToken)?.ToString().Split('\n');
+ if (lines == null)
+ continue;
+
+ foreach (var line in lines)
+ {
+ var className = line.Trim();
+
+ // Build up the source code.
+ string source = $@"//
+
+namespace Entities
+{{
+ public partial class {className}
+ {{
+ }}
+}}
+";
+ // Add the source code to the compilation.
+ context.AddSource($"{className}.g.cs", source);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAttributes.cs b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAttributes.cs
new file mode 100644
index 00000000000..5572f309b63
--- /dev/null
+++ b/src/Avalonia.CssSrcGen/Avalonia.CssSrcGen/SourceGeneratorWithAttributes.cs
@@ -0,0 +1,159 @@
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+
+namespace Avalonia.CssSrcGen;
+
+///
+/// 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 SourceGeneratorWithAttributes : IIncrementalGenerator
+{
+ private const string Namespace = "Generators";
+ private const string AttributeName = "CssGeneratorAttribute";
+
+ private const string AttributeSourceCode = $@"//
+
+namespace {Namespace}
+{{
+ [System.AttributeUsage(System.AttributeTargets.Class)]
+ public class {AttributeName} : System.Attribute
+ {{
+ }}
+}}";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Add the marker attribute to the compilation.
+ context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
+ "CssGeneratorAttribute.g.cs",
+ SourceText.From(AttributeSourceCode, Encoding.UTF8)));
+
+ // Filter classes annotated with the [Report] attribute. Only filtered Syntax Nodes can trigger code generation.
+ var provider = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ (s, _) => s is ClassDeclarationSyntax,
+ (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 (ClassDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen(
+ GeneratorSyntaxContext context)
+ {
+ var classDeclarationSyntax = (ClassDeclarationSyntax)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 == $"{Namespace}.{AttributeName}")
+ 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()
+ .Select(p =>
+ $@" yield return $""{p.Name}:{{this.{p.Name}}}"";"); // e.g. yield return $"Id:{this.Id}";
+
+ // Build up the source code
+ var code = $$"""
+ //
+
+ using System;
+ using System.Collections.Generic;
+ using Avalonia;
+ using Avalonia.Controls;
+ using Avalonia.Markup.Xaml;
+ using Avalonia.Media;
+ using Avalonia.Styling;
+
+ namespace {{namespaceName}};
+
+ partial class {{className}}
+ {
+ public override void EndInit() //{{className}}
+ {
+ var headerBorderStyle = new Style(x => x.OfType().Class("header-border"));
+ headerBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Lime, 0.5)));
+ headerBorderStyle.Setters.Add(new Setter(Border.BorderBrushProperty, new SolidColorBrush(Colors.Lime)));
+ headerBorderStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(0.5)));
+ headerBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(5.0, 5.0, 0.0, 0.0)));
+ headerBorderStyle.Setters.Add(new Setter(Border.MaxWidthProperty, 450.0));
+ headerBorderStyle.Setters.Add(new Setter(Decorator.PaddingProperty, new Thickness(10.0)));
+
+ this.Styles.Add(headerBorderStyle);
+
+ var headerTextStyle = new Style(x => x.OfType().Class("header"));
+ headerTextStyle.Setters.Add(new Setter(TextBlock.FontSizeProperty, 18.0));
+ headerTextStyle.Setters.Add(new Setter(TextBlock.FontWeightProperty, FontWeight.Bold));
+
+ this.Styles.Add(headerTextStyle);
+
+ var thinBorderStyle = new Style(x => x.OfType().Class("thin"));
+ thinBorderStyle.Setters.Add(new Setter(Border.BorderBrushProperty, new SolidColorBrush(Colors.Lime)));
+ thinBorderStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(0.5)));
+ thinBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(0.0, 0.0, 5.0, 5.0)));
+ thinBorderStyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0.0, 0.0, 0.0, 15.0)));
+
+ this.Styles.Add(thinBorderStyle);
+ }
+ }
+
+ """;
+
+ // Add the source code to the compilation.
+ context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
+ }
+ }
+}