Skip to content

Commit 1e11181

Browse files
committed
add EnumOptionAttr
1 parent d71b08b commit 1e11181

11 files changed

+319
-158
lines changed

CommunityToolkit.Tooling.SampleGen.Tests/Helpers/TestHelpers.Compilation.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,20 @@ public static partial class TestHelpers
1212
{
1313
internal static IEnumerable<MetadataReference> GetAllReferencedAssemblies()
1414
{
15-
return from assembly in AppDomain.CurrentDomain.GetAssemblies()
16-
where !assembly.IsDynamic
17-
let reference = MetadataReference.CreateFromFile(assembly.Location)
18-
select reference;
15+
return AppDomain.CurrentDomain.GetAssemblies()
16+
.Where(assembly => !assembly.IsDynamic)
17+
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location));
1918
}
2019

2120
internal static SyntaxTree ToSyntaxTree(this string source)
2221
{
2322
return CSharpSyntaxTree.ParseText(source,
24-
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
23+
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12));
2524
}
2625

2726
internal static CSharpCompilation CreateCompilation(this SyntaxTree syntaxTree, string assemblyName, IEnumerable<MetadataReference>? references = null)
2827
{
29-
return CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, references ?? GetAllReferencedAssemblies(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
28+
return CSharpCompilation.Create(assemblyName, [syntaxTree], references ?? GetAllReferencedAssemblies(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
3029
}
3130

3231
internal static CSharpCompilation CreateCompilation(this IEnumerable<SyntaxTree> syntaxTree, string assemblyName, IEnumerable<MetadataReference>? references = null)

CommunityToolkit.Tooling.SampleGen.Tests/ToolkitSampleGeneratedPaneTests.cs

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
using CommunityToolkit.Tooling.SampleGen.Diagnostics;
66
using CommunityToolkit.Tooling.SampleGen.Tests.Helpers;
7-
using Microsoft.CodeAnalysis;
87
using Microsoft.VisualStudio.TestTools.UnitTesting;
9-
using System.Linq;
108

119
namespace CommunityToolkit.Tooling.SampleGen.Tests;
1210

@@ -51,6 +49,94 @@ public class UserControl { }
5149
result.AssertDiagnosticsAre();
5250
}
5351

52+
[TestMethod]
53+
public void PaneOption_GeneratesEnumWithoutDiagnostics()
54+
{
55+
var source = """
56+
using Windows.UI.Xaml;
57+
using CommunityToolkit.Tooling.SampleGen;
58+
using CommunityToolkit.Tooling.SampleGen.Attributes;
59+
60+
namespace MyApp
61+
{
62+
[ToolkitSampleEnumOption<Windows.UI.Xaml.Controls.Visibility>("MyVisibility", Title = "Visibility")]
63+
64+
[ToolkitSample(id: nameof(Sample), "Test Sample", description: "")]
65+
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
66+
{
67+
}
68+
}
69+
70+
namespace Windows.UI.Xaml.Controls
71+
{
72+
public class UserControl { }
73+
public enum Visibility { Visible = 3, Collapsed = 7 }
74+
}
75+
""";
76+
77+
var result = source.RunSourceGenerator<ToolkitSampleOptionGenerator>(SAMPLE_ASM_NAME);
78+
79+
result.AssertNoCompilationErrors();
80+
result.AssertDiagnosticsAre();
81+
}
82+
83+
[TestMethod]
84+
public void PaneOption_GeneratesEnumProperty()
85+
{
86+
var sampleProjectAssembly = """
87+
using Windows.UI.Xaml;
88+
using CommunityToolkit.Tooling.SampleGen;
89+
using CommunityToolkit.Tooling.SampleGen.Attributes;
90+
91+
namespace MyApp
92+
{
93+
[ToolkitSampleEnumOption<Windows.UI.Xaml.Controls.Visibility>("MyVisibility", Title = "Visibility")]
94+
95+
[ToolkitSample(id: nameof(Sample), "Test Sample", description: "")]
96+
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
97+
{
98+
public Sample()
99+
{
100+
var y = this.MyVisibility;
101+
}
102+
}
103+
}
104+
105+
namespace Windows.UI.Xaml.Controls
106+
{
107+
public class UserControl { }
108+
public enum Visibility { Visible = 3, Collapsed = 7 }
109+
}
110+
""".ToSyntaxTree()
111+
.CreateCompilation("MyApp.Samples")
112+
.ToMetadataReference();
113+
114+
// Create application head that references generated sample project
115+
var headCompilation = string.Empty
116+
.ToSyntaxTree()
117+
.CreateCompilation("MyApp.Head")
118+
.AddReferences(sampleProjectAssembly);
119+
120+
// Run source generator
121+
var result = headCompilation.RunSourceGenerator<ToolkitSampleMetadataGenerator>();
122+
123+
result.AssertDiagnosticsAre();
124+
result.AssertNoCompilationErrors();
125+
126+
Assert.AreEqual(result.Compilation.GetFileContentsByName("ToolkitSampleRegistry.g.cs"), """
127+
#nullable enable
128+
namespace CommunityToolkit.Tooling.SampleGen;
129+
130+
public static class ToolkitSampleRegistry
131+
{
132+
public static System.Collections.Generic.Dictionary<string, CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleMetadata> Listing { get; } = new()
133+
{
134+
["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") })
135+
};
136+
}
137+
""", "Unexpected code generated");
138+
}
139+
54140
[TestMethod]
55141
public void PaneOption_GeneratesTitleProperty()
56142
{
@@ -77,7 +163,7 @@ namespace Windows.UI.Xaml.Controls
77163
{
78164
public class UserControl { }
79165
}
80-
""".ToSyntaxTree()
166+
""".ToSyntaxTree()
81167
.CreateCompilation("MyApp.Samples")
82168
.ToMetadataReference();
83169

@@ -99,8 +185,8 @@ namespace CommunityToolkit.Tooling.SampleGen;
99185
100186
public static class ToolkitSampleRegistry
101187
{
102-
public static System.Collections.Generic.Dictionary<string, CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleMetadata> Listing
103-
{ get; } = new() {
188+
public static System.Collections.Generic.Dictionary<string, CommunityToolkit.Tooling.SampleGen.Metadata.ToolkitSampleMetadata> Listing { get; } = new()
189+
{
104190
["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") })
105191
};
106192
}

CommunityToolkit.Tooling.SampleGen/Attributes/MultiChoiceOption.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@ namespace CommunityToolkit.Tooling.SampleGen.Attributes;
1212
/// </summary>
1313
/// <param name="Label">A label shown to the user for this option.</param>
1414
/// <param name="Value">The value passed to XAML when this option is selected.</param>
15-
public record MultiChoiceOption(string Label, string Value)
15+
public record MultiChoiceOption(string Label, object Value)
1616
{
17+
public virtual bool Equals(MultiChoiceOption? other)
18+
{
19+
return other is not null && (ReferenceEquals(this, other) || Value.Equals(other.Value));
20+
}
21+
22+
public override int GetHashCode()
23+
{
24+
return Value.GetHashCode();
25+
}
26+
1727
/// <remarks>
1828
/// The string has been overriden to display the label only,
1929
/// especially so the data can be easily displayed in XAML without a custom template, converter or code behind.

CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleBoolOptionAttribute.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public sealed class ToolkitSampleBoolOptionAttribute : ToolkitSampleOptionBaseAt
1919
/// </summary>
2020
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
2121
/// <param name="defaultState">The initial value for the bound property.</param>
22-
/// <param name="title">A title to display on top of this option.</param>
2322
public ToolkitSampleBoolOptionAttribute(string bindingName, bool defaultState)
2423
: base(bindingName, defaultState)
2524
{
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#region Copyright
2+
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
// See the LICENSE file in the project root for more information.
6+
7+
#endregion
8+
9+
namespace CommunityToolkit.Tooling.SampleGen.Attributes;
10+
11+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
12+
public sealed class ToolkitSampleEnumOptionAttribute<TEnum> : ToolkitSampleOptionBaseAttribute where TEnum : struct, Enum
13+
{
14+
/// <summary>
15+
/// Creates a new instance of <see cref="ToolkitSampleEnumOptionAttribute{TEnum}"/>.
16+
/// </summary>
17+
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
18+
public ToolkitSampleEnumOptionAttribute(string bindingName)
19+
: base(bindingName, null)
20+
{
21+
}
22+
23+
/// <summary>
24+
/// The source generator-friendly type name used for casting.
25+
/// </summary>
26+
internal override string TypeName { get; } = "int";
27+
}

CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace CommunityToolkit.Tooling.SampleGen.Attributes;
99
/// </summary>
1010
/// <remarks>
1111
/// Using this attribute will automatically generate an <see cref="INotifyPropertyChanged"/>-enabled property
12-
/// that you can bind to in XAML, and displays an options pane alonside your sample which allows the user to manipulate the property.
12+
/// that you can bind to in XAML, and displays an options pane alongside your sample which allows the user to manipulate the property.
1313
/// <para/>
1414
/// </remarks>
1515
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
@@ -20,10 +20,10 @@ public sealed class ToolkitSampleMultiChoiceOptionAttribute : ToolkitSampleOptio
2020
/// </summary>
2121
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
2222
/// <param name="choices">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.</param>
23-
/// <param name="title">A title to display on top of this option.</param>
2423
public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, params string[] choices)
2524
: base(bindingName, null)
2625
{
26+
TypeName = "string";
2727
Choices = choices.Select(x =>
2828
{
2929
if (x.Contains(" : "))
@@ -36,6 +36,18 @@ public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, params string
3636
}).ToArray();
3737
}
3838

39+
/// <summary>
40+
/// Creates a new instance of <see cref="ToolkitSampleMultiChoiceOptionAttribute"/>.
41+
/// </summary>
42+
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
43+
/// <param name="choices">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.</param>
44+
public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, List<(string, object)> choices)
45+
: base(bindingName, null)
46+
{
47+
TypeName = "int";
48+
Choices = choices.Select(x => new MultiChoiceOption(x.Item1, x.Item2)).ToArray();
49+
}
50+
3951
/// <summary>
4052
/// A collection of choices to display in the options pane.
4153
/// </summary>
@@ -44,5 +56,5 @@ public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, params string
4456
/// <summary>
4557
/// The source generator-friendly type name used for casting.
4658
/// </summary>
47-
internal override string TypeName { get; } = "string";
59+
internal override string TypeName { get; }
4860
}

CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleNumericOptionAttribute.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace CommunityToolkit.Tooling.SampleGen.Attributes;
99
/// </summary>
1010
/// <remarks>
1111
/// Using this attribute will automatically generate an <see cref="INotifyPropertyChanged"/>-enabled property
12-
/// that you can bind to in XAML, and displays an options pane alonside your sample which allows the user to manipulate the property.
12+
/// that you can bind to in XAML, and displays an options pane alongside your sample which allows the user to manipulate the property.
1313
/// <para/>
1414
/// </remarks>
1515
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
@@ -19,8 +19,11 @@ public sealed class ToolkitSampleNumericOptionAttribute : ToolkitSampleOptionBas
1919
/// Creates a new instance of <see cref="ToolkitSampleNumericOptionAttribute"/>.
2020
/// </summary>
2121
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
22-
/// <param name="choices">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.</param>
23-
/// <param name="title">A title to display on top of this option.</param>
22+
/// <param name="initial"></param>
23+
/// <param name="min"></param>
24+
/// <param name="max"></param>
25+
/// <param name="step"></param>
26+
/// <param name="showAsNumberBox"></param>
2427
public ToolkitSampleNumericOptionAttribute(string bindingName, double initial = 0, double min = 0, double max = 10, double step = 1, bool showAsNumberBox = false)
2528
: base(bindingName, null)
2629
{

CommunityToolkit.Tooling.SampleGen/Attributes/ToolkitSampleOptionBaseAttribute.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public abstract class ToolkitSampleOptionBaseAttribute : Attribute
1414
/// </summary>
1515
/// <param name="bindingName">The name of the generated property, which you can bind to in XAML.</param>
1616
/// <param name="defaultState">The initial value for the bound property.</param>
17-
/// <param name="title">A title to display on top of this option.</param>
1817
public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState)
1918
{
2019
Name = bindingName;

CommunityToolkit.Tooling.SampleGen/GeneratorExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public static IEnumerable<ISymbol> CrawlForAllSymbols(this INamespaceSymbol name
3030

3131
foreach (var member in namespaceSymbol.GetNamespaceMembers())
3232
{
33-
if (member is INamespaceSymbol nestedNamespace)
33+
if (member is not null)
3434
{
35-
foreach (var item in CrawlForAllSymbols(nestedNamespace))
35+
foreach (var item in CrawlForAllSymbols(member))
3636
{
3737
yield return item;
3838
}
@@ -95,7 +95,7 @@ public static T ReconstructAs<T>(this AttributeData attributeData)
9595
}
9696

9797
/// <summary>
98-
/// Checks whether or not a given type symbol has a specified full name.
98+
/// Checks whether a given type symbol has a specified full name.
9999
/// </summary>
100100
/// <param name="symbol">The input <see cref="ISymbol"/> instance to check.</param>
101101
/// <param name="name">The full name to check.</param>
@@ -123,7 +123,7 @@ public static bool HasFullyQualifiedName(this ISymbol symbol, string name)
123123

124124
// Enums arrive as the underlying integer type, which doesn't work as a param for Activator.CreateInstance()
125125
if (argType != null && parameterTypedConstant.Kind == TypedConstantKind.Enum)
126-
return Enum.Parse(argType, parameterTypedConstant.Value?.ToString());
126+
return Enum.Parse(argType, parameterTypedConstant.Value!.ToString());
127127

128128
if (parameterTypedConstant.Kind == TypedConstantKind.Array)
129129
{
@@ -163,7 +163,7 @@ public static bool HasFullyQualifiedName(this ISymbol symbol, string name)
163163
/// <param name="attributeData">The target <see cref="AttributeData"/> instance to check.</param>
164164
/// <param name="name">The name of the argument to check.</param>
165165
/// <param name="value">The resulting argument value, if present.</param>
166-
/// <returns>Whether or not <paramref name="attributeData"/> contains an argument named <paramref name="name"/> with a valid value.</returns>
166+
/// <returns>Whether <paramref name="attributeData"/> contains an argument named <paramref name="name"/> with a valid value.</returns>
167167
public static bool TryGetNamedArgument<T>(this AttributeData attributeData, string name, out T? value)
168168
{
169169
foreach (KeyValuePair<string, TypedConstant> properties in attributeData.NamedArguments)

0 commit comments

Comments
 (0)