Skip to content

Commit 86c0047

Browse files
authored
Merge pull request #336 from CommunityToolkit/user/niels9001/more-options
More Sample Option Attributes
2 parents bfb745f + 4c7528c commit 86c0047

23 files changed

+554
-115
lines changed

common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/ToolkitSampleMetadataTests.cs

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
using Microsoft.CodeAnalysis.CSharp;
99
using Microsoft.CodeAnalysis.Text;
1010
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
using System.ComponentModel.DataAnnotations;
1112

1213
namespace CommunityToolkit.Labs.Core.SourceGenerators.Tests;
1314

1415
[TestClass]
1516
public partial class ToolkitSampleMetadataTests
1617
{
1718
[TestMethod]
18-
public void PaneOption_GeneratesProperty()
19+
public void PaneOption_GeneratesWithoutDiagnostics()
1920
{
2021
var source = $@"
2122
using System.ComponentModel;
@@ -24,8 +25,8 @@ public void PaneOption_GeneratesProperty()
2425
2526
namespace MyApp
2627
{{
27-
[ToolkitSampleBoolOption(""Test"", ""Toggle y"", false)]
28-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"", ""Segoe UI"", ""Arial"")]
28+
[ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
29+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
2930
3031
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
3132
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
@@ -46,6 +47,50 @@ public class UserControl {{ }}
4647
VerifyGeneratedDiagnostics<ToolkitSampleOptionGenerator>(source, string.Empty);
4748
}
4849

50+
[TestMethod]
51+
public void PaneOption_GeneratesTitleProperty()
52+
{
53+
var source = """
54+
using System.ComponentModel;
55+
using CommunityToolkit.Labs.Core.SourceGenerators;
56+
using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
57+
58+
namespace MyApp
59+
{
60+
[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")]
61+
[ToolkitSample(id: nameof(Sample), "Test Sample", description: "")]
62+
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
63+
{
64+
public Sample()
65+
{
66+
var x = this.Test;
67+
var y = this.TextFontFamily;
68+
}
69+
}
70+
}
71+
72+
namespace Windows.UI.Xaml.Controls
73+
{
74+
public class UserControl { }
75+
}
76+
""";
77+
78+
var result = """
79+
#nullable enable
80+
namespace CommunityToolkit.Labs.Core.SourceGenerators;
81+
82+
public static class ToolkitSampleRegistry
83+
{
84+
public static System.Collections.Generic.Dictionary<string, CommunityToolkit.Labs.Core.SourceGenerators.Metadata.ToolkitSampleMetadata> Listing
85+
{ get; } = new() {
86+
["Sample"] = new CommunityToolkit.Labs.Core.SourceGenerators.Metadata.ToolkitSampleMetadata("Sample", "Test Sample", "", typeof(MyApp.Sample), () => new MyApp.Sample(), null, null, new CommunityToolkit.Labs.Core.SourceGenerators.Metadata.IGeneratedToolkitSampleOptionViewModel[] { new CommunityToolkit.Labs.Core.SourceGenerators.Metadata.ToolkitSampleNumericOptionMetadataViewModel(name: "TextSize", initial: 12, min: 8, max: 48, step: 2, showAsNumberBox: false, title: "FontSize") })
87+
};
88+
}
89+
""";
90+
91+
VerifyGenerateSources("MyApp.Tests", source, new[] { new ToolkitSampleMetadataGenerator() }, ignoreDiagnostics: true, ("ToolkitSampleRegistry.g.cs", result));
92+
}
93+
4994
// https://github.com/CommunityToolkit/Labs-Windows/issues/175
5095
[TestMethod]
5196
public void PaneOption_GeneratesProperty_DuplicatePropNamesAcrossSampleClasses()
@@ -57,8 +102,8 @@ public void PaneOption_GeneratesProperty_DuplicatePropNamesAcrossSampleClasses()
57102
58103
namespace MyApp
59104
{{
60-
[ToolkitSampleBoolOption(""Test"", ""Toggle y"", false)]
61-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"", ""Segoe UI"", ""Arial"")]
105+
[ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
106+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
62107
63108
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
64109
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
@@ -70,8 +115,8 @@ public Sample()
70115
}}
71116
}}
72117
73-
[ToolkitSampleBoolOption(""Test"", ""Toggle y"", false)]
74-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"", ""Segoe UI"", ""Arial"")]
118+
[ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle y"")]
119+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
75120
76121
[ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
77122
public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
@@ -101,7 +146,7 @@ public void PaneOptionOnNonSample()
101146
102147
namespace MyApp
103148
{
104-
[ToolkitSampleBoolOption(""BindToMe"", ""Toggle visibility"", false)]
149+
[ToolkitSampleBoolOption(""BindToMe"", false, Title = ""Toggle visibility"")]
105150
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
106151
{
107152
}
@@ -129,7 +174,7 @@ public void PaneOptionWithBadName(string name)
129174
namespace MyApp
130175
{{
131176
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
132-
[ToolkitSampleBoolOption(""{name}"", ""Toggle visibility"", false)]
177+
[ToolkitSampleBoolOption(""{name}"", false, Title = ""Toggle visibility"")]
133178
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
134179
{{
135180
}}
@@ -153,7 +198,7 @@ public void PaneOptionWithConflictingPropertyName()
153198
154199
namespace MyApp
155200
{{
156-
[ToolkitSampleBoolOption(""IsVisible"", ""Toggle x"", false)]
201+
[ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
157202
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
158203
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
159204
{{
@@ -179,7 +224,7 @@ public void PaneOptionWithConflictingInheritedPropertyName()
179224
180225
namespace MyApp
181226
{{
182-
[ToolkitSampleBoolOption(""IsVisible"", ""Toggle x"", false)]
227+
[ToolkitSampleBoolOption(""IsVisible"", false, Title = ""Toggle x"")]
183228
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
184229
public partial class Sample : Base
185230
{{
@@ -209,9 +254,9 @@ public void PaneOptionWithDuplicateName()
209254
210255
namespace MyApp
211256
{{
212-
[ToolkitSampleBoolOption(""test"", ""Toggle x"", false)]
213-
[ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
214-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"", ""Segoe UI"", ""Arial"")]
257+
[ToolkitSampleBoolOption(""test"", false, Title = ""Toggle x"")]
258+
[ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
259+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", Title = ""Text foreground"")]
215260
216261
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
217262
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
@@ -237,14 +282,14 @@ public void PaneOptionWithDuplicateName_AllowedBetweenSamples()
237282
238283
namespace MyApp
239284
{{
240-
[ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
285+
[ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
241286
242287
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
243288
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
244289
{{
245290
}}
246291
247-
[ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
292+
[ToolkitSampleBoolOption(""test"", false, Title = ""Toggle y"")]
248293
249294
[ToolkitSample(id: nameof(Sample2), ""Test Sample"", description: """")]
250295
public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
@@ -270,7 +315,7 @@ public void PaneMultipleChoiceOptionWithNoChoices()
270315
271316
namespace MyApp
272317
{{
273-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"")]
318+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", Title = ""Font family"")]
274319
275320
[ToolkitSample(id: nameof(Sample), ""Test Sample"", description: """")]
276321
public partial class Sample : Windows.UI.Xaml.Controls.UserControl
@@ -296,8 +341,8 @@ public void SampleGeneratedOptionAttributeOnUnsupportedType()
296341
297342
namespace MyApp
298343
{{
299-
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", title: ""Text foreground"", ""Segoe UI"", ""Arial"")]
300-
[ToolkitSampleBoolOption(""Test"", ""Toggle visibility"", false)]
344+
[ToolkitSampleMultiChoiceOption(""TextFontFamily"", ""Segoe UI"", ""Arial"", ""Consolas"", Title = ""Font family"")]
345+
[ToolkitSampleBoolOption(""Test"", false, Title = ""Toggle visibility"")]
301346
public partial class Sample
302347
{{
303348
}}
@@ -444,6 +489,55 @@ from assembly in AppDomain.CurrentDomain.GetAssemblies()
444489
GC.KeepAlive(sampleAttributeType);
445490
}
446491

492+
//// See: https://github.com/CommunityToolkit/dotnet/blob/c2053562d1a4d4829fc04b1cb86d1564c2c4a03c/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs#L103
493+
/// <summary>
494+
/// Generates the requested sources
495+
/// </summary>
496+
/// <param name="source">The input source to process.</param>
497+
/// <param name="generators">The generators to apply to the input syntax tree.</param>
498+
/// <param name="results">The source files to compare.</param>
499+
private static void VerifyGenerateSources(string assemblyName, string source, IIncrementalGenerator[] generators, bool ignoreDiagnostics = false, params (string Filename, string Text)[] results)
500+
{
501+
// Ensure our types are loaded
502+
Type sampleattributeObjectType = typeof(ToolkitSampleAttribute);
503+
504+
// Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies)
505+
IEnumerable<MetadataReference> references =
506+
from assembly in AppDomain.CurrentDomain.GetAssemblies()
507+
where !assembly.IsDynamic
508+
let reference = MetadataReference.CreateFromFile(assembly.Location)
509+
select reference;
510+
511+
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
512+
513+
// Create a syntax tree with the input source
514+
CSharpCompilation compilation = CSharpCompilation.Create(
515+
assemblyName,
516+
new SyntaxTree[] { syntaxTree },
517+
references,
518+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
519+
520+
GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
521+
522+
// Run all source generators on the input source code
523+
_ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics);
524+
525+
// Ensure that no diagnostics were generated
526+
if (!ignoreDiagnostics)
527+
{
528+
CollectionAssert.AreEquivalent(Array.Empty<Diagnostic>(), diagnostics);
529+
}
530+
531+
foreach ((string filename, string text) in results)
532+
{
533+
SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filename);
534+
535+
Assert.AreEqual(text, generatedTree.ToString());
536+
}
537+
538+
GC.KeepAlive(sampleattributeObjectType);
539+
}
540+
447541
// From: https://github.com/dotnet/roslyn/blob/main/src/Compilers/Test/Core/SourceGeneration/TestGenerators.cs
448542
internal class InMemoryAdditionalText : AdditionalText
449543
{

common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleBoolOptionAttribute.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,14 @@ public sealed class ToolkitSampleBoolOptionAttribute : ToolkitSampleOptionBaseAt
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>
2222
/// <param name="title">A title to display on top of this option.</param>
23-
public ToolkitSampleBoolOptionAttribute(string bindingName, string label, bool defaultState, string? title = null)
24-
: base(bindingName, defaultState, title)
23+
public ToolkitSampleBoolOptionAttribute(string bindingName, bool defaultState)
24+
: base(bindingName, defaultState)
2525
{
26-
Label = label;
26+
2727
}
2828

2929
/// <summary>
3030
/// The source generator-friendly type name used for casting.
3131
/// </summary>
3232
internal override string TypeName { get; } = "bool";
33-
34-
/// <summary>
35-
/// A label to display along the boolean option.
36-
/// </summary>
37-
public string Label { get; }
3833
}

common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
1616
public sealed class ToolkitSampleMultiChoiceOptionAttribute : ToolkitSampleOptionBaseAttribute
1717
{
1818
/// <summary>
19-
/// Creates a new instance of <see cref="ToolkitSampleBoolOptionAttribute"/>.
19+
/// Creates a new instance of <see cref="ToolkitSampleMultiChoiceOptionAttribute"/>.
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>
2323
/// <param name="title">A title to display on top of this option.</param>
24-
public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, string? title = null, params string[] choices)
25-
: base(bindingName, null, title)
24+
public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, params string[] choices)
25+
: base(bindingName, null)
2626
{
2727
Choices = choices.Select(x =>
2828
{
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
6+
7+
/// <summary>
8+
/// Represents a boolean sample option.
9+
/// </summary>
10+
/// <remarks>
11+
/// 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.
13+
/// <para/>
14+
/// </remarks>
15+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
16+
public sealed class ToolkitSampleNumericOptionAttribute : ToolkitSampleOptionBaseAttribute
17+
{
18+
/// <summary>
19+
/// Creates a new instance of <see cref="ToolkitSampleNumericOptionAttribute"/>.
20+
/// </summary>
21+
/// <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>
24+
public ToolkitSampleNumericOptionAttribute(string bindingName, double initial = 0, double min = 0, double max = 10, double step = 1, bool showAsNumberBox = false)
25+
: base(bindingName, null)
26+
{
27+
Initial = initial;
28+
Min = min;
29+
Max = max;
30+
Step = step;
31+
ShowAsNumberBox = showAsNumberBox;
32+
}
33+
34+
/// <summary>
35+
/// The default start value.
36+
/// </summary>
37+
public double Initial { get; }
38+
39+
/// <summary>
40+
/// The minimal value.
41+
/// </summary>
42+
public double Min { get; }
43+
44+
/// <summary>
45+
/// The maximum value.
46+
/// </summary>
47+
public double Max { get; }
48+
49+
/// <summary>
50+
/// The step value.
51+
/// </summary>
52+
public double Step { get; }
53+
54+
/// <summary>
55+
/// Determines if a Slider or NumberBox is shown.
56+
/// </summary>
57+
public bool ShowAsNumberBox { get; }
58+
59+
/// <summary>
60+
/// The source generator-friendly type name used for casting.
61+
/// </summary>
62+
internal override string TypeName { get; } = "double";
63+
}

common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionBaseAttribute.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ public abstract class ToolkitSampleOptionBaseAttribute : Attribute
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>
1717
/// <param name="title">A title to display on top of this option.</param>
18-
public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState, string? title = null)
18+
public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState)
1919
{
20-
Title = title;
2120
Name = bindingName;
2221
DefaultState = defaultState;
2322
}
@@ -35,7 +34,7 @@ public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState
3534
/// <summary>
3635
/// A title to display on top of the option.
3736
/// </summary>
38-
public string? Title { get; }
37+
public string? Title { get; set; }
3938

4039
/// <summary>
4140
/// The source generator-friendly type name used for casting.

0 commit comments

Comments
 (0)