Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Colorize async as keyword in some cases #60558

Merged
merged 6 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
272 changes: 272 additions & 0 deletions src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2292,5 +2292,277 @@ static void Main(string[] args)
Punctuation.OpenCurly,
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncInIncompleteMember(TestHost testHost)
{
await TestAsync(
@"class Test
{
public async
}",
testHost,
parseOptions: null,
Keyword("class"),
Class("Test"),
Punctuation.OpenCurly,
Keyword("public"),
Keyword("async"),
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncInIncompleteMemberWhenAsyncTypeIsDefined(TestHost testHost)
{
await TestAsync(
@"[|class Test
{
public async
}|]

class async
{
}",
testHost,
parseOptions: null,
Keyword("class"),
Class("Test"),
Punctuation.OpenCurly,
Keyword("public"),
Class("async"),
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncInPotentialLocalFunctionDeclaration(TestHost testHost)
{
await TestAsync(
@"void M()
{
async
}",
testHost,
parseOptions: null,
Keyword("void"),
Method("M"),
Punctuation.OpenParen,
Punctuation.CloseParen,
Punctuation.OpenCurly,
Keyword("async"),
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncInPotentialLocalFunctionDeclarationWhenAsyncTypeIsDefined(TestHost testHost)
{
await TestAsync(
@"[|void M()
{
async
}|]

class async
{
}",
testHost,
parseOptions: null,
Keyword("void"),
Method("M"),
Punctuation.OpenParen,
Punctuation.CloseParen,
Punctuation.OpenCurly,
Class("async"),
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsLocalMemberType_NoAsyncInScope(TestHost testHost)
{
await TestAsync(
@"class Test
{
void M()
{
[|async a;|]
}
}",
testHost,
parseOptions: null,
Keyword("async"),
Local("a"),
Punctuation.Semicolon);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsLocalMemberType_AsyncInScope(TestHost testHost)
{
await TestAsync(
@"
class async { }

class Test
{
void M()
{
[|async a;|]
}
}",
testHost,
parseOptions: null,
Class("async"),
Local("a"),
Punctuation.Semicolon);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsPropertyType_NoAsyncInScope(TestHost testHost)
{
await TestAsync(
@"class Test
{
[|public async Prop { get; set; }|]
}",
testHost,
parseOptions: null,
Keyword("public"),
Keyword("async"),
Property("Prop"),
Punctuation.OpenCurly,
Keyword("get"),
Punctuation.Semicolon,
Keyword("set"),
Punctuation.Semicolon,
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsPropertyType_AsyncInScope(TestHost testHost)
{
await TestAsync(
@"
class async { }

class Test
{
[|public async Prop { get; set; }|]
}",
testHost,
parseOptions: null,
Keyword("public"),
Class("async"),
Property("Prop"),
Punctuation.OpenCurly,
Keyword("get"),
Punctuation.Semicolon,
Keyword("set"),
Punctuation.Semicolon,
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsMethodReturnType_NoAsyncInScope(TestHost testHost)
{
await TestAsync(
@"class Test
{
[|public async M()|] {}
}",
testHost,
parseOptions: null,
Keyword("public"),
Keyword("async"),
Method("M"),
Punctuation.OpenParen,
Punctuation.CloseParen);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsMethodReturnType_AsyncInScope(TestHost testHost)
{
await TestAsync(
@"
class async { }

class Test
{
[|public async M()|] {}
}",
testHost,
parseOptions: null,
Keyword("public"),
Class("async"),
Method("M"),
Punctuation.OpenParen,
Punctuation.CloseParen);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncAsAccessingName(TestHost testHost)
{
await TestAsync(
@"class Test
{
void M()
{
var a = [|C.async;|]
}
}

class C
{
public static int async;
}",
testHost,
parseOptions: null,
Class("C"),
Operators.Dot,
Field("async"),
Static("async"),
Punctuation.Semicolon);
}

[Theory]
[CombinatorialData]
[WorkItem(60399, "https://github.com/dotnet/roslyn/issues/60339")]
public async Task TestAsyncInIncompleteDelegateOrLambda(TestHost testHost)
{
await TestAsync(
@"using System;
class Test
{
void M()
{
[|Action a = async |]
}
}",
testHost,
parseOptions: null,
Delegate("Action"),
Local("a"),
Operators.Equals,
Keyword("async"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ private void ClassifyTypeSyntax(
TryClassifySymbol(name, symbolInfo, semanticModel, result, cancellationToken) ||
TryClassifyFromIdentifier(name, symbolInfo, result) ||
TryClassifyValueIdentifier(name, symbolInfo, result) ||
TryClassifyNameOfIdentifier(name, symbolInfo, result);
TryClassifyNameOfIdentifier(name, symbolInfo, result) ||
TryClassifyAsyncIdentifier(name, symbolInfo, result);
}

private bool TryClassifySymbol(
Expand Down Expand Up @@ -334,12 +335,10 @@ private static bool TryClassifyValueIdentifier(
SymbolInfo symbolInfo,
ArrayBuilder<ClassifiedSpan> result)
{
var identifierName = name as IdentifierNameSyntax;
if (symbolInfo.Symbol.IsImplicitValueParameter())
if (name is IdentifierNameSyntax identifierName &&
symbolInfo.Symbol.IsImplicitValueParameter())
{
#nullable disable // Can 'identifierName' be null here?
result.Add(new ClassifiedSpan(identifierName.Identifier.Span, ClassificationTypeNames.Keyword));
#nullable enable
return true;
}

Expand All @@ -351,8 +350,7 @@ private static bool TryClassifyNameOfIdentifier(
{
if (name is IdentifierNameSyntax identifierName &&
identifierName.Identifier.IsKindOrHasMatchingText(SyntaxKind.NameOfKeyword) &&
symbolInfo.Symbol == null &&
!symbolInfo.CandidateSymbols.Any())
symbolInfo.GetAnySymbol() is null)
{
result.Add(new ClassifiedSpan(identifierName.Identifier.Span, ClassificationTypeNames.Keyword));
return true;
Expand All @@ -361,7 +359,25 @@ private static bool TryClassifyNameOfIdentifier(
return false;
}

private static bool IsSymbolWithName([NotNullWhen(returnValue: true)] ISymbol? symbol, string name)
private static bool TryClassifyAsyncIdentifier(NameSyntax name, SymbolInfo symbolInfo, ArrayBuilder<ClassifiedSpan> result)
{
var symbol = symbolInfo.GetAnySymbol();

// Simple approach, if the user ever types 'async' and it doesn't actually bind to anything, presume that
// they intend to use it as a keyword and are about to create an async symbol. This works for all error
// cases, while not conflicting with the extremely rare case where 'async' might actually be used to
// reference an actual symbol with that name.
if (symbol is null &&
name is IdentifierNameSyntax { Identifier.Text: "async" })
{
result.Add(new(name.Span, ClassificationTypeNames.Keyword));
return true;
}

return false;
}

private static bool IsSymbolWithName([NotNullWhen(true)] ISymbol? symbol, string name)
{
if (symbol is null || symbol.Name != name)
{
Expand Down