|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntelliSense.CompletionSetSources;
public class KeywordCompletionProviderTests : AbstractCSharpCompletionProviderTests
{
internal override Type GetCompletionProviderType()
=> typeof(KeywordCompletionProvider);
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task IsCommitCharacterTest()
=> await VerifyCommonCommitCharactersAsync("$$", textTypedSoFar: "");
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public void IsTextualTriggerCharacterTest()
=> TestCommonIsTextualTriggerCharacter();
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task SendEnterThroughToEditorTest()
{
await VerifySendEnterThroughToEnterAsync("$$", "class", sendThroughEnterOption: EnterKeyRule.Never, expected: false);
await VerifySendEnterThroughToEnterAsync("$$", "class", sendThroughEnterOption: EnterKeyRule.AfterFullyTypedWord, expected: true);
await VerifySendEnterThroughToEnterAsync("$$", "class", sendThroughEnterOption: EnterKeyRule.Always, expected: true);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task InEmptyFile()
{
var markup = "$$";
await VerifyAnyItemExistsAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInInactiveCode()
{
var markup = """
class C
{
void M()
{
#if false
$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInCharLiteral()
{
var markup = """
class C
{
void M()
{
var c = '$$';
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedCharLiteral()
{
var markup = """
class C
{
void M()
{
var c = '$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedCharLiteralAtEndOfFile()
{
var markup = """
class C
{
void M()
{
var c = '$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInString()
{
var markup = """
class C
{
void M()
{
var s = "$$";
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInStringInDirective()
{
var markup = """
#r "$$"
""";
await VerifyNoItemsExistAsync(markup, SourceCodeKind.Script);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedString()
{
var markup = """
class C
{
void M()
{
var s = "$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedStringInDirective()
{
var markup = """
#r "$$"
""";
await VerifyNoItemsExistAsync(markup, SourceCodeKind.Script);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedStringAtEndOfFile()
{
var markup = """
class C
{
void M()
{
var s = "$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInVerbatimString()
{
var markup = """
class C
{
void M()
{
var s = @"
$$
";
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedVerbatimString()
{
var markup = """
class C
{
void M()
{
var s = @"
$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInUnterminatedVerbatimStringAtEndOfFile()
{
var markup = """
class C
{
void M()
{
var s = @"$$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInSingleLineComment()
{
var markup = """
class C
{
void M()
{
// $$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInSingleLineCommentAtEndOfFile()
{
var markup = """
namespace A
{
}// $$
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task NotInMutliLineComment()
{
var markup = """
class C
{
void M()
{
/*
$$
*/
""";
await VerifyNoItemsExistAsync(markup);
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/968256")]
public async Task UnionOfItemsFromBothContexts()
{
var markup = """
<Workspace>
<Project Language="C#" CommonReferences="true" AssemblyName="Proj1" PreprocessorSymbols="GOO">
<Document FilePath="CurrentDocument.cs"><![CDATA[
class C
{
#if GOO
void goo() {
#endif
$$
#if GOO
}
#endif
}
]]>
</Document>
</Project>
<Project Language="C#" CommonReferences="true" AssemblyName="Proj2">
<Document IsLinkFile="true" LinkAssemblyName="Proj1" LinkFilePath="CurrentDocument.cs"/>
</Project>
</Workspace>
""";
await VerifyItemInLinkedFilesAsync(markup, "public", null);
await VerifyItemInLinkedFilesAsync(markup, "for", null);
}
[WorkItem("https://github.com/dotnet/roslyn/issues/7768")]
[WorkItem("https://github.com/dotnet/roslyn/issues/8228")]
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task FormattingAfterCompletionCommit_AfterGetAccessorInSingleLineIncompleteProperty()
{
var markupBeforeCommit = """
class Program
{
int P {g$$
void Main() { }
}
""";
var expectedCodeAfterCommit = """
class Program
{
int P {get;
void Main() { }
}
""";
await VerifyProviderCommitAsync(markupBeforeCommit, "get", expectedCodeAfterCommit, commitChar: ';');
}
[WorkItem("https://github.com/dotnet/roslyn/issues/7768")]
[WorkItem("https://github.com/dotnet/roslyn/issues/8228")]
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task FormattingAfterCompletionCommit_AfterBothAccessorsInSingleLineIncompleteProperty()
{
var markupBeforeCommit = """
class Program
{
int P {get;set$$
void Main() { }
}
""";
var expectedCodeAfterCommit = """
class Program
{
int P {get;set;
void Main() { }
}
""";
await VerifyProviderCommitAsync(markupBeforeCommit, "set", expectedCodeAfterCommit, commitChar: ';');
}
[WorkItem("https://github.com/dotnet/roslyn/issues/7768")]
[WorkItem("https://github.com/dotnet/roslyn/issues/8228")]
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task FormattingAfterCompletionCommit_InSingleLineMethod()
{
var markupBeforeCommit = """
class Program
{
public static void Test() { return$$
void Main() { }
}
""";
var expectedCodeAfterCommit = """
class Program
{
public static void Test() { return;
void Main() { }
}
""";
await VerifyProviderCommitAsync(markupBeforeCommit, "return", expectedCodeAfterCommit, commitChar: ';');
}
[WorkItem("https://github.com/dotnet/roslyn/issues/14218")]
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task PredefinedTypeKeywordsShouldBeRecommendedAfterCaseInASwitch()
{
var text = """
class Program
{
public static void Test()
{
object o = null;
switch (o)
{
case $$
}
}
}
""";
await VerifyItemExistsAsync(text, "int");
await VerifyItemExistsAsync(text, "string");
await VerifyItemExistsAsync(text, "byte");
await VerifyItemExistsAsync(text, "char");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task PrivateOrProtectedModifiers()
{
var text = """
class C
{
$$
}
""";
await VerifyItemExistsAsync(text, "private");
await VerifyItemExistsAsync(text, "protected");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task PrivateProtectedModifier()
{
var text = """
class C
{
private $$
}
""";
await VerifyItemExistsAsync(text, "protected");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ProtectedPrivateModifier()
{
var text = """
class C
{
protected $$
}
""";
await VerifyItemExistsAsync(text, "private");
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/34774")]
public async Task DoNotSuggestEventAfterReadonlyInClass()
{
var markup =
"""
class C {
readonly $$
}
""";
await VerifyItemIsAbsentAsync(markup, "event");
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/34774")]
public async Task DoNotSuggestEventAfterReadonlyInInterface()
{
var markup =
"""
interface C {
readonly $$
}
""";
await VerifyItemIsAbsentAsync(markup, "event");
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/34774")]
public async Task SuggestEventAfterReadonlyInStruct()
{
var markup =
"""
struct C {
readonly $$
}
""";
await VerifyItemExistsAsync(markup, "event");
}
[Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
[InlineData("struct", true)]
[InlineData("record struct", true)]
[InlineData("class", false)]
[InlineData("record", false)]
[InlineData("record class", false)]
[InlineData("interface", false)]
public async Task SuggestReadonlyPropertyAccessor(string declarationType, bool present)
{
var markup =
$@"{declarationType} C {{
int X {{
$$
}}
}}
";
if (present)
{
await VerifyItemExistsAsync(markup, "readonly");
}
else
{
await VerifyItemIsAbsentAsync(markup, "readonly");
}
}
[Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
[InlineData("struct", true)]
[InlineData("class", false)]
[InlineData("interface", false)]
public async Task SuggestReadonlyBeforePropertyAccessor(string declarationType, bool present)
{
var markup =
$@"{declarationType} C {{
int X {{
$$ get;
}}
}}
";
if (present)
{
await VerifyItemExistsAsync(markup, "readonly");
}
else
{
await VerifyItemIsAbsentAsync(markup, "readonly");
}
}
[Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
[InlineData("struct", true)]
[InlineData("class", false)]
[InlineData("interface", false)]
public async Task SuggestReadonlyIndexerAccessor(string declarationType, bool present)
{
var markup =
$@"{declarationType} C {{
int this[int i] {{
$$
}}
}}
";
if (present)
{
await VerifyItemExistsAsync(markup, "readonly");
}
else
{
await VerifyItemIsAbsentAsync(markup, "readonly");
}
}
[Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
[InlineData("struct", true)]
[InlineData("class", false)]
[InlineData("interface", false)]
public async Task SuggestReadonlyEventAccessor(string declarationType, bool present)
{
var markup =
$@"{declarationType} C {{
event System.Action E {{
$$
}}
}}
";
if (present)
{
await VerifyItemExistsAsync(markup, "readonly");
}
else
{
await VerifyItemIsAbsentAsync(markup, "readonly");
}
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
public async Task SuggestAccessorAfterReadonlyInStruct()
{
var markup =
"""
struct C {
int X {
readonly $$
}
}
""";
await VerifyItemExistsAsync(markup, "get");
await VerifyItemExistsAsync(markup, "set");
await VerifyItemIsAbsentAsync(markup, "void");
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/39265")]
public async Task SuggestReadonlyMethodInStruct()
{
var markup =
"""
struct C {
public $$ void M() {}
}
""";
await VerifyItemExistsAsync(markup, "readonly");
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/58921"), CombinatorialData, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestInCastExpressionThatMightBeParenthesizedExpression1(bool hasNewline)
{
var markup =
$@"
class C
{{
void M()
{{
var data = (n$$) {(hasNewline ? Environment.NewLine : string.Empty)} M();
}}
}}";
if (hasNewline)
{
await VerifyItemExistsAsync(markup, "new");
await VerifyItemExistsAsync(markup, "this");
await VerifyItemExistsAsync(markup, "null");
await VerifyItemExistsAsync(markup, "base");
await VerifyItemExistsAsync(markup, "true");
await VerifyItemExistsAsync(markup, "false");
await VerifyItemExistsAsync(markup, "typeof");
await VerifyItemExistsAsync(markup, "sizeof");
await VerifyItemExistsAsync(markup, "nameof");
}
else
{
await VerifyItemIsAbsentAsync(markup, "new");
await VerifyItemIsAbsentAsync(markup, "this");
await VerifyItemIsAbsentAsync(markup, "null");
await VerifyItemIsAbsentAsync(markup, "base");
await VerifyItemIsAbsentAsync(markup, "true");
await VerifyItemIsAbsentAsync(markup, "false");
await VerifyItemIsAbsentAsync(markup, "typeof");
await VerifyItemIsAbsentAsync(markup, "sizeof");
await VerifyItemIsAbsentAsync(markup, "nameof");
}
}
[Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem("https://github.com/dotnet/roslyn/issues/57886")]
public async Task TestInCastExpressionThatMightBeParenthesizedExpression2(bool hasExpression)
{
var markup =
$@"class C
{{
bool Prop => (t$$) {(hasExpression ? "n" : string.Empty)}
private int n;
}}";
if (hasExpression)
{
await VerifyItemIsAbsentAsync(markup, "new");
await VerifyItemIsAbsentAsync(markup, "this");
await VerifyItemIsAbsentAsync(markup, "null");
await VerifyItemIsAbsentAsync(markup, "base");
await VerifyItemIsAbsentAsync(markup, "true");
await VerifyItemIsAbsentAsync(markup, "false");
await VerifyItemIsAbsentAsync(markup, "typeof");
await VerifyItemIsAbsentAsync(markup, "sizeof");
await VerifyItemIsAbsentAsync(markup, "nameof");
}
else
{
await VerifyItemExistsAsync(markup, "new");
await VerifyItemExistsAsync(markup, "this");
await VerifyItemExistsAsync(markup, "null");
await VerifyItemExistsAsync(markup, "base");
await VerifyItemExistsAsync(markup, "true");
await VerifyItemExistsAsync(markup, "false");
await VerifyItemExistsAsync(markup, "typeof");
await VerifyItemExistsAsync(markup, "sizeof");
await VerifyItemExistsAsync(markup, "nameof");
}
}
[Theory]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
[InlineData("record struct")]
public async Task SuggestRequiredInClassOrStructOrRecord(string type)
{
var markup = $$"""
{{type}} C
{
$$
""";
await VerifyItemExistsAsync(markup, "required");
}
[Fact]
public async Task DoNotSuggestRequiredInInterface()
{
var markup = $$"""
interface I
{
public $$
""";
await VerifyItemIsAbsentAsync(markup, "required");
}
[Theory]
[InlineData("static")]
[InlineData("const")]
[InlineData("readonly")]
public async Task DoNotSuggestRequiredOnFilteredKeywordMembers(string keyword)
{
var markup = $$"""
class C
{
{{keyword}} $$
""";
await VerifyItemIsAbsentAsync(markup, "required");
}
[Theory]
[InlineData("static")]
[InlineData("const")]
[InlineData("readonly")]
public async Task DoNotSuggestFilteredKeywordsOnRequiredMembers(string keyword)
{
var markup = $$"""
class C
{
required $$
""";
await VerifyItemIsAbsentAsync(markup, keyword);
}
[Fact]
public async Task DoNotSuggestRequiredOnRequiredMembers()
{
var markup = $$"""
class C
{
required $$
""";
await VerifyItemIsAbsentAsync(markup, "required");
}
[Fact]
public async Task SuggestFileOnTypes()
{
var markup = $$"""
$$ class C { }
""";
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task DoNotSuggestFileAfterFile()
{
var markup = $$"""
file $$
""";
await VerifyItemIsAbsentAsync(markup, "file");
}
[Fact]
public async Task SuggestFileAfterReadonly()
{
// e.g. 'readonly file struct X { }'
var markup = $$"""
readonly $$
""";
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task SuggestFileBeforeFileType()
{
var markup = $$"""
$$
file class C { }
""";
// it might seem like we want to prevent 'file file class',
// but it's likely the user is declaring a file-local type above an existing file-local type here.
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task SuggestFileBeforeDelegate()
{
var markup = $$"""
$$ delegate
""";
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task DoNotSuggestFileOnNestedTypes()
{
var markup = $$"""
class Outer
{
$$ class C { }
}
""";
await VerifyItemIsAbsentAsync(markup, "file");
}
[Fact]
public async Task DoNotSuggestFileOnNonTypeMembers()
{
var markup = $$"""
class C
{
$$
}
""";
await VerifyItemIsAbsentAsync(markup, "file");
}
[Theory]
[InlineData("public")]
[InlineData("internal")]
[InlineData("protected")]
[InlineData("private")]
public async Task DoNotSuggestFileAfterFilteredKeywords(string keyword)
{
var markup = $$"""
{{keyword}} $$
""";
await VerifyItemIsAbsentAsync(markup, "file");
}
[Theory]
[InlineData("public")]
[InlineData("internal")]
[InlineData("protected")]
[InlineData("private")]
public async Task DoNotSuggestFilteredKeywordsAfterFile(string keyword)
{
var markup = $$"""
file $$
""";
await VerifyItemIsAbsentAsync(markup, keyword);
}
[Fact]
public async Task SuggestFileInFileScopedNamespace()
{
var markup = $$"""
namespace NS;
$$
""";
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task SuggestFileInNamespace()
{
var markup = $$"""
namespace NS
{
$$
}
""";
await VerifyItemExistsAsync(markup, "file");
}
[Fact]
public async Task SuggestFileAfterClass()
{
var markup = $$"""
file class C { }
$$
""";
await VerifyItemExistsAsync(markup, "file");
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/67985")]
[MemberData(nameof(TypeDeclarationKeywords))]
public async Task TestTypeDeclarationKeywordsNotAfterUsingUnsafe(string keyword)
{
await VerifyItemIsAbsentAsync("using unsafe $$", keyword);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/67985")]
[MemberData(nameof(TypeDeclarationKeywords))]
public async Task TestTypeDeclarationKeywordsNotAfterUsingStaticUnsafe(string keyword)
{
await VerifyItemIsAbsentAsync("using static unsafe $$", keyword);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/67985")]
[MemberData(nameof(TypeDeclarationKeywords))]
public async Task TestTypeDeclarationKeywordsNotAfterGlobalUsingUnsafe(string keyword)
{
await VerifyItemIsAbsentAsync("global using unsafe $$", keyword);
}
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/67985")]
[MemberData(nameof(TypeDeclarationKeywords))]
public async Task TestTypeDeclarationKeywordsNotAfterGlobalUsingStaticUnsafe(string keyword)
{
await VerifyItemIsAbsentAsync("global using static unsafe $$", keyword);
}
public static IEnumerable<object[]> TypeDeclarationKeywords()
{
yield return new[] { "abstract" };
yield return new[] { "class" };
yield return new[] { "delegate" };
yield return new[] { "file" };
yield return new[] { "interface" };
yield return new[] { "internal" };
yield return new[] { "partial" };
yield return new[] { "public" };
yield return new[] { "readonly" };
yield return new[] { "record" };
yield return new[] { "ref" };
yield return new[] { "sealed" };
yield return new[] { "static" };
yield return new[] { "struct" };
}
}
|