Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Classification;
Expand All @@ -14,40 +13,66 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification;

public abstract class AbstractCSharpClassifierTests : AbstractClassifierTests
{
protected static EditorTestWorkspace CreateWorkspace(string code, ParseOptions options, TestHost testHost)
protected static EditorTestWorkspace CreateWorkspace(
string code, ParseOptions? options, TestHost testHost)
{
var composition = EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost);
return EditorTestWorkspace.CreateCSharp(code, parseOptions: options, composition: composition, isMarkup: false);
}

protected new Task TestAsync(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code,
TestHost testHost,
params FormattedClassification[] expected)
{
return base.TestAsync(code, testHost, expected);
}

protected new Task TestAsync(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code,
TestHost testHost,
ParseOptions? parseOptions,
params FormattedClassification[] expected)
{
return base.TestAsync(code, testHost, parseOptions, expected);
}

protected override async Task DefaultTestAsync(string code, string allCode, TestHost testHost, FormattedClassification[] expected)
{
await TestAsync(code, allCode, testHost, parseOptions: null, expected);
await TestAsync(code, allCode, testHost, parseOptions: Options.Script, expected);
}

protected override string WrapInClass(string className, string code)
=> $@"class {className} {{
{code}
}}";
=> $$"""
class {{className}} {
{{code}}
}
""";

protected override string WrapInExpression(string code)
=> $@"class C {{
void M() {{
var q =
{code}
}}
}}";
=> $$"""
class C {
void M() {
var q =
{{code}}
}
}
""";

protected override string WrapInMethod(string className, string methodName, string code)
=> $@"class {className} {{
void {methodName}() {{
{code}
}}
}}";
=> $$"""
class {{className}} {
void {{methodName}}() {
{{code}}
}
}
""";

protected override string WrapInNamespace(string code)
=> $@"namespace N {{
{code}
}}";
=> $$"""
namespace N {
{{code}}
}
""";
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification;
[Trait(Traits.Feature, Traits.Features.Classification)]
public sealed partial class SemanticClassifierTests : AbstractCSharpClassifierTests
{
protected override async Task<ImmutableArray<ClassifiedSpan>> GetClassificationSpansAsync(string code, ImmutableArray<TextSpan> spans, ParseOptions? options, TestHost testHost)
protected override async Task<ImmutableArray<ClassifiedSpan>> GetClassificationSpansAsync(
string code, ImmutableArray<TextSpan> spans, ParseOptions? options, TestHost testHost)
{
using var workspace = CreateWorkspace(code, options, testHost);
var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);

return await GetSemanticClassificationsAsync(document, spans);
}

private new Task TestAsync(
[StringSyntax("C#-Test")] string code,
TestHost testHost,
params FormattedClassification[] expected)
{
return base.TestAsync(code, testHost, expected);
}

[Theory, CombinatorialData]
public async Task GenericClassDeclaration(TestHost testHost)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Editor.UnitTests.Classification;
Expand Down Expand Up @@ -41,7 +43,7 @@ protected override async Task<ImmutableArray<ClassifiedSpan>> GetClassificationS
public async Task VarAsUsingAliasForNamespace(TestHost testHost)
{
await TestAsync(
@"using var = System;",
@"using var = System;",
testHost,
Keyword("using"),
Namespace("var"),
Expand Down Expand Up @@ -3233,4 +3235,64 @@ void M()
Punctuation.CloseCurly,
], actualFormatted);
}

[Theory, CombinatorialData]
public async Task TestModernExtension1(TestHost testHost)
{
await TestAsync(
"""
static class C
{
extension(string s)
{
public bool IsNullOrEmpty() => false;
}

void M(string s)
{
var v = s.IsNullOrEmpty();
}
}
""",
testHost,
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersionExtensions.CSharpNext),
Keyword("static"),
Keyword("class"),
Class("C"),
Static("C"),
Punctuation.OpenCurly,
Keyword("extension"),
Punctuation.OpenParen,
Keyword("string"),
Parameter("s"),
Punctuation.CloseParen,
Punctuation.OpenCurly,
Keyword("public"),
Keyword("bool"),
ExtensionMethod("IsNullOrEmpty"),
Punctuation.OpenParen,
Punctuation.CloseParen,
Operators.EqualsGreaterThan,
Keyword("false"),
Punctuation.Semicolon,
Punctuation.CloseCurly,
Keyword("void"),
Method("M"),
Punctuation.OpenParen,
Keyword("string"),
Parameter("s"),
Punctuation.CloseParen,
Punctuation.OpenCurly,
Keyword("var"),
Local("v"),
Operators.Equals,
Parameter("s"),
Operators.Dot,
ExtensionMethod("IsNullOrEmpty"),
Punctuation.OpenParen,
Punctuation.CloseParen,
Punctuation.Semicolon,
Punctuation.CloseCurly,
Punctuation.CloseCurly);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable disable

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
Expand All @@ -29,7 +30,8 @@ public abstract class AbstractCSharpCompletionProviderTests<TWorkspaceFixture> :
{
protected const string NonBreakingSpaceString = "\x00A0";

protected static string GetMarkup(string source, LanguageVersion languageVersion)
protected static string GetMarkup(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string source, LanguageVersion languageVersion)
=> $@"<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" LanguageVersion=""{languageVersion.ToDisplayString()}"">
<Document FilePath=""Test2.cs"">
Expand All @@ -40,17 +42,17 @@ protected static string GetMarkup(string source, LanguageVersion languageVersion
</Project>
</Workspace>";

protected override EditorTestWorkspace CreateWorkspace(string fileContents)
protected override EditorTestWorkspace CreateWorkspace([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string fileContents)
=> EditorTestWorkspace.CreateCSharp(fileContents, composition: GetComposition());

internal override CompletionService GetCompletionService(Project project)
=> Assert.IsType<CSharpCompletionService>(base.GetCompletionService(project));

private protected override Task BaseVerifyWorkerAsync(
string code, int position,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position,
string expectedItemOrNull, string expectedDescriptionOrNull,
SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, char? deletedCharTrigger, bool checkForAbsence,
int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix,
Glyph? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix,
string displayTextPrefix, string inlineDescription = null, bool? isComplexTextEdit = null,
List<CompletionFilter> matchingFilters = null, CompletionItemFlags? flags = null,
CompletionOptions options = null, bool skipSpeculation = false)
Expand All @@ -63,7 +65,7 @@ private protected override Task BaseVerifyWorkerAsync(
}

private protected override Task BaseVerifyWorkerAsync(
string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger, bool? hasSuggestionItem,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger, bool? hasSuggestionItem,
SourceCodeKind sourceCodeKind, ItemExpectation[] expectedResults,
List<CompletionFilter> matchingFilters, CompletionItemFlags? flags, CompletionOptions options, bool skipSpeculation = false)
{
Expand All @@ -73,10 +75,10 @@ private protected override Task BaseVerifyWorkerAsync(
}

private protected override async Task VerifyWorkerAsync(
string code, int position,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position,
string expectedItemOrNull, string expectedDescriptionOrNull,
SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
bool checkForAbsence, int? glyph, int? matchPriority,
bool checkForAbsence, Glyph? glyph, int? matchPriority,
bool? hasSuggestionItem, string displayTextSuffix, string displayTextPrefix, string inlineDescription = null,
bool? isComplexTextEdit = null, List<CompletionFilter> matchingFilters = null, CompletionItemFlags? flags = null,
CompletionOptions options = null, bool skipSpeculation = false)
Expand All @@ -99,9 +101,9 @@ protected override string ItemPartiallyWritten(string expectedItemOrNull)
=> expectedItemOrNull[0] == '@' ? expectedItemOrNull.Substring(1, 1) : expectedItemOrNull[..1];

private async Task VerifyInFrontOfCommentAsync(
string code, int position, string insertText, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position, string insertText, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
string expectedItemOrNull, string expectedDescriptionOrNull,
SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph,
SourceCodeKind sourceCodeKind, bool checkForAbsence, Glyph? glyph,
int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix,
string displayTextPrefix, string inlineDescription, bool? isComplexTextEdit, List<CompletionFilter> matchingFilters,
CompletionOptions options, bool skipSpeculation = false)
Expand All @@ -118,9 +120,9 @@ await base.VerifyWorkerAsync(
}

private async Task VerifyInFrontOfCommentAsync(
string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
string expectedItemOrNull, string expectedDescriptionOrNull,
SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph,
SourceCodeKind sourceCodeKind, bool checkForAbsence, Glyph? glyph,
int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix,
string displayTextPrefix, string inlineDescription, bool? isComplexTextEdit,
List<CompletionFilter> matchingFilters, CompletionOptions options, bool skipSpeculation = false)
Expand All @@ -133,9 +135,9 @@ await VerifyInFrontOfCommentAsync(
}

private protected async Task VerifyInFrontOfComment_ItemPartiallyWrittenAsync(
string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, int position, bool usePreviousCharAsTrigger, char? deletedCharTrigger,
string expectedItemOrNull, string expectedDescriptionOrNull,
SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph,
SourceCodeKind sourceCodeKind, bool checkForAbsence, Glyph? glyph,
int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix,
string displayTextPrefix, string inlineDescription, bool? isComplexTextEdit,
List<CompletionFilter> matchingFilters, CompletionOptions options, bool skipSpeculation = false)
Expand All @@ -147,7 +149,7 @@ await VerifyInFrontOfCommentAsync(
displayTextPrefix, inlineDescription, isComplexTextEdit, matchingFilters, options, skipSpeculation: skipSpeculation);
}

protected static string AddInsideMethod(string text)
protected static string AddInsideMethod([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string text)
{
return
"""
Expand All @@ -162,7 +164,8 @@ void F()
""";
}

protected static string AddUsingDirectives(string usingDirectives, string text)
protected static string AddUsingDirectives(
string usingDirectives, [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string text)
{
return
usingDirectives +
Expand All @@ -172,7 +175,9 @@ protected static string AddUsingDirectives(string usingDirectives, string text)
text;
}

protected async Task VerifySendEnterThroughToEnterAsync(string initialMarkup, string textTypedSoFar, EnterKeyRule sendThroughEnterOption, bool expected)
protected async Task VerifySendEnterThroughToEnterAsync(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
string textTypedSoFar, EnterKeyRule sendThroughEnterOption, bool expected)
{
using var workspace = CreateWorkspace(initialMarkup);
var hostDocument = workspace.DocumentWithCursor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ private async Task VerifyKeywordAsync(
var expectedDescription = dotAwait
? GetDescription(CompletionDisplayTextAwait, FeaturesResources.Await_the_preceding_expression)
: GetDescription(CompletionDisplayTextAwait, FeaturesResources.Asynchronously_waits_for_the_task_to_finish);
await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwait, glyph: (int)Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);
await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwait, glyph: Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);

if (dotAwaitf)
{
expectedDescription = string.Format(FeaturesResources.Await_the_preceding_expression_and_add_ConfigureAwait_0, "false");
await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait, glyph: (int)Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);
await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait, glyph: Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);
}
else
{
Expand Down
Loading
Loading