diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index 574c9a9ba27bb..b2b018c431df1 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -3612,5 +3612,231 @@ void M() where T : notnull { } TypeParameter("T"), Keyword("notnull")); } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task NonDiscardVariableDeclaration() + { + await TestAsync(@" +class X +{ + void N() + { + var _ = int.Parse(""""); + } +}", + Keyword("var"), + Static("Parse"), + Method("Parse")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task NonDiscardVariableDeclarationMultipleDeclarators() + { + await TestAsync(@" +class X +{ + void N() + { + int i = 1, _ = 1; + int _ = 2, j = 1; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardAssignment() + { + await TestAsync(@" +class X +{ + void N() + { + _ = int.Parse(""""); + } +}", + Keyword("_"), + Static("Parse"), + Method("Parse")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInOutDeclaration() + { + await TestAsync(@" +class X +{ + void N() + { + int.TryParse("""", out var _); + } +}", + Method("TryParse"), + Static("TryParse"), + Keyword("var"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInOutAssignment() + { + await TestAsync(@" +class X +{ + void N() + { + int.TryParse("""", out _); + } +}", + Method("TryParse"), + Static("TryParse"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInDeconstructionAssignment() + { + await TestAsync(@" +class X +{ + void N() + { + (x, _) = (0, 0); + } +}", + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInDeconstructionDeclaration() + { + await TestAsync(@" +class X +{ + void N() + { + (int x, int _) = (0, 0); + } +}", + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInPatternMatch() + { + await TestAsync(@" +class X +{ + bool N(object x) + { + return x is int _; + } +}", + Parameter("x"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInSwitch() + { + await TestAsync(@" +class X +{ + bool N(object x) + { + switch(x) + { + case int _: + return true; + default: + return false; + } + } +}", + Parameter("x"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardInSwitchPatternMatch() + { + await TestAsync(@" +class X +{ + bool N(object x) + { + return x switch + { + _ => return true; + }; + } +}", + Parameter("x"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task UnusedUnderscoreParameterInLambda() + { + await TestAsync(@" +class X +{ + void N() + { + System.Func a = (int _) => 0; + } +}", + Namespace("System"), + Delegate("Func")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task UsedUnderscoreParameterInLambda() + { + await TestAsync(@" +class X +{ + void N() + { + System.Func a = (int _) => _; + } +}", + Namespace("System"), + Delegate("Func"), + Parameter("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardsInLambda() + { + await TestAsync(@" +class X +{ + void N() + { + System.Func a = (int _, int _) => 0; + } +}", + Namespace("System"), + Delegate("Func"), + Keyword("_"), + Keyword("_")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task DiscardsInLambdaWithInferredType() + { + await TestAsync(@" +class X +{ + void N() + { + System.Func a = (_, _) => 0; + } +}", + Namespace("System"), + Delegate("Func"), + Keyword("_"), + Keyword("_")); + } } } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs index 9e8aebc13b44e..1429fb5237942 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs @@ -36,7 +36,8 @@ public CSharpSyntaxClassificationService(HostLanguageServices languageServices) new NameSyntaxClassifier(), new OperatorOverloadSyntaxClassifier(), new SyntaxTokenClassifier(), - new UsingDirectiveSyntaxClassifier() + new UsingDirectiveSyntaxClassifier(), + new DiscardSyntaxClassifier() }); } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs new file mode 100644 index 0000000000000..90c18f86b6f90 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/DiscardSyntaxClassifier.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Classification.Classifiers; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Classification.Classifiers +{ + internal class DiscardSyntaxClassifier : AbstractSyntaxClassifier + { + public override ImmutableArray SyntaxNodeTypes { get; } = ImmutableArray.Create( + typeof(DiscardDesignationSyntax), + typeof(DiscardPatternSyntax), + typeof(ParameterSyntax), + typeof(IdentifierNameSyntax)); + + public override void AddClassifications( + Workspace workspace, + SyntaxNode syntax, + SemanticModel semanticModel, + ArrayBuilder result, + CancellationToken cancellationToken) + { + if (syntax.IsKind(SyntaxKind.DiscardDesignation) || syntax.IsKind(SyntaxKind.DiscardPattern)) + { + result.Add(new ClassifiedSpan(syntax.Span, ClassificationTypeNames.Keyword)); + return; + } + + switch (syntax) + { + case ParameterSyntax parameter when parameter.Identifier.Text == "_": + var symbol = semanticModel.GetDeclaredSymbol(parameter, cancellationToken); + + if (symbol?.IsDiscard == true) + { + result.Add(new ClassifiedSpan(parameter.Identifier.Span, ClassificationTypeNames.Keyword)); + } + break; + + case IdentifierNameSyntax identifierName when identifierName.Identifier.Text == "_": + var symbolInfo = semanticModel.GetSymbolInfo(identifierName, cancellationToken); + + if (symbolInfo.Symbol?.Kind == SymbolKind.Discard) + { + result.Add(new ClassifiedSpan(syntax.Span, ClassificationTypeNames.Keyword)); + } + break; + } + } + } +}