From 14d28cbb2a0ff10880a5724ddcda982493c02a5b Mon Sep 17 00:00:00 2001 From: Yatmund Christiansen Date: Tue, 8 Sep 2026 16:03:22 +0100 Subject: [PATCH] fix: navigate to handler from a Send/Publish call site and stop truncating the handler search --- CHANGELOG.md | 7 ++ .../Actions/GoToHandlerAction.cs | 15 ++-- .../Actions/GoToHandlerNavigationAction.cs | 11 +-- ...MediatorRequestNavigateFromHereProvider.cs | 8 +- .../Psi/Search/InheritorsConsumer.cs | 2 +- .../ReSharper/Psi/Tree/MediatorCallSite.cs | 81 +++++++++++++++++++ .../Services/Find/HandlerSelector.cs | 9 ++- .../Services/Find/IHandlerSelector.cs | 2 +- 8 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/dotnet/MediatorPlugin/ReSharper/Psi/Tree/MediatorCallSite.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d3fef31..50abfea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog All notable changes to this project will be documented in this file. +## 26.09.04XX +- "Go to Handler" now also works with the caret anywhere inside a `mediator.Send(...)` / + `Publish(...)` call: the first argument's type is resolved, so there is no need to navigate to the + request declaration first. +- Raise the inheritor search cap from 50 to 300 so solutions with many handlers no longer have the + matching handler truncated away before it can be selected. + ## 26.08.20XX - Fix issue #117: updated SDK to 2026.2. diff --git a/src/dotnet/MediatorPlugin/Actions/GoToHandlerAction.cs b/src/dotnet/MediatorPlugin/Actions/GoToHandlerAction.cs index 28e572b..397b53c 100644 --- a/src/dotnet/MediatorPlugin/Actions/GoToHandlerAction.cs +++ b/src/dotnet/MediatorPlugin/Actions/GoToHandlerAction.cs @@ -9,6 +9,7 @@ using JetBrains.TextControl; using JetBrains.Util; using ReSharper.MediatorPlugin.Diagnostics; +using ReSharper.MediatorPlugin.ReSharper.Psi.Tree; using ReSharper.MediatorPlugin.Services.Find; using ReSharper.MediatorPlugin.Services.Navigation; @@ -81,14 +82,12 @@ protected override Action ExecutePsiTransaction(ISolution solution private static IIdentifier? GetSelectedMediatrRequest(LanguageIndependentContextActionDataProvider dataProvider) { var selectedTreeNode = dataProvider.GetSelectedTreeNode(); - - if (selectedTreeNode is IIdentifier identifier) - { - Logger.Instance.Log(LoggingLevel.VERBOSE, $"Selected tree node is an identifier: {identifier.Name}"); - return identifier; - } - Logger.Instance.Log(LoggingLevel.VERBOSE, "Selected tree node is not an identifier"); - return null; + IIdentifier? identifier = MediatorCallSite.ResolveRequestIdentifier(selectedTreeNode); + + if (identifier is null) + Logger.Instance.Log(LoggingLevel.VERBOSE, "Selected tree node is not a Mediator request or dispatch call"); + + return identifier; } } \ No newline at end of file diff --git a/src/dotnet/MediatorPlugin/Actions/GoToHandlerNavigationAction.cs b/src/dotnet/MediatorPlugin/Actions/GoToHandlerNavigationAction.cs index f39e2dc..d670311 100644 --- a/src/dotnet/MediatorPlugin/Actions/GoToHandlerNavigationAction.cs +++ b/src/dotnet/MediatorPlugin/Actions/GoToHandlerNavigationAction.cs @@ -7,6 +7,7 @@ using JetBrains.ReSharper.Feature.Services.Navigation.ContextNavigation; using JetBrains.ReSharper.Psi.Tree; using ReSharper.MediatorPlugin.Diagnostics; +using ReSharper.MediatorPlugin.ReSharper.Psi.Tree; using ReSharper.MediatorPlugin.Services.Find; using ReSharper.MediatorPlugin.Services.Navigation; @@ -23,7 +24,9 @@ public bool Update(IDataContext context, ActionPresentation presentation, Delega { var selectedTreeNode = context.GetSelectedTreeNode(); - if (selectedTreeNode is not IIdentifier identifier) + IIdentifier? identifier = MediatorCallSite.ResolveRequestIdentifier(selectedTreeNode); + + if (identifier is null) return nextUpdate.Invoke(); return _handlerSelector.IsMediatorRequestSupported(identifier); @@ -36,12 +39,6 @@ public void Execute(IDataContext context, DelegateExecute nextExecute) var solution = context.GetComponent(); var selectedTreeNode = context.GetSelectedTreeNode(); - if (selectedTreeNode is not IIdentifier) - { - Logger.Instance.Log(LoggingLevel.VERBOSE, "Selected element is not an identifier"); - return; - } - _handlerSelector.NavigateToHandler ( solution, diff --git a/src/dotnet/MediatorPlugin/Navigations/Providers/MediatorRequestNavigateFromHereProvider.cs b/src/dotnet/MediatorPlugin/Navigations/Providers/MediatorRequestNavigateFromHereProvider.cs index cf83cd1..173560d 100644 --- a/src/dotnet/MediatorPlugin/Navigations/Providers/MediatorRequestNavigateFromHereProvider.cs +++ b/src/dotnet/MediatorPlugin/Navigations/Providers/MediatorRequestNavigateFromHereProvider.cs @@ -33,18 +33,12 @@ IDataContext dataContext var solution = dataContext.GetComponent(); var selectedTreeNode = dataContext.GetSelectedTreeNode(); - if (selectedTreeNode is not IIdentifier) - { - Logger.Instance.Log(LoggingLevel.VERBOSE, $"Selected element is not an instance {nameof(IIdentifier)}"); - return; - } - _handlerSelector.NavigateToHandler ( solution, selectedTreeNode, new DataContextNavigationOptionsFactory(dataContext) - ); + ); } ) }; diff --git a/src/dotnet/MediatorPlugin/ReSharper/Psi/Search/InheritorsConsumer.cs b/src/dotnet/MediatorPlugin/ReSharper/Psi/Search/InheritorsConsumer.cs index 56763ac..9a91456 100644 --- a/src/dotnet/MediatorPlugin/ReSharper/Psi/Search/InheritorsConsumer.cs +++ b/src/dotnet/MediatorPlugin/ReSharper/Psi/Search/InheritorsConsumer.cs @@ -6,7 +6,7 @@ namespace ReSharper.MediatorPlugin.ReSharper.Psi.Search; internal sealed class InheritorsConsumer : IFindResultConsumer { - private const int MaxInheritors = 50; + private const int MaxInheritors = 300; private readonly HashSet _elements = new(); diff --git a/src/dotnet/MediatorPlugin/ReSharper/Psi/Tree/MediatorCallSite.cs b/src/dotnet/MediatorPlugin/ReSharper/Psi/Tree/MediatorCallSite.cs new file mode 100644 index 0000000..a641619 --- /dev/null +++ b/src/dotnet/MediatorPlugin/ReSharper/Psi/Tree/MediatorCallSite.cs @@ -0,0 +1,81 @@ +using System.Linq; +using JetBrains.Diagnostics; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.CSharp.Tree; +using JetBrains.ReSharper.Psi.Tree; +using JetBrains.ReSharper.Psi.Util; +using ReSharper.MediatorPlugin.Diagnostics; + +namespace ReSharper.MediatorPlugin.ReSharper.Psi.Tree; + +internal static class MediatorCallSite +{ + public static IIdentifier? ResolveRequestIdentifier(ITreeNode? node) + { + if (node is null) + return null; + + return ResolveFromEnclosingCall(node) ?? node as IIdentifier; + } + + private static IIdentifier? ResolveFromEnclosingCall(ITreeNode node) + { + IInvocationExpression? invocation = node.GetContainingNode(returnThis: true); + + if (invocation is null) + { + Logger.Instance.Log(LoggingLevel.VERBOSE, "> Caret is not inside an invocation expression"); + return null; + } + + foreach (ICSharpArgument argument in invocation.Arguments) + { + IIdentifier? identifier = ArgumentTypeDeclarationIdentifier(argument.Value); + + if (identifier is not null) + return identifier; + } + + Logger.Instance.Log(LoggingLevel.VERBOSE, $"> No invocation argument resolved to a navigable type declaration (method '{InvokedMethodName(invocation)}')"); + return null; + } + + private static IIdentifier? ArgumentTypeDeclarationIdentifier(ICSharpExpression? argumentValue) + { + ITypeElement? typeElement = ResolveTypeElement(argumentValue); + + if (typeElement is null) + return null; + + if (typeElement.GetDeclarations().FirstOrDefault() is not IClassLikeDeclaration declaration) + { + Logger.Instance.Log(LoggingLevel.VERBOSE, $"> Argument type '{typeElement.GetClrName().FullName}' has no source declaration to navigate from"); + return null; + } + + Logger.Instance.Log(LoggingLevel.VERBOSE, $"> Resolved invocation argument to type '{declaration.DeclaredName}'"); + return declaration.NameIdentifier; + } + + private static ITypeElement? ResolveTypeElement(ICSharpExpression? expression) + { + switch (expression) + { + case null: + return null; + + case IObjectCreationExpression objectCreation + when objectCreation.TypeReference?.Resolve().DeclaredElement is ITypeElement created: + return created; + + default: + return (expression.GetExpressionType().ToIType() as IDeclaredType)?.GetTypeElement(); + } + } + + private static string InvokedMethodName(IInvocationExpression invocation) + { + return (invocation.InvokedExpression as IReferenceExpression)?.Reference.GetName() ?? ""; + } +} diff --git a/src/dotnet/MediatorPlugin/Services/Find/HandlerSelector.cs b/src/dotnet/MediatorPlugin/Services/Find/HandlerSelector.cs index 7d7898c..d5b4913 100644 --- a/src/dotnet/MediatorPlugin/Services/Find/HandlerSelector.cs +++ b/src/dotnet/MediatorPlugin/Services/Find/HandlerSelector.cs @@ -7,6 +7,7 @@ using JetBrains.ReSharper.Psi.Tree; using JetBrains.TextControl; using ReSharper.MediatorPlugin.Diagnostics; +using ReSharper.MediatorPlugin.ReSharper.Psi.Tree; using ReSharper.MediatorPlugin.Services.Libraries; using ReSharper.MediatorPlugin.Services.Navigation; using System.Collections.Generic; @@ -34,13 +35,15 @@ IIdentifier identifier public void NavigateToHandler ( ISolution solution, - ITreeNode selectedTreeNode, + ITreeNode? selectedTreeNode, INavigationOptionsFactory navigationOptionsFactory ) { - if (selectedTreeNode is not IIdentifier selectedIdentifier) + IIdentifier? selectedIdentifier = MediatorCallSite.ResolveRequestIdentifier(selectedTreeNode); + + if (selectedIdentifier is null) { - Logger.Instance.Log(LoggingLevel.VERBOSE, $"Selected element is not an instance {nameof(IIdentifier)}"); + Logger.Instance.Log(LoggingLevel.VERBOSE, "Selected element is not a Mediator request or dispatch call"); return; } diff --git a/src/dotnet/MediatorPlugin/Services/Find/IHandlerSelector.cs b/src/dotnet/MediatorPlugin/Services/Find/IHandlerSelector.cs index 6464f1d..9fe7942 100644 --- a/src/dotnet/MediatorPlugin/Services/Find/IHandlerSelector.cs +++ b/src/dotnet/MediatorPlugin/Services/Find/IHandlerSelector.cs @@ -14,7 +14,7 @@ IIdentifier identifier void NavigateToHandler ( ISolution solution, - ITreeNode selectedTreeNode, + ITreeNode? selectedTreeNode, INavigationOptionsFactory navigationOptionsFactory ); } \ No newline at end of file