From d20421ecc788e0dec5930ad7254088f1aa5bbda7 Mon Sep 17 00:00:00 2001 From: lorcanmooney Date: Sun, 29 May 2016 01:01:50 +0100 Subject: [PATCH] Add namespace-name suggestions for C# --- .../SuggestionModeCompletionProviderTests.cs | 16 ++ .../SymbolCompletionProviderTests.cs | 158 +++++++++++++++++- .../SnippetCompletionProvider.cs | 7 +- .../CSharpSuggestionModeCompletionProvider.cs | 4 + .../Extensions/SyntaxTreeExtensions.cs | 6 +- .../CSharpSemanticFactsService.cs | 5 + .../CSharpRecommendationService.cs | 38 ++++- .../ISemanticFactsService.cs | 1 + .../ContextQuery/AbstractSyntaxContext.cs | 12 +- .../VisualBasicSemanticFactsService.vb | 7 + 10 files changed, 234 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs index a7cf21bcc1b..ca9245822a4 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs @@ -590,6 +590,22 @@ void foo() await VerifyBuilderAsync(markup); } + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceDeclaration_Unqualified() + { + var markup = @"namespace $$"; + await VerifyBuilderAsync(markup); + } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceDeclaration_Qualified() + { + var markup = @"namespace A.$$"; + await VerifyBuilderAsync(markup); + } + private async Task VerifyNotBuilderAsync(string markup) { await VerifyWorkerAsync(markup, isBuilder: false); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index e9dbcafc981..15b70eaa1f1 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -370,20 +370,166 @@ public async Task MethodParamAttribute() await VerifyItemExistsAsync(AddUsingDirectives("using System;", content), @"System"); } + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NamespaceName1() + public async Task NamespaceName_Unqualified_TopLevelNoPeers() { - await VerifyItemIsAbsentAsync(AddUsingDirectives("using System;", @"namespace $$"), @"String"); - await VerifyItemIsAbsentAsync(AddUsingDirectives("using System;", @"namespace $$"), @"System"); + var source = @"using System; + +namespace $$"; + + await VerifyItemExistsAsync(source, "System"); + await VerifyItemIsAbsentAsync(source, "String"); + } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Unqualified_TopLevelWithPeer() + { + var source = @" +namespace A { } + +namespace $$"; + + await VerifyItemExistsAsync(source, "A"); } + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NamespaceName2() + public async Task NamespaceName_Unqualified_NestedWithNoPeers() { - await VerifyItemIsAbsentAsync(@"namespace $$", @"String"); - await VerifyItemIsAbsentAsync(@"namespace $$", @"System"); + var source = @" +namespace A +{ + namespace $$ +}"; + + await VerifyNoItemsExistAsync(source); } + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Unqualified_NestedWithPeer() + { + var source = @" +namespace A +{ + namespace B { } + + namespace $$ +}"; + + await VerifyItemIsAbsentAsync(source, "A"); + await VerifyItemExistsAsync(source, "B"); + } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Unqualified_ExcludesCurrentDeclaration() + { + var source = @"namespace N$$S"; + + await VerifyItemIsAbsentAsync(source, "NS"); + } + +// [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] +// [Fact, Trait(Traits.Feature, Traits.Features.Completion)] +// public async Task NamespaceName_Unqualified_IncompleteDeclaration() +// { +// var source = @" +//namespace A.B.C0 { } +// +//namespace A +//{ +// namespace B +// { +// namespace C$$ +// +// namespace C1.X { } +// } +// +// namespace B.C2.Y { } +//} +// +//namespace A.B.C3.Z { }"; +// +// await VerifyItemIsAbsentAsync(source, "C0"); +// await VerifyItemExistsAsync(source, "C1"); +// await VerifyItemExistsAsync(source, "C2"); +// await VerifyItemExistsAsync(source, "C3"); +// await VerifyItemIsAbsentAsync(source, "X"); +// await VerifyItemIsAbsentAsync(source, "Y"); +// await VerifyItemIsAbsentAsync(source, "Z"); +// } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Qualified_NoPeers() + { + var source = @"namespace A.$$"; + + await VerifyNoItemsExistAsync(source); + } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Qualified_TopLevelWithPeer() + { + var source = @" +namespace A.B { } + +namespace A.$$"; + + await VerifyItemExistsAsync(source, "B"); + } + + [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NamespaceName_Qualified_NestedWithPeer() + { + var source = @" +namespace A +{ + namespace B.C { } + + namespace B.$$ +}"; + + await VerifyItemIsAbsentAsync(source, "A"); + await VerifyItemIsAbsentAsync(source, "B"); + await VerifyItemExistsAsync(source, "C"); + } + +// [WorkItem(7213, "https://github.com/dotnet/roslyn/issues/7213")] +// [Fact, Trait(Traits.Feature, Traits.Features.Completion)] +// public async Task NamespaceName_Qualified_IncompleteDeclaration() +// { +// var source = @" +//namespace A.B.C.D0 { } +// +//namespace A +//{ +// namespace B +// { +// namespace C.D$$ +// +// namespace C.D1.X { } +// } +// +// namespace B.C.D2.Y { } +//} +// +//namespace A.B.C.D3.Z { }"; +// +// await VerifyItemIsAbsentAsync(source, "D0"); +// await VerifyItemExistsAsync(source, "D1"); +// await VerifyItemExistsAsync(source, "D2"); +// await VerifyItemExistsAsync(source, "D3"); +// await VerifyItemIsAbsentAsync(source, "X"); +// await VerifyItemIsAbsentAsync(source, "Y"); +// await VerifyItemIsAbsentAsync(source, "Z"); +// } + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task UnderNamespace() { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs index 3c60a9c6f97..f358fa7cae5 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs @@ -110,7 +110,7 @@ private async Task> GetSnippetsForDocumentAsync(Docu return SpecializedCollections.EmptyEnumerable(); } - return await GetSnippetCompletionItemsAsync(workspace, semanticModel, position, itemSpan, isPreProcessorContext: true, cancellationToken: cancellationToken).ConfigureAwait(false); + return await GetSnippetCompletionItemsAsync(workspace, semanticModel, itemSpan, isPreProcessorContext: true, cancellationToken: cancellationToken).ConfigureAwait(false); } if (semanticFacts.IsGlobalStatementContext(semanticModel, position, cancellationToken) || @@ -119,16 +119,17 @@ private async Task> GetSnippetsForDocumentAsync(Docu semanticFacts.IsTypeContext(semanticModel, position, cancellationToken) || semanticFacts.IsTypeDeclarationContext(semanticModel, position, cancellationToken) || semanticFacts.IsNamespaceContext(semanticModel, position, cancellationToken) || + semanticFacts.IsNamespaceDeclarationNameContext(semanticModel, position, cancellationToken) || semanticFacts.IsMemberDeclarationContext(semanticModel, position, cancellationToken) || semanticFacts.IsLabelContext(semanticModel, position, cancellationToken)) { - return await GetSnippetCompletionItemsAsync(workspace, semanticModel, position, itemSpan, isPreProcessorContext: false, cancellationToken: cancellationToken).ConfigureAwait(false); + return await GetSnippetCompletionItemsAsync(workspace, semanticModel, itemSpan, isPreProcessorContext: false, cancellationToken: cancellationToken).ConfigureAwait(false); } return SpecializedCollections.EmptyEnumerable(); } - private async Task> GetSnippetCompletionItemsAsync(Workspace workspace, SemanticModel semanticModel, int position, TextSpan itemSpan, bool isPreProcessorContext, CancellationToken cancellationToken) + private async Task> GetSnippetCompletionItemsAsync(Workspace workspace, SemanticModel semanticModel, TextSpan itemSpan, bool isPreProcessorContext, CancellationToken cancellationToken) { var service = _snippetInfoService ?? workspace.Services.GetLanguageServices(semanticModel.Language).GetService(); if (service == null) diff --git a/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs index 697a43dd126..c62ba1b561a 100644 --- a/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs @@ -59,6 +59,10 @@ protected override async Task GetSuggestionModeItemAsync(Documen { return CreateSuggestionModeItem(CSharpFeaturesResources.RangeVariable, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToPotentialRangeVariableDecl); } + else if (tree.IsNamespaceDeclarationNameContext(position, cancellationToken)) + { + return CreateEmptySuggestionModeItem(itemSpan); + } } return null; diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs index e62c62319ba..3a6ca39aa50 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTreeExtensions.cs @@ -114,13 +114,13 @@ private static bool BaseTypeDeclarationContainsPosition(BaseTypeDeclarationSynta public static bool IsNamespaceDeclarationNameContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken) { var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken); - var namespaceName = token.GetAncestor(); - if (namespaceName == null) + var namespaceDeclaration = token.GetAncestor(); + if (namespaceDeclaration == null) { return false; } - return namespaceName.Name.Span.IntersectsWith(position); + return namespaceDeclaration.Name.Span.IntersectsWith(position) || token == namespaceDeclaration.NamespaceKeyword; } public static bool IsRightOfDotOrArrowOrColonColon(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken) diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs index 5582d3134fa..99e4a9043f3 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs @@ -60,6 +60,11 @@ public bool IsNamespaceContext(SemanticModel semanticModel, int position, Cancel return semanticModel.SyntaxTree.IsNamespaceContext(position, cancellationToken, semanticModel); } + public bool IsNamespaceDeclarationNameContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken) + { + return semanticModel.SyntaxTree.IsNamespaceDeclarationNameContext(position, cancellationToken); + } + public bool IsTypeDeclarationContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken) { return semanticModel.SyntaxTree.IsTypeDeclarationContext( diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs index 5b6e1bd37e8..ef773f8495b 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -48,13 +49,6 @@ internal class CSharpRecommendationService : AbstractRecommendationService return SpecializedCollections.EmptyEnumerable(); } - // TODO: don't show completion set at namespace name part to match Dev10 behavior - // if we want to provide new feature that shows all existing namespaces later, remove this - if (context.IsNamespaceDeclarationNameContext) - { - return SpecializedCollections.EmptyEnumerable(); - } - if (context.IsRightOfNameSeparator) { return GetSymbolsOffOfContainer(context, cancellationToken); @@ -100,6 +94,10 @@ internal class CSharpRecommendationService : AbstractRecommendationService { return SpecializedCollections.SingletonEnumerable(context.SemanticModel.GetDeclaredSymbol(context.ContainingTypeOrEnumDeclaration, cancellationToken)); } + else if (context.IsNamespaceDeclarationNameContext) + { + return GetSymbolsForNamespaceDeclarationNameContext(context, cancellationToken); + } return SpecializedCollections.EmptyEnumerable(); } @@ -238,6 +236,22 @@ internal class CSharpRecommendationService : AbstractRecommendationService return symbols; } + private static IEnumerable GetSymbolsForNamespaceDeclarationNameContext( + CSharpSyntaxContext context, + CancellationToken cancellationToken) + { + var namespaceDeclaration = context.TargetToken.GetAncestor(); + + var declaredNamespaceSymbol = context.SemanticModel.GetDeclaredSymbol(namespaceDeclaration); + var containingNamespaceSymbol = context.SemanticModel.Compilation.GetCompilationNamespace(declaredNamespaceSymbol.ContainingNamespace); + + var symbols = context.SemanticModel + .LookupNamespacesAndTypes(context.LeftToken.SpanStart, containingNamespaceSymbol) + .Where(symbol => IsNonIntersectingNamespace(symbol, context)); + + return symbols; + } + private static IEnumerable GetSymbolsForExpressionOrStatementContext( CSharpSyntaxContext context, bool filterOutOfScopeLocals, @@ -321,6 +335,11 @@ internal class CSharpRecommendationService : AbstractRecommendationService position: name.SpanStart, container: symbol); + if (context.IsNamespaceDeclarationNameContext) + { + return symbols.Where(s => IsNonIntersectingNamespace(s, context)); + } + // Filter the types when in a using directive, but not an alias. // // Cases: @@ -350,6 +369,11 @@ internal class CSharpRecommendationService : AbstractRecommendationService return SpecializedCollections.EmptyEnumerable(); } + private static bool IsNonIntersectingNamespace(ISymbol symbol, CSharpSyntaxContext context) + { + return symbol.IsNamespace() && symbol.Locations.Any(location => !context.IntersectsWith(location)); + } + private static IEnumerable GetSymbolsOffOfExpression( CSharpSyntaxContext context, ExpressionSyntax originalExpression, diff --git a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs index 660f8a29310..df13a1a5643 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs @@ -44,6 +44,7 @@ internal interface ISemanticFactsService : ILanguageService bool IsStatementContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsTypeContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsNamespaceContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); + bool IsNamespaceDeclarationNameContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsTypeDeclarationContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsMemberDeclarationContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsPreProcessorDirectiveContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ContextQuery/AbstractSyntaxContext.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ContextQuery/AbstractSyntaxContext.cs index 0b5c9f90f0d..7684fc2ae8e 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ContextQuery/AbstractSyntaxContext.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ContextQuery/AbstractSyntaxContext.cs @@ -1,9 +1,9 @@ // 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.Threading; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.LanguageServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery @@ -106,5 +106,15 @@ public ISet GetOuterTypes(CancellationToken cancellationToken) { return this.Workspace.Services.GetService(); } + + public bool IntersectsWith(Location location) + { + if (location == null) + { + throw new ArgumentNullException(nameof(location)); + } + + return location.SourceTree == SyntaxTree && location.SourceSpan.IntersectsWith(Position); + } } } diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb index ece381c5e45..046baa50309 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb @@ -243,5 +243,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Function IsNameOfContext(semanticModel As SemanticModel, position As Integer, cancellationToken As CancellationToken) As Boolean Implements ISemanticFactsService.IsNameOfContext Return semanticModel.SyntaxTree.IsNameOfContext(position, cancellationToken) End Function + + Public Function IsNamespaceDeclarationNameContext(semanticModel As SemanticModel, position As Integer, cancellationToken As CancellationToken) As Boolean Implements ISemanticFactsService.IsNamespaceDeclarationNameContext + ' + ' TODO: part of https://github.com/dotnet/roslyn/issues/7213 + ' + Return False + End Function End Class End Namespace -- GitLab