HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

GoTo Implementation: First-class navigation for the VBE

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thenavigationfirstgotoforvbeimplementationclass

Problem

ReSharper has a very handy feature that's particularly useful when coding against abstractions. Since coding against abstractions is something that can also be done in VBA, the next release of rubberduck is going to include that functionality as well:

The click handler for the code pane context menu simply calls the paremeterless overload of FindAllImplementations:

private void _findAllImplementationsContextMenu_Click(CommandBarButton Ctrl, ref bool CancelDefault)
{
    FindAllImplementations();
}

private void FindAllImplementations()
{
    var selection = IDE.ActiveCodePane.GetSelection();
    var progress = new ParsingProgressPresenter();
    var parseResult = progress.Parse(_parser, IDE.ActiveVBProject);

    var implementsStatement = parseResult.Declarations.FindInterfaces()
        .SelectMany(i => i.References.Where(reference => reference.Context.Parent is VBAParser.ImplementsStmtContext))
        .SingleOrDefault(r => r.QualifiedModuleName == selection.QualifiedName && r.Selection.Contains(selection.Selection));

    if (implementsStatement != null)
    {
        FindAllImplementations(implementsStatement.Declaration, parseResult);
    }

    var member = parseResult.Declarations.FindInterfaceImplementationMembers()
        .SingleOrDefault(m => m.Selection.Contains(selection.Selection));

    if (member == null)
    {
        member = parseResult.Declarations.FindInterfaceMembers()
            .SingleOrDefault(m => m.Project == selection.QualifiedName.Project
                                  && m.ComponentName == selection.QualifiedName.ComponentName
                                  && m.Selection.Contains(selection.Selection));
    }

    if (member == null)
    {
        return;
    }

    FindAllImplementations(member, parseResult);
}


There's also a public overload that takes a Declaration target, so that the functionality can be used from the Code Explorer's own context menu:

```
public void FindAllImplementations(Declaration target)
{

Solution

var member = parseResult.Declarations.FindInterfaceImplementationMembers()
    .SingleOrDefault(m => m.Selection.Contains(selection.Selection));

if (member == null)
{
    member = parseResult.Declarations.FindInterfaceMembers()
        .SingleOrDefault(m => m.Project == selection.QualifiedName.Project
                              && m.ComponentName == selection.QualifiedName.ComponentName
                              && m.Selection.Contains(selection.Selection));
}


This has a crash-causing bug. You need to change the top .SingleOrDefault() to match the bottom one.

Right here, you need to ensure the members are of the same interface - not just have the same name:

if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName);


Otherwise, you return all the IClass2_DoSomethings along with the IClass1_DoSomethings.

A simple .Where() will fix it:

if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName)
           .Where(item => item.IdentifierName == target.ComponentName + "_" + target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName)
       .Where(item => item.IdentifierName == member.ComponentName + "_" + member.IdentifierName);


Thanks for helping me develop patches for these problems, @Mat's Mug

Code Snippets

var member = parseResult.Declarations.FindInterfaceImplementationMembers()
    .SingleOrDefault(m => m.Selection.Contains(selection.Selection));

if (member == null)
{
    member = parseResult.Declarations.FindInterfaceMembers()
        .SingleOrDefault(m => m.Project == selection.QualifiedName.Project
                              && m.ComponentName == selection.QualifiedName.ComponentName
                              && m.Selection.Contains(selection.Selection));
}
if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName);
if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName)
           .Where(item => item.IdentifierName == target.ComponentName + "_" + target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName)
       .Where(item => item.IdentifierName == member.ComponentName + "_" + member.IdentifierName);

Context

StackExchange Code Review Q#91785, answer score: 4

Revisions (0)

No revisions yet.