diff --git a/src/EditorFeatures/CSharpTest/Classification/AbstractCSharpClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/AbstractCSharpClassifierTests.cs index 786804095e8458bcd5288c00b542e99f3cd8f78f..f7a9be5667bd4388208931d6eae26bed2cfe0796 100644 --- a/src/EditorFeatures/CSharpTest/Classification/AbstractCSharpClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/AbstractCSharpClassifierTests.cs @@ -40,24 +40,68 @@ internal string GetText(ClassifiedSpan tuple) actual.Sort((t1, t2) => t1.TextSpan.Start - t2.TextSpan.Start); - var max = Math.Max(expected.Length, actual.Count); - for (int i = 0; i < max; i++) + var actualFormatted = actual.Select(a => new FormattedClassification(a.ClassificationType, allCode.Substring(a.TextSpan.Start, a.TextSpan.Length))); + var expectedFormatted = expected.Select(e => new FormattedClassification(e.Item2, e.Item1)); + AssertEx.Equal(expectedFormatted, actualFormatted); + } + + private class FormattedClassification + { + private readonly string _classification; + private readonly string _text; + + public FormattedClassification(string classification, string text) { - if (i >= expected.Length) - { - AssertEx.Fail("Unexpected actual classification: {0}", GetText(actual[i])); - } - else if (i >= actual.Count) + _classification = classification; + _text = text; + } + + public override bool Equals(object obj) + { + if (obj is FormattedClassification other) { - AssertEx.Fail("Missing classification for: {0}", GetText(expected[i])); + return this._classification == other._classification && this._text == other._text; } - var tuple = expected[i]; - var classification = actual[i]; + return false; + } + + public override int GetHashCode() + { + return _classification.GetHashCode() ^ _text.GetHashCode(); + } - var text = allCode.Substring(classification.TextSpan.Start, classification.TextSpan.Length); - Assert.Equal(tuple.Item1, text); - Assert.Equal(tuple.Item2, classification.ClassificationType); + public override string ToString() + { + switch(_classification) + { + case "punctuation": + switch (_text) + { + case "(": + return "Punctation.OpenParen"; + case ")": + return "Punctation.CloseParen"; + case ";": + return "Punctation.Semicolon"; + case ":": + return "Punctuation.Colon"; + case ",": + return "Punctuation.Comma"; + } + goto default; + + case "operator": + switch(_text) + { + case "=": + return "Operators.Equals"; + } + goto default; + + default: + return $"{char.ToUpperInvariant(_classification[0])}{_classification.Substring(1)}(\"{_text}\")"; + } } } diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index 0ab6f1676f70d7ef97b93de43e614d3cf82d3bec..16c6f5a05d1d3df2ae8bad6fd8310649b2f0b952 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -67,8 +67,6 @@ public async Task GenericClassDeclaration() public async Task RefVar() { await TestInMethodAsync( - className: "Class", - methodName: "M", code: @"int i = 0; ref var x = ref i;", expected: Keyword("var")); } diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs index b540b427e9c9bb2abd0ebacb67487acaaff7f7c0..c04542725eab046bdacd4bf7a0f3d21167f7f4d1 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs @@ -7,6 +7,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification { + [Trait(Traits.Feature, Traits.Features.Classification)] public partial class SyntacticClassifierTests { [Fact, Trait(Traits.Feature, Traits.Features.Classification)] @@ -925,5 +926,79 @@ public async Task PP_PragmaWarningRestoreThree() Punctuation.Comma, Number("102")); } + + [Fact] + public async Task DiscardInOutDeclaration() + { + await TestInMethodAsync( + code: @"M2(out var _);", + expected: Classifications(Identifier("M2"), Punctuation.OpenParen, Keyword("out"), Identifier("var"), + Identifier("_"), Punctuation.CloseParen, Punctuation.Semicolon)); + } + + [Fact] + public async Task DiscardInCasePattern() + { + await TestInMethodAsync( + code: @"switch (1) { case int _: }", + expected: Classifications(Keyword("switch"), Punctuation.OpenParen, Number("1"), Punctuation.CloseParen, + Punctuation.OpenCurly, Keyword("case"), Keyword("int"), Identifier("_"), Punctuation.Colon, Punctuation.CloseCurly)); + } + + [Fact] + public async Task DiscardInDeconstruction() + { + await TestInMethodAsync( + code: @"var (x, _) = (1, 2);", + expected: Classifications(Identifier("var"), Punctuation.OpenParen, Identifier("x"), Punctuation.Comma, + Identifier("_"), Punctuation.CloseParen, Operators.Equals, Punctuation.OpenParen, Number("1"), + Punctuation.Comma, Number("2"), Punctuation.CloseParen, Punctuation.Semicolon)); + } + + [Fact] + public async Task DiscardInDeconstruction2() + { + await TestInMethodAsync( + code: @"(var _, var _) = (1, 2);", + expected: Classifications(Punctuation.OpenParen, Identifier("var"), Identifier("_"), Punctuation.Comma, + Identifier("var"), Identifier("_"), Punctuation.CloseParen, Operators.Equals, Punctuation.OpenParen, + Number("1"), Punctuation.Comma, Number("2"), Punctuation.CloseParen, Punctuation.Semicolon)); + } + + [Fact] + public async Task ShortDiscardInDeconstruction() + { + await TestInMethodAsync( + code: @"int x; (_, x) = (1, 2);", + expected: Classifications(Keyword("int"), Identifier("x"), Punctuation.Semicolon, Punctuation.OpenParen, + Identifier("_"), Punctuation.Comma, Identifier("x"), Punctuation.CloseParen, Operators.Equals, + Punctuation.OpenParen, Number("1"), Punctuation.Comma, Number("2"), Punctuation.CloseParen, + Punctuation.Semicolon)); + } + + [Fact] + public async Task ShortDiscardInOutDeclaration() + { + await TestInMethodAsync( + code: @"M2(out _);", + expected: Classifications(Identifier("M2"), Punctuation.OpenParen, Keyword("out"), Identifier("_"), Punctuation.CloseParen, + Punctuation.Semicolon)); + } + + [Fact] + public async Task ShortDiscardInAssignment() + { + await TestInMethodAsync( + code: @"_ = 1;", + expected: Classifications(Identifier("_"), Operators.Equals, Number("1"), Punctuation.Semicolon)); + } + + [Fact] + public async Task UnderscoreInAssignment() + { + await TestInMethodAsync(code: @"int _; _ = 1;" , + expected: Classifications(Keyword("int"), Identifier("_"), Punctuation.Semicolon, Identifier("_"), Operators.Equals, + Number("1"), Punctuation.Semicolon)); + } } } diff --git a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs index c088e657f681d61158e239ea5fe0e6359148de3a..f14feace57609eb608df8a31b7e56acae1f1ba01 100644 --- a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs +++ b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Threading; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -24,6 +23,10 @@ internal static class ClassificationHelpers /// The correct syntactic classification for the token. public static string GetClassification(SyntaxToken token) { + if (token.IsKind(SyntaxKind.DiscardDesignation, SyntaxKind.UnderscoreToken)) + { + return ClassificationTypeNames.Identifier; + } if (SyntaxFacts.IsKeywordKind(token.Kind())) { return ClassificationTypeNames.Keyword;