Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.FindSymbols;
Expand All @@ -12,34 +13,60 @@ internal abstract partial class AbstractGoToBaseService : IGoToBaseService
public async Task FindBasesAsync(Document document, int position, IFindUsagesContext context)
{
var cancellationToken = context.CancellationToken;
var tuple = await FindBaseHelpers.FindBasesAsync(document, position, cancellationToken).ConfigureAwait(false);
if (tuple == null)
var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
document, position, cancellationToken).ConfigureAwait(false);

if (symbolAndProject == default)
{
await context.ReportMessageAsync(
EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret).ConfigureAwait(false);
return;
}

var (symbol, implementations, message) = tuple.Value;

if (message != null)
{
await context.ReportMessageAsync(message).ConfigureAwait(false);
return;
}
var symbol = symbolAndProject.Value.symbol;
var bases = FindBaseHelpers.FindBases(
symbol, symbolAndProject.Value.project, cancellationToken);

await context.SetSearchTitleAsync(
string.Format(EditorFeaturesResources._0_bases,
FindUsagesHelpers.GetDisplayName(symbol))).ConfigureAwait(false);

var solution = document.Project.Solution;
var project = document.Project;
var solution = project.Solution;
var projectId = project.Id;

var found = false;

// For each potential base, try to find its definition in sources.
// If found, add its' definitionItem to the context.
// If not found but the symbol is from metadata, create its' definition item from metadata and add to the context.
foreach (var baseSymbol in bases)
{
var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(
SymbolAndProjectId.Create(baseSymbol, projectId), solution, cancellationToken).ConfigureAwait(false);
if (sourceDefinition.Symbol != null &&
sourceDefinition.Symbol.Locations.Any(l => l.IsInSource))
{
var definitionItem = await sourceDefinition.Symbol.ToClassifiedDefinitionItemAsync(
solution.GetProject(sourceDefinition.ProjectId), includeHiddenLocations: false,
FindReferencesSearchOptions.Default, cancellationToken: cancellationToken)
.ConfigureAwait(false);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
found = true;
}
else if (baseSymbol.Locations.Any(l => l.IsInMetadata))
{
var definitionItem = baseSymbol.ToNonClassifiedDefinitionItem(
project, includeHiddenLocations: true);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
found = true;
}
}

foreach (var implementation in implementations)
if (!found)
{
var definitionItem = await implementation.Symbol.ToClassifiedDefinitionItemAsync(
solution.GetProject(implementation.ProjectId), includeHiddenLocations: false,
FindReferencesSearchOptions.Default, cancellationToken: cancellationToken).ConfigureAwait(false);
await context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false);
await context.ReportMessageAsync(EditorFeaturesResources.The_symbol_has_no_base)
.ConfigureAwait(false);
}
}
}
Expand Down
41 changes: 11 additions & 30 deletions src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,33 @@
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.FindReferences;

namespace Microsoft.CodeAnalysis.Editor.GoToBase
{
internal static class FindBaseHelpers
{
public static async Task<(ISymbol symbol, ImmutableArray<SymbolAndProjectId> implementations, string message)?> FindBasesAsync(Document document, int position, CancellationToken cancellationToken)
{
var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (symbolAndProject == null)
{
return null;
}

var symbol = symbolAndProject.Value.symbol;
var project = symbolAndProject.Value.project;

var bases = await FindBasesWorkerAsync(symbol, project, cancellationToken).ConfigureAwait(false);
var filteredSymbols = bases.WhereAsArray(s => s.Symbol.Locations.Any(l => l.IsInSource));

return filteredSymbols.Length == 0
? (symbol, filteredSymbols, EditorFeaturesResources.The_symbol_has_no_base)
: (symbol, filteredSymbols, null);
}

private static async Task<ImmutableArray<SymbolAndProjectId>> FindBasesWorkerAsync(
public static ImmutableArray<ISymbol> FindBases(
ISymbol symbol, Project project, CancellationToken cancellationToken)
{
if (symbol is INamedTypeSymbol namedTypeSymbol &&
(namedTypeSymbol.TypeKind == TypeKind.Class || namedTypeSymbol.TypeKind == TypeKind.Interface || namedTypeSymbol.TypeKind == TypeKind.Struct))
(namedTypeSymbol.TypeKind == TypeKind.Class ||
namedTypeSymbol.TypeKind == TypeKind.Interface ||
namedTypeSymbol.TypeKind == TypeKind.Struct))
{
return await BaseTypeFinder.FindBaseTypesAndInterfacesAsync(namedTypeSymbol, project, cancellationToken).ConfigureAwait(false);
return BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol);
}
else if (symbol.Kind == SymbolKind.Property || symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.Event)
else if (symbol.Kind == SymbolKind.Property ||
symbol.Kind == SymbolKind.Method ||
symbol.Kind == SymbolKind.Event)
{
return await BaseTypeFinder.FindOverriddenAndImplementedMembersAsync(symbol, project, cancellationToken).ConfigureAwait(false);
return BaseTypeFinder.FindOverriddenAndImplementedMembers(
symbol, project, cancellationToken);
}
else
{
return ImmutableArray.Create<SymbolAndProjectId>();
return ImmutableArray<ISymbol>.Empty;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public AsyncCompletionData.CommitResult TryCommit(
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None);
}

if (!Helpers.TryGetInitialTriggerLocation(session, out var triggerLocation))
if (!Helpers.TryGetInitialTriggerLocation(item, out var triggerLocation))
{
// Need the trigger snapshot to calculate the span when the commit changes to be applied.
// They should always be available from VS. Just to be defensive, if it's not found here, Roslyn should not make a commit.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncComplet
internal class CompletionSource : ForegroundThreadAffinitizedObject, IAsyncExpandingCompletionSource
{
internal const string RoslynItem = nameof(RoslynItem);
internal const string TriggerLocation = nameof(TriggerLocation);
internal const string CompletionListSpan = nameof(CompletionListSpan);
internal const string InsertionText = nameof(InsertionText);
internal const string HasSuggestionItemOptions = nameof(HasSuggestionItemOptions);
Expand Down Expand Up @@ -284,7 +285,7 @@ private bool TryInvokeSnippetCompletion(
foreach (var roslynItem in completionList.Items)
{
cancellationToken.ThrowIfCancellationRequested();
var item = Convert(document, roslynItem, filterSet);
var item = Convert(document, roslynItem, filterSet, triggerLocation);
itemsBuilder.Add(item);
}

Expand Down Expand Up @@ -408,7 +409,8 @@ public VSCompletionItemData(
private VSCompletionItem Convert(
Document document,
RoslynCompletionItem roslynItem,
FilterSet filterSet)
FilterSet filterSet,
SnapshotPoint triggerLocation)
{
VSCompletionItemData itemData;

Expand Down Expand Up @@ -462,6 +464,7 @@ private VSCompletionItem Convert(
attributeIcons: itemData.AttributeIcons);

item.Properties.AddProperty(RoslynItem, roslynItem);
item.Properties.AddProperty(TriggerLocation, triggerLocation);

return item;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ internal static bool TryGetInitialTriggerLocation(EditorAsyncCompletion.IAsyncCo
return false;
}

internal static bool TryGetInitialTriggerLocation(VSCompletionItem item, out SnapshotPoint initialTriggerLocation)
{
if (item.Properties.TryGetProperty(CompletionSource.TriggerLocation, out initialTriggerLocation))
{
return true;
}

initialTriggerLocation = default;
return false;
}

// This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
// We expect that Editor will introduce this support and we will get rid of relying on the "★" then.
internal static bool IsPreferredItem(this RoslynCompletionItem completionItem)
Expand Down
61 changes: 44 additions & 17 deletions src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase
<[UseExportProvider]>
Public Class CSharpGoToBaseTests
Inherits GoToBaseTestsBase
Private Overloads Async Function TestAsync(source As String, Optional shouldSucceed As Boolean = True) As Task
Await TestAsync(source, LanguageNames.CSharp, shouldSucceed)
Private Overloads Async Function TestAsync(source As String, Optional shouldSucceed As Boolean = True,
Optional metadataDefinitions As String() = Nothing) As Task
Await TestAsync(source, LanguageNames.CSharp, shouldSucceed, metadataDefinitions)
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -17,7 +18,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithSingleClass() As Task
Await TestAsync("class $$C { }")
Await TestAsync("class $$C { }", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -29,15 +30,15 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase

class $$D : C
{
}")
}", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithAbstractClassFromInterface() As Task
Await TestAsync(
"interface [|I|] { }
abstract class [|C|] : I { }
class $$D : C { }")
class $$D : C { }", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -46,7 +47,7 @@ class $$D : C { }")
"class [|D|] { }
sealed class $$C : D
{
}")
}", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -66,22 +67,22 @@ sealed class $$C : D

class $$D : C
{
}")
}", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithSingleClassImplementation() As Task
Await TestAsync(
"class $$C : I { }
interface [|I|] { }")
interface [|I|] { }", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithTwoClassImplementations() As Task
Await TestAsync(
"class $$C : I { }
class D : I { }
interface [|I|] { }")
interface [|I|] { }", metadataDefinitions:={"mscorlib:Object"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -96,7 +97,7 @@ interface [|I2|] : I { }
interface I1 : I { }
interface [|I|] : J1, J2 { }
interface [|J1|] { }
interface [|J2|] { }")
interface [|J2|] { }", metadataDefinitions:={"mscorlib:Object"})
End Function

#End Region
Expand All @@ -108,14 +109,14 @@ interface [|J2|] { }")
Await TestAsync(
"struct $$C
{
}")
}", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithSingleStructImplementation() As Task
Await TestAsync(
"struct $$C : I { }
interface [|I|] { }")
interface [|I|] { }", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"})
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand All @@ -126,7 +127,7 @@ interface [|I2|] : I { }
interface I1 : I { }
interface [|I|] : J1, J2 { }
interface [|J1|] { }
interface [|J2|] { }")
interface [|J2|] { }", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"})
End Function

#End Region
Expand Down Expand Up @@ -283,11 +284,12 @@ interface I { void [|M|](); }")

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithVirtualMethodHiddenWithInterfaceOnBaseClass() As Task
' We should not find a hidden method.
' We should not find hidden methods
' and methods in interfaces if hidden below but the nested class does not implement the interface.
Await TestAsync(
"class C : I { public virtual void M() { } }
class D : C { public new void $$M() { } }
interface I { void [|M|](); }")
interface I { void M(); }")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Expand Down Expand Up @@ -317,7 +319,8 @@ interface I { void [|M|](); }")

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithVirtualMethodHiddenAndInterfaceImplementedOnDerivedType() As Task
' We should not find a hidden method.
' We should not find hidden methods
' but should find methods in interfaces if hidden below but the nested class implements the interface.
Await TestAsync(
"class C : I { public virtual void M() { } }
class D : C, I { public new void $$M() { } }
Expand All @@ -328,7 +331,7 @@ interface I { void [|M|](); }")
Public Async Function TestWithAbstractMethodImplementation() As Task
Await TestAsync(
"abstract class C : I { public abstract void [|M|]() { } }
class D : C { public override void $$M() { } }}
class D : C { public override void $$M() { } }
interface I { void [|M|](); }")
End Function

Expand Down Expand Up @@ -386,6 +389,30 @@ sealed class C2 : A {
}")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithOverloadsOverrdiesAndInterfaceImplementation_01() As Task
Await TestAsync(
"abstract class C : I { public virtual void [|M|]() { } public virtual void M(int i) { }}
class D : C { public override void $$M() { } public override void M(int i) { }}
interface I { void [|M|](); void M(int i};")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestWithOverloadsOverrdiesAndInterfaceImplementation_02() As Task
Await TestAsync(
"abstract class C : I { public virtual void M() { } public virtual void [|M|](int i) { }}
class D : C { public override void M() { } public override void $$M(int i) { }}
interface I { void M(); void [|M|](int i};")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.GoToBase)>
Public Async Function TestOverrideOfMethodFromMetadata() As Task
Await TestAsync(
"using System;
class C { public override string $$ToString() { return base.ToString(); } }
", metadataDefinitions:={"mscorlib:Object.ToString"})
End Function

#End Region

#Region "Properties and Events"
Expand Down
Loading