diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs
index c638c5a3c690..2dbe4c4df911 100644
--- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs
+++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs
@@ -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);
diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs
index c408f900de8f..446013410a66 100644
--- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs
+++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs
@@ -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(@"
-
- {1}
- {2}
+
+ {1}
+ {2}
-", sourceLanguage, SecurityElement.Escape(markup), SecurityElement.Escape(referencedCode));
+", sourceLanguage, SecurityElement.Escape(sourceCode), SecurityElement.Escape(referencedCode), sourceFileName, referencedFileName);
}
private async Task VerifyItemWithReferenceWorkerAsync(
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs
index f51bae194c60..9e8bd6952bc0 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs
@@ -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
@@ -35,11 +36,10 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition,
public override ImmutableHashSet TriggerCharacters { get; } = CompletionUtilities.CommonTriggerCharacters;
- protected override ImmutableArray GetImportedNamespaces(
- SyntaxNode location,
- SemanticModel semanticModel,
+ protected override Task> GetImportedNamespacesAsync(
+ SyntaxContext syntaxContext,
CancellationToken cancellationToken)
- => ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel);
+ => ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken);
protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token)
{
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs
index 31e8e19dafc5..ab3df7582d3e 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs
@@ -2,19 +2,80 @@
// 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.Collections.Concurrent;
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 GetImportedNamespaces(
- SyntaxNode location,
- SemanticModel semanticModel)
- => semanticModel.GetUsingNamespacesInScope(location)
- .SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
+ private record class CacheEntry(DocumentId? GlobalUsingsDocumentId, Checksum GlobalUsingsDocumentChecksum, ImmutableArray GlobalUsings)
+ {
+ public static CacheEntry Default { get; } = new(null, Checksum.Null, ImmutableArray.Empty);
+ }
+
+ private static readonly ConcurrentDictionary _sdkGlobalUsingsCache = new();
+
+ public static async Task> GetImportedNamespacesAsync(SyntaxContext context, CancellationToken cancellationToken)
+ {
+ // 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(cancellationToken);
+ var usingsFromCurrentDocument = context.SemanticModel.GetUsingNamespacesInScope(location).SelectAsArray(GetNamespaceName);
+
+ if (_sdkGlobalUsingsCache.TryGetValue(context.Document.Project.Id, out var cacheEntry))
+ {
+ // Just return whatever was cached last time. It'd be very rare for this file to change. To minimize impact on completion perf,
+ // we'd tolerate temporarily staled results. A background task is created to refresh it if necessary.
+ _ = GetGlobalUsingsAsync(context.Document.Project, cacheEntry, CancellationToken.None);
+ return usingsFromCurrentDocument.Concat(cacheEntry.GlobalUsings);
+ }
+ else
+ {
+ // cache miss, we have to calculate it now.
+ var globalUsings = await GetGlobalUsingsAsync(context.Document.Project, CacheEntry.Default, cancellationToken).ConfigureAwait(false);
+ return usingsFromCurrentDocument.Concat(globalUsings);
+ }
+
+ static async Task> GetGlobalUsingsAsync(Project project, CacheEntry cacheEntry, CancellationToken cancellationToken)
+ {
+ // 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 (which is a constant) for now as a temporary workaround.
+ // https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.GenerateGlobalUsings.targets
+ var fileName = project.Name + ".GlobalUsings.g.cs";
+
+ var globalUsingDocument = cacheEntry.GlobalUsingsDocumentId is null
+ ? project.Documents.FirstOrDefault(d => d.Name.Equals(fileName))
+ : await project.GetDocumentAsync(cacheEntry.GlobalUsingsDocumentId, cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ if (globalUsingDocument is null)
+ {
+ _sdkGlobalUsingsCache[project.Id] = CacheEntry.Default;
+ return CacheEntry.Default.GlobalUsings;
+ }
+
+ // We only checksum off of the contents of the file.
+ var checksum = await globalUsingDocument.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
+ if (checksum == cacheEntry.GlobalUsingsDocumentChecksum)
+ return cacheEntry.GlobalUsings;
+
+ var root = await globalUsingDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ var model = await globalUsingDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
+ var globalUsings = model.GetUsingNamespacesInScope(root).SelectAsArray(GetNamespaceName);
+
+ _sdkGlobalUsingsCache[project.Id] = new(globalUsingDocument.Id, checksum, globalUsings);
+ return globalUsings;
+ }
+
+ static string GetNamespaceName(INamespaceSymbol symbol)
+ => symbol.ToDisplayString(SymbolDisplayFormats.NameFormat);
+ }
}
}
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs
index 1b8e810b2762..f23a6b53d09a 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs
@@ -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
@@ -37,11 +38,10 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition,
public override ImmutableHashSet TriggerCharacters { get; } = CompletionUtilities.CommonTriggerCharacters;
- protected override ImmutableArray GetImportedNamespaces(
- SyntaxNode location,
- SemanticModel semanticModel,
+ protected override Task> GetImportedNamespacesAsync(
+ SyntaxContext syntaxContext,
CancellationToken cancellationToken)
- => ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel);
+ => ImportCompletionProviderHelper.GetImportedNamespacesAsync(syntaxContext, cancellationToken);
protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token)
{
diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs
index 07c788112071..610cea8ecff2 100644
--- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs
+++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs
@@ -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;
@@ -22,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal abstract class AbstractImportCompletionProvider : LSPCompletionProvider, INotifyCommittingItemCompletionProvider
{
- protected abstract ImmutableArray GetImportedNamespaces(SyntaxNode location, SemanticModel semanticModel, CancellationToken cancellationToken);
+ protected abstract Task> GetImportedNamespacesAsync(SyntaxContext syntaxContext, CancellationToken cancellationToken);
protected abstract bool ShouldProvideCompletion(CompletionContext completionContext, SyntaxContext syntaxContext);
protected abstract Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet namespacesInScope, CancellationToken cancellationToken);
protected abstract bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token);
@@ -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);
}
@@ -70,14 +69,12 @@ private static async Task CreateContextAsync(Document document, i
return document.GetRequiredLanguageService().CreateContext(document, semanticModel, position, cancellationToken);
}
- private HashSet GetNamespacesInScope(Document document, SyntaxContext syntaxContext, CancellationToken cancellationToken)
+ private async Task> 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();
diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb
index f2002af0362a..dd514063e469 100644
--- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb
+++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb
@@ -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
@@ -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
diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb
index 5a09a6ad7633..948719ea1364 100644
--- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb
+++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb
@@ -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
diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb
index bf843dc88aa0..6d0ac1e851db 100644
--- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb
+++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb
@@ -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
@@ -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)
End Function
Protected Overrides Function IsFinalSemicolonOfUsingOrExtern(directive As SyntaxNode, token As SyntaxToken) As Boolean