Skip to content

Commit

Permalink
Fix nested enum test failure
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 authored and Sergio0694 committed Dec 29, 2024
1 parent 922b016 commit dff25ef
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +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];
string? defaultValueTypeReferenceId = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValueTypeReferenceIdPropertyName];

SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

Expand All @@ -82,7 +82,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
propertyDeclaration,
fieldDeclaration,
defaultValue,
defaultValueTypeFullyQualifiedMetadataName),
defaultValueTypeReferenceId),
equivalenceKey: "Use a partial property"),
diagnostic);
}
Expand Down Expand Up @@ -131,7 +131,7 @@ private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeLis
SemanticModel semanticModel,
AttributeListSyntax generatedDependencyPropertyAttributeList,
string? defaultValueExpression,
string? defaultValueTypeFullyQualifiedMetadataName)
string? defaultValueTypeReferenceId)
{
// 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.
Expand All @@ -140,13 +140,34 @@ private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeLis
{
ExpressionSyntax parsedExpression = ParseExpression(defaultValueExpression);

// 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 } })
// Special case values which are simple enum member accesses, like 'global::Windows.UI.Xaml.Visibility.Collapsed'.
// We have two cases to handle, which require different logic to ensure the correct tree is always generated:
// - For nested enum types, we'll have a reference id. In this case, we manually insert annotations.
// - For normal enum member accesses, we resolve the type and then construct the tree from that expression.
if (defaultValueTypeReferenceId is not null)
{
string fullyQualifiedMetadataName = defaultValueTypeFullyQualifiedMetadataName ?? expressionSyntax.ToFullString();
// Here we're relying on the special 'SymbolId' annotation, which is used internally by Roslyn to track
// necessary imports for type expressions. We need to rely on this implementation detail here because
// there is no public API to correctly produce a tree for an enum member access on a nested type.
// This internal detail is one that many generators take a dependency on already, so it's safe-ish.
parsedExpression = parsedExpression.WithAdditionalAnnotations(
Simplifier.Annotation,
Simplifier.AddImportsAnnotation,
new SyntaxAnnotation("SymbolId", defaultValueTypeReferenceId));

SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(document);

// Create the attribute argument to insert
SyntaxNode attributeArgumentSyntax = syntaxGenerator.AttributeArgument("DefaultValue", parsedExpression);

// Actually add the argument to the existing attribute syntax
return (AttributeListSyntax)syntaxGenerator.AddAttributeArguments(generatedDependencyPropertyAttributeList, [attributeArgumentSyntax]);
}
else if (parsedExpression is MemberAccessExpressionSyntax { Expression: { } expressionSyntax, Name: IdentifierNameSyntax { Identifier.Text: { } memberName } })
{
string fullyQualifiedMetadataName = expressionSyntax.ToFullString();

// 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.
// Ensure we strip the global prefix, if present (it should always be present if we didn't have a metadata name)
if (fullyQualifiedMetadataName.StartsWith("global::"))
{
fullyQualifiedMetadataName = fullyQualifiedMetadataName["global::".Length..];
Expand All @@ -165,11 +186,10 @@ private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeLis
// Create the member access expression for the target enum type
SyntaxNode enumMemberAccessExpressionSyntax = syntaxGenerator.MemberAccessExpression(enumTypeSyntax, memberName);

// Create the attribute argument to insert
SyntaxNode attributeArgumentSyntax = syntaxGenerator.AttributeArgument("DefaultValue", enumMemberAccessExpressionSyntax);

// Actually add the argument to the existing attribute syntax
return (AttributeListSyntax)syntaxGenerator.AddAttributeArguments(generatedDependencyPropertyAttributeList, [attributeArgumentSyntax]);
// Create the attribute argument, like in the previous case
return (AttributeListSyntax)syntaxGenerator.AddAttributeArguments(
generatedDependencyPropertyAttributeList,
[syntaxGenerator.AttributeArgument("DefaultValue", enumMemberAccessExpressionSyntax)]);
}
}

Expand Down Expand Up @@ -204,7 +224,7 @@ private static async Task<Document> ConvertToPartialProperty(
PropertyDeclarationSyntax propertyDeclaration,
FieldDeclarationSyntax fieldDeclaration,
string? defaultValueExpression,
string? defaultValueTypeFullyQualifiedMetadataName)
string? defaultValueTypeReferenceId)
{
await Task.CompletedTask;

Expand All @@ -225,7 +245,7 @@ private static async Task<Document> ConvertToPartialProperty(
generatedDependencyPropertyAttributeList,
syntaxEditor,
defaultValueExpression,
defaultValueTypeFullyQualifiedMetadataName);
defaultValueTypeReferenceId);

RemoveLeftoverLeadingEndOfLines([fieldDeclaration], syntaxEditor);

Expand Down Expand Up @@ -253,7 +273,7 @@ private static void ConvertToPartialProperty(
AttributeListSyntax generatedDependencyPropertyAttributeList,
SyntaxEditor syntaxEditor,
string? defaultValueExpression,
string? defaultValueTypeFullyQualifiedMetadataName)
string? defaultValueTypeReferenceId)
{
// 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
Expand All @@ -269,7 +289,7 @@ private static void ConvertToPartialProperty(
semanticModel,
generatedDependencyPropertyAttributeList,
defaultValueExpression,
defaultValueTypeFullyQualifiedMetadataName);
defaultValueTypeReferenceId);

// Start setting up the updated attribute lists
SyntaxList<AttributeListSyntax> attributeLists = propertyDeclaration.AttributeLists;
Expand Down Expand Up @@ -470,7 +490,7 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider

// Retrieve the properties passed by the analyzer
string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName];
string? defaultValueTypeFullyQualifiedMetadataName = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValueTypeFullyQualifiedMetadataNamePropertyName];
string? defaultValueTypeFullyQualifiedMetadataName = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValueTypeReferenceIdPropertyName];


ConvertToPartialProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public sealed class UseGeneratedDependencyPropertyOnManualPropertyAnalyzer : Dia
/// <summary>
/// The property name for the fully qualified metadata name of the default value, if present.
/// </summary>
public const string DefaultValueTypeFullyQualifiedMetadataNamePropertyName = "DefaultValueTypeFullyQualifiedMetadataName";
public const string DefaultValueTypeReferenceIdPropertyName = "DefaultValueTypeReferenceId";

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [UseGeneratedDependencyPropertyForManualProperty];
Expand Down Expand Up @@ -473,8 +473,10 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
}
else if (operandType.ContainingType is not null)
{
// If the enum is nested, we need to also
fieldFlags.DefaultValueTypeFullyQualifiedMetadataName = operandType.GetFullyQualifiedMetadataName();
// If the enum is nested, we need to also track the type symbol specifically, as the fully qualified
// expression we'd be using otherwise would not be the same as the metadata name, and resolving the
// enum type symbol from that in the code fixer would fail. This is an edge case, but it can happen.
fieldFlags.DefaultValueTypeReferenceId = DocumentationCommentId.CreateReferenceId(operandType);
}
}
}
Expand Down Expand Up @@ -567,7 +569,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
[fieldLocation],
ImmutableDictionary.Create<string, string?>()
.Add(DefaultValuePropertyName, fieldFlags.DefaultValue?.ToString())
.Add(DefaultValueTypeFullyQualifiedMetadataNamePropertyName, fieldFlags.DefaultValueTypeFullyQualifiedMetadataName),
.Add(DefaultValueTypeReferenceIdPropertyName, fieldFlags.DefaultValueTypeReferenceId),
pair.Key));
}
}
Expand All @@ -588,7 +590,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla
fieldFlags.PropertyName = null;
fieldFlags.PropertyType = null;
fieldFlags.DefaultValue = null;
fieldFlags.DefaultValueTypeFullyQualifiedMetadataName = null;
fieldFlags.DefaultValueTypeReferenceId = null;
fieldFlags.FieldLocation = null;

fieldFlagsStack.Push(fieldFlags);
Expand Down Expand Up @@ -664,9 +666,9 @@ private sealed class FieldFlags
public TypedConstantInfo? DefaultValue;

/// <summary>
/// The fully qualified metadata name of the default value, if needed.
/// The documentation comment reference id for type of the default value, if needed.
/// </summary>
public string? DefaultValueTypeFullyQualifiedMetadataName;
public string? DefaultValueTypeReferenceId;

/// <summary>
/// The location of the target field being initialized.
Expand Down

0 comments on commit dff25ef

Please sign in to comment.