Skip to content

Commit e071ed2

Browse files
authored
Merge pull request #720 from CommunityToolkit/dev/escape-observable-fields
Handle [ObservableProperty] fields with keyword identifiers
2 parents 34f82fe + 84476ce commit e071ed2

File tree

2 files changed

+336
-9
lines changed

2 files changed

+336
-9
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

+29-9
Original file line numberDiff line numberDiff line change
@@ -795,13 +795,33 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
795795
// Get the property type syntax
796796
TypeSyntax propertyType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations);
797797

798+
string getterFieldIdentifierName;
799+
ExpressionSyntax getterFieldExpression;
800+
ExpressionSyntax setterFieldExpression;
801+
798802
// In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments
799803
// with it in the generated setter body are executed correctly and without conflicts with the implicit value parameter.
800-
ExpressionSyntax fieldExpression = propertyInfo.FieldName switch
804+
if (propertyInfo.FieldName == "value")
801805
{
802-
"value" => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("value")),
803-
string name => IdentifierName(name)
804-
};
806+
// We only need to add "this." when referencing the field in the setter (getter and XML docs are not ambiguous)
807+
getterFieldIdentifierName = "value";
808+
getterFieldExpression = IdentifierName(getterFieldIdentifierName);
809+
setterFieldExpression = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), (IdentifierNameSyntax)getterFieldExpression);
810+
}
811+
else if (SyntaxFacts.GetKeywordKind(propertyInfo.FieldName) != SyntaxKind.None ||
812+
SyntaxFacts.GetContextualKeywordKind(propertyInfo.FieldName) != SyntaxKind.None)
813+
{
814+
// If the identifier for the field could potentially be a keyword, we must escape it.
815+
// This usually happens if the annotated field was escaped as well (eg. "@event").
816+
// In this case, we must always escape the identifier, in all cases.
817+
getterFieldIdentifierName = $"@{propertyInfo.FieldName}";
818+
getterFieldExpression = setterFieldExpression = IdentifierName(getterFieldIdentifierName);
819+
}
820+
else
821+
{
822+
getterFieldIdentifierName = propertyInfo.FieldName;
823+
getterFieldExpression = setterFieldExpression = IdentifierName(getterFieldIdentifierName);
824+
}
805825

806826
if (propertyInfo.NotifyPropertyChangedRecipients || propertyInfo.IsOldPropertyValueDirectlyReferenced)
807827
{
@@ -813,7 +833,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
813833
VariableDeclaration(propertyType)
814834
.AddVariables(
815835
VariableDeclarator(Identifier("__oldValue"))
816-
.WithInitializer(EqualsValueClause(fieldExpression)))));
836+
.WithInitializer(EqualsValueClause(setterFieldExpression)))));
817837
}
818838

819839
// Add the OnPropertyChanging() call first:
@@ -863,7 +883,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
863883
ExpressionStatement(
864884
AssignmentExpression(
865885
SyntaxKind.SimpleAssignmentExpression,
866-
fieldExpression,
886+
setterFieldExpression,
867887
IdentifierName("value"))));
868888

869889
// If validation is requested, add a call to ValidateProperty:
@@ -959,7 +979,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
959979
IdentifierName("Default")),
960980
IdentifierName("Equals")))
961981
.AddArgumentListArguments(
962-
Argument(fieldExpression),
982+
Argument(setterFieldExpression),
963983
Argument(IdentifierName("value")))),
964984
Block(setterStatements.AsEnumerable()));
965985

@@ -1009,13 +1029,13 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
10091029
.AddArgumentListArguments(
10101030
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservablePropertyGenerator).FullName))),
10111031
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservablePropertyGenerator).Assembly.GetName().Version.ToString()))))))
1012-
.WithOpenBracketToken(Token(TriviaList(Comment($"/// <inheritdoc cref=\"{propertyInfo.FieldName}\"/>")), SyntaxKind.OpenBracketToken, TriviaList())),
1032+
.WithOpenBracketToken(Token(TriviaList(Comment($"/// <inheritdoc cref=\"{getterFieldIdentifierName}\"/>")), SyntaxKind.OpenBracketToken, TriviaList())),
10131033
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))))
10141034
.AddAttributeLists(forwardedAttributes.ToArray())
10151035
.AddModifiers(Token(SyntaxKind.PublicKeyword))
10161036
.AddAccessorListAccessors(
10171037
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
1018-
.WithExpressionBody(ArrowExpressionClause(IdentifierName(propertyInfo.FieldName)))
1038+
.WithExpressionBody(ArrowExpressionClause(getterFieldExpression))
10191039
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
10201040
setAccessor);
10211041
}

0 commit comments

Comments
 (0)