diff --git a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs index c8f8ca2d..49a2e155 100644 --- a/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs +++ b/CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs @@ -91,7 +91,6 @@ public void PaneOption_GeneratesEnumProperty() namespace MyApp { [ToolkitSampleEnumOption("MyVisibility", Title = "Visibility")] - [ToolkitSample(id: nameof(Sample), "Test Sample", description: "")] public partial class Sample : Windows.UI.Xaml.Controls.UserControl { @@ -123,7 +122,7 @@ public enum Visibility { Visible = 3, Collapsed = 7 } result.AssertDiagnosticsAre(); result.AssertNoCompilationErrors(); - Assert.AreEqual(result.Compilation.GetFileContentsByName("ToolkitSampleRegistry.g.cs"), """ + Assert.AreEqual(""" #nullable enable namespace CommunityToolkit.Tooling.SampleGen; @@ -131,16 +130,27 @@ 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.ToolkitSampleMultiChoiceOptionMetadataViewModel(name: "MyVisibility", options: new[] { new CommunityToolkit.Tooling.SampleGen.Attributes.MultiChoiceOption("Visible", 3),new CommunityToolkit.Tooling.SampleGen.Attributes.MultiChoiceOption("Collapsed", 7) }, title: "Visibility") }) + ["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.ToolkitSampleMultiChoiceOptionMetadataViewModel(name: "MyVisibility", + options: new[] + { + new CommunityToolkit.Tooling.SampleGen.Attributes.MultiChoiceOption("Visible", Windows.UI.Xaml.Controls.Visibility.Visible), + new CommunityToolkit.Tooling.SampleGen.Attributes.MultiChoiceOption("Collapsed", Windows.UI.Xaml.Controls.Visibility.Collapsed) + }, title: "Visibility") + }) }; } - """, "Unexpected code generated"); + """, + result.Compilation.GetFileContentsByName("ToolkitSampleRegistry.g.cs"), + "Unexpected code generated"); } [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. + // The sample registry is designed to be declared in the sample project, and generated in the project head where it's 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; @@ -153,9 +163,7 @@ namespace MyApp [ToolkitSample(id: nameof(Sample), "Test Sample", description: "")] public partial class Sample : Windows.UI.Xaml.Controls.UserControl { - public Sample() - { - } + public Sample() { } } } @@ -179,18 +187,24 @@ public class UserControl { } 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() + Assert.AreEqual(""" + #nullable enable + namespace CommunityToolkit.Tooling.SampleGen; + + public static class ToolkitSampleRegistry { - ["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"); + 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") + }) + }; + } + """, + result.Compilation.GetFileContentsByName("ToolkitSampleRegistry.g.cs"), + "Unexpected code generated"); } [TestMethod] diff --git a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs index 9806ee9a..cb09127b 100644 --- a/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs +++ b/CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs @@ -40,12 +40,13 @@ public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, params string /// Creates a new instance of . /// /// The name of the generated property, which you can bind to in XAML. + /// /// A list of the choices to display to the user. Can be literal values, or labeled values. Use a " : " separator (single colon surrounded by at least 1 whitespace) to separate a label from a value. - public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, List<(string, object)> choices) + internal ToolkitSampleMultiChoiceOptionAttribute(string bindingName, string typeName, MultiChoiceOption[] choices) : base(bindingName, null) { - TypeName = "int"; - Choices = choices.Select(x => new MultiChoiceOption(x.Item1, x.Item2)).ToArray(); + TypeName = typeName; + Choices = choices; } /// diff --git a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs index 82a1d1a6..dc79e703 100644 --- a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs +++ b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleMetadataGenerator.Sample.cs @@ -68,9 +68,12 @@ void Execute(IncrementalValuesProvider types, bool skipDiagnostics = fa if (x.AttributeData.AttributeClass.TypeArguments.FirstOrDefault() is { } typeSymbol) { var parameters = x.AttributeData.ConstructorArguments.Select(GeneratorExtensions.PrepareParameterTypeForActivator).ToList(); - var members = typeSymbol.GetMembers().OfType().Select(t => (t.Name, t.ConstantValue!)).ToList(); + var members = typeSymbol.GetMembers().OfType().Select(t => new MultiChoiceOption(t.Name, t.ToDisplayString())).ToArray(); + parameters.Add(typeSymbol.ToDisplayString()); parameters.Add(members); - var multiChoiceOptionAttribute = (ToolkitSampleMultiChoiceOptionAttribute)Activator.CreateInstance(typeof(ToolkitSampleMultiChoiceOptionAttribute), parameters.ToArray()); + var multiChoiceOptionAttribute = (ToolkitSampleMultiChoiceOptionAttribute)Activator.CreateInstance( + typeof(ToolkitSampleMultiChoiceOptionAttribute), BindingFlags.NonPublic | BindingFlags.Instance, + null, parameters.ToArray(), null); item = (x.Symbol, multiChoiceOptionAttribute); } } @@ -298,7 +301,7 @@ private static void ReportGeneratedMultiChoiceOptionsPaneDiagnostics(SourceProdu { foreach (var item in generatedOptionPropertyData) { - if (item.Item2 is ToolkitSampleMultiChoiceOptionAttribute multiChoiceAttr && multiChoiceAttr.Choices.Length == 0) + if (item.Item2 is ToolkitSampleMultiChoiceOptionAttribute { Choices.Length: 0 }) { ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneMultiChoiceOptionWithNoChoices, item.Item1.Locations.FirstOrDefault(), item.Item2.Title)); } @@ -315,7 +318,7 @@ public static class ToolkitSampleRegistry { public static System.Collections.Generic.Dictionary Listing { get; } = new() { - {{string.Join(",\n ", sampleMetadata.Select(MetadataToRegistryCall).ToArray())}} + {{string.Join(",\n", sampleMetadata.Select(MetadataToRegistryCall).ToArray())}} }; } """; @@ -326,11 +329,16 @@ private static string MetadataToRegistryCall(KeyValuePair new {metadata.SampleAssemblyQualifiedName}()"; - var generatedSampleOptionsParam = $"new {typeof(IGeneratedToolkitSampleOptionViewModel).FullName}[] {{ {string.Join(", ", BuildNewGeneratedSampleOptionMetadataSource(metadata).ToArray())} }}"; var sampleOptionsParam = metadata.SampleOptionsAssemblyQualifiedName is null ? "null" : $"typeof({metadata.SampleOptionsAssemblyQualifiedName})"; var sampleOptionsPaneFactoryParam = metadata.SampleOptionsAssemblyQualifiedName is null ? "null" : $"x => new {metadata.SampleOptionsAssemblyQualifiedName}(({metadata.SampleAssemblyQualifiedName})x)"; - return @$"[""{kvp.Key}""] = new {typeof(ToolkitSampleMetadata).FullName}(""{metadata.Id}"", ""{metadata.DisplayName}"", ""{metadata.Description}"", {sampleControlTypeParam}, {sampleControlFactoryParam}, {sampleOptionsParam}, {sampleOptionsPaneFactoryParam}, {generatedSampleOptionsParam})"; + return $$""" + ["{{kvp.Key}}"] = new {{typeof(ToolkitSampleMetadata).FullName}}("{{metadata.Id}}", "{{metadata.DisplayName}}", "{{metadata.Description}}", {{sampleControlTypeParam}}, {{sampleControlFactoryParam}}, {{sampleOptionsParam}}, {{sampleOptionsPaneFactoryParam}}, + new {{typeof(IGeneratedToolkitSampleOptionViewModel).FullName}}[] + { + {{string.Join(",\n", BuildNewGeneratedSampleOptionMetadataSource(metadata).ToArray())}} + }) + """; } private static IEnumerable BuildNewGeneratedSampleOptionMetadataSource(ToolkitSampleRecord sample) @@ -340,13 +348,22 @@ private static IEnumerable BuildNewGeneratedSampleOptionMetadataSource(T yield return item switch { ToolkitSampleMultiChoiceOptionAttribute multiChoiceAttr => - $@"new {typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel).FullName}(name: ""{multiChoiceAttr.Name}"", options: new[] {{ {string.Join(",", multiChoiceAttr.Choices.Select(x => $@"new {typeof(MultiChoiceOption).FullName}(""{x.Label}"", {(x.Value is string ? $"\"{x.Value}\"" : x.Value)})").ToArray())} }}, title: ""{multiChoiceAttr.Title}"")", + $$""" + new {{typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel).FullName}}(name: "{{multiChoiceAttr.Name}}", + options: new[] + { + {{string.Join(",\n", multiChoiceAttr.Choices.Select(x => + $""" + new {typeof(MultiChoiceOption).FullName}("{x.Label}", {(multiChoiceAttr.TypeName is "string" ? $"\"{x.Value}\"" : x.Value)}) + """).ToArray())}} + }, title: "{{multiChoiceAttr.Title}}") + """, ToolkitSampleBoolOptionAttribute boolAttribute => - $@"new {typeof(ToolkitSampleBoolOptionMetadataViewModel).FullName}(name: ""{boolAttribute.Name}"", defaultState: {boolAttribute.DefaultState?.ToString().ToLower()}, title: ""{boolAttribute.Title}"")", + $@" new {typeof(ToolkitSampleBoolOptionMetadataViewModel).FullName}(name: ""{boolAttribute.Name}"", defaultState: {boolAttribute.DefaultState?.ToString().ToLower()}, title: ""{boolAttribute.Title}"")", ToolkitSampleNumericOptionAttribute numericAttribute => - $@"new {typeof(ToolkitSampleNumericOptionMetadataViewModel).FullName}(name: ""{numericAttribute.Name}"", initial: {numericAttribute.Initial}, min: {numericAttribute.Min}, max: {numericAttribute.Max}, step: {numericAttribute.Step}, showAsNumberBox: {numericAttribute.ShowAsNumberBox.ToString().ToLower()}, title: ""{numericAttribute.Title}"")", + $@" new {typeof(ToolkitSampleNumericOptionMetadataViewModel).FullName}(name: ""{numericAttribute.Name}"", initial: {numericAttribute.Initial}, min: {numericAttribute.Min}, max: {numericAttribute.Max}, step: {numericAttribute.Step}, showAsNumberBox: {numericAttribute.ShowAsNumberBox.ToString().ToLower()}, title: ""{numericAttribute.Title}"")", ToolkitSampleTextOptionAttribute textAttribute => - $@"new {typeof(ToolkitSampleTextOptionMetadataViewModel).FullName}(name: ""{textAttribute.Name}"", placeholderText: ""{textAttribute.PlaceholderText}"", title: ""{textAttribute.Title}"")", + $@" new {typeof(ToolkitSampleTextOptionMetadataViewModel).FullName}(name: ""{textAttribute.Name}"", placeholderText: ""{textAttribute.PlaceholderText}"", title: ""{textAttribute.Title}"")", _ => throw new NotSupportedException($"Unsupported or unhandled type {item.GetType()}.") }; } diff --git a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs index ea363f31..49cd6024 100644 --- a/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs +++ b/CommunityToolkit.Tooling.SampleGen/ToolkitSampleOptionGenerator.cs @@ -40,10 +40,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (x.AttributeData.AttributeClass?.ContainingNamespace.ToDisplayString() == typeof(ToolkitSampleEnumOptionAttribute<>).Namespace && x.AttributeData.AttributeClass?.MetadataName == typeof(ToolkitSampleEnumOptionAttribute<>).Name) { - var parameters = x.AttributeData.ConstructorArguments.Select(GeneratorExtensions.PrepareParameterTypeForActivator).ToList(); - parameters.Add(new List<(string, object)>()); - var multiChoiceOptionAttribute = (ToolkitSampleMultiChoiceOptionAttribute)Activator.CreateInstance(typeof(ToolkitSampleMultiChoiceOptionAttribute), parameters.ToArray()); - item = (multiChoiceOptionAttribute, x.Symbol, typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel)); + if (x.AttributeData.AttributeClass.TypeArguments.FirstOrDefault() is { } typeSymbol) + { + var parameters = x.AttributeData.ConstructorArguments.Select(GeneratorExtensions.PrepareParameterTypeForActivator).ToList(); + parameters.Add(typeSymbol.ToDisplayString()); + parameters.Add(Array.Empty()); + var multiChoiceOptionAttribute = (ToolkitSampleMultiChoiceOptionAttribute)Activator.CreateInstance( + typeof(ToolkitSampleMultiChoiceOptionAttribute), BindingFlags.NonPublic | BindingFlags.Instance, + null, parameters.ToArray(), null); + item = (multiChoiceOptionAttribute, x.Symbol, typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel)); + } } else if (x.AttributeData.TryReconstructAs() is { } boolOptionAttribute) { @@ -186,13 +192,13 @@ public partial class {{containingClassSymbol.Name}} { public {{typeName}} {{propertyName}} { - get => (({{typeName}})(({{viewModelType.FullName}})GeneratedPropertyMetadata!.First(x => x.Name == "{{propertyName}}"))!.Value!)!; + get => ({{typeName}})(({{viewModelType.FullName}})GeneratedPropertyMetadata!.First(x => x.Name is "{{propertyName}}"))!.Value!; set { - if (GeneratedPropertyMetadata?.FirstOrDefault(x => x.Name == nameof({{propertyName}})) is {{viewModelType.FullName}} metadata) + if (GeneratedPropertyMetadata?.FirstOrDefault(x => x.Name is "{{propertyName}}") is {{viewModelType.FullName}} metadata) { metadata.Value = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({{propertyName}}))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("{{propertyName}}")); } } }