diff --git a/CommunityToolkit.App.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml b/CommunityToolkit.App.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml
index f92426ca..f6618a85 100644
--- a/CommunityToolkit.App.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml
+++ b/CommunityToolkit.App.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml
@@ -17,52 +17,52 @@
+ IsOn="{x:Bind BoolValue, Mode=TwoWay}" />
+ ItemsSource="{x:Bind Options}"
+ SelectedIndex="0"
+ SelectedItem="{x:Bind Value, Mode=TwoWay}" />
+ Maximum="{x:Bind Max, Mode=OneWay}"
+ Minimum="{x:Bind Min, Mode=OneWay}"
+ StepFrequency="{x:Bind Step, Mode=OneWay}"
+ Value="{x:Bind Initial, Mode=TwoWay}" />
+ Maximum="{x:Bind Max, Mode=OneWay}"
+ Minimum="{x:Bind Min, Mode=OneWay}"
+ SmallChange="{x:Bind Step, Mode=OneWay}"
+ SpinButtonPlacementMode="Compact"
+ Value="{x:Bind Initial, Mode=TwoWay}" />
+ Header="{x:Bind Title, Mode=OneWay}"
+ Text="{x:Bind PlaceholderText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+ ItemsSource="{x:Bind SampleOptions, Mode=OneWay}">
+ Spacing="12" />
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/InMemoryAdditionalText.cs b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/InMemoryAdditionalText.cs
new file mode 100644
index 00000000..45888822
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/InMemoryAdditionalText.cs
@@ -0,0 +1,19 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
+
+internal class InMemoryAdditionalText : AdditionalText
+{
+ private readonly SourceText _content;
+
+ public InMemoryAdditionalText(string path, string content)
+ {
+ Path = path;
+ _content = SourceText.From(content, Encoding.UTF8);
+ }
+
+ public override string Path { get; }
+
+ public override SourceText GetText(CancellationToken cancellationToken = default) => _content;
+}
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/SourceGeneratorRunResult.cs b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/SourceGeneratorRunResult.cs
new file mode 100644
index 00000000..37ac706d
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/SourceGeneratorRunResult.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+
+namespace CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
+
+public record SourceGeneratorRunResult(Compilation Compilation, ImmutableArray Diagnostics);
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.Compilation.cs b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.Compilation.cs
new file mode 100644
index 00000000..5d309c28
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.Compilation.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Collections.Immutable;
+
+namespace CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
+
+public static partial class TestHelpers
+{
+ internal static IEnumerable GetAllReferencedAssemblies()
+ {
+ return from assembly in AppDomain.CurrentDomain.GetAssemblies()
+ where !assembly.IsDynamic
+ let reference = MetadataReference.CreateFromFile(assembly.Location)
+ select reference;
+ }
+
+ internal static SyntaxTree ToSyntaxTree(this string source)
+ {
+ return CSharpSyntaxTree.ParseText(source,
+ CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
+ }
+
+ internal static CSharpCompilation CreateCompilation(this SyntaxTree syntaxTree, string assemblyName, IEnumerable? references = null)
+ {
+ return CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, references ?? GetAllReferencedAssemblies(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+
+ internal static CSharpCompilation CreateCompilation(this IEnumerable syntaxTree, string assemblyName, IEnumerable? references = null)
+ {
+ return CSharpCompilation.Create(assemblyName, syntaxTree, references ?? GetAllReferencedAssemblies(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+
+ internal static GeneratorDriver CreateSourceGeneratorDriver(this SyntaxTree syntaxTree, params IIncrementalGenerator[] generators)
+ {
+ return CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
+ }
+
+ internal static GeneratorDriver CreateSourceGeneratorDriver(this Compilation compilation, params IIncrementalGenerator[] generators)
+ {
+ return CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)compilation.SyntaxTrees.First().Options);
+ }
+
+ internal static GeneratorDriver WithMarkdown(this GeneratorDriver driver, params string[] markdownFilesToCreate)
+ {
+ foreach (var markdown in markdownFilesToCreate)
+ {
+ if (!string.IsNullOrWhiteSpace(markdown))
+ {
+ var text = new InMemoryAdditionalText(@"C:\pathtorepo\components\experiment\samples\experiment.Samples\documentation.md", markdown);
+ driver = driver.AddAdditionalTexts(ImmutableArray.Create(text));
+ }
+ }
+
+ return driver;
+ }
+}
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.cs b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.cs
new file mode 100644
index 00000000..c94da675
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.cs
@@ -0,0 +1,61 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Immutable;
+
+namespace CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
+
+public static partial class TestHelpers
+{
+ internal static SourceGeneratorRunResult RunSourceGenerator(this string source, string assemblyName, string markdown = "") where TGenerator : class, IIncrementalGenerator, new() => RunSourceGenerator(source.ToSyntaxTree(), assemblyName, markdown);
+
+ internal static SourceGeneratorRunResult RunSourceGenerator(this SyntaxTree syntaxTree, string assemblyName, string markdown = "")
+ where TGenerator : class, IIncrementalGenerator, new()
+ {
+ var compilation = syntaxTree.CreateCompilation(assemblyName); // assembly name should always be supplied in param
+ return RunSourceGenerator(compilation, markdown);
+ }
+
+ internal static SourceGeneratorRunResult RunSourceGenerator(this Compilation compilation, string markdown = "")
+ where TGenerator : class, IIncrementalGenerator, new()
+ {
+ // Create a driver for the source generator
+ var driver = compilation
+ .CreateSourceGeneratorDriver(new TGenerator())
+ .WithMarkdown(markdown);
+
+ // Update the original compilation using the source generator
+ _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation generatorCompilation, out ImmutableArray postGeneratorCompilationDiagnostics);
+
+ return new(generatorCompilation, postGeneratorCompilationDiagnostics);
+ }
+
+ internal static void AssertDiagnosticsAre(this IEnumerable diagnostics, params DiagnosticDescriptor[] expectedDiagnosticDescriptors)
+ {
+ var expectedIds = expectedDiagnosticDescriptors.Select(x => x.Id).ToHashSet();
+ var resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToHashSet();
+
+ Assert.IsTrue(resultingIds.SetEquals(expectedIds), $"Expected [{string.Join(", ", expectedIds)}] diagnostic Ids. Got [{string.Join(", ", resultingIds)}]");
+ }
+
+ internal static void AssertNoCompilationErrors(this Compilation outputCompilation)
+ {
+ var generatedCompilationDiagnostics = outputCompilation.GetDiagnostics();
+ Assert.IsTrue(generatedCompilationDiagnostics.All(x => x.Severity != DiagnosticSeverity.Error), $"Expected no generated compilation errors. Got: \n{string.Join("\n", generatedCompilationDiagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).Select(x => $"[{x.Id}: {x.GetMessage()}]"))}");
+ }
+
+ internal static string GetFileContentsByName(this Compilation compilation, string filename)
+ {
+ var generatedTree = compilation.SyntaxTrees.SingleOrDefault(tree => Path.GetFileName(tree.FilePath) == filename);
+ Assert.IsNotNull(generatedTree, $"No file named {filename} was generated");
+
+ return generatedTree.ToString();
+ }
+
+ internal static void AssertSourceGenerated(this Compilation compilation, string filename, string expectedContents)
+ {
+ }
+
+ internal static void AssertDiagnosticsAre(this SourceGeneratorRunResult result, params DiagnosticDescriptor[] expectedDiagnosticDescriptors) => AssertDiagnosticsAre(result.Diagnostics, expectedDiagnosticDescriptors);
+
+ internal static void AssertNoCompilationErrors(this SourceGeneratorRunResult result) => AssertNoCompilationErrors(result.Compilation);
+}
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs
new file mode 100644
index 00000000..17d5e8ee
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs
@@ -0,0 +1,385 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Tooling.SampleGen.Diagnostics;
+using CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
+using Microsoft.CodeAnalysis;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Linq;
+
+namespace CommunityToolkit.Tooling.SampleGen.Tests;
+
+[TestClass]
+public partial class ToolkitSampleGeneratedPaneTests
+{
+ private const string SAMPLE_ASM_NAME = "PaneTests.Samples";
+
+ [TestMethod]
+ public void PaneOption_GeneratesWithoutDiagnostics()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public Sample()
+ {{
+ var x = this.Test;
+ var y = this.TextFontFamily;
+ }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre();
+ }
+
+ [TestMethod]
+ public void PaneOption_GeneratesTitleProperty()
+ {
+ // The sample registry is designed to be declared in the sample project, and generated in the project head where its displayed in the UI as data.
+ // To test the contents of the generated sample registry, we must replicate this setup.
+ var sampleProjectAssembly = """
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {
+ [ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")]
+ [ToolkitSample(id: nameof(Sample), "Test Sample", description: "")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {
+ public Sample()
+ {
+ }
+ }
+ }
+
+ namespace Windows.UI.Xaml.Controls
+ {
+ public class UserControl { }
+ }
+ """.ToSyntaxTree()
+ .CreateCompilation("MyApp.Samples")
+ .ToMetadataReference();
+
+ // Create application head that references generated sample project
+ var headCompilation = string.Empty
+ .ToSyntaxTree()
+ .CreateCompilation("MyApp.Head")
+ .AddReferences(sampleProjectAssembly);
+
+ // Run source generator
+ var result = headCompilation.RunSourceGenerator();
+
+ result.AssertDiagnosticsAre();
+ result.AssertNoCompilationErrors();
+
+ Assert.AreEqual(result.Compilation.GetFileContentsByName("ToolkitSampleRegistry.g.cs"), """
+ #nullable enable
+ namespace CommunityToolkit.Tooling.SampleGen;
+
+ public static class ToolkitSampleRegistry
+ {
+ public static System.Collections.Generic.Dictionary Listing
+ { get; } = new() {
+ ["Sample"] = new CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleMetadata("Sample", "Test Sample", "", typeof(MyApp.Sample), () => new MyApp.Sample(), null, null, new CommunityToolkit.Tooling.SampleGen.Metadata.IGeneratedToolkitSampleOptionViewModel[] { new CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleNumericOptionMetadataViewModel(name: "TextSize", initial: 12, min: 8, max: 48, step: 2, showAsNumberBox: false, title: "FontSize") })
+ };
+ }
+ """, "Unexpected code generated");
+ }
+
+ [TestMethod]
+ public void PaneOption_GeneratesProperty_DuplicatePropNamesAcrossSampleClasses()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public Sample()
+ {{
+ var x = this.Test;
+ var y = this.TextFontFamily;
+ }}
+ }}
+
+ [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
+
+ [ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
+ public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public Sample2()
+ {{
+ var x = this.Test;
+ var y = this.TextFontFamily;
+ }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre();
+ }
+
+ [TestMethod]
+ public void PaneOptionOnNonSample()
+ {
+ string source = @"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {
+ [ToolkitSampleBoolOption(""BindToMe"", false, Title = ""Toggle visibility"")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {
+ }
+ }
+
+ namespace Windows.UI.Xaml.Controls
+ {
+ public class UserControl { }
+ }";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+ result.Diagnostics.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample);
+ }
+
+ [DataRow("", DisplayName = "Empty string"), DataRow(" ", DisplayName = "Only whitespace"), DataRow("Test ", DisplayName = "Text with whitespace")]
+ [DataRow("_", DisplayName = "Underscore"), DataRow("$", DisplayName = "Dollar sign"), DataRow("%", DisplayName = "Percent symbol")]
+ [DataRow("class", DisplayName = "Reserved keyword 'class'"), DataRow("string", DisplayName = "Reserved keyword 'string'"), DataRow("sealed", DisplayName = "Reserved keyword 'sealed'"), DataRow("ref", DisplayName = "Reserved keyword 'ref'")]
+ [TestMethod]
+ public void PaneOptionWithBadName(string name)
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ [ToolkitSampleBoolOption(""{name}"", false, Title = ""Toggle visibility"")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneOptionWithBadName, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithConflictingPropertyName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public string IsVisible {{ get; set; }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneOptionWithConflictingName, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithConflictingInheritedPropertyName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Base
+ {{
+ }}
+
+ public class Base : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public string IsVisible {{ get; set; }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneOptionWithConflictingName, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithDuplicateName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle x"")]
+ [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", Title = ""Text foreground"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneOptionWithDuplicateName, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithDuplicateName_AllowedBetweenSamples()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+
+ [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
+
+ [ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
+ public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+
+ [TestMethod]
+ public void SampleGeneratedOptionAttributeOnUnsupportedType()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
+ [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle visibility"")]
+ public partial class Sample
+ {{
+ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SampleGeneratedOptionAttributeOnUnsupportedType, DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample);
+ }
+
+ [TestMethod]
+ public void PaneMultipleChoiceOptionWithNoChoices()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Tooling.SampleGen;
+ using CommunityToolkit.Tooling.SampleGen.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", Title = ""Font family"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SamplePaneMultiChoiceOptionWithNoChoices, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ }
+}
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.Documentation.cs b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.Documentation.cs
index 1b2b156e..f9c6ffef 100644
--- a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.Documentation.cs
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.Documentation.cs
@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
namespace CommunityToolkit.Tooling.SampleGen.Tests;
@@ -40,7 +41,10 @@ public void MissingFrontMatterSection()
Without any front matter.
";
- VerifyGeneratedDiagnostics(SimpleSource, markdown, DiagnosticDescriptors.MarkdownYAMLFrontMatterException.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
+ var result = SimpleSource.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.MarkdownYAMLFrontMatterException, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
}
[DataRow(1, DisplayName = "Title")]
@@ -74,7 +78,11 @@ public void MissingFrontMatterField(int removeline)
lines.RemoveAt(removeline);
markdown = String.Join('\n', lines);
- VerifyGeneratedDiagnostics(SimpleSource, markdown, DiagnosticDescriptors.MarkdownYAMLFrontMatterMissingField.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id); // We won't see the sample reference as we bail out when the front matter fails to be complete...
+ var result = SimpleSource.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ // We won't see the sample reference as we bail out when the front matter fails to be complete
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.MarkdownYAMLFrontMatterMissingField, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
}
[TestMethod]
@@ -97,9 +105,10 @@ public void MarkdownInvalidSampleReference()
Without any front matter.
";
- VerifyGeneratedDiagnostics(SimpleSource, markdown,
- DiagnosticDescriptors.MarkdownSampleIdNotFound.Id,
- DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
+ var result = SimpleSource.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.MarkdownSampleIdNotFound, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
}
[TestMethod]
@@ -120,9 +129,10 @@ public void DocumentationMissingSample()
# This is some test documentation...
Without any sample.";
- VerifyGeneratedDiagnostics(SimpleSource, markdown,
- DiagnosticDescriptors.DocumentationHasNoSamples.Id,
- DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
+ var result = SimpleSource.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.DocumentationHasNoSamples, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
}
[TestMethod]
@@ -143,8 +153,12 @@ public void DocumentationValid()
# This is some test documentation...
Which is valid.
> [!SAMPLE Sample]";
+
+
+ var result = SimpleSource.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
- VerifyGeneratedDiagnostics(SimpleSource, markdown);
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre();
}
[TestMethod]
@@ -165,9 +179,10 @@ public void DocumentationInvalidDiscussionId()
# This is some test documentation...
Without an invalid discussion id.";
- VerifyGeneratedDiagnostics(string.Empty, markdown,
- DiagnosticDescriptors.MarkdownYAMLFrontMatterException.Id,
- DiagnosticDescriptors.DocumentationHasNoSamples.Id);
+ var result = string.Empty.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.MarkdownYAMLFrontMatterException, DiagnosticDescriptors.DocumentationHasNoSamples);
}
[TestMethod]
@@ -188,8 +203,9 @@ public void DocumentationInvalidIssueId()
# This is some test documentation...
Without an invalid discussion id.";
- VerifyGeneratedDiagnostics(string.Empty, markdown,
- DiagnosticDescriptors.MarkdownYAMLFrontMatterException.Id,
- DiagnosticDescriptors.DocumentationHasNoSamples.Id);
+ var result = string.Empty.RunSourceGenerator(SAMPLE_ASM_NAME, markdown);
+
+ result.AssertNoCompilationErrors();
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.MarkdownYAMLFrontMatterException, DiagnosticDescriptors.DocumentationHasNoSamples);
}
}
diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.cs b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.cs
index c051c86b..5c9ddc5d 100644
--- a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.cs
+++ b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleMetadataTests.cs
@@ -2,11 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using CommunityToolkit.Tooling.SampleGen.Attributes;
using CommunityToolkit.Tooling.SampleGen.Diagnostics;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.Text;
+using CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
@@ -16,341 +13,7 @@ namespace CommunityToolkit.Tooling.SampleGen.Tests;
[TestClass]
public partial class ToolkitSampleMetadataTests
{
- [TestMethod]
- public void PaneOption_GeneratesWithoutDiagnostics()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
-
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- public Sample()
- {{
- var x = this.Test;
- var y = this.TextFontFamily;
- }}
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty);
- }
-
- [TestMethod]
- public void PaneOption_GeneratesTitleProperty()
- {
- var source = """
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {
- [ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")]
- [ToolkitSample(id: nameof(Sample), "Test Sample", description: "")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {
- public Sample()
- {
- var x = this.Test;
- var y = this.TextFontFamily;
- }
- }
- }
-
- namespace Windows.UI.Xaml.Controls
- {
- public class UserControl { }
- }
- """;
-
- var result = """
- #nullable enable
- namespace CommunityToolkit.Tooling.SampleGen;
-
- public static class ToolkitSampleRegistry
- {
- public static System.Collections.Generic.Dictionary Listing
- { get; } = new() {
- ["Sample"] = new CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleMetadata("Sample", "Test Sample", "", typeof(MyApp.Sample), () => new MyApp.Sample(), null, null, new CommunityToolkit.Tooling.SampleGen.Metadata.IGeneratedToolkitSampleOptionViewModel[] { new CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleNumericOptionMetadataViewModel(name: "TextSize", initial: 12, min: 8, max: 48, step: 2, showAsNumberBox: false, title: "FontSize") })
- };
- }
- """;
-
- VerifyGenerateSources("MyApp.Tests", source, new[] { new ToolkitSampleMetadataGenerator() }, ignoreDiagnostics: true, ("ToolkitSampleRegistry.g.cs", result));
- }
-
- // https://github.com/CommunityToolkit/Labs-Windows/issues/175
- [TestMethod]
- public void PaneOption_GeneratesProperty_DuplicatePropNamesAcrossSampleClasses()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
-
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- public Sample()
- {{
- var x = this.Test;
- var y = this.TextFontFamily;
- }}
- }}
-
- [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
-
- [ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
- public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
- {{
- public Sample2()
- {{
- var x = this.Test;
- var y = this.TextFontFamily;
- }}
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty);
- }
-
- [TestMethod]
- public void PaneOptionOnNonSample()
- {
- string source = @"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {
- [ToolkitSampleBoolOption(""BindToMe"", false, Title = ""Toggle visibility"")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {
- }
- }
-
- namespace Windows.UI.Xaml.Controls
- {
- public class UserControl { }
- }";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample.Id);
- }
-
- [DataRow("", DisplayName = "Empty string"), DataRow(" ", DisplayName = "Only whitespace"), DataRow("Test ", DisplayName = "Text with whitespace")]
- [DataRow("_", DisplayName = "Underscore"), DataRow("$", DisplayName = "Dollar sign"), DataRow("%", DisplayName = "Percent symbol")]
- [DataRow("class", DisplayName = "Reserved keyword 'class'"), DataRow("string", DisplayName = "Reserved keyword 'string'"), DataRow("sealed", DisplayName = "Reserved keyword 'sealed'"), DataRow("ref", DisplayName = "Reserved keyword 'ref'")]
- [TestMethod]
- public void PaneOptionWithBadName(string name)
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- [ToolkitSampleBoolOption(""{name}"", false, Title = ""Toggle visibility"")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneOptionWithBadName.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void PaneOptionWithConflictingPropertyName()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- public string IsVisible {{ get; set; }}
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneOptionWithConflictingName.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void PaneOptionWithConflictingInheritedPropertyName()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Base
- {{
- }}
-
- public class Base : Windows.UI.Xaml.Controls.UserControl
- {{
- public string IsVisible {{ get; set; }}
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneOptionWithConflictingName.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void PaneOptionWithDuplicateName()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle x"")]
- [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", Title = ""Text foreground"")]
-
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneOptionWithDuplicateName.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void PaneOptionWithDuplicateName_AllowedBetweenSamples()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
-
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- }}
-
- [ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
-
- [ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
- public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
- {{
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void PaneMultipleChoiceOptionWithNoChoices()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", Title = ""Font family"")]
-
- [ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
- public partial class Sample : Windows.UI.Xaml.Controls.UserControl
- {{
- }}
- }}
-
- namespace Windows.UI.Xaml.Controls
- {{
- public class UserControl {{ }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SamplePaneMultiChoiceOptionWithNoChoices.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- [TestMethod]
- public void SampleGeneratedOptionAttributeOnUnsupportedType()
- {
- var source = $@"
- using System.ComponentModel;
- using CommunityToolkit.Tooling.SampleGen;
- using CommunityToolkit.Tooling.SampleGen.Attributes;
-
- namespace MyApp
- {{
- [ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
- [ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle visibility"")]
- public partial class Sample
- {{
- }}
- }}";
-
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SampleGeneratedOptionAttributeOnUnsupportedType.Id, DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample.Id);
- }
+ private const string SAMPLE_ASM_NAME = "MetadataTests.Samples";
[TestMethod]
public void SampleAttributeOnUnsupportedType()
@@ -368,7 +31,10 @@ public partial class Sample
}}
}}";
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SampleAttributeOnUnsupportedType.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SampleAttributeOnUnsupportedType, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ result.AssertNoCompilationErrors();
}
[TestMethod]
@@ -397,7 +63,10 @@ namespace Windows.UI.Xaml.Controls
public class UserControl {{ }}
}}";
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SampleOptionPaneAttributeOnUnsupportedType.Id, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
+
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SampleOptionPaneAttributeOnUnsupportedType, DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ result.AssertNoCompilationErrors();
}
[TestMethod]
@@ -422,136 +91,9 @@ namespace Windows.UI.Xaml.Controls
public class UserControl {{ }}
}}";
- // TODO: We should have this return the references to the registries or something so we can check the generated output?
- VerifyGeneratedDiagnostics(source, string.Empty, DiagnosticDescriptors.SampleNotReferencedInMarkdown.Id);
- }
-
- ///
- /// Verifies the output of a source generator.
- ///
- /// The generator type to use.
- /// The input source to process.
- /// The input documentation info to process.
- /// The diagnostic ids to expect for the input source code.
- private static void VerifyGeneratedDiagnostics(string source, string markdown, params string[] diagnosticsIds)
- where TGenerator : class, IIncrementalGenerator, new()
- {
- VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source), markdown, diagnosticsIds);
- }
-
- ///
- /// Verifies the output of a source generator.
- ///
- /// The generator type to use.
- /// The input source tree to process.
- /// The input documentation info to process.
- /// The diagnostic ids to expect for the input source code.
- private static void VerifyGeneratedDiagnostics(SyntaxTree syntaxTree, string markdown, params string[] diagnosticsIds)
- where TGenerator : class, IIncrementalGenerator, new()
- {
- var sampleAttributeType = typeof(ToolkitSampleAttribute);
-
- var references =
- from assembly in AppDomain.CurrentDomain.GetAssemblies()
- where !assembly.IsDynamic
- let reference = MetadataReference.CreateFromFile(assembly.Location)
- select reference;
-
- var compilation = CSharpCompilation.Create(
- "original.Samples",
- new[] { syntaxTree },
- references,
- new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
-
- var compilationDiagnostics = compilation.GetDiagnostics();
-
- IIncrementalGenerator generator = new TGenerator();
-
- GeneratorDriver driver =
- CSharpGeneratorDriver
- .Create(generator)
- .WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
-
- if (!string.IsNullOrWhiteSpace(markdown))
- {
- var text = new InMemoryAdditionalText(@"C:\pathtorepo\components\experiment\samples\experiment.Samples\documentation.md", markdown);
-
- driver = driver.AddAdditionalTexts(ImmutableArray.Create(text));
- }
-
- _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics);
-
- HashSet resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToHashSet();
- var generatedCompilationDiaghostics = outputCompilation.GetDiagnostics();
-
- Assert.IsTrue(resultingIds.SetEquals(diagnosticsIds), $"Expected one of [{string.Join(", ", diagnosticsIds)}] diagnostic Ids. Got [{string.Join(", ", resultingIds)}]");
- Assert.IsTrue(generatedCompilationDiaghostics.All(x => x.Severity != DiagnosticSeverity.Error), $"Expected no generated compilation errors. Got: \n{string.Join("\n", generatedCompilationDiaghostics.Where(x => x.Severity == DiagnosticSeverity.Error).Select(x => $"[{x.Id}: {x.GetMessage()}]"))}");
-
- GC.KeepAlive(sampleAttributeType);
- }
-
- //// See: https://github.com/CommunityToolkit/dotnet/blob/c2053562d1a4d4829fc04b1cb86d1564c2c4a03c/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs#L103
- ///
- /// Generates the requested sources
- ///
- /// The input source to process.
- /// The generators to apply to the input syntax tree.
- /// The source files to compare.
- private static void VerifyGenerateSources(string assemblyName, string source, IIncrementalGenerator[] generators, bool ignoreDiagnostics = false, params (string Filename, string Text)[] results)
- {
- // Ensure our types are loaded
- Type sampleattributeObjectType = typeof(ToolkitSampleAttribute);
-
- // Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies)
- IEnumerable references =
- from assembly in AppDomain.CurrentDomain.GetAssemblies()
- where !assembly.IsDynamic
- let reference = MetadataReference.CreateFromFile(assembly.Location)
- select reference;
-
- SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
-
- // Create a syntax tree with the input source
- CSharpCompilation compilation = CSharpCompilation.Create(
- assemblyName,
- new SyntaxTree[] { syntaxTree },
- references,
- new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
-
- GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
-
- // Run all source generators on the input source code
- _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics);
-
- // Ensure that no diagnostics were generated
- if (!ignoreDiagnostics)
- {
- CollectionAssert.AreEquivalent(Array.Empty(), diagnostics);
- }
-
- foreach ((string filename, string text) in results)
- {
- SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filename);
-
- Assert.AreEqual(text, generatedTree.ToString());
- }
-
- GC.KeepAlive(sampleattributeObjectType);
- }
-
- // From: https://github.com/dotnet/roslyn/blob/main/src/Compilers/Test/Core/SourceGeneration/TestGenerators.cs
- internal class InMemoryAdditionalText : AdditionalText
- {
- private readonly SourceText _content;
-
- public InMemoryAdditionalText(string path, string content)
- {
- Path = path;
- _content = SourceText.From(content, Encoding.UTF8);
- }
-
- public override string Path { get; }
+ var result = source.RunSourceGenerator(SAMPLE_ASM_NAME);
- public override SourceText GetText(CancellationToken cancellationToken = default) => _content;
+ result.AssertDiagnosticsAre(DiagnosticDescriptors.SampleNotReferencedInMarkdown);
+ result.AssertNoCompilationErrors();
}
}
diff --git a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleBoolOptionAttribute.cs b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleBoolOptionAttribute.cs
index 24577848..7b600b7d 100644
--- a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleBoolOptionAttribute.cs
+++ b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleBoolOptionAttribute.cs
@@ -23,7 +23,6 @@ public sealed class ToolkitSampleBoolOptionAttribute : ToolkitSampleOptionBaseAt
public ToolkitSampleBoolOptionAttribute(string bindingName, bool defaultState)
: base(bindingName, defaultState)
{
-
}
///
diff --git a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs
index 109b438d..406baa52 100644
--- a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs
+++ b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs
@@ -5,7 +5,7 @@
namespace CommunityToolkit.Tooling.SampleGen.Attributes;
///
-/// Represents a boolean sample option.
+/// Generates a property and multi-choice option in the sample option pane that can be used to update it.
///
///
/// Using this attribute will automatically generate an -enabled property
diff --git a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleOptionBaseAttribute.cs b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleOptionBaseAttribute.cs
index 9df4dfe9..ac4dd33e 100644
--- a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleOptionBaseAttribute.cs
+++ b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleOptionBaseAttribute.cs
@@ -24,7 +24,7 @@ public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState
///
/// A name that you can bind to in your XAML.
///
- public string Name { get; }
+ public string Name { get; internal set; }
///
/// The default state.
diff --git a/CommunityToolkit.Tooling.SampleGen/CommunityToolkit.Tooling.SampleGen.csproj b/CommunityToolkit.Tooling.SampleGen/CommunityToolkit.Tooling.SampleGen.csproj
index ae539b98..f1ab648c 100644
--- a/CommunityToolkit.Tooling.SampleGen/CommunityToolkit.Tooling.SampleGen.csproj
+++ b/CommunityToolkit.Tooling.SampleGen/CommunityToolkit.Tooling.SampleGen.csproj
@@ -4,7 +4,7 @@
netstandard2.0
enable
nullable
- 10.0
+ 11.0
diff --git a/CommunityToolkit.Tooling.SampleGen/GeneratorExtensions.cs b/CommunityToolkit.Tooling.SampleGen/GeneratorExtensions.cs
index 23bae29a..8f9aafaf 100644
--- a/CommunityToolkit.Tooling.SampleGen/GeneratorExtensions.cs
+++ b/CommunityToolkit.Tooling.SampleGen/GeneratorExtensions.cs
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace CommunityToolkit.Tooling.SampleGen;
@@ -12,18 +14,29 @@ public static class GeneratorExtensions
/// Crawls a namespace and all child namespaces for all contained types.
///
/// A flattened enumerable of s.
- public static IEnumerable CrawlForAllNamedTypes(this INamespaceSymbol namespaceSymbol)
+ public static IEnumerable CrawlForAllSymbols(this INamespaceSymbol namespaceSymbol)
{
- foreach (var member in namespaceSymbol.GetMembers())
+ // Get all classes and methods
+ foreach (var item in namespaceSymbol.GetTypeMembers())
+ {
+ yield return item;
+
+ foreach (var itemMember in item.GetMembers())
+ {
+ if (itemMember.Kind == SymbolKind.Method)
+ yield return itemMember;
+ }
+ }
+
+ foreach (var member in namespaceSymbol.GetNamespaceMembers())
{
if (member is INamespaceSymbol nestedNamespace)
{
- foreach (var item in CrawlForAllNamedTypes(nestedNamespace))
+ foreach (var item in CrawlForAllSymbols(nestedNamespace))
+ {
yield return item;
+ }
}
-
- if (member is INamedTypeSymbol typeSymbol)
- yield return typeSymbol;
}
}
diff --git a/CommunityToolkit.Tooling.SampleGen/Metadata/ToolkitSampleButtonCommand.cs b/CommunityToolkit.Tooling.SampleGen/Metadata/ToolkitSampleButtonCommand.cs
new file mode 100644
index 00000000..b27f7375
--- /dev/null
+++ b/CommunityToolkit.Tooling.SampleGen/Metadata/ToolkitSampleButtonCommand.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Input;
+
+namespace CommunityToolkit.Tooling.SampleGen.Metadata;
+
+///
+/// A command that invokes the provided when executed.
+///
+public class ToolkitSampleButtonCommand : ICommand
+{
+ private readonly Action _callback;
+
+ public ToolkitSampleButtonCommand(Action callback)
+ {
+ _callback = callback;
+ }
+
+ ///
+ public event EventHandler? CanExecuteChanged;
+
+ ///
+ public bool CanExecute(object parameter)
+ {
+ return true;
+ }
+ ///
+ public void Execute(object parameter)
+ {
+ _callback();
+ }
+}
diff --git a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs
index d13cbbf5..0d37a907 100644
--- a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs
+++ b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs
@@ -8,7 +8,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
namespace CommunityToolkit.Tooling.SampleGen;
@@ -23,16 +22,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
var symbolsInExecutingAssembly = context.SyntaxProvider
.CreateSyntaxProvider(
- static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0,
+ static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0 || s is MethodDeclarationSyntax m && m.AttributeLists.Count > 0,
static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node))
.Where(static m => m is not null)
.Select(static (x, _) => x!);
var symbolsInReferencedAssemblies = context.CompilationProvider
- .SelectMany((x, _) => x.SourceModule.ReferencedAssemblySymbols)
- .SelectMany((asm, _) => asm.GlobalNamespace.CrawlForAllNamedTypes())
- .Where(x => x.TypeKind == TypeKind.Class && x.CanBeReferencedByName)
- .Select((x, _) => (ISymbol)x);
+ .SelectMany((x, _) => x.SourceModule.ReferencedAssemblySymbols)
+ .SelectMany((asm, _) => asm.GlobalNamespace.CrawlForAllSymbols());
var markdownFiles = context.AdditionalTextsProvider
.Where(static file => file.Path.EndsWith(".md"))
@@ -56,11 +53,11 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa
// Find and reconstruct generated pane option attributes + the original type symbol.
var generatedPaneOptions = allAttributeData
- .Select(static (x, _) =>
+ .Select((x, _) =>
{
(ISymbol Symbol, ToolkitSampleOptionBaseAttribute Attribute) item = default;
- // Try and get base attribute of whatever sample attribute types we support.
+ // Reconstruct declared sample option attribute class instances from Roslyn symbols.
if (x.Item2.TryReconstructAs() is ToolkitSampleBoolOptionAttribute boolOptionAttribute)
{
item = (x.Item1, boolOptionAttribute);
@@ -78,15 +75,14 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa
item = (x.Item1, textOptionAttribute);
}
- // Add extra property data, like Title back to Attribute
- if (item.Attribute != null && x.Item2.TryGetNamedArgument(nameof(ToolkitSampleOptionBaseAttribute.Title), out string? title) && !string.IsNullOrWhiteSpace(title))
+ // Add extra property data, like Title, back to Attribute
+ if (item.Attribute is not null && x.Item2.TryGetNamedArgument(nameof(ToolkitSampleOptionBaseAttribute.Title), out string? title) && !string.IsNullOrWhiteSpace(title))
{
item.Attribute.Title = title;
}
return item;
})
- .Where(static x => x != default)
.Collect();
// Find and reconstruct sample attributes
@@ -116,12 +112,11 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa
{
var toolkitSampleAttributeData = data.Left.Left.Left.Right.Where(x => x != default).Distinct();
var optionsPaneAttribute = data.Left.Left.Left.Left.Where(x => x != default).Distinct();
- var generatedOptionPropertyData = data.Left.Left.Right.Where(x => x != default);
+ var generatedOptionPropertyData = data.Left.Left.Right.Where(x => x.Attribute is not null && x.Symbol is not null);
var markdownFileData = data.Left.Right.Where(x => x != default).Distinct();
var currentAssembly = data.Right;
var isExecutingInSampleProject = currentAssembly?.EndsWith(".Samples") ?? false;
- var isExecutingInTestProject = currentAssembly?.EndsWith(".Tests") ?? false;
// Reconstruct sample metadata from attributes
var sampleMetadata = toolkitSampleAttributeData
@@ -135,7 +130,7 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa
sample.Attribute.Description,
sample.AttachedQualifiedTypeName,
optionsPaneAttribute.FirstOrDefault(x => x.Item1?.SampleId == sample.Attribute.Id).Item2?.ToString(),
- generatedOptionPropertyData.Where(x => ReferenceEquals(x.Item1, sample.Symbol)).Select(x => x.Item2)
+ generatedOptionPropertyData.Where(x => x.Symbol.Equals(sample.Symbol, SymbolEqualityComparer.Default)).Select(x => x.Item2)
)
);
@@ -147,9 +142,7 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa
ReportDocumentDiagnostics(ctx, sampleMetadata, markdownFileData, toolkitSampleAttributeData, docFrontMatter);
}
- // For tests we need one pass to do diagnostics and registry as we're in a contrived environment that'll have both our scenarios. Though we check if we have anything to write, as we will hit both executes.
- if ((!isExecutingInSampleProject && !skipRegistry) ||
- (isExecutingInTestProject && (docFrontMatter.Any() || sampleMetadata.Any())))
+ if (!isExecutingInSampleProject && !skipRegistry)
{
CreateDocumentRegistry(ctx, docFrontMatter);
CreateSampleRegistry(ctx, sampleMetadata);
@@ -186,9 +179,9 @@ private static void ReportDiagnosticsForInvalidAttributeUsage(SourceProductionCo
IEnumerable<(ToolkitSampleOptionsPaneAttribute?, ISymbol)> optionsPaneAttribute,
IEnumerable<(ISymbol, ToolkitSampleOptionBaseAttribute)> generatedOptionPropertyData)
{
- var toolkitAttributesOnUnsupportedType = toolkitSampleAttributeData.Where(x => x.Symbol is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
- var optionsAttributeOnUnsupportedType = optionsPaneAttribute.Where(x => x.Item2 is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
- var generatedOptionAttributeOnUnsupportedType = generatedOptionPropertyData.Where(x => x.Item1 is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
+ var toolkitAttributesOnUnsupportedType = toolkitSampleAttributeData.Where(x => x.Symbol is INamedTypeSymbol namedSym && !IsValidXamlControl(namedSym));
+ var optionsAttributeOnUnsupportedType = optionsPaneAttribute.Where(x => x.Item2 is INamedTypeSymbol namedSym && !IsValidXamlControl(namedSym));
+ var generatedOptionAttributeOnUnsupportedType = generatedOptionPropertyData.Where(x => x.Item1 is INamedTypeSymbol namedSym && !IsValidXamlControl(namedSym));
foreach (var item in toolkitAttributesOnUnsupportedType)
@@ -232,7 +225,7 @@ private static void ReportDiagnosticsGeneratedOptionsPane(SourceProductionContex
ReportGeneratedMultiChoiceOptionsPaneDiagnostics(ctx, generatedOptionPropertyData);
// Check for generated options which don't have a valid sample attribute
- var generatedOptionsWithMissingSampleAttribute = generatedOptionPropertyData.Where(x => !toolkitSampleAttributeData.Any(sample => ReferenceEquals(sample.Symbol, x.Item1)));
+ var generatedOptionsWithMissingSampleAttribute = generatedOptionPropertyData.Where(x => x.Item1 is INamedTypeSymbol && !toolkitSampleAttributeData.Any(sample => ReferenceEquals(sample.Symbol, x.Item1)));
foreach (var item in generatedOptionsWithMissingSampleAttribute)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample, item.Item1.Locations.FirstOrDefault()));
@@ -255,7 +248,7 @@ private static void ReportDiagnosticsGeneratedOptionsPane(SourceProductionContex
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionWithDuplicateName, item.SelectMany(x => x.Item1.Locations).FirstOrDefault(), item.Key));
// Check for generated options that conflict with an existing property name
- var generatedOptionsWithConflictingPropertyNames = generatedOptionPropertyData.Where(x => GetAllMembers((INamedTypeSymbol)x.Item1).Any(y => x.Item2.Name == y.Name));
+ var generatedOptionsWithConflictingPropertyNames = generatedOptionPropertyData.Where(x => x.Item1 is INamedTypeSymbol sym && GetAllMembers(sym).Any(y => x.Item2.Name == y.Name));
foreach (var item in generatedOptionsWithConflictingPropertyNames)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionWithConflictingName, item.Item1.Locations.FirstOrDefault(), item.Item2.Name));
diff --git a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs
index 16fe9de7..d487eb25 100644
--- a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs
+++ b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs
@@ -23,7 +23,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
- static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0,
+ static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0 || s is MethodDeclarationSyntax m && m.AttributeLists.Count > 0,
static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node))
.Where(static m => m is not null)
.Select(static (x, _) => x!);
@@ -36,16 +36,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Select((x, _) =>
{
if (x.Item2.TryReconstructAs() is ToolkitSampleBoolOptionAttribute boolOptionAttribute)
- return (Attribute: (ToolkitSampleOptionBaseAttribute)boolOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleBoolOptionMetadataViewModel));
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)boolOptionAttribute, AttachedSymbol: x.Item1, Type: typeof(ToolkitSampleBoolOptionMetadataViewModel));
if (x.Item2.TryReconstructAs() is ToolkitSampleMultiChoiceOptionAttribute multiChoiceOptionAttribute)
- return (Attribute: (ToolkitSampleOptionBaseAttribute)multiChoiceOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel));
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)multiChoiceOptionAttribute, AttachedSymbol: x.Item1, Type: typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel));
- if(x.Item2.TryReconstructAs() is ToolkitSampleNumericOptionAttribute numericOptionAttribute)
- return (Attribute: (ToolkitSampleOptionBaseAttribute)numericOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleNumericOptionMetadataViewModel));
+ if (x.Item2.TryReconstructAs() is ToolkitSampleNumericOptionAttribute numericOptionAttribute)
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)numericOptionAttribute, AttachedSymbol: x.Item1, Type: typeof(ToolkitSampleNumericOptionMetadataViewModel));
if (x.Item2.TryReconstructAs() is ToolkitSampleTextOptionAttribute textOptionAttribute)
- return (Attribute: (ToolkitSampleOptionBaseAttribute)textOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleTextOptionMetadataViewModel));
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)textOptionAttribute, AttachedSymbol: x.Item1, Type: typeof(ToolkitSampleTextOptionMetadataViewModel));
return default;
})
@@ -53,37 +53,62 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(sampleAttributeOptions, (ctx, data) =>
{
- if (_handledContainingClasses.Add(data.ContainingClassSymbol))
+ var format = new SymbolDisplayFormat(
+ globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+ propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
+ memberOptions: SymbolDisplayMemberOptions.IncludeContainingType
+ );
+
+ var containingClass = data.AttachedSymbol.Kind switch
{
- if (data.ContainingClassSymbol is ITypeSymbol typeSym && !typeSym.AllInterfaces.Any(x => x.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged")))
+ SymbolKind.Method => data.AttachedSymbol.ContainingSymbol,
+ SymbolKind.NamedType => data.AttachedSymbol,
+ _ => throw new NotSupportedException("Only methods and classes are supported here."),
+ };
+
+ var name = data.AttachedSymbol.Kind switch
+ {
+ SymbolKind.Method => $"{data.AttachedSymbol.ToDisplayString(format)}.CommandProperty.g",
+ SymbolKind.NamedType => $"{data.AttachedSymbol.ToDisplayString(format)}.Property.{data.Attribute.Name}.g",
+ _ => throw new NotSupportedException("Only methods and classes are supported here."),
+ };
+
+ // Generate property container and INPC
+ if (this._handledContainingClasses.Add(containingClass))
+ {
+ if (containingClass is ITypeSymbol typeSym && !typeSym.AllInterfaces.Any(x => x.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged")))
{
- var inpcImpl = BuildINotifyPropertyChangedImplementation(data.ContainingClassSymbol);
- ctx.AddSource($"{data.ContainingClassSymbol}.NotifyPropertyChanged.g", inpcImpl);
+ var inpcImpl = BuildINotifyPropertyChangedImplementation(containingClass);
+ ctx.AddSource($"{containingClass}.NotifyPropertyChanged.g", inpcImpl);
}
- var propertyContainerSource = BuildGeneratedPropertyMetadataContainer(data.ContainingClassSymbol);
- ctx.AddSource($"{data.ContainingClassSymbol}.GeneratedPropertyContainer.g", propertyContainerSource);
+ ctx.AddSource($"{containingClass.ToDisplayString(format)}.GeneratedPropertyContainer.g", BuildGeneratedPropertyMetadataContainer(containingClass));
}
- var name = $"{data.ContainingClassSymbol}.Property.{data.Attribute.Name}.g";
-
- if (_handledPropertyNames.Add(name))
+ // Generate property
+ if (this._handledPropertyNames.Add(name))
{
- var dependencyPropertySource = BuildProperty(data.ContainingClassSymbol, data.Attribute.Name, data.Attribute.TypeName, data.Type);
+ var dependencyPropertySource = data.AttachedSymbol.Kind switch
+ {
+ SymbolKind.NamedType => BuildProperty(containingClassSymbol: data.AttachedSymbol, data.Attribute.Name, data.Attribute.TypeName, data.Type),
+ _ => throw new NotSupportedException("Only methods and classes are supported here."),
+ };
+
ctx.AddSource(name, dependencyPropertySource);
}
});
}
- private static string BuildINotifyPropertyChangedImplementation(ISymbol containingClassSymbol)
+ private static string BuildINotifyPropertyChangedImplementation(ISymbol attachedSymbol)
{
return $@"#nullable enable
using System.ComponentModel;
-namespace {containingClassSymbol.ContainingNamespace}
+namespace {attachedSymbol.ContainingNamespace}
{{
- public partial class {containingClassSymbol.Name} : {nameof(System.ComponentModel.INotifyPropertyChanged)}
+ public partial class {attachedSymbol.Name} : {nameof(System.ComponentModel.INotifyPropertyChanged)}
{{
public event PropertyChangedEventHandler? PropertyChanged;
}}
@@ -91,15 +116,15 @@ public partial class {containingClassSymbol.Name} : {nameof(System.ComponentMode
";
}
- private static string BuildGeneratedPropertyMetadataContainer(ISymbol containingClassSymbol)
+ private static string BuildGeneratedPropertyMetadataContainer(ISymbol attachedSymbol)
{
return $@"#nullable enable
using System.ComponentModel;
using System.Collections.Generic;
-namespace {containingClassSymbol.ContainingNamespace}
+namespace {attachedSymbol.ContainingNamespace}
{{
- public partial class {containingClassSymbol.Name} : {typeof(IToolkitSampleGeneratedOptionPropertyContainer).Namespace}.{nameof(IToolkitSampleGeneratedOptionPropertyContainer)}
+ public partial class {attachedSymbol.Name} : {typeof(IToolkitSampleGeneratedOptionPropertyContainer).Namespace}.{nameof(IToolkitSampleGeneratedOptionPropertyContainer)}
{{
private IEnumerable<{typeof(IGeneratedToolkitSampleOptionViewModel).FullName}>? _generatedPropertyMetadata;