13
\$\begingroup\$

Next release of Rubberduck (should be 1.4) introduces a reorder parameters refactoring, which lets user reorder the parameters of a module (or class) member, and automatically adjust all usages.


Before:

DoSomething(ByVal a As String, ByVal b As Integer, ByVal c As Long)

After:

DoSomething(ByVal c As Long, ByVal b As Integer, ByVal a As String)

This is the IDialogView interface implementation:

interface IReorderParametersView : IDialogView
{
    Declaration Target { get; set; }
    List<Parameter> Parameters { get; set; }
    void InitializeParameterGrid();
}

And here is the Parameter class:

public class Parameter
{
    public string FullDeclaration { get; private set; }
    public int Index { get; private set; }
    public bool IsOptional { get; private set; }
    public bool IsParamArray { get; private set; }

    public Parameter(string fullDeclaration, int index)
    {
        FullDeclaration = fullDeclaration;
        Index = index;
        IsOptional = FullDeclaration.Contains("Optional");
        IsParamArray = FullDeclaration.Contains("ParamArray");
    }
}

This is the dialog code-behind:

public partial class ReorderParametersDialog : Form, IReorderParametersView
{
    public List<Parameter> Parameters { get; set; }
    private Parameter _selectedItem;
    private Rectangle _dragBoxFromMouseDown;
    Point _startPoint;
    private int _newRowIndex;

    public ReorderParametersDialog()
    {
        Parameters = new List<Parameter>();
        InitializeComponent();
        InitializeCaptions();

        MethodParametersGrid.SelectionChanged += MethodParametersGrid_SelectionChanged;
        MethodParametersGrid.MouseMove += MethodParametersGrid_MouseMove;
        MethodParametersGrid.MouseDown += MethodParametersGrid_MouseDown;
        MethodParametersGrid.DragOver += MethodParametersGrid_DragOver;
        MethodParametersGrid.DragDrop += MethodParametersGrid_DragDrop;
    }

    private void InitializeCaptions()
    {
        OkButton.Text = RubberduckUI.OkButtonText;
        CancelButton.Text = RubberduckUI.CancelButtonText;
        Text = RubberduckUI.ReorderParamsDialog_Caption;
        TitleLabel.Text = RubberduckUI.ReorderParamsDialog_TitleText;
        InstructionsLabel.Text = RubberduckUI.ReorderParamsDialog_InstructionsLabelText;
        MoveUpButton.Text = RubberduckUI.ReorderParamsDialog_MoveUpButtonText;
        MoveDownButton.Text = RubberduckUI.ReorderParamsDialog_MoveDownButtonText;
    }

    private void MethodParametersGrid_SelectionChanged(object sender, EventArgs e)
    {
        SelectionChanged();
    }

    private void MethodParametersGrid_MouseMove(object sender, MouseEventArgs e)
    {
        if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
        {
            if (_dragBoxFromMouseDown != Rectangle.Empty && !_dragBoxFromMouseDown.Contains(e.X, e.Y))
            {
                var dropEffect = MethodParametersGrid.DoDragDrop(
                      MethodParametersGrid.Rows[_newRowIndex],
                      DragDropEffects.Move);
            }
        }
    }

    private void MethodParametersGrid_MouseDown(object sender, MouseEventArgs e)
    {
        _newRowIndex = MethodParametersGrid.HitTest(e.X, e.Y).RowIndex;

        if (_newRowIndex == -1)
        {
            _dragBoxFromMouseDown = Rectangle.Empty;
            return;
        }

        _startPoint = new Point(e.X, e.Y);

        var dragSize = SystemInformation.DragSize;
        _dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);
    }

    private void MethodParametersGrid_DragOver(object sender, DragEventArgs e)
    {
        e.Effect = DragDropEffects.Move;
    }

    private void MethodParametersGrid_DragDrop(object sender, DragEventArgs e)
    {
        var clientPoint = MethodParametersGrid.PointToClient(new Point(e.X, e.Y));

        if (e.Effect == DragDropEffects.Move && _newRowIndex != -1)
        {
            var rowIndexOfItemUnderMouse = MethodParametersGrid.HitTest(clientPoint.X, clientPoint.Y).RowIndex;

            if (rowIndexOfItemUnderMouse < 0)
            {
                if (clientPoint.Y < _startPoint.Y)
                {
                    rowIndexOfItemUnderMouse = 0;
                }
                else
                {
                    rowIndexOfItemUnderMouse = Parameters.Count - 1;
                }
            }

            var tmp = Parameters.ElementAt(_newRowIndex);
            Parameters.RemoveAt(_newRowIndex);
            Parameters.Insert(rowIndexOfItemUnderMouse, tmp);
            ReselectParameter();
        }
    }

    public void InitializeParameterGrid()
    {
        MethodParametersGrid.AutoGenerateColumns = false;
        MethodParametersGrid.Columns.Clear();
        MethodParametersGrid.DataSource = Parameters;
        MethodParametersGrid.AlternatingRowsDefaultCellStyle.BackColor = Color.Lavender;
        MethodParametersGrid.MultiSelect = false;
        MethodParametersGrid.AllowUserToResizeRows = false;
        MethodParametersGrid.AllowDrop = true;
        MethodParametersGrid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;

        var column = new DataGridViewTextBoxColumn
        {
            Name = "Parameter",
            DataPropertyName = "FullDeclaration",
            HeaderText = "Parameter",
            ReadOnly = true,
            AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
        };

        MethodParametersGrid.Columns.Add(column);
        _selectedItem = Parameters[0];
    }

    private void OkButtonClick(object sender, EventArgs e)
    {
        OnOkButtonClicked();
    }

    public event EventHandler CancelButtonClicked;
    public void OnCancelButtonClicked()
    {
        Hide();
    }

    public event EventHandler OkButtonClicked;
    public void OnOkButtonClicked()
    {
        var handler = OkButtonClicked;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    private Declaration _target;
    public Declaration Target
    {
        get { return _target; }
        set { _target = value; }
    }

    private void MoveUpButtonClicked(object sender, EventArgs e)
    {
        if (MethodParametersGrid.SelectedRows.Count == 0)
        {
            return;
        }

        var selectedIndex = MethodParametersGrid.SelectedRows[0].Index;
        SwapParameters(selectedIndex, selectedIndex - 1);

        ReselectParameter();
    }

    private void MoveDownButtonClicked(object sender, EventArgs e)
    {
        if (MethodParametersGrid.SelectedRows.Count == 0)
        {
            return;
        }

        var selectedIndex = MethodParametersGrid.SelectedRows[0].Index;
        SwapParameters(selectedIndex, selectedIndex + 1);

        ReselectParameter();
    }

    private void SwapParameters(int index1, int index2)
    {
        var tmp = Parameters[index1];
        Parameters[index1] = Parameters[index2];
        Parameters[index2] = tmp;
    }

    private void ReselectParameter()
    {
        MethodParametersGrid.Refresh();
        MethodParametersGrid.Rows
                            .Cast<DataGridViewRow>()
                            .Single(row => row.DataBoundItem == _selectedItem).Selected = true;

        SelectionChanged();
    }

    private void SelectionChanged()
    {
        _selectedItem = MethodParametersGrid.SelectedRows.Count == 0
            ? null
            : (Parameter)MethodParametersGrid.SelectedRows[0].DataBoundItem;

        MoveUpButton.Enabled = _selectedItem != null
            && MethodParametersGrid.SelectedRows[0].Index != 0;

        MoveDownButton.Enabled = _selectedItem != null
            && MethodParametersGrid.SelectedRows[0].Index != Parameters.Count - 1;
    }
}

And here is the presenter:

class ReorderParametersPresenter
{
    private readonly IReorderParametersView _view;
    private readonly Declarations _declarations;
    private readonly QualifiedSelection _selection;

    public ReorderParametersPresenter(IReorderParametersView view, VBProjectParseResult parseResult, QualifiedSelection selection)
    {
        _view = view;
        _declarations = parseResult.Declarations;
        _selection = selection;

        _view.OkButtonClicked += OnOkButtonClicked;
    }

    public void Show()
    {
        AcquireTarget(_selection);

        if (_view.Target != null)
        {
            LoadParameters();

            if (_view.Parameters.Count < 2) 
            {
                var message = string.Format(RubberduckUI.ReorderPresenter_LessThanTwoVariablesError, _view.Target.IdentifierName);
                MessageBox.Show(message, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return; 
            }

            _view.InitializeParameterGrid();
           _view.ShowDialog();
        }
    }

    private void LoadParameters()
    {
        var procedure = (dynamic)_view.Target.Context;
        var argList = (VBAParser.ArgListContext)procedure.argList();
        var args = argList.arg();

        var index = 0;
        foreach (var arg in args)
        {
            _view.Parameters.Add(new Parameter(arg.GetText(), index++));
        }
    }

    private void OnOkButtonClicked(object sender, EventArgs e)
    {
        if (!_view.Parameters.Where((t, i) => t.Index != i).Any())
        {
            return;
        }

        var indexOfFirstOptionalParam = _view.Parameters.FindIndex(param => param.IsOptional);
        if (indexOfFirstOptionalParam >= 0)
        {
            for (var index = indexOfFirstOptionalParam + 1; index < _view.Parameters.Count; index++)
            {
                if (!_view.Parameters.ElementAt(index).IsOptional)
                {
                    MessageBox.Show(RubberduckUI.ReorderPresenter_OptionalVariableError, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
            }
        }

        var indexOfParamArray = _view.Parameters.FindIndex(param => param.IsParamArray);
        if (indexOfParamArray >= 0)
        {
            if (indexOfParamArray != _view.Parameters.Count - 1)
            {
                MessageBox.Show(RubberduckUI.ReorderPresenter_ParamArrayError, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
        }

        AdjustSignatures();
        AdjustReferences(_view.Target.References);
    }

    private void AdjustReferences(IEnumerable<IdentifierReference> references)
    {
        foreach (var reference in references.Where(item => item.Context != _view.Target.Context))
        {
            var proc = (dynamic)reference.Context.Parent;
            var module = reference.QualifiedModuleName.Component.CodeModule;

            // This is to prevent throws when this statement fails:
            // (VBAParser.ArgsCallContext)proc.argsCall();
            try
            {
                var check = (VBAParser.ArgsCallContext)proc.argsCall();
            }
            catch
            {
                continue;
            }

            var argList = (VBAParser.ArgsCallContext)proc.argsCall();

            if (argList == null)
            {
                continue;
            }

            RewriteCall(reference, argList, module);
        }
    }

    private void RewriteCall(IdentifierReference reference, VBAParser.ArgsCallContext argList, Microsoft.Vbe.Interop.CodeModule module)
    {
        var paramNames = argList.argCall().Select(arg => arg.GetText()).ToList();

        var lineCount = argList.Stop.Line - argList.Start.Line + 1; // adjust for total line count

        var variableIndex = 0;
        for (var line = argList.Start.Line; line < argList.Start.Line + lineCount; line++)
        {
            var newContent = module.Lines[line, 1].Replace(" , ", "");

            var currentStringIndex = line == argList.Start.Line ? reference.Declaration.IdentifierName.Length : 0;

            for (var i = 0; i < paramNames.Count && variableIndex < _view.Parameters.Count; i++)
            {
                var variableStringIndex = newContent.IndexOf(paramNames.ElementAt(i), currentStringIndex);

                if (variableStringIndex > -1)
                {
                    if (_view.Parameters.ElementAt(variableIndex).Index >= paramNames.Count)
                    {
                        newContent = newContent.Insert(variableStringIndex, " , ");
                        i--;
                        variableIndex++;
                        continue;
                    }

                    var oldVariableString = paramNames.ElementAt(i);
                    var newVariableString = paramNames.ElementAt(_view.Parameters.ElementAt(variableIndex).Index);
                    var beginningSub = newContent.Substring(0, variableStringIndex);
                    var replaceSub = newContent.Substring(variableStringIndex).Replace(oldVariableString, newVariableString);

                    newContent = beginningSub + replaceSub;

                    variableIndex++;
                    currentStringIndex = beginningSub.Length + newVariableString.Length;
                }
            }

            module.ReplaceLine(line, newContent);
        }
    }

    private void AdjustSignatures()
    {
        var proc = (dynamic)_view.Target.Context;
        var argList = (VBAParser.ArgListContext)proc.argList();
        var module = _view.Target.QualifiedName.QualifiedModuleName.Component.CodeModule;

        // if we are reordering a property getter, check if we need to reorder a letter/setter too
        if (_view.Target.DeclarationType == DeclarationType.PropertyGet)
        {
            var setter = _declarations.Items.FirstOrDefault(item => item.ParentScope == _view.Target.ParentScope &&
                                          item.IdentifierName == _view.Target.IdentifierName &&
                                          item.DeclarationType == DeclarationType.PropertySet);

            if (setter != null)
            {
                AdjustSignatures(setter);
            }

            var letter = _declarations.Items.FirstOrDefault(item => item.ParentScope == _view.Target.ParentScope &&
                          item.IdentifierName == _view.Target.IdentifierName &&
                          item.DeclarationType == DeclarationType.PropertyLet);

            if (letter != null)
            {
                AdjustSignatures(letter);
            }
        }

        RewriteSignature(argList, module);

        foreach (var withEvents in _declarations.Items.Where(item => item.IsWithEvents && item.AsTypeName == _view.Target.ComponentName))
        {
            foreach (var reference in _declarations.FindEventProcedures(withEvents))
            {
                AdjustSignatures(reference);
            }
        }

        var interfaceImplementations = _declarations.FindInterfaceImplementationMembers()
                                                    .Where(item => item.Project.Equals(_view.Target.Project) &&
                                                           item.IdentifierName == _view.Target.ComponentName + "_" + _view.Target.IdentifierName);
        foreach (var interfaceImplentation in interfaceImplementations)
        {
            AdjustSignatures(interfaceImplentation);

            AdjustReferences(interfaceImplentation.References);
        }
    }

    private void AdjustSignatures(IdentifierReference reference)
    {
        var proc = (dynamic)reference.Context.Parent;
        var module = reference.QualifiedModuleName.Component.CodeModule;
        var argList = (VBAParser.ArgListContext)proc.argList();

        RewriteSignature(argList, module);
    }

    private void AdjustSignatures(Declaration reference)
    {
        var proc = (dynamic)reference.Context.Parent;
        var module = reference.QualifiedName.QualifiedModuleName.Component.CodeModule;
        VBAParser.ArgListContext argList;

        if (reference.DeclarationType == DeclarationType.PropertySet || reference.DeclarationType == DeclarationType.PropertyLet)
        {
            argList = (VBAParser.ArgListContext)proc.children[0].argList();
        }
        else
        {
            argList = (VBAParser.ArgListContext)proc.subStmt().argList();
        }

        RewriteSignature(argList, module);
    }

    private void RewriteSignature(VBAParser.ArgListContext argList, Microsoft.Vbe.Interop.CodeModule module)
    {
        var args = argList.arg();

        var variableIndex = 0;
        for (var lineNum = argList.Start.Line; lineNum < argList.Start.Line + argList.GetSelection().LineCount; lineNum++)
        {
            var newContent = module.Lines[lineNum, 1];
            var currentStringIndex = 0;

            for (var i = variableIndex; i < _view.Parameters.Count; i++)
            {
                var variableStringIndex = newContent.IndexOf(_view.Parameters.Find(item => item.Index == variableIndex).FullDeclaration, currentStringIndex);

                if (variableStringIndex > -1)
                {
                    var oldVariableString = _view.Parameters.Find(item => item.Index == variableIndex).FullDeclaration;
                    var newVariableString = _view.Parameters.ElementAt(i).FullDeclaration;
                    var beginningSub = newContent.Substring(0, variableStringIndex);
                    var replaceSub = newContent.Substring(variableStringIndex).Replace(oldVariableString, newVariableString);

                    newContent = beginningSub + replaceSub;

                    variableIndex++;
                    currentStringIndex = beginningSub.Length + newVariableString.Length;
                }
            }

            module.ReplaceLine(lineNum, newContent);
        }
    }

    private static readonly DeclarationType[] ValidDeclarationTypes =
        {
             DeclarationType.Event,
             DeclarationType.Function,
             DeclarationType.Procedure,
             DeclarationType.PropertyGet,
             DeclarationType.PropertyLet,
             DeclarationType.PropertySet
        };

    private void AcquireTarget(QualifiedSelection selection)
    {
        var target = _declarations.Items
            .Where(item => !item.IsBuiltIn)
            .FirstOrDefault(item => IsSelectedDeclaration(selection, item)
                                 || IsSelectedReference(selection, item));

        if (target == null || !ValidDeclarationTypes.Contains(target.DeclarationType))
        {
            FindTarget(ref target, selection);
        }

        if (target != null && target.DeclarationType == DeclarationType.PropertySet)
        {
            var getter = _declarations.Items.FirstOrDefault(item => item.ParentScope == target.ParentScope &&
                                          item.IdentifierName == target.IdentifierName &&
                                          item.DeclarationType == DeclarationType.PropertyGet);

            if (getter != null)
            {
                target = getter;
            }
        }

        PromptIfTargetImplementsInterface(ref target);
        _view.Target = target;
    }

    private void FindTarget(ref Declaration target, QualifiedSelection selection)
    {
        var targets = _declarations.Items
            .Where(item => !item.IsBuiltIn
                        && item.ComponentName == selection.QualifiedName.ComponentName
                        && ValidDeclarationTypes.Contains(item.DeclarationType));

        foreach (var declaration in targets)
        {
            var startLine = declaration.Context.GetSelection().StartLine;
            var startColumn = declaration.Context.GetSelection().StartColumn;
            var endLine = declaration.Context.GetSelection().EndLine;
            var endColumn = declaration.Context.GetSelection().EndColumn;

            if (startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine)
            {
                if (startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn)
                {
                    continue;
                }
                if (endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn)
                {
                    continue;
                }

                target = declaration;
            }

            foreach (var reference in declaration.References)
            {
                var proc = (dynamic)reference.Context.Parent;

                // This is to prevent throws when this statement fails:
                // (VBAParser.ArgsCallContext)proc.argsCall();
                try
                {
                    var check = (VBAParser.ArgsCallContext)proc.argsCall();
                }
                catch
                {
                    continue;
                }

                var argList = (VBAParser.ArgsCallContext)proc.argsCall();

                if (argList == null)
                {
                    continue;
                }

                startLine = argList.Start.Line;
                startColumn = argList.Start.Column;
                endLine = argList.Stop.Line;
                endColumn = argList.Stop.Column + argList.Stop.Text.Length + 1;

                if ((startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine) && 
                    (startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn ||
                        endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn))
                {
                    continue;
                }

                target = reference.Declaration;
                return;
            }
        }
    }

    private void PromptIfTargetImplementsInterface(ref Declaration target)
    {
        var declaration = target;
        var interfaceImplementation = _declarations.FindInterfaceImplementationMembers().SingleOrDefault(m => m.Equals(declaration));
        if (target == null || interfaceImplementation == null)
        {
            return;
        }

        var interfaceMember = _declarations.FindInterfaceMember(interfaceImplementation);
        var message = string.Format(RubberduckUI.ReorderPresenter_TargetIsInterfaceMemberImplementation, target.IdentifierName, interfaceMember.ComponentName, interfaceMember.IdentifierName);

        var confirm = MessageBox.Show(message, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
        if (confirm == DialogResult.No)
        {
            target = null;
            return;
        }

        target = interfaceMember;
    }

    private bool IsSelectedReference(QualifiedSelection selection, Declaration declaration)
    {
        return declaration.References.Any(r =>
            r.QualifiedModuleName == selection.QualifiedName &&
            r.Selection.ContainsFirstCharacter(selection.Selection));
    }

    private bool IsSelectedDeclaration(QualifiedSelection selection, Declaration declaration)
    {
        return declaration.QualifiedName.QualifiedModuleName == selection.QualifiedName
               && (declaration.Selection.ContainsFirstCharacter(selection.Selection));
    }
}

My main concerns are how similar certain sections of the code are, any ideas to make this less repetitive are welcome.

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

In the Show() method, this condition could be reversed into a positive one:

if (_view.Target != null)
{
    ...
}

...which would reduce nesting because if the _view.Target is null, then there's nothing to act upon and the method can return early:

if (_view.Target == null)
{
    return;
}

The name of the resource string RubberduckUI.ReorderPresenter_LessThanTwoVariablesError is slightly off - a better name would be ReorderPresenter_LessThanTwoParametersError, or perhaps ReorderPresenter_RequiresTwoOrMoreParametersError.

There's a similar scheme with ReorderPresenter_OptionalVariableError: a "variable" isn't a "parameter", and the resource string could have a name that better explains why this is happening - like ReorderPresenter_OptionalParametersMustBeLastError.

I don't like that you're referring to parameters as "variables" in multiple places; there's a DeclarationType.Variable enum member, and then there's a DeclarationType.Parameter enum member - they are two completely distinct things.

From a UX standpoint, there's an opportunity here: a Parameter knows when it's Optional, so you have a way to validate the data before the user hits the Ok button. One way to implement this could be to remove the column heading, and insert a column before the "Parameters" column, that would display some [x] icon (Rubberduck uses such an icon elsewhere for similar purposes) with a tooltip explaining what's wrong; the Ok button could be disabled when an icon is shown in that column.

This would greatly simplify the OnOkButtonClicked handler, which has a confusing name (I've been guilty of that too) - OnXxxx should be for procedures that raise an event, not handle it.

Why is AdjustSignatures plural? It's only ever processing one signature at a time, right?


A Declaration called reference is confusing:

private void AdjustSignatures(Declaration reference)

Either you have a Declaration, or you have one of its references!

\$\endgroup\$
4
\$\begingroup\$

Looks fairly clean to me for the most part.

You could reduce nesting a bit here by extracting a boolean method in the code behind.

private void MethodParametersGrid_MouseMove(object sender, MouseEventArgs e)
{
    if (ShouldDragDrop(e, _dragBoxFromMouseDown))
    {
        var dropEffect = MethodParametersGrid.DoDragDrop(
              MethodParametersGrid.Rows[_newRowIndex],
              DragDropEffects.Move);
    }
}

This also looks like a good opportunity to extract a method for clarity's sake.

_dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);

Remember, removing duplication isn't the only reason to create a method. It's also used to provide useful abstractions.

In MoveUpButtonClicked and MoveDownButtonClicked this line of code is duplicated.

var selectedIndex = MethodParametersGrid.SelectedRows[0].Index;

It might be worth extracting another method for it.

\$\endgroup\$
1
\$\begingroup\$

The FindTarget() method, which I believe has some bugs in this version, is very messy:

private void FindTarget(ref Declaration target, QualifiedSelection selection)
{
    var targets = _declarations.Items
        .Where(item => !item.IsBuiltIn
                    && item.ComponentName == selection.QualifiedName.ComponentName
                    && ValidDeclarationTypes.Contains(item.DeclarationType));

    foreach (var declaration in targets)
    {
        var startLine = declaration.Context.GetSelection().StartLine;
        var startColumn = declaration.Context.GetSelection().StartColumn;
        var endLine = declaration.Context.GetSelection().EndLine;
        var endColumn = declaration.Context.GetSelection().EndColumn;

        if (startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine)
        {
            if (startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn)
            {
                continue;
            }
            if (endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn)
            {
                continue;
            }

            target = declaration;
        }

        foreach (var reference in declaration.References)
        {
            var proc = (dynamic)reference.Context.Parent;

            // This is to prevent throws when this statement fails:
            // (VBAParser.ArgsCallContext)proc.argsCall();
            try
            {
                var check = (VBAParser.ArgsCallContext)proc.argsCall();
            }
            catch
            {
                continue;
            }

            var argList = (VBAParser.ArgsCallContext)proc.argsCall();

            if (argList == null)
            {
                continue;
            }

            startLine = argList.Start.Line;
            startColumn = argList.Start.Column;
            endLine = argList.Stop.Line;
            endColumn = argList.Stop.Column + argList.Stop.Text.Length + 1;

            if ((startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine) && 
                (startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn ||
                    endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn))
            {
                continue;
            }

            target = reference.Declaration;
            return;
        }
    }
}

I can clean this up by using the defined type Selection():

private Declaration FindTarget(QualifiedSelection selection, DeclarationType[] validDeclarationTypes)
{
    var target = _declarations.Items
        .Where(item => !item.IsBuiltIn)
        .FirstOrDefault(item => IsSelectedDeclaration(selection, item)
                             || IsSelectedReference(selection, item));

    if (target != null && validDeclarationTypes.Contains(target.DeclarationType))
    {
        return target;
    }

    target = null;

    var targets = _declarations.Items
        .Where(item => !item.IsBuiltIn
                    && item.ComponentName == selection.QualifiedName.ComponentName
                    && validDeclarationTypes.Contains(item.DeclarationType));

    var currentSelection = new Selection(0, 0, int.MaxValue, int.MaxValue);

    foreach (var declaration in targets)
    {
        var declarationSelection = new Selection(declaration.Context.Start.Line,
                                                 declaration.Context.Start.Column,
                                                 declaration.Context.Stop.Line,
                                                 declaration.Context.Stop.Column);

        if (currentSelection.Contains(declarationSelection) && declarationSelection.Contains(selection.Selection))
        {
            target = declaration;
            currentSelection = declarationSelection;
        }

        foreach (var reference in declaration.References)
        {
            var proc = (dynamic)reference.Context.Parent;
            VBAParser.ArgsCallContext paramList;

            // This is to prevent throws when this statement fails:
            // (VBAParser.ArgsCallContext)proc.argsCall();
            try
            {
                paramList = (VBAParser.ArgsCallContext)proc.argsCall();
            }
            catch
            {
                continue;
            }

            if (paramList == null) { continue; }

            var referenceSelection = new Selection(paramList.Start.Line,
                                                   paramList.Start.Column,
                                                   paramList.Stop.Line,
                                                   paramList.Stop.Column + paramList.Stop.Text.Length + 1);

            if (currentSelection.Contains(declarationSelection) && referenceSelection.Contains(selection.Selection))
            {
                target = reference.Declaration;
                currentSelection = referenceSelection;
            }
        }
    }
    return target;
}

In addition, I have made it much more generic, and it returns a value instead of modifying one.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.