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

Let Import Completion check for global usings auto-generated by SDK #59360

Merged
merged 4 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,67 @@ static MyClass
await VerifyProviderCommitAsync(markup, "MyClass", expectedCodeAfterCommit, commitChar: null, sourceCodeKind: kind);
}

[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(58473, "https://github.com/dotnet/roslyn/issues/58473")]
public async Task TestGlobalUsingsInSdkAutoGeneratedFile()
{
var source = @"
using System;
$$";

var globalUsings = @"
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
";

var markup = CreateMarkupForSingleProject(source, globalUsings, LanguageNames.CSharp, referencedFileName: "ProjectName.GlobalUsings.g.cs");
await VerifyTypeImportItemIsAbsentAsync(markup, "Task", inlineDescription: "System.Threading.Tasks");
await VerifyTypeImportItemIsAbsentAsync(markup, "Console", inlineDescription: "System");
}

[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(58473, "https://github.com/dotnet/roslyn/issues/58473")]
public async Task TestGlobalUsingsInSameFile()
{
var source = @"
global using global::System;
global using global::System.Threading.Tasks;

$$";

var markup = CreateMarkupForSingleProject(source, "", LanguageNames.CSharp);
await VerifyTypeImportItemIsAbsentAsync(markup, "Console", inlineDescription: "System");
await VerifyTypeImportItemIsAbsentAsync(markup, "Task", inlineDescription: "System.Threading.Tasks");
}

[Fact(Skip = "https://github.com/dotnet/roslyn/issues/59088")]
[Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(58473, "https://github.com/dotnet/roslyn/issues/58473")]
public async Task TestGlobalUsingsInUserDocument()
{
var source = @"
$$";

var globalUsings = @"
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
";

var markup = CreateMarkupForSingleProject(source, globalUsings, LanguageNames.CSharp, referencedFileName: "GlobalUsings.cs");
await VerifyTypeImportItemIsAbsentAsync(markup, "Task", inlineDescription: "System.Threading.Tasks");
await VerifyTypeImportItemIsAbsentAsync(markup, "Console", inlineDescription: "System");
}

private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null, CompletionItemFlags? flags = null)
=> VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, flags: flags);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -773,15 +773,20 @@ private Task VerifyItemInSameProjectAsync(string markup, string referencedCode,
return VerifyItemWithReferenceWorkerAsync(xmlString, expectedItem, expectedSymbols);
}

protected static string CreateMarkupForSingleProject(string markup, string referencedCode, string sourceLanguage)
protected static string CreateMarkupForSingleProject(
string sourceCode,
string referencedCode,
string sourceLanguage,
string sourceFileName = "SourceDocument",
string referencedFileName = "ReferencedDocument")
{
return string.Format(@"
<Workspace>
<Project Language=""{0}"" CommonReferences=""true"">
<Document FilePath=""SourceDocument"">{1}</Document>
<Document FilePath=""ReferencedDocument"">{2}</Document>
<Project Language=""{0}"" CommonReferences=""true"" Name=""ProjectName"">
<Document FilePath=""{3}"">{1}</Document>
<Document FilePath=""{4}"">{2}</Document>
</Project>
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), SecurityElement.Escape(referencedCode));
</Workspace>", sourceLanguage, SecurityElement.Escape(sourceCode), SecurityElement.Escape(referencedCode), sourceFileName, referencedFileName);
}

private async Task VerifyItemWithReferenceWorkerAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
Expand All @@ -35,11 +36,10 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition,

public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.CommonTriggerCharacters;

protected override ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel,
protected override Task<ImmutableArray<string>> GetImportedNamespacesAsync(
SyntaxContext syntaxContext,
CancellationToken cancellationToken)
=> ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel);
=> ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken);

protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,39 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
internal static class ImportCompletionProviderHelper
{
public static ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel)
=> semanticModel.GetUsingNamespacesInScope(location)
.SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
public static async Task<ImmutableArray<string>> GetImportedNamespacesAsync(SyntaxContext context, CancellationToken token)
{
// The location is the containing node of the LeftToken, or the compilation unit itself if LeftToken
// indicates the beginning of the document (i.e. no parent).
var location = context.LeftToken.Parent ?? context.SyntaxTree.GetRoot(token);
var usings = context.SemanticModel.GetUsingNamespacesInScope(location);

// Since we don't have a compiler API to easily get all global usings yet,
// hardcode the the name of SDK auto-generated global using file for now
// as a temporary workaround.
var fileName = context.Document.Project.Name + ".GlobalUsings.g.cs";
var globalUsingDocument = context.Document.Project.Documents.FirstOrDefault(d => d.Name.Equals(fileName));
genlu marked this conversation as resolved.
Show resolved Hide resolved
if (globalUsingDocument != null)
{
var root = await globalUsingDocument.GetRequiredSyntaxRootAsync(token).ConfigureAwait(false);
var model = await globalUsingDocument.GetRequiredSemanticModelAsync(token).ConfigureAwait(false);
var globalUsings = model.GetUsingNamespacesInScope(root);
usings.UnionWith(globalUsings);
}

return usings.SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
Expand All @@ -37,11 +38,10 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition,

public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.CommonTriggerCharacters;

protected override ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel,
protected override Task<ImmutableArray<string>> GetImportedNamespacesAsync(
SyntaxContext syntaxContext,
CancellationToken cancellationToken)
=> ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel);
=> ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken);

protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Completion.Log;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
Expand All @@ -22,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal abstract class AbstractImportCompletionProvider : LSPCompletionProvider, INotifyCommittingItemCompletionProvider
{
protected abstract ImmutableArray<string> GetImportedNamespaces(SyntaxNode location, SemanticModel semanticModel, CancellationToken cancellationToken);
protected abstract Task<ImmutableArray<string>> GetImportedNamespacesAsync(SyntaxContext syntaxContext, CancellationToken cancellationToken);
protected abstract bool ShouldProvideCompletion(CompletionContext completionContext, SyntaxContext syntaxContext);
protected abstract Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet<string> namespacesInScope, CancellationToken cancellationToken);
protected abstract bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token);
Expand Down Expand Up @@ -58,7 +57,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext completionC

// Find all namespaces in scope at current cursor location,
// which will be used to filter so the provider only returns out-of-scope types.
var namespacesInScope = GetNamespacesInScope(document, syntaxContext, cancellationToken);
var namespacesInScope = await GetNamespacesInScopeAsync(syntaxContext, cancellationToken).ConfigureAwait(false);
await AddCompletionItemsAsync(completionContext, syntaxContext, namespacesInScope, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -70,14 +69,12 @@ private static async Task<SyntaxContext> CreateContextAsync(Document document, i
return document.GetRequiredLanguageService<ISyntaxContextService>().CreateContext(document, semanticModel, position, cancellationToken);
}

private HashSet<string> GetNamespacesInScope(Document document, SyntaxContext syntaxContext, CancellationToken cancellationToken)
private async Task<HashSet<string>> GetNamespacesInScopeAsync(SyntaxContext syntaxContext, CancellationToken cancellationToken)
{
var semanticModel = syntaxContext.SemanticModel;
var document = syntaxContext.Document;

// The location is the containing node of the LeftToken, or the compilation unit itself if LeftToken
// indicates the beginning of the document (i.e. no parent).
var location = syntaxContext.LeftToken.Parent ?? syntaxContext.SyntaxTree.GetRoot(cancellationToken);
var importedNamespaces = GetImportedNamespaces(location, semanticModel, cancellationToken);
var importedNamespaces = await GetImportedNamespacesAsync(syntaxContext, cancellationToken).ConfigureAwait(false);

// This hashset will be used to match namespace names, so it must have the same case-sensitivity as the source language.
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.[Shared].Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.Text

Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Expand Down Expand Up @@ -41,8 +42,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers

Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerCharsAndParen

Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String)
Return ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel)
Protected Overrides Function GetImportedNamespacesAsync(syntaxContext As SyntaxContext, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of String))
Return ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken)
End Function

Protected Overrides Function IsFinalSemicolonOfUsingOrExtern(directive As SyntaxNode, token As SyntaxToken) As Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@
' See the LICENSE file in the project root for more information.

Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.[Shared].Extensions.ContextQuery

Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers

Friend NotInheritable Class ImportCompletionProviderHelper

Public Shared Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel) As ImmutableArray(Of String)
Public Shared Async Function GetImportedNamespacesAsync(syntaxContext As SyntaxContext, token As CancellationToken) As Task(Of ImmutableArray(Of String))

' The location Is the containing node of the LeftToken, Or the compilation unit itself if LeftToken
' indicates the beginning of the document (i.e. no parent).
Dim Location = If(syntaxContext.LeftToken.Parent, Await syntaxContext.SyntaxTree.GetRootAsync(token).ConfigureAwait(False))

Dim builder = ArrayBuilder(Of String).GetInstance()

' Get namespaces from import directives
Dim importsInScope = semanticModel.GetImportNamespacesInScope(location)
Dim importsInScope = syntaxContext.SemanticModel.GetImportNamespacesInScope(Location)
For Each import As INamespaceSymbol In importsInScope
builder.Add(import.ToDisplayString(SymbolDisplayFormats.NameFormat))
Next

' Get global imports from compilation option
Dim vbOptions = DirectCast(semanticModel.Compilation.Options, VisualBasicCompilationOptions)
Dim vbOptions = DirectCast(syntaxContext.SemanticModel.Compilation.Options, VisualBasicCompilationOptions)
For Each globalImport As GlobalImport In vbOptions.GlobalImports
builder.Add(globalImport.Name)
Next
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.[Shared].Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Expand Down Expand Up @@ -36,8 +37,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers

Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerCharsAndParen

Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String)
Return ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel)
Protected Overrides Function GetImportedNamespacesAsync(syntaxContext As SyntaxContext, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of String))
Return ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
End Function

Protected Overrides Function IsFinalSemicolonOfUsingOrExtern(directive As SyntaxNode, token As SyntaxToken) As Boolean
Expand Down