Skip to content

Commit

Permalink
Escape record keywords (#74227)
Browse files Browse the repository at this point in the history
* First pass

* Second attempt at treating `record` as a special case when escaping

* Escape just type and alias names for records

* Rename Foo to C as we want to avoid the use of Foo in the code base

* Remove blank line

* Move check for record keyword into EscapeIdentifier

* minor: fixed formatting (removed spaces/tabs)

* PR feedback - optimised checks to see if symbol needs escaping, and add a unit test for escaping an alias.
  • Loading branch information
SteveDunn authored Jul 18, 2024
1 parent fa05fe4 commit 46c701c
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,20 @@ protected override void FreeNotFirstVisitor(AbstractSymbolDisplayVisitor visitor

internal SymbolDisplayPart CreatePart(SymbolDisplayPartKind kind, ISymbol? symbol, string text)
{
text = (text == null) ? "?" :
(_escapeKeywordIdentifiers && IsEscapable(kind)) ? EscapeIdentifier(text) : text;
if (text == null)
{
return new SymbolDisplayPart(kind, symbol, "?");
}

if (!_escapeKeywordIdentifiers)
{
return new SymbolDisplayPart(kind, symbol, text);
}

if (IsEscapable(kind))
{
text = EscapeIdentifier(text, symbol?.Kind is SymbolKind.NamedType or SymbolKind.Alias);
}

return new SymbolDisplayPart(kind, symbol, text);
}
Expand Down Expand Up @@ -124,12 +136,16 @@ private static bool IsEscapable(SymbolDisplayPartKind kind)
}
}

private static string EscapeIdentifier(string identifier)
private static string EscapeIdentifier(string identifier, bool isNamedTypeOrAliasName)
{
var kind = SyntaxFacts.GetKeywordKind(identifier);
return kind == SyntaxKind.None
? identifier
: $"@{identifier}";
SyntaxKind kind = SyntaxFacts.GetKeywordKind(identifier);

if (kind is SyntaxKind.None && isNamedTypeOrAliasName && StringComparer.Ordinal.Equals(identifier, "record"))
{
kind = SyntaxKind.RecordKeyword;
}

return kind == SyntaxKind.None ? identifier : $"@{identifier}";
}

public override void VisitAssembly(IAssemblySymbol symbol)
Expand Down
106 changes: 105 additions & 1 deletion src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,81 @@ class @true {
SymbolDisplayPartKind.Punctuation);
}

[Fact]
public void TestEscapeRecordKeywordIdentifiers_EscapesTypeNames()
{
var text = @"
class @record {
@record @struct(@record @true, string name, bool @bool = true) { return @record; } }
";

Func<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("record", 0).Single().
GetMembers("struct").Single();

var format = new SymbolDisplayFormat(
memberOptions: SymbolDisplayMemberOptions.IncludeType | SymbolDisplayMemberOptions.IncludeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeName | SymbolDisplayParameterOptions.IncludeDefaultValue,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

TestSymbolDescription(
text,
findSymbol,
format,
"@record @struct(@record @true, string name, bool @bool = true)",
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.MethodName, //@struct
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ParameterName, //@record
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ParameterName, //string
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ParameterName, //@bool
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Punctuation);
}

[Fact]
public void TestEscapeRecordKeywordIdentifiers_DoesNotEscapesMethodNames()
{
var text = @"
class C {
C record() { return default; } }
";

Func<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("record").Single();

var format = new SymbolDisplayFormat(
memberOptions: SymbolDisplayMemberOptions.IncludeType | SymbolDisplayMemberOptions.IncludeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeName | SymbolDisplayParameterOptions.IncludeDefaultValue,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

TestSymbolDescription(
text,
findSymbol,
format,
"C record()",
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.MethodName, //record
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Punctuation);
}

[Fact, WorkItem("https:/dotnet/roslyn/issues/74117")]
public void TestRecordStructName()
{
Expand Down Expand Up @@ -2589,7 +2664,7 @@ class C2 {} } } }
GetTypeMembers("C2").Single();

var format = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);

TestSymbolDescription(
text,
Expand All @@ -2605,6 +2680,35 @@ class C2 {} } } }
SymbolDisplayPartKind.ClassName);
}

[Fact]
public void TestAliases_AliasesNamedRecordAreEscaped()
{
var text = @"
using @record = N1;
namespace N1 {
class C1 {} }
";

Func<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetNestedNamespace("N1").
GetTypeMembers("C1").Single();

var format = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);

TestSymbolDescription(
text,
findSymbol,
format,
"@record.C1",
text.IndexOf("namespace", StringComparison.Ordinal),
true,
SymbolDisplayPartKind.AliasName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.ClassName);
}

[Fact]
public void TestAlias2()
{
Expand Down

0 comments on commit 46c701c

Please sign in to comment.