Skip to content

Commit

Permalink
Add Avalonia.CssSrcGen with source generators and examples
Browse files Browse the repository at this point in the history
Introduced a new project `Avalonia.CssSrcGen` to demonstrate Roslyn source generators. Added sample generators, tests, and examples for incremental and non-incremental generators. Integrated it with existing projects, along with supporting assets like launch settings and documentation.
  • Loading branch information
wdcossey committed Dec 29, 2024
1 parent 07f3ad2 commit 9320583
Show file tree
Hide file tree
Showing 17 changed files with 525 additions and 7 deletions.
22 changes: 22 additions & 0 deletions Avalonia.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 4 additions & 0 deletions samples/ControlCatalog/ControlCatalog.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.CssSrcGen\Avalonia.CssSrcGen\Avalonia.CssSrcGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\SkiaSharp.props" />
Expand Down
11 changes: 6 additions & 5 deletions samples/ControlCatalog/Pages/ButtonsPage.xaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ButtonsPage">
x:Class="ControlCatalog.Pages.ButtonsPage"
Styles="{}">

<UserControl.Resources>

Expand All @@ -17,8 +18,8 @@
</MenuFlyout>

</UserControl.Resources>
<UserControl.Styles >

<!--<UserControl.Styles >
<Style Selector="Border.header-border">
<Setter Property="Background">
Expand All @@ -32,7 +33,7 @@
<Setter Property="MaxWidth" Value="450" />
<Setter Property="Padding" Value="10" />
</Style>
<Style Selector="TextBlock.header">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Bold" />
Expand All @@ -45,7 +46,7 @@
<Setter Property="Margin" Value="0,0,0,15" />
</Style>
</UserControl.Styles>
</UserControl.Styles>-->

<!-- Styles and overall page design based on AcrylicPage -->

Expand Down
5 changes: 3 additions & 2 deletions samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -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>("RepeatButton").Click += OnRepeatButtonClick;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Avalonia.CssSrcGen.Sample</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Avalonia.CssSrcGen\Avalonia.CssSrcGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<ItemGroup>
<None Remove="DDD.UbiquitousLanguageRegistry.txt"/>
<AdditionalFiles Include="DDD.UbiquitousLanguageRegistry.txt"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Customer
Product
Stock
Shop
Employee
21 changes: 21 additions & 0 deletions src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/Examples.cs
Original file line number Diff line number Diff line change
@@ -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<string> CreateEntityReport(SampleEntity entity)
{
return entity.Report();
}
}
12 changes: 12 additions & 0 deletions src/Avalonia.CssSrcGen/Avalonia.CssSrcGen.Sample/SampleEntity.cs
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>

<RootNamespace>Avalonia.CssSrcGen.Tests</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2"/>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Avalonia.CssSrcGen\Avalonia.CssSrcGen.csproj"/>
</ItemGroup>


</Project>
Original file line number Diff line number Diff line change
@@ -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<AdditionalText>(
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);
}
}
Original file line number Diff line number Diff line change
@@ -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 = @"// <auto-generated/>
using System;
using System.Collections.Generic;
namespace TestNamespace;
partial class Vector3
{
public IEnumerable<string> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>

<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>

<RootNamespace>Avalonia.CssSrcGen</RootNamespace>
<PackageId>Avalonia.CssSrcGen</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
</ItemGroup>


</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynSourceGenerator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../Avalonia.CssSrcGen.Sample/Avalonia.CssSrcGen.Sample.csproj"
}
}
}
Loading

0 comments on commit 9320583

Please sign in to comment.