Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
02716dd
Update IconTintColorBehavior.shared.cs
TheCodeTraveler Nov 6, 2025
65a7094
Update ImageTouchBehavior.shared.cs
TheCodeTraveler Nov 6, 2025
559dd63
Add `new` keyword functionality to `BindablePropertyAttributeSourceGe…
TheCodeTraveler Nov 5, 2025
fc41062
Update BindablePropertyAttributeSourceGenerator.cs
TheCodeTraveler Nov 6, 2025
7dc243c
Update StatusBarBehavior
TheCodeTraveler Nov 6, 2025
e55fe00
Properly Sanitize ReturnType and DeclaringType
TheCodeTraveler Nov 6, 2025
969836d
Return correct string for boolean operators
TheCodeTraveler Nov 6, 2025
7f5b206
Update StatusBarBehavior
TheCodeTraveler Nov 6, 2025
543b7d2
Update TouchBehavior
TheCodeTraveler Nov 6, 2025
7e17abe
Return correct string for boolean operators
TheCodeTraveler Nov 6, 2025
3f2142c
Update Expander.shared.cs
TheCodeTraveler Nov 6, 2025
618ec37
Add `GetEnumValueByNameAsString`
TheCodeTraveler Nov 6, 2025
ff12f59
Fix`GetEnumValueByNameAsString`
TheCodeTraveler Nov 6, 2025
b9dc3bb
Finish TouchBehavior
TheCodeTraveler Nov 6, 2025
ef4d904
Finish Behaviors
TheCodeTraveler Nov 6, 2025
8020d16
Finish Behaviors
TheCodeTraveler Nov 7, 2025
4d2dc30
Fix Declaring Type
TheCodeTraveler Nov 7, 2025
187d9d8
Fix ReturnType
TheCodeTraveler Nov 7, 2025
33523d6
Fix Enum Support
TheCodeTraveler Nov 7, 2025
28bbb8e
Add `PolySharp`
TheCodeTraveler Nov 7, 2025
3dd790d
Add `BindablePropertyModel .BindablePropertyName `
TheCodeTraveler Nov 7, 2025
81bed50
Fix Enum Support
TheCodeTraveler Nov 7, 2025
6928dc4
Add `BindablePropertyModel .BindablePropertyName `
TheCodeTraveler Nov 7, 2025
401214e
Ensure Floating Point Types include a decimal
TheCodeTraveler Nov 7, 2025
efc9d67
Fix TouchBehaviorTests
TheCodeTraveler Nov 7, 2025
b5bb11a
Fix TouchBehaviorTests
TheCodeTraveler Nov 7, 2025
523e58b
Add `GetNamedTypeArgumentsAttributeValueByNameAsCastedString` and `Ge…
TheCodeTraveler Nov 7, 2025
b7e1572
Fix ProgressBar
TheCodeTraveler Nov 7, 2025
b642931
Merge branch 'main' into Use-`-BindableProperty]`-for-Behaviors
TheCodeTraveler Nov 7, 2025
7a319d3
`dotnet format`
TheCodeTraveler Nov 7, 2025
fff02d8
Merge branch 'Use-`-BindableProperty]`-for-Behaviors' of https://gith…
TheCodeTraveler Nov 7, 2025
3af56cd
`dotnet format`
TheCodeTraveler Nov 7, 2025
2193584
Add `CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.csproj`
TheCodeTraveler Nov 9, 2025
cac3db8
Merge branch 'main' into Use-`-BindableProperty]`-for-Behaviors
TheCodeTraveler Nov 16, 2025
d111631
Update to Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.NET100
TheCodeTraveler Nov 16, 2025
07df7df
Update CommunityToolkit.Maui.slnx
TheCodeTraveler Nov 16, 2025
3558289
Merge branch 'main' into Use-`-BindableProperty]`-for-Behaviors
TheCodeTraveler Nov 17, 2025
f82a8fb
Add ReferenceAssembliesExtensions extension
TheCodeTraveler Nov 20, 2025
bff5c71
Update ReferenceAssembliesExtensions.cs
TheCodeTraveler Nov 20, 2025
6f35142
`dotnet format`
TheCodeTraveler Nov 20, 2025
e434acd
Create ReferenceAssembliesExtensions.cs
TheCodeTraveler Nov 20, 2025
4ad8da5
Fix `Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.NET100`
TheCodeTraveler Nov 20, 2025
2c7f400
Merge branch 'main' into Use-`-BindableProperty]`-for-Behaviors
TheCodeTraveler Nov 20, 2025
6eb998b
Update NuGet Packages
TheCodeTraveler Nov 20, 2025
1016a7d
Merge branch 'main' into Use-`-BindableProperty]`-for-Behaviors
TheCodeTraveler Nov 21, 2025
b3d81ca
Add `global::`
TheCodeTraveler Nov 21, 2025
01b32b9
Add `global::`
TheCodeTraveler Nov 21, 2025
f59ad5c
GenerateBindableProperty_SimpleExample_GeneratesCorrectCode Passing
TheCodeTraveler Nov 21, 2025
0ee552d
Add support for `SpecialType.System_String`
TheCodeTraveler Nov 21, 2025
75629f3
Finish BindablePropertyAttributeSourceGeneratorTests
TheCodeTraveler Nov 21, 2025
b35112a
Add Support for Keywords
TheCodeTraveler Nov 21, 2025
e640029
Update Namings
TheCodeTraveler Nov 21, 2025
83a0dc3
Update BindablePropertyAttributeSourceGenerator.cs
TheCodeTraveler Nov 21, 2025
29e7ecd
Add support for global namespaces
TheCodeTraveler Nov 21, 2025
0480ff0
Add support for global namespaces
TheCodeTraveler Nov 21, 2025
d787b32
`dotnet format`
TheCodeTraveler Nov 21, 2025
05bb99f
Finish Edge Cases
TheCodeTraveler Nov 21, 2025
fa566d9
Use constants for class and namespaces
TheCodeTraveler Nov 21, 2025
7b4ac1a
Complete GenerateBindableProperty_ComplexInheritanceScenario_Generate…
TheCodeTraveler Nov 21, 2025
1a4f893
Complete GenerateBindableProperty_MultipleClassesInSameFile_Generates…
TheCodeTraveler Nov 21, 2025
af89368
Complete GenerateBindableProperty_WithCustomTypes_GeneratesCorrectCode
TheCodeTraveler Nov 21, 2025
05fad06
Update IntegrationTests.cs
TheCodeTraveler Nov 21, 2025
ea64f86
Add support for nested classes
TheCodeTraveler Nov 21, 2025
ade68fe
Add support for nested classes
TheCodeTraveler Nov 21, 2025
4f92ecf
Add support for genericclasses
TheCodeTraveler Nov 21, 2025
dc28b6d
Update dotnet-build.yml
TheCodeTraveler Nov 21, 2025
c44a7c2
Update csproj
TheCodeTraveler Nov 21, 2025
d79bcb8
Update csproj
TheCodeTraveler Nov 21, 2025
83e1e96
Update csproj
TheCodeTraveler Nov 21, 2025
dbe75e2
Use `Path.Combine` for macos support
TheCodeTraveler Nov 21, 2025
c30a0f8
Fix support for nested classes
TheCodeTraveler Nov 21, 2025
1f97d27
Update BindablePropertyAttributeSourceGenerator.cs
TheCodeTraveler Nov 21, 2025
569213a
Revert "Update BindablePropertyAttributeSourceGenerator.cs"
TheCodeTraveler Nov 21, 2025
d5f68e7
Normalize Line Endings in Unit Test for Windows vs macOS
TheCodeTraveler Nov 21, 2025
bba4bc8
Revert "Normalize Line Endings in Unit Test for Windows vs macOS"
TheCodeTraveler Nov 21, 2025
14e8710
Only run Internal Source Generator Unit Tests on Windows
TheCodeTraveler Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CommunityToolkit.Maui.Core;

static class ProgressBarAnimationBehaviorDefaults
{
public const double Progress = 0.0;
public const uint Length = 500;
public static Easing Easing { get; } = Easing.Linear;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CommunityToolkit.Maui.Core;

static class UserStoppedTypingBehaviorDefaults
{
public const int StoppedTypingTimeThreshold = 1000;
public const int MinimumLengthThreshold = 0;
public const bool ShouldDismissKeyboardAutomatically = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;

Expand All @@ -17,47 +18,45 @@ public class BindablePropertyAttributeSourceGenerator : IIncrementalGenerator
static readonly SemanticValues emptySemanticValues = new(default, []);

const string bpFullName = "global::Microsoft.Maui.Controls.BindableProperty";
const string bindingModeFullName = "global::Microsoft.Maui.Controls.";

const string bpAttribute =
/* language=C#-test */
//lang=csharp
$$"""
// <auto-generated>
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
#pragma warning disable
#nullable enable
namespace CommunityToolkit.Maui;

[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
sealed partial class BindablePropertyAttribute : Attribute
{
public string? PropertyName { get; }
public Type? DeclaringType { get; set; }
public object? DefaultValue { get; set; }
public string DefaultBindingMode { get; set; } = string.Empty;
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 BindablePropertyAttribute(string propertyName)
{
PropertyName = propertyName;
}

public BindablePropertyAttribute()
{
}
}
""";
// <auto-generated>
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator

#pragma warning disable
#nullable enable
namespace CommunityToolkit.Maui;

[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
sealed partial class BindablePropertyAttribute : Attribute
{
public string? PropertyName { get; }
public Type? DeclaringType { get; set; }
public object? DefaultValue { get; set; }
public 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 BindablePropertyAttribute(string propertyName)
{
PropertyName = propertyName;
}

public BindablePropertyAttribute()
{
}
}
""";

public void Initialize(IncrementalGeneratorInitializationContext context)
{

#if DEBUG

if (!Debugger.IsAttached)
Expand All @@ -82,8 +81,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
static void ExecuteAllValues(SourceProductionContext context, ImmutableArray<SemanticValues> semanticValues)
{
var groupedValues = semanticValues
.GroupBy(static sv => (sv.ClassInformation.ClassName, sv.ClassInformation.ContainingNamespace))
.ToDictionary(static d => d.Key, static d => d.ToArray());
.GroupBy(static sv => (sv.ClassInformation.ClassName, sv.ClassInformation.ContainingNamespace))
.ToDictionary(static d => d.Key, static d => d.ToArray());

foreach (var keyValuePair in groupedValues)
{
Expand Down Expand Up @@ -117,6 +116,10 @@ static string GenerateSource(SemanticValues value)
$$"""

// <auto-generated>
// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator

#pragma warning disable
#nullable enable
// Test2 : {{DateTime.Now}}

namespace {{value.ClassInformation.ContainingNamespace}};
Expand All @@ -137,6 +140,9 @@ namespace {{value.ClassInformation.ContainingNamespace}};

static void GenerateBindableProperty(StringBuilder sb, BindablePropertyModel info)
{
// Sanitize the Return Type and Declaring Type because Nullable Reference Types cannot be used in the `typeof()` operator
var sanitizedReturnType = GetNonNullableType(info.ReturnType);

/*
/// <summary>
/// Backing BindableProperty for the <see cref="PropertyName"/> property.
Expand All @@ -150,10 +156,10 @@ static void GenerateBindableProperty(StringBuilder sb, BindablePropertyModel inf
sb.AppendLine($"public static readonly {bpFullName} {info.PropertyName}Property = ")
.Append($"{bpFullName}.Create(")
.Append($"\"{info.PropertyName}\", ")
.Append($"typeof({info.ReturnType}), ")
.Append($"typeof({sanitizedReturnType}), ")
.Append($"typeof({info.DeclaringType}), ")
.Append($"{info.DefaultValue}, ")
.Append($"{bindingModeFullName}{info.DefaultBindingMode}, ")
.Append($"{info.DefaultBindingMode}, ")
.Append($"{info.ValidateValueMethodName}, ")
.Append($"{info.PropertyChangedMethodName}, ")
.Append($"{info.PropertyChangingMethodName}, ")
Expand All @@ -174,8 +180,7 @@ static void GenerateProperty(StringBuilder sb, BindablePropertyModel info)
// set => SetValue(TextProperty, value);
// }
//

sb.AppendLine($"public partial {info.ReturnType} {info.PropertyName}")
sb.AppendLine($"public {info.NewKeywordText}partial {info.ReturnType} {info.PropertyName}")
.AppendLine("{")
.Append("get => (")
.Append(info.ReturnType)
Expand All @@ -191,7 +196,7 @@ static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context,
{
var propertyDeclarationSyntax = Unsafe.As<PropertyDeclarationSyntax>(context.TargetNode);
var semanticModel = context.SemanticModel;
var propertySymbol = (IPropertySymbol?)semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax, cancellationToken);
var propertySymbol = (IPropertySymbol?)ModelExtensions.GetDeclaredSymbol(semanticModel, propertyDeclarationSyntax, cancellationToken);

if (propertySymbol is null)
{
Expand All @@ -207,13 +212,15 @@ static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context,

var bindablePropertyModels = new List<BindablePropertyModel>(context.Attributes.Length);

var doesContainNewKeyword = propertyDeclarationSyntax.Modifiers.Any(static x => x.IsKind(SyntaxKind.NewKeyword));

var attributeData = context.Attributes[0];
bindablePropertyModels.Add(CreateBindablePropertyModel(attributeData, propertySymbol.Type.ToDisplayString(), propertySymbol.Name, returnType));
bindablePropertyModels.Add(CreateBindablePropertyModel(attributeData, propertySymbol.ContainingType, propertySymbol.Name, returnType, doesContainNewKeyword));

return new(propertyInfo, bindablePropertyModels.ToImmutableArray());
}

static BindablePropertyModel CreateBindablePropertyModel(in AttributeData attributeData, in string declaringTypeString, in string defaultName, in ITypeSymbol returnType)
static BindablePropertyModel CreateBindablePropertyModel(in AttributeData attributeData, in INamedTypeSymbol declaringType, in string defaultName, in ITypeSymbol returnType, in bool doesContainNewKeyword)
{
if (attributeData.AttributeClass is null)
{
Expand All @@ -222,27 +229,34 @@ static BindablePropertyModel CreateBindablePropertyModel(in AttributeData attrib

var defaultValue = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultValue));
var coerceValueMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.CoerceValueMethodName));
var defaultBindingMode = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultBindingMode), "BindingMode.OneWay");
var defaultBindingMode = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultBindingMode), "Microsoft.Maui.Controls.BindingMode.OneWay");
var defaultValueCreatorMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultValueCreatorMethodName));
var declaringType = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DeclaringType), declaringTypeString);
var propertyChangedMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangedMethodName));
var propertyChangingMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangingMethodName));
var propertyName = attributeData.GetConstructorArgumentsAttributeValueByNameAsString(defaultName);
var validateValueMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.ValidateValueMethodName));
var newKeywordText = doesContainNewKeyword ? "new " : string.Empty;

return new BindablePropertyModel(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValueMethodName, propertyChangedMethodName, propertyChangingMethodName, coerceValueMethodName, defaultValueCreatorMethodName, newKeywordText);
}

static ITypeSymbol GetNonNullableType(ITypeSymbol typeSymbol)
{
// Check for Nullable<T>
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType && namedTypeSymbol.ConstructedFrom.SpecialType is SpecialType.System_Nullable_T)
{
return typeSymbol;
}

return new BindablePropertyModel
// Check for Nullable Reference Type
if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated)
{
CoerceValueMethodName = coerceValueMethodName,
DefaultBindingMode = defaultBindingMode,
DefaultValue = defaultValue,
DefaultValueCreatorMethodName = defaultValueCreatorMethodName,
DeclaringType = declaringType,
PropertyChangedMethodName = propertyChangedMethodName,
PropertyChangingMethodName = propertyChangingMethodName,
PropertyName = propertyName,
ReturnType = returnType,
ValidateValueMethodName = validateValueMethodName
};
// For reference types, NullableAnnotation.None indicates non-nullable.
return typeSymbol.WithNullableAnnotation(NullableAnnotation.None);
}

// Handle other cases if necessary (e.g., NotAnnotated, which might depend on context)
return typeSymbol;
}

static bool SyntaxPredicate(SyntaxNode node, CancellationToken cancellationToken) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ public static string GetNamedArgumentsAttributeValueByNameAsString(this Attribut
{
var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value;

// true.ToString() => "True" and false.ToString() => "False", but we want "true" and "false"
if (data.Kind is TypedConstantKind.Primitive && data.Type?.SpecialType is SpecialType.System_Boolean)
{
return data.Value is null ? placeholder : data.Value.ToString().ToLowerInvariant();
}

if (data.Kind is TypedConstantKind.Enum && data.Type is not null && data.Value is not null)
{
var members = data.Type.GetMembers();

return members[(int)data.Value].ToString();
}

return data.Value is null ? placeholder : data.Value.ToString();
}

Expand All @@ -26,6 +39,12 @@ public static string GetConstructorArgumentsAttributeValueByNameAsString(this At

var data = attribute.ConstructorArguments[0];

// true.ToString() => "True" and false.ToString() => "False", but we want "true" and "false"
if (data.Kind is TypedConstantKind.Primitive && data.Type?.SpecialType is SpecialType.System_Boolean)
{
return data.Value is null ? placeholder : data.Value.ToString().ToLowerInvariant();
}

return data.Value is null ? placeholder : data.Value.ToString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,7 @@

namespace CommunityToolkit.Maui.SourceGenerators.Internal.Models;

record BindablePropertyModel
{
public string PropertyName { get; set; } = string.Empty;
public ITypeSymbol? ReturnType { get; set; }
public string DeclaringType { get; set; } = string.Empty;
public string DefaultValue { get; set; } = string.Empty;
public string DefaultBindingMode { get; set; } = string.Empty;
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;
}
record BindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, ITypeSymbol DeclaringType, string DefaultValue, string DefaultBindingMode, string ValidateValueMethodName, string PropertyChangedMethodName, string PropertyChangingMethodName, string CoerceValueMethodName, string DefaultValueCreatorMethodName, string NewKeywordText);

record SemanticValues(ClassInformation ClassInformation, EquatableArray<BindablePropertyModel> BindableProperties);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"Profile 1": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\CommunityToolkit.Maui\\CommunityToolkit.Maui.csproj"
}
}
}
Loading
Loading