Skip to content

Commit 47aeed7

Browse files
committed
Strenghtened the ByRef QuickFix to handle line continuations etc
1 parent eb6f24f commit 47aeed7

File tree

3 files changed

+186
-17
lines changed

3 files changed

+186
-17
lines changed

RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
using Rubberduck.Inspections.Abstract;
33
using Rubberduck.Inspections.Resources;
44
using Rubberduck.Parsing.Grammar;
5+
using Rubberduck.Parsing.Symbols;
56
using Rubberduck.VBEditor;
7+
using System;
8+
using System.Collections.Generic;
69
using System.Text.RegularExpressions;
710
using static Rubberduck.Parsing.Grammar.VBAParser;
811

@@ -13,33 +16,117 @@ namespace Rubberduck.Inspections.QuickFixes
1316
/// </summary>
1417
public class PassParameterByReferenceQuickFix : QuickFixBase
1518
{
16-
public PassParameterByReferenceQuickFix(ParserRuleContext context, QualifiedSelection selection)
17-
: base(context, selection, InspectionsUI.PassParameterByReferenceQuickFix)
19+
private Declaration _target;
20+
private int _byValTokenProcLine;
21+
private int _byValIdentifierNameProcLine;
22+
23+
public PassParameterByReferenceQuickFix(Declaration target, QualifiedSelection selection)
24+
: base(target.Context, selection, InspectionsUI.PassParameterByReferenceQuickFix)
1825
{
26+
_target = target;
27+
_byValTokenProcLine = 0;
28+
_byValIdentifierNameProcLine = 0;
1929
}
2030

2131
public override void Fix()
2232
{
23-
var byValParameter = Context.GetText();
33+
string byValTargetString;
34+
string byValTokenReplacementString;
35+
string replacementString;
2436

25-
var byRefParameter = BuildByRefParameter(byValParameter);
37+
var procLines = RetrieveProcedureLines();
2638

27-
ReplaceByValParameterInModule(byValParameter, byRefParameter);
28-
}
39+
SetMemberLineValues(procLines);
40+
41+
string moduleLineWithByValToken = procLines[_byValTokenProcLine - 1];
42+
43+
if (_byValTokenProcLine == _byValIdentifierNameProcLine)
44+
{
45+
//The replacement is based on the (e.g. "ByVal identifierName")
46+
byValTargetString = Tokens.ByVal + " " + _target.IdentifierName;
47+
byValTokenReplacementString = BuildByRefParameter(byValTargetString);
48+
replacementString = moduleLineWithByValToken.Replace(byValTargetString, byValTokenReplacementString);
49+
}
50+
else
51+
{
52+
//if the token and identifier are on different lines, then the target
53+
//string consists of the ByVal token and the LineContinuation token.
54+
//(e.g. the replacement is based on "ByVal _". Spaces between tokens can vary)
55+
byValTargetString = GetUniqueTargetStringForByValAtEndOfLine(moduleLineWithByValToken);
56+
byValTokenReplacementString = BuildByRefParameter(byValTargetString);
57+
58+
//avoid updating possible cases of ByVal followed by underscore-prefixed identifiers
59+
var index = moduleLineWithByValToken.LastIndexOf(byValTargetString);
60+
var firstPart = moduleLineWithByValToken.Substring(0, index);
61+
replacementString = firstPart + byValTokenReplacementString;
62+
}
2963

64+
var module = Selection.QualifiedName.Component.CodeModule;
65+
module.ReplaceLine(RetrieveTheProcedureStartLine() + _byValTokenProcLine-1, replacementString);
66+
}
67+
private string[] RetrieveProcedureLines()
68+
{
69+
var moduleContent = Context.Start.InputStream.ToString();
70+
string[] newLine = { "\r\n" };
71+
var moduleLines = moduleContent.Split(newLine, StringSplitOptions.None);
72+
var procLines = new List<string>();
73+
var startIndex = RetrieveTheProcedureStartLine();
74+
var endIndex = RetrieveTheProcedureEndLine();
75+
for ( int idx = startIndex - 1; idx < endIndex; idx++)
76+
{
77+
procLines.Add(moduleLines[idx]);
78+
}
79+
return procLines.ToArray();
80+
}
81+
private int RetrieveTheProcedureStartLine()
82+
{
83+
var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent;
84+
return parserRuleCtxt.Start.Line;
85+
}
86+
private int RetrieveTheProcedureEndLine()
87+
{
88+
var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent;
89+
return parserRuleCtxt.Stop.Line;
90+
}
3091
private string BuildByRefParameter(string originalParameter)
3192
{
3293
var everythingAfterTheByValToken = originalParameter.Substring(Tokens.ByVal.Length);
3394
return Tokens.ByRef + everythingAfterTheByValToken;
3495
}
35-
private void ReplaceByValParameterInModule( string byValParameter, string byRefParameter)
96+
private string GetUniqueTargetStringForByValAtEndOfLine(string procLineWithByValToken)
3697
{
37-
var selection = Selection.Selection;
38-
var module = Selection.QualifiedName.Component.CodeModule;
98+
System.Diagnostics.Debug.Assert(procLineWithByValToken.Contains(Tokens.LineContinuation));
99+
100+
var positionOfLineContinuation = procLineWithByValToken.LastIndexOf(Tokens.LineContinuation);
101+
var positionOfLastByValToken = procLineWithByValToken.LastIndexOf(Tokens.ByVal);
102+
return procLineWithByValToken.Substring(positionOfLastByValToken, positionOfLineContinuation - positionOfLastByValToken + 2);
103+
}
104+
private void SetMemberLineValues(string[] procedureLines)
105+
{
106+
string line;
107+
bool byValTokenFound = false;
108+
bool byValIdentifierNameFound = false;
109+
for (int zbIndexByValLine = 0; !byValIdentifierNameFound && zbIndexByValLine < procedureLines.Length; zbIndexByValLine++)
110+
{
111+
line = procedureLines[zbIndexByValLine];
112+
if (line.Contains(Tokens.ByVal))
113+
{
114+
_byValTokenProcLine = zbIndexByValLine + 1;
115+
byValTokenFound = true;
116+
}
117+
if (byValTokenFound)
118+
{
119+
if (line.Contains(_target.IdentifierName))
120+
{
121+
_byValIdentifierNameProcLine = zbIndexByValLine + 1;
122+
byValIdentifierNameFound = true;
123+
}
124+
}
125+
}
39126

40-
var lines = module.GetLines(selection.StartLine, selection.LineCount);
41-
var result = lines.Replace(byValParameter, byRefParameter);
42-
module.ReplaceLine(selection.StartLine, result);
127+
System.Diagnostics.Debug.Assert(_byValTokenProcLine > 0);
128+
System.Diagnostics.Debug.Assert(_byValIdentifierNameProcLine > 0);
129+
return;
43130
}
44131
}
45132
}

RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public override IEnumerable<QuickFixBase> QuickFixes
2828
return _quickFixes ?? (_quickFixes = new QuickFixBase[]
2929
{
3030
new AssignedByValParameterQuickFix(Target, QualifiedSelection),
31-
new PassParameterByReferenceQuickFix(Target.Context, QualifiedSelection),
31+
new PassParameterByReferenceQuickFix(Target, QualifiedSelection),
3232
new IgnoreOnceQuickFix(Context, QualifiedSelection, Inspection.AnnotationName)
3333
});
3434
}

RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,13 @@ Dim var1 As Integer
9797
[TestCategory("Inspections")]
9898
public void AssignedByValParameter_QuickFixWorks()
9999
{
100+
100101
string inputCode =
101-
@"Public Sub Foo(ByVal barByVal As String)
102+
@"Public Sub Foo(Optional ByVal barByVal As String = ""XYZ"")
102103
Let barByVal = ""test""
103104
End Sub";
104105
string expectedCode =
105-
@"Public Sub Foo(ByRef barByVal As String)
106+
@"Public Sub Foo(Optional ByRef barByVal As String = ""XYZ"")
106107
Let barByVal = ""test""
107108
End Sub";
108109

@@ -111,18 +112,99 @@ public void AssignedByValParameter_QuickFixWorks()
111112

112113
//check when ByVal argument is one of several parameters
113114
inputCode =
114-
@"Public Sub Foo(ByRef firstArg As Long, ByVal barByVal As String, secondArg as Double)
115+
@"Public Sub Foo(ByRef firstArg As Long, Optional ByVal barByVal As String = """", secondArg as Double)
115116
Let barByVal = ""test""
116117
End Sub";
117118
expectedCode =
118-
@"Public Sub Foo(ByRef firstArg As Long, ByRef barByVal As String, secondArg as Double)
119+
@"Public Sub Foo(ByRef firstArg As Long, Optional ByRef barByVal As String = """", secondArg as Double)
119120
Let barByVal = ""test""
120121
End Sub";
121122

122123
quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode);
123124
Assert.AreEqual(expectedCode, quickFixResult);
125+
126+
inputCode =
127+
@"
128+
Private Sub Foo(Optional ByVal _
129+
bar _
130+
As _
131+
Long = 4, _
132+
ByVal _
133+
barTwo _
134+
As _
135+
Long)
136+
bar = 42
137+
End Sub
138+
"
139+
;
140+
expectedCode =
141+
@"
142+
Private Sub Foo(Optional ByRef _
143+
bar _
144+
As _
145+
Long = 4, _
146+
ByVal _
147+
barTwo _
148+
As _
149+
Long)
150+
bar = 42
151+
End Sub
152+
"
153+
;
154+
quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode);
155+
Assert.AreEqual(expectedCode, quickFixResult);
156+
157+
inputCode =
158+
@"Private Sub Foo(ByVal barByVal As Long, ByVal _xByValbar As Long, ByVal _
159+
barTwo _
160+
As _
161+
Long)
162+
barTwo = 42
163+
End Sub
164+
";
165+
expectedCode =
166+
@"Private Sub Foo(ByVal barByVal As Long, ByVal _xByValbar As Long, ByRef _
167+
barTwo _
168+
As _
169+
Long)
170+
barTwo = 42
171+
End Sub
172+
";
173+
174+
quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode);
175+
Assert.AreEqual(expectedCode, quickFixResult);
176+
177+
inputCode =
178+
@"Sub DoSomething(_
179+
ByVal foo As Long, _
180+
ByRef _
181+
bar, _
182+
ByRef barbecue _
183+
)
184+
foo = 4
185+
bar = barbecue * _
186+
bar + foo / barbecue
187+
End Sub
188+
";
189+
190+
expectedCode =
191+
@"Sub DoSomething(_
192+
ByRef foo As Long, _
193+
ByRef _
194+
bar, _
195+
ByRef barbecue _
196+
)
197+
foo = 4
198+
bar = barbecue * _
199+
bar + foo / barbecue
200+
End Sub
201+
";
202+
quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode);
203+
Assert.AreEqual(expectedCode, quickFixResult);
204+
124205
}
125206

207+
126208
[TestMethod]
127209
[TestCategory("Inspections")]
128210
public void AssignedByValParameter_IgnoreQuickFixWorks()

0 commit comments

Comments
 (0)