mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 23:08:36 +08:00
1348 lines
53 KiB
C#
1348 lines
53 KiB
C#
extern alias LinkedPublish;
|
|
|
|
using System.Collections.Immutable;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.Diagnostics;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using LinkedRegistrationPackageManifestGenerator = LinkedPublish::AtomUI.Generator.LinkedRegistration.LinkedRegistrationPackageManifestGenerator;
|
|
|
|
namespace AtomUI.Generator.Tests;
|
|
|
|
public class ThemeSchemaGeneratorTests
|
|
{
|
|
[Fact]
|
|
public void Generates_Complete_Aot_Schema_Descriptors_With_Stable_Slots()
|
|
{
|
|
var outputCompilation = RunGenerator(CreateCompilation(TokenSource), out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
outputCompilation.GetDiagnostics(TestContext.Current.CancellationToken)
|
|
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
|
.ShouldBeEmpty();
|
|
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
|
|
source.ShouldContain("namespace AtomUI.Generated.ThemeSchemaGeneratorTests;");
|
|
source.ShouldContain("internal static class GeneratedThemeSchema");
|
|
source.ShouldNotContain("namespace AtomUI.Theme.Schema;");
|
|
source.ShouldNotContain("class ThemeSchemaDescriptorPool");
|
|
source.ShouldContain("typeof(global::Demo.Button)");
|
|
source.ShouldContain("new ControlTokenIdentity(\"AtomUI\", \"Button\")");
|
|
source.ShouldContain("global::AtomUI.Theme.Resources.SharedTokenKind");
|
|
source.ShouldContain("new TokenDescriptor(\"Alpha\", 0, TokenStage.Seed");
|
|
source.ShouldContain("new TokenDescriptor(\"Zeta\", 1, TokenStage.Alias");
|
|
source.ShouldContain("new TokenDescriptor(\"Height\", 0, TokenStage.Control");
|
|
source.ShouldContain("new TokenDescriptor(\"Label\", 1, TokenStage.Control");
|
|
source.ShouldNotContain("SupportedGlobalToken");
|
|
source.ShouldContain("ThemeTokenValueParser.Parse<global::System.Double>(value)");
|
|
source.ShouldContain("ThemeTokenValueFormatter.Format((global::System.Double)value!)");
|
|
source.ShouldContain("((global::Demo.ButtonToken)token).Height = (global::System.Double)value!");
|
|
source.ShouldContain("static () => new global::Demo.ButtonToken()");
|
|
source.ShouldContain("((global::Demo.ButtonToken)token).CalculateTokenValues(appearance == global::AtomUI.Theme.ThemeAppearance.Dark)");
|
|
source.ShouldContain("new ThemeAlgorithmDescriptor(global::AtomUI.Theme.Algorithms.ThemeAlgorithm.Dark, 1, ThemeAppearanceEffect.Dark");
|
|
source.ShouldContain("new ThemeAlgorithmDescriptor(global::AtomUI.Theme.Algorithms.ThemeAlgorithm.Default, 1, ThemeAppearanceEffect.Light");
|
|
source.ShouldNotContain("Activator.CreateInstance");
|
|
source.ShouldNotContain("PropertyInfo");
|
|
source.ShouldNotContain("typeof(global::Demo.ButtonToken)");
|
|
}
|
|
|
|
[Fact]
|
|
public void Descriptor_Output_Is_Independent_Of_Declaration_Order()
|
|
{
|
|
var first = GetGeneratedSource(
|
|
RunGenerator(CreateCompilation(TokenSource), out _),
|
|
"GeneratedThemeSchema.g.cs");
|
|
var second = GetGeneratedSource(
|
|
RunGenerator(CreateCompilation(ReorderedTokenSource), out _),
|
|
"GeneratedThemeSchema.g.cs");
|
|
|
|
second.ShouldBe(first);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ignores_An_Undefined_ThemeAlgorithm_Attribute_Value()
|
|
{
|
|
var invalidSource = TokenSource.Replace(
|
|
"ThemeAlgorithm.Dark, 1, ThemeAppearanceEffect.Dark",
|
|
"(ThemeAlgorithm)999, 1, ThemeAppearanceEffect.Dark",
|
|
StringComparison.Ordinal);
|
|
|
|
var outputCompilation = RunGenerator(CreateCompilation(invalidSource), out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
source.ShouldContain("ThemeAlgorithm.Default");
|
|
source.ShouldNotContain("ThemeAlgorithm.Dark");
|
|
source.ShouldNotContain("999");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("AtomUI.Desktop.Controls.DataGrid", "AtomUIDesktopControlsDataGrid")]
|
|
[InlineData("acme-controls_package", "AcmeControlsPackage")]
|
|
[InlineData("123.Controls", "Assembly123Controls")]
|
|
public void Generated_Namespace_Uses_One_Pascal_Owner_Identifier_Without_Separators(
|
|
string assemblyName,
|
|
string expectedOwnerIdentifier)
|
|
{
|
|
var compilation = CreateCompilation(TokenSource, assemblyName);
|
|
var outputCompilation = RunGenerator(compilation, out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
source.ShouldContain($"namespace AtomUI.Generated.{expectedOwnerIdentifier};");
|
|
source.ShouldNotContain("namespace AtomUI.Generated._");
|
|
}
|
|
|
|
[Fact]
|
|
public void Ignores_Non_Token_Effective_Global_Calculation_Helpers()
|
|
{
|
|
var outputCompilation = RunGenerator(CreateCompilation("""
|
|
using AtomUI.Theme.DesignTokens;
|
|
|
|
namespace Demo
|
|
{
|
|
[ControlDesignToken]
|
|
internal sealed class ButtonToken : AbstractControlDesignToken
|
|
{
|
|
public double Height { get; set; }
|
|
|
|
public override void CalculateTokenValues(bool isDark)
|
|
{
|
|
_ = EffectiveGlobalToken.ColorPalettes.Count;
|
|
Height = EffectiveGlobalToken.ControlHeight;
|
|
}
|
|
}
|
|
}
|
|
"""), out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
source.ShouldNotContain("\"ColorPalettes\"");
|
|
}
|
|
|
|
[Fact]
|
|
public void Generates_One_Package_Registration_Entry()
|
|
{
|
|
var outputCompilation = RunGenerator(
|
|
CreateCompilation(TokenSource),
|
|
out var diagnostics,
|
|
new InMemoryAdditionalText(
|
|
"Button/Themes/ButtonTheme.axaml",
|
|
"""
|
|
<ControlTheme xmlns="https://github.com/avaloniaui"
|
|
xmlns:atom="https://atomui.net"
|
|
TargetType="Demo.Button">
|
|
<Setter Property="Tag" Value="{atom:ButtonTokenResource Height}" />
|
|
</ControlTheme>
|
|
"""));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedControlPackageRegistration.g.cs");
|
|
source.ShouldContain("internal static class GeneratedControlPackageRegistration");
|
|
source.ShouldContain("internal static void Register(");
|
|
source.ShouldContain("global::AtomUI.Theme.IThemeManagerBuilder themeManagerBuilder");
|
|
source.ShouldContain("global::AtomUI.Theme.Resources.IControlThemesProvider controlThemesProvider");
|
|
source.ShouldContain("global::System.Func<global::System.Collections.Generic.IReadOnlyList<global::AtomUI.Theme.Schema.ControlThemeAssetDescriptor>, global::System.Collections.Generic.IReadOnlyList<global::AtomUI.Theme.Schema.ControlThemeAssetDescriptor>>? selectAssets = null");
|
|
source.ShouldContain("GeneratedThemeSchema.GetControls()");
|
|
source.ShouldContain("GeneratedControlThemeAssetManifest.GetDescriptors()");
|
|
source.ShouldContain("asset.ReferencedControlIdentities");
|
|
source.ShouldContain("includeIdentity(referencedIdentity)");
|
|
source.ShouldContain("var packageAssets = selectAssets is null ? selectedAssets : selectAssets(selectedAssets)");
|
|
source.ShouldContain("GeneratedControlThemeAssetResources.AddResources(controlThemesProvider, packageAssets)");
|
|
source.ShouldContain("new global::AtomUI.Theme.ControlPackageRegistration(");
|
|
source.ShouldContain(" packageAssets,");
|
|
source.ShouldContain("themeManagerBuilder.AddControlPackage(package)");
|
|
source.ShouldNotContain("AddControlToken");
|
|
source.ShouldNotContain("AddLanguageProviders");
|
|
}
|
|
|
|
[Fact]
|
|
public void Generates_A_Linkable_Registration_Unit_Without_Referencing_The_Full_Schema()
|
|
{
|
|
var outputCompilation = RunGenerator(CreateCompilation(TokenSource), out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(
|
|
outputCompilation,
|
|
"GeneratedRegistrationUnits.g.cs");
|
|
|
|
source.ShouldContain("namespace AtomUI.Generated.ThemeSchemaGeneratorTests.LinkedRegistrationV1;");
|
|
source.ShouldContain("EditorBrowsableState.Never");
|
|
source.ShouldContain("public static partial class GeneratedRegistrationUnit_ThemeSchemaGeneratorTests_");
|
|
source.ShouldContain("public static void Add(");
|
|
source.ShouldContain("global::AtomUI.Registration.AotTrimControlPackageRegistrationBuilder builder");
|
|
source.ShouldNotContain("TryEnterUnit");
|
|
source.ShouldNotContain("AddDependencies");
|
|
source.ShouldContain("builder.AddControl(");
|
|
source.ShouldContain(
|
|
"global::AtomUI.Generated.ThemeSchemaGeneratorTests.GeneratedThemeSchemaDescriptorFactory.CreateControlDescriptor_Button_");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.Unit.v1\"");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.ControlMap.v1\"");
|
|
source.ShouldNotContain("AssemblyMetadata(\"AtomUI.Linked.Control.v1\"");
|
|
source.ShouldNotContain("GeneratedThemeSchema.GetControls()");
|
|
source.ShouldNotContain("s_controls");
|
|
source.ShouldNotContain("new ControlTokenDescriptor(");
|
|
source.ShouldNotContain("static () => new global::Demo.ButtonToken()");
|
|
|
|
var schemaSource = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
schemaSource.ShouldContain("internal static class GeneratedThemeSchemaDescriptorFactory");
|
|
schemaSource.ShouldContain("GeneratedThemeSchemaDescriptorFactory.CreateControlDescriptor_Button_");
|
|
schemaSource.ShouldContain("new ControlTokenDescriptor(");
|
|
schemaSource.ShouldContain("static () => new global::Demo.ButtonToken()");
|
|
}
|
|
|
|
[Fact]
|
|
public void Package_Granularity_Groups_Controls_From_Different_Directories_Into_One_Unit()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class DatePicker : Avalonia.Controls.Control { }
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"DatePicker/DatePicker.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class RangeDatePicker : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "RangeDatePicker/RangeDatePicker.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
var outputCompilation = RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
new InMemoryAdditionalText(
|
|
"DatePicker/Themes/DatePickerTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"Demo.DatePicker\" />"),
|
|
new InMemoryAdditionalText(
|
|
"DatePicker/Themes/RangeDatePickerTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"Demo.RangeDatePicker\" />"));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
source.Split("public static partial class GeneratedRegistrationUnit_ThemeSchemaGeneratorTests_", StringSplitOptions.None)
|
|
.Length.ShouldBe(2);
|
|
source.ShouldContain("Demo.DatePicker");
|
|
source.ShouldContain("Demo.RangeDatePicker");
|
|
source.ShouldContain("CreateControlDescriptor_DatePicker_");
|
|
source.ShouldContain("CreateControlDescriptor_RangeDatePicker_");
|
|
source.ShouldNotContain("typeof(global::Demo.DatePicker)");
|
|
source.ShouldNotContain("typeof(global::Demo.RangeDatePicker)");
|
|
source.ShouldNotContain("GeneratedRegistrationUnit_DatePicker_");
|
|
source.ShouldNotContain("GeneratedRegistrationUnit_RangeDatePicker_");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Groups_Controls_By_Source_Directory()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control { }
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
source.ShouldContain("GeneratedRegistrationUnit_Button_");
|
|
source.ShouldContain("GeneratedRegistrationUnit_Alert_");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FButton");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FAlert");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Generates_CSharp_Control_Dependencies()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public Alert CreateAlert() => new Alert();
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunLinkedGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "LinkedRegistrationPackageAnalysis.g.cs");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.UnitEdge.v1\"");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FButton");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FAlert");
|
|
source.ShouldContain("CSharpType");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Follows_Same_Assembly_Helper_Method_Dependencies()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public object CreateAlert() => ControlFactory.CreateAlert();
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
internal static class ControlFactory
|
|
{
|
|
internal static object CreateAlert() => new Alert();
|
|
}
|
|
""",
|
|
path: "Infrastructure/ControlFactory.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken),
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunLinkedGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "LinkedRegistrationPackageAnalysis.g.cs");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.RootUnit.v1\"");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FAlert");
|
|
source.ShouldNotContain("AtomUI.Linked.Fallback.v1");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Follows_Helper_Property_And_Generic_Type_Dependencies()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public object CreateAlert() => ControlFactory.Create<Alert>();
|
|
public object GetAlert() => ControlFactory.Alert;
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
internal static class ControlFactory
|
|
{
|
|
internal static object Alert => new Alert();
|
|
internal static object Create<T>() where T : new() => new T();
|
|
}
|
|
""",
|
|
path: "Infrastructure/ControlFactory.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken),
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunLinkedGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "LinkedRegistrationPackageAnalysis.g.cs");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.RootUnit.v1\"");
|
|
source.ShouldContain("ThemeSchemaGeneratorTests%2FAlert");
|
|
source.ShouldNotContain("AtomUI.Linked.Fallback.v1");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Does_Not_Treat_Control_Api_Signatures_As_Runtime_Dependencies()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public Alert? Current { get; set; }
|
|
public event System.Action<Alert>? Changed;
|
|
public Alert? PassThrough(Alert? value) => value;
|
|
public void Raise(Alert value) => Changed?.Invoke(value);
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
var dependencyFragment = global::AtomUI.Generator.LinkedRegistration
|
|
.LinkedRegistrationFragmentName.ForUnit("ThemeSchemaGeneratorTests/Alert");
|
|
source.ShouldNotContain($"{dependencyFragment}.Add(builder);");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Does_Not_Treat_Generic_Api_Type_Arguments_As_Construction()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public System.Type GetAlertListType() => typeof(System.Collections.Generic.List<Alert>);
|
|
public System.Collections.Generic.IEnumerable<Alert> EmptyAlerts() =>
|
|
System.Linq.Enumerable.Empty<Alert>();
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
var dependencyFragment = global::AtomUI.Generator.LinkedRegistration
|
|
.LinkedRegistrationFragmentName.ForUnit("ThemeSchemaGeneratorTests/Alert");
|
|
source.ShouldNotContain($"{dependencyFragment}.Add(builder);");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Unresolved_Dynamic_Control_Creation_Warns_Without_Widening()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public object Create(System.Type type) =>
|
|
System.Activator.CreateInstance(type)!;
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Alert : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Alert/Alert.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunLinkedGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
var diagnostic = diagnostics.ShouldHaveSingleItem();
|
|
diagnostic.Id.ShouldBe("ATOMUILINK010");
|
|
diagnostic.Location.GetLineSpan().Path.ShouldBe("Button/Button.cs");
|
|
var source = GetGeneratedSource(outputCompilation, "LinkedRegistrationPackageAnalysis.g.cs");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.Fallback.v1\"");
|
|
source.ShouldContain("DynamicInvocation");
|
|
}
|
|
|
|
[Fact]
|
|
public void Directory_Granularity_Dynamic_Invocation_Warns_Without_Widening()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
public object Create(dynamic factory) => factory.Create();
|
|
}
|
|
""",
|
|
"ThemeSchemaGeneratorTests",
|
|
"Button/Button.cs");
|
|
|
|
var outputCompilation = RunLinkedGeneratorWithGranularity(
|
|
compilation,
|
|
out var diagnostics,
|
|
"Directory");
|
|
|
|
var diagnostic = diagnostics.ShouldHaveSingleItem();
|
|
diagnostic.Id.ShouldBe("ATOMUILINK010");
|
|
diagnostic.Location.GetLineSpan().Path.ShouldBe("Button/Button.cs");
|
|
var source = GetGeneratedSource(outputCompilation, "LinkedRegistrationPackageAnalysis.g.cs");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.Fallback.v1\"");
|
|
source.ShouldContain("DynamicInvocation");
|
|
}
|
|
|
|
[Fact]
|
|
public void Reports_Invalid_Registration_Granularity()
|
|
{
|
|
RunGeneratorWithGranularity(
|
|
CreateCompilation(TokenSource),
|
|
out var diagnostics,
|
|
"Control");
|
|
|
|
var diagnostic = diagnostics.ShouldHaveSingleItem();
|
|
diagnostic.Id.ShouldBe("ATOMUILINK005");
|
|
diagnostic.Severity.ShouldBe(DiagnosticSeverity.Error);
|
|
diagnostic.GetMessage().ShouldContain("use 'Package' or 'Directory'");
|
|
}
|
|
|
|
[Fact]
|
|
public void Maps_Every_Public_Control_Without_Adding_A_Descriptor_For_Descriptorless_Controls()
|
|
{
|
|
var compilation = CreateCompilation(
|
|
TokenSource,
|
|
sourcePath: "Button/ButtonToken.cs").AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class LayoutProbe : Avalonia.Controls.Control { }
|
|
""",
|
|
path: "Layout/LayoutProbe.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGenerator(compilation, out var diagnostics);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var unitsSource = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
unitsSource.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.ControlMap.v1\"");
|
|
unitsSource.ShouldContain("Demo.LayoutProbe");
|
|
unitsSource.ShouldContain("ThemeSchemaGeneratorTests%2FThemeSchemaGeneratorTests");
|
|
unitsSource.ShouldContain("public static partial class GeneratedRegistrationUnit_ThemeSchemaGeneratorTests_");
|
|
unitsSource.ShouldNotContain("typeof(global::Demo.LayoutProbe)");
|
|
|
|
var schemaSource = GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs");
|
|
schemaSource.ShouldNotContain("typeof(global::Demo.LayoutProbe)");
|
|
}
|
|
|
|
[Fact]
|
|
public void Does_Not_Emit_Linked_Registration_Metadata_Without_A_Package_Entry()
|
|
{
|
|
var outputCompilation = RunGenerator(
|
|
CreateCompilation(TokenSource),
|
|
out var diagnostics,
|
|
includeRegistrationEntry: false,
|
|
registrationGranularity: null,
|
|
linkedPublish: false);
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
GetGeneratedSource(outputCompilation, "GeneratedThemeSchema.g.cs")
|
|
.ShouldContain("typeof(global::Demo.Button)");
|
|
outputCompilation.SyntaxTrees.ShouldNotContain(tree =>
|
|
tree.FilePath.EndsWith("GeneratedRegistrationUnits.g.cs", StringComparison.Ordinal));
|
|
outputCompilation.SyntaxTrees.ShouldNotContain(tree =>
|
|
tree.FilePath.EndsWith(
|
|
"GeneratedFullControlPackageRegistrationFragment.g.cs",
|
|
StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void Does_Not_Claim_ControlMap_Ownership_For_A_Referenced_Control()
|
|
{
|
|
var references = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"))!
|
|
.Split(Path.PathSeparator)
|
|
.Select(path => MetadataReference.CreateFromFile(path))
|
|
.Cast<MetadataReference>()
|
|
.ToImmutableArray();
|
|
var avaloniaControls = CSharpCompilation.Create(
|
|
"Avalonia.Controls",
|
|
[CSharpSyntaxTree.ParseText(
|
|
"""
|
|
namespace Avalonia.Controls;
|
|
public class Control { }
|
|
""",
|
|
cancellationToken: TestContext.Current.CancellationToken)],
|
|
references,
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
|
.ToMetadataReference();
|
|
var commonControls = CSharpCompilation.Create(
|
|
"AtomUI.Controls",
|
|
[CSharpSyntaxTree.ParseText(
|
|
"""
|
|
[assembly: System.Reflection.AssemblyMetadata("AtomUIThemeControlCatalog", "AtomUI")]
|
|
namespace AtomUI.Controls;
|
|
public class SharedControl : Avalonia.Controls.Control { }
|
|
""",
|
|
cancellationToken: TestContext.Current.CancellationToken)],
|
|
references.Add(avaloniaControls),
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
|
.ToMetadataReference();
|
|
var compilation = CreateCompilation(
|
|
"""
|
|
namespace Demo;
|
|
public sealed class LocalControl : Avalonia.Controls.Control { }
|
|
""",
|
|
sourcePath: "Local/LocalControl.cs")
|
|
.RemoveAllReferences()
|
|
.AddReferences(references)
|
|
.AddReferences(avaloniaControls, commonControls);
|
|
|
|
var outputCompilation = RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
new InMemoryAdditionalText(
|
|
"Local/Themes/SharedControlTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"AtomUI.Controls.SharedControl\" />"));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(outputCompilation, "GeneratedRegistrationUnits.g.cs");
|
|
source.ShouldContain("CreateControlDescriptor_SharedControl_");
|
|
source.ShouldNotContain("typeof(global::AtomUI.Controls.SharedControl)");
|
|
source.ShouldNotContain("AtomUI.Controls.SharedControl|ThemeSchemaGeneratorTests%2FLocal");
|
|
source.ShouldContain("Demo.LocalControl");
|
|
}
|
|
|
|
[Fact]
|
|
public void Generates_A_Public_Hidden_Full_Registration_Fragment()
|
|
{
|
|
var outputCompilation = RunGenerator(
|
|
CreateCompilation(TokenSource),
|
|
out var diagnostics,
|
|
new InMemoryAdditionalText(
|
|
"Button/Themes/ButtonTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"Demo.Button\" />"));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(
|
|
outputCompilation,
|
|
"GeneratedFullControlPackageRegistrationFragment.g.cs");
|
|
|
|
source.ShouldContain("public static class GeneratedFullControlPackageRegistrationFragment");
|
|
source.ShouldContain("EditorBrowsableState.Never");
|
|
source.ShouldContain("GeneratedControlPackageRegistration.Register(");
|
|
source.ShouldContain("AssemblyMetadata(\"AtomUI.Linked.Package.v1\"");
|
|
}
|
|
|
|
[Fact]
|
|
public void Derives_Stable_Package_Entries_From_Annotated_Method_Symbols()
|
|
{
|
|
var compilation = CreateCompilation(TokenSource).AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
using AtomUI.Registration;
|
|
|
|
namespace Demo;
|
|
|
|
public static class AlternateThemeManagerBuilderExtensions
|
|
{
|
|
[ControlPackageRegistrationEntry]
|
|
public static AtomUI.IAtomUIBuilder UseAllControls(
|
|
this AtomUI.IAtomUIBuilder builder) => builder;
|
|
}
|
|
""",
|
|
path: "AlternateThemeManagerBuilderExtensions.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
includeRegistrationEntry: true,
|
|
registrationGranularity: null,
|
|
linkedPublish: false,
|
|
new InMemoryAdditionalText(
|
|
"Button/Themes/ButtonTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"Demo.Button\" />"));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
var source = GetGeneratedSource(
|
|
outputCompilation,
|
|
"GeneratedFullControlPackageRegistrationFragment.g.cs");
|
|
source.ShouldContain(
|
|
"Demo.AlternateThemeManagerBuilderExtensions.UseAllControls%3BDemo.ThemeManagerBuilderExtensions.UseControls");
|
|
}
|
|
|
|
[Fact]
|
|
public void Accepts_An_Entry_Return_Type_Assignable_To_IAtomUIBuilder()
|
|
{
|
|
var compilation = CreateCompilation(TokenSource).AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
using AtomUI.Registration;
|
|
|
|
namespace Demo;
|
|
|
|
public interface ICustomAtomUIBuilder : AtomUI.IAtomUIBuilder
|
|
{
|
|
}
|
|
|
|
public static class CustomThemeManagerBuilderExtensions
|
|
{
|
|
[ControlPackageRegistrationEntry]
|
|
public static ICustomAtomUIBuilder UseCustomControls(
|
|
this AtomUI.IAtomUIBuilder builder) => (ICustomAtomUIBuilder)builder;
|
|
}
|
|
""",
|
|
path: "CustomThemeManagerBuilderExtensions.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
var outputCompilation = RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
includeRegistrationEntry: false,
|
|
registrationGranularity: null,
|
|
linkedPublish: false,
|
|
new InMemoryAdditionalText(
|
|
"Button/Themes/ButtonTheme.axaml",
|
|
"<ControlTheme xmlns=\"https://github.com/avaloniaui\" TargetType=\"Demo.Button\" />"));
|
|
|
|
diagnostics.ShouldBeEmpty();
|
|
GetGeneratedSource(
|
|
outputCompilation,
|
|
"GeneratedFullControlPackageRegistrationFragment.g.cs")
|
|
.ShouldContain("Demo.CustomThemeManagerBuilderExtensions.UseCustomControls");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(
|
|
"public static AtomUI.IAtomUIBuilder UseControls(AtomUI.IAtomUIBuilder builder) => builder;",
|
|
"extension method")]
|
|
[InlineData(
|
|
"public static T UseControls<T>(this T builder) where T : AtomUI.IAtomUIBuilder => builder;",
|
|
"non-generic")]
|
|
[InlineData(
|
|
"internal static AtomUI.IAtomUIBuilder UseControls(this AtomUI.IAtomUIBuilder builder) => builder;",
|
|
"public")]
|
|
[InlineData(
|
|
"public static object UseControls(this AtomUI.IAtomUIBuilder builder) => builder;",
|
|
"assignable to IAtomUIBuilder")]
|
|
public void Reports_Invalid_Package_Entry_Declarations_In_The_Producer(
|
|
string methodDeclaration,
|
|
string expectedReason)
|
|
{
|
|
var compilation = CreateCompilation(TokenSource).AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
$$"""
|
|
using AtomUI.Registration;
|
|
|
|
namespace Demo;
|
|
|
|
public static class InvalidThemeManagerBuilderExtensions
|
|
{
|
|
[ControlPackageRegistrationEntry]
|
|
{{methodDeclaration}}
|
|
}
|
|
""",
|
|
path: "InvalidThemeManagerBuilderExtensions.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
includeRegistrationEntry: false,
|
|
registrationGranularity: null,
|
|
linkedPublish: false);
|
|
|
|
var diagnostic = diagnostics.ShouldHaveSingleItem();
|
|
diagnostic.Id.ShouldBe("ATOMUILINK009");
|
|
diagnostic.GetMessage().ShouldContain(expectedReason);
|
|
diagnostic.Location.GetLineSpan().Path.ShouldBe("InvalidThemeManagerBuilderExtensions.cs");
|
|
}
|
|
|
|
[Fact]
|
|
public void Rejects_Overloaded_Annotated_Entry_Names()
|
|
{
|
|
var compilation = CreateCompilation(TokenSource).AddSyntaxTrees(
|
|
CSharpSyntaxTree.ParseText(
|
|
"""
|
|
using AtomUI.Registration;
|
|
|
|
namespace Demo;
|
|
|
|
public static class OverloadedThemeManagerBuilderExtensions
|
|
{
|
|
[ControlPackageRegistrationEntry]
|
|
public static AtomUI.IAtomUIBuilder UseControls(
|
|
this AtomUI.IAtomUIBuilder builder) => builder;
|
|
|
|
public static AtomUI.IAtomUIBuilder UseControls(
|
|
this AtomUI.IAtomUIBuilder builder,
|
|
bool full) => builder;
|
|
}
|
|
""",
|
|
path: "OverloadedThemeManagerBuilderExtensions.cs",
|
|
cancellationToken: TestContext.Current.CancellationToken));
|
|
|
|
RunGenerator(
|
|
compilation,
|
|
out var diagnostics,
|
|
includeRegistrationEntry: false,
|
|
registrationGranularity: null,
|
|
linkedPublish: false);
|
|
|
|
var diagnostic = diagnostics.ShouldHaveSingleItem();
|
|
diagnostic.Id.ShouldBe("ATOMUILINK009");
|
|
diagnostic.GetMessage().ShouldContain("overloaded");
|
|
}
|
|
|
|
private static CSharpCompilation RunGenerator(
|
|
CSharpCompilation compilation,
|
|
out ImmutableArray<Diagnostic> diagnostics,
|
|
params AdditionalText[] additionalTexts)
|
|
{
|
|
return RunGenerator(
|
|
compilation,
|
|
out diagnostics,
|
|
includeRegistrationEntry: true,
|
|
registrationGranularity: null,
|
|
linkedPublish: false,
|
|
additionalTexts);
|
|
}
|
|
|
|
private static CSharpCompilation RunGeneratorWithGranularity(
|
|
CSharpCompilation compilation,
|
|
out ImmutableArray<Diagnostic> diagnostics,
|
|
string registrationGranularity,
|
|
params AdditionalText[] additionalTexts)
|
|
{
|
|
return RunGenerator(
|
|
compilation,
|
|
out diagnostics,
|
|
includeRegistrationEntry: true,
|
|
registrationGranularity,
|
|
linkedPublish: false,
|
|
additionalTexts);
|
|
}
|
|
|
|
private static CSharpCompilation RunLinkedGeneratorWithGranularity(
|
|
CSharpCompilation compilation,
|
|
out ImmutableArray<Diagnostic> diagnostics,
|
|
string registrationGranularity,
|
|
params AdditionalText[] additionalTexts)
|
|
{
|
|
return RunGenerator(
|
|
compilation,
|
|
out diagnostics,
|
|
includeRegistrationEntry: true,
|
|
registrationGranularity,
|
|
linkedPublish: true,
|
|
additionalTexts);
|
|
}
|
|
|
|
private static CSharpCompilation RunGenerator(
|
|
CSharpCompilation compilation,
|
|
out ImmutableArray<Diagnostic> diagnostics,
|
|
bool includeRegistrationEntry,
|
|
string? registrationGranularity,
|
|
bool linkedPublish,
|
|
params AdditionalText[] additionalTexts)
|
|
{
|
|
var cancellationToken = TestContext.Current.CancellationToken;
|
|
if (includeRegistrationEntry)
|
|
{
|
|
compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(
|
|
RegistrationEntrySource,
|
|
path: "ThemeManagerBuilderExtensions.cs",
|
|
cancellationToken: cancellationToken));
|
|
}
|
|
var options = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
if (registrationGranularity is not null)
|
|
{
|
|
options["build_property.AtomUIRegistrationGranularity"] = registrationGranularity;
|
|
}
|
|
if (linkedPublish)
|
|
{
|
|
options["build_property.AtomUILinkedPublish"] = "true";
|
|
options["build_property.AtomUIRegistrationPackageId"] =
|
|
compilation.AssemblyName ?? "ThemeSchemaGeneratorTests";
|
|
}
|
|
AnalyzerConfigOptionsProvider? optionsProvider = options.Count == 0
|
|
? null
|
|
: new TestAnalyzerConfigOptionsProvider(options);
|
|
var generators = linkedPublish
|
|
? new ISourceGenerator[]
|
|
{
|
|
new TokenResourceKeyGenerator().AsSourceGenerator(),
|
|
new LinkedRegistrationPackageManifestGenerator().AsSourceGenerator()
|
|
}
|
|
: [new TokenResourceKeyGenerator().AsSourceGenerator()];
|
|
var driver = CSharpGeneratorDriver.Create(
|
|
generators,
|
|
additionalTexts.ToImmutableArray(),
|
|
(CSharpParseOptions)compilation.SyntaxTrees[0].Options,
|
|
optionsProvider);
|
|
|
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out diagnostics, cancellationToken);
|
|
return (CSharpCompilation)outputCompilation;
|
|
}
|
|
|
|
private static string GetGeneratedSource(CSharpCompilation compilation, string fileName)
|
|
{
|
|
return compilation.SyntaxTrees
|
|
.Single(tree => tree.FilePath.EndsWith(fileName, StringComparison.Ordinal))
|
|
.GetText(TestContext.Current.CancellationToken)
|
|
.ToString();
|
|
}
|
|
|
|
private static CSharpCompilation CreateCompilation(
|
|
string source,
|
|
string assemblyName = "ThemeSchemaGeneratorTests",
|
|
string sourcePath = "TokenSource.cs")
|
|
{
|
|
var references = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"))!
|
|
.Split(Path.PathSeparator)
|
|
.Select(path => MetadataReference.CreateFromFile(path))
|
|
.Cast<MetadataReference>()
|
|
.ToImmutableArray();
|
|
|
|
return CSharpCompilation.Create(
|
|
assemblyName,
|
|
[CSharpSyntaxTree.ParseText(source, path: sourcePath), CSharpSyntaxTree.ParseText(AtomUIStubs)],
|
|
references,
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
|
}
|
|
|
|
private const string TokenSource = """
|
|
using AtomUI.Theme.Algorithms;
|
|
using AtomUI.Theme.Schema;
|
|
using AtomUI.Theme.DesignTokens;
|
|
|
|
namespace Demo
|
|
{
|
|
[GlobalDesignToken]
|
|
public partial class DesignToken : AbstractDesignToken
|
|
{
|
|
[DesignTokenKind(DesignTokenKind.Alias)]
|
|
public string Zeta { get; set; } = string.Empty;
|
|
|
|
[DesignTokenKind(DesignTokenKind.Seed)]
|
|
public double Alpha { get; set; }
|
|
}
|
|
|
|
[ControlDesignToken]
|
|
internal sealed class ButtonToken : AbstractControlDesignToken
|
|
{
|
|
public string Label { get; set; } = string.Empty;
|
|
public double Height { get; set; }
|
|
|
|
public override void CalculateTokenValues(bool isDark)
|
|
{
|
|
Height = EffectiveGlobalToken.ControlHeight;
|
|
}
|
|
}
|
|
|
|
[ThemeAlgorithm(ThemeAlgorithm.Default, 1, ThemeAppearanceEffect.Light)]
|
|
internal sealed class DefaultAlgorithm : IThemeAlgorithm
|
|
{
|
|
public DefaultAlgorithm()
|
|
{
|
|
}
|
|
}
|
|
|
|
[ThemeAlgorithm(ThemeAlgorithm.Dark, 1, ThemeAppearanceEffect.Dark)]
|
|
internal sealed class DarkAlgorithm : IThemeAlgorithm
|
|
{
|
|
public DarkAlgorithm()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
private const string ReorderedTokenSource = """
|
|
using AtomUI.Theme.Algorithms;
|
|
using AtomUI.Theme.Schema;
|
|
using AtomUI.Theme.DesignTokens;
|
|
|
|
namespace Demo
|
|
{
|
|
[ThemeAlgorithm(ThemeAlgorithm.Dark, 1, ThemeAppearanceEffect.Dark)]
|
|
internal sealed class DarkAlgorithm : IThemeAlgorithm
|
|
{
|
|
public DarkAlgorithm()
|
|
{
|
|
}
|
|
}
|
|
|
|
[ThemeAlgorithm(ThemeAlgorithm.Default, 1, ThemeAppearanceEffect.Light)]
|
|
internal sealed class DefaultAlgorithm : IThemeAlgorithm
|
|
{
|
|
public DefaultAlgorithm()
|
|
{
|
|
}
|
|
}
|
|
|
|
[ControlDesignToken]
|
|
internal sealed class ButtonToken : AbstractControlDesignToken
|
|
{
|
|
public double Height { get; set; }
|
|
public string Label { get; set; } = string.Empty;
|
|
|
|
public override void CalculateTokenValues(bool isDark)
|
|
{
|
|
Height = EffectiveGlobalToken.ControlHeight;
|
|
}
|
|
}
|
|
|
|
[GlobalDesignToken]
|
|
public partial class DesignToken : AbstractDesignToken
|
|
{
|
|
[DesignTokenKind(DesignTokenKind.Seed)]
|
|
public double Alpha { get; set; }
|
|
|
|
[DesignTokenKind(DesignTokenKind.Alias)]
|
|
public string Zeta { get; set; } = string.Empty;
|
|
}
|
|
}
|
|
""";
|
|
|
|
private const string RegistrationEntrySource = """
|
|
using AtomUI.Registration;
|
|
|
|
namespace Demo;
|
|
|
|
public static class ThemeManagerBuilderExtensions
|
|
{
|
|
[ControlPackageRegistrationEntry]
|
|
public static AtomUI.IAtomUIBuilder UseControls(
|
|
this AtomUI.IAtomUIBuilder builder) => builder;
|
|
}
|
|
""";
|
|
|
|
private const string AtomUIStubs = """
|
|
namespace Avalonia.Controls
|
|
{
|
|
public class Control
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace Demo
|
|
{
|
|
public sealed class Button : Avalonia.Controls.Control
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme
|
|
{
|
|
public readonly struct ControlTokenRegistration
|
|
{
|
|
public ControlTokenRegistration(System.Type tokenType, string tokenId, string? resourceCatalog)
|
|
{
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
namespace AtomUI.Registration
|
|
{
|
|
[System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)]
|
|
public sealed class ControlPackageRegistrationEntryAttribute : System.Attribute
|
|
{
|
|
}
|
|
|
|
public sealed class AotTrimControlPackageRegistrationBuilder
|
|
{
|
|
public bool TryEnterUnit(string unitId) => true;
|
|
|
|
public void AddControl(AtomUI.Theme.Schema.ControlTokenDescriptor descriptor)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace AtomUI
|
|
{
|
|
public interface IAtomUIBuilder
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme.Resources
|
|
{
|
|
public abstract class TokenResourceExtension<TTokenKind>
|
|
where TTokenKind : System.Enum
|
|
{
|
|
protected TokenResourceExtension()
|
|
{
|
|
}
|
|
|
|
protected TokenResourceExtension(TTokenKind kind)
|
|
{
|
|
}
|
|
|
|
protected virtual object GetResourceKey(TTokenKind kind) => kind;
|
|
}
|
|
|
|
public static class ControlTokenResourceKey
|
|
{
|
|
public static object Global(
|
|
AtomUI.Theme.Schema.ControlTokenIdentity identity,
|
|
SharedTokenKind kind) => kind;
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme
|
|
{
|
|
public enum ThemeAppearance : byte
|
|
{
|
|
Light,
|
|
Dark
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme.Schema
|
|
{
|
|
public enum TokenStage : byte
|
|
{
|
|
Seed,
|
|
Map,
|
|
Alias,
|
|
Control
|
|
}
|
|
|
|
public enum ThemeAppearanceEffect : byte
|
|
{
|
|
Preserve,
|
|
Light,
|
|
Dark
|
|
}
|
|
|
|
public readonly record struct ControlTokenIdentity(string Catalog, string Id);
|
|
|
|
public sealed class TokenDescriptor
|
|
{
|
|
public TokenDescriptor(
|
|
string name,
|
|
int slot,
|
|
TokenStage stage,
|
|
System.Type valueType,
|
|
object resourceKey,
|
|
System.Func<string, object?> parser,
|
|
System.Func<object?, string> formatter,
|
|
System.Func<AtomUI.Theme.DesignTokens.AbstractDesignToken, object?> getter,
|
|
System.Action<AtomUI.Theme.DesignTokens.AbstractDesignToken, object?> setter,
|
|
System.Func<AtomUI.Theme.DesignTokens.AbstractDesignToken, object?> resourceProjector)
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class ControlTokenDescriptor
|
|
{
|
|
public ControlTokenDescriptor(
|
|
System.Type controlType,
|
|
ControlTokenIdentity identity,
|
|
System.Collections.Generic.IReadOnlyList<TokenDescriptor> ownTokens,
|
|
System.Func<AtomUI.Theme.DesignTokens.AbstractControlDesignToken> factory,
|
|
System.Action<AtomUI.Theme.DesignTokens.AbstractControlDesignToken, AtomUI.Theme.ThemeAppearance> evaluator)
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class ThemeAlgorithmDescriptor
|
|
{
|
|
public ThemeAlgorithmDescriptor(
|
|
AtomUI.Theme.Algorithms.ThemeAlgorithm algorithm,
|
|
int revision,
|
|
ThemeAppearanceEffect appearanceEffect,
|
|
System.Func<AtomUI.Theme.Algorithms.IThemeAlgorithm> factory)
|
|
{
|
|
}
|
|
}
|
|
|
|
public static class ThemeTokenValueParser
|
|
{
|
|
public static T Parse<T>(string value)
|
|
{
|
|
return default!;
|
|
}
|
|
}
|
|
|
|
public static class ThemeTokenValueFormatter
|
|
{
|
|
public static string Format<T>(T value)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public static class ThemeResourceValue
|
|
{
|
|
public static object? Project<T>(T value)
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme.Algorithms
|
|
{
|
|
public enum ThemeAlgorithm
|
|
{
|
|
Default = 0,
|
|
Dark = 1,
|
|
Compact = 2
|
|
}
|
|
|
|
public interface IThemeAlgorithm
|
|
{
|
|
}
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false)]
|
|
public sealed class ThemeAlgorithmAttribute : System.Attribute
|
|
{
|
|
public ThemeAlgorithmAttribute(
|
|
ThemeAlgorithm algorithm,
|
|
int revision,
|
|
AtomUI.Theme.Schema.ThemeAppearanceEffect appearanceEffect)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Theme.DesignTokens
|
|
{
|
|
public enum DesignTokenKind
|
|
{
|
|
Seed,
|
|
Map,
|
|
Alias
|
|
}
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Property)]
|
|
public sealed class DesignTokenKindAttribute : System.Attribute
|
|
{
|
|
public DesignTokenKindAttribute(DesignTokenKind kind)
|
|
{
|
|
}
|
|
}
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false)]
|
|
public sealed class GlobalDesignTokenAttribute : System.Attribute
|
|
{
|
|
}
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false)]
|
|
public sealed class ControlDesignTokenAttribute : System.Attribute
|
|
{
|
|
}
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Property)]
|
|
public sealed class NotTokenDefinitionAttribute : System.Attribute
|
|
{
|
|
}
|
|
|
|
public abstract class AbstractDesignToken
|
|
{
|
|
}
|
|
|
|
public sealed class DesignToken : AbstractDesignToken
|
|
{
|
|
[NotTokenDefinition]
|
|
public System.Collections.Generic.IDictionary<string, string> ColorPalettes { get; } =
|
|
new System.Collections.Generic.Dictionary<string, string>();
|
|
|
|
public double ControlHeight { get; set; }
|
|
}
|
|
|
|
public abstract class AbstractControlDesignToken : AbstractDesignToken
|
|
{
|
|
protected DesignToken EffectiveGlobalToken { get; } = new DesignToken();
|
|
|
|
public virtual void CalculateTokenValues(bool isDark)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
private sealed class InMemoryAdditionalText : AdditionalText
|
|
{
|
|
private readonly Microsoft.CodeAnalysis.Text.SourceText _text;
|
|
|
|
internal InMemoryAdditionalText(string path, string text)
|
|
{
|
|
Path = path;
|
|
_text = Microsoft.CodeAnalysis.Text.SourceText.From(text);
|
|
}
|
|
|
|
public override string Path { get; }
|
|
|
|
public override Microsoft.CodeAnalysis.Text.SourceText GetText(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return _text;
|
|
}
|
|
}
|
|
|
|
private sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
|
|
{
|
|
private static readonly AnalyzerConfigOptions s_empty =
|
|
new TestAnalyzerConfigOptions(new Dictionary<string, string>());
|
|
private readonly AnalyzerConfigOptions _globalOptions;
|
|
|
|
internal TestAnalyzerConfigOptionsProvider(IReadOnlyDictionary<string, string> globalOptions)
|
|
{
|
|
_globalOptions = new TestAnalyzerConfigOptions(globalOptions);
|
|
}
|
|
|
|
public override AnalyzerConfigOptions GlobalOptions => _globalOptions;
|
|
|
|
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => s_empty;
|
|
|
|
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => s_empty;
|
|
}
|
|
|
|
private sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions
|
|
{
|
|
private readonly IReadOnlyDictionary<string, string> _values;
|
|
|
|
internal TestAnalyzerConfigOptions(IReadOnlyDictionary<string, string> values)
|
|
{
|
|
_values = values;
|
|
}
|
|
|
|
public override bool TryGetValue(string key, out string value)
|
|
{
|
|
return _values.TryGetValue(key, out value!);
|
|
}
|
|
}
|
|
}
|