With the ANTLR-powered parser, I was able to reimplement all code inspections from the last release build, and the rest of the inspections on the road map are now implementable.

![Rubberduck's "Code Inspections" toolbar, floating](https://camo.githubusercontent.com/d1bbb074d59fc763789889d1be38b5ab1b30ce01/687474703a2f2f692e737461636b2e696d6775722e636f6d2f304d536f742e706e67)

Code inspections and quick-fixes are nice, but we want our Rubberduck even smarter than that. So I wrote one of the coolest things I've ever implemented - an *Extract Method* refactoring functionality!

Problem is, I'm pretty green working with an ANTLR parse tree, and *I don't know* if I'm doing this right. It works, but perhaps I'm doing dumb things that slow things down or something. Also as much as I'm proud of what this code does, it looks... horrible.

![Rubberduck's Extract Method dialog, previewing a method extracted from the body of a FizzBuzz loop.](https://i.stack.imgur.com/3KQ2S.png)

Changing the return value automatically updates the preview box. For example, if we decided we didn't want to return anything, the signature would change from `Function` to `Sub`, and `result` would be passed as a `ByRef` parameter instead of being a local variable to return:

![per above description, showing the 'result' variable now as a 'ByRef' parameter](https://i.stack.imgur.com/EH6TR.png)

The only thing it doesn't do [yet] I wish it did, is cleaning up unused declarations in the original method... but I can live with that for a few more releases.

---

The view is consumed as an interface:

```
public interface IExtractMethodDialog : IDialogView
{
    ExtractedParameter ReturnValue { get; set; }
    IEnumerable<ExtractedParameter> ReturnValues { get; set; }

    VBAccessibility Accessibility { get; set; }
    string MethodName { get; set; }
    bool SetReturnValue { get; set; }
    bool CanSetReturnValue { get; set; }

    event EventHandler RefreshPreview;
    void OnRefreshPreview();
    string Preview { get; set; }

    IEnumerable<ExtractedParameter> Parameters { get; set; }

    IEnumerable<ExtractedParameter> Inputs { get; set; }
    IEnumerable<ExtractedParameter> Outputs { get; set; }
    IEnumerable<ExtractedParameter> Locals { get; set; }
}

```

The `IDialogView` interface it inherits is just a bare-bones dialog interface:

```
public interface IDialogView
{
    event EventHandler CancelButtonClicked;
    void OnCancelButtonClicked();

    event EventHandler OkButtonClicked;
    void OnOkButtonClicked();

    DialogResult ShowDialog();
}

```

The bulk of the logic is in the `ExtractMethodPresenter` class.

* Parameters are all variables in use in the selected snippet.
* If a variable is only ever used in the selected snippet, then its declaration is moved to the new method and it's not passed as a parameter.
* If a variable is used after the generated method call, and there's only one of such variables, that variable is the default suggested return value.
* If there's more than one, they are passed as ByRef parameters.

Here's my implementation:

```
public class ExtractMethodPresenter
{
    private readonly IExtractMethodDialog _view;

    private readonly IParseTree _parentMethodTree;
    private IDictionary<VisualBasic6Parser.AmbiguousIdentifierContext, ExtractedDeclarationUsage> _parentMethodDeclarations;

    private readonly IEnumerable<ExtractedParameter> _input;
    private readonly IEnumerable<ExtractedParameter> _output;
    private readonly IEnumerable<VisualBasic6Parser.AmbiguousIdentifierContext> _locals; 

    private readonly string _selectedCode;
    private readonly VBE _vbe;
    private readonly Selection _selection;

    public ExtractMethodPresenter(VBE vbe, IExtractMethodDialog dialog, IParseTree parentMethod, Selection selection)
    {
        _vbe = vbe;
        _selection = selection;

        _view = dialog;
        _parentMethodTree = parentMethod;
        _selectedCode = vbe.ActiveCodePane.CodeModule.get_Lines(selection.StartLine, selection.LineCount);

        _parentMethodDeclarations = ExtractMethodRefactoring.GetParentMethodDeclarations(parentMethod, selection);

        var input = _parentMethodDeclarations.Where(kvp => kvp.Value == ExtractedDeclarationUsage.UsedBeforeSelection).ToList();
        var output = _parentMethodDeclarations.Where(kvp => kvp.Value == ExtractedDeclarationUsage.UsedAfterSelection).ToList();

        _locals = _parentMethodDeclarations.Where(
            kvp => kvp.Value == ExtractedDeclarationUsage.UsedOnlyInSelection
                || kvp.Value == ExtractedDeclarationUsage.UsedAfterSelection
            ).Select(kvp => kvp.Key);

        _input = ExtractParameters(input);
        _output = ExtractParameters(output);
    }

    private IEnumerable<ExtractedParameter> ExtractParameters(IList<KeyValuePair<VisualBasic6Parser.AmbiguousIdentifierContext, ExtractedDeclarationUsage>> declarations)
    {
        var consts = declarations
            .Where(kvp => kvp.Key.Parent is VisualBasic6Parser.ConstSubStmtContext)
            .Select(kvp => kvp.Key.Parent)
            .Cast<VisualBasic6Parser.ConstSubStmtContext>()
            .Select(constant => new ExtractedParameter(
                constant.ambiguousIdentifier().GetText(),
                constant.asTypeClause() == null
                    ? Tokens.Variant
                    : constant.asTypeClause().type().GetText(),
                ExtractedParameter.PassedBy.ByVal));

        var variables = declarations
            .Where(kvp => kvp.Key.Parent is VisualBasic6Parser.VariableSubStmtContext)
            .Select(kvp => new ExtractedParameter(
                kvp.Key.GetText(),
                ((VisualBasic6Parser.VariableSubStmtContext) kvp.Key.Parent).asTypeClause() == null
                    ? Tokens.Variant
                    : ((VisualBasic6Parser.VariableSubStmtContext) kvp.Key.Parent).asTypeClause().type().GetText(),
                ExtractedParameter.PassedBy.ByVal));

        var arguments = declarations
            .Where(kvp => kvp.Key.Parent is VisualBasic6Parser.ArgContext)
            .Select(kvp => new ExtractedParameter(
                kvp.Key.GetText(),
                ((VisualBasic6Parser.ArgContext)kvp.Key.Parent).asTypeClause() == null
                    ? Tokens.Variant
                    : ((VisualBasic6Parser.ArgContext)kvp.Key.Parent).asTypeClause().type().GetText(),
                ExtractedParameter.PassedBy.ByVal));

        return consts.Union(variables.Union(arguments));
    }

    public void Show()
    {
        _view.MethodName = "Method1";
        _view.Inputs = _input.ToList();
        _view.Outputs = _output.Select(output => new ExtractedParameter(output.Name, output.TypeName, ExtractedParameter.PassedBy.ByRef)).ToList();
        _view.Locals = _locals.Select(variable => new ExtractedParameter(variable.GetText(), string.Empty, ExtractedParameter.PassedBy.ByVal)).ToList();

        var returnValues = new[] { new ExtractedParameter("(none)", string.Empty, ExtractedParameter.PassedBy.ByVal) }
            .Union(_view.Outputs)
            .Union(_view.Inputs)
            .ToList();

        _view.ReturnValues = returnValues;
        if (_output.Count() == 1)
        {
            _view.ReturnValue = _output.Single();
        }

        _view.RefreshPreview += _view_RefreshPreview;
        _view.OnRefreshPreview();

        var result = _view.ShowDialog();
        if (result != DialogResult.OK)
        {
            return;
        }

        _vbe.ActiveCodePane.CodeModule.DeleteLines(_selection.StartLine, _selection.LineCount - 1);
        _vbe.ActiveCodePane.CodeModule.ReplaceLine(_selection.StartLine, GetMethodCall());

        _vbe.ActiveCodePane.CodeModule.AddFromString(GetExtractedMethod());
    }

    private void _view_RefreshPreview(object sender, EventArgs e)
    {
        var hasReturnValue = _view.ReturnValue != null && _view.ReturnValue.Name != "(none)";
        _view.CanSetReturnValue = 
            hasReturnValue && !IsValueType(_view.ReturnValue.TypeName);

        Preview();
    }

    private void Preview()
    {
        _view.Preview = GetExtractedMethod();
    }

    private string GetMethodCall()
    {
        string result;
        var returnValueName = _view.ReturnValue.Name;
        var argsList = string.Join(", ", _view.Parameters.Select(p => p.Name));
        if (returnValueName != "(none)")
        {
            var setter = _view.SetReturnValue ? Tokens.Set + ' ' : string.Empty;
            result = setter + returnValueName + " = " + _view.MethodName + '(' + argsList + ')';
        }
        else
        {
            result = _view.MethodName + ' ' + argsList;
        }

        return "    " + result; // todo: smarter indentation
    }

    private static readonly IEnumerable<string> ValueTypes = new[]
    {
        Tokens.Boolean,
        Tokens.Byte,
        Tokens.Currency,
        Tokens.Date,
        Tokens.Decimal,
        Tokens.Double,
        Tokens.Integer,
        Tokens.Long,
        Tokens.LongLong,
        Tokens.Single,
        Tokens.String
    };

    public static bool IsValueType(string typeName)
    {
        return ValueTypes.Contains(typeName);
    }

    [ComVisible(false)]
    private string GetExtractedMethod()
    {
        const string newLine = "\r\n";

        var access = _view.Accessibility.ToString();
        var keyword = Tokens.Sub;
        var returnType = string.Empty;

        var isFunction = _view.ReturnValue != null && _view.ReturnValue.Name != "(none)";
        if (isFunction)
        {
            keyword = Tokens.Function;
            returnType = Tokens.As + ' ' + _view.ReturnValue.TypeName;
        }

        var parameters = "(" + string.Join(", ", _view.Parameters) + ")";

        var result = access + ' ' + keyword + ' ' + _view.MethodName + parameters + ' ' + returnType + newLine;

        var localConsts = _locals.Select(e => e.Parent)
            .OfType<VisualBasic6Parser.ConstSubStmtContext>()
            .Select(e => "    " + Tokens.Const + ' ' + e.ambiguousIdentifier().GetText() + ' ' + e.asTypeClause().GetText() + " = " + e.valueStmt().GetText());
        var localVariables = _locals.Select(e => e.Parent)
            .OfType<VisualBasic6Parser.VariableSubStmtContext>()
            .Where(e => _view.Parameters.All(param => param.Name != e.ambiguousIdentifier().GetText()))
            .Select(e => "    " + Tokens.Dim + ' ' + e.ambiguousIdentifier().GetText() + ' ' + e.asTypeClause().GetText());
        var locals = string.Join(newLine, localConsts.Union(localVariables).ToArray());

        result += newLine + locals + newLine + _selectedCode + newLine;

        if (isFunction)
        {
            // return value by assigning the method itself:
            var setter = _view.SetReturnValue ? Tokens.Set + ' ' : string.Empty;
            result += "    " + setter + _view.MethodName + " = " + _view.ReturnValue.Name + newLine;
        }

        result += Tokens.End + ' ' + keyword + newLine;

        return newLine + result + newLine;
    }
}

```

Then there's this smelly `ExtractMethodRefactoring` class:

```
public class ExtractMethodRefactoring
{
    public static IDictionary<VisualBasic6Parser.AmbiguousIdentifierContext, ExtractedDeclarationUsage> GetParentMethodDeclarations(IParseTree parseTree, Selection selection)
    {
        var declarations = parseTree.GetContexts<DeclarationListener, ParserRuleContext>(new DeclarationListener()).ToList();

        var constants = declarations.OfType<VisualBasic6Parser.ConstSubStmtContext>().Select(constant => constant.ambiguousIdentifier());
        var variables = declarations.OfType<VisualBasic6Parser.VariableSubStmtContext>().Select(variable => variable.ambiguousIdentifier());
        var arguments = declarations.OfType<VisualBasic6Parser.ArgContext>().Select(arg => arg.ambiguousIdentifier());

        var identifiers = constants.Union(variables)
                                   .Union(arguments)
                                   .ToDictionary(declaration => declaration.GetText(), 
                                                 declaration => declaration);

        var references = parseTree.GetContexts<VariableReferencesListener, VisualBasic6Parser.AmbiguousIdentifierContext>(new VariableReferencesListener())
                                  .GroupBy(usage => new { Identifier = usage.GetText()})
                                  .ToList();

        var notUsedInSelection = references.Where(usage => usage.All(token => !selection.Contains(token.GetSelection())))
                                           .Select(usage => usage.Key).ToList();

        var usedBeforeSelection = references.Where(usage => usage.Any(token => token.GetSelection().EndLine < selection.StartLine))
                                                .Select(usage => usage.Key)
                                            .Where(usage => notUsedInSelection.All(e => e.Identifier != usage.Identifier));

        var usedAfterSelection = references.Where(usage => usage.Any(token => token.GetSelection().StartLine > selection.EndLine))
                                               .Select(usage => usage.Key)
                                            .Where(usage => notUsedInSelection.All(e => e.Identifier != usage.Identifier));

        var usedOnlyWithinSelection = references.Where(usage => usage.All(token => selection.Contains(token.GetSelection())))
                                                .Select(usage => usage.Key);

        var result = new Dictionary<VisualBasic6Parser.AmbiguousIdentifierContext, ExtractedDeclarationUsage>();

        // temporal coupling: references used after selection must be added first
        foreach (var reference in usedAfterSelection)
        {
            VisualBasic6Parser.AmbiguousIdentifierContext key;
            if (identifiers.TryGetValue(reference.Identifier, out key))
            {
                if (!result.ContainsKey(key))
                {
                    result.Add(key, ExtractedDeclarationUsage.UsedAfterSelection);
                }
            }
        }

        foreach (var reference in usedBeforeSelection)
        {
            VisualBasic6Parser.AmbiguousIdentifierContext key;
            if (identifiers.TryGetValue(reference.Identifier, out key))
            {
                if (!result.ContainsKey(key))
                {
                    result.Add(key, ExtractedDeclarationUsage.UsedBeforeSelection);
                }
            }
        }

        foreach (var reference in usedOnlyWithinSelection)
        {
            VisualBasic6Parser.AmbiguousIdentifierContext key;
            if (identifiers.TryGetValue(reference.Identifier, out key))
            {
                if (!result.ContainsKey(key))
                {
                    result.Add(key, ExtractedDeclarationUsage.UsedOnlyInSelection);
                }
            }
        }

        foreach (var reference in notUsedInSelection)
        {
            VisualBasic6Parser.AmbiguousIdentifierContext key;
            if (identifiers.TryGetValue(reference.Identifier, out key))
            {
                if (!result.ContainsKey(key))
                {
                    result.Add(key, ExtractedDeclarationUsage.NotUsed);
                }
            }
        }

        return result;
    }
}

```

I use this extension method quite a lot, to walk a parse tree whenever I need to:

```
public static class AntlrExtensions
{
    public static IEnumerable<TContext> GetContexts<TListener, TContext>(this IParseTree parseTree, TListener listener)
        where TListener : IExtensionListener<TContext>, IParseTreeListener
        where TContext : class
    {
        var walker = new ParseTreeWalker();
        walker.Walk(listener, parseTree);

        return listener.Members;
    }
}

```

Obviously I need to break this code into smaller pieces. Where to start?

Here is the `ExtractedParameter` class, for context:

```
public class ExtractedParameter
{
    public enum PassedBy
    {
        ByRef,
        ByVal
    }

    public ExtractedParameter(string name, string typeName, PassedBy passed)
    {
        Name = name;
        TypeName = typeName;
        Passed = passed;
    }

    public string Name { get; set; }
    public string TypeName { get; set; }
    public PassedBy Passed { get; set; }

    public override string ToString()
    {
        return Passed.ToString() + ' ' + Name + ' ' + Tokens.As + ' ' + TypeName;
    }
}

```