Skip to content

Commit 81c5187

Browse files
stephenquanTheCodeTravelerCopilot
authored
Extend BindableProperty source gen to handle partial property initializers (CommunityToolkit#2987)
* Extend BindableProperty source gen to handle partial property initializers * Update CommonUsageTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Only implement new initializer code for when initializers are present * Revert IntegrationTests * Revert setter accessibility changes * Corrected setter * Refactor GetDefaultValueCreatorMethod. Update XML Doc. Apply to readonly BindableProperty. Extend unit test. * Include global root namespace for BindableObject * Corrected typo in GetDefautValueCreatorMethodName * Revert initializer-setter changes (not needed). Refactor getter-initializer implementation. * Remove unneeded test from BindablePropertyModelTests * Refctor EffectiveDefaultValueCreatorMethodName property (replacing GetDefaultValueCreatorMethodName method) * Update BindablePropertyName_WithInitializer_ReturnsCorrectEffectiveDefaultValueCreatorMethodName for clarity * Corrected unit test name GenerateBindableProperty_WithInitializer_GeneratesCorrectCode * Refactor InitializingPropertyName property * Add Edge Case Test `GenerateBindableProperty_WithBothInitializerAndDefault_GeneratedCodeDefaultsToUseDefaultValueCreatorMethod` * Use `"null"` instead of `string.IsNullOrEmpty()` * Remove `DefaultValue` * Move out-of-scope variables to `file static class` * Use raw string literal * Update variable name * Implement `[BindableProperty]` for `AvatarView` * Include class name in `file static class` * Use `[BindableProperty]` for AvatarView --------- Co-authored-by: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c434100 commit 81c5187

File tree

11 files changed

+458
-163
lines changed

11 files changed

+458
-163
lines changed

src/CommunityToolkit.Maui.Core/Primitives/Defaults/AvatarViewDefaults.shared.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ namespace CommunityToolkit.Maui.Core;
66
static class AvatarViewDefaults
77
{
88
/// <summary>Default avatar border width.</summary>
9-
public const double DefaultBorderWidth = 1;
9+
public const double BorderWidth = 1;
1010

1111
/// <summary>Default height request.</summary>
12-
public const double DefaultHeightRequest = 48;
12+
public const double HeightRequest = 48;
1313

1414
/// <summary>Default avatar text.</summary>
15-
public const string DefaultText = "?";
15+
public const string Text = "?";
1616

1717
/// <summary>Default width request.</summary>
18-
public const double DefaultWidthRequest = 48;
18+
public const double WidthRequest = 48;
1919

2020
/// <summary>default avatar border colour.</summary>
21-
public static Color DefaultBorderColor { get; } = Colors.White;
21+
public static Color BorderColor { get; } = Colors.White;
2222

2323
/// <summary>Default corner radius.</summary>
24-
public static CornerRadius DefaultCornerRadius { get; } = new(24, 24, 24, 24);
24+
public static CornerRadius CornerRadius { get; } = new(24, 24, 24, 24);
2525

2626
/// <summary>Default padding.</summary>
27-
public static Thickness DefaultPadding { get; } = new(1);
27+
public static Thickness Padding { get; } = new(1);
2828
}

src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/CommonUsageTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,4 +652,77 @@ public partial class {{defaultTestClassName}}
652652

653653
await VerifySourceGeneratorAsync(source, expectedGenerated);
654654
}
655+
656+
657+
[Fact]
658+
public async Task GenerateBindableProperty_WithInitializer_GeneratesCorrectCode()
659+
{
660+
const string source =
661+
/* language=C#-test */
662+
//lang=csharp
663+
$$"""
664+
using CommunityToolkit.Maui;
665+
using Microsoft.Maui.Controls;
666+
using System;
667+
668+
namespace {{defaultTestNamespace}};
669+
670+
public partial class {{defaultTestClassName}} : View
671+
{
672+
[BindablePropertyAttribute]
673+
public partial string Text { get; set; } = "Initial Value";
674+
675+
[BindablePropertyAttribute]
676+
public partial TimeSpan CustomDuration { get; set; } = TimeSpan.FromSeconds(30);
677+
}
678+
""";
679+
680+
const string expectedGenerated =
681+
/* language=C#-test */
682+
//lang=csharp
683+
$$"""
684+
// <auto-generated>
685+
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
686+
#pragma warning disable
687+
#nullable enable
688+
namespace {{defaultTestNamespace}};
689+
public partial class {{defaultTestClassName}}
690+
{
691+
/// <summary>
692+
/// Backing BindableProperty for the <see cref = "Text"/> property.
693+
/// </summary>
694+
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultText);
695+
public partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
696+
697+
/// <summary>
698+
/// Backing BindableProperty for the <see cref = "CustomDuration"/> property.
699+
/// </summary>
700+
public static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CustomDuration", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultCustomDuration);
701+
public partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); set => SetValue(CustomDurationProperty, value); }
702+
}
703+
704+
file static class __{{defaultTestClassName}}BindablePropertyInitHelpers
705+
{
706+
public static bool IsInitializingText = false;
707+
public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable)
708+
{
709+
IsInitializingText = true;
710+
var defaultValue = ((TestView)bindable).Text;
711+
IsInitializingText = false;
712+
return defaultValue;
713+
}
714+
715+
public static bool IsInitializingCustomDuration = false;
716+
public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable)
717+
{
718+
IsInitializingCustomDuration = true;
719+
var defaultValue = ((TestView)bindable).CustomDuration;
720+
IsInitializingCustomDuration = false;
721+
return defaultValue;
722+
}
723+
}
724+
""";
725+
726+
await VerifySourceGeneratorAsync(source, expectedGenerated);
727+
}
655728
}

src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,4 +456,51 @@ public partial class {{defaultTestClassName}}
456456

457457
await VerifySourceGeneratorAsync(source, expectedGenerated);
458458
}
459+
460+
[Fact]
461+
public async Task GenerateBindableProperty_WithBothInitializerAndDefault_GeneratedCodeDefaultsToUseDefaultValueCreatorMethod()
462+
{
463+
const string source =
464+
/* language=C#-test */
465+
//lang=csharp
466+
$$"""
467+
using CommunityToolkit.Maui;
468+
using Microsoft.Maui.Controls;
469+
using System;
470+
471+
namespace {{defaultTestNamespace}};
472+
473+
public partial class {{defaultTestClassName}} : View
474+
{
475+
[BindablePropertyAttribute(DefaultValueCreatorMethodName = nameof(CreateDefaultText))]
476+
public partial string Text { get; set; } = "Initial Value";
477+
478+
static string CreateDefaultText(BindableObject bindable)
479+
{
480+
return "Initial Value";
481+
}
482+
}
483+
""";
484+
485+
const string expectedGenerated =
486+
/* language=C#-test */
487+
//lang=csharp
488+
$$"""
489+
// <auto-generated>
490+
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
491+
#pragma warning disable
492+
#nullable enable
493+
namespace {{defaultTestNamespace}};
494+
public partial class {{defaultTestClassName}}
495+
{
496+
/// <summary>
497+
/// Backing BindableProperty for the <see cref = "Text"/> property.
498+
/// </summary>
499+
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, CreateDefaultText);
500+
public partial string Text { get => false ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
501+
}
502+
""";
503+
504+
await VerifySourceGeneratorAsync(source, expectedGenerated);
505+
}
459506
}

src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public void BindablePropertyName_ReturnsCorrectPropertyName()
2929
"null",
3030
string.Empty,
3131
true, // IsReadOnlyBindableProperty
32-
string.Empty // SetterAccessibility
32+
string.Empty, // SetterAccessibility
33+
false
3334
);
3435

3536
// Act
@@ -56,6 +57,7 @@ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues()
5657
const string coerceValueMethodName = "CoerceValue";
5758
const string defaultValueCreatorMethodName = "CreateDefaultValue";
5859
const string newKeywordText = "new ";
60+
const bool hasInitializer = false;
5961

6062
// Act
6163
var model = new BindablePropertyModel(
@@ -71,7 +73,8 @@ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues()
7173
defaultValueCreatorMethodName,
7274
newKeywordText,
7375
true, // IsReadOnlyBindableProperty
74-
string.Empty // SetterAccessibility
76+
string.Empty, // SetterAccessibility
77+
hasInitializer
7578
);
7679

7780
// Assert
@@ -86,7 +89,10 @@ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues()
8689
Assert.Equal(coerceValueMethodName, model.CoerceValueMethodName);
8790
Assert.Equal(defaultValueCreatorMethodName, model.DefaultValueCreatorMethodName);
8891
Assert.Equal(newKeywordText, model.NewKeywordText);
92+
Assert.Equal(hasInitializer, model.HasInitializer);
8993
Assert.Equal("TestPropertyProperty", model.BindablePropertyName);
94+
Assert.Equal(defaultValueCreatorMethodName, model.EffectiveDefaultValueCreatorMethodName);
95+
Assert.Equal("IsInitializingTestProperty", model.InitializingPropertyName);
9096
}
9197

9298
[Fact]
@@ -128,7 +134,8 @@ public void SemanticValues_WithClassInfoAndProperties_StoresCorrectValues()
128134
"null",
129135
string.Empty,
130136
true, // IsReadOnlyBindableProperty
131-
string.Empty // SetterAccessibilityText
137+
string.Empty, // SetterAccessibilityText
138+
false
132139
);
133140

134141
var bindableProperties = new[] { bindableProperty }.ToImmutableArray();
@@ -156,4 +163,80 @@ static Compilation CreateCompilation(string source)
156163
references,
157164
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
158165
}
166+
167+
[Fact]
168+
public void BindablePropertyName_WithInitializer_ReturnsCorrectEffectiveDefaultValueCreatorMethodName()
169+
{
170+
// Arrange
171+
var compilation = CreateCompilation(
172+
"""
173+
public class TestClass { public string TestProperty { get; set; } = "Initial Value"; }");
174+
""");
175+
var typeSymbol = compilation.GetTypeByMetadataName("TestClass")!;
176+
var propertySymbol = typeSymbol.GetMembers("TestProperty").OfType<IPropertySymbol>().First();
177+
178+
const string propertyName = "TestProperty";
179+
const bool hasInitializer = true;
180+
181+
var model = new BindablePropertyModel(
182+
propertyName,
183+
propertySymbol.Type,
184+
typeSymbol,
185+
"null",
186+
"Microsoft.Maui.Controls.BindingMode.OneWay",
187+
"null",
188+
"null",
189+
"null",
190+
"null",
191+
"null",
192+
string.Empty,
193+
true,
194+
string.Empty,
195+
hasInitializer
196+
);
197+
198+
// Act
199+
var effectiveDefaultValueCreatorMethodName = model.EffectiveDefaultValueCreatorMethodName;
200+
201+
// Assert
202+
Assert.Equal("CreateDefault" + propertyName, effectiveDefaultValueCreatorMethodName);
203+
}
204+
205+
[Fact]
206+
public void BindablePropertyName_WithInitializerAndDefaulValueCreator_ReturnsCorrectEffectiveDefaultValueCreatorMethodName()
207+
{
208+
// Arrange
209+
var compilation = CreateCompilation(
210+
"""
211+
public class TestClass { public string TestProperty { get; set; } = "Initial Value"; }
212+
""");
213+
var typeSymbol = compilation.GetTypeByMetadataName("TestClass")!;
214+
var propertySymbol = typeSymbol.GetMembers("TestProperty").OfType<IPropertySymbol>().First();
215+
216+
const string propertyName = "TestProperty";
217+
const bool hasInitializer = true;
218+
219+
var model = new BindablePropertyModel(
220+
propertyName,
221+
propertySymbol.Type,
222+
typeSymbol,
223+
"null",
224+
"Microsoft.Maui.Controls.BindingMode.OneWay",
225+
"null",
226+
"null",
227+
"null",
228+
"null",
229+
"CreateTextDefaultValue",
230+
string.Empty,
231+
true,
232+
string.Empty,
233+
hasInitializer
234+
);
235+
236+
// Act
237+
var effectiveDefaultValueCreatorMethodName = model.EffectiveDefaultValueCreatorMethodName;
238+
239+
// Assert
240+
Assert.Equal("CreateTextDefaultValue", effectiveDefaultValueCreatorMethodName);
241+
}
159242
}

0 commit comments

Comments
 (0)