Skip to content

Commit e34fe49

Browse files
committed
Merge pull request #886 from Hosch250/MoveCloserToUsageRefactoring
Move closer to usage refactoring
2 parents 8c554f6 + 752a110 commit e34fe49

File tree

9 files changed

+369
-9
lines changed

9 files changed

+369
-9
lines changed

RetailCoder.VBE/Refactorings/IntroduceField/IntroduceFieldRefactoring.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public void Refactor()
3030

3131
if (!selection.HasValue)
3232
{
33-
_messageBox.Show(RubberduckUI.PromoteVariable_InvalidSelection);
33+
_messageBox.Show(RubberduckUI.PromoteVariable_InvalidSelection, RubberduckUI.IntroduceField_Caption,
34+
System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation);
3435
return;
3536
}
3637

RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public void Refactor()
4242

4343
if (!selection.HasValue)
4444
{
45-
_messageBox.Show(RubberduckUI.PromoteVariable_InvalidSelection);
45+
_messageBox.Show(RubberduckUI.PromoteVariable_InvalidSelection, RubberduckUI.IntroduceParameter_Caption,
46+
System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation);
4647
return;
4748
}
4849

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Rubberduck.Parsing.Grammar;
5+
using Rubberduck.Parsing.Symbols;
6+
using Rubberduck.Parsing.VBA;
7+
using Rubberduck.UI;
8+
using Rubberduck.VBEditor;
9+
10+
namespace Rubberduck.Refactorings.MoveCloserToUsage
11+
{
12+
public class MoveCloserToUsageRefactoring : IRefactoring
13+
{
14+
private readonly List<Declaration> _declarations;
15+
private readonly IActiveCodePaneEditor _editor;
16+
private readonly IMessageBox _messageBox;
17+
18+
public MoveCloserToUsageRefactoring(RubberduckParserState parseResult, IActiveCodePaneEditor editor, IMessageBox messageBox)
19+
{
20+
_declarations = parseResult.AllDeclarations.ToList();
21+
_editor = editor;
22+
_messageBox = messageBox;
23+
}
24+
25+
public void Refactor()
26+
{
27+
var qualifiedSelection = _editor.GetSelection();
28+
if (qualifiedSelection != null)
29+
{
30+
Refactor(FindSelection(qualifiedSelection.Value));
31+
}
32+
else
33+
{
34+
_messageBox.Show("Invalid Selection.", "Rubberduck - Move Closer To Usage", System.Windows.Forms.MessageBoxButtons.OK,
35+
System.Windows.Forms.MessageBoxIcon.Exclamation);
36+
}
37+
}
38+
39+
public void Refactor(QualifiedSelection selection)
40+
{
41+
Refactor(FindSelection(selection));
42+
}
43+
44+
public void Refactor(Declaration target)
45+
{
46+
if (target.DeclarationType != DeclarationType.Variable)
47+
{
48+
throw new ArgumentException(@"Invalid Argument", "target");
49+
}
50+
51+
if (!target.References.Any())
52+
{
53+
var message = string.Format(RubberduckUI.MoveCloserToUsage_TargetHasNoReferences, target.IdentifierName);
54+
55+
_messageBox.Show(message, RubberduckUI.MoveCloserToUsage_Caption, System.Windows.Forms.MessageBoxButtons.OK,
56+
System.Windows.Forms.MessageBoxIcon.Exclamation);
57+
58+
return;
59+
}
60+
61+
if (TargetIsReferencedFromMultipleMethods(target))
62+
{
63+
var message = string.Format(RubberduckUI.MoveCloserToUsage_TargetIsUsedInMultipleMethods, target.IdentifierName);
64+
_messageBox.Show(message, RubberduckUI.MoveCloserToUsage_Caption, System.Windows.Forms.MessageBoxButtons.OK,
65+
System.Windows.Forms.MessageBoxIcon.Exclamation);
66+
67+
return;
68+
}
69+
70+
MoveDeclaration(target);
71+
}
72+
73+
private bool TargetIsReferencedFromMultipleMethods(Declaration target)
74+
{
75+
var firstReference = target.References.FirstOrDefault();
76+
77+
return firstReference != null && target.References.Any(r => r.ParentScope != firstReference.ParentScope);
78+
}
79+
80+
private void MoveDeclaration(Declaration target)
81+
{
82+
InsertDeclaration(target);
83+
RemoveVariable(target);
84+
}
85+
86+
private void InsertDeclaration(Declaration target)
87+
{
88+
var firstReference = target.References.OrderBy(r => r.Selection.StartLine).First();
89+
90+
var oldLines = _editor.GetLines(firstReference.Selection);
91+
var newLines = oldLines.Insert(firstReference.Selection.StartColumn - 1, GetDeclarationString(target));
92+
93+
_editor.DeleteLines(firstReference.Selection);
94+
_editor.InsertLines(firstReference.Selection.StartLine, newLines);
95+
}
96+
97+
private string GetDeclarationString(Declaration target)
98+
{
99+
return Environment.NewLine + " Dim " + target.IdentifierName + " As " + target.AsTypeName + Environment.NewLine;
100+
}
101+
102+
private void RemoveVariable(Declaration target)
103+
{
104+
Selection selection;
105+
var declarationText = target.Context.GetText();
106+
var multipleDeclarations = HasMultipleDeclarationsInStatement(target);
107+
108+
var variableStmtContext = GetVariableStmtContext(target);
109+
110+
if (!multipleDeclarations)
111+
{
112+
declarationText = variableStmtContext.GetText();
113+
selection = GetVariableStmtContextSelection(target);
114+
}
115+
else
116+
{
117+
selection = new Selection(target.Context.Start.Line, target.Context.Start.Column,
118+
target.Context.Stop.Line, target.Context.Stop.Column);
119+
}
120+
121+
var oldLines = _editor.GetLines(selection);
122+
123+
var newLines = oldLines.Replace(" _" + Environment.NewLine, string.Empty)
124+
.Remove(selection.StartColumn, declarationText.Length);
125+
126+
if (multipleDeclarations)
127+
{
128+
selection = GetVariableStmtContextSelection(target);
129+
newLines = RemoveExtraComma(_editor.GetLines(selection).Replace(oldLines, newLines));
130+
}
131+
132+
_editor.DeleteLines(selection);
133+
134+
if (newLines.Trim() != string.Empty)
135+
{
136+
_editor.InsertLines(selection.StartLine, newLines);
137+
}
138+
}
139+
140+
private string RemoveExtraComma(string str)
141+
{
142+
if (str.Count(c => c == ',') == 1)
143+
{
144+
return str.Remove(str.IndexOf(','), 1);
145+
}
146+
147+
var significantCharacterAfterComma = false;
148+
149+
for (var index = str.IndexOf("Dim", StringComparison.Ordinal) + 3; index < str.Length; index++)
150+
{
151+
if (!significantCharacterAfterComma && str[index] == ',')
152+
{
153+
return str.Remove(index, 1);
154+
}
155+
156+
if (!char.IsWhiteSpace(str[index]) && str[index] != '_' && str[index] != ',')
157+
{
158+
significantCharacterAfterComma = true;
159+
}
160+
161+
if (str[index] == ',')
162+
{
163+
significantCharacterAfterComma = false;
164+
}
165+
}
166+
167+
return str.Remove(str.LastIndexOf(','), 1);
168+
}
169+
170+
public Selection GetVariableStmtContextSelection(Declaration target)
171+
{
172+
var statement = GetVariableStmtContext(target);
173+
174+
return new Selection(statement.Start.Line, statement.Start.Column,
175+
statement.Stop.Line, statement.Stop.Column);
176+
}
177+
178+
public VBAParser.VariableStmtContext GetVariableStmtContext(Declaration target)
179+
{
180+
var statement = target.Context.Parent.Parent as VBAParser.VariableStmtContext;
181+
if (statement == null)
182+
{
183+
throw new NullReferenceException("Statement not found");
184+
}
185+
186+
return statement;
187+
}
188+
189+
public bool HasMultipleDeclarationsInStatement(Declaration target)
190+
{
191+
var statement = target.Context.Parent as VBAParser.VariableListStmtContext;
192+
193+
return statement != null && statement.children.Count(i => i is VBAParser.VariableSubStmtContext) > 1;
194+
}
195+
196+
private Declaration FindSelection(QualifiedSelection selection)
197+
{
198+
var target = _declarations
199+
.FirstOrDefault(item => item.IsSelected(selection) || item.References.Any(r => r.IsSelected(selection)));
200+
201+
if (target != null) { return target; }
202+
203+
var targets = _declarations.Where(item => item.ComponentName == selection.QualifiedName.ComponentName);
204+
205+
foreach (var declaration in targets)
206+
{
207+
var declarationSelection = new Selection(declaration.Context.Start.Line,
208+
declaration.Context.Start.Column,
209+
declaration.Context.Stop.Line,
210+
declaration.Context.Stop.Column + declaration.Context.Stop.Text.Length);
211+
212+
if (declarationSelection.Contains(selection.Selection) ||
213+
!HasMultipleDeclarationsInStatement(declaration) && GetVariableStmtContextSelection(declaration).Contains(selection.Selection))
214+
{
215+
return declaration;
216+
}
217+
218+
var reference =
219+
declaration.References.FirstOrDefault(r => r.Selection.Contains(selection.Selection));
220+
221+
if (reference != null)
222+
{
223+
return reference.Declaration;
224+
}
225+
}
226+
return null;
227+
}
228+
}
229+
}

RetailCoder.VBE/Root/CommandBarsModule.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ private IMenuItem GetRefactoringsParentMenu()
189189
_kernel.Get<RefactorIntroduceParameterCommandMenuItem>(),
190190
_kernel.Get<RefactorIntroduceFieldCommandMenuItem>(),
191191
_kernel.Get<RefactorEncapsulateFieldCommandMenuItem>(),
192+
_kernel.Get<RefactorMoveCloserToUsageCommandMenuItem>(),
192193
};
193194
return new RefactoringsParentMenu(items);
194195
}

RetailCoder.VBE/UI/Command/MenuItems/ParentMenus/RefactoringsParentMenu.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ public enum RefactoringsMenuItemDisplayOrder
2121
IntroduceParameter,
2222
IntroduceField,
2323
EncapsulateField,
24+
MoveCloserToUsage,
2425
}
2526
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Windows.Input;
2+
using Rubberduck.Parsing.VBA;
3+
using Rubberduck.UI.Command.MenuItems.ParentMenus;
4+
5+
namespace Rubberduck.UI.Command.MenuItems
6+
{
7+
public class RefactorMoveCloserToUsageCommandMenuItem : CommandMenuItemBase
8+
{
9+
public RefactorMoveCloserToUsageCommandMenuItem(ICommand command)
10+
: base(command)
11+
{
12+
}
13+
14+
public override string Key { get { return "RefactorMenu_MoveCloserToUsage"; } }
15+
public override int DisplayOrder { get { return (int)RefactoringsMenuItemDisplayOrder.MoveCloserToUsage; } }
16+
17+
public override bool EvaluateCanExecute(RubberduckParserState state)
18+
{
19+
return state.Status == ParserState.Ready;
20+
}
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Microsoft.Vbe.Interop;
2+
using Rubberduck.Parsing.VBA;
3+
using Rubberduck.Refactorings.MoveCloserToUsage;
4+
using Rubberduck.VBEditor;
5+
using Rubberduck.VBEditor.VBEInterfaces.RubberduckCodePane;
6+
7+
namespace Rubberduck.UI.Command.Refactorings
8+
{
9+
public class RefactorMoveCloserToUsageCommand : RefactorCommandBase
10+
{
11+
private readonly RubberduckParserState _state;
12+
private readonly ICodePaneWrapperFactory _wrapperWrapperFactory;
13+
14+
public RefactorMoveCloserToUsageCommand(VBE vbe, RubberduckParserState state, IActiveCodePaneEditor editor, ICodePaneWrapperFactory wrapperWrapperFactory)
15+
: base(vbe, editor)
16+
{
17+
_state = state;
18+
_wrapperWrapperFactory = wrapperWrapperFactory;
19+
}
20+
21+
public override void Execute(object parameter)
22+
{
23+
if (Vbe.ActiveCodePane == null)
24+
{
25+
return;
26+
}
27+
var codePane = _wrapperWrapperFactory.Create(Vbe.ActiveCodePane);
28+
var selection = new QualifiedSelection(new QualifiedModuleName(codePane.CodeModule.Parent), codePane.Selection);
29+
30+
var refactoring = new MoveCloserToUsageRefactoring(_state, Editor, new MessageBox());
31+
refactoring.Refactor(selection);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)