diff --git a/Directory.Build.props b/Directory.Build.props index c9c8e29b4c..5e0169388e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ enable - NETSDK1023,MCTEXP001 + NETSDK1023,MCTEXP001 net10.0 preview enable @@ -22,7 +22,7 @@ https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitati all - 10.0.20 + 10.0.30 true true true diff --git a/src/CommunityToolkit.Maui.Core/Primitives/Defaults/DockLayoutDefaults.shared.cs b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/DockLayoutDefaults.shared.cs index b553ba89c2..ad74e607d6 100644 --- a/src/CommunityToolkit.Maui.Core/Primitives/Defaults/DockLayoutDefaults.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/DockLayoutDefaults.shared.cs @@ -2,6 +2,7 @@ static class DockLayoutDefaults { + public const DockPosition DockPosition = Core.DockPosition.None; public const bool ShouldExpandLastChild = true; public const double HorizontalSpacing = 0.0d; public const double VerticalSpacing = 0.0d; diff --git a/src/CommunityToolkit.Maui.Core/Primitives/Defaults/StateViewDefaults.shared.cs b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/StateViewDefaults.shared.cs new file mode 100644 index 0000000000..4bc47fc8dd --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/StateViewDefaults.shared.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkit.Maui.Core; + +static class StateViewDefaults +{ + public const string StateKey = ""; +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/AttachedBindablePropertySourceGeneratorBenchmarks.cs b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/AttachedBindablePropertySourceGeneratorBenchmarks.cs new file mode 100644 index 0000000000..ce0711e30c --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/AttachedBindablePropertySourceGeneratorBenchmarks.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +namespace CommunityToolkit.Maui.SourceGenerators.Benchmarks; + +[MemoryDiagnoser] +public class AttachedBindablePropertyAttributeSourceGeneratorBenchmarks +{ + static readonly AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_CommonUsageTests commonUsageTests = new(); + static readonly AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_EdgeCaseTests edgeCaseTests = new(); + static readonly AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_IntegrationTests integrationTests = new(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_SimpleExample_GeneratesCorrectCode() + => commonUsageTests.GenerateAttachedBindableProperty_SimpleExample_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_MultipleProperties_GeneratesCorrectCode() + => commonUsageTests.GenerateAttachedBindableProperty_MultipleProperties_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_WithAllParameters_GeneratesCorrectCode() + => commonUsageTests.GenerateAttachedBindableProperty_WithAllParameters_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_InternalClass_GeneratesCorrectCode() + => commonUsageTests.GenerateAttachedBindableProperty_InternalClass_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode() + => integrationTests.GenerateAttachedBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_NestedClass_GeneratesCorrectCode() + => integrationTests.GenerateAttachedBindableProperty_AttributeOnNestedInnerClass_GeneratesCorrectCode(); + + [Benchmark] + public Task GenerateAttachedBindableProperty_WithComplexDefaultValues_GeneratesCorrectCode() + => edgeCaseTests.GenerateAttachedBindableProperty_WithComplexDefaultValues_GeneratesCorrectCode(); +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/BindablePropertyAttributeSourceGeneratorBenchmarks.cs b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/BindablePropertyAttributeSourceGeneratorBenchmarks.cs index d9bd79b2d6..f28bc03538 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/BindablePropertyAttributeSourceGeneratorBenchmarks.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/BindablePropertyAttributeSourceGeneratorBenchmarks.cs @@ -6,9 +6,9 @@ namespace CommunityToolkit.Maui.SourceGenerators.Benchmarks; [MemoryDiagnoser] public class BindablePropertyAttributeSourceGeneratorBenchmarks { - static readonly CommonUsageTests commonUsageTests = new(); - static readonly EdgeCaseTests edgeCaseTests = new(); - static readonly IntegrationTests integrationTests = new(); + static readonly BindablePropertyAttributeSourceGenerator_CommonUsageTests commonUsageTests = new(); + static readonly BindablePropertyAttributeSourceGenerator_EdgeCaseTests edgeCaseTests = new(); + static readonly BindablePropertyAttributeSourceGenerator_IntegrationTests integrationTests = new(); [Benchmark] public Task GenerateBindableProperty_SimpleExample_GeneratesCorrectCode() diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/Program.cs b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/Program.cs index 9104c9138a..fc7bc1b6db 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/Program.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Benchmarks/Program.cs @@ -9,5 +9,6 @@ public static void Main(string[] args) { var config = DefaultConfig.Instance; BenchmarkRunner.Run(config, args); + BenchmarkRunner.Run(config, args); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_CommonUsageTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_CommonUsageTests.cs new file mode 100644 index 0000000000..c1c115f327 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_CommonUsageTests.cs @@ -0,0 +1,1408 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_CommonUsageTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_SimpleExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_DefaultValueEnumExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Day", DefaultValue = System.DayOfWeek.Monday)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Day property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Day", typeof(global::System.DayOfWeek), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::System.DayOfWeek)1, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Day for the child element. + /// + public static global::System.DayOfWeek GetDay(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::System.DayOfWeek)bindable.GetValue(DayProperty); + /// + /// Sets Day for the child element. + /// + public static void SetDay(global::Microsoft.Maui.Controls.BindableObject bindable, global::System.DayOfWeek value) => bindable.SetValue(DayProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_DefaultValueArrayExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Numbers", DefaultValue = new int[] { 1, 2, 3 })] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Numbers property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Numbers", typeof(int[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (int[])new[] { 1, 2, 3 }, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Numbers for the child element. + /// + public static int[] GetNumbers(global::Microsoft.Maui.Controls.BindableObject bindable) => (int[])bindable.GetValue(NumbersProperty); + /// + /// Sets Numbers for the child element. + /// + public static void SetNumbers(global::Microsoft.Maui.Controls.BindableObject bindable, int[] value) => bindable.SetValue(NumbersProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NullableReferenceType_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", IsNullable = true)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string? GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string? )bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string? value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_MultipleProperties_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text")] + [AttachedBindableProperty("Number")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + /// + /// Attached BindableProperty for the Number property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Number", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Number for the child element. + /// + public static int GetNumber(global::Microsoft.Maui.Controls.BindableObject bindable) => (int)bindable.GetValue(NumberProperty); + /// + /// Sets Number for the child element. + /// + public static void SetNumber(global::Microsoft.Maui.Controls.BindableObject bindable, int value) => bindable.SetValue(NumberProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithAllParameters_GeneratesCorrectCode() + { + const string bindablePropertyXmlDocumentation = "///This is the custom XML documentation for the TextProperty"; + const string getterMethodXmlDocumentation = "///This is the custom XML documentation for the GetText method."; + const string setterMethodXmlDocumentation = "///This is the custom XML documentation for the SetText method."; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("Value", + IsNullable = true, + DefaultValue = 7, + DefaultBindingMode = BindingMode.TwoWay, + ValidateValueMethodName = nameof(ValidateValue), + PropertyChangedMethodName = nameof(OnPropertyChanged), + PropertyChangingMethodName = nameof(OnPropertyChanging), + CoerceValueMethodName = nameof(CoerceValue), + DefaultValueCreatorMethodName = nameof(CreateDefaultValue), + BindablePropertyXmlDocumentation = "{{bindablePropertyXmlDocumentation}}", + GetterMethodXmlDocumentation = "{{getterMethodXmlDocumentation}}", + SetterMethodXmlDocumentation = "{{setterMethodXmlDocumentation}}", + BindablePropertyAccessibility = AccessModifier.Internal, + GetterAccessibility = AccessModifier.Internal, + SetterAccessibility = AccessModifier.Internal)] + public partial class {{defaultTestClassName}} : View + { + static bool ValidateValue(global::Microsoft.Maui.Controls.BindableObject bindable, object value) => true; + static void OnPropertyChanged(global::Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) { } + static void OnPropertyChanging(global::Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) { } + static object CoerceValue(global::Microsoft.Maui.Controls.BindableObject bindable, object value) => value; + static object CreateDefaultValue(global::Microsoft.Maui.Controls.BindableObject bindable) => 0; + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + {{bindablePropertyXmlDocumentation}} + internal static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Value", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (int? )7, (Microsoft.Maui.Controls.BindingMode)1, ValidateValue, OnPropertyChanged, OnPropertyChanging, CoerceValue, CreateDefaultValue); + {{getterMethodXmlDocumentation}} + internal static int? GetValue(global::Microsoft.Maui.Controls.BindableObject bindable) => (int? )bindable.GetValue(ValueProperty); + {{setterMethodXmlDocumentation}} + internal static void SetValue(global::Microsoft.Maui.Controls.BindableObject bindable, int? value) => bindable.SetValue(ValueProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalClass_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text")] + internal partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + internal partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoAttribute_GeneratesNoCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + public string Text { get; set; } + } + """; + + await VerifySourceGeneratorAsync(source, string.Empty); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_EmptyClass_GeneratesNoCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + } + """; + + await VerifySourceGeneratorAsync(source, string.Empty); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicSetterAccessibility_GeneratesPublicSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalSetterAccessibility_GeneratesInternalSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + internal static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedSetterAccessibility_GeneratesPrivateProtectedSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + private protected static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalSetterAccessibility_GeneratesProtectedInternalSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + protected internal static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedSetterAccessibility_GeneratesProtectedSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + protected static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateSetterAccessibility_GeneratesPrivateSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + private static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoSetterAccessibility_GeneratesNoSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicGetterAccessibility_GeneratesPublicGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalGetterAccessibility_GeneratesInternalGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + internal static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedGetterAccessibility_GeneratesPrivateProtectedGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + private protected static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalGetterAccessibility_GeneratesProtectedInternalGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + protected internal static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedGetterAccessibility_GeneratesProtectedGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + protected static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateGetterAccessibility_GeneratesPrivateGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + private static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoneGetterAccessibility_GeneratesNoGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicBindablePropertyAccessibility_GeneratesInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalBindablePropertyAccessibility_GeneratesInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalBindablePropertyAccessibility_GeneratesProtectedInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + protected internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedBindablePropertyAccessibility_GeneratesProtectedBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + protected static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedBindablePropertyAccessibility_GeneratesPrivateProtectedBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + private protected static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateBindablePropertyAccessibility_GeneratesPrivateBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + private static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoneBindablePropertyAccessibility_ThrowsException() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + await Assert.ThrowsAsync(() => VerifySourceGeneratorAsync(source, string.Empty)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GenericClassExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_EdgeCaseTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_EdgeCaseTests.cs new file mode 100644 index 0000000000..f4a8e51bfa --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_EdgeCaseTests.cs @@ -0,0 +1,646 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_EdgeCaseTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_LowercasePropertyName_ThrowsNotSupportedException() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("text")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + var invalidOperationException = await Assert.ThrowsAsync(() => VerifySourceGeneratorAsync(source, string.Empty)); + Assert.Contains("NotSupportedException", invalidOperationException.Message); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyWithReservedKeywords_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Class")] + [AttachedBindableProperty("Namespace")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Class property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty ClassProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Class", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Class for the child element. + /// + public static string GetClass(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(ClassProperty); + /// + /// Sets Class for the child element. + /// + public static void SetClass(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(ClassProperty, value); + /// + /// Attached BindableProperty for the Namespace property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NamespaceProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Namespace", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Namespace for the child element. + /// + public static string GetNamespace(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(NamespaceProperty); + /// + /// Sets Namespace for the child element. + /// + public static void SetNamespace(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(NamespaceProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyIsByteEnum_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("InvoiceStatus", DefaultValue = Status.Approved)] + public partial class {{defaultTestClassName}} : View + { + } + + public enum Status : byte + { + Pending = 0, + Approved = 1, + Rejected = 2 + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the InvoiceStatus property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("InvoiceStatus", typeof(global::{{defaultTestNamespace}}.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::{{defaultTestNamespace}}.Status)1, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets InvoiceStatus for the child element. + /// + public static global::{{defaultTestNamespace}}.Status GetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.Status)bindable.GetValue(InvoiceStatusProperty); + /// + /// Sets InvoiceStatus for the child element. + /// + public static void SetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.Status value) => bindable.SetValue(InvoiceStatusProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyIsLongEnum_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("InvoiceStatus", DefaultValue = Status.Rejected)] + public partial class {{defaultTestClassName}} : View + { + } + + public enum Status : long + { + Pending = 0, + Approved = 1, + Rejected = long.MaxValue + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the InvoiceStatus property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("InvoiceStatus", typeof(global::{{defaultTestNamespace}}.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::{{defaultTestNamespace}}.Status)9223372036854775807L, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets InvoiceStatus for the child element. + /// + public static global::{{defaultTestNamespace}}.Status GetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.Status)bindable.GetValue(InvoiceStatusProperty); + /// + /// Sets InvoiceStatus for the child element. + /// + public static void SetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.Status value) => bindable.SetValue(InvoiceStatusProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NullableValueTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using System; + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("NullableInt", IsNullable = true)] + [AttachedBindableProperty("NullableDateTime", IsNullable = true)] + [AttachedBindableProperty("NullableBool", IsNullable = true)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the NullableInt property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableIntProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableInt", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableInt for the child element. + /// + public static int? GetNullableInt(global::Microsoft.Maui.Controls.BindableObject bindable) => (int? )bindable.GetValue(NullableIntProperty); + /// + /// Sets NullableInt for the child element. + /// + public static void SetNullableInt(global::Microsoft.Maui.Controls.BindableObject bindable, int? value) => bindable.SetValue(NullableIntProperty, value); + /// + /// Attached BindableProperty for the NullableDateTime property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableDateTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableDateTime", typeof(global::System.DateTime), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableDateTime for the child element. + /// + public static global::System.DateTime? GetNullableDateTime(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::System.DateTime? )bindable.GetValue(NullableDateTimeProperty); + /// + /// Sets NullableDateTime for the child element. + /// + public static void SetNullableDateTime(global::Microsoft.Maui.Controls.BindableObject bindable, global::System.DateTime? value) => bindable.SetValue(NullableDateTimeProperty, value); + /// + /// Attached BindableProperty for the NullableBool property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableBoolProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableBool", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableBool for the child element. + /// + public static bool? GetNullableBool(global::Microsoft.Maui.Controls.BindableObject bindable) => (bool? )bindable.GetValue(NullableBoolProperty); + /// + /// Sets NullableBool for the child element. + /// + public static void SetNullableBool(global::Microsoft.Maui.Controls.BindableObject bindable, bool? value) => bindable.SetValue(NullableBoolProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ArrayTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("StringArray")] + [AttachedBindableProperty("MultiDimensionalArray")] + [AttachedBindableProperty("JaggedArray")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the StringArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty StringArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("StringArray", typeof(string[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets StringArray for the child element. + /// + public static string[] GetStringArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (string[])bindable.GetValue(StringArrayProperty); + /// + /// Sets StringArray for the child element. + /// + public static void SetStringArray(global::Microsoft.Maui.Controls.BindableObject bindable, string[] value) => bindable.SetValue(StringArrayProperty, value); + /// + /// Attached BindableProperty for the MultiDimensionalArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty MultiDimensionalArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("MultiDimensionalArray", typeof(int[, ]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets MultiDimensionalArray for the child element. + /// + public static int[, ] GetMultiDimensionalArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (int[, ])bindable.GetValue(MultiDimensionalArrayProperty); + /// + /// Sets MultiDimensionalArray for the child element. + /// + public static void SetMultiDimensionalArray(global::Microsoft.Maui.Controls.BindableObject bindable, int[, ] value) => bindable.SetValue(MultiDimensionalArrayProperty, value); + /// + /// Attached BindableProperty for the JaggedArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty JaggedArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("JaggedArray", typeof(byte[][]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets JaggedArray for the child element. + /// + public static byte[][] GetJaggedArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (byte[][])bindable.GetValue(JaggedArrayProperty); + /// + /// Sets JaggedArray for the child element. + /// + public static void SetJaggedArray(global::Microsoft.Maui.Controls.BindableObject bindable, byte[][] value) => bindable.SetValue(JaggedArrayProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_LongNamespaces_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace Very.Long.Namespace.With.Many.Segments.TestNamespace; + + [AttachedBindableProperty("Text")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace Very.Long.Namespace.With.Many.Segments.TestNamespace; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof(Very.Long.Namespace.With.Many.Segments.TestNamespace.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GlobalNamespace_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + [AttachedBindableProperty("Text")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_SpecialCharactersInPropertyName_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Property_With_Underscores")] + [AttachedBindableProperty("Property123WithNumbers")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Property_With_Underscores property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty Property_With_UnderscoresProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Property_With_Underscores", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Property_With_Underscores for the child element. + /// + public static string GetProperty_With_Underscores(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(Property_With_UnderscoresProperty); + /// + /// Sets Property_With_Underscores for the child element. + /// + public static void SetProperty_With_Underscores(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(Property_With_UnderscoresProperty, value); + /// + /// Attached BindableProperty for the Property123WithNumbers property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty Property123WithNumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Property123WithNumbers", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Property123WithNumbers for the child element. + /// + public static string GetProperty123WithNumbers(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(Property123WithNumbersProperty); + /// + /// Sets Property123WithNumbers for the child element. + /// + public static void SetProperty123WithNumbers(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(Property123WithNumbersProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithComplexDefaultValues_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("IsEnabled", DefaultValue = true)] + [AttachedBindableProperty("Pi", DefaultValue = 3.14)] + [AttachedBindableProperty("Letter", DefaultValue = 'A')] + [AttachedBindableProperty("DoubleEpsilon", DefaultValue = double.Epsilon)] + [AttachedBindableProperty("SingleEpsilon", DefaultValue = float.Epsilon)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the IsEnabled property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (bool)true, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets IsEnabled for the child element. + /// + public static bool GetIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable) => (bool)bindable.GetValue(IsEnabledProperty); + /// + /// Sets IsEnabled for the child element. + /// + public static void SetIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable, bool value) => bindable.SetValue(IsEnabledProperty, value); + /// + /// Attached BindableProperty for the Pi property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (double)3.1400000000000001, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Pi for the child element. + /// + public static double GetPi(global::Microsoft.Maui.Controls.BindableObject bindable) => (double)bindable.GetValue(PiProperty); + /// + /// Sets Pi for the child element. + /// + public static void SetPi(global::Microsoft.Maui.Controls.BindableObject bindable, double value) => bindable.SetValue(PiProperty, value); + /// + /// Attached BindableProperty for the Letter property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (char)'A', (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Letter for the child element. + /// + public static char GetLetter(global::Microsoft.Maui.Controls.BindableObject bindable) => (char)bindable.GetValue(LetterProperty); + /// + /// Sets Letter for the child element. + /// + public static void SetLetter(global::Microsoft.Maui.Controls.BindableObject bindable, char value) => bindable.SetValue(LetterProperty, value); + /// + /// Attached BindableProperty for the DoubleEpsilon property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DoubleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("DoubleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (double)double.Epsilon, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets DoubleEpsilon for the child element. + /// + public static double GetDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) => (double)bindable.GetValue(DoubleEpsilonProperty); + /// + /// Sets DoubleEpsilon for the child element. + /// + public static void SetDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable, double value) => bindable.SetValue(DoubleEpsilonProperty, value); + /// + /// Attached BindableProperty for the SingleEpsilon property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty SingleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("SingleEpsilon", typeof(float), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (float)float.Epsilon, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets SingleEpsilon for the child element. + /// + public static float GetSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) => (float)bindable.GetValue(SingleEpsilonProperty); + /// + /// Sets SingleEpsilon for the child element. + /// + public static void SetSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable, float value) => bindable.SetValue(SingleEpsilonProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithBothInitializerAndDefault_GeneratedCodeSetsBothValues() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("Text", DefaultValue = "Initial Value", DefaultValueCreatorMethodName = nameof(CreateDefaultText))] + public partial class {{defaultTestClassName}} : View + { + static string CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) + { + return "Initial Value"; + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (string)"Initial Value", (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, CreateDefaultText); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_IntegrationTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_IntegrationTests.cs new file mode 100644 index 0000000000..38d7d69b03 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_IntegrationTests.cs @@ -0,0 +1,415 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ClassAttribute_IntegrationTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("Text")] + public abstract partial class BaseView : View + { + } + + [AttachedBindablePropertyAttribute("DerivedText")] + public partial class DerivedView : BaseView + { + } + """; + + const string expectedBaseGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class BaseView + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.BaseView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + const string expectedDerivedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class DerivedView + { + /// + /// Attached BindableProperty for the DerivedText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DerivedTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("DerivedText", typeof(string), typeof({{defaultTestNamespace}}.DerivedView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets DerivedText for the child element. + /// + public static string GetDerivedText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(DerivedTextProperty); + /// + /// Sets DerivedText for the child element. + /// + public static void SetDerivedText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(DerivedTextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, ("BaseView.g.cs", expectedBaseGenerated), ("DerivedView.g.cs", expectedDerivedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GenericClass_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("Text")] + public partial class {{defaultTestClassName}} : View where T : class + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_AttributeOnNestedInnerClass_GeneratesCorrectCode() + { + const string outerClassName = "Outerclass"; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{outerClassName}} + { + [AttachedBindablePropertyAttribute("Text")] + public partial class {{defaultTestClassName}} : View + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$$"""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{{defaultTestNamespace}}}; + public partial class {{{outerClassName}}} + { + public partial class {{{defaultTestClassName}}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{{defaultTestNamespace}}}.{{{outerClassName}}}.{{{defaultTestClassName}}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + } + """"; + + await VerifySourceGeneratorAsync(source, ($"{outerClassName}.{defaultTestClassName}.g.cs", expectedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NestedInnerClassWithAttributeOnOutterClass_GeneratesCorrectCode() + { + const string outerClassName = "Outerclass"; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("Text")] + public partial class {{outerClassName}} : View + { + public partial class {{defaultTestClassName}} : View + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$$"""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{{defaultTestNamespace}}}; + public partial class {{{outerClassName}}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{{defaultTestNamespace}}}.{{{outerClassName}}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """"; + + await VerifySourceGeneratorAsync(source, ($"{outerClassName}.g.cs", expectedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithCustomTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System.Collections.Generic; + + namespace {{defaultTestNamespace}}; + + public class CustomModel + { + public string Name { get; set; } + } + + [AttachedBindablePropertyAttribute("Model", IsNullable = true)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Model property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty ModelProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Model", typeof(global::{{defaultTestNamespace}}.CustomModel), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Model for the child element. + /// + public static global::{{defaultTestNamespace}}.CustomModel? GetModel(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.CustomModel? )bindable.GetValue(ModelProperty); + /// + /// Sets Model for the child element. + /// + public static void SetModel(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.CustomModel? value) => bindable.SetValue(ModelProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_MultipleClassesInSameFile_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindablePropertyAttribute("FirstText")] + public partial class FirstView : View + { + } + + [AttachedBindablePropertyAttribute("SecondText")] + public partial class SecondView : View + { + } + """; + + const string expectedFirstGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class FirstView + { + /// + /// Attached BindableProperty for the FirstText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty FirstTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("FirstText", typeof(string), typeof({{defaultTestNamespace}}.FirstView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets FirstText for the child element. + /// + public static string GetFirstText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(FirstTextProperty); + /// + /// Sets FirstText for the child element. + /// + public static void SetFirstText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(FirstTextProperty, value); + } + """; + + const string expectedSecondGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class SecondView + { + /// + /// Attached BindableProperty for the SecondText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty SecondTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("SecondText", typeof(string), typeof({{defaultTestNamespace}}.SecondView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets SecondText for the child element. + /// + public static string GetSecondText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(SecondTextProperty); + /// + /// Sets SecondText for the child element. + /// + public static void SetSecondText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(SecondTextProperty, value); + } + """; + + + await VerifySourceGeneratorAsync(source, ("FirstView.g.cs", expectedFirstGenerated), ("SecondView.g.cs", expectedSecondGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_CustomXmlDocumentation_GeneratesCorrectCode() + { + const string bindablePropertyXmlDocumentation = "///This is the custom XML documentation for the TextProperty"; + const string getterMethodXmlDocumentation = "///This is the custom XML documentation for the GetText method."; + const string setterMethodXmlDocumentation = "///This is the custom XML documentation for the SetText method."; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyXmlDocumentation = "{{bindablePropertyXmlDocumentation}}", GetterMethodXmlDocumentation = "{{getterMethodXmlDocumentation}}", SetterMethodXmlDocumentation = "{{setterMethodXmlDocumentation}}")] + public partial class {{defaultTestClassName}} : View + { + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + {{bindablePropertyXmlDocumentation}} + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + {{getterMethodXmlDocumentation}} + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + {{setterMethodXmlDocumentation}} + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_CommonUsageTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_CommonUsageTests.cs new file mode 100644 index 0000000000..41a8558b88 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_CommonUsageTests.cs @@ -0,0 +1,1500 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_CommonUsageTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_SimpleExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_DefaultValueEnumExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Day", DefaultValue = System.DayOfWeek.Monday)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Day property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Day", typeof(global::System.DayOfWeek), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::System.DayOfWeek)1, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Day for the child element. + /// + public static global::System.DayOfWeek GetDay(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::System.DayOfWeek)bindable.GetValue(DayProperty); + /// + /// Sets Day for the child element. + /// + public static void SetDay(global::Microsoft.Maui.Controls.BindableObject bindable, global::System.DayOfWeek value) => bindable.SetValue(DayProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_DefaultValueArrayExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Numbers", DefaultValue = new int[] { 1, 2, 3 })] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Numbers property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Numbers", typeof(int[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (int[])new[] { 1, 2, 3 }, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Numbers for the child element. + /// + public static int[] GetNumbers(global::Microsoft.Maui.Controls.BindableObject bindable) => (int[])bindable.GetValue(NumbersProperty); + /// + /// Sets Numbers for the child element. + /// + public static void SetNumbers(global::Microsoft.Maui.Controls.BindableObject bindable, int[] value) => bindable.SetValue(NumbersProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NullableReferenceType_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", IsNullable = true)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string? GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string? )bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string? value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_MultipleProperties_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + [AttachedBindableProperty("Number")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + /// + /// Attached BindableProperty for the Number property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Number", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Number for the child element. + /// + public static int GetNumber(global::Microsoft.Maui.Controls.BindableObject bindable) => (int)bindable.GetValue(NumberProperty); + /// + /// Sets Number for the child element. + /// + public static void SetNumber(global::Microsoft.Maui.Controls.BindableObject bindable, int value) => bindable.SetValue(NumberProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithAllParameters_GeneratesCorrectCode() + { + const string bindablePropertyXmlDocumentation = "///This is the custom XML documentation for the TextProperty"; + const string getterMethodXmlDocumentation = "///This is the custom XML documentation for the GetText method."; + const string setterMethodXmlDocumentation = "///This is the custom XML documentation for the SetText method."; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindablePropertyAttribute("Value", + IsNullable = true, + DefaultValue = 7, + DefaultBindingMode = BindingMode.TwoWay, + ValidateValueMethodName = nameof(ValidateValue), + PropertyChangedMethodName = nameof(OnPropertyChanged), + PropertyChangingMethodName = nameof(OnPropertyChanging), + CoerceValueMethodName = nameof(CoerceValue), + DefaultValueCreatorMethodName = nameof(CreateDefaultValue), + BindablePropertyXmlDocumentation = "{{bindablePropertyXmlDocumentation}}", + GetterMethodXmlDocumentation = "{{getterMethodXmlDocumentation}}", + SetterMethodXmlDocumentation = "{{setterMethodXmlDocumentation}}", + BindablePropertyAccessibility = AccessModifier.Internal, + GetterAccessibility = AccessModifier.Internal, + SetterAccessibility = AccessModifier.Internal)] + public {{defaultTestClassName}}() + { + } + + static bool ValidateValue(global::Microsoft.Maui.Controls.BindableObject bindable, object value) => true; + static void OnPropertyChanged(global::Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) { } + static void OnPropertyChanging(global::Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) { } + static object CoerceValue(global::Microsoft.Maui.Controls.BindableObject bindable, object value) => value; + static object CreateDefaultValue(global::Microsoft.Maui.Controls.BindableObject bindable) => 0; + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + {{bindablePropertyXmlDocumentation}} + internal static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Value", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (int? )7, (Microsoft.Maui.Controls.BindingMode)1, ValidateValue, OnPropertyChanged, OnPropertyChanging, CoerceValue, CreateDefaultValue); + {{getterMethodXmlDocumentation}} + internal static int? GetValue(global::Microsoft.Maui.Controls.BindableObject bindable) => (int? )bindable.GetValue(ValueProperty); + {{setterMethodXmlDocumentation}} + internal static void SetValue(global::Microsoft.Maui.Controls.BindableObject bindable, int? value) => bindable.SetValue(ValueProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalClass_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + internal partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + internal partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoAttribute_GeneratesNoCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + public {{defaultTestClassName}}() + { + } + + public string Text { get; set; } + } + """; + + await VerifySourceGeneratorAsync(source, string.Empty); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_EmptyClass_GeneratesNoCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + public {{defaultTestClassName}}() + { + } + } + """; + + await VerifySourceGeneratorAsync(source, string.Empty); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicSetterAccessibility_GeneratesPublicSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalSetterAccessibility_GeneratesInternalSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + internal static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedSetterAccessibility_GeneratesPrivateProtectedSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + private protected static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalSetterAccessibility_GeneratesProtectedInternalSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + protected internal static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedSetterAccessibility_GeneratesProtectedSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + protected static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateSetterAccessibility_GeneratesPrivateSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + private static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(textPropertyKey, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoneSetterAccessibility_GeneratesNoSetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", SetterAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey textPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateAttachedReadOnly("Text", typeof(string), typeof(TestNamespace.TestView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicGetterAccessibility_GeneratesPublicGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalGetterAccessibility_GeneratesInternalGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + internal static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedGetterAccessibility_GeneratesPrivateProtectedGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + private protected static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalGetterAccessibility_GeneratesProtectedInternalGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + protected internal static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedGetterAccessibility_GeneratesProtectedGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + protected static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateGetterAccessibility_GeneratesPrivateGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + private static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoneGetterAccessibility_GeneratesNoGetter() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", GetterAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PublicBindablePropertyAccessibility_GeneratesInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Public)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_InternalBindablePropertyAccessibility_GeneratesInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Internal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedInternalBindablePropertyAccessibility_GeneratesProtectedInternalBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.ProtectedInternal)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + protected internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ProtectedBindablePropertyAccessibility_GeneratesProtectedBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Protected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + protected static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateProtectedBindablePropertyAccessibility_GeneratesPrivateProtectedBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.PrivateProtected)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + private protected static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PrivateBindablePropertyAccessibility_GeneratesPrivateBindableProperty() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.Private)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + private static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NoneBindablePropertyAccessibility_ThrowsException() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + [AttachedBindableProperty("Text", BindablePropertyAccessibility = CommunityToolkit.Maui.AccessModifier.None)] + public partial class {{defaultTestClassName}} : View + { + } + """; + + await Assert.ThrowsAsync(() => VerifySourceGeneratorAsync(source, string.Empty)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GenericClassExample_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_EdgeCaseTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_EdgeCaseTests.cs new file mode 100644 index 0000000000..92d010db3e --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_EdgeCaseTests.cs @@ -0,0 +1,680 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_EdgeCaseTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_LowercasePropertyName_ThrowsNotSupportedException() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("text")] + public {{defaultTestClassName}}() + { + } + } + """; + + var invalidOperationException = await Assert.ThrowsAsync(() => VerifySourceGeneratorAsync(source, string.Empty)); + Assert.Contains("NotSupportedException", invalidOperationException.Message); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyWithReservedKeywords_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Class")] + [AttachedBindableProperty("Namespace")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Class property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty ClassProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Class", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Class for the child element. + /// + public static string GetClass(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(ClassProperty); + /// + /// Sets Class for the child element. + /// + public static void SetClass(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(ClassProperty, value); + /// + /// Attached BindableProperty for the Namespace property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NamespaceProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Namespace", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Namespace for the child element. + /// + public static string GetNamespace(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(NamespaceProperty); + /// + /// Sets Namespace for the child element. + /// + public static void SetNamespace(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(NamespaceProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyIsByteEnum_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("InvoiceStatus", DefaultValue = Status.Approved)] + public {{defaultTestClassName}}() + { + } + } + + public enum Status : byte + { + Pending = 0, + Approved = 1, + Rejected = 2 + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the InvoiceStatus property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("InvoiceStatus", typeof(global::{{defaultTestNamespace}}.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::{{defaultTestNamespace}}.Status)1, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets InvoiceStatus for the child element. + /// + public static global::{{defaultTestNamespace}}.Status GetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.Status)bindable.GetValue(InvoiceStatusProperty); + /// + /// Sets InvoiceStatus for the child element. + /// + public static void SetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.Status value) => bindable.SetValue(InvoiceStatusProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_PropertyIsLongEnum_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("InvoiceStatus", DefaultValue = Status.Rejected)] + public {{defaultTestClassName}}() + { + } + } + + public enum Status : long + { + Pending = 0, + Approved = 1, + Rejected = long.MaxValue + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the InvoiceStatus property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("InvoiceStatus", typeof(global::{{defaultTestNamespace}}.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (global::{{defaultTestNamespace}}.Status)9223372036854775807L, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets InvoiceStatus for the child element. + /// + public static global::{{defaultTestNamespace}}.Status GetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.Status)bindable.GetValue(InvoiceStatusProperty); + /// + /// Sets InvoiceStatus for the child element. + /// + public static void SetInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.Status value) => bindable.SetValue(InvoiceStatusProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NullableValueTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using System; + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("NullableInt")] + [AttachedBindableProperty("NullableDateTime")] + [AttachedBindableProperty("NullableBool")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the NullableInt property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableIntProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableInt", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableInt for the child element. + /// + public static int? GetNullableInt(global::Microsoft.Maui.Controls.BindableObject bindable) => (int? )bindable.GetValue(NullableIntProperty); + /// + /// Sets NullableInt for the child element. + /// + public static void SetNullableInt(global::Microsoft.Maui.Controls.BindableObject bindable, int? value) => bindable.SetValue(NullableIntProperty, value); + /// + /// Attached BindableProperty for the NullableDateTime property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableDateTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableDateTime", typeof(global::System.DateTime), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableDateTime for the child element. + /// + public static global::System.DateTime? GetNullableDateTime(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::System.DateTime? )bindable.GetValue(NullableDateTimeProperty); + /// + /// Sets NullableDateTime for the child element. + /// + public static void SetNullableDateTime(global::Microsoft.Maui.Controls.BindableObject bindable, global::System.DateTime? value) => bindable.SetValue(NullableDateTimeProperty, value); + /// + /// Attached BindableProperty for the NullableBool property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableBoolProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("NullableBool", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets NullableBool for the child element. + /// + public static bool? GetNullableBool(global::Microsoft.Maui.Controls.BindableObject bindable) => (bool? )bindable.GetValue(NullableBoolProperty); + /// + /// Sets NullableBool for the child element. + /// + public static void SetNullableBool(global::Microsoft.Maui.Controls.BindableObject bindable, bool? value) => bindable.SetValue(NullableBoolProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_ArrayTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("StringArray")] + [AttachedBindableProperty("MultiDimensionalArray")] + [AttachedBindableProperty("JaggedArray")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the StringArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty StringArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("StringArray", typeof(string[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets StringArray for the child element. + /// + public static string[] GetStringArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (string[])bindable.GetValue(StringArrayProperty); + /// + /// Sets StringArray for the child element. + /// + public static void SetStringArray(global::Microsoft.Maui.Controls.BindableObject bindable, string[] value) => bindable.SetValue(StringArrayProperty, value); + /// + /// Attached BindableProperty for the MultiDimensionalArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty MultiDimensionalArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("MultiDimensionalArray", typeof(int[, ]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets MultiDimensionalArray for the child element. + /// + public static int[, ] GetMultiDimensionalArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (int[, ])bindable.GetValue(MultiDimensionalArrayProperty); + /// + /// Sets MultiDimensionalArray for the child element. + /// + public static void SetMultiDimensionalArray(global::Microsoft.Maui.Controls.BindableObject bindable, int[, ] value) => bindable.SetValue(MultiDimensionalArrayProperty, value); + /// + /// Attached BindableProperty for the JaggedArray property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty JaggedArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("JaggedArray", typeof(byte[][]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets JaggedArray for the child element. + /// + public static byte[][] GetJaggedArray(global::Microsoft.Maui.Controls.BindableObject bindable) => (byte[][])bindable.GetValue(JaggedArrayProperty); + /// + /// Sets JaggedArray for the child element. + /// + public static void SetJaggedArray(global::Microsoft.Maui.Controls.BindableObject bindable, byte[][] value) => bindable.SetValue(JaggedArrayProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_LongNamespaces_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace Very.Long.Namespace.With.Many.Segments.TestNamespace; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace Very.Long.Namespace.With.Many.Segments.TestNamespace; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof(Very.Long.Namespace.With.Many.Segments.TestNamespace.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GlobalNamespace_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_SpecialCharactersInPropertyName_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Property_With_Underscores")] + [AttachedBindableProperty("Property123WithNumbers")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Property_With_Underscores property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty Property_With_UnderscoresProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Property_With_Underscores", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Property_With_Underscores for the child element. + /// + public static string GetProperty_With_Underscores(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(Property_With_UnderscoresProperty); + /// + /// Sets Property_With_Underscores for the child element. + /// + public static void SetProperty_With_Underscores(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(Property_With_UnderscoresProperty, value); + /// + /// Attached BindableProperty for the Property123WithNumbers property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty Property123WithNumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Property123WithNumbers", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Property123WithNumbers for the child element. + /// + public static string GetProperty123WithNumbers(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(Property123WithNumbersProperty); + /// + /// Sets Property123WithNumbers for the child element. + /// + public static void SetProperty123WithNumbers(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(Property123WithNumbersProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithComplexDefaultValues_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("IsEnabled", DefaultValue = true)] + [AttachedBindableProperty("Pi", DefaultValue = 3.14)] + [AttachedBindableProperty("Letter", DefaultValue = 'A')] + [AttachedBindableProperty("DoubleEpsilon", DefaultValue = double.Epsilon)] + [AttachedBindableProperty("SingleEpsilon", DefaultValue = float.Epsilon)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the IsEnabled property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (bool)true, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets IsEnabled for the child element. + /// + public static bool GetIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable) => (bool)bindable.GetValue(IsEnabledProperty); + /// + /// Sets IsEnabled for the child element. + /// + public static void SetIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable, bool value) => bindable.SetValue(IsEnabledProperty, value); + /// + /// Attached BindableProperty for the Pi property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (double)3.1400000000000001, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Pi for the child element. + /// + public static double GetPi(global::Microsoft.Maui.Controls.BindableObject bindable) => (double)bindable.GetValue(PiProperty); + /// + /// Sets Pi for the child element. + /// + public static void SetPi(global::Microsoft.Maui.Controls.BindableObject bindable, double value) => bindable.SetValue(PiProperty, value); + /// + /// Attached BindableProperty for the Letter property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (char)'A', (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Letter for the child element. + /// + public static char GetLetter(global::Microsoft.Maui.Controls.BindableObject bindable) => (char)bindable.GetValue(LetterProperty); + /// + /// Sets Letter for the child element. + /// + public static void SetLetter(global::Microsoft.Maui.Controls.BindableObject bindable, char value) => bindable.SetValue(LetterProperty, value); + /// + /// Attached BindableProperty for the DoubleEpsilon property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DoubleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("DoubleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (double)double.Epsilon, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets DoubleEpsilon for the child element. + /// + public static double GetDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) => (double)bindable.GetValue(DoubleEpsilonProperty); + /// + /// Sets DoubleEpsilon for the child element. + /// + public static void SetDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable, double value) => bindable.SetValue(DoubleEpsilonProperty, value); + /// + /// Attached BindableProperty for the SingleEpsilon property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty SingleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("SingleEpsilon", typeof(float), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (float)float.Epsilon, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets SingleEpsilon for the child element. + /// + public static float GetSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) => (float)bindable.GetValue(SingleEpsilonProperty); + /// + /// Sets SingleEpsilon for the child element. + /// + public static void SetSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable, float value) => bindable.SetValue(SingleEpsilonProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithBothInitializerAndDefault_GeneratedCodeSetsBothValues() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindablePropertyAttribute("Text", DefaultValue = "Initial Value", DefaultValueCreatorMethodName = nameof(CreateDefaultText))] + public {{defaultTestClassName}}() + { + } + + static string CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) + { + return "Initial Value"; + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (string)"Initial Value", (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, CreateDefaultText); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_IntegrationTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_IntegrationTests.cs new file mode 100644 index 0000000000..17b9017f27 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_IntegrationTests.cs @@ -0,0 +1,450 @@ +using Xunit; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public class AttachedBindablePropertyAttributeSourceGenerator_ConstructorAttribute_IntegrationTests : BaseAttachedBindablePropertyAttributeSourceGeneratorTest +{ + [Fact] + public async Task GenerateAttachedBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public abstract partial class BaseView : View + { + [AttachedBindablePropertyAttribute("Text")] + protected BaseView() + { + } + } + + public partial class DerivedView : BaseView + { + [AttachedBindablePropertyAttribute("DerivedText")] + public DerivedView() + { + } + } + """; + + const string expectedBaseGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class BaseView + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.BaseView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + const string expectedDerivedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class DerivedView + { + /// + /// Attached BindableProperty for the DerivedText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty DerivedTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("DerivedText", typeof(string), typeof({{defaultTestNamespace}}.DerivedView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets DerivedText for the child element. + /// + public static string GetDerivedText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(DerivedTextProperty); + /// + /// Sets DerivedText for the child element. + /// + public static void SetDerivedText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(DerivedTextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, ("BaseView.g.cs", expectedBaseGenerated), ("DerivedView.g.cs", expectedDerivedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_GenericClass_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View where T : class + { + [AttachedBindablePropertyAttribute("Text")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_AttributeOnNestedInnerClass_GeneratesCorrectCode() + { + const string outerClassName = "Outerclass"; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{outerClassName}} + { + public {{outerClassName}}() + { + } + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindablePropertyAttribute("Text")] + public {{defaultTestClassName}}() + { + } + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$$"""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{{defaultTestNamespace}}}; + public partial class {{{outerClassName}}} + { + public partial class {{{defaultTestClassName}}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{{defaultTestNamespace}}}.{{{outerClassName}}}.{{{defaultTestClassName}}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + } + """"; + + await VerifySourceGeneratorAsync(source, ($"{outerClassName}.{defaultTestClassName}.g.cs", expectedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_NestedInnerClassWithAttributeOnOutterClass_GeneratesCorrectCode() + { + const string outerClassName = "Outerclass"; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{outerClassName}} : View + { + [AttachedBindablePropertyAttribute("Text")] + public {{outerClassName}}() + { + } + + public partial class {{defaultTestClassName}} : View + { + public {{defaultTestClassName}}() + { + } + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$$"""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{{defaultTestNamespace}}}; + public partial class {{{outerClassName}}} + { + /// + /// Attached BindableProperty for the Text property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{{defaultTestNamespace}}}.{{{outerClassName}}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Text for the child element. + /// + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + /// + /// Sets Text for the child element. + /// + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """"; + + await VerifySourceGeneratorAsync(source, ($"{outerClassName}.g.cs", expectedGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_WithCustomTypes_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + using System.Collections.Generic; + + namespace {{defaultTestNamespace}}; + + public class CustomModel + { + public string Name { get; set; } + } + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindablePropertyAttribute("Model", IsNullable = true)] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + /// + /// Attached BindableProperty for the Model property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty ModelProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Model", typeof(global::{{defaultTestNamespace}}.CustomModel), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets Model for the child element. + /// + public static global::{{defaultTestNamespace}}.CustomModel? GetModel(global::Microsoft.Maui.Controls.BindableObject bindable) => (global::{{defaultTestNamespace}}.CustomModel? )bindable.GetValue(ModelProperty); + /// + /// Sets Model for the child element. + /// + public static void SetModel(global::Microsoft.Maui.Controls.BindableObject bindable, global::{{defaultTestNamespace}}.CustomModel? value) => bindable.SetValue(ModelProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_MultipleClassesInSameFile_GeneratesCorrectCode() + { + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class FirstView : View + { + [AttachedBindablePropertyAttribute("FirstText")] + public FirstView() + { + } + } + + public partial class SecondView : View + { + [AttachedBindablePropertyAttribute("SecondText")] + public SecondView() + { + } + } + """; + + const string expectedFirstGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class FirstView + { + /// + /// Attached BindableProperty for the FirstText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty FirstTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("FirstText", typeof(string), typeof({{defaultTestNamespace}}.FirstView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets FirstText for the child element. + /// + public static string GetFirstText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(FirstTextProperty); + /// + /// Sets FirstText for the child element. + /// + public static void SetFirstText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(FirstTextProperty, value); + } + """; + + const string expectedSecondGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class SecondView + { + /// + /// Attached BindableProperty for the SecondText property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty SecondTextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("SecondText", typeof(string), typeof({{defaultTestNamespace}}.SecondView), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + /// + /// Gets SecondText for the child element. + /// + public static string GetSecondText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(SecondTextProperty); + /// + /// Sets SecondText for the child element. + /// + public static void SetSecondText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(SecondTextProperty, value); + } + """; + + + await VerifySourceGeneratorAsync(source, ("FirstView.g.cs", expectedFirstGenerated), ("SecondView.g.cs", expectedSecondGenerated)); + } + + [Fact] + public async Task GenerateAttachedBindableProperty_CustomXmlDocumentation_GeneratesCorrectCode() + { + const string bindablePropertyXmlDocumentation = "///This is the custom XML documentation for the TextProperty"; + const string getterMethodXmlDocumentation = "///This is the custom XML documentation for the GetText method."; + const string setterMethodXmlDocumentation = "///This is the custom XML documentation for the SetText method."; + + const string source = + /* language=C#-test */ + //lang=csharp + $$""" + using CommunityToolkit.Maui; + using Microsoft.Maui.Controls; + + namespace {{defaultTestNamespace}}; + + public partial class {{defaultTestClassName}} : View + { + [AttachedBindableProperty("Text", BindablePropertyXmlDocumentation = "{{bindablePropertyXmlDocumentation}}", GetterMethodXmlDocumentation = "{{getterMethodXmlDocumentation}}", SetterMethodXmlDocumentation = "{{setterMethodXmlDocumentation}}")] + public {{defaultTestClassName}}() + { + } + } + """; + + const string expectedGenerated = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{defaultTestClassName}} + { + {{bindablePropertyXmlDocumentation}} + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null); + {{getterMethodXmlDocumentation}} + public static string GetText(global::Microsoft.Maui.Controls.BindableObject bindable) => (string)bindable.GetValue(TextProperty); + {{setterMethodXmlDocumentation}} + public static void SetText(global::Microsoft.Maui.Controls.BindableObject bindable, string value) => bindable.SetValue(TextProperty, value); + } + """; + + await VerifySourceGeneratorAsync(source, expectedGenerated); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/BaseAttachedBindablePropertyAttributeGeneratorTest.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/BaseAttachedBindablePropertyAttributeGeneratorTest.cs new file mode 100644 index 0000000000..c396cb9d4d --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/AttachedBindablePropertyAttributeSourceGeneratorTests/BaseAttachedBindablePropertyAttributeGeneratorTest.cs @@ -0,0 +1,125 @@ +namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.AttachedBindablePropertyAttributeSourceGeneratorTests; + +public abstract class BaseAttachedBindablePropertyAttributeSourceGeneratorTest : BaseTest +{ + const string generatedBindablePropertyAttributeFileName = "AttachedBindablePropertyAttribute.g.cs"; + const string expectedAttribute = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertySourceGenerator + + #pragma warning disable + #nullable enable + namespace CommunityToolkit.Maui; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Constructor, AllowMultiple = true, Inherited = false)] + [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyDiagnostic.BindablePropertyAttributeExperimentalDiagnosticId}}")] + /// Source generater that creates an Attached and two methods: Get{PropertyName}(BindableProperty bindable) and Set{PropertyName}(BindableProperty bindable, T value) + ///Type of the Attached Bindable Property. Set to to generate a nullable type for + ///Name of the Attached Property + /// + ///* Generates a field using
+ ///* Generates Get{PropertyName}(BindableProperty bindable)
+ ///* Generates Set{PropertyName}(BindableProperty bindable, T value)
+ ///* The property type will be treated as non-nullable unless is set to + ///
+ public sealed partial class AttachedBindablePropertyAttribute(string propertyName) : global::System.Attribute where T : notnull + { + ///Name of the Attached Property + public string PropertyName { get; } = propertyName; + ///Should generate a nullable type for T + public bool IsNullable { get; init; } + ///The default value for the property + public T? DefaultValue { get; init; } + ///The BindingMode to use on SetBinding() if no BindingMode is given. Default is + public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; init; } = global::Microsoft.Maui.Controls.BindingMode.Default; + ///Method name for to be run when a value is set. Default value is null + ///The referenced method must be + public string? ValidateValueMethodName { get; init; } + ///Method name for to be run when a value has changed. Default value is null + ///The referenced method must be + public string? PropertyChangedMethodName { get; init; } + ///Method name for to be run when a value is set. Default value is null + ///The referenced method must be + public string? PropertyChangingMethodName { get; init; } + ///Method name for used to coerce the range of a value. Default value is null + ///The referenced method must be + public string? CoerceValueMethodName { get; init; } + ///Method name for used to initialize default value for reference types. Default value is null + ///The referenced method must be + public string? DefaultValueCreatorMethodName { get; init; } + ///Custom XML Comments added to BindableProperty + ///Must be the entire XML string, including /// + /// + public string? BindablePropertyXmlDocumentation { get; init; } + ///Custom XML Comments added to Get{PropertyName}(BindableProperty bindable) + ///Must be the entire XML string, including /// + public string? GetterMethodXmlDocumentation { get; init; } + ///Custom XML Comments added to Set{PropertyName}(BindableProperty bindable, T value) + ///Must be the entire XML string, including /// + public string? SetterMethodXmlDocumentation { get; init; } + ///The access modifier applied to the generated field + public global::CommunityToolkit.Maui.AccessModifier BindablePropertyAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + ///The access modifier applied to the generated method Get{PropertyName}(BindableProperty bindable) + public global::CommunityToolkit.Maui.AccessModifier GetterAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + ///The access modifier applied to the generated method Set{PropertyName}(BindableProperty bindable, T value) + public global::CommunityToolkit.Maui.AccessModifier SetterAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + } + """; + + const string generatedEnumFileName = "AccessModifier.g.cs"; + const string expectedEnum = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertySourceGenerator + + #pragma warning disable + #nullable enable + namespace CommunityToolkit.Maui; + + ///Used by CommunityToolkit.Maui source generators to define access modifiers for generated code + public enum AccessModifier + { + ///Generates access modifier + Public = 0, + ///Generates access modifier + Internal = 1, + ///Generates access modifier + ProtectedInternal = 2, + ///Generates access modifier + Protected = 3, + ///Generates access modifier + PrivateProtected = 4, + ///Generates access modifier + Private = 5, + ///Generates no code + None = 6 + } + """; + + protected static Task VerifySourceGeneratorAsync(string source, params List<(string FileName, string GeneratedFile)> expectedGeneratedFilesList) + { + expectedGeneratedFilesList.Add((generatedBindablePropertyAttributeFileName, expectedAttribute)); + expectedGeneratedFilesList.Add((generatedEnumFileName, expectedEnum)); + + return VerifySourceGeneratorAsync(source, expectedGeneratedFilesList); + } + + + protected static Task VerifySourceGeneratorAsync(string source, string expectedGeneratedFile) + { + List<(string FileName, string GeneratedFile)> expectedGeneratedFilesList = + [ + (generatedBindablePropertyAttributeFileName, expectedAttribute), + (generatedEnumFileName, expectedEnum), + ($"{defaultTestClassName}.g.cs", expectedGeneratedFile) + ]; + + return VerifySourceGeneratorAsync(source, expectedGeneratedFilesList); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs index a0eb7fc4b1..20f9815344 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs @@ -7,13 +7,16 @@ namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests; public abstract class BaseTest { - protected static async Task VerifySourceGeneratorAsync(string source, string expectedAttribute, params List<(string FileName, string GeneratedFile)> expectedGenerated) + protected const string defaultTestClassName = "TestView"; + protected const string defaultTestNamespace = "TestNamespace"; + + protected static async Task VerifySourceGeneratorAsync(string source, params List<(string FileName, string GeneratedFile)> expectedGeneratedFilesList) + where TSourceGenerator : IIncrementalGenerator, new() { const string sourceGeneratorNamespace = "CommunityToolkit.Maui.SourceGenerators.Internal"; - const string bindablePropertyAttributeGeneratedFileName = "BindablePropertyAttribute.g.cs"; - var sourceGeneratorFullName = typeof(BindablePropertyAttributeSourceGenerator).FullName ?? throw new InvalidOperationException("Source Generator Type Path cannot be null"); + var sourceGeneratorFullName = typeof(TSourceGenerator).FullName ?? throw new InvalidOperationException("Source Generator Type Path cannot be null"); - var test = new ExperimentalBindablePropertyTest + var test = new ExperimentalBindablePropertyTest { #if NET10_0 ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net100, @@ -28,16 +31,13 @@ protected static async Task VerifySourceGeneratorAsync(string source, string exp { MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindableObject).Assembly.Location), MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindableProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindingMode).Assembly.Location) + MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindingMode).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Microsoft.CodeAnalysis.Accessibility).Assembly.Location), } } }; - var expectedAttributeText = Microsoft.CodeAnalysis.Text.SourceText.From(expectedAttribute, System.Text.Encoding.UTF8); - var bindablePropertyAttributeFilePath = Path.Combine(sourceGeneratorNamespace, sourceGeneratorFullName, bindablePropertyAttributeGeneratedFileName); - test.TestState.GeneratedSources.Add((bindablePropertyAttributeFilePath, expectedAttributeText)); - - foreach (var generatedFile in expectedGenerated.Where(static x => !string.IsNullOrEmpty(x.GeneratedFile))) + foreach (var generatedFile in expectedGeneratedFilesList.Where(static x => !string.IsNullOrEmpty(x.GeneratedFile))) { var expectedGeneratedText = Microsoft.CodeAnalysis.Text.SourceText.From(generatedFile.GeneratedFile, System.Text.Encoding.UTF8); var generatedFilePath = Path.Combine(sourceGeneratorNamespace, sourceGeneratorFullName, generatedFile.FileName); @@ -48,7 +48,8 @@ protected static async Task VerifySourceGeneratorAsync(string source, string exp } // This class can be deleted once [Experimental] is removed from BindablePropertyAttribute - sealed class ExperimentalBindablePropertyTest : CSharpSourceGeneratorTest + sealed class ExperimentalBindablePropertyTest : CSharpSourceGeneratorTest + where TSourceGenerator : IIncrementalGenerator, new() { protected override CompilationOptions CreateCompilationOptions() { @@ -56,7 +57,7 @@ protected override CompilationOptions CreateCompilationOptions() return compilationOptions.WithSpecificDiagnosticOptions(new Dictionary { - { BindablePropertyAttributeSourceGenerator.BindablePropertyAttributeExperimentalDiagnosticId, ReportDiagnostic.Warn } + { BindablePropertyDiagnostic.BindablePropertyAttributeExperimentalDiagnosticId, ReportDiagnostic.Warn } }); } } diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs index 5a184f5f6c..cb2e407d0c 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs @@ -1,11 +1,9 @@ namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests; -public class BaseBindablePropertyAttributeSourceGeneratorTest : BaseTest +public abstract class BaseBindablePropertyAttributeSourceGeneratorTest : BaseTest { - protected const string defaultTestClassName = "TestView"; - protected const string defaultTestNamespace = "TestNamespace"; - - protected const string expectedAttribute = + const string generatedBindablePropertyAttributeFileName = "BindablePropertyAttribute.g.cs"; + const string expectedAttribute = /* language=C#-test */ //lang=csharp $$""" @@ -18,20 +16,34 @@ namespace CommunityToolkit.Maui; [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyAttributeSourceGenerator.BindablePropertyAttributeExperimentalDiagnosticId}}")] - sealed partial class BindablePropertyAttribute : global::System.Attribute + [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyDiagnostic.BindablePropertyAttributeExperimentalDiagnosticId}}")] + public sealed partial class BindablePropertyAttribute : global::System.Attribute { public string? PropertyName { get; } - public global::System.Type? DeclaringType { get; set; } - public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; set; } - public string ValidateValueMethodName { get; set; } = string.Empty; - public string PropertyChangedMethodName { get; set; } = string.Empty; - public string PropertyChangingMethodName { get; set; } = string.Empty; - public string CoerceValueMethodName { get; set; } = string.Empty; - public string DefaultValueCreatorMethodName { get; set; } = string.Empty; + public global::System.Type? DeclaringType { get; init; } + public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; init; } + public string ValidateValueMethodName { get; init; } = string.Empty; + public string PropertyChangedMethodName { get; init; } = string.Empty; + public string PropertyChangingMethodName { get; init; } = string.Empty; + public string CoerceValueMethodName { get; init; } = string.Empty; + public string DefaultValueCreatorMethodName { get; init; } = string.Empty; } """; - protected static Task VerifySourceGeneratorAsync(string source, string expectedGenerated) => - VerifySourceGeneratorAsync(source, expectedAttribute, ($"{defaultTestClassName}.g.cs", expectedGenerated)); + protected static Task VerifySourceGeneratorAsync(string source, params List<(string FileName, string GeneratedFile)> expectedGeneratedFilesList) + { + expectedGeneratedFilesList.Add((generatedBindablePropertyAttributeFileName, expectedAttribute)); + return VerifySourceGeneratorAsync(source, expectedGeneratedFilesList); + } + + protected static Task VerifySourceGeneratorAsync(string source, string expectedGeneratedFile) + { + List<(string FileName, string GeneratedFile)> expectedGeneratedFilesList = + [ + (generatedBindablePropertyAttributeFileName, expectedAttribute), + ($"{defaultTestClassName}.g.cs", expectedGeneratedFile) + ]; + + return VerifySourceGeneratorAsync(source, expectedGeneratedFilesList); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/CommonUsageTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs similarity index 94% rename from src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/CommonUsageTests.cs rename to src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs index 99cf85c087..04a364a70b 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/CommonUsageTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs @@ -2,7 +2,7 @@ namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests; -public class CommonUsageTests : BaseBindablePropertyAttributeSourceGeneratorTest +public class BindablePropertyAttributeSourceGenerator_CommonUsageTests : BaseBindablePropertyAttributeSourceGeneratorTest { [Fact] public async Task GenerateBindableProperty_SimpleExample_GeneratesCorrectCode() @@ -35,7 +35,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -76,7 +76,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public new 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, null); public new partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -117,7 +117,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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, null); public partial string? Text { get => (string? )GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -161,13 +161,13 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Number", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial int Number { get => (int)GetValue(NumberProperty); set => SetValue(NumberProperty, value); } @@ -220,7 +220,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)1, ValidateValue, OnPropertyChanged, OnPropertyChanging, CoerceValue, CreateDefaultValue); public partial int Value { get => false ? field : (int)GetValue(ValueProperty); set => SetValue(ValueProperty, value); } @@ -261,7 +261,7 @@ namespace {{defaultTestNamespace}}; internal partial class TestView { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -292,7 +292,7 @@ public partial class {{defaultTestClassName}} : View } [Fact] - public async Task GenerateBindableProperty_EmptyClass_GeneratesAttributeOnly() + public async Task GenerateBindableProperty_EmptyClass_GeneratesNoCode() { const string source = /* language=C#-test */ @@ -341,7 +341,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Text { get => (string)GetValue(TextProperty); internal set => SetValue(TextProperty, value); } @@ -383,7 +383,7 @@ public partial class {{defaultTestClassName}} { 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); /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; public partial string Text { get => (string)GetValue(TextProperty); private protected set => SetValue(textPropertyKey, value); } @@ -394,7 +394,7 @@ public partial class {{defaultTestClassName}} } [Fact] - public async Task GenerateBindableProperty_ProtectedInternalSetter_GeneratesPrivateProtectedSetter() + public async Task GenerateBindableProperty_ProtectedInternalSetter_GeneratesProtectedInternalSetter() { const string source = /* language=C#-test */ @@ -424,7 +424,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Text { get => (string)GetValue(TextProperty); protected internal set => SetValue(TextProperty, value); } @@ -466,7 +466,7 @@ public partial class {{defaultTestClassName}} { 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); /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; public partial string Text { get => (string)GetValue(TextProperty); protected set => SetValue(textPropertyKey, value); } @@ -508,7 +508,7 @@ public partial class {{defaultTestClassName}} { 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); /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; public partial string Text { get => (string)GetValue(TextProperty); private set => SetValue(textPropertyKey, value); } @@ -550,7 +550,7 @@ public partial class {{defaultTestClassName}} { 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); /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = textPropertyKey.BindableProperty; public partial string Text { get => (string)GetValue(TextProperty); } @@ -596,13 +596,13 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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); public partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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); public partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); set => SetValue(CustomDurationProperty, value); } @@ -664,7 +664,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs similarity index 91% rename from src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs rename to src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs index 91bd2a923f..3bbfcdf8ff 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs @@ -2,7 +2,7 @@ namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests; -public class EdgeCaseTests : BaseBindablePropertyAttributeSourceGeneratorTest +public class BindablePropertyAttributeSourceGenerator_EdgeCaseTests : BaseBindablePropertyAttributeSourceGeneratorTest { [Fact] public async Task GenerateBindableProperty_PropertyWithReservedKeywords_GeneratesCorrectCode() @@ -38,13 +38,13 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty classProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("@class", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string @class { get => (string)GetValue(classProperty); set => SetValue(classProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty namespaceProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("@namespace", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string @namespace { get => (string)GetValue(namespaceProperty); set => SetValue(namespaceProperty, value); } @@ -92,7 +92,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); public partial TestNamespace.Status InvoiceStatus { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } @@ -152,7 +152,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); public partial TestNamespace.Status InvoiceStatus { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } @@ -212,19 +212,19 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableIntProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableInt", typeof(int? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial int? NullableInt { get => (int? )GetValue(NullableIntProperty); set => SetValue(NullableIntProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableDateTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableDateTime", typeof(System.DateTime? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial System.DateTime? NullableDateTime { get => (System.DateTime? )GetValue(NullableDateTimeProperty); set => SetValue(NullableDateTimeProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableBoolProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableBool", typeof(bool? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial bool? NullableBool { get => (bool? )GetValue(NullableBoolProperty); set => SetValue(NullableBoolProperty, value); } @@ -252,7 +252,7 @@ public partial class {{defaultTestClassName}} : View public partial string[] StringArray { get; set; } [BindableProperty] - public partial int[,] MultiDimensionalArray { get; set; } + public partial int[, ] MultiDimensionalArray { get; set; } [BindableProperty] public partial byte[][] JaggedArray { get; set; } @@ -271,19 +271,19 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty StringArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("StringArray", typeof(string[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string[] StringArray { get => (string[])GetValue(StringArrayProperty); set => SetValue(StringArrayProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty MultiDimensionalArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("MultiDimensionalArray", typeof(int[, ]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial int[, ] MultiDimensionalArray { get => (int[, ])GetValue(MultiDimensionalArrayProperty); set => SetValue(MultiDimensionalArrayProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty JaggedArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("JaggedArray", typeof(byte[][]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial byte[][] JaggedArray { get => (byte[][])GetValue(JaggedArrayProperty); set => SetValue(JaggedArrayProperty, value); } @@ -324,7 +324,7 @@ namespace Very.Long.Namespace.With.Many.Segments.TestNamespace; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(Very.Long.Namespace.With.Many.Segments.TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -362,7 +362,7 @@ public partial class {{defaultTestClassName}} : View public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -406,13 +406,13 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty Property_With_UnderscoresProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Property_With_Underscores", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Property_With_Underscores { get => (string)GetValue(Property_With_UnderscoresProperty); set => SetValue(Property_With_UnderscoresProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty Property123WithNumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Property123WithNumbers", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string Property123WithNumbers { get => (string)GetValue(Property123WithNumbersProperty); set => SetValue(Property123WithNumbersProperty, value); } @@ -472,43 +472,43 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultIsEnabled); public partial bool IsEnabled { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingIsEnabled ? field : (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultPi); public partial double Pi { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingPi ? field : (double)GetValue(PiProperty); set => SetValue(PiProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultLetter); public partial char Letter { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingLetter ? field : (char)GetValue(LetterProperty); set => SetValue(LetterProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty TimeSpentProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("TimeSpent", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultTimeSpent); public partial System.TimeSpan TimeSpent { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingTimeSpent ? field : (System.TimeSpan)GetValue(TimeSpentProperty); set => SetValue(TimeSpentProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty DoubleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("DoubleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultDoubleEpsilon); public partial double DoubleEpsilon { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingDoubleEpsilon ? field : (double)GetValue(DoubleEpsilonProperty); set => SetValue(DoubleEpsilonProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty SingleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("SingleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultSingleEpsilon); public partial double SingleEpsilon { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingSingleEpsilon ? field : (double)GetValue(SingleEpsilonProperty); set => SetValue(SingleEpsilonProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty CurrentTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CurrentTime", typeof(System.DateTimeOffset), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultCurrentTime); public partial System.DateTimeOffset CurrentTime { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCurrentTime ? field : (System.DateTimeOffset)GetValue(CurrentTimeProperty); set => SetValue(CurrentTimeProperty, value); } @@ -621,7 +621,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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); public partial string Text { get => false ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } @@ -669,20 +669,20 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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); internal partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// 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); protected internal partial string Time { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingTime ? field : (string)GetValue(TimeProperty); set => SetValue(TimeProperty, value); } 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); /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// internal static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = customDurationPropertyKey.BindableProperty; internal partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); private set => SetValue(customDurationPropertyKey, value); } @@ -741,9 +741,7 @@ public partial class {{defaultTestClassName}} : View } """; - var exception = await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); - - 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); + await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); } [Fact] @@ -765,9 +763,7 @@ public partial class {{defaultTestClassName}} : View } """; - var exception = await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); - - 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); + await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); } [Fact] @@ -789,8 +785,6 @@ public partial class {{defaultTestClassName}} : View } """; - var exception = await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); - - 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); + await Assert.ThrowsAsync(async () => await VerifySourceGeneratorAsync(source, string.Empty)); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs similarity index 83% rename from src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs rename to src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs index 629707eb6a..32621b5da3 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs @@ -1,13 +1,8 @@ -using CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Testing; -using Microsoft.CodeAnalysis.Testing; using Xunit; namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests; -public class IntegrationTests : BaseBindablePropertyAttributeSourceGeneratorTest +public class BindablePropertyAttributeSourceGenerator_IntegrationTests : BaseBindablePropertyAttributeSourceGeneratorTest { [Fact] public async Task GenerateBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode() @@ -49,7 +44,7 @@ namespace {{defaultTestNamespace}}; public partial class BaseView { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty BaseTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("BaseText", typeof(string), typeof(TestNamespace.BaseView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string BaseText { get => (string)GetValue(BaseTextProperty); set => SetValue(BaseTextProperty, value); } @@ -68,20 +63,20 @@ namespace {{defaultTestNamespace}}; public partial class DerivedView { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty DerivedTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("DerivedText", typeof(string), typeof(TestNamespace.DerivedView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string DerivedText { get => (string)GetValue(DerivedTextProperty); set => SetValue(DerivedTextProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public new static readonly global::Microsoft.Maui.Controls.BindableProperty BaseTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("BaseText", typeof(string), typeof(TestNamespace.DerivedView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public new partial string BaseText { get => (string)GetValue(BaseTextProperty); set => SetValue(BaseTextProperty, value); } } """; - await VerifySourceGeneratorAsync(source, expectedAttribute, ("BaseView.g.cs", expectedBaseGenerated), ("DerivedView.g.cs", expectedDerivedGenerated)); + await VerifySourceGeneratorAsync(source, ("BaseView.g.cs", expectedBaseGenerated), ("DerivedView.g.cs", expectedDerivedGenerated)); } [Fact] @@ -118,13 +113,13 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(T), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial T? Value { get => (T? )GetValue(ValueProperty); set => SetValue(ValueProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty NameProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Name", typeof(U), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial U? Name { get => (U? )GetValue(NameProperty); set => SetValue(NameProperty, value); } @@ -165,7 +160,7 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(T), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultValue); public partial T? Value { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingValue ? field : (T? )GetValue(ValueProperty); set => SetValue(ValueProperty, value); } @@ -213,28 +208,28 @@ public partial class {{defaultTestClassName}} : View """; const string expectedGenerated = -/* language=C#-test */ -//lang=csharp -$$""" -// -// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator -#pragma warning disable -#nullable enable -namespace {{defaultTestNamespace}}; -public partial class {{outerClassName}} -{ - public partial class {{defaultTestClassName}} - { - /// - /// Backing BindableProperty for the property. - /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{outerClassName}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); - public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } - } -} -"""; - - await VerifySourceGeneratorAsync(source, expectedAttribute, ($"{outerClassName}.{defaultTestClassName}.g.cs", expectedGenerated)); + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator + #pragma warning disable + #nullable enable + namespace {{defaultTestNamespace}}; + public partial class {{outerClassName}} + { + public partial class {{defaultTestClassName}} + { + /// + /// BindableProperty for the property. + /// + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{outerClassName}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); + public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } + } + } + """; + + await VerifySourceGeneratorAsync(source, ($"{outerClassName}.{defaultTestClassName}.g.cs", expectedGenerated)); } [Fact] @@ -280,19 +275,19 @@ namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty ModelProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Model", typeof(TestNamespace.CustomModel), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial {{defaultTestNamespace}}.CustomModel Model { get => ({{defaultTestNamespace}}.CustomModel)GetValue(ModelProperty); set => SetValue(ModelProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty ItemsProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Items", typeof(System.Collections.Generic.List), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial System.Collections.Generic.List Items { get => (System.Collections.Generic.List)GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); } /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty PropertiesProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Properties", typeof(System.Collections.Generic.Dictionary), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial System.Collections.Generic.Dictionary Properties { get => (System.Collections.Generic.Dictionary)GetValue(PropertiesProperty); set => SetValue(PropertiesProperty, value); } @@ -339,7 +334,7 @@ namespace {{defaultTestNamespace}}; public partial class FirstView { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty FirstTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("FirstText", typeof(string), typeof(TestNamespace.FirstView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string FirstText { get => (string)GetValue(FirstTextProperty); set => SetValue(FirstTextProperty, value); } @@ -358,7 +353,7 @@ namespace {{defaultTestNamespace}}; public partial class SecondView { /// - /// Backing BindableProperty for the property. + /// BindableProperty for the property. /// public static readonly global::Microsoft.Maui.Controls.BindableProperty SecondTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("SecondText", typeof(string), typeof(TestNamespace.SecondView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null); public partial string SecondText { get => (string)GetValue(SecondTextProperty); set => SetValue(SecondTextProperty, value); } @@ -366,6 +361,6 @@ public partial class SecondView """; - await VerifySourceGeneratorAsync(source, expectedAttribute, ("FirstView.g.cs", expectedFirstGenerated), ("SecondView.g.cs", expectedSecondGenerated)); + await VerifySourceGeneratorAsync(source, ("FirstView.g.cs", expectedFirstGenerated), ("SecondView.g.cs", expectedSecondGenerated)); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs index 82c654fb16..1ad9ae042e 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs @@ -141,7 +141,7 @@ public void SemanticValues_WithClassInfoAndProperties_StoresCorrectValues() var bindableProperties = new[] { bindableProperty }.ToImmutableArray(); // Act - var semanticValues = new SemanticValues(classInfo, bindableProperties); + var semanticValues = new BindablePropertySemanticValues(classInfo, bindableProperties); // Assert Assert.Equal(classInfo, semanticValues.ClassInformation); diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyDiagnostic.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyDiagnostic.cs new file mode 100644 index 0000000000..10519ccac3 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyDiagnostic.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkit.Maui.SourceGenerators.Internal; + +public static class BindablePropertyDiagnostic +{ + public const string BindablePropertyAttributeExperimentalDiagnosticId = "MCTEXP001"; +} diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/AttachedBindablePropertyAttributeSourceGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/AttachedBindablePropertyAttributeSourceGenerator.cs new file mode 100644 index 0000000000..68c46f52a6 --- /dev/null +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/AttachedBindablePropertyAttributeSourceGenerator.cs @@ -0,0 +1,880 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Xml.Linq; +using CommunityToolkit.Maui.SourceGenerators.Helpers; +using CommunityToolkit.Maui.SourceGenerators.Internal.Helpers; +using CommunityToolkit.Maui.SourceGenerators.Internal.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace CommunityToolkit.Maui.SourceGenerators.Internal; + +[Generator] +public class AttachedBindablePropertyAttributeSourceGenerator : IIncrementalGenerator +{ + static readonly AttachedBindablePropertySemanticValues emptyAttachedBindablePropertySemanticValues = new(default, []); + + const string bindablePropertyFullName = "global::Microsoft.Maui.Controls.BindableProperty"; + const string bindableObjectFullName = "global::Microsoft.Maui.Controls.BindableObject"; + + const string attachedBindablePropertyAttributeSource = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertySourceGenerator + + #pragma warning disable + #nullable enable + namespace CommunityToolkit.Maui; + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Constructor, AllowMultiple = true, Inherited = false)] + [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyDiagnostic.BindablePropertyAttributeExperimentalDiagnosticId}}")] + /// Source generater that creates an Attached and two methods: Get{PropertyName}(BindableProperty bindable) and Set{PropertyName}(BindableProperty bindable, T value) + ///Type of the Attached Bindable Property. Set to to generate a nullable type for + ///Name of the Attached Property + /// + ///* Generates a field using
+ ///* Generates Get{PropertyName}(BindableProperty bindable)
+ ///* Generates Set{PropertyName}(BindableProperty bindable, T value)
+ ///* The property type will be treated as non-nullable unless is set to + ///
+ public sealed partial class AttachedBindablePropertyAttribute(string propertyName) : global::System.Attribute where T : notnull + { + ///Name of the Attached Property + public string PropertyName { get; } = propertyName; + ///Should generate a nullable type for T + public bool IsNullable { get; init; } + ///The default value for the property + public T? DefaultValue { get; init; } + ///The BindingMode to use on SetBinding() if no BindingMode is given. Default is + public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; init; } = global::Microsoft.Maui.Controls.BindingMode.Default; + ///Method name for to be run when a value is set. Default value is null + ///The referenced method must be + public string? ValidateValueMethodName { get; init; } + ///Method name for to be run when a value has changed. Default value is null + ///The referenced method must be + public string? PropertyChangedMethodName { get; init; } + ///Method name for to be run when a value is set. Default value is null + ///The referenced method must be + public string? PropertyChangingMethodName { get; init; } + ///Method name for used to coerce the range of a value. Default value is null + ///The referenced method must be + public string? CoerceValueMethodName { get; init; } + ///Method name for used to initialize default value for reference types. Default value is null + ///The referenced method must be + public string? DefaultValueCreatorMethodName { get; init; } + ///Custom XML Comments added to BindableProperty + ///Must be the entire XML string, including /// + /// + public string? BindablePropertyXmlDocumentation { get; init; } + ///Custom XML Comments added to Get{PropertyName}(BindableProperty bindable) + ///Must be the entire XML string, including /// + public string? GetterMethodXmlDocumentation { get; init; } + ///Custom XML Comments added to Set{PropertyName}(BindableProperty bindable, T value) + ///Must be the entire XML string, including /// + public string? SetterMethodXmlDocumentation { get; init; } + ///The access modifier applied to the generated field + public global::CommunityToolkit.Maui.AccessModifier BindablePropertyAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + ///The access modifier applied to the generated method Get{PropertyName}(BindableProperty bindable) + public global::CommunityToolkit.Maui.AccessModifier GetterAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + ///The access modifier applied to the generated method Set{PropertyName}(BindableProperty bindable, T value) + public global::CommunityToolkit.Maui.AccessModifier SetterAccessibility { get; init; } = global::CommunityToolkit.Maui.AccessModifier.Public; + } + """; + + const string accessibilityEnumSource = + /* language=C#-test */ + //lang=csharp + $$""" + // + // See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertySourceGenerator + + #pragma warning disable + #nullable enable + namespace CommunityToolkit.Maui; + + ///Used by CommunityToolkit.Maui source generators to define access modifiers for generated code + public enum AccessModifier + { + ///Generates access modifier + Public = 0, + ///Generates access modifier + Internal = 1, + ///Generates access modifier + ProtectedInternal = 2, + ///Generates access modifier + Protected = 3, + ///Generates access modifier + PrivateProtected = 4, + ///Generates access modifier + Private = 5, + ///Generates no code + None = 6 + } + """; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { +#if DEBUG + if (!Debugger.IsAttached) + { + // To debug this SG, uncomment the line below and rebuild the SourceGenerator project. + //Debugger.Launch(); + } +#endif + + context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("AttachedBindablePropertyAttribute.g.cs", SourceText.From(attachedBindablePropertyAttributeSource, Encoding.UTF8))); + context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("AccessModifier.g.cs", SourceText.From(accessibilityEnumSource, Encoding.UTF8))); + + var classProvider = context.SyntaxProvider.ForAttributeWithMetadataName("CommunityToolkit.Maui.AttachedBindablePropertyAttribute`1", + IsNonEmptyClassDeclarationSyntax, SemanticTransform) + .Where(static x => x.ClassInformation != default || !x.BindableProperties.IsEmpty) + .Collect(); + + var constructorProvider = context.SyntaxProvider.ForAttributeWithMetadataName("CommunityToolkit.Maui.AttachedBindablePropertyAttribute`1", + IsNonEmptyConstructorDeclarationSyntax, SemanticTransformConstructor) + .Where(static x => x.ClassInformation != default || !x.BindableProperties.IsEmpty) + .Collect(); + + context.RegisterSourceOutput(classProvider, ExecuteAllValues); + context.RegisterSourceOutput(constructorProvider, ExecuteAllValues); + } + + static void ExecuteAllValues(SourceProductionContext context, ImmutableArray semanticValues) + { + // Use ArrayPool for temporary storage + var attachedBindablePropertiesBuffer = System.Buffers.ArrayPool.Shared.Rent(32); + + try + { + foreach (var attachedBindablePropertySemanticValues in semanticValues) + { + var className = attachedBindablePropertySemanticValues.ClassInformation.ClassName; + var containingNamespace = attachedBindablePropertySemanticValues.ClassInformation.ContainingNamespace; + var containingTypes = attachedBindablePropertySemanticValues.ClassInformation.ContainingTypes; + var genericTypeParameters = attachedBindablePropertySemanticValues.ClassInformation.GenericTypeParameters; + + if (string.IsNullOrEmpty(className) || string.IsNullOrEmpty(containingNamespace)) + { + continue; + } + + // Flatten attached bindable properties without SelectMany allocation + var attachedBindablePropertiesCount = 0; + + foreach (var abp in attachedBindablePropertySemanticValues.BindableProperties.AsImmutableArray()) + { + if (attachedBindablePropertiesCount >= attachedBindablePropertiesBuffer.Length) + { + var newBuffer = System.Buffers.ArrayPool.Shared.Rent(attachedBindablePropertiesBuffer.Length * 2); + Array.Copy(attachedBindablePropertiesBuffer, newBuffer, attachedBindablePropertiesBuffer.Length); + System.Buffers.ArrayPool.Shared.Return(attachedBindablePropertiesBuffer); + attachedBindablePropertiesBuffer = newBuffer; + } + + attachedBindablePropertiesBuffer[attachedBindablePropertiesCount++] = abp; + } + + var attachedBindablePropertiesArray = EquatableArray.FromImmutableArray(ImmutableArray.Create(attachedBindablePropertiesBuffer, 0, attachedBindablePropertiesCount)); + + var classAccessibility = attachedBindablePropertySemanticValues.ClassInformation.DeclaredAccessibility; + + var combinedClassInfo = new ClassInformation(className, classAccessibility, containingNamespace, containingTypes, genericTypeParameters); + var combinedValues = new AttachedBindablePropertySemanticValues(combinedClassInfo, attachedBindablePropertiesArray); + + var fileNameSuffix = string.IsNullOrEmpty(containingTypes) ? className : string.Concat(containingTypes, ".", className); + var source = GenerateSource(combinedValues); + SourceStringService.FormatText(ref source); + context.AddSource($"{fileNameSuffix}.g.cs", SourceText.From(source, Encoding.UTF8)); + } + } + finally + { + System.Buffers.ArrayPool.Shared.Return(attachedBindablePropertiesBuffer); + } + } + + static string GenerateSource(AttachedBindablePropertySemanticValues value) + { + // Pre-calculate StringBuilder capacity to avoid resizing + var estimatedCapacity = 500 + (value.BindableProperties.AsImmutableArray().Length * 500); + var sb = new StringBuilder(estimatedCapacity); + + sb.Append("// \n// See: CommunityToolkit.Maui.SourceGenerators.Internal.AttachedBindablePropertyAttributeSourceGenerator\n\n#pragma warning disable\n#nullable enable\n\n"); + + if (!IsGlobalNamespace(value.ClassInformation)) + { + sb.Append("namespace ").Append(value.ClassInformation.ContainingNamespace).Append(";\n\n"); + } + + // Generate nested class hierarchy + if (!string.IsNullOrEmpty(value.ClassInformation.ContainingTypes)) + { + var containingTypeNames = value.ClassInformation.ContainingTypes.Split('.'); + foreach (var typeName in containingTypeNames) + { + sb.Append(value.ClassInformation.DeclaredAccessibility).Append(" partial class ").Append(typeName).Append("\n{\n\n"); + } + } + + // Get the class name with generic parameters + var classNameWithGenerics = value.ClassInformation.ClassName; + if (!string.IsNullOrEmpty(value.ClassInformation.GenericTypeParameters)) + { + classNameWithGenerics = string.Concat(value.ClassInformation.ClassName, "<", value.ClassInformation.GenericTypeParameters, ">"); + } + + sb.Append(value.ClassInformation.DeclaredAccessibility).Append(" partial class ").Append(classNameWithGenerics).Append("\n{\n\n"); + + // Build fully-qualified declaring type name + var fullDeclaringType = BuildFullyQualifiedTypeName(value.ClassInformation, classNameWithGenerics); + + foreach (var info in value.BindableProperties.AsImmutableArray()) + { + if (info.IsReadOnlyBindableProperty) + { + GenerateAttachedReadOnlyBindableProperty(sb, in info, fullDeclaringType); + } + else + { + GenerateAttachedBindableProperty(sb, in info, fullDeclaringType); + } + } + + sb.Append('}'); + + // Close nested class hierarchy + if (!string.IsNullOrEmpty(value.ClassInformation.ContainingTypes)) + { + var containingTypeNames = value.ClassInformation.ContainingTypes.Split('.'); + for (int i = 0; i < containingTypeNames.Length; i++) + { + sb.Append("\n}"); + } + } + + return sb.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string BuildFullyQualifiedTypeName(ClassInformation classInfo, string classNameWithGenerics) + { + if (IsGlobalNamespace(classInfo)) + { + return string.IsNullOrEmpty(classInfo.ContainingTypes) + ? classNameWithGenerics + : $"{classInfo.ContainingTypes}.{classNameWithGenerics}"; + } + + return string.IsNullOrEmpty(classInfo.ContainingTypes) + ? $"{classInfo.ContainingNamespace}.{classNameWithGenerics}" + : $"{classInfo.ContainingNamespace}.{classInfo.ContainingTypes}.{classNameWithGenerics}"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void GenerateAttachedReadOnlyBindableProperty(StringBuilder sb, in AttachedBindablePropertyModel info, in string fullDeclaringType) + { + // Sanitize the Return Type because Nullable Reference Types cannot be used in the `typeof()` operator + var nonNullableReturnType = ConvertToNonNullableTypeSymbol(info.ReturnType); + var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? string.Concat("@", info.PropertyName) : info.PropertyName; + var formattedReturnType = GetFormattedReturnType(info.ReturnType, info.ShouldPostpendNullable); + var nonNullableFormattedType = GetFormattedTypeForTypeOf(nonNullableReturnType); + + // Generate BindablePropertyKey for read-only properties + sb.Append("static readonly global::Microsoft.Maui.Controls.BindablePropertyKey ") + .Append(info.BindablePropertyKeyName) + .Append(" = \n") + .Append(bindablePropertyFullName) + .Append(".CreateAttachedReadOnly(\"") + .Append(sanitizedPropertyName) + .Append("\", typeof(") + .Append(nonNullableReturnType) + .Append("), typeof(") + .Append(fullDeclaringType) + .Append("),") + .Append(info.DefaultValue) + .Append(", ") + .Append(info.DefaultBindingMode) + .Append(", ") + .Append(info.ValidateValueMethodName) + .Append(", ") + .Append(info.PropertyChangedMethodName) + .Append(", ") + .Append(info.PropertyChangingMethodName) + .Append(", ") + .Append(info.CoerceValueMethodName) + .Append(", "); + + sb.Append(info.DefaultValueCreatorMethodName) + .Append(");\n"); + + sb.Append(info.EffectiveBindablePropertyXmlDocumentation) + .Append("\r\n"); + + // Generate public BindableProperty from the key + sb.Append(info.BindablePropertyAccessibility) + .Append("static readonly ") + .Append(bindablePropertyFullName) + .Append(' ') + .Append(info.BindablePropertyName) + .Append(" = ") + .Append(info.BindablePropertyKeyName) + .Append(".BindableProperty;\n"); + + // Generate Get method + if (info.GetterAccessibility is not null) + { + GenerateGetter(sb, info.EffectiveGetterMethodXmlDocumentation, info.GetterAccessibility, formattedReturnType, info.PropertyName, info.BindablePropertyName); + } + + // Generate Set method + if (info.SetterAccessibility is not null) + { + GenerateSetter(sb, info.EffectiveSetterMethodXmlDocumentation, info.SetterAccessibility, formattedReturnType, info.PropertyName, info.BindablePropertyName, info.BindablePropertyKeyName, info.IsReadOnlyBindableProperty); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void GenerateAttachedBindableProperty(StringBuilder sb, in AttachedBindablePropertyModel info, string fullDeclaringType) + { + // Sanitize the Return Type because Nullable Reference Types cannot be used in the `typeof()` operator + var nonNullableReturnType = ConvertToNonNullableTypeSymbol(info.ReturnType); + var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? string.Concat("@", info.PropertyName) : info.PropertyName; + var formattedReturnType = GetFormattedReturnType(info.ReturnType, info.ShouldPostpendNullable); + var nonNullableFormattedType = GetFormattedTypeForTypeOf(nonNullableReturnType); + + // Generate BindableProperty + sb.Append(info.EffectiveBindablePropertyXmlDocumentation) + .Append("\r\n"); + + sb.Append(info.BindablePropertyAccessibility) + .Append("static readonly ") + .Append(bindablePropertyFullName) + .Append(' ') + .Append(info.BindablePropertyName) + .Append(" = ") + .Append(bindablePropertyFullName) + .Append(".CreateAttached(\"") + .Append(sanitizedPropertyName) + .Append("\", typeof(") + .Append(nonNullableFormattedType) + .Append("), typeof(") + .Append(fullDeclaringType) + .Append("), ") + .Append(info.DefaultValue) + .Append(", ") + .Append(info.DefaultBindingMode) + .Append(", ") + .Append(info.ValidateValueMethodName) + .Append(", ") + .Append(info.PropertyChangedMethodName) + .Append(", ") + .Append(info.PropertyChangingMethodName) + .Append(", ") + .Append(info.CoerceValueMethodName) + .Append(", ") + .Append(info.DefaultValueCreatorMethodName) + .Append(");\n\n"); + + // Generate Get method + if (info.GetterAccessibility is not null) + { + + GenerateGetter(sb, info.EffectiveGetterMethodXmlDocumentation, info.GetterAccessibility, formattedReturnType, info.PropertyName, info.BindablePropertyName); + } + + // Generate Set method + if (info.SetterAccessibility is not null) + { + GenerateSetter(sb, info.EffectiveSetterMethodXmlDocumentation, info.SetterAccessibility, formattedReturnType, info.PropertyName, info.BindablePropertyName, info.BindablePropertyKeyName, info.IsReadOnlyBindableProperty); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void GenerateGetter(in StringBuilder sb, in string getterMethodXmlDocumentation, in string getterAccessibility, in string returnType, in string propertyName, in string bindablePropertyName) + { + sb.Append(getterMethodXmlDocumentation) + .Append("\r\n"); + + sb.Append(getterAccessibility) + .Append("static ") + .Append(returnType) + .Append(" Get") + .Append(propertyName) + .Append("(") + .Append(bindableObjectFullName) + .Append(" bindable) => (") + .Append(returnType) + .Append(")bindable.GetValue(") + .Append(bindablePropertyName) + .Append(");\n\n"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void GenerateSetter(in StringBuilder sb, in string setterMethodXmlDocumentation, in string setterAccessibility, in string returnType, in string propertyName, in string bindablePropertyName, in string bindablePropertyKeyName, in bool isReadyOnlyBindableProperty) + { + sb.Append(setterMethodXmlDocumentation) + .Append("\r\n"); + + sb.Append(setterAccessibility) + .Append("static void Set") + .Append(propertyName) + .Append("(") + .Append(bindableObjectFullName) + .Append(" bindable, ") + .Append(returnType) + .Append(" value) => bindable.SetValue("); + + if (isReadyOnlyBindableProperty) + { + sb.Append(bindablePropertyKeyName); + } + else + { + sb.Append(bindablePropertyName); + } + + sb.Append(", value);\n\n"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static AttachedBindablePropertySemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + var classDeclarationSyntax = (ClassDeclarationSyntax)context.TargetNode; + var semanticModel = context.SemanticModel; + var classSymbol = (INamedTypeSymbol?)ModelExtensions.GetDeclaredSymbol(semanticModel, classDeclarationSyntax, cancellationToken); + + if (classSymbol is null) + { + return emptyAttachedBindablePropertySemanticValues; + } + + return ExtractAttachedBindableProperties(classSymbol, context.Attributes, cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static AttachedBindablePropertySemanticValues SemanticTransformConstructor(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + var constructorDeclarationSyntax = (ConstructorDeclarationSyntax)context.TargetNode; + var semanticModel = context.SemanticModel; + var constructorSymbol = (IMethodSymbol?)ModelExtensions.GetDeclaredSymbol(semanticModel, constructorDeclarationSyntax, cancellationToken); + + if (constructorSymbol is null || constructorSymbol.ContainingType is null) + { + return emptyAttachedBindablePropertySemanticValues; + } + + return ExtractAttachedBindableProperties(constructorSymbol.ContainingType, context.Attributes, cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static AttachedBindablePropertySemanticValues ExtractAttachedBindableProperties(INamedTypeSymbol classSymbol, ImmutableArray attributes, CancellationToken cancellationToken) + { + var @namespace = classSymbol.ContainingNamespace.ToDisplayString(); + var className = classSymbol.Name; + var classAccessibility = classSymbol.DeclaredAccessibility.ToString().ToLower(); + + // Build containing types hierarchy + var containingTypes = GetContainingTypes(classSymbol); + + // Extract generic type parameters + var genericTypeParameters = GetGenericTypeParameters(classSymbol); + + var classInfo = new ClassInformation(className, classAccessibility, @namespace, containingTypes, genericTypeParameters); + + // Use array for attached bindable properties + var attachedBindablePropertyModels = new AttachedBindablePropertyModel[attributes.Length]; + + for (int i = 0; i < attributes.Length; i++) + { + var attributeData = attributes[i]; + attachedBindablePropertyModels[i] = CreateAttachedBindablePropertyModel(attributeData, classSymbol); + } + + return new(classInfo, ImmutableArray.Create(attachedBindablePropertyModels).AsEquatableArray()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static AttachedBindablePropertyModel CreateAttachedBindablePropertyModel(AttributeData attributeData, INamedTypeSymbol declaringType) + { + if (attributeData.AttributeClass is null || attributeData.ConstructorArguments.Length is 0) + { + throw new ArgumentException("Invalid attribute data"); + } + + // Get property name from constructor argument + var propertyNameArg = attributeData.ConstructorArguments[0]; + var propertyName = propertyNameArg.Value as string ?? throw new InvalidOperationException("Property name must be a string"); + + // Validate property name starts with uppercase + if (string.IsNullOrEmpty(propertyName) || char.IsLower(propertyName[0])) + { + throw new NotSupportedException($"Property name '{propertyName}' must start with an uppercase letter cannot be empty"); + } + + // Get the generic type argument (the T in AttachedBindableProperty) + var typeArg = (attributeData.AttributeClass?.TypeArguments.FirstOrDefault()) ?? throw new InvalidOperationException("Could not determine property type from attribute"); + var isDeclaringTypeNullable = (bool)(attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "IsNullable").Value.Value ?? false); + + var defaultValue = GetDefaultValueString(attributeData, typeArg, AttachedBindablePropertyModel.ShouldPostpendNullableToType(typeArg, isDeclaringTypeNullable)); + var defaultBindingMode = attributeData.GetNamedTypeArgumentsAttributeValueForDefaultBindingMode("DefaultBindingMode", "(global::Microsoft.Maui.Controls.BindingMode)0"); + var validateValueMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString("ValidateValueMethodName"); + var propertyChangedMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString("PropertyChangedMethodName"); + var propertyChangingMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString("PropertyChangingMethodName"); + var coerceValueMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString("CoerceValueMethodName"); + var defaultValueCreatorMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString("DefaultValueCreatorMethodName"); + + var getterAccessibility = GetAccessibilityString(attributeData, "GetterAccessibility"); + var setterAccessibility = GetAccessibilityString(attributeData, "SetterAccessibility"); + var bindablePropertyAccessibility = GetAccessibilityString(attributeData, "BindablePropertyAccessibility") + ?? throw new InvalidOperationException("Bindable Property Accessibilty Cannot be null"); + + var getterMethodXMLDocumentation = GetXMLDocumenation(attributeData, "GetterMethodXmlDocumentation"); + var setterMethodXMLDocumentation = GetXMLDocumenation(attributeData, "SetterMethodXmlDocumentation"); + var bindablePropertyXMLDocumentation = GetXMLDocumenation(attributeData, "BindablePropertyXmlDocumentation"); + + var isReadOnly = GetIsReadOnlyBindableProperty(attributeData); + + return new AttachedBindablePropertyModel( + propertyName, + typeArg, + declaringType, + defaultValue, + defaultBindingMode, + validateValueMethodName, + propertyChangedMethodName, + propertyChangingMethodName, + coerceValueMethodName, + defaultValueCreatorMethodName, + getterAccessibility, + setterAccessibility, + bindablePropertyAccessibility, + isDeclaringTypeNullable, + bindablePropertyXMLDocumentation, + getterMethodXMLDocumentation, + setterMethodXMLDocumentation, + isReadOnly + ); + } + + // DefaultValue can only be a primitive type, enum, typeof expression or array + // In C#, attribute arguments are limited to compile-time constants, typeof expressions, and single-dimensional arrays + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetDefaultValueString(AttributeData attributeData, ITypeSymbol typeArg, bool shouldPostpendNullableSymbol) + { + var defaultValueArg = attributeData.NamedArguments + .FirstOrDefault(x => x.Key is "DefaultValue"); + + if (defaultValueArg.Value.IsNull) + { + return "null"; + } + + var value = defaultValueArg.Value; + + // Handle Arrays + if (typeArg.TypeKind is TypeKind.Array) + { + return GetFormattedArrayValue(value, GetFormattedReturnType(typeArg, shouldPostpendNullableSymbol)); + } + + // Handle null values + if (value.Value is null) + { + return "null"; + } + + var typeSymbol = GetFormattedReturnType(typeArg, shouldPostpendNullableSymbol); + + return value.Value switch + { + // Handle char + char charVal => $"({typeSymbol})'{charVal}'", + + // Handle string + string stringVal => $"({typeSymbol})\"{GetEscapedString(stringVal)}\"", + + // Handle enums and all remaining compile-time constants + _ => $"({typeSymbol}){GetFormattedConstantValue(value)}" + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string? GetXMLDocumenation(AttributeData attributeData, string parameterName) + { + var xmlDocumentation = attributeData.NamedArguments + .FirstOrDefault(x => x.Key == parameterName) + .Value; + + if (xmlDocumentation.IsNull || xmlDocumentation.Value is null) + { + return null; + } + + return xmlDocumentation.Value.ToString(); + } + + static bool GetIsReadOnlyBindableProperty(AttributeData attributeData) + { + var accessibility = attributeData.NamedArguments + .FirstOrDefault(x => x.Key == "SetterAccessibility") + .Value; + + if (accessibility.IsNull || accessibility.Value is null) + { + return false; + } + + var enumValue = (int)accessibility.Value; + + return enumValue switch + { + 0 => false, // Public + 1 => false, // Internal + 2 => false, // ProtectedInternal + 3 => true, // Protected + 4 => true, // PrivateProtected + 5 => true, // Private + 6 => true, // null + _ => throw new NotSupportedException($"{accessibility.Value} is not yet supported") + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string? GetAccessibilityString(AttributeData attributeData, string parameterName) + { + var accessibility = attributeData.NamedArguments + .FirstOrDefault(x => x.Key == parameterName) + .Value; + + if (accessibility.IsNull || accessibility.Value is null) + { + return "public "; + } + + var enumValue = (int)accessibility.Value; + + return enumValue switch + { + 0 => "public ", + 1 => "internal ", + 2 => "protected internal ", + 3 => "protected ", + 4 => "private protected ", + 5 => "private ", + 6 => null, + _ => throw new NotSupportedException($"{accessibility.Value} is not yet supported") + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsNonEmptyClassDeclarationSyntax(SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsNonEmptyConstructorDeclarationSyntax(SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return node is ConstructorDeclarationSyntax { AttributeLists.Count: > 0 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsDotnetKeyword(in string name) => SyntaxFacts.GetKeywordKind(name) is not SyntaxKind.None; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsGlobalNamespace(in ClassInformation classInformation) => classInformation.ContainingNamespace is "" or ""; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetContainingTypes(INamedTypeSymbol typeSymbol) + { + var current = typeSymbol.ContainingType; + if (current is null) + { + return string.Empty; + } + + var sb = new StringBuilder(100); + var stack = new Stack(4); + + while (current is not null) + { + stack.Push(current.Name); + current = current.ContainingType; + } + + var first = true; + while (stack.Count > 0) + { + if (!first) + { + sb.Append('.'); + } + + sb.Append(stack.Pop()); + first = false; + } + + return sb.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetGenericTypeParameters(INamedTypeSymbol typeSymbol) + { + if (!typeSymbol.IsGenericType || typeSymbol.TypeParameters.IsEmpty) + { + return string.Empty; + } + + var typeParams = typeSymbol.TypeParameters; + if (typeParams.Length is 1) + { + return typeParams[0].Name; + } + + var sb = new StringBuilder(typeParams.Length * 10); + for (int i = 0; i < typeParams.Length; i++) + { + if (i > 0) + { + sb.Append(", "); + } + + sb.Append(typeParams[i].Name); + } + + return sb.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static ITypeSymbol ConvertToNonNullableTypeSymbol(in ITypeSymbol typeSymbol) + { + // Check for Nullable + if (typeSymbol is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T } nullableType) + { + return nullableType.TypeArguments[0]; + } + + // Check for Nullable Reference Type + if (typeSymbol.NullableAnnotation is NullableAnnotation.Annotated) + { + return typeSymbol.WithNullableAnnotation(NullableAnnotation.None); + } + + return typeSymbol; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetFormattedReturnType(ITypeSymbol typeSymbol, bool shouldPostpendNullableSymbol) + { + var formattedType = GetFormattedTypeForTypeOf(typeSymbol); + + return shouldPostpendNullableSymbol ? $"{formattedType}?" : formattedType; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetFormattedTypeForTypeOf(ITypeSymbol typeSymbol) + { + if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) + { + string elementType = GetFormattedTypeForTypeOf(arrayTypeSymbol.ElementType); + var rank = arrayTypeSymbol.Rank > 1 ? new string(',', arrayTypeSymbol.Rank - 1) : string.Empty; + return string.Concat(elementType, "[", rank, "]"); + } + + return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetEscapedString(string value) + { + return value + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetFormattedArrayValue(TypedConstant arrayConstant, string typeSymbol) + { + if (arrayConstant.Values.Length is 0) + { + return $"({typeSymbol})[]"; + } + + // Single-dimensional array + var sb = new StringBuilder(); + sb.Append("(").Append(typeSymbol).Append(")new[] { "); + + for (int i = 0; i < arrayConstant.Values.Length; i++) + { + if (i > 0) + { + sb.Append(", "); + } + + var element = arrayConstant.Values[i]; + sb.Append(GetFormattedConstantValue(element)); + } + + sb.Append(" }"); + return sb.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static string GetFormattedConstantValue(TypedConstant constant) + { + if (constant.IsNull) + { + return "null"; + } + + return constant.Value switch + { + true => "true", + false => "false", + int intVal => intVal.ToString(), + byte byteVal => byteVal.ToString(), + sbyte sbyteVal => sbyteVal.ToString(), + short shortVal => shortVal.ToString(), + ushort ushortVal => ushortVal.ToString(), + long longVal => $"{longVal}L", + uint uintVal => $"{uintVal}U", + ulong ulongVal => $"{ulongVal}UL", + double doubleVal => doubleVal switch + { + double.NaN => "double.NaN", + double.PositiveInfinity => "double.PositiveInfinity", + double.NegativeInfinity => "double.NegativeInfinity", + double.Epsilon => "double.Epsilon", + double.MaxValue => "double.MaxValue", + double.MinValue => "double.MinValue", + _ => doubleVal.ToString("G17") + }, + float floatVal => floatVal switch + { + float.NaN => "float.NaN", + float.PositiveInfinity => "float.PositiveInfinity", + float.NegativeInfinity => "float.NegativeInfinity", + float.Epsilon => "float.Epsilon", + float.MaxValue => "float.MaxValue", + float.MinValue => "float.MinValue", + _ => $"{floatVal:G9}f" + }, + char charVal => $"'{charVal}'", + string stringVal => $"\"{GetEscapedString(stringVal)}\"", + _ => "null" + }; + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs index 2a354ce309..eda6bdcd8a 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs @@ -15,12 +15,10 @@ namespace CommunityToolkit.Maui.SourceGenerators.Internal; [Generator] public class BindablePropertyAttributeSourceGenerator : IIncrementalGenerator { - public const string BindablePropertyAttributeExperimentalDiagnosticId = "MCTEXP001"; + static readonly BindablePropertySemanticValues emptyBindablePropertySemanticValues = new(default, []); - static readonly SemanticValues emptySemanticValues = new(default, []); - - const string bpFullName = "global::Microsoft.Maui.Controls.BindableProperty"; - const string bpAttribute = + const string bindablePropertyFullName = "global::Microsoft.Maui.Controls.BindableProperty"; + const string bindablePropertyAttributeSource = /* language=C#-test */ //lang=csharp $$""" @@ -33,17 +31,17 @@ namespace CommunityToolkit.Maui; [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyAttributeExperimentalDiagnosticId}}")] - sealed partial class BindablePropertyAttribute : global::System.Attribute + [global::System.Diagnostics.CodeAnalysis.Experimental("{{BindablePropertyDiagnostic.BindablePropertyAttributeExperimentalDiagnosticId}}")] + public sealed partial class BindablePropertyAttribute : global::System.Attribute { public string? PropertyName { get; } - public global::System.Type? DeclaringType { get; set; } - public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; set; } - public string ValidateValueMethodName { get; set; } = string.Empty; - public string PropertyChangedMethodName { get; set; } = string.Empty; - public string PropertyChangingMethodName { get; set; } = string.Empty; - public string CoerceValueMethodName { get; set; } = string.Empty; - public string DefaultValueCreatorMethodName { get; set; } = string.Empty; + public global::System.Type? DeclaringType { get; init; } + public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; init; } + public string ValidateValueMethodName { get; init; } = string.Empty; + public string PropertyChangedMethodName { get; init; } = string.Empty; + public string PropertyChangingMethodName { get; init; } = string.Empty; + public string CoerceValueMethodName { get; init; } = string.Empty; + public string DefaultValueCreatorMethodName { get; init; } = string.Empty; } """; @@ -59,7 +57,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } #endif - context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("BindablePropertyAttribute.g.cs", SourceText.From(bpAttribute, Encoding.UTF8))); + context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("BindablePropertyAttribute.g.cs", SourceText.From(bindablePropertyAttributeSource, Encoding.UTF8))); var provider = context.SyntaxProvider.ForAttributeWithMetadataName("CommunityToolkit.Maui.BindablePropertyAttribute", IsNonEmptyPropertyDeclarationSyntax, SemanticTransform) @@ -70,10 +68,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(provider, ExecuteAllValues); } - static void ExecuteAllValues(SourceProductionContext context, ImmutableArray semanticValues) + static void ExecuteAllValues(SourceProductionContext context, ImmutableArray semanticValues) { // Pre-allocate dictionary with expected capacity - var groupedValues = new Dictionary<(string, string, string, string), List>(semanticValues.Length); + var groupedValues = new Dictionary<(string, string, string, string), List>(semanticValues.Length); // Single-pass grouping without LINQ foreach (var sv in semanticValues) @@ -125,7 +123,7 @@ static void ExecuteAllValues(SourceProductionContext context, ImmutableArray\r\n/// Backing BindableProperty for the \r\n/// BindableProperty for the property.\r\n/// \r\n"); @@ -298,7 +296,7 @@ static void GenerateReadOnlyBindableProperty(StringBuilder sb, in BindableProper .Append(" ") .Append(info.NewKeywordText) .Append("static readonly ") - .Append(bpFullName) + .Append(bindablePropertyFullName) .Append(' ') .Append(info.BindablePropertyName) .Append(" = ") @@ -315,7 +313,7 @@ static void GenerateBindableProperty(StringBuilder sb, in BindablePropertyModel var nonNullableReturnType = ConvertToNonNullableTypeSymbol(info.ReturnType); var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? string.Concat("@", info.PropertyName) : info.PropertyName; - sb.Append("/// \r\n/// Backing BindableProperty for the \r\n/// BindableProperty for the property.\r\n/// \r\n"); @@ -324,11 +322,11 @@ static void GenerateBindableProperty(StringBuilder sb, in BindablePropertyModel .Append(" ") .Append(info.NewKeywordText) .Append("static readonly ") - .Append(bpFullName) + .Append(bindablePropertyFullName) .Append(' ') .Append(info.BindablePropertyName) .Append(" = \n") - .Append(bpFullName) + .Append(bindablePropertyFullName) .Append(".Create(\"") .Append(sanitizedPropertyName) .Append("\", typeof(") @@ -406,7 +404,7 @@ static void GenerateProperty(StringBuilder sb, in BindablePropertyModel info, in sb.Append("}\n"); } - static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + static BindablePropertySemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { var propertyDeclarationSyntax = Unsafe.As(context.TargetNode); var semanticModel = context.SemanticModel; @@ -415,7 +413,7 @@ static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, if (propertySymbol is null) { - return emptySemanticValues; + return emptyBindablePropertySemanticValues; } var @namespace = propertySymbol.ContainingNamespace.ToDisplayString(); @@ -488,7 +486,7 @@ static bool HasNewKeyword(PropertyDeclarationSyntax syntax) Accessibility.Internal => "internal", Accessibility.ProtectedOrInternal => "protected internal", Accessibility.Public => "public", - _ => throw new NotSupportedException($"The property accessiblity, {propertySymbol.DeclaredAccessibility}, for {propertySymbol.Name} is not supported. The supported accessibility kinds are `public`, `internal` and `protected internal`."), + _ => throw new NotSupportedException($"The property accessibility, {propertySymbol.DeclaredAccessibility}, for {propertySymbol.Name} is not supported. The supported accessibility kinds are `public`, `internal` and `protected internal`."), }; static string GetContainingTypes(INamedTypeSymbol typeSymbol) diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs index 524aba5199..62187d50b6 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs @@ -11,17 +11,17 @@ public static TypedConstant GetAttributeValueByName(this AttributeData attribute return x; } - public static string GetNamedTypeArgumentsAttributeValueForDefaultBindingMode(this AttributeData attribute, string name, string placeholder = "null") + public static string GetNamedTypeArgumentsAttributeValueForDefaultBindingMode(this AttributeData attribute, string name, string placeholder) { var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value; return data.Value is null ? placeholder : $"({data.Type}){data.Value}"; } - public static string GetNamedMethodGroupArgumentsAttributeValueByNameAsString(this AttributeData attribute, string name, string placeholder = "null") + public static string GetNamedMethodGroupArgumentsAttributeValueByNameAsString(this AttributeData attribute, string name) { var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value; - return data.Value is null ? placeholder : data.Value.ToString(); + return data.Value?.ToString() ?? "null"; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs index 82f42032c3..c48d3b6d11 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs @@ -11,9 +11,47 @@ public record BindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, public string BindablePropertyKeyName => $"{char.ToLower(PropertyName[0])}{PropertyName[1..]}PropertyKey"; public string EffectiveDefaultValueCreatorMethodName => ShouldUsePropertyInitializer ? $"CreateDefault{PropertyName}" : DefaultValueCreatorMethodName; public string InitializingPropertyName => $"IsInitializing{PropertyName}"; +} + +public record AttachedBindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, ITypeSymbol DeclaringType, string DefaultValue, string DefaultBindingMode, string ValidateValueMethodName, string PropertyChangedMethodName, string PropertyChangingMethodName, string CoerceValueMethodName, string DefaultValueCreatorMethodName, string? GetterAccessibility, string? SetterAccessibility, string BindablePropertyAccessibility, bool IsDeclaringTypeNullable, string? BindablePropertyXmlDocumentation, string? GetterMethodXmlDocumentation, string? SetterMethodXmlDocumentation, bool IsReadOnlyBindableProperty) +{ + public string BindablePropertyName => $"{PropertyName}Property"; + public bool ShouldPostpendNullable => ShouldPostpendNullableToType(ReturnType, IsDeclaringTypeNullable); + public string BindablePropertyKeyName => $"{char.ToLower(PropertyName[0])}{PropertyName[1..]}PropertyKey"; + public string EffectiveBindablePropertyXmlDocumentation => BindablePropertyXmlDocumentation ?? + /* language=C#-test */ + //lang=csharp + $""" + /// + /// Attached BindableProperty for the {PropertyName} property. + /// + """; + + public string EffectiveGetterMethodXmlDocumentation => GetterMethodXmlDocumentation ?? + /* language=C#-test */ + //lang=csharp + $""" + /// + /// Gets {PropertyName} for the child element. + /// + """; + + public string EffectiveSetterMethodXmlDocumentation => SetterMethodXmlDocumentation ?? + /* language=C#-test */ + //lang=csharp + $""" + /// + /// Sets {PropertyName} for the child element. + /// + """; + + internal static bool ShouldPostpendNullableToType(ITypeSymbol typeSymbol, bool isDeclaringTypeNullable) + => isDeclaringTypeNullable && typeSymbol.OriginalDefinition.SpecialType is not SpecialType.System_Nullable_T; } -public record SemanticValues(ClassInformation ClassInformation, EquatableArray BindableProperties); +public record BindablePropertySemanticValues(ClassInformation ClassInformation, EquatableArray BindableProperties); + +public record AttachedBindablePropertySemanticValues(ClassInformation ClassInformation, EquatableArray BindableProperties); public readonly record struct ClassInformation(string ClassName, string DeclaredAccessibility, string ContainingNamespace, string ContainingTypes = "", string GenericTypeParameters = ""); \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTest.cs b/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTest.cs index 8b55f5fefd..9b6d2ae11a 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTest.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTest.cs @@ -66,8 +66,17 @@ public void EnsureTryRemoveBindingContextUnchangedWhenViewAttached() Assert.True(wasSuccessful); Assert.Equal(view.BindingContext, attachedBehavior.BindingContext); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + view.Behaviors.Clear(); + } - protected class MockValidationBehavior : ValidationBehavior + sealed class MockViewModel; + + protected sealed class MockValidationBehavior : ValidationBehavior { public string? ExpectedValue { get; init; } public bool SimulateValidationDelay { get; init; } = false; @@ -82,9 +91,4 @@ protected override async ValueTask ValidateAsync(string? value, Cancellati return value == ExpectedValue; } } - - class MockViewModel - { - - } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTests.cs b/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTests.cs index 70a215edc8..f6b48c5d41 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Behaviors/BaseBehaviorTests.cs @@ -1,10 +1,11 @@ +using CommunityToolkit.Maui.Behaviors; using CommunityToolkit.Maui.UnitTests.Mocks; using FluentAssertions; using Xunit; namespace CommunityToolkit.Maui.UnitTests.Behaviors; -public class BaseBehaviorTests +public class BaseBehaviorTests() : BaseBehaviorTest(new MockBehavior(), new Label()) { [Fact] public void AttachAndDetachCallsShouldCorrectlyAssignView() diff --git a/src/CommunityToolkit.Maui.UnitTests/Layouts/DockLayoutTests.cs b/src/CommunityToolkit.Maui.UnitTests/Layouts/DockLayoutTests.cs index 292467b4bb..7a69e5f103 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Layouts/DockLayoutTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Layouts/DockLayoutTests.cs @@ -45,9 +45,12 @@ public DockLayoutTests() public void EnsureDefaults() { var layout = new DockLayout(); + var view = new View(); + Assert.Equal(DockLayoutDefaults.ShouldExpandLastChild, layout.ShouldExpandLastChild); Assert.Equal(DockLayoutDefaults.HorizontalSpacing, layout.HorizontalSpacing); Assert.Equal(DockLayoutDefaults.VerticalSpacing, layout.VerticalSpacing); + Assert.Equal(DockLayoutDefaults.DockPosition, DockLayout.GetDockPosition(view)); } #region Measure diff --git a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs index 5e6a01c9b8..d99abca58e 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using CommunityToolkit.Maui.Core; using CommunityToolkit.Maui.Layouts; using CommunityToolkit.Maui.UnitTests.Mocks; using FluentAssertions; @@ -506,7 +507,7 @@ public void StateContainer_ElementNotInheritsLayoutThrowsException() [Fact] public void StateContainer_CreatesControllerWithLayout() { - var containerController = StateContainer.GetContainerController(layout); + var containerController = StateContainer.GetLayoutController(layout); Assert.NotNull(containerController); Assert.IsType(containerController.GetLayout()); @@ -586,7 +587,20 @@ public void Controller_GridStateViewSpansParent() Assert.Equal(Grid.GetRowSpan(view), grid.RowDefinitions.Count); } - class ViewModel : INotifyPropertyChanged + [Fact] + public void EnsureDefaults() + { + // Arrange + var stackLayout = new StackLayout(); + + // Act Assert + Assert.Equal(StateContainerDefaults.StateViews, StateContainer.GetStateViews(stackLayout)); + Assert.Equal(StateContainerDefaults.CurrentState, StateContainer.GetCurrentState(stackLayout)); + Assert.Equal(StateContainerDefaults.CanStateChange, StateContainer.GetCanStateChange(stackLayout)); + Assert.Equal(StateViewDefaults.StateKey, StateView.GetStateKey(stackLayout)); + } + + sealed class ViewModel : INotifyPropertyChanged { public bool CanChangeState { diff --git a/src/CommunityToolkit.Maui/Behaviors/AttachedBehaviors/SetFocusOnEntryCompletedBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/AttachedBehaviors/SetFocusOnEntryCompletedBehavior.shared.cs index 32e1f6c15f..5c2ca05368 100644 --- a/src/CommunityToolkit.Maui/Behaviors/AttachedBehaviors/SetFocusOnEntryCompletedBehavior.shared.cs +++ b/src/CommunityToolkit.Maui/Behaviors/AttachedBehaviors/SetFocusOnEntryCompletedBehavior.shared.cs @@ -3,31 +3,9 @@ /// /// The is an attached property for entries that allows the user to specify what should gain focus after the user completes that entry. /// +[AttachedBindableProperty("NextElement", PropertyChangedMethodName = nameof(OnNextElementChanged))] public partial class SetFocusOnEntryCompletedBehavior : BaseBehavior { - /// - /// The attached property. - /// - public static readonly BindableProperty NextElementProperty = - BindableProperty.CreateAttached( - "NextElement", - typeof(VisualElement), - typeof(SetFocusOnEntryCompletedBehavior), - default(VisualElement), - propertyChanged: OnNextElementChanged); - - /// - /// Required accessor for attached property. - /// - public static VisualElement GetNextElement(BindableObject view) => - (VisualElement)view.GetValue(NextElementProperty); - - /// - /// Required accessor for attached property. - /// - public static void SetNextElement(BindableObject view, VisualElement value) => - view.SetValue(NextElementProperty, value); - static void OnNextElementChanged(BindableObject bindable, object oldValue, object newValue) { var entry = (Entry)bindable; diff --git a/src/CommunityToolkit.Maui/Behaviors/Validators/MultiValidationBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/Validators/MultiValidationBehavior.shared.cs index b34df542f1..0906e32d1f 100644 --- a/src/CommunityToolkit.Maui/Behaviors/Validators/MultiValidationBehavior.shared.cs +++ b/src/CommunityToolkit.Maui/Behaviors/Validators/MultiValidationBehavior.shared.cs @@ -10,13 +10,30 @@ namespace CommunityToolkit.Maui.Behaviors; [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] [RequiresUnreferencedCode($"{nameof(MultiValidationBehavior)} is not trim safe because it uses bindings with string paths.")] [ContentProperty(nameof(Children))] +[AttachedBindableProperty("Error", IsNullable = true, GetterMethodXmlDocumentation = getErrorMethodXmlDocumentation, SetterMethodXmlDocumentation = setErrorMethodXmlDocumentation)] public partial class MultiValidationBehavior : ValidationBehavior { - /// - /// BindableProperty for getting the error. - /// - public static readonly BindableProperty ErrorProperty = - BindableProperty.CreateAttached(nameof(GetError), typeof(object), typeof(MultiValidationBehavior), null); + const string getErrorMethodXmlDocumentation = + /* language=C#-test */ + //lang=csharp + """ + /// + /// Method to extract the error from the attached property for a child behavior in . + /// + /// The that we extract the attached Error property + /// Object containing error information + """; + + const string setErrorMethodXmlDocumentation = + /* language=C#-test */ + //lang=csharp + """ + /// + /// Method to set the error on the attached property for a child behavior in . + /// + /// The on which we set the attached Error property value + /// The value to set + """; readonly ObservableCollection children = []; @@ -31,20 +48,6 @@ public partial class MultiValidationBehavior : ValidationBehavior [BindableProperty(DefaultBindingMode = BindingMode.OneWayToSource)] public partial List? Errors { get; set; } = MultiValidationBehaviorDefaults.Errors; - /// - /// Method to extract the error from the attached property for a child behavior in . - /// - /// The that we extract the attached Error property - /// Object containing error information - public static object? GetError(BindableObject bindable) => bindable.GetValue(ErrorProperty); - - /// - /// Method to set the error on the attached property for a child behavior in . - /// - /// The on which we set the attached Error property value - /// The value to set - public static void SetError(BindableObject bindable, object value) => bindable.SetValue(ErrorProperty, value); - /// protected override async ValueTask ValidateAsync(object? value, CancellationToken token) { diff --git a/src/CommunityToolkit.Maui/Behaviors/Validators/ValidationBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/Validators/ValidationBehavior.shared.cs index 069c1775d1..3cb6d14433 100644 --- a/src/CommunityToolkit.Maui/Behaviors/Validators/ValidationBehavior.shared.cs +++ b/src/CommunityToolkit.Maui/Behaviors/Validators/ValidationBehavior.shared.cs @@ -330,15 +330,24 @@ async ValueTask UpdateStateAsync(VisualElement? view, ValidationFlags flags, boo if (view is not null) { - UpdateStyle(view, IsValid); + TryUpdateStyle(view, IsValid); } } - void UpdateStyle(in VisualElement view, bool isValid) + bool TryUpdateStyle(in VisualElement view, bool isValid) { - VisualStateManager.GoToState(view, isValid ? ValidVisualState : InvalidVisualState); + try + { + VisualStateManager.GoToState(view, isValid ? ValidVisualState : InvalidVisualState); + + view.Style = (isValid ? ValidStyle : InvalidStyle) ?? view.Style; - view.Style = (isValid ? ValidStyle : InvalidStyle) ?? view.Style; + return true; + } + catch (ArgumentException) + { + return false; + } } void ResetValidationTokenSource(CancellationTokenSource? newTokenSource) diff --git a/src/CommunityToolkit.Maui/Layouts/DockLayout.shared.cs b/src/CommunityToolkit.Maui/Layouts/DockLayout.shared.cs index 6a4fc7adf9..8a9c917efb 100644 --- a/src/CommunityToolkit.Maui/Layouts/DockLayout.shared.cs +++ b/src/CommunityToolkit.Maui/Layouts/DockLayout.shared.cs @@ -7,16 +7,17 @@ namespace CommunityToolkit.Maui.Layouts; /// /// DockLayout positions its child elements along the edges of the layout container. /// +[AttachedBindableProperty(nameof(DockPosition), DefaultValue = DockLayoutDefaults.DockPosition, PropertyChangedMethodName = nameof(OnDockPositionPropertyChanged), BindablePropertyXmlDocumentation = dockPositionBindablePropertyXmlDocumentation)] public partial class DockLayout : Layout, IDockLayout { - /// - /// Docking position for a view. - /// - public static readonly BindableProperty DockPositionProperty = BindableProperty.CreateAttached(nameof(DockPosition), - typeof(DockPosition), - typeof(DockLayout), - DockPosition.None, - propertyChanged: OnDockPositionPropertyChanged); + const string dockPositionBindablePropertyXmlDocumentation = + /* language=C#-test */ + //lang=csharp + """ + /// + /// Docking position for a view. + /// + """; /// /// If true, the last child is expanded to fill the remaining space (default: true). @@ -37,22 +38,6 @@ public partial class DockLayout : Layout, IDockLayout public partial double VerticalSpacing { get; set; } = DockLayoutDefaults.VerticalSpacing; - /// - /// Gets the docking position for a view. - /// - public static DockPosition GetDockPosition(BindableObject view) - { - return (DockPosition)view.GetValue(DockPositionProperty); - } - - /// - /// Sets the docking position for a view. - /// - public static void SetDockPosition(BindableObject view, DockPosition value) - { - view.SetValue(DockPositionProperty, value); - } - /// public DockPosition GetDockPosition(IView view) { diff --git a/src/CommunityToolkit.Maui/Layouts/StateContainer/StateContainer.shared.cs b/src/CommunityToolkit.Maui/Layouts/StateContainer/StateContainer.shared.cs index 841f0c9c8b..51f94983a9 100644 --- a/src/CommunityToolkit.Maui/Layouts/StateContainer/StateContainer.shared.cs +++ b/src/CommunityToolkit.Maui/Layouts/StateContainer/StateContainer.shared.cs @@ -4,64 +4,26 @@ /// The attached properties enable any inheriting element to become state-aware. /// States are defined in the with attached properties. /// -public static class StateContainer +[AttachedBindableProperty>(stateViewsPropertyName, DefaultValueCreatorMethodName = nameof(CreateDefaultStateViewsProperty))] +[AttachedBindableProperty(currentStatePropertyName, IsNullable = true, DefaultValue = StateContainerDefaults.CurrentState, PropertyChangingMethodName = nameof(OnCurrentStateChanging), BindablePropertyXmlDocumentation = currentStateBindablePropertyXmlDocumentation)] +[AttachedBindableProperty(canStateChangePropertyName, DefaultValue = StateContainerDefaults.CanStateChange, DefaultBindingMode = BindingMode.OneWayToSource, SetterAccessibility = AccessModifier.Private)] +[AttachedBindableProperty(layoutControllerPropertyName, DefaultValueCreatorMethodName = nameof(ContainerControllerCreator), BindablePropertyAccessibility = AccessModifier.Internal, GetterAccessibility = AccessModifier.Internal, SetterAccessibility = AccessModifier.None)] +public static partial class StateContainer { const string stateViewsPropertyName = "StateViews"; const string currentStatePropertyName = "CurrentState"; const string canStateChangePropertyName = "CanStateChange"; const string layoutControllerPropertyName = "LayoutController"; - internal static readonly BindableProperty LayoutControllerProperty - = BindableProperty.CreateAttached(layoutControllerPropertyName, typeof(StateContainerController), typeof(StateContainer), default(StateContainerController), defaultValueCreator: ContainerControllerCreator); - - /// - /// Backing for the and methods. - /// - public static readonly BindableProperty StateViewsProperty - = BindableProperty.CreateAttached(stateViewsPropertyName, typeof(IList), typeof(StateContainer), default(IList), defaultValueCreator: _ => new List()); - - /// - /// Backing for the and methods. - /// To ensure does not throw a due to active animations, first verify is before changing - /// - public static readonly BindableProperty CurrentStateProperty - = BindableProperty.CreateAttached(currentStatePropertyName, typeof(string), typeof(StateContainer), default(string), propertyChanging: OnCurrentStateChanging); - - /// - /// Backing for the method. - /// - public static readonly BindableProperty CanStateChangeProperty - = BindableProperty.CreateAttached(canStateChangePropertyName, typeof(bool), typeof(StateContainer), true, BindingMode.OneWayToSource); - - /// - /// Set the StateViews property - /// - public static void SetStateViews(BindableObject b, IList value) - => b.SetValue(StateViewsProperty, value); - - /// - /// Get the CanStateChange property - /// - public static bool GetCanStateChange(BindableObject b) - => (bool)b.GetValue(CanStateChangeProperty); - - /// - /// Get the StateViews property - /// - public static IList GetStateViews(BindableObject b) - => (IList)b.GetValue(StateViewsProperty); - - /// - /// Set the CurrentState property - /// - public static void SetCurrentState(BindableObject b, string? value) - => b.SetValue(CurrentStateProperty, value); - - /// - /// Get the CurrentState property - /// - public static string GetCurrentState(BindableObject b) - => (string)b.GetValue(CurrentStateProperty); + const string currentStateBindablePropertyXmlDocumentation = + /* language=C#-test */ + //lang=csharp + """ + /// + /// Backing for the and methods. + /// To ensure does not throw a due to active animations, first verify is before changing + /// + """; /// /// Change state with custom animation. @@ -82,7 +44,7 @@ public static async Task ChangeStateWithAnimation( ValidateCanStateChange(bindable); SetCanStateChange(bindable, false); - var layout = GetContainerController(bindable).GetLayout(); + var layout = GetLayoutController(bindable).GetLayout(); try { @@ -130,7 +92,7 @@ public static async Task ChangeStateWithAnimation( ValidateCanStateChange(bindable); SetCanStateChange(bindable, false); - var layout = GetContainerController(bindable).GetLayout(); + var layout = GetLayoutController(bindable).GetLayout(); try { @@ -145,7 +107,6 @@ public static async Task ChangeStateWithAnimation( { await afterStateChange.Invoke(layout, cancellationToken).WaitAsync(cancellationToken); } - } finally { @@ -163,7 +124,7 @@ public static async Task ChangeStateWithAnimation(BindableObject bindable, strin ValidateCanStateChange(bindable); SetCanStateChange(bindable, false); - var layout = GetContainerController(bindable).GetLayout(); + var layout = GetLayoutController(bindable).GetLayout(); try { @@ -186,12 +147,6 @@ public static async Task ChangeStateWithAnimation(BindableObject bindable, strin } } - internal static StateContainerController GetContainerController(BindableObject b) => - (StateContainerController)b.GetValue(LayoutControllerProperty); - - static void SetCanStateChange(BindableObject b, bool value) - => b.SetValue(CanStateChangeProperty, value); - static void OnCurrentStateChanging(BindableObject bindable, object oldValue, object newValue) { if (oldValue == newValue) @@ -208,11 +163,11 @@ static void ChangeState(BindableObject bindable, string? state) { if (string.IsNullOrEmpty(state)) { - GetContainerController(bindable).SwitchToContent(); + GetLayoutController(bindable).SwitchToContent(); } else { - GetContainerController(bindable).SwitchToState(state); + GetLayoutController(bindable).SwitchToState(state); } } @@ -236,19 +191,15 @@ static void ValidateCanStateChange(in BindableObject bindable) throw new StateContainerException($"{canStateChangePropertyName} is false. {currentStatePropertyName} cannot be changed while a state change is in progress. To avoid this exception, first verify {canStateChangePropertyName} is {true} before changing {currentStatePropertyName}."); } } + + static IList CreateDefaultStateViewsProperty(BindableObject bindable) => StateContainerDefaults.StateViews; } /// /// An thrown when enters an invalid state /// -public sealed class StateContainerException : InvalidOperationException -{ - /// - /// Constructor for - /// - /// - public StateContainerException(string message) : base(message) - { - - } -} \ No newline at end of file +/// +/// Constructor for +/// +/// +public sealed class StateContainerException(string message) : InvalidOperationException(message); \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Layouts/StateContainer/StateView.shared.cs b/src/CommunityToolkit.Maui/Layouts/StateContainer/StateView.shared.cs index 41ac35d559..1dbafa309b 100644 --- a/src/CommunityToolkit.Maui/Layouts/StateContainer/StateView.shared.cs +++ b/src/CommunityToolkit.Maui/Layouts/StateContainer/StateView.shared.cs @@ -1,28 +1,11 @@ -namespace CommunityToolkit.Maui.Layouts; +using CommunityToolkit.Maui.Core; + +namespace CommunityToolkit.Maui.Layouts; /// /// The attached properties can be used on any inheriting element to represent a state inside a . /// -public static class StateView +[AttachedBindableProperty("StateKey", DefaultValue = StateViewDefaults.StateKey)] +public static partial class StateView { - /// - /// Backing BindableProperty for the and methods. - /// - public static readonly BindableProperty StateKeyProperty - = BindableProperty.CreateAttached("StateKey", typeof(string), typeof(IView), string.Empty); - - /// - /// Set the StateKey property - /// - /// The that will set the property. - /// The StateKey value for this . - public static void SetStateKey(BindableObject b, string value) - => b.SetValue(StateKeyProperty, value); - - /// - /// Get the StateKey property - /// - /// - public static string GetStateKey(BindableObject b) - => (string)b.GetValue(StateKeyProperty); } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.cs b/src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs similarity index 56% rename from src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.cs rename to src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs index 017c3d3704..546d10c425 100644 --- a/src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.cs +++ b/src/CommunityToolkit.Maui/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs @@ -9,38 +9,10 @@ namespace CommunityToolkit.Maui.PlatformConfiguration.AndroidSpecific; /// Provides platform-specific configuration properties for the Android navigation bar. /// [SupportedOSPlatform("Android23.0")] +[AttachedBindableProperty("Color", DefaultValueCreatorMethodName = nameof(CreateColorDefaultValue))] +[AttachedBindableProperty("Style", DefaultValue = NavigationBarStyle.Default)] public static partial class NavigationBar { - /// - /// Identifies the Color bindable property. - /// - public static readonly BindableProperty ColorProperty = BindableProperty.CreateAttached("Color", typeof(Color), typeof(NavigationBar), Colors.Transparent); - - /// - /// Identifies the Style bindable property. - /// - public static readonly BindableProperty StyleProperty = BindableProperty.CreateAttached("Style", typeof(NavigationBarStyle), typeof(NavigationBar), NavigationBarStyle.Default); - - /// - /// Gets the color of the navigation bar. - /// - /// The bindable object. - /// The color of the navigation bar. - public static Color GetColor(BindableObject element) - { - return (Color)element.GetValue(ColorProperty); - } - - /// - /// Sets the color of the navigation bar. - /// - /// The bindable object. - /// The color to set. - public static void SetColor(BindableObject element, Color value) - { - element.SetValue(ColorProperty, value); - } - /// /// Sets the color of the navigation bar. /// @@ -63,26 +35,6 @@ public static Color GetColor(this IPlatformElementConfiguration - /// Sets the style of the navigation bar. - /// - /// The bindable object. - /// The style to set. - public static void SetStyle(BindableObject element, NavigationBarStyle value) - { - element.SetValue(StyleProperty, value); - } - - /// - /// Gets the style of the navigation bar. - /// - /// The bindable object. - /// The style of the navigation bar. - public static NavigationBarStyle GetStyle(BindableObject element) - { - return (NavigationBarStyle)element.GetValue(StyleProperty); - } - /// /// Sets the style of the navigation bar. /// @@ -113,4 +65,6 @@ internal static partial void RemapForControls() } #endif + + static Color CreateColorDefaultValue(BindableObject bindable) => Colors.Transparent; } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Primitives/StateContainerDefaults.shared.cs b/src/CommunityToolkit.Maui/Primitives/StateContainerDefaults.shared.cs new file mode 100644 index 0000000000..bb3a6ce3b4 --- /dev/null +++ b/src/CommunityToolkit.Maui/Primitives/StateContainerDefaults.shared.cs @@ -0,0 +1,8 @@ +namespace CommunityToolkit.Maui; + +static class StateContainerDefaults +{ + public const string? CurrentState = null; + public const bool CanStateChange = true; + public static IList StateViews { get; } = []; +} \ No newline at end of file