未验证 提交 a298cd75 编写于 作者: G Gen Lu 提交者: GitHub

Merge pull request #36395 from genlu/AttributeCompletion

Don't show full attribute type name if in attribute name context
...@@ -118,6 +118,8 @@ internal static bool IsValidClrNamespaceName(this string name) ...@@ -118,6 +118,8 @@ internal static bool IsValidClrNamespaceName(this string name)
return lastChar != '.'; return lastChar != '.';
} }
private const string AttributeSuffix = "Attribute";
internal static string GetWithSingleAttributeSuffix( internal static string GetWithSingleAttributeSuffix(
this string name, this string name,
bool isCaseSensitive) bool isCaseSensitive)
...@@ -128,7 +130,7 @@ internal static bool IsValidClrNamespaceName(this string name) ...@@ -128,7 +130,7 @@ internal static bool IsValidClrNamespaceName(this string name)
name = cleaned; name = cleaned;
} }
return name + "Attribute"; return name + AttributeSuffix;
} }
internal static bool TryGetWithoutAttributeSuffix( internal static bool TryGetWithoutAttributeSuffix(
...@@ -150,9 +152,7 @@ internal static bool IsValidClrNamespaceName(this string name) ...@@ -150,9 +152,7 @@ internal static bool IsValidClrNamespaceName(this string name)
bool isCaseSensitive, bool isCaseSensitive,
out string result) out string result)
{ {
const string AttributeSuffix = "Attribute"; if (name.HasAttributeSuffix(isCaseSensitive))
var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
if (name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison))
{ {
result = name.Substring(0, name.Length - AttributeSuffix.Length); result = name.Substring(0, name.Length - AttributeSuffix.Length);
return true; return true;
...@@ -162,6 +162,12 @@ internal static bool IsValidClrNamespaceName(this string name) ...@@ -162,6 +162,12 @@ internal static bool IsValidClrNamespaceName(this string name)
return false; return false;
} }
internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive)
{
var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison);
}
internal static bool IsValidUnicodeString(this string str) internal static bool IsValidUnicodeString(this string str)
{ {
int i = 0; int i = 0;
......
...@@ -907,6 +907,278 @@ class Bat ...@@ -907,6 +907,278 @@ class Bat
AssertRelativeOrder(new List<string>() { "SomeType", "SomeTypeWithLongerName" }, completionList.Items); AssertRelativeOrder(new List<string>() { "SomeType", "SomeTypeWithLongerName" }, completionList.Items);
} }
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeInAttributeNameContext()
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
public class MyAttributeWithoutSuffix : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "My", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttributeWithoutSuffix", inlineDescription: "Foo"); // We intentionally ignore attribute types without proper suffix for perf reason
await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttribute", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeInAttributeNameContext(SourceCodeKind kind)
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
[My$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "My", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeInNonAttributeNameContext()
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
public class MyAttributeWithoutSuffix : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "MyAttribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute");
await VerifyTypeImportItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttributeWithoutSuffix");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeInNonAttributeNameContext(SourceCodeKind kind)
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
class Program
{
MyAttribute$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "MyAttribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeWithoutSuffixInAttributeNameContext()
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeWithoutSuffixInAttributeNameContext(SourceCodeKind kind)
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
[Myattribute$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "Myattribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeWithoutSuffixInNonAttributeNameContext()
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeWithoutSuffixInNonAttributeNameContext(SourceCodeKind kind)
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
class Program
{
Myattribute$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "Myattribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task VBAttributeTypeWithoutSuffixInAttributeNameContext()
{
var file1 = @"
Namespace Foo
Public Class Myattribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
End Namespace";
var file2 = @"
namespace Test
{
[$$
class Program
{
}
}";
var markup = CreateMarkupForProjecWithProjectReference(file2, file1, LanguageNames.CSharp, LanguageNames.VisualBasic);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyVBClass", inlineDescription: "Foo");
}
private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrder, ImmutableArray<CompletionItem> allCompletionItems) private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrder, ImmutableArray<CompletionItem> allCompletionItems)
{ {
var hashset = new HashSet<string>(expectedTypesInRelativeOrder); var hashset = new HashSet<string>(expectedTypesInRelativeOrder);
...@@ -919,9 +1191,9 @@ private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrde ...@@ -919,9 +1191,9 @@ private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrde
} }
} }
private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null) private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null)
{ {
return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription); return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull);
} }
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.LanguageServices;
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Experiments
Imports Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Imports Microsoft.VisualStudio.Composition
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.CompletionProviders
<UseExportProvider>
Public Class TypeImportCompletionProviderTests
Inherits AbstractVisualBasicCompletionProviderTests
Public Sub New(workspaceFixture As VisualBasicTestWorkspaceFixture)
MyBase.New(workspaceFixture)
End Sub
Private Property ShowImportCompletionItemsOptionValue As Boolean = True
Protected Overrides Sub SetWorkspaceOptions(workspace As TestWorkspace)
workspace.Options = workspace.Options.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue)
End Sub
Protected Overrides Function GetExportProvider() As ExportProvider
Return ExportProviderCache.GetOrCreateExportProviderFactory(TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(GetType(TestExperimentationService))).CreateExportProvider()
End Function
Friend Overrides Function CreateCompletionProvider() As CompletionProvider
Return New TypeImportCompletionProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInAttributeNameContext() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyAttribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
Public Class MyAttributeWithoutSuffix
Inherits System.Attribute
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttribute")
Await VerifyItemIsAbsentAsync(markup, "MyAttributeWithoutSuffix", inlineDescription:="Foo") ' We intentionally ignore attribute types without proper suffix for perf reason
Await VerifyItemIsAbsentAsync(markup, "MyAttribute", inlineDescription:="Foo")
Await VerifyItemIsAbsentAsync(markup, "MyVBClass", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInNonAttributeNameContext() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyAttribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
Public Class MyAttributeWithoutSuffix
Inherits System.Attribute
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
Sub Main()
Dim x As $$
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "MyAttribute", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttribute")
Await VerifyItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttributeWithoutSuffix")
Await VerifyItemExistsAsync(markup, "MyVBClass", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyVBClass")
Await VerifyItemIsAbsentAsync(markup, "My", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInAttributeNameContext2() As Task
' attribute suffix isn't capitalized
Dim file1 = <Text>
Namespace Foo
Public Class Myattribute
Inherits System.Attribute
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.Myattribute")
Await VerifyItemIsAbsentAsync(markup, "Myattribute", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function CSharpAttributeTypeWithoutSuffixInAttributeNameContext() As Task
' attribute suffix isn't capitalized
Dim file1 = <Text>
namespace Foo
{
public class Myattribute : System.Attribute { }
}</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForProjecWithProjectReference(file2, file1, LanguageNames.VisualBasic, LanguageNames.CSharp)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.Myattribute")
Await VerifyItemIsAbsentAsync(markup, "Myattribute", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35124, "https://github.com/dotnet/roslyn/issues/35124")>
Public Async Function GenericTypeShouldDisplayProperVBSyntax() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyGenericClass(Of T)
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
Sub Main()
Dim x As $$
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "MyGenericClass", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", displayTextSuffix:="(Of ...)", expectedDescriptionOrNull:="Class Foo.MyGenericClass(Of T)")
End Function
End Class
End Namespace
// 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.Composition;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
[ExportLanguageServiceFactory(typeof(ITypeImportCompletionService), LanguageNames.CSharp), Shared]
internal sealed class CSharpTypeImportCompletionServiceFactory : ILanguageServiceFactory
{
[ImportingConstructor]
public CSharpTypeImportCompletionServiceFactory()
{
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
return new CSharpTypeImportCompletionService(languageServices.WorkspaceServices.Workspace);
}
private class CSharpTypeImportCompletionService : AbstractTypeImportCompletionService
{
public CSharpTypeImportCompletionService(Workspace workspace)
: base(workspace)
{
}
protected override string GenericTypeSuffix
=> "<>";
protected override bool IsCaseSensitive => true;
}
}
}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.Completion.Log; using Microsoft.CodeAnalysis.Completion.Log;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Experiments;
...@@ -78,7 +79,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext, ...@@ -78,7 +79,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext,
var document = completionContext.Document; var document = completionContext.Document;
var project = document.Project; var project = document.Project;
var workspace = project.Solution.Workspace; var workspace = project.Solution.Workspace;
var typeImportCompletionService = workspace.Services.GetService<ITypeImportCompletionService>(); var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>();
// Find all namespaces in scope at current cursor location, // Find all namespaces in scope at current cursor location,
// which will be used to filter so the provider only returns out-of-scope types. // which will be used to filter so the provider only returns out-of-scope types.
...@@ -86,7 +87,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext, ...@@ -86,7 +87,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext,
// Get completion items from current project. // Get completion items from current project.
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAndInternalItem, cancellationToken) await typeImportCompletionService.GetTopLevelTypesAsync(project, syntaxContext, HandlePublicAndInternalItem, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
// Get declarations from directly referenced projects and PEs // Get declarations from directly referenced projects and PEs
...@@ -105,6 +106,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd ...@@ -105,6 +106,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
{ {
await typeImportCompletionService.GetTopLevelTypesAsync( await typeImportCompletionService.GetTopLevelTypesAsync(
assemblyProject, assemblyProject,
syntaxContext,
GetHandler(compilation.Assembly, assembly), GetHandler(compilation.Assembly, assembly),
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
} }
...@@ -114,6 +116,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd ...@@ -114,6 +116,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
project.Solution, project.Solution,
compilation, compilation,
peReference, peReference,
syntaxContext,
GetHandler(compilation.Assembly, assembly), GetHandler(compilation.Assembly, assembly),
cancellationToken); cancellationToken);
} }
...@@ -125,24 +128,24 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd ...@@ -125,24 +128,24 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
return; return;
// Decide which item handler to use based on IVT // Decide which item handler to use based on IVT
Action<TypeImportCompletionItemInfo> GetHandler(IAssemblySymbol assembly, IAssemblySymbol referencedAssembly) Action<CompletionItem, bool> GetHandler(IAssemblySymbol assembly, IAssemblySymbol referencedAssembly)
=> assembly.IsSameAssemblyOrHasFriendAccessTo(referencedAssembly) => assembly.IsSameAssemblyOrHasFriendAccessTo(referencedAssembly)
? (Action<TypeImportCompletionItemInfo>)HandlePublicAndInternalItem ? (Action<CompletionItem, bool>)HandlePublicAndInternalItem
: HandlePublicItem; : HandlePublicItem;
// Add only public types to completion list // Add only public types to completion list
void HandlePublicItem(TypeImportCompletionItemInfo itemInfo) void HandlePublicItem(CompletionItem item, bool isPublic)
=> AddItems(itemInfo, isInternalsVisible: false, completionContext, namespacesInScope, telemetryCounter); => AddItems(item, isPublic, isInternalsVisible: false, completionContext, namespacesInScope, telemetryCounter);
// Add both public and internal types to completion list // Add both public and internal types to completion list
void HandlePublicAndInternalItem(TypeImportCompletionItemInfo itemInfo) void HandlePublicAndInternalItem(CompletionItem item, bool isPublic)
=> AddItems(itemInfo, isInternalsVisible: true, completionContext, namespacesInScope, telemetryCounter); => AddItems(item, isPublic, isInternalsVisible: true, completionContext, namespacesInScope, telemetryCounter);
static void AddItems(TypeImportCompletionItemInfo itemInfo, bool isInternalsVisible, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter) static void AddItems(CompletionItem item, bool isPublic, bool isInternalsVisible, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
{ {
if (itemInfo.IsPublic || isInternalsVisible) if (isPublic || isInternalsVisible)
{ {
var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(itemInfo.Item); var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(item);
if (!namespacesInScope.Contains(containingNamespace)) if (!namespacesInScope.Contains(containingNamespace))
{ {
// We can return cached item directly, item's span will be fixed by completion service. // We can return cached item directly, item's span will be fixed by completion service.
...@@ -150,7 +153,7 @@ static void AddItems(TypeImportCompletionItemInfo itemInfo, bool isInternalsVisi ...@@ -150,7 +153,7 @@ static void AddItems(TypeImportCompletionItemInfo itemInfo, bool isInternalsVisi
// On the other hand, because of this (i.e. mutating the span of cached item for each run), // On the other hand, because of this (i.e. mutating the span of cached item for each run),
// the provider can not be used as a service by components that might be run in parallel // the provider can not be used as a service by components that might be run in parallel
// with completion, which would be a race. // with completion, which would be a race.
completionContext.AddItem(itemInfo.Item); completionContext.AddItem(item);
counter.ItemsCount++; ; counter.ItemsCount++; ;
} }
} }
......
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract partial class AbstractTypeImportCompletionService : ITypeImportCompletionService
{
private ITypeImportCompletionCacheService CacheService { get; }
protected abstract string GenericTypeSuffix { get; }
protected abstract bool IsCaseSensitive { get; }
internal AbstractTypeImportCompletionService(Workspace workspace)
{
CacheService = workspace.Services.GetService<ITypeImportCompletionCacheService>();
}
public async Task GetTopLevelTypesAsync(
Project project,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
throw new ArgumentException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
GetAccessibleTopLevelTypesWorker(
project.Id,
compilation.Assembly,
checksum,
syntaxContext,
handleItem,
CacheService.ProjectItemsCache,
cancellationToken);
}
public void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken)
{
var key = GetReferenceKey(peReference);
if (key == null)
{
// Can't cache items for reference with null key. We don't want risk potential perf regression by
// making those items repeatedly, so simply not returning anything from this assembly, until
// we have a better understanding on this sceanrio.
// TODO: Add telemetry
return;
}
if (!(compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assemblySymbol))
{
return;
}
var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken);
GetAccessibleTopLevelTypesWorker(
key,
assemblySymbol,
checksum,
syntaxContext,
handleItem,
CacheService.PEItemsCache,
cancellationToken);
return;
static string GetReferenceKey(PortableExecutableReference reference)
=> reference.FilePath ?? reference.Display;
}
private void GetAccessibleTopLevelTypesWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
IDictionary<TKey, ReferenceCacheEntry> cache,
CancellationToken cancellationToken)
{
var language = syntaxContext.SemanticModel.Language;
// Cache miss, create all requested items.
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
var builder = new ReferenceCacheEntry.Builder(checksum, language, GenericTypeSuffix);
GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, builder, cancellationToken);
cacheEntry = builder.ToReferenceCacheEntry();
cache[key] = cacheEntry;
}
foreach (var itemInfo in cacheEntry.GetItemsForContext(language, GenericTypeSuffix, syntaxContext.IsAttributeNameContext, IsCaseSensitive))
{
handleItem(itemInfo.Item, itemInfo.IsPublic);
}
}
private static void GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
ReferenceCacheEntry.Builder builder,
CancellationToken cancellationToken)
{
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
return;
static void VisitNamespace(
INamespaceSymbol symbol,
string containingNamespace,
ReferenceCacheEntry.Builder builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
containingNamespace = ConcatNamespace(containingNamespace, symbol.Name);
foreach (var memberNamespace in symbol.GetNamespaceMembers())
{
VisitNamespace(memberNamespace, containingNamespace, builder, cancellationToken);
}
var overloads = PooledDictionary<string, TypeOverloadInfo>.GetInstance();
var types = symbol.GetTypeMembers();
// Iterate over all top level internal and public types, keep track of "type overloads".
foreach (var type in types)
{
// No need to check accessibility here, since top level types can only be internal or public.
if (type.CanBeReferencedByName)
{
overloads.TryGetValue(type.Name, out var overloadInfo);
overloads[type.Name] = overloadInfo.Aggregate(type);
}
}
foreach (var pair in overloads)
{
var overloadInfo = pair.Value;
// Create CompletionItem for non-generic type overload, if exists.
if (overloadInfo.NonGenericOverload != null)
{
builder.AddItem(
overloadInfo.NonGenericOverload,
containingNamespace,
overloadInfo.NonGenericOverload.DeclaredAccessibility == Accessibility.Public);
}
// Create one CompletionItem for all generic type overloads, if there's any.
// For simplicity, we always show the type symbol with lowest arity in CompletionDescription
// and without displaying the total number of overloads.
if (overloadInfo.BestGenericOverload != null)
{
// If any of the generic overloads is public, then the completion item is considered public.
builder.AddItem(
overloadInfo.BestGenericOverload,
containingNamespace,
overloadInfo.ContainsPublicGenericOverload);
}
}
overloads.Free();
}
}
private static string ConcatNamespace(string containingNamespace, string name)
{
Debug.Assert(name != null);
if (string.IsNullOrEmpty(containingNamespace))
{
return name;
}
return containingNamespace + "." + name;
}
private readonly struct TypeOverloadInfo
{
public TypeOverloadInfo(INamedTypeSymbol nonGenericOverload, INamedTypeSymbol bestGenericOverload, bool containsPublicGenericOverload)
{
NonGenericOverload = nonGenericOverload;
BestGenericOverload = bestGenericOverload;
ContainsPublicGenericOverload = containsPublicGenericOverload;
}
public INamedTypeSymbol NonGenericOverload { get; }
// Generic with fewest type parameters is considered best symbol to show in description.
public INamedTypeSymbol BestGenericOverload { get; }
public bool ContainsPublicGenericOverload { get; }
public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
{
if (type.Arity == 0)
{
return new TypeOverloadInfo(nonGenericOverload: type, BestGenericOverload, ContainsPublicGenericOverload);
}
// We consider generic with fewer type parameters better symbol to show in description
var newBestGenericOverload = BestGenericOverload == null || type.Arity < BestGenericOverload.Arity
? type
: BestGenericOverload;
var newContainsPublicGenericOverload = type.DeclaredAccessibility >= Accessibility.Public || ContainsPublicGenericOverload;
return new TypeOverloadInfo(NonGenericOverload, newBestGenericOverload, newContainsPublicGenericOverload);
}
}
private readonly struct ReferenceCacheEntry
{
public class Builder
{
private readonly string _language;
private readonly string _genericTypeSuffix;
private readonly Checksum _checksum;
public Builder(Checksum checksum, string language, string genericTypeSuffix)
{
_checksum = checksum;
_language = language;
_genericTypeSuffix = genericTypeSuffix;
_itemsBuilder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
}
private ArrayBuilder<TypeImportCompletionItemInfo> _itemsBuilder;
public ReferenceCacheEntry ToReferenceCacheEntry()
{
return new ReferenceCacheEntry(
_checksum,
_language,
_itemsBuilder.ToImmutableAndFree());
}
public void AddItem(INamedTypeSymbol symbol, string containingNamespace, bool isPublic)
{
var isGeneric = symbol.Arity > 0;
// Need to determine if a type is an attribute up front since we want to filter out
// non-attribute types when in attribute context. We can't do this lazily since we don't hold
// on to symbols. However, the cost of calling `IsAttribute` on every top-level type symbols
// is prohibitively high, so we opt for the heuristic that would do the simple textual "Attribute"
// suffix check first, then the more expensive symbolic check. As a result, all unimported
// attribute types that don't have "Attribute" suffix would be filtered out when in attribute context.
var isAttribute = symbol.Name.HasAttributeSuffix(isCaseSensitive: false) && symbol.IsAttribute();
var item = TypeImportCompletionItem.Create(symbol, containingNamespace, _genericTypeSuffix);
item.IsCached = true;
_itemsBuilder.Add(new TypeImportCompletionItemInfo(item, isPublic, isGeneric, isAttribute));
return;
}
}
private ReferenceCacheEntry(
Checksum checksum,
string language,
ImmutableArray<TypeImportCompletionItemInfo> items)
{
Checksum = checksum;
Language = language;
ItemInfos = items;
}
public string Language { get; }
public Checksum Checksum { get; }
private ImmutableArray<TypeImportCompletionItemInfo> ItemInfos { get; }
public ImmutableArray<TypeImportCompletionItemInfo> GetItemsForContext(string language, string genericTypeSuffix, bool isAttributeContext, bool isCaseSensitive)
{
var isSameLanguage = Language == language;
if (isSameLanguage && !isAttributeContext)
{
return ItemInfos;
}
var builder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
foreach (var info in ItemInfos)
{
CompletionItem item = info.Item;
if (isAttributeContext)
{
if (!info.IsAttribute)
{
continue;
}
item = GetAppropriateAttributeItem(info.Item, isCaseSensitive);
}
if (!isSameLanguage && info.IsGeneric)
{
// We don't want to cache this item.
item = TypeImportCompletionItem.CreateItemWithGenericDisplaySuffix(item, genericTypeSuffix);
}
builder.Add(info.WithItem(item));
}
return builder.ToImmutableAndFree();
static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive)
{
if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix))
{
// We don't want to cache this item.
return TypeImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix);
}
return attributeItem;
}
}
}
private readonly struct TypeImportCompletionItemInfo
{
private readonly ItemPropertyKind _properties;
public TypeImportCompletionItemInfo(CompletionItem item, bool isPublic, bool isGeneric, bool isAttribute)
{
Item = item;
_properties = (isPublic ? ItemPropertyKind.IsPublic : 0)
| (isGeneric ? ItemPropertyKind.IsGeneric : 0)
| (isAttribute ? ItemPropertyKind.IsAttribute : 0);
}
public CompletionItem Item { get; }
public bool IsPublic
=> (_properties & ItemPropertyKind.IsPublic) != 0;
public bool IsGeneric
=> (_properties & ItemPropertyKind.IsGeneric) != 0;
public bool IsAttribute
=> (_properties & ItemPropertyKind.IsAttribute) != 0;
public TypeImportCompletionItemInfo WithItem(CompletionItem item)
{
return new TypeImportCompletionItemInfo(item, IsPublic, IsGeneric, IsAttribute);
}
[Flags]
private enum ItemPropertyKind : byte
{
IsPublic = 0x1,
IsGeneric = 0x2,
IsAttribute = 0x4,
}
}
}
}
// 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal partial class AbstractTypeImportCompletionService
{
private interface ITypeImportCompletionCacheService : IWorkspaceService
{
// PE references are keyed on assembly path.
IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
}
[ExportWorkspaceServiceFactory(typeof(ITypeImportCompletionCacheService), ServiceLayer.Editor), Shared]
private class TypeImportCompletionCacheServiceFactory : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public TypeImportCompletionCacheServiceFactory()
{
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new TypeImportCompletionCacheService(_peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class TypeImportCompletionCacheService : ITypeImportCompletionCacheService
{
public IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
public TypeImportCompletionCacheService(
ConcurrentDictionary<string, ReferenceCacheEntry> peCache,
ConcurrentDictionary<ProjectId, ReferenceCacheEntry> projectCache)
{
PEItemsCache = peCache;
ProjectItemsCache = projectCache;
}
}
}
}
}
// 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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal interface ITypeImportCompletionService : ILanguageService
{
/// <summary>
/// Get all the top level types from given project. This method is intended to be used for
/// getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="GetTopLevelTypesFromPEReference"/>.
/// </summary>
Task GetTopLevelTypesAsync(
Project project,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken);
void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken);
}
}
...@@ -16,8 +16,9 @@ internal static class TypeImportCompletionItem ...@@ -16,8 +16,9 @@ internal static class TypeImportCompletionItem
private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" }; private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" };
private const string TypeAritySuffixName = nameof(TypeAritySuffixName); private const string TypeAritySuffixName = nameof(TypeAritySuffixName);
private const string AttributeFullName = nameof(AttributeFullName);
public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace) public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace, string genericTypeSuffix)
{ {
PooledDictionary<string, string> propertyBuilder = null; PooledDictionary<string, string> propertyBuilder = null;
...@@ -27,17 +28,13 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi ...@@ -27,17 +28,13 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi
propertyBuilder.Add(TypeAritySuffixName, GetAritySuffix(typeSymbol.Arity)); propertyBuilder.Add(TypeAritySuffixName, GetAritySuffix(typeSymbol.Arity));
} }
// Hack: add tildes (ASCII: 126) to name and namespace as sort text: // Add tildes (ASCII: 126) to name and namespace as sort text:
// 1. '~' before type name makes import items show after in-scope items // 1. '~' before type name makes import items show after in-scope items
// 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list, // 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list,
// it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'. // it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'.
var sortTextBuilder = PooledStringBuilder.GetInstance(); var sortTextBuilder = PooledStringBuilder.GetInstance();
sortTextBuilder.Builder.AppendFormat(SortTextFormat, typeSymbol.Name, containingNamespace); sortTextBuilder.Builder.AppendFormat(SortTextFormat, typeSymbol.Name, containingNamespace);
// TODO:
// 1. Suffix should be language specific, i.e. `(Of ...)` if triggered from VB.
// 2. Sort the import items to be after in-scope symbols in a less hacky way.
// 3. Editor support for resolving item text conflicts?
return CompletionItem.Create( return CompletionItem.Create(
displayText: typeSymbol.Name, displayText: typeSymbol.Name,
filterText: typeSymbol.Name, filterText: typeSymbol.Name,
...@@ -46,10 +43,37 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi ...@@ -46,10 +43,37 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi
tags: GlyphTags.GetTags(typeSymbol.GetGlyph()), tags: GlyphTags.GetTags(typeSymbol.GetGlyph()),
rules: CompletionItemRules.Default, rules: CompletionItemRules.Default,
displayTextPrefix: null, displayTextPrefix: null,
displayTextSuffix: typeSymbol.Arity == 0 ? string.Empty : "<>", displayTextSuffix: typeSymbol.Arity == 0 ? string.Empty : genericTypeSuffix,
inlineDescription: containingNamespace); inlineDescription: containingNamespace);
} }
public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem attributeItem, string attributeNameWithoutSuffix)
{
Debug.Assert(!attributeItem.Properties.ContainsKey(AttributeFullName));
// Remember the full type name so we can get the symbol when description is displayed.
var newProperties = attributeItem.Properties.Add(AttributeFullName, attributeItem.DisplayText);
var sortTextBuilder = PooledStringBuilder.GetInstance();
sortTextBuilder.Builder.AppendFormat(SortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription);
return CompletionItem.Create(
displayText: attributeNameWithoutSuffix,
filterText: attributeNameWithoutSuffix,
sortText: sortTextBuilder.ToStringAndFree(),
properties: newProperties,
tags: attributeItem.Tags,
rules: attributeItem.Rules,
displayTextPrefix: attributeItem.DisplayTextPrefix,
displayTextSuffix: attributeItem.DisplayTextSuffix,
inlineDescription: attributeItem.InlineDescription);
}
public static CompletionItem CreateItemWithGenericDisplaySuffix(CompletionItem item, string genericTypeSuffix)
{
return item.WithDisplayTextSuffix(genericTypeSuffix);
}
public static string GetContainingNamespace(CompletionItem item) public static string GetContainingNamespace(CompletionItem item)
=> item.InlineDescription; => item.InlineDescription;
...@@ -95,7 +119,8 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam ...@@ -95,7 +119,8 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam
private static string GetMetadataName(CompletionItem item) private static string GetMetadataName(CompletionItem item)
{ {
var containingNamespace = GetContainingNamespace(item); var containingNamespace = GetContainingNamespace(item);
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, item.DisplayText); var typeName = item.Properties.TryGetValue(AttributeFullName, out var attributeFullName) ? attributeFullName : item.DisplayText;
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, typeName);
if (item.Properties.TryGetValue(TypeAritySuffixName, out var aritySuffix)) if (item.Properties.TryGetValue(TypeAritySuffixName, out var aritySuffix))
{ {
return fullyQualifiedName + aritySuffix; return fullyQualifiedName + aritySuffix;
......
// 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;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal interface ITypeImportCompletionService : IWorkspaceService
{
/// <summary>
/// Get all the top level types from given project. This method is intended to be used for
/// getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="GetTopLevelTypesFromPEReference"/>.
/// </summary>
Task GetTopLevelTypesAsync(
Project project,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken);
void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken);
}
internal readonly struct TypeImportCompletionItemInfo
{
public TypeImportCompletionItemInfo(CompletionItem item, bool isPublic)
{
Item = item;
IsPublic = isPublic;
}
public CompletionItem Item { get; }
public bool IsPublic { get; }
}
[ExportWorkspaceServiceFactory(typeof(ITypeImportCompletionService), ServiceLayer.Editor), Shared]
internal sealed class TypeImportCompletionService : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public TypeImportCompletionService()
{
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new Service(workspace, _peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class Service : ITypeImportCompletionService
{
// PE references are keyed on assembly path.
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache;
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache;
public Service(
Workspace workspace,
ConcurrentDictionary<string, ReferenceCacheEntry> peReferenceCache,
ConcurrentDictionary<ProjectId, ReferenceCacheEntry> projectReferenceCache)
{
_peItemsCache = peReferenceCache;
_projectItemsCache = projectReferenceCache;
workspace.WorkspaceChanged += OnWorkspaceChanged;
}
public void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
_peItemsCache.Clear();
_projectItemsCache.Clear();
break;
case WorkspaceChangeKind.ProjectRemoved:
case WorkspaceChangeKind.ProjectReloaded:
_projectItemsCache.TryRemove(e.ProjectId, out _);
break;
}
}
public async Task GetTopLevelTypesAsync(
Project project,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
throw new ArgumentException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
GetAccessibleTopLevelTypesWorker(
project.Id,
compilation.Assembly,
checksum,
handleItem,
_projectItemsCache,
cancellationToken);
}
public void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken)
{
var key = GetReferenceKey(peReference);
if (key == null)
{
// Can't cache items for reference with null key. We don't want risk potential perf regression by
// making those items repeatedly, so simply not returning anything from this assembly, until
// we have a better understanding on this sceanrio.
// TODO: Add telemetry
return;
}
if (!(compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assemblySymbol))
{
return;
}
var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken);
GetAccessibleTopLevelTypesWorker(
key,
assemblySymbol,
checksum,
handleItem,
_peItemsCache,
cancellationToken);
return;
static string GetReferenceKey(PortableExecutableReference reference)
=> reference.FilePath ?? reference.Display;
}
private static void GetAccessibleTopLevelTypesWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
Action<TypeImportCompletionItemInfo> handleItem,
ConcurrentDictionary<TKey, ReferenceCacheEntry> cache,
CancellationToken cancellationToken)
{
// Cache miss, create all requested items.
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
var items = GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, cancellationToken);
cacheEntry = new ReferenceCacheEntry(checksum, items);
cache[key] = cacheEntry;
}
foreach (var item in cacheEntry.CachedItems)
{
handleItem(item);
}
}
private static ImmutableArray<TypeImportCompletionItemInfo> GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
CancellationToken cancellationToken)
{
var builder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
return builder.ToImmutableAndFree();
static void VisitNamespace(
INamespaceSymbol symbol,
string containingNamespace,
ArrayBuilder<TypeImportCompletionItemInfo> builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
containingNamespace = ConcatNamespace(containingNamespace, symbol.Name);
foreach (var memberNamespace in symbol.GetNamespaceMembers())
{
VisitNamespace(memberNamespace, containingNamespace, builder, cancellationToken);
}
var overloads = PooledDictionary<string, TypeOverloadInfo>.GetInstance();
var types = symbol.GetTypeMembers();
// Iterate over all top level internal and public types, keep track of "type overloads".
foreach (var type in types)
{
// No need to check accessibility here, since top level types can only be internal or public.
if (type.CanBeReferencedByName)
{
overloads.TryGetValue(type.Name, out var overloadInfo);
overloads[type.Name] = overloadInfo.Aggregate(type);
}
}
foreach (var pair in overloads)
{
var overloadInfo = pair.Value;
// Create CompletionItem for non-generic type overload, if exists.
if (overloadInfo.NonGenericOverload != null)
{
var item = TypeImportCompletionItem.Create(overloadInfo.NonGenericOverload, containingNamespace);
var isPublic = overloadInfo.NonGenericOverload.DeclaredAccessibility == Accessibility.Public;
item.IsCached = true;
builder.Add(new TypeImportCompletionItemInfo(item, isPublic));
}
// Create one CompletionItem for all generic type overloads, if there's any.
// For simplicity, we always show the type symbol with lowest arity in CompletionDescription
// and without displaying the total number of overloads.
if (overloadInfo.BestGenericOverload != null)
{
// If any of the generic overloads is public, then the completion item is considered public.
var item = TypeImportCompletionItem.Create(overloadInfo.BestGenericOverload, containingNamespace);
var isPublic = overloadInfo.ContainsPublicGenericOverload;
item.IsCached = true;
builder.Add(new TypeImportCompletionItemInfo(item, isPublic));
}
}
overloads.Free();
}
}
}
private static string ConcatNamespace(string containingNamespace, string name)
{
Debug.Assert(name != null);
if (string.IsNullOrEmpty(containingNamespace))
{
return name;
}
return containingNamespace + "." + name;
}
private readonly struct TypeOverloadInfo
{
public TypeOverloadInfo(INamedTypeSymbol nonGenericOverload, INamedTypeSymbol bestGenericOverload, bool containsPublicGenericOverload)
{
NonGenericOverload = nonGenericOverload;
BestGenericOverload = bestGenericOverload;
ContainsPublicGenericOverload = containsPublicGenericOverload;
}
public INamedTypeSymbol NonGenericOverload { get; }
// Generic with fewest type parameters is considered best symbol to show in description.
public INamedTypeSymbol BestGenericOverload { get; }
public bool ContainsPublicGenericOverload { get; }
public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
{
if (type.Arity == 0)
{
return new TypeOverloadInfo(nonGenericOverload: type, BestGenericOverload, ContainsPublicGenericOverload);
}
// We consider generic with fewer type parameters better symbol to show in description
var newBestGenericOverload = BestGenericOverload == null || type.Arity < BestGenericOverload.Arity
? type
: BestGenericOverload;
var newContainsPublicGenericOverload = type.DeclaredAccessibility >= Accessibility.Public || ContainsPublicGenericOverload;
return new TypeOverloadInfo(NonGenericOverload, newBestGenericOverload, newContainsPublicGenericOverload);
}
}
private readonly struct ReferenceCacheEntry
{
public ReferenceCacheEntry(
Checksum checksum,
ImmutableArray<TypeImportCompletionItemInfo> cachedItems)
{
Checksum = checksum;
CachedItems = cachedItems;
}
public Checksum Checksum { get; }
public ImmutableArray<TypeImportCompletionItemInfo> CachedItems { get; }
}
}
}
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Composition
Imports Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
<ExportLanguageServiceFactory(GetType(ITypeImportCompletionService), LanguageNames.VisualBasic), [Shared]>
Friend NotInheritable Class BasicTypeImportCompletionServiceFactory
Implements ILanguageServiceFactory
<ImportingConstructor>
Public Sub New()
End Sub
Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService
Return New BasicTypeImportCompletionService(languageServices.WorkspaceServices.Workspace)
End Function
Private Class BasicTypeImportCompletionService
Inherits AbstractTypeImportCompletionService
Public Sub New(workspace As Workspace)
MyBase.New(workspace)
End Sub
Protected Overrides ReadOnly Property GenericTypeSuffix As String
Get
Return "(Of ...)"
End Get
End Property
Protected Overrides ReadOnly Property IsCaseSensitive As Boolean
Get
Return False
End Get
End Property
End Class
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册