Skip to content

Commit cb5208a

Browse files
committed
Colon seperated statement handling and whitespace improvements
1 parent d7ccb02 commit cb5208a

File tree

2 files changed

+93
-45
lines changed

2 files changed

+93
-45
lines changed

Rubberduck.Refactorings/ExtractMethod/ExtractMethodModel.cs

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,19 @@ private void Setup()
8383
//List of "inbound" variables. Parent procedure parameters + explicit dims which get referenced inside the selection.
8484
//TODO - add case where reference earlier in the same line as where the selection starts (unusual but could exist if using colons to separate multiple statements)
8585
var inboundParameters = sourceMethodParameters.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)));
86-
var inboundLocalVariables = declarationsInParentMethod.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)) &&
87-
d.References.Any(r => r.Selection.EndLine < QualifiedSelection.Selection.StartLine));
86+
var inboundLocalVariables = declarationsInParentMethod
87+
.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)) &&
88+
(d.References.Any(r => r.Selection.EndLine < QualifiedSelection.Selection.StartLine ||
89+
(r.Selection.EndLine == QualifiedSelection.Selection.StartLine &&
90+
r.Selection.EndColumn < QualifiedSelection.Selection.StartColumn))));
8891
var inboundVariables = inboundParameters.Concat(inboundLocalVariables);
8992

9093
//List of "outbound" variables. Any variables assigned a value in the selection which are then referenced after the selection
9194
var outboundVariables = sourceMethodParameters.Concat(declarationsInParentMethod)
92-
.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)) && d.References.Any(r => r.Selection.StartLine > QualifiedSelection.Selection.EndLine));
95+
.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)) &&
96+
(d.References.Any(r => r.Selection.StartLine > QualifiedSelection.Selection.EndLine ||
97+
(r.Selection.StartLine == QualifiedSelection.Selection.EndLine &&
98+
r.Selection.StartColumn > QualifiedSelection.Selection.EndColumn))));
9399

94100
SetUpParameters(inboundVariables, outboundVariables);
95101

@@ -103,8 +109,12 @@ private void Setup()
103109
// - where declaration is before the selection but only references are inside the selection
104110
_declarationsToMoveIn = declarationsInParentMethod.Except(declarationsInSelection)
105111
.Where(d => d.References.Any(r => QualifiedSelection.Selection.Contains(r.Selection)) &&
106-
!d.References.Any(r => r.Selection.EndLine < QualifiedSelection.Selection.StartLine) &&
107-
!d.References.Any(r => r.Selection.StartLine > QualifiedSelection.Selection.EndLine));
112+
!d.References.Any(r => r.Selection.EndLine < QualifiedSelection.Selection.StartLine ||
113+
(r.Selection.EndLine == QualifiedSelection.Selection.StartLine &&
114+
r.Selection.EndColumn < QualifiedSelection.Selection.StartColumn)) &&
115+
!d.References.Any(r => r.Selection.StartLine > QualifiedSelection.Selection.EndLine ||
116+
(r.Selection.StartLine == QualifiedSelection.Selection.EndLine &&
117+
r.Selection.StartColumn > QualifiedSelection.Selection.EndColumn)));
108118
}
109119

110120
private IOrderedEnumerable<Declaration> GetDeclarationsInSelection(QualifiedSelection qualifiedSelection)
@@ -157,6 +167,7 @@ private void SetUpParameters(IEnumerable<Declaration> inboundVariables, IEnumera
157167
}
158168

159169
public string SelectedCode { get; private set; }
170+
private int SelectionIndentation;
160171

161172
//Code excluding declarations that are to be moved out of the selection
162173
public string SelectedCodeToExtract
@@ -170,10 +181,21 @@ public string SelectedCodeToExtract
170181
//TODO - create way to map selection to the string or confirm that commented out code above works (including whitespace)
171182

172183
var targetMethodSelection = TargetMethod.Selection;
173-
var selectionCode = targetMethodCode.Skip(QualifiedSelection.Selection.StartLine - targetMethodSelection.StartLine)
174-
.Take(QualifiedSelection.Selection.EndLine - QualifiedSelection.Selection.StartLine + 1)
175-
.ToList();
176184
var selectionToExtract = QualifiedSelection.Selection;
185+
var selectionCode = targetMethodCode.Skip(selectionToExtract.StartLine - targetMethodSelection.StartLine)
186+
.Take(selectionToExtract.EndLine - selectionToExtract.StartLine + 1)
187+
.ToList();
188+
SelectionIndentation = selectionCode[0].Length - selectionCode[0].TrimStart(' ').Length;
189+
//Handle first and last lines of selection.
190+
//As a start, detect if code on those lines that is not in the selection and throw error
191+
if (selectionToExtract.StartColumn > 1)
192+
{
193+
selectionCode[0] = selectionCode[0].Substring(selectionToExtract.StartColumn - 1);
194+
}
195+
if (selectionToExtract.EndColumn < targetMethodCode[selectionToExtract.EndLine - selectionToExtract.StartLine].Length)
196+
{
197+
selectionCode[selectionCode.Count - 1] = selectionCode[selectionCode.Count - 1].Substring(0, selectionToExtract.EndColumn - 1);
198+
}
177199

178200
foreach (var decl in _declarationsToMoveOut.Except(_declarationsToMoveOut.Where(d => d.IdentifierName == ReturnParameter.Name)))
179201
{
@@ -207,17 +229,6 @@ public string SelectedCodeToExtract
207229
selectionCode.RemoveAt(startLine - selectionToExtract.StartLine);
208230
}
209231

210-
//TODO - Handle first and last lines of selection. Could be affected by previous moves, so need better approach using parse tree.
211-
//As a start, detect if code on those lines that is not in the selection and throw error
212-
//if (selectionToExtract.StartColumn > 1)
213-
//{
214-
// selectionCode[0] = selectionCode[0].Substring(selectionToExtract.StartColumn - 1);
215-
//}
216-
//if (selectionToExtract.EndColumn < targetMethodCode[selectionToExtract.EndLine - selectionToExtract.StartLine].Length)
217-
//{
218-
// selectionCode[selectionCode.Count - 1] = selectionCode[selectionCode.Count - 1].Substring(0, selectionToExtract.EndColumn);
219-
//}
220-
221232
//var selectionsToMoveOut = (from dec in _declarationsToMoveOut
222233
// orderby dec.Selection.StartLine, dec.Selection.StartColumn
223234
// select dec.Selection);
@@ -234,16 +245,7 @@ public string ReplacementCode
234245
get
235246
{
236247
var strings = new List<string> { string.Empty };
237-
string indentation;
238-
if (SelectedContexts.First().GetType() == typeof(VBAParser.BlockStmtContext))
239-
{
240-
indentation = FrontPadding((VBAParser.BlockStmtContext)SelectedContexts.First());
241-
}
242-
else
243-
{
244-
var enclosingStatementContext = SelectedContexts.First().GetAncestor<VBAParser.BlockStmtContext>();
245-
indentation = FrontPadding(enclosingStatementContext);
246-
}
248+
var indentation = new string(' ', SelectionIndentation);
247249

248250
foreach (var dec in _declarationsToMoveOut)
249251
{
@@ -259,7 +261,7 @@ public string ReplacementCode
259261
}
260262

261263
// Make call to new method
262-
var argList = string.Join(",", from p in ArgumentsToPass select p.Name);
264+
var argList = string.Join(", ", from p in ArgumentsToPass select p.Name);
263265

264266
if (ReturnParameter == ExtractMethodParameter.None)
265267
{
@@ -318,20 +320,6 @@ public string NewMethodCode
318320
}
319321
}
320322

321-
private string FrontPadding(VBAParser.BlockStmtContext context)
322-
{
323-
//TODO - starts from block statement but that could be following another statement e.g. a = 1 : b = 2, so better to find whole line
324-
var paddingChars = context.Start.Column;
325-
if (paddingChars > 0)
326-
{
327-
return new string(' ', paddingChars);
328-
}
329-
else
330-
{
331-
return string.Empty;
332-
}
333-
}
334-
335323
private static bool IsStatic(Declaration declaration)
336324
{
337325
var ctxt = declaration.Context.GetAncestor<VBAParser.VariableStmtContext>();

RubberduckTests/Refactoring/ExtractMethod/ExtractMethodModelTests.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,69 @@ End If
451451
});
452452
}
453453

454+
[Test(Description = "Handle indenting and splitting of code when two statements in one colon-separated line")]
455+
[Category("Refactorings")]
456+
[Category("ExtractMethod")]
457+
public void FindCorrectIndentingForReplacementCode()
458+
{
459+
var inputCode = @"
460+
Sub Test
461+
Dim a As Integer: Dim b As Integer
462+
a = 10
463+
b = 12
464+
Debug.Print a + b
465+
End Sub";
466+
var selection = new Selection(3, 23, 6, 22);
467+
var expectedNewMethodCode = @"
468+
Private Sub NewMethod()
469+
Dim a As Integer
470+
Dim b As Integer
471+
a = 10
472+
b = 12
473+
Debug.Print a + b
474+
End Sub";
475+
var expectedReplacementCode = @"
476+
NewMethod";
477+
var model = InitialModel(inputCode, selection, true);
478+
var newMethodCode = model.NewMethodCode;
479+
480+
Assert.AreEqual(expectedNewMethodCode, newMethodCode);
481+
var replacementCode = model.ReplacementCode;
482+
Assert.AreEqual(expectedReplacementCode, replacementCode);
483+
}
484+
485+
486+
487+
[Test(Description = "Two references to a variable in the same line, only one extracted")]
488+
[Category("Refactorings")]
489+
[Category("ExtractMethod")]
490+
public void SplitOutLogicalStatementFromMultistatementLine()
491+
{
492+
var inputCode = @"
493+
Sub splitter()
494+
Dim a As Integer
495+
Dim b As Integer
496+
b = 10: b = b + 10
497+
a = b
498+
Debug.Print a
499+
End Sub";
500+
var selection = new Selection(5, 13, 6, 10);
501+
var expectedNewMethodCode = @"
502+
Private Sub NewMethod(ByVal b As Integer, ByRef a As Integer)
503+
b = b + 10
504+
a = b
505+
End Sub";
506+
var expectedReplacementCode = @"
507+
NewMethod b, a";
508+
var model = InitialModel(inputCode, selection, true);
509+
var newMethodCode = model.NewMethodCode;
510+
511+
Assert.AreEqual(expectedNewMethodCode, newMethodCode);
512+
var replacementCode = model.ReplacementCode;
513+
Assert.AreEqual(expectedReplacementCode, replacementCode);
514+
}
454515

455516
//TODO - add Dim a As New Object with declaration copied/split
456-
//TODO - add changes to original function return value (or raise error if too complex)
457517
//TODO - exception testing to cover all exception types (and message???)
458518

459519
protected override IRefactoring TestRefactoring(

0 commit comments

Comments
 (0)