From 98ed50ac55d4b4fee9f011773d5f7c3336e7e335 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:26:44 -0800 Subject: [PATCH 1/9] Support 'view call hierarchy' on primary consstructor --- .../Portable/FindUsages/FindUsagesHelpers.cs | 21 +++++++- .../CallHierarchyCommandHandler.cs | 51 +++++++++---------- .../Def/CallHierarchy/CallHierarchyItem.cs | 2 +- .../CallHierarchy/CallHierarchyProvider.cs | 4 +- .../Core/Portable/Rename/RenameUtilities.cs | 33 +++--------- .../Extensions/ITypeSymbolExtensions.cs | 10 ++-- .../SemanticFacts/CSharpSemanticFacts.cs | 3 ++ .../Services/SemanticFacts/ISemanticFacts.cs | 2 + .../SemanticFacts/VisualBasicSemanticFacts.vb | 6 +++ .../AbstractSemanticFactsService.cs | 4 ++ 10 files changed, 71 insertions(+), 65 deletions(-) diff --git a/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs b/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs index 1a2d3910fcd87..4c80956c8deb2 100644 --- a/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs +++ b/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SymbolMapping; @@ -18,6 +19,10 @@ internal static class FindUsagesHelpers public static string GetDisplayName(ISymbol symbol) => symbol.IsConstructor() ? symbol.ContainingType.Name : symbol.Name; + public static Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( + Document document, int position, CancellationToken cancellationToken) + => GetRelevantSymbolAndProjectAtPositionAsync(document, position, preferPrimaryConstructor: false, cancellationToken); + /// /// Common helper for both the synchronous and streaming versions of FAR. /// It returns the symbol we want to search for and the solution we should @@ -29,14 +34,26 @@ public static string GetDisplayName(ISymbol symbol) /// scenarios). /// public static async Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( - Document document, int position, CancellationToken cancellationToken) + Document document, int position, bool preferPrimaryConstructor, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false); + var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken).ConfigureAwait(false); if (symbol == null) return null; + if (preferPrimaryConstructor && symbol is INamedTypeSymbol namedType) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var headerFacts = document.GetRequiredLanguageService(); + var semanticFacts = document.GetRequiredLanguageService(); + if (headerFacts.IsOnTypeHeader(root, position, out _) && + semanticFacts.TryGetPrimaryConstructor(namedType, out var primaryConstructor)) + { + symbol = primaryConstructor; + } + } + // If this document is not in the primary workspace, we may want to search for results // in a solution different from the one we started in. Use the starting workspace's // ISymbolMappingService to get a context for searching in the proper solution. diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs index 826fbb6f7b468..15c72d260dc66 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs @@ -4,21 +4,26 @@ #nullable disable +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SymbolMapping; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -58,53 +63,43 @@ public CallHierarchyCommandHandler( public bool ExecuteCommand(ViewCallHierarchyCommandArgs args, CommandExecutionContext context) { + var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + return false; + + var point = args.TextView.Caret.Position.Point.GetPoint(args.SubjectBuffer, PositionAffinity.Predecessor); + if (point is null) + return false; + // We're showing our own UI, ensure the editor doesn't show anything itself. context.OperationContext.TakeOwnership(); var token = _listener.BeginAsyncOperation(nameof(ExecuteCommand)); - ExecuteCommandAsync(args, context) + ExecuteCommandAsync(document, point.Value.Position) .ReportNonFatalErrorAsync() .CompletesAsyncOperation(token); return true; } - private async Task ExecuteCommandAsync(ViewCallHierarchyCommandArgs args, CommandExecutionContext commandExecutionContext) + private async Task ExecuteCommandAsync(Document document, int caretPosition) { - Document document; - using (var context = _threadOperationExecutor.BeginExecute( EditorFeaturesResources.Call_Hierarchy, ServicesVSResources.Navigating, allowCancellation: true, showProgress: false)) { - document = await args.SubjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync( - commandExecutionContext.OperationContext).ConfigureAwait(true); - if (document == null) - { - return; - } - - var caretPosition = args.TextView.Caret.Position.BufferPosition.Position; var cancellationToken = context.UserCancellationToken; - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var symbolUnderCaret = await SymbolFinder.FindSymbolAtPositionAsync( - semanticModel, caretPosition, document.Project.Solution.Services, cancellationToken).ConfigureAwait(false); + var symbolAndProject = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync( + document, caretPosition, preferPrimaryConstructor: true, cancellationToken).ConfigureAwait(false); - if (symbolUnderCaret != null) + if (symbolAndProject is (var symbol, var project)) { - // Map symbols so that Call Hierarchy works from metadata-as-source - var mappingService = document.Project.Solution.Services.GetService(); - var mapping = await mappingService.MapSymbolAsync(document, symbolUnderCaret, cancellationToken).ConfigureAwait(false); + var node = await _provider.CreateItemAsync(symbol, project, callsites: [], cancellationToken).ConfigureAwait(false); - if (mapping.Symbol != null) + if (node != null) { - var node = await _provider.CreateItemAsync(mapping.Symbol, mapping.Project, [], cancellationToken).ConfigureAwait(false); - - if (node != null) - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _presenter.PresentRoot((CallHierarchyItem)node); - return; - } + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + _presenter.PresentRoot(node); + return; } } diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs index 24be31df5a6a3..ac1e25b7a82be 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy; -internal class CallHierarchyItem : ICallHierarchyMemberItem +internal sealed class CallHierarchyItem : ICallHierarchyMemberItem { private readonly Workspace _workspace; private readonly INavigableLocation _navigableLocation; diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs index 26041774a48bb..a06ed70147d8a 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs @@ -52,7 +52,7 @@ public CallHierarchyProvider( _streamingPresenter = streamingPresenter; } - public async Task CreateItemAsync( + public async Task CreateItemAsync( ISymbol symbol, Project project, ImmutableArray callsites, CancellationToken cancellationToken) { if (symbol.Kind is SymbolKind.Method or @@ -65,7 +65,7 @@ SymbolKind.Event or var finders = await CreateFindersAsync(symbol, project, cancellationToken).ConfigureAwait(false); var location = await GoToDefinitionHelpers.GetDefinitionLocationAsync( symbol, project.Solution, this.ThreadingContext, _streamingPresenter.Value, cancellationToken).ConfigureAwait(false); - ICallHierarchyMemberItem item = new CallHierarchyItem( + return new CallHierarchyItem( this, symbol, location, diff --git a/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs b/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs index def784de0012b..3e884957ee51d 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs @@ -345,37 +345,16 @@ public static async Task FindDefinitionSymbolAsync( } // if we are renaming a compiler generated delegate for an event, cascade to the event - if (symbol.Kind == SymbolKind.NamedType) - { - var typeSymbol = (INamedTypeSymbol)symbol; - if (typeSymbol.IsImplicitlyDeclared && typeSymbol.IsDelegateType() && typeSymbol.AssociatedSymbol != null) - { - return typeSymbol.AssociatedSymbol; - } - } + if (symbol is INamedTypeSymbol { IsImplicitlyDeclared: true, TypeKind: TypeKind.Delegate, AssociatedSymbol: not null } typeSymbol) + return typeSymbol.AssociatedSymbol; // If we are renaming a constructor or destructor, we wish to rename the whole type - if (symbol.Kind == SymbolKind.Method) - { - var methodSymbol = (IMethodSymbol)symbol; - if (methodSymbol.MethodKind is MethodKind.Constructor or - MethodKind.StaticConstructor or - MethodKind.Destructor) - { - return methodSymbol.ContainingType; - } - } + if (symbol is IMethodSymbol { MethodKind: MethodKind.Constructor | MethodKind.StaticConstructor | MethodKind.Destructor }) + return symbol.ContainingType; // If we are renaming a backing field for a property, cascade to the property - if (symbol.Kind == SymbolKind.Field) - { - var fieldSymbol = (IFieldSymbol)symbol; - if (fieldSymbol.IsImplicitlyDeclared && - fieldSymbol.AssociatedSymbol.IsKind(SymbolKind.Property)) - { - return fieldSymbol.AssociatedSymbol; - } - } + if (symbol is IFieldSymbol { IsImplicitlyDeclared: true, AssociatedSymbol: IPropertySymbol associatedProperty }) + return associatedProperty; // in case this is e.g. an overridden property accessor, we'll treat the property itself as the definition symbol var property = await TryGetPropertyFromAccessorOrAnOverrideAsync(bestSymbol, solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ITypeSymbolExtensions.cs index 304762df06b73..aab4c0035fdf6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ITypeSymbolExtensions.cs @@ -44,13 +44,13 @@ public static bool TryGetPrimaryConstructor(this INamedTypeSymbol typeSymbol, [N Debug.Assert(typeSymbol.GetParameters().IsDefaultOrEmpty, "If GetParameters extension handles record, we can remove the handling here."); // A bit hacky to determine the parameters of primary constructor associated with a given record. - // Simplifying is tracked by: https://github.com/dotnet/roslyn/issues/53092. - // Note: When the issue is handled, we can remove the logic here and handle things in GetParameters extension. BUT - // if GetParameters extension method gets updated to handle records, we need to test EVERY usage - // of the extension method and make sure the change is applicable to all these usages. + // Simplifying is tracked by: https://github.com/dotnet/roslyn/issues/53092. Note: When the issue is + // handled, we can remove the logic here and handle things in GetParameters extension. BUT if GetParameters + // extension method gets updated to handle records, we need to test EVERY usage of the extension method and + // make sure the change is applicable to all these usages. primaryConstructor = typeSymbol.InstanceConstructors.FirstOrDefault( - c => c.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax); + c => c.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is TypeDeclarationSyntax); return primaryConstructor is not null; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index ebd2ccc4e5b32..40ba28461581a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -414,6 +414,9 @@ SyntaxKind.ElifDirectiveTrivia or SyntaxKind.DefineDirectiveTrivia or SyntaxKind.UndefDirectiveTrivia); + public bool TryGetPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) + => typeSymbol.TryGetPrimaryConstructor(out primaryConstructor); + #if !CODE_STYLE public async Task GetInterceptorSymbolAsync(Document document, int position, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs index 7f05d5afec83f..49a6b23251d52 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs @@ -121,6 +121,8 @@ internal partial interface ISemanticFacts /// IPreprocessingSymbol? GetPreprocessingSymbol(SemanticModel semanticModel, SyntaxNode node); + bool TryGetPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor); + #if !CODE_STYLE /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb index 540dba1f7d0b4..31b8674d601df 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.Diagnostics.CodeAnalysis Imports System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis @@ -328,6 +329,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return False End Function + Public Function TryGetPrimaryConstructor(typeSymbol As INamedTypeSymbol, ByRef primaryConstructor As IMethodSymbol) As Boolean Implements ISemanticFacts.TryGetPrimaryConstructor + ' VB does not support primary constructors + Return False + End Function + #If Not CODE_STYLE Then Public Function GetInterceptorSymbolAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of ISymbol) Implements ISemanticFacts.GetInterceptorSymbolAsync diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs index c3682a87189e6..4274201c8822f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -257,6 +258,9 @@ public string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode public IPreprocessingSymbol GetPreprocessingSymbol(SemanticModel semanticModel, SyntaxNode node) => SemanticFacts.GetPreprocessingSymbol(semanticModel, node); + public bool TryGetPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol primaryConstructor) + => SemanticFacts.TryGetPrimaryConstructor(typeSymbol, out primaryConstructor); + #if !CODE_STYLE public Task GetInterceptorSymbolAsync(Document document, int position, CancellationToken cancellationToken) From e699059640ed95a387e68f059d722c4aaf05cd8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:31:51 -0800 Subject: [PATCH 2/9] Fix --- .../Core/Def/CallHierarchy/CallHierarchyProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs index a06ed70147d8a..627c1e96f6330 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs @@ -73,8 +73,6 @@ SymbolKind.Event or () => symbol.GetGlyph().GetImageSource(GlyphService), callsites, project); - - return item; } return null; From b76f04cc5734d25580ded8ea47ba717c03f27de9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:35:01 -0800 Subject: [PATCH 3/9] Arrays --- .../Core/Def/CallHierarchy/CallHierarchyItem.cs | 6 +++--- .../Core/Def/CallHierarchy/CallHierarchyProvider.cs | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs index ac1e25b7a82be..69e54e1e0a570 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyItem.cs @@ -23,8 +23,8 @@ internal sealed class CallHierarchyItem : ICallHierarchyMemberItem { private readonly Workspace _workspace; private readonly INavigableLocation _navigableLocation; - private readonly IEnumerable _callsites; - private readonly IEnumerable _finders; + private readonly ImmutableArray _callsites; + private readonly ImmutableArray _finders; private readonly Func _glyphCreator; private readonly CallHierarchyProvider _provider; @@ -32,7 +32,7 @@ public CallHierarchyItem( CallHierarchyProvider provider, ISymbol symbol, INavigableLocation navigableLocation, - IEnumerable finders, + ImmutableArray finders, Func glyphCreator, ImmutableArray callsites, Project project) diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs index 627c1e96f6330..a0d3b081b30d0 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyProvider.cs @@ -98,11 +98,11 @@ public FieldInitializerItem CreateInitializerItem(IEnumerable> CreateFindersAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) + public async Task> CreateFindersAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { if (symbol.Kind is SymbolKind.Property or - SymbolKind.Event or - SymbolKind.Method) + SymbolKind.Event or + SymbolKind.Method) { var finders = new List { @@ -136,14 +136,12 @@ SymbolKind.Event or finders.Add(new ImplementerFinder(symbol, project.Id, AsyncListener, this)); } - return finders; + return finders.ToImmutableArray(); } if (symbol.Kind == SymbolKind.Field) - { return [new FieldReferenceFinder(symbol, project.Id, AsyncListener, this)]; - } - return null; + return []; } } From 57f2353bd184ceaefcff7e64ccb79357d2ee6460 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:36:29 -0800 Subject: [PATCH 4/9] cleanup tests --- .../CallHierarchy/CSharpCallHierarchyTests.cs | 916 +++++++++--------- 1 file changed, 466 insertions(+), 450 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs index e1b1bf3a44b7b..915ce44dc0f58 100644 --- a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs +++ b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.UnitTests.CallHierarchy; @@ -11,505 +9,523 @@ using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CallHierarchy -{ - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.CallHierarchy)] - public class CSharpCallHierarchyTests - { - [WpfFact] - public async Task InvokeOnMethod() - { - var text = @" -namespace N -{ - class C - { - void G$$oo() - { - } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()"); - } - - [WpfFact] - public async Task InvokeOnProperty() - { - var text = @" -namespace N -{ - class C - { - public int G$$oo { get; set;} - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo"); - } - - [WpfFact] - public async Task InvokeOnEvent() - { - var text = @" -using System; -namespace N -{ - class C - { - public event EventHandler Go$$o; - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo"); - } - - [WpfFact] - public async Task Method_FindCalls() - { - var text = @" -namespace N -{ - class C - { - void G$$oo() - { - } - } +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CallHierarchy; - class G - { - void Main() - { - var c = new C(); - c.Goo(); - } - - void Main2() - { - var c = new C(); - c.Goo(); - } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main()", "N.G.Main2()"]); - } - - [WpfFact] - public async Task Method_InterfaceImplementation() - { - var text = @" -namespace N +[UseExportProvider] +[Trait(Traits.Feature, Traits.Features.CallHierarchy)] +public sealed class CSharpCallHierarchyTests { - interface I + [WpfFact] + public async Task InvokeOnMethod() { - void Goo(); + var text = """ + namespace N + { + class C + { + void G$$oo() + { + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()"); } - class C : I + [WpfFact] + public async Task InvokeOnProperty() { - public void G$$oo() - { - } + var text = """ + namespace N + { + class C + { + public int G$$oo { get; set;} + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo"); } - class G + [WpfFact] + public async Task InvokeOnEvent() { - void Main() - { - I c = new C(); - c.Goo(); - } - - void Main2() - { - var c = new C(); - c.Goo(); - } + var text = """ + using System; + namespace N + { + class C + { + public event EventHandler Go$$o; + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo"); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Calls_To_Interface_Implementation_0, "N.I.Goo()")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main2()"]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_Interface_Implementation_0, "N.I.Goo()"), ["N.G.Main()"]); - } - - [WpfFact] - public async Task Method_CallToOverride() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task Method_FindCalls() { - protected virtual void G$$oo() { } + var text = """ + namespace N + { + class C + { + void G$$oo() + { + } + } + + class G + { + void Main() + { + var c = new C(); + c.Goo(); + } + + void Main2() + { + var c = new C(); + c.Goo(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main()", "N.G.Main2()"]); } - class D : C + [WpfFact] + public async Task Method_InterfaceImplementation() { - protected override void Goo() { } - - void Bar() - { - C c; - c.Goo() - } - - void Baz() - { - D d; - d.Goo(); - } + var text = """ + namespace N + { + interface I + { + void Goo(); + } + + class C : I + { + public void G$$oo() + { + } + } + + class G + { + void Main() + { + I c = new C(); + c.Goo(); + } + + void Main2() + { + var c = new C(); + c.Goo(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Calls_To_Interface_Implementation_0, "N.I.Goo()")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main2()"]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_Interface_Implementation_0, "N.I.Goo()"), ["N.G.Main()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), EditorFeaturesResources.Calls_To_Overrides]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.D.Bar()"]); - testState.VerifyResult(root, EditorFeaturesResources.Calls_To_Overrides, ["N.D.Baz()"]); - } - - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/829705")] - public async Task Method_CallToBase() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task Method_CallToOverride() { - protected virtual void Goo() { } + var text = """ + namespace N + { + class C + { + protected virtual void G$$oo() { } + } + + class D : C + { + protected override void Goo() { } + + void Bar() + { + C c; + c.Goo() + } + + void Baz() + { + D d; + d.Goo(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), EditorFeaturesResources.Calls_To_Overrides]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.D.Bar()"]); + testState.VerifyResult(root, EditorFeaturesResources.Calls_To_Overrides, ["N.D.Baz()"]); } - class D : C + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/829705")] + public async Task Method_CallToBase() { - protected override void Goo() { } - - void Bar() - { - C c; - c.Goo() - } - - void Baz() - { - D d; - d.Go$$o(); - } + var text = """ + namespace N + { + class C + { + protected virtual void Goo() { } + } + + class D : C + { + protected override void Goo() { } + + void Bar() + { + C c; + c.Goo() + } + + void Baz() + { + D d; + d.Go$$o(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.D.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Calls_To_Base_Member_0, "N.C.Goo()")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.D.Baz()"]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_Base_Member_0, "N.C.Goo()"), ["N.D.Bar()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.D.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Calls_To_Base_Member_0, "N.C.Goo()")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.D.Baz()"]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_Base_Member_0, "N.C.Goo()"), ["N.D.Bar()"]); - } - - [WpfFact] - public async Task FieldInitializers() - { - var text = @" -namespace N -{ - class C - { - public int goo = Goo(); - protected int Goo$$() { return 0; } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo")]); - testState.VerifyResultName(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), [EditorFeaturesResources.Initializers]); - } - - [WpfFact] - public async Task FieldReferences() - { - var text = @" -namespace N -{ - class C + [WpfFact] + public async Task FieldInitializers() { - public int g$$oo; + var text = """ + namespace N + { + class C + { + public int goo = Goo(); - protected void Goo() { goo = 3; } + protected int Goo$$() { return 0; } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo")]); + testState.VerifyResultName(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), [EditorFeaturesResources.Initializers]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.goo", [string.Format(EditorFeaturesResources.References_To_Field_0, "goo")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.References_To_Field_0, "goo"), ["N.C.Goo()"]); - } - - [WpfFact] - public async Task PropertyGet() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task FieldReferences() { - public int val - { - g$$et + var text = """ + namespace N { - return 0; - } - } + class C + { + public int g$$oo; - public int goo() - { - var x = this.val; - } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.val.get", [string.Format(EditorFeaturesResources.Calls_To_0, "get_val")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "get_val"), ["N.C.goo()"]); - } - - [WpfFact] - public async Task Generic() - { - var text = @" -namespace N -{ - class C - { - public int gen$$eric(this string generic, ref T stuff) - { - return 0; - } - - public int goo() - { - int i; - generic("", ref i); - } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.generic(this string, ref T)", [string.Format(EditorFeaturesResources.Calls_To_0, "generic")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "generic"), ["N.C.goo()"]); - } - - [WpfFact] - public async Task ExtensionMethods() - { - var text = @" -namespace ConsoleApplication10 -{ - class Program - { - static void Main(string[] args) - { - var x = ""string""; - x.BarStr$$ing(); - } - } - - public static class Extensions - { - public static string BarString(this string s) - { - return s; - } + protected void Goo() { goo = 3; } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.goo", [string.Format(EditorFeaturesResources.References_To_Field_0, "goo")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.References_To_Field_0, "goo"), ["N.C.Goo()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "ConsoleApplication10.Extensions.BarString(this string)", [string.Format(EditorFeaturesResources.Calls_To_0, "BarString")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "BarString"), ["ConsoleApplication10.Program.Main(string[])"]); - } - - [WpfFact] - public async Task GenericExtensionMethods() - { - var text = @" -using System.Collections.Generic; -using System.Linq; -namespace N -{ - class Program + + [WpfFact] + public async Task PropertyGet() { - static void Main(string[] args) - { - List x = new List(); - var z = x.Si$$ngle(); - } + var text = """ + namespace N + { + class C + { + public int val + { + g$$et + { + return 0; + } + } + + public int goo() + { + var x = this.val; + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.val.get", [string.Format(EditorFeaturesResources.Calls_To_0, "get_val")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "get_val"), ["N.C.goo()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "System.Linq.Enumerable.Single(this System.Collections.Generic.IEnumerable)", [string.Format(EditorFeaturesResources.Calls_To_0, "Single")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Single"), ["N.Program.Main(string[])"]); - } - - [WpfFact] - public async Task InterfaceImplementors() - { - var text = @" -namespace N -{ - interface I + + [WpfFact] + public async Task Generic() { - void Go$$o(); + var text = """ + namespace N + { + class C + { + public int gen$$eric(this string generic, ref T stuff) + { + return 0; + } + + public int goo() + { + int i; + generic(", ref i); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.generic(this string, ref T)", [string.Format(EditorFeaturesResources.Calls_To_0, "generic")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "generic"), ["N.C.goo()"]); } - class C : I + [WpfFact] + public async Task ExtensionMethods() { - public void Goo() - { - } + var text = """ + namespace ConsoleApplication10 + { + class Program + { + static void Main(string[] args) + { + var x = "string"; + x.BarStr$$ing(); + } + } + + public static class Extensions + { + public static string BarString(this string s) + { + return s; + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "ConsoleApplication10.Extensions.BarString(this string)", [string.Format(EditorFeaturesResources.Calls_To_0, "BarString")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "BarString"), ["ConsoleApplication10.Program.Main(string[])"]); } - class G + [WpfFact] + public async Task GenericExtensionMethods() { - void Main() - { - I c = new C(); - c.Goo(); - } - - void Main2() - { - var c = new C(); - c.Goo(); - } + var text = """ + using System.Collections.Generic; + using System.Linq; + namespace N + { + class Program + { + static void Main(string[] args) + { + List x = new List(); + var z = x.Si$$ngle(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "System.Linq.Enumerable.Single(this System.Collections.Generic.IEnumerable)", [string.Format(EditorFeaturesResources.Calls_To_0, "Single")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Single"), ["N.Program.Main(string[])"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.I.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Implements_0, "Goo")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main()"]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Implements_0, "Goo"), ["N.C.Goo()"]); - } - - [WpfFact] - public async Task NoFindOverridesOnSealedMethod() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task InterfaceImplementors() { - void G$$oo() - { - } + var text = """ + namespace N + { + interface I + { + void Go$$o(); + } + + class C : I + { + public void Goo() + { + } + } + + class G + { + void Main() + { + I c = new C(); + c.Goo(); + } + + void Main2() + { + var c = new C(); + c.Goo(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.I.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), string.Format(EditorFeaturesResources.Implements_0, "Goo")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), ["N.G.Main()"]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Implements_0, "Goo"), ["N.C.Goo()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - Assert.DoesNotContain("Overrides", root.SupportedSearchCategories.Select(s => s.DisplayName)); - } - - [WpfFact] - public async Task FindOverrides() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task NoFindOverridesOnSealedMethod() { - public virtual void G$$oo() - { - } + var text = """ + namespace N + { + class C + { + void G$$oo() + { + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + Assert.DoesNotContain("Overrides", root.SupportedSearchCategories.Select(s => s.DisplayName)); } - class G : C + [WpfFact] + public async Task FindOverrides() { - public override void Goo() - { - } + var text = """ + namespace N + { + class C + { + public virtual void G$$oo() + { + } + } + + class G : C + { + public override void Goo() + { + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), EditorFeaturesResources.Overrides_]); + testState.VerifyResult(root, EditorFeaturesResources.Overrides_, ["N.G.Goo()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), EditorFeaturesResources.Overrides_]); - testState.VerifyResult(root, EditorFeaturesResources.Overrides_, ["N.G.Goo()"]); - } - - [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/844613")] - public async Task AbstractMethodInclusionToOverrides() - { - var text = @" -using System; - -abstract class Base -{ - public abstract void $$M(); -} - -class Derived : Base -{ - public override void M() + + [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/844613")] + public async Task AbstractMethodInclusionToOverrides() { - throw new NotImplementedException(); + var text = """ + using System; + + abstract class Base + { + public abstract void $$M(); + } + + class Derived : Base + { + public override void M() + { + throw new NotImplementedException(); + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "Base.M()", [string.Format(EditorFeaturesResources.Calls_To_0, "M"), EditorFeaturesResources.Overrides_, EditorFeaturesResources.Calls_To_Overrides]); + testState.VerifyResult(root, EditorFeaturesResources.Overrides_, ["Derived.M()"]); } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "Base.M()", [string.Format(EditorFeaturesResources.Calls_To_0, "M"), EditorFeaturesResources.Overrides_, EditorFeaturesResources.Calls_To_Overrides]); - testState.VerifyResult(root, EditorFeaturesResources.Overrides_, ["Derived.M()"]); - } - - [WpfFact] - public async Task SearchAfterEditWorks() - { - var text = @" -namespace N -{ - class C + + [WpfFact] + public async Task SearchAfterEditWorks() { - void G$$oo() - { - } - - void M() - { - Goo(); - } - } -}"; - using var testState = CallHierarchyTestState.Create(text); - var root = await testState.GetRootAsync(); + var text = """ + namespace N + { + class C + { + void G$$oo() + { + } + + void M() + { + Goo(); + } + } + } + """; + using var testState = CallHierarchyTestState.Create(text); + var root = await testState.GetRootAsync(); - testState.Workspace.Documents.Single().GetTextBuffer().Insert(0, "/* hello */"); + testState.Workspace.Documents.Single().GetTextBuffer().Insert(0, "/* hello */"); - testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"),]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), expectedCallers: ["N.C.M()"]); - } + testState.VerifyRoot(root, "N.C.Goo()", [string.Format(EditorFeaturesResources.Calls_To_0, "Goo"),]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "Goo"), expectedCallers: ["N.C.M()"]); + } - [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/57856")] - public async Task PropertySet() - { - var code = @" -namespace N -{ - class C + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/57856")] + public async Task PropertySet() { - public int Property { get; s$$et; } - void M() - { - Property = 2; - } - } -}"; - using var testState = CallHierarchyTestState.Create(code); - var root = await testState.GetRootAsync(); - testState.VerifyRoot(root, "N.C.Property.set", [string.Format(EditorFeaturesResources.Calls_To_0, "set_Property")]); - testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "set_Property"), ["N.C.M()"]); - } + var code = """ + namespace N + { + class C + { + public int Property { get; s$$et; } + void M() + { + Property = 2; + } + } + } + """; + using var testState = CallHierarchyTestState.Create(code); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "N.C.Property.set", [string.Format(EditorFeaturesResources.Calls_To_0, "set_Property")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "set_Property"), ["N.C.M()"]); } } From 968256cff051cc64485066e076526a75cbe7955a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:41:30 -0800 Subject: [PATCH 5/9] Add test --- .../CallHierarchy/CSharpCallHierarchyTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs index 915ce44dc0f58..7eb87cd5d2e96 100644 --- a/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs +++ b/src/VisualStudio/CSharp/Test/CallHierarchy/CSharpCallHierarchyTests.cs @@ -528,4 +528,26 @@ void M() testState.VerifyRoot(root, "N.C.Property.set", [string.Format(EditorFeaturesResources.Calls_To_0, "set_Property")]); testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, "set_Property"), ["N.C.M()"]); } + + [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/77327")] + public async Task PrimaryConstructor() + { + var code = """ + public class $$Class1(string test) + { + } + + class D + { + public void M() + { + var c = new Class1("test"); + } + } + """; + using var testState = CallHierarchyTestState.Create(code); + var root = await testState.GetRootAsync(); + testState.VerifyRoot(root, "Class1.Class1(string)", [string.Format(EditorFeaturesResources.Calls_To_0, ".ctor")]); + testState.VerifyResult(root, string.Format(EditorFeaturesResources.Calls_To_0, ".ctor"), ["D.M()"]); + } } From 18f5800de7c47edbbbb01aac634eb2d6275451cf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:43:12 -0800 Subject: [PATCH 6/9] Docs --- .../Core/Portable/FindUsages/FindUsagesHelpers.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs b/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs index 4c80956c8deb2..207c7ba1e36db 100644 --- a/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs +++ b/src/Features/Core/Portable/FindUsages/FindUsagesHelpers.cs @@ -24,15 +24,14 @@ public static string GetDisplayName(ISymbol symbol) => GetRelevantSymbolAndProjectAtPositionAsync(document, position, preferPrimaryConstructor: false, cancellationToken); /// - /// Common helper for both the synchronous and streaming versions of FAR. - /// It returns the symbol we want to search for and the solution we should - /// be searching. - /// - /// Note that the returned may absolutely *not* be - /// the same as document.Project.Solution. This is because - /// there may be symbol mapping involved (for example in Metadata-As-Source - /// scenarios). + /// Common helper for both the synchronous and streaming versions of FAR. It returns the symbol we want to search + /// for and the solution we should be searching. + /// Note that the returned may absolutely *not* be the same as + /// document.Project.Solution. This is because there may be symbol mapping involved (for example in + /// Metadata-As-Source scenarios). /// + /// Whether the named type or primary constructor should be preferred if the + /// position is on a type-header fof a type declaration that has primary constructor parameters. public static async Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( Document document, int position, bool preferPrimaryConstructor, CancellationToken cancellationToken) { From fa00152b24ee44d286ed1c3e2cb47b460eb65548 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 10:43:51 -0800 Subject: [PATCH 7/9] NRT --- .../Def/CallHierarchy/CallHierarchyCommandHandler.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs index 15c72d260dc66..ccb49296d1506 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs @@ -2,24 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.SymbolMapping; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.LanguageServices; @@ -107,7 +99,7 @@ private async Task ExecuteCommandAsync(Document document, int caretPosition) await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); } - var notificationService = document.Project.Solution.Services.GetService(); + var notificationService = document.Project.Solution.Services.GetRequiredService(); notificationService.SendNotification(EditorFeaturesResources.Cursor_must_be_on_a_member_name, severity: NotificationSeverity.Information); } From c226bfcd0cd78125bed35d8d66ef111e32157542 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 12:21:06 -0800 Subject: [PATCH 8/9] Primary constructor --- .../CallHierarchyCommandHandler.cs | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs index ccb49296d1506..8ae227be08710 100644 --- a/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs +++ b/src/VisualStudio/Core/Def/CallHierarchy/CallHierarchyCommandHandler.cs @@ -27,31 +27,26 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.CallHierarchy; [ContentType(ContentTypeNames.VisualBasicContentType)] [Name("CallHierarchy")] [Order(After = PredefinedCommandHandlerNames.DocumentationComments)] -internal class CallHierarchyCommandHandler : ICommandHandler +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class CallHierarchyCommandHandler( + IThreadingContext threadingContext, + IUIThreadOperationExecutor threadOperationExecutor, + IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, + [ImportMany] IEnumerable presenters, + CallHierarchyProvider provider) : ICommandHandler { - private readonly IThreadingContext _threadingContext; - private readonly IUIThreadOperationExecutor _threadOperationExecutor; - private readonly IAsynchronousOperationListener _listener; - private readonly ICallHierarchyPresenter _presenter; - private readonly CallHierarchyProvider _provider; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly IUIThreadOperationExecutor _threadOperationExecutor = threadOperationExecutor; + private readonly IAsynchronousOperationListener _listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.CallHierarchy); + private readonly ICallHierarchyPresenter _presenter = presenters.FirstOrDefault(); + private readonly CallHierarchyProvider _provider = provider; - public string DisplayName => EditorFeaturesResources.Call_Hierarchy; + public string DisplayName + => EditorFeaturesResources.Call_Hierarchy; - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CallHierarchyCommandHandler( - IThreadingContext threadingContext, - IUIThreadOperationExecutor threadOperationExecutor, - IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, - [ImportMany] IEnumerable presenters, - CallHierarchyProvider provider) - { - _threadingContext = threadingContext; - _threadOperationExecutor = threadOperationExecutor; - _listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.CallHierarchy); - _presenter = presenters.FirstOrDefault(); - _provider = provider; - } + public CommandState GetCommandState(ViewCallHierarchyCommandArgs args) + => CommandState.Available; public bool ExecuteCommand(ViewCallHierarchyCommandArgs args, CommandExecutionContext context) { @@ -102,7 +97,4 @@ private async Task ExecuteCommandAsync(Document document, int caretPosition) var notificationService = document.Project.Solution.Services.GetRequiredService(); notificationService.SendNotification(EditorFeaturesResources.Cursor_must_be_on_a_member_name, severity: NotificationSeverity.Information); } - - public CommandState GetCommandState(ViewCallHierarchyCommandArgs args) - => CommandState.Available; } From 149bfcdceef1bfb012307f5f48586da5ca1fa901 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 24 Feb 2025 23:04:51 -0800 Subject: [PATCH 9/9] Simplify --- .../Core/Portable/Rename/RenameUtilities.cs | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs b/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs index 3e884957ee51d..471b1941a4742 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameUtilities.cs @@ -327,21 +327,11 @@ public static async Task FindDefinitionSymbolAsync( // If we're renaming a property, it might be a synthesized property for a method // backing field. - if (symbol.Kind == SymbolKind.Parameter) + if (symbol is IParameterSymbol { ContainingSymbol: IMethodSymbol { AssociatedSymbol: IPropertySymbol associatedParameterProperty } containingMethod }) { - if (symbol.ContainingSymbol.Kind == SymbolKind.Method) - { - var containingMethod = (IMethodSymbol)symbol.ContainingSymbol; - if (containingMethod.AssociatedSymbol is IPropertySymbol) - { - var associatedPropertyOrEvent = (IPropertySymbol)containingMethod.AssociatedSymbol; - var ordinal = containingMethod.Parameters.IndexOf((IParameterSymbol)symbol); - if (ordinal < associatedPropertyOrEvent.Parameters.Length) - { - return associatedPropertyOrEvent.Parameters[ordinal]; - } - } - } + var ordinal = containingMethod.Parameters.IndexOf((IParameterSymbol)symbol); + if (ordinal < associatedParameterProperty.Parameters.Length) + return associatedParameterProperty.Parameters[ordinal]; } // if we are renaming a compiler generated delegate for an event, cascade to the event @@ -349,7 +339,7 @@ public static async Task FindDefinitionSymbolAsync( return typeSymbol.AssociatedSymbol; // If we are renaming a constructor or destructor, we wish to rename the whole type - if (symbol is IMethodSymbol { MethodKind: MethodKind.Constructor | MethodKind.StaticConstructor | MethodKind.Destructor }) + if (symbol is IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor or MethodKind.Destructor }) return symbol.ContainingType; // If we are renaming a backing field for a property, cascade to the property