3

I'm writing a plugin for resharper which I want to use to navigate from a ConcreteCommand -> ConcreteCommandHandler where those types look like this

public class ConcreteCommand : ICommand

public class ConcreteCommandHandler : ICommandHandler<ConcreteCommand>

I've got as far as adding my navigation menu option when the cursor is on a ICommand instance/definition (currently only by checking if the name contains 'Command' and not 'CommandHandler'), and I think I have the code necessary to actually search for a type which inherits something, but my issue is that the only thing I actually have a type for is my ConcereteCommand and I need to create (or get a reference to) the generic type ICommandHandler<T> with T being the type the cursor is currently on.

So I have 2 things I still want to know:

  • How can I check if my IDeclaredElement is an implementation of a particular interface (ideally by specifying the full name in a string from config)?
  • How can I create a ITypeElement which is a generic type of a specific interface where I can set the generic type from my existing IDeclaredElements type, so I can then find classes which inherit this?

My existing code looks like this:

[ContextNavigationProvider]
public class CommandHandlerNavigationProvider : INavigateFromHereProvider
{
    public IEnumerable<ContextNavigation> CreateWorkflow(IDataContext dataContext)
    {
        ICollection<IDeclaredElement> declaredElements = dataContext.GetData(DataConstants.DECLARED_ELEMENTS);
        if (declaredElements != null || declaredElements.Any())
        {
            IDeclaredElement declaredElement = declaredElements.First();

            if (IsCommand(declaredElement))
            {
                var solution = dataContext.GetData(JetBrains.ProjectModel.DataContext.DataConstants.SOLUTION);
                yield return new ContextNavigation("This Command's &handler", null, NavigationActionGroup.Other, () => { GotToInheritor(solution,declaredElement); });
            }
        }
    }

    private void GotToInheritor(ISolution solution, IDeclaredElement declaredElement)
    {            
        var inheritorsConsumer = new InheritorsConsumer();
        SearchDomainFactory searchDomainFactory = solution.GetComponent<SearchDomainFactory>();
//How can I create the ITypeElement MyNameSpace.ICommandHandler<(ITypeElement)declaredElement> here?  
      solution.GetPsiServices().Finder.FindInheritors((ITypeElement)declaredElement, searchDomainFactory.CreateSearchDomain(solution, true),                    inheritorsConsumer, NullProgressIndicator.Instance);
    }

    private bool IsCommand(IDeclaredElement declaredElement)
    {
//How can I check if my declaredElement is an implementation of ICommand here?
        string className = declaredElement.ShortName;
        return className.Contains("Command")
               && !className.Contains("CommandHandler");
    }
}
Sam Holder
  • 32,535
  • 13
  • 101
  • 181

1 Answers1

1

Ok managed to work this out with a fair bit of pushing in the right direction from @CitizenMatt.

basically my solution looks like this (still needs some tidying up)

private static readonly List<HandlerMapping> HandlerMappings = new List<HandlerMapping>
{
    new HandlerMapping("HandlerNavigationTest.ICommand", "HandlerNavigationTest.ICommandHandler`1", "HandlerNavigationTest"),
    new HandlerMapping("HandlerNavTest2.IEvent", "HandlerNavTest2.IEventHandler`1", "HandlerNavTest2")
};

public IEnumerable<ContextNavigation> CreateWorkflow(IDataContext dataContext)
{
    ICollection<IDeclaredElement> declaredElements = dataContext.GetData(DataConstants.DECLARED_ELEMENTS);
    if (declaredElements != null && declaredElements.Any())
    {
        IDeclaredElement declaredElement = declaredElements.First();

        ISolution solution = dataContext.GetData(JetBrains.ProjectModel.DataContext.DataConstants.SOLUTION);
        ITypeElement handlerType = GetHandlerType(declaredElement);
        if (handlerType != null)
        {
            yield return new ContextNavigation("&Handler", null, NavigationActionGroup.Other, () => GoToInheritor(solution, declaredElement as IClass, dataContext, handlerType));
        }
    }
}

private static ITypeElement GetHandlerType(IDeclaredElement declaredElement)
{
    var theClass = declaredElement as IClass;
    if (theClass != null)
    {
        foreach (IPsiModule psiModule in declaredElement.GetPsiServices().Modules.GetModules())
        {
            foreach (var handlerMapping in HandlerMappings)
            {
                IDeclaredType commandInterfaceType = TypeFactory.CreateTypeByCLRName(handlerMapping.HandledType, psiModule, theClass.ResolveContext);

                ITypeElement typeElement = commandInterfaceType.GetTypeElement();
                if (typeElement != null)
                {
                    if (theClass.IsDescendantOf(typeElement))
                    {
                        IDeclaredType genericType = TypeFactory.CreateTypeByCLRName(handlerMapping.HandlerType, psiModule, theClass.ResolveContext);
                        ITypeElement genericTypeElement = genericType.GetTypeElement();
                        return genericTypeElement;
                    }
                }
            }

        }
    }

    return null;
}

private static void GoToInheritor(ISolution solution, IClass theClass, IDataContext dataContext, ITypeElement genericHandlerType)
{
    var inheritorsConsumer = new InheritorsConsumer();
    var searchDomainFactory = solution.GetComponent<SearchDomainFactory>();

    IDeclaredType theType = TypeFactory.CreateType(theClass);
    IDeclaredType commandHandlerType = TypeFactory.CreateType(genericHandlerType, theType);
    ITypeElement handlerTypeelement = commandHandlerType.GetTypeElement();
    solution.GetPsiServices().Finder.FindInheritors(handlerTypeelement, searchDomainFactory.CreateSearchDomain(solution, true),
        inheritorsConsumer, NullProgressIndicator.Instance);
    var potentialNavigationPoints = new List<INavigationPoint>();
    foreach (ITypeElement inheritedInstance in inheritorsConsumer.FoundElements)
    {
        IDeclaredType[] baseClasses = inheritedInstance.GetAllSuperTypes();
        foreach (IDeclaredType declaredType in baseClasses)
        {
            if (declaredType.IsInterfaceType())
            {
                if (declaredType.Equals(commandHandlerType))
                {
                    var navigationPoint = new DeclaredElementNavigationPoint(inheritedInstance);
                    potentialNavigationPoints.Add(navigationPoint);
                }
            }
        }
    }

    if (potentialNavigationPoints.Any())
    {
        NavigationOptions options = NavigationOptions.FromDataContext(dataContext, "Which handler do you want to navigate to?");
        NavigationManager.GetInstance(solution).Navigate(potentialNavigationPoints, options);
    }
} 

public class InheritorsConsumer : IFindResultConsumer<ITypeElement>
{
    private const int MaxInheritors = 50;

    private readonly HashSet<ITypeElement> elements = new HashSet<ITypeElement>();

    public IEnumerable<ITypeElement> FoundElements
    {
        get { return elements; }
    } 

    public ITypeElement Build(FindResult result)
    {
        var inheritedElement = result as FindResultInheritedElement;
        if (inheritedElement != null)
            return (ITypeElement) inheritedElement.DeclaredElement;
        return null;
    }

    public FindExecution Merge(ITypeElement data)
    {
        elements.Add(data);
        return elements.Count < MaxInheritors ? FindExecution.Continue : FindExecution.Stop;
    }
}

And this allows me no navigate to multiple handlers if they exist. This currently relies on the interfaces for the handled type and the handler type being in the same assembly. But this seems reasonable enough for me at the moment.

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • 1
    Yep, this is how you do it. Ideally, you should be able to get the `IPsiModule` that represents the assembly that your type is defined in, either by getting it via `Modules.GetPsiModules(project)` if you know the project it's defined in, or `Modules.GetAssemblyPsiModuleByName(assemblyName)` if it's in a compiled assembly. Or maybe even just using `Modules.GetModules()` and checking the `Name` property. – citizenmatt Jun 23 '15 at 10:39
  • Thanks @citizenmatt. I've been struggling with the second part though. I'm able to get the type element for the generic type in a similar way, and think I know how to set the generic parameter (using `genericType.GetSubstitution().Apply(theType);`) but cannot find how I can get an `IType` from my `IDeclaredElement` which is my class the cursor was on when my provider was invoked. I cast it to an `IClass` and all sorts but the `Type()` extension method always seems to return null... – Sam Holder Jun 23 '15 at 10:44
  • 1
    Again, you're in the right direction. A closed generic type is an instance of `IDeclaredElement` with an `ISubstitution` that represents the types in the type parameters. You can create this through `TypeFactory.CreateType` again, passing in a substitution or an array of `IType` instances. Just use `TypeFactory.CreateType` again to create an `IType` from a declared element. – citizenmatt Jun 23 '15 at 11:24
  • @citizenmatt Woop! Woop! have the correct type in my inheritors consumer now! Just need to get the ide to navigate to the code file for this type now. – Sam Holder Jun 23 '15 at 12:44
  • @citizenmatt I updated my answer with what I've done to get the right class/source file but how do I actually get VS to navigate to this file? Again my google-fu doesn't seem quite strong enough – Sam Holder Jun 23 '15 at 14:56
  • Oh, your google-fu isn't failing, it's just not documented properly :( Check out `NavigationManager.Navigate` (inject NM as a component) and use something like `new DeclaredElementNavigationPoint` to pass to `Navigate`. Note that this will only handle 1 target to navigate to. If you have multiple, pass in a list of navigation points, and ReSharper will show a menu for you to choose the actual target you want. – citizenmatt Jun 24 '15 at 08:38
  • @citizenmatt Thanks! Navigation is now working great! Only remaining problem is that I'm not finding elements that are of the closed generic type only all implementations of the open generic type. I've asked a [new question](http://stackoverflow.com/q/31044308/97614) about this though with some specific details. Any pointers welcome. – Sam Holder Jun 25 '15 at 07:59