diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
index 83afc96b..656a7697 100644
--- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
+++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
@@ -60,6 +60,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
// Retrieve the properties passed by the analyzer
string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName];
+ string? defaultValueTypeFullyQualifiedMetadataName = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValueTypeFullyQualifiedMetadataNamePropertyName];
SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
@@ -80,7 +81,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
root,
propertyDeclaration,
fieldDeclaration,
- defaultValue),
+ defaultValue,
+ defaultValueTypeFullyQualifiedMetadataName),
equivalenceKey: "Use a partial property"),
diagnostic);
}
@@ -122,12 +124,14 @@ private static bool TryGetGeneratedDependencyPropertyAttributeList(
/// The original document being fixed.
/// The instance for the current compilation.
/// The expression for the default value of the property, if present
+ /// The fully qualified metadata name of the default value, if present.
/// The updated attribute syntax.
private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeList(
Document document,
SemanticModel semanticModel,
AttributeListSyntax generatedDependencyPropertyAttributeList,
- string? defaultValueExpression)
+ string? defaultValueExpression,
+ string? defaultValueTypeFullyQualifiedMetadataName)
{
// If we do have a default value expression, set it in the attribute.
// We extract the generated attribute so we can add the new argument.
@@ -139,18 +143,19 @@ private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeLis
// Special case values which are simple enum member accesses, like 'global::Windows.UI.Xaml.Visibility.Collapsed'
if (parsedExpression is MemberAccessExpressionSyntax { Expression: { } expressionSyntax, Name: IdentifierNameSyntax { Identifier.Text: { } memberName } })
{
- string fullyQualifiedTypeName = expressionSyntax.ToFullString();
+ string fullyQualifiedMetadataName = defaultValueTypeFullyQualifiedMetadataName ?? expressionSyntax.ToFullString();
- // Ensure we strip the global prefix, if present (it should always be present)
- if (fullyQualifiedTypeName.StartsWith("global::"))
+ // Ensure we strip the global prefix, if present (it should always be present if we didn't have a metadata name).
+ // Note that using the fully qualified type name is just a fallback, as we should always have the metadata name.
+ if (fullyQualifiedMetadataName.StartsWith("global::"))
{
- fullyQualifiedTypeName = fullyQualifiedTypeName["global::".Length..];
+ fullyQualifiedMetadataName = fullyQualifiedMetadataName["global::".Length..];
}
// Try to resolve the attribute type, if present. This API takes a fully qualified metadata name, not
// a fully qualified type name. However, for virtually all cases for enum types, the two should match.
// That is, they will be the same if the type is not nested, and not generic, which is what we expect.
- if (semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedTypeName) is INamedTypeSymbol enumTypeSymbol)
+ if (semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) is INamedTypeSymbol enumTypeSymbol)
{
SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(document);
@@ -190,6 +195,7 @@ private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeLis
/// The for the property being updated.
/// The for the declared property to remove.
/// The expression for the default value of the property, if present
+ /// The fully qualified metadata name of the default value, if present.
/// An updated document with the applied code fix, and being replaced with a partial property.
private static async Task ConvertToPartialProperty(
Document document,
@@ -197,7 +203,8 @@ private static async Task ConvertToPartialProperty(
SyntaxNode root,
PropertyDeclarationSyntax propertyDeclaration,
FieldDeclarationSyntax fieldDeclaration,
- string? defaultValueExpression)
+ string? defaultValueExpression,
+ string? defaultValueTypeFullyQualifiedMetadataName)
{
await Task.CompletedTask;
@@ -217,7 +224,8 @@ private static async Task ConvertToPartialProperty(
fieldDeclaration,
generatedDependencyPropertyAttributeList,
syntaxEditor,
- defaultValueExpression);
+ defaultValueExpression,
+ defaultValueTypeFullyQualifiedMetadataName);
RemoveLeftoverLeadingEndOfLines([fieldDeclaration], syntaxEditor);
@@ -235,6 +243,7 @@ private static async Task ConvertToPartialProperty(
/// The with the attribute to add.
/// The instance to use.
/// The expression for the default value of the property, if present
+ /// The fully qualified metadata name of the default value, if present.
/// An updated document with the applied code fix, and being replaced with a partial property.
private static void ConvertToPartialProperty(
Document document,
@@ -243,7 +252,8 @@ private static void ConvertToPartialProperty(
FieldDeclarationSyntax fieldDeclaration,
AttributeListSyntax generatedDependencyPropertyAttributeList,
SyntaxEditor syntaxEditor,
- string? defaultValueExpression)
+ string? defaultValueExpression,
+ string? defaultValueTypeFullyQualifiedMetadataName)
{
// Replace the property with the partial property using the attribute. Note that it's important to use the
// lambda 'ReplaceNode' overload here, rather than creating a modifier property declaration syntax node and
@@ -258,7 +268,8 @@ private static void ConvertToPartialProperty(
document,
semanticModel,
generatedDependencyPropertyAttributeList,
- defaultValueExpression);
+ defaultValueExpression,
+ defaultValueTypeFullyQualifiedMetadataName);
// Start setting up the updated attribute lists
SyntaxList attributeLists = propertyDeclaration.AttributeLists;
@@ -459,6 +470,8 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider
// Retrieve the properties passed by the analyzer
string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName];
+ string? defaultValueTypeFullyQualifiedMetadataName = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValueTypeFullyQualifiedMetadataNamePropertyName];
+
ConvertToPartialProperty(
document,
@@ -467,7 +480,8 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider
fieldDeclaration,
generatedDependencyPropertyAttributeList,
syntaxEditor,
- defaultValue);
+ defaultValue,
+ defaultValueTypeFullyQualifiedMetadataName);
fieldDeclarations.Add(fieldDeclaration);
}
diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs
index b7e9193d..b0468b88 100644
--- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs
+++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs
@@ -57,6 +57,11 @@ public sealed class UseGeneratedDependencyPropertyOnManualPropertyAnalyzer : Dia
///
public const string DefaultValuePropertyName = "DefaultValue";
+ ///
+ /// The property name for the fully qualified metadata name of the default value, if present.
+ ///
+ public const string DefaultValueTypeFullyQualifiedMetadataNamePropertyName = "DefaultValueTypeFullyQualifiedMetadataName";
+
///
public override ImmutableArray SupportedDiagnostics { get; } = [UseGeneratedDependencyPropertyForManualProperty];
@@ -449,19 +454,27 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
}
else if (TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue))
{
- // We have found a valid constant. As an optimization, we check whether the constant was the value
- // of some projected built-in WinRT enum type (ie. not any user-defined enum type). If that is the
- // case, the XAML infrastructure can default that values automatically, meaning we can skip the
- // overhead of instantiating a 'PropertyMetadata' instance in code, and marshalling default value.
- if (conversionOperation.Operand.Type is { TypeKind: TypeKind.Enum } operandType &&
- operandType.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)))
+ // We have found a valid constant. If it's an enum type, we have a couple special cases to handle.
+ if (conversionOperation.Operand.Type is { TypeKind: TypeKind.Enum } operandType)
{
- // Before actually enabling the optimization, validate that the default value is actually
- // the same as the default value of the enum (ie. the value of its first declared field).
- if (operandType.TryGetDefaultValueForEnumType(out object? defaultValue) &&
- conversionOperation.Operand.ConstantValue.Value == defaultValue)
+ // As an optimization, we check whether the constant was the value
+ // of some projected built-in WinRT enum type (ie. not any user-defined enum type). If that is the
+ // case, the XAML infrastructure can default that values automatically, meaning we can skip the
+ // overhead of instantiating a 'PropertyMetadata' instance in code, and marshalling default value.
+ if (operandType.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)))
+ {
+ // Before actually enabling the optimization, validate that the default value is actually
+ // the same as the default value of the enum (ie. the value of its first declared field).
+ if (operandType.TryGetDefaultValueForEnumType(out object? defaultValue) &&
+ conversionOperation.Operand.ConstantValue.Value == defaultValue)
+ {
+ fieldFlags.DefaultValue = null;
+ }
+ }
+ else if (operandType.ContainingType is not null)
{
- fieldFlags.DefaultValue = null;
+ // If the enum is nested, we need to also
+ fieldFlags.DefaultValueTypeFullyQualifiedMetadataName = operandType.GetFullyQualifiedMetadataName();
}
}
}
@@ -552,7 +565,9 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
UseGeneratedDependencyPropertyForManualProperty,
pair.Key.Locations.FirstOrDefault(),
[fieldLocation],
- ImmutableDictionary.Create().Add(DefaultValuePropertyName, fieldFlags.DefaultValue?.ToString()),
+ ImmutableDictionary.Create()
+ .Add(DefaultValuePropertyName, fieldFlags.DefaultValue?.ToString())
+ .Add(DefaultValueTypeFullyQualifiedMetadataNamePropertyName, fieldFlags.DefaultValueTypeFullyQualifiedMetadataName),
pair.Key));
}
}
@@ -573,6 +588,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
fieldFlags.PropertyName = null;
fieldFlags.PropertyType = null;
fieldFlags.DefaultValue = null;
+ fieldFlags.DefaultValueTypeFullyQualifiedMetadataName = null;
fieldFlags.FieldLocation = null;
fieldFlagsStack.Push(fieldFlags);
@@ -647,6 +663,11 @@ private sealed class FieldFlags
///
public TypedConstantInfo? DefaultValue;
+ ///
+ /// The fully qualified metadata name of the default value, if needed.
+ ///
+ public string? DefaultValueTypeFullyQualifiedMetadataName;
+
///
/// The location of the target field being initialized.
///
diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
index b284bb9f..5dc85149 100644
--- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
+++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs
@@ -373,6 +373,124 @@ public partial class MyControl : Control
await test.RunAsync();
}
+ [TestMethod]
+ public async Task SimpleProperty_WithExplicitValue_NestedEnumType()
+ {
+ const string original = """
+ using Windows.UI.Xaml;
+ using Windows.UI.Xaml.Controls;
+
+ namespace MyApp;
+
+ public partial class MyControl : Control
+ {
+ public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
+ name: "Name",
+ propertyType: typeof(MyContainingType.MyEnum),
+ ownerType: typeof(MyControl),
+ typeMetadata: new PropertyMetadata(MyContainingType.MyEnum.B));
+
+ public MyContainingType.MyEnum [|Name|]
+ {
+ get => (MyContainingType.MyEnum)GetValue(NameProperty);
+ set => SetValue(NameProperty, value);
+ }
+ }
+
+ public class MyContainingType
+ {
+ public enum MyEnum { A, B }
+ }
+ """;
+
+ const string @fixed = """
+ using CommunityToolkit.WinUI;
+ using Windows.UI.Xaml;
+ using Windows.UI.Xaml.Controls;
+
+ namespace MyApp;
+
+ public partial class MyControl : Control
+ {
+ [GeneratedDependencyProperty(DefaultValue = MyContainingType.MyEnum.B)]
+ public partial MyContainingType.MyEnum {|CS9248:Name|} { get; set; }
+ }
+
+ public class MyContainingType
+ {
+ public enum MyEnum { A, B }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed
+ };
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleProperty_WithExplicitValue_NestedEnumType_WithUsingStatic()
+ {
+ const string original = """
+ using Windows.UI.Xaml;
+ using Windows.UI.Xaml.Controls;
+ using static MyApp.MyContainingType;
+
+ namespace MyApp;
+
+ public partial class MyControl : Control
+ {
+ public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
+ name: "Name",
+ propertyType: typeof(MyEnum),
+ ownerType: typeof(MyControl),
+ typeMetadata: new PropertyMetadata(MyEnum.B));
+
+ public MyEnum [|Name|]
+ {
+ get => (MyEnum)GetValue(NameProperty);
+ set => SetValue(NameProperty, value);
+ }
+ }
+
+ public class MyContainingType
+ {
+ public enum MyEnum { A, B }
+ }
+ """;
+
+ const string @fixed = """
+ using CommunityToolkit.WinUI;
+ using Windows.UI.Xaml;
+ using Windows.UI.Xaml.Controls;
+ using static MyApp.MyContainingType;
+
+ namespace MyApp;
+
+ public partial class MyControl : Control
+ {
+ [GeneratedDependencyProperty(DefaultValue = MyEnum.B)]
+ public partial MyEnum {|CS9248:Name|} { get; set; }
+ }
+
+ public class MyContainingType
+ {
+ public enum MyEnum { A, B }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed
+ };
+
+ await test.RunAsync();
+ }
+
[TestMethod]
public async Task SimpleProperty_WithExplicitValue_NotDefault_AddsNamespace()
{