Skip to content

Commit c080050

Browse files
Implement [BindableProperty] for ImageSources, Add [BindableProperty] Support for internal Properties (#3019)
1 parent c3279e1 commit c080050

File tree

10 files changed

+269
-123
lines changed

10 files changed

+269
-123
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,10 @@ public partial class {{defaultTestClassName}} : View
381381
namespace {{defaultTestNamespace}};
382382
public partial class {{defaultTestClassName}}
383383
{
384+
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
384385
/// <summary>
385386
/// Backing BindableProperty for the <see cref = "Text"/> property.
386387
/// </summary>
387-
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
388388
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty;
389389
public partial string Text { get => (string)GetValue(TextProperty); private protected set => SetValue(textPropertyKey, value); }
390390
}
@@ -464,10 +464,10 @@ public partial class {{defaultTestClassName}} : View
464464
namespace {{defaultTestNamespace}};
465465
public partial class {{defaultTestClassName}}
466466
{
467+
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
467468
/// <summary>
468469
/// Backing BindableProperty for the <see cref = "Text"/> property.
469470
/// </summary>
470-
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
471471
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty;
472472
public partial string Text { get => (string)GetValue(TextProperty); protected set => SetValue(textPropertyKey, value); }
473473
}
@@ -506,10 +506,10 @@ public partial class {{defaultTestClassName}} : View
506506
namespace {{defaultTestNamespace}};
507507
public partial class {{defaultTestClassName}}
508508
{
509+
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
509510
/// <summary>
510511
/// Backing BindableProperty for the <see cref = "Text"/> property.
511512
/// </summary>
512-
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
513513
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty;
514514
public partial string Text { get => (string)GetValue(TextProperty); private set => SetValue(textPropertyKey, value); }
515515
}
@@ -548,10 +548,10 @@ public partial class {{defaultTestClassName}} : View
548548
namespace {{defaultTestNamespace}};
549549
public partial class {{defaultTestClassName}}
550550
{
551+
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
551552
/// <summary>
552553
/// Backing BindableProperty for the <see cref = "Text"/> property.
553554
/// </summary>
554-
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
555555
public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty;
556556
public partial string Text { get => (string)GetValue(TextProperty); }
557557
}

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

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,167 @@ public partial class {{defaultTestClassName}}
630630

631631
await VerifySourceGeneratorAsync(source, expectedGenerated);
632632
}
633+
634+
[Fact]
635+
public async Task GenerateBindableProperty_InternalProperty_GeneratesCorrectCode()
636+
{
637+
const string source =
638+
/* language=C#-test */
639+
//lang=csharp
640+
$$"""
641+
using CommunityToolkit.Maui;
642+
using Microsoft.Maui.Controls;
643+
using System;
644+
645+
namespace {{defaultTestNamespace}};
646+
647+
public partial class {{defaultTestClassName}} : View
648+
{
649+
[BindablePropertyAttribute]
650+
internal partial string Text { get; set; } = "Initial Value";
651+
652+
[BindablePropertyAttribute]
653+
protected internal partial string Time { get; set; } = string.Empty;
654+
655+
[BindablePropertyAttribute]
656+
internal partial TimeSpan CustomDuration { get; private set; } = TimeSpan.FromSeconds(30);
657+
}
658+
""";
659+
660+
const string expectedGenerated =
661+
/* language=C#-test */
662+
//lang=csharp
663+
$$"""
664+
// <auto-generated>
665+
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
666+
#pragma warning disable
667+
#nullable enable
668+
namespace {{defaultTestNamespace}};
669+
public partial class {{defaultTestClassName}}
670+
{
671+
/// <summary>
672+
/// Backing BindableProperty for the <see cref = "Text"/> property.
673+
/// </summary>
674+
internal 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);
675+
internal partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
676+
677+
/// <summary>
678+
/// Backing BindableProperty for the <see cref = "Time"/> property.
679+
/// </summary>
680+
protected internal static readonly global::Microsoft.Maui.Controls.BindableProperty TimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Time", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultTime);
681+
protected internal partial string Time { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingTime ? field : (string)GetValue(TimeProperty); set => SetValue(TimeProperty, value); }
682+
683+
static readonly global::Microsoft.Maui.Controls.BindablePropertyKey customDurationPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("CustomDuration", typeof(System.TimeSpan), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __TestViewBindablePropertyInitHelpers.CreateDefaultCustomDuration);
684+
/// <summary>
685+
/// Backing BindableProperty for the <see cref = "CustomDuration"/> property.
686+
/// </summary>
687+
internal static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = customDurationPropertyKey.BindableProperty;
688+
internal partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); private set => SetValue(customDurationPropertyKey, value); }
689+
}
690+
691+
file static class __{{defaultTestClassName}}BindablePropertyInitHelpers
692+
{
693+
public static volatile bool IsInitializingText = false;
694+
public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable)
695+
{
696+
IsInitializingText = true;
697+
var defaultValue = ((TestView)bindable).Text;
698+
IsInitializingText = false;
699+
return defaultValue;
700+
}
701+
702+
public static volatile bool IsInitializingTime = false;
703+
public static object CreateDefaultTime(global::Microsoft.Maui.Controls.BindableObject bindable)
704+
{
705+
IsInitializingTime = true;
706+
var defaultValue = ((TestView)bindable).Time;
707+
IsInitializingTime = false;
708+
return defaultValue;
709+
}
710+
711+
public static volatile bool IsInitializingCustomDuration = false;
712+
public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable)
713+
{
714+
IsInitializingCustomDuration = true;
715+
var defaultValue = ((TestView)bindable).CustomDuration;
716+
IsInitializingCustomDuration = false;
717+
return defaultValue;
718+
}
719+
}
720+
""";
721+
722+
await VerifySourceGeneratorAsync(source, expectedGenerated);
723+
}
724+
725+
[Fact]
726+
public async Task GenerateBindableProperty_PrivateProperty_ThrowsNotSupportedException()
727+
{
728+
const string source =
729+
/* language=C#-test */
730+
//lang=csharp
731+
$$"""
732+
using CommunityToolkit.Maui;
733+
using Microsoft.Maui.Controls;
734+
735+
namespace {{defaultTestNamespace}};
736+
737+
public partial class {{defaultTestClassName}} : View
738+
{
739+
[BindablePropertyAttribute]
740+
private partial string Text { get; set; }
741+
}
742+
""";
743+
744+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await VerifySourceGeneratorAsync(source, string.Empty));
745+
746+
Assert.Contains("System.NotSupportedException: The property accessiblity, Private, for Text is not supported. The supported accessibility kinds are `public`, `internal` and `protected internal`.", exception.Message);
747+
}
748+
749+
[Fact]
750+
public async Task GenerateBindableProperty_ProtectedProperty_ThrowsNotSupportedException()
751+
{
752+
const string source =
753+
/* language=C#-test */
754+
//lang=csharp
755+
$$"""
756+
using CommunityToolkit.Maui;
757+
using Microsoft.Maui.Controls;
758+
759+
namespace {{defaultTestNamespace}};
760+
761+
public partial class {{defaultTestClassName}} : View
762+
{
763+
[BindablePropertyAttribute]
764+
protected partial string Text { get; set; }
765+
}
766+
""";
767+
768+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await VerifySourceGeneratorAsync(source, string.Empty));
769+
770+
Assert.Contains("System.NotSupportedException: The property accessiblity, Protected, for Text is not supported. The supported accessibility kinds are `public`, `internal` and `protected internal`.", exception.Message);
771+
}
772+
773+
[Fact]
774+
public async Task GenerateBindableProperty_PrivateProtectedProperty_ThrowsNotSupportedException()
775+
{
776+
const string source =
777+
/* language=C#-test */
778+
//lang=csharp
779+
$$"""
780+
using CommunityToolkit.Maui;
781+
using Microsoft.Maui.Controls;
782+
783+
namespace {{defaultTestNamespace}};
784+
785+
public partial class {{defaultTestClassName}} : View
786+
{
787+
[BindablePropertyAttribute]
788+
private protected partial string Text { get; set; }
789+
}
790+
""";
791+
792+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await VerifySourceGeneratorAsync(source, string.Empty));
793+
794+
Assert.Contains("System.NotSupportedException: The property accessiblity, ProtectedAndInternal, for Text is not supported. The supported accessibility kinds are `public`, `internal` and `protected internal`.", exception.Message);
795+
}
633796
}

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

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

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

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

7780
// Assert
@@ -89,6 +92,7 @@ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues()
8992
Assert.Equal("TestPropertyProperty", model.BindablePropertyName);
9093
Assert.Equal(defaultValueCreatorMethodName, model.EffectiveDefaultValueCreatorMethodName);
9194
Assert.Equal("IsInitializingTestProperty", model.InitializingPropertyName);
95+
Assert.Equal(propertyAccessibility, model.PropertyAccessibility);
9296
}
9397

9498
[Fact]
@@ -130,7 +134,8 @@ public void SemanticValues_WithClassInfoAndProperties_StoresCorrectValues()
130134
string.Empty,
131135
true, // IsReadOnlyBindableProperty
132136
string.Empty, // SetterAccessibilityText
133-
false
137+
false,
138+
"public"
134139
);
135140

136141
var bindableProperties = new[] { bindableProperty }.ToImmutableArray();
@@ -164,6 +169,8 @@ public void BindablePropertyName_WithInitializer_ReturnsCorrectEffectiveDefaultV
164169
{
165170
// Arrange
166171
var compilation = CreateCompilation(
172+
/* language=C#-test */
173+
//lang=csharp
167174
"""
168175
public class TestClass { public string TestProperty { get; set; } = "Initial Value"; }");
169176
""");
@@ -186,7 +193,8 @@ public class TestClass { public string TestProperty { get; set; } = "Initial Val
186193
string.Empty,
187194
true,
188195
string.Empty,
189-
hasInitializer
196+
hasInitializer,
197+
"public"
190198
);
191199

192200
// Act
@@ -201,6 +209,8 @@ public void BindablePropertyName_WithInitializerAndDefaulValueCreator_ReturnsCor
201209
{
202210
// Arrange
203211
var compilation = CreateCompilation(
212+
/* language=C#-test */
213+
//lang=csharp
204214
"""
205215
public class TestClass { public string TestProperty { get; set; } = "Initial Value"; }
206216
""");
@@ -223,7 +233,8 @@ public class TestClass { public string TestProperty { get; set; } = "Initial Val
223233
string.Empty,
224234
true,
225235
string.Empty,
226-
hasInitializer
236+
hasInitializer,
237+
"public"
227238
);
228239

229240
// Act

0 commit comments

Comments
 (0)