diff --git a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs index 91dab7ce21eb9..24d63f9691da2 100644 --- a/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs +++ b/src/EditorFeatures/Core/GoToBase/AbstractGoToBaseService.cs @@ -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; @@ -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); } } } diff --git a/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs b/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs index 1fc1346fc98fb..aaa35b3b28819 100644 --- a/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs +++ b/src/EditorFeatures/Core/GoToBase/FindBaseHelpers.cs @@ -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 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> FindBasesWorkerAsync( + public static ImmutableArray 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(); + return ImmutableArray.Empty; } } } diff --git a/src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb b/src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb index 9be91859e39b7..a47835785ffa1 100644 --- a/src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb +++ b/src/EditorFeatures/Test2/GoToBase/CSharpGoToBaseTests.vb @@ -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 @@ -17,7 +18,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase Public Async Function TestWithSingleClass() As Task - Await TestAsync("class $$C { }") + Await TestAsync("class $$C { }", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -29,7 +30,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase class $$D : C { -}") +}", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -37,7 +38,7 @@ class $$D : C Await TestAsync( "interface [|I|] { } abstract class [|C|] : I { } -class $$D : C { }") +class $$D : C { }", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -46,7 +47,7 @@ class $$D : C { }") "class [|D|] { } sealed class $$C : D { -}") +}", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -66,14 +67,14 @@ sealed class $$C : D class $$D : C { -}") +}", metadataDefinitions:={"mscorlib:Object"}) End Function Public Async Function TestWithSingleClassImplementation() As Task Await TestAsync( "class $$C : I { } -interface [|I|] { }") +interface [|I|] { }", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -81,7 +82,7 @@ interface [|I|] { }") Await TestAsync( "class $$C : I { } class D : I { } -interface [|I|] { }") +interface [|I|] { }", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -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 @@ -108,14 +109,14 @@ interface [|J2|] { }") Await TestAsync( "struct $$C { -}") +}", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"}) End Function Public Async Function TestWithSingleStructImplementation() As Task Await TestAsync( "struct $$C : I { } -interface [|I|] { }") +interface [|I|] { }", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"}) End Function @@ -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 @@ -283,11 +284,12 @@ interface I { void [|M|](); }") 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 @@ -317,7 +319,8 @@ interface I { void [|M|](); }") 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() { } } @@ -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 @@ -386,6 +389,30 @@ sealed class C2 : A { }") End Function + + 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 + + + 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 + + + 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" diff --git a/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb b/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb index b157aaf85341d..224d0938be2c6 100644 --- a/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb +++ b/src/EditorFeatures/Test2/GoToBase/GoToBaseTestsBase.vb @@ -5,17 +5,19 @@ Imports Microsoft.CodeAnalysis.Editor.GoToBase Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase Public MustInherit Class GoToBaseTestsBase - Protected Async Function TestAsync(workspaceDefinition As XElement, Optional shouldSucceed As Boolean = True) As Task + Protected Async Function TestAsync(workspaceDefinition As XElement, Optional shouldSucceed As Boolean = True, + Optional metadataDefinitions As String() = Nothing) As Task Await GoToHelpers.TestAsync( workspaceDefinition, Async Function(document As Document, position As Integer, context As SimpleFindUsagesContext) Dim gotoBaseService = document.GetLanguageService(Of IGoToBaseService) Await gotoBaseService.FindBasesAsync(document, position, context) End Function, - shouldSucceed) + shouldSucceed, metadataDefinitions) End Function - Protected Async Function TestAsync(source As String, language As String, Optional shouldSucceed As Boolean = True) As Task + Protected Async Function TestAsync(source As String, language As String, Optional shouldSucceed As Boolean = True, + Optional metadataDefinitions As String() = Nothing) As Task Await TestAsync( CommonReferences="true"> @@ -24,7 +26,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase , - shouldSucceed) + shouldSucceed, metadataDefinitions) End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/GoToBase/VisuaBasicGoToBaseTests.vb b/src/EditorFeatures/Test2/GoToBase/VisuaBasicGoToBaseTests.vb index f59dac4982c9c..ff0d8d61b72f5 100644 --- a/src/EditorFeatures/Test2/GoToBase/VisuaBasicGoToBaseTests.vb +++ b/src/EditorFeatures/Test2/GoToBase/VisuaBasicGoToBaseTests.vb @@ -4,8 +4,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase <[UseExportProvider]> Public Class VisualBasicGoToBaseTests Inherits GoToBaseTestsBase - Private Overloads Async Function TestAsync(source As String, Optional shouldSucceed As Boolean = True) As Task - Await TestAsync(source, LanguageNames.VisualBasic, 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.VisualBasic, shouldSucceed, metadataDefinitions) End Function @@ -19,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToBase Public Async Function TestWithSingleClass() As Task Await TestAsync( "class $$C -end class") +end class", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -30,7 +31,7 @@ end class class $$D inherits C -end class") +end class", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -43,7 +44,7 @@ mustinherit class [|C|] end class class $$D inherits C -end class") +end class", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -53,7 +54,7 @@ end class") end class NotInheritable class $$C inherits D -end class") +end class", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -71,7 +72,7 @@ end class class $$D inherits C -end class") +end class", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -81,7 +82,7 @@ end class") implements I end class interface [|I|] -end interface") +end interface", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -94,7 +95,7 @@ class D implements I end class interface [|I|] -end interface") +end interface", metadataDefinitions:={"mscorlib:Object"}) End Function @@ -127,7 +128,7 @@ end interface interface [|J1|] end interface interface [|J2|] -end interface") +end interface", metadataDefinitions:={"mscorlib:Object"}) End Function #End Region @@ -138,7 +139,7 @@ end interface") Public Async Function TestWithStruct() As Task Await TestAsync( "structure $$S -end structure") +end structure", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"}) End Function @@ -148,7 +149,7 @@ end structure") implements I end structure interface [|I|] -end interface") +end interface", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"}) End Function @@ -169,7 +170,7 @@ end interface interface [|J1|] end interface interface [|J2|] -end interface") +end interface", metadataDefinitions:={"mscorlib:Object", "mscorlib:ValueType"}) End Function #End Region @@ -342,7 +343,8 @@ End Interface") 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 Implements I @@ -355,7 +357,7 @@ Class D End Sub End Class Interface I - Sub [|M|]() + Sub M() End Interface") End Function @@ -417,7 +419,12 @@ End Interface") Public Async Function TestWithVirtualMethodHiddenAndInterfaceImplementedOnDerivedType() As Task - ' We should not find a hidden method. + ' We should not find hidden methods. + ' We should not find methods of interfaces no implemented by the method symbol. + ' In this example, + ' Dim i As I = New D() + ' i.M() + ' calls the method from C not from D. Await TestAsync( "Class C Implements I @@ -430,6 +437,26 @@ Class D Public Shadows Sub $$M() End Sub End Class +Interface I + Sub M() +End Interface") + End Function + + + Public Async Function TestWithVirtualMethodHiddenAndInterfaceAndMethodImplementedOnDerivedType() As Task + ' We should not find hidden methods but should find the interface method. + Await TestAsync( +"Class C + Implements I + Public Overridable Sub M() Implements I.M + End Sub +End Class +Class D + Inherits C + Implements I + Public Shadows Sub $$M() Implements I.M + End Sub +End Class Interface I Sub [|M|]() End Interface") @@ -515,6 +542,64 @@ NotInheritable Class C2 End Class") End Function + + Public Async Function TestWithOverloadsOverrdiesAndInterfaceImplementation_01() As Task + Await TestAsync( +"Class C + Implements I + Public Overridable Sub [|N|]() Implements I.M + End Sub + Public Overridable Sub N(i As Integer) Implements I.M + End Sub +End Class +Class D + Inherits C + Public Overrides Sub $$N() + End Sub + Public Overrides Sub N(i As Integer) + End Sub +End Class +Interface I + Sub [|M|]() + Sub M(i As Integer) +End Interface") + End Function + + + Public Async Function TestWithOverloadsOverrdiesAndInterfaceImplementation_02() As Task + Await TestAsync( +"Class C + Implements I + Public Overridable Sub N() Implements I.M + End Sub + Public Overridable Sub [|N|](i As Integer) Implements I.M + End Sub +End Class +Class D + Inherits C + Public Overrides Sub N() + End Sub + Public Overrides Sub $$N(i As Integer) + End Sub +End Class +Interface I + Sub M() + Sub [|M|](i As Integer) +End Interface") + End Function + + + Public Async Function TestOverrideOfMethodFromMetadata() As Task + Await TestAsync( +"Imports System +Class C + Public Overrides Function $$ToString() As String + Return base.ToString(); + End Function +End Class +", metadataDefinitions:={"mscorlib:Object.ToString"}) + End Function + #End Region #Region "Properties and Events" diff --git a/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb b/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb index ea6ee1312463e..5b55740298c30 100644 --- a/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb +++ b/src/EditorFeatures/Test2/GoToHelpers/GoToHelpers.vb @@ -7,7 +7,11 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Friend Class GoToHelpers - Friend Shared Async Function TestAsync(workspaceDefinition As XElement, testingMethod As Func(Of Document, Integer, SimpleFindUsagesContext, Task), Optional shouldSucceed As Boolean = True) As Task + Friend Shared Async Function TestAsync( + workspaceDefinition As XElement, + testingMethod As Func(Of Document, Integer, SimpleFindUsagesContext, Task), + Optional shouldSucceed As Boolean = True, + Optional metadataDefinitions As String() = Nothing) As Task Using workspace = TestWorkspace.Create(workspaceDefinition) Dim documentWithCursor = workspace.DocumentWithCursor Dim position = documentWithCursor.CursorPosition.Value @@ -40,6 +44,30 @@ Friend Class GoToHelpers Assert.True(actual.CompareTo(expected) = 0, $"Expected: ({expected}) but got: ({actual})") Next + + Dim actualDefintionsWithoutSpans = context.GetDefinitions(). + Where(Function(d) d.SourceSpans.IsDefaultOrEmpty). + Select(Function(di) + Return String.Format("{0}:{1}", + String.Join("", di.OriginationParts.Select(Function(t) t.Text)), + String.Join("", di.NameDisplayParts.Select(Function(t) t.Text))) + End Function).ToList() + + actualDefintionsWithoutSpans.Sort() + + If metadataDefinitions Is Nothing Then + metadataDefinitions = {} + End If + + Assert.Equal(actualDefintionsWithoutSpans.Count, metadataDefinitions.Count) + + For i = 0 To actualDefintionsWithoutSpans.Count - 1 + Dim actual = actualDefintionsWithoutSpans(i) + Dim expected = metadataDefinitions(i) + + Assert.True(actual.CompareTo(expected) = 0, + $"Expected: ({expected}) but got: ({actual})") + Next End If End Using End Function diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs index 68af88391155d..a50c869454d94 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs @@ -54,6 +54,12 @@ protected override async Task OnDefinitionFoundWorkerAsync(DefinitionItem defini var entry = await TryCreateEntryAsync(definitionBucket, definition).ConfigureAwait(false); entries.AddIfNotNull(entry); } + else if (definition.SourceSpans.Length == 0) + { + // No source spans means metadata references. + // Display it for Go to Base and try to navigate to metadata. + entries.Add(new MetadataDefinitionItemEntry(this, definitionBucket)); + } else { // If we have multiple spans (i.e. for partial types), then create a diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs index c804727d94bfb..7e1858eccd5aa 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs @@ -1,16 +1,11 @@ // 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 System.Threading.Tasks; -using System.Windows; -using System.Windows.Documents; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; using Roslyn.Utilities; @@ -23,10 +18,8 @@ internal partial class StreamingFindUsagesPresenter /// a . Navigation to that location is provided by this type. /// Subclasses can be used to provide customized line text to display in the entry. /// - private abstract class AbstractDocumentSpanEntry : Entry + private abstract class AbstractDocumentSpanEntry : AbstractItemEntry { - private readonly AbstractTableDataSourceFindUsagesContext _context; - private readonly string _projectName; private readonly object _boxedProjectGuid; @@ -40,10 +33,8 @@ protected AbstractDocumentSpanEntry( Guid projectGuid, SourceText lineText, MappedSpanResult mappedSpanResult) - : base(definitionBucket) + : base(definitionBucket, context.Presenter) { - _context = context; - _projectName = projectName; _boxedProjectGuid = projectGuid; @@ -51,8 +42,6 @@ protected AbstractDocumentSpanEntry( _mappedSpanResult = mappedSpanResult; } - protected StreamingFindUsagesPresenter Presenter => _context.Presenter; - protected override object GetValueWorker(string keyName) { switch (keyName) @@ -74,23 +63,6 @@ protected override object GetValueWorker(string keyName) return null; } - public override bool TryCreateColumnContent(string columnName, out FrameworkElement content) - { - if (columnName == StandardTableColumnDefinitions2.LineText) - { - var inlines = CreateLineTextInlines(); - var textBlock = inlines.ToTextBlock(Presenter.ClassificationFormatMap, wrap: false); - - content = textBlock; - return true; - } - - content = null; - return false; - } - - protected abstract IList CreateLineTextInlines(); - public static async Task TryMapAndGetFirstAsync(DocumentSpan documentSpan, SourceText sourceText, CancellationToken cancellationToken) { var service = documentSpan.Document.Services.GetService(); diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractItemEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractItemEntry.cs new file mode 100644 index 0000000000000..36a5ab0df76e9 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractItemEntry.cs @@ -0,0 +1,41 @@ +// 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.Generic; +using System.Windows; +using System.Windows.Documents; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.VisualStudio.Shell.TableControl; + +namespace Microsoft.VisualStudio.LanguageServices.FindUsages +{ + internal partial class StreamingFindUsagesPresenter + { + private abstract class AbstractItemEntry : Entry + { + protected readonly StreamingFindUsagesPresenter _presenter; + + public AbstractItemEntry(RoslynDefinitionBucket definitionBucket, StreamingFindUsagesPresenter presenter) + : base(definitionBucket) + { + _presenter = presenter; + } + + public override bool TryCreateColumnContent(string columnName, out FrameworkElement content) + { + if (columnName == StandardTableColumnDefinitions2.LineText) + { + var inlines = CreateLineTextInlines(); + var textBlock = inlines.ToTextBlock(_presenter.ClassificationFormatMap, wrap: false); + + content = textBlock; + return true; + } + + content = null; + return false; + } + + protected abstract IList CreateLineTextInlines(); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs index 2e2f61a8b4189..43cd49fd15157 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs @@ -30,7 +30,7 @@ public DefinitionItemEntry( } protected override IList CreateLineTextInlines() - => DefinitionBucket.DefinitionItem.DisplayParts.ToInlines(Presenter.ClassificationFormatMap, Presenter.TypeMap); + => DefinitionBucket.DefinitionItem.DisplayParts.ToInlines(_presenter.ClassificationFormatMap, _presenter.TypeMap); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index f47b4af2f9f80..12d60bcf4ffd7 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -69,7 +69,7 @@ public DocumentSpanEntry( ? WrittenReferenceHighlightTag.TagId : ReferenceHighlightTag.TagId; - var properties = Presenter.FormatMapService + var properties = _presenter.FormatMapService .GetEditorFormatMap("text") .GetProperties(propertyId); @@ -83,8 +83,8 @@ public DocumentSpanEntry( cs => new ClassifiedText(cs.ClassificationType, _excerptResult.Content.ToString(cs.TextSpan))); var inlines = classifiedTexts.ToInlines( - Presenter.ClassificationFormatMap, - Presenter.TypeMap, + _presenter.ClassificationFormatMap, + _presenter.TypeMap, runCallback: (run, classifiedText, position) => { if (properties["Background"] is Brush highlightBrush) @@ -136,7 +136,7 @@ protected override object GetValueWorker(string keyName) private DisposableToolTip CreateDisposableToolTip(Document document, TextSpan sourceSpan) { - Presenter.AssertIsForeground(); + _presenter.AssertIsForeground(); var controlService = document.Project.Solution.Workspace.Services.GetService(); var sourceText = document.GetTextSynchronously(CancellationToken.None); @@ -144,7 +144,7 @@ private DisposableToolTip CreateDisposableToolTip(Document document, TextSpan so var excerptService = document.Services.GetService(); if (excerptService != null) { - var excerpt = Presenter.ThreadingContext.JoinableTaskFactory.Run(() => excerptService.TryExcerptAsync(document, sourceSpan, ExcerptMode.Tooltip, CancellationToken.None)); + var excerpt = _presenter.ThreadingContext.JoinableTaskFactory.Run(() => excerptService.TryExcerptAsync(document, sourceSpan, ExcerptMode.Tooltip, CancellationToken.None)); if (excerpt != null) { // get tooltip from excerpt service diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/MetadataDefinitionItemEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/MetadataDefinitionItemEntry.cs new file mode 100644 index 0000000000000..8983624afb822 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/MetadataDefinitionItemEntry.cs @@ -0,0 +1,41 @@ +// 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.Generic; +using System.Windows.Documents; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.VisualStudio.Shell.TableManager; + +namespace Microsoft.VisualStudio.LanguageServices.FindUsages +{ + internal partial class StreamingFindUsagesPresenter + { + private class MetadataDefinitionItemEntry : AbstractItemEntry, ISupportsNavigation + { + public MetadataDefinitionItemEntry( + AbstractTableDataSourceFindUsagesContext context, + RoslynDefinitionBucket definitionBucket) + : base(definitionBucket, context.Presenter) + { + } + + protected override object GetValueWorker(string keyName) + { + switch (keyName) + { + case StandardTableKeyNames.Text: + return DefinitionBucket.DefinitionItem.DisplayParts.JoinText(); + } + + return null; + } + + bool ISupportsNavigation.TryNavigateTo(bool isPreview) + => DefinitionBucket.DefinitionItem.TryNavigateTo(_presenter._workspace, isPreview); + + protected override IList CreateLineTextInlines() + => DefinitionBucket.DefinitionItem.DisplayParts + .ToInlines(_presenter.ClassificationFormatMap, _presenter.TypeMap); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/FindReferencesTableControlEventProcessorProvider.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/FindReferencesTableControlEventProcessorProvider.cs index e5d3d299a1472..add06c8d8a01a 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/FindReferencesTableControlEventProcessorProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/FindReferencesTableControlEventProcessorProvider.cs @@ -44,6 +44,15 @@ public override void PreprocessNavigate(ITableEntryHandle entry, TableEntryNavig } } + if (entry.TryGetValue(StreamingFindUsagesPresenter.SelfKeyName, out var item) && item is ISupportsNavigation itemSupportsNavigation) + { + if (itemSupportsNavigation.TryNavigateTo(e.IsPreview)) + { + e.Handled = true; + return; + } + } + base.PreprocessNavigate(entry, e); } } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs index cb8575c8c85d4..ee23037b65637 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs @@ -18,7 +18,6 @@ internal partial class StreamingFindUsagesPresenter private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation { private readonly StreamingFindUsagesPresenter _presenter; - private readonly AbstractTableDataSourceFindUsagesContext _context; public readonly DefinitionItem DefinitionItem; @@ -31,7 +30,6 @@ public RoslynDefinitionBucket( identifier: context.Identifier) { _presenter = presenter; - _context = context; DefinitionItem = definitionItem; } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/TableEntriesSnapshot.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/TableEntriesSnapshot.cs index bc525e46af4f0..0754b00195e42 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/TableEntriesSnapshot.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/TableEntriesSnapshot.cs @@ -9,6 +9,9 @@ namespace Microsoft.VisualStudio.LanguageServices.FindUsages { internal partial class StreamingFindUsagesPresenter { + // Name of the key used to retireve the whole entry object. + internal const string SelfKeyName = "self"; + private class TableEntriesSnapshot : WpfTableEntriesSnapshotBase { private readonly int _versionNumber; @@ -34,6 +37,14 @@ public override int IndexOf(int currentIndex, ITableEntriesSnapshot newSnapshot) public override bool TryGetValue(int index, string keyName, out object content) { + // TableControlEventProcessor.PreprocessNavigate needs to get an entry + // to call TryNavigateTo on it. + if (keyName == SelfKeyName) + { + content = _entries[index]; + return true; + } + return _entries[index].TryGetValue(keyName, out content); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index 81050759d168d..b562a27ed308b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -2,7 +2,6 @@ using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -10,63 +9,54 @@ namespace Microsoft.CodeAnalysis.FindSymbols.FindReferences { internal static partial class BaseTypeFinder { - public static async Task> FindBaseTypesAndInterfacesAsync( - INamedTypeSymbol type, Project project, CancellationToken cancellationToken) - { - var typesAndInterfaces = FindBaseTypesAndInterfaces(type); - return await ConvertToSymbolAndProjectIdsAsync(typesAndInterfaces.CastArray(), project, cancellationToken).ConfigureAwait(false); - } + public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) + => FindBaseTypes(type).AddRange(type.AllInterfaces).CastArray(); - public static async Task> FindOverriddenAndImplementedMembersAsync( + public static ImmutableArray FindOverriddenAndImplementedMembers( ISymbol symbol, Project project, CancellationToken cancellationToken) { var solution = project.Solution; - var results = ArrayBuilder.GetInstance(); - var interfaceImplementations = ArrayBuilder.GetInstance(); + var results = ArrayBuilder.GetInstance(); // This is called for all: class, struct or interface member. - interfaceImplementations.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); + results.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); // The type scenario. Iterate over all base classes to find overridden and hidden (new/Shadows) methods. foreach (var type in FindBaseTypes(symbol.ContainingType)) { foreach (var member in type.GetMembers(symbol.Name)) { - var sourceMember = await SymbolFinder.FindSourceDefinitionAsync( - SymbolAndProjectId.Create(member, project.Id), - solution, - cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - if (sourceMember.Symbol != null) + // Add to results overridden members only. Do not add hidden members. + if (SymbolFinder.IsOverride(solution, symbol, member, cancellationToken)) { - // Add to results overridden members only. Do not add hidden members. - if (SymbolFinder.IsOverride(solution, symbol, sourceMember.Symbol, cancellationToken)) - { - results.Add(sourceMember); - } + results.Add(member); - // For both overridden and inherited members, - // find all explicit and implicit interface implementations. - // We need to start from each base class for cases like N() Implements I.M() - // where N() can be hidden or overwritten in a nested class later on. - interfaceImplementations.AddRange(member.ExplicitOrImplicitInterfaceImplementations()); + // We should add implementations only for overridden members but not for hidden ones. + // In the following example: + // interface I { void M(); } + // class A : I { public void M(); } + // class B : A { public new void M(); } + // we should not find anything for B.M() because it does not implement the interface: + // I i = new B(); i.M(); + // will call the method from A. + // However, if we change the code to + // class B : A, I { public new void M(); } + // then + // I i = new B(); i.M(); + // will call the method from B. We should find the base for B.M in this case. + // And if we change 'new' to 'override' in the original code and add 'virtual' where needed, + // we should find I.M as a base for B.M(). And the next line helps with this scenario. + results.AddRange(member.ExplicitOrImplicitInterfaceImplementations()); } } } // Remove duplicates from interface implementations before adding their projects. - results.AddRange( - await ConvertToSymbolAndProjectIdsAsync( - interfaceImplementations.ToImmutableAndFree().Distinct(), - project, - cancellationToken).ConfigureAwait(false)); - return results.ToImmutableAndFree().Distinct(); } - private static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) - => FindBaseTypes(type).AddRange(type.AllInterfaces); - private static ImmutableArray FindBaseTypes(INamedTypeSymbol type) { var typesBuilder = ArrayBuilder.GetInstance(); @@ -80,22 +70,5 @@ private static ImmutableArray FindBaseTypes(INamedTypeSymbol t return typesBuilder.ToImmutableAndFree(); } - - private static async Task> ConvertToSymbolAndProjectIdsAsync( - ImmutableArray implementations, Project project, CancellationToken cancellationToken) - { - var result = ArrayBuilder.GetInstance(); - foreach (var implementation in implementations) - { - var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync( - SymbolAndProjectId.Create(implementation, project.Id), project.Solution, cancellationToken).ConfigureAwait(false); - if (sourceDefinition.Symbol != null) - { - result.Add(sourceDefinition); - } - } - - return result.ToImmutableAndFree(); - } } }