Skip to content

Commit 95024c2

Browse files
committed
#1942: AssignedByValParameter insp, new quick-fix
New AssignedByValParameter inpsection QuickFix that introduces a local variable copy of the ByVal parameter. Adds tests associated with the quickfix and some refactoring to reduce duplicate code.
1 parent 38a47fc commit 95024c2

11 files changed

+1007
-125
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using Rubberduck.Inspections.Abstract;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using Rubberduck.VBEditor;
6+
using Rubberduck.Inspections.Resources;
7+
using Rubberduck.Parsing.Grammar;
8+
using Rubberduck.Parsing.Symbols;
9+
using System.Windows.Forms;
10+
using Rubberduck.UI.Refactorings;
11+
12+
namespace Rubberduck.Inspections.QuickFixes
13+
{
14+
public class AssignedByValParameterQuickFix : QuickFixBase
15+
{
16+
17+
private Declaration _target;
18+
private string _localCopyVariableName;
19+
private bool _forceUseOfGeneratedName;
20+
private string[] _originalCodeLines;
21+
22+
public AssignedByValParameterQuickFix(Declaration target, QualifiedSelection selection)
23+
: base(target.Context, selection, InspectionsUI.AssignedByValParameterQuickFix)
24+
{
25+
_target = target;
26+
_localCopyVariableName = "local" + CapitalizeFirstLetter(_target.IdentifierName);
27+
_forceUseOfGeneratedName = false;
28+
29+
_originalCodeLines = GetMethodLines();
30+
}
31+
public override bool CanFixInModule { get { return false; } }
32+
public override bool CanFixInProject { get { return false; } }
33+
34+
public override void Fix()
35+
{
36+
if (!_forceUseOfGeneratedName)
37+
{
38+
GetLocalCopyVariableNameFromUser();
39+
40+
if (IsCancelled) { return; }
41+
}
42+
43+
modifyBlockToUseLocalCopyVariable();
44+
45+
}
46+
//This function created solely to support unit testing - prevents popup dialog
47+
public void ForceFixUsingGeneratedName()
48+
{
49+
_forceUseOfGeneratedName = true;
50+
Fix();
51+
}
52+
53+
private void GetLocalCopyVariableNameFromUser()
54+
{
55+
using (var view = new AssignedByValParameterQuickFixDialog(_originalCodeLines))
56+
{
57+
view.Target = _target;
58+
view.NewName = _localCopyVariableName;
59+
view.ShowDialog();
60+
61+
IsCancelled = view.DialogResult == DialogResult.Cancel;
62+
if (!IsCancelled) { _localCopyVariableName = view.NewName; }
63+
}
64+
}
65+
private void modifyBlockToUseLocalCopyVariable()
66+
{
67+
if(ProposedNameIsInUse()) { return; }
68+
69+
var module = Selection.QualifiedName.Component.CodeModule;
70+
int startLine = Selection.Selection.StartLine;
71+
72+
module.InsertLines(++startLine, buildLocalCopyDeclaration());
73+
module.InsertLines(++startLine, buildLocalCopyAssignment());
74+
var moduleLines = GetModuleLines();
75+
//moduleLines array index is zero-based
76+
string endOfScopeStatement = GetEndOfScopeStatementForDeclaration(moduleLines[Selection.Selection.StartLine - 1]);
77+
78+
bool IsInScope = true;
79+
int obIndex; //One-Based index for module object
80+
int zbIndex; //Zero-Based index for moduleLines array
81+
//starts with lines after the above inserts
82+
for (zbIndex = startLine ; IsInScope && zbIndex < module.CountOfLines; zbIndex++)
83+
{
84+
obIndex = zbIndex + 1;
85+
if (LineRequiresUpdate(moduleLines[zbIndex]))
86+
{
87+
string newStatement = moduleLines[zbIndex].Replace(_target.IdentifierName, _localCopyVariableName);
88+
module.ReplaceLine(obIndex, newStatement);
89+
}
90+
IsInScope = !moduleLines[zbIndex].Contains(endOfScopeStatement);
91+
}
92+
}
93+
private bool ProposedNameIsInUse()
94+
{
95+
return GetMethodLines().Any(c => c.Contains(Tokens.Dim + " " + _localCopyVariableName + " "));
96+
}
97+
private bool LineRequiresUpdate(string line)
98+
{
99+
return line.Contains(" " + _target.IdentifierName + " ")
100+
|| line.Contains(NameAsLeftHandSide())
101+
|| line.Contains(NameAsRightHandSide())
102+
|| line.Contains(NameAsObjectMethodOrAccessorCall())
103+
|| line.Contains(NameAsSubOrFunctionParam())
104+
|| line.Contains(NameAsSubOrFunctionParamFirst())
105+
|| line.Contains(NameAsSubOrFunctionParamLast());
106+
}
107+
108+
private string NameAsLeftHandSide() { return _target.IdentifierName + " "; }
109+
private string NameAsRightHandSide() { return " " + _target.IdentifierName; }
110+
private string NameAsObjectMethodOrAccessorCall() { return " " + _target.IdentifierName + "."; }
111+
private string NameAsSubOrFunctionParam() { return _target.IdentifierName + ","; }
112+
private string NameAsSubOrFunctionParamFirst() { return "(" + _target.IdentifierName; }
113+
private string NameAsSubOrFunctionParamLast() { return _target.IdentifierName + ")"; }
114+
115+
116+
private string buildLocalCopyDeclaration()
117+
{
118+
return Tokens.Dim + " " + _localCopyVariableName + " " + Tokens.As
119+
+ " " + _target.AsTypeName;
120+
}
121+
private string buildLocalCopyAssignment()
122+
{
123+
return (ValueTypes.Contains(_target.AsTypeName) ? string.Empty : Tokens.Set + " ")
124+
+ _localCopyVariableName + " = " + _target.IdentifierName; ;
125+
}
126+
private string[] GetModuleLines()
127+
{
128+
var module = Selection.QualifiedName.Component.CodeModule;
129+
var lines = module.GetLines(1, module.CountOfLines);
130+
string[] newLine = new string[] { "\r\n" };
131+
return lines.Split(newLine, StringSplitOptions.None);
132+
}
133+
private string[] GetMethodLines()
134+
{
135+
int zbIndex; //zero-based index
136+
zbIndex = Selection.Selection.StartLine - 1;
137+
//int startLine = Selection.Selection.StartLine;
138+
string[] allLines = GetModuleLines();
139+
140+
string endStatement = GetEndOfScopeStatementForDeclaration(allLines[zbIndex]);
141+
142+
bool IsInScope = true;
143+
System.Collections.Generic.List<string> codeBlockLines = new System.Collections.Generic.List<string>();
144+
for ( ; IsInScope && zbIndex < allLines.Count(); zbIndex++)
145+
{
146+
codeBlockLines.Add(allLines[zbIndex]);
147+
IsInScope = !allLines[zbIndex].Contains(endStatement);
148+
}
149+
return codeBlockLines.ToArray();
150+
}
151+
private string GetEndOfScopeStatementForDeclaration(string declaration)
152+
{
153+
return declaration.Contains("Sub ") ? "End Sub" : "End Function";
154+
}
155+
private static readonly IReadOnlyList<string> ValueTypes = new[]
156+
{
157+
Tokens.Boolean,
158+
Tokens.Byte,
159+
Tokens.Currency,
160+
Tokens.Date,
161+
Tokens.Decimal,
162+
Tokens.Double,
163+
Tokens.Integer,
164+
Tokens.Long,
165+
Tokens.LongLong,
166+
Tokens.Single,
167+
Tokens.String,
168+
Tokens.Variant
169+
};
170+
private string CapitalizeFirstLetter(string name)
171+
{
172+
return name.Substring(0, 1).ToUpper() + name.Substring(1);
173+
}
174+
175+
}
176+
}

RetailCoder.VBE/Inspections/Resources/InspectionsUI.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

RetailCoder.VBE/Inspections/Resources/InspectionsUI.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,4 +647,7 @@ If the parameter can be null, ignore this inspection result; passing a null valu
647647
<data name="ApplicationWorksheetFunctionQuickFix" xml:space="preserve">
648648
<value>Use Application.WorksheetFunction explicitly.</value>
649649
</data>
650+
<data name="AssignedByValParameterQuickFix" xml:space="preserve">
651+
<value>Create and use a local copy of the parameter</value>
652+
</data>
650653
</root>

RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public override IEnumerable<QuickFixBase> QuickFixes
2727
{
2828
return _quickFixes ?? (_quickFixes = new QuickFixBase[]
2929
{
30+
new AssignedByValParameterQuickFix(Target, QualifiedSelection),
3031
new PassParameterByReferenceQuickFix(Target.Context, QualifiedSelection),
3132
new IgnoreOnceQuickFix(Context, QualifiedSelection, Inspection.AnnotationName)
3233
});

RetailCoder.VBE/Rubberduck.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@
372372
<Compile Include="Inspections\MemberNotOnInterfaceInspection.cs" />
373373
<Compile Include="Inspections\QuickFixes\AddIdentifierToWhiteListQuickFix.cs" />
374374
<Compile Include="Inspections\QuickFixes\ApplicationWorksheetFunctionQuickFix.cs" />
375+
<Compile Include="Inspections\QuickFixes\AssignedByValParameterQuickFix.cs" />
375376
<Compile Include="Inspections\Resources\InspectionsUI.Designer.cs">
376377
<AutoGen>True</AutoGen>
377378
<DesignTime>True</DesignTime>
@@ -743,6 +744,12 @@
743744
<Compile Include="UI\IOpenFileDialog.cs" />
744745
<Compile Include="UI\ISaveFileDialog.cs" />
745746
<Compile Include="UI\ParserErrors\ParseErrorListItem.cs" />
747+
<Compile Include="UI\Refactorings\AssignedByValParameterQuickFixDialog.cs">
748+
<SubType>Form</SubType>
749+
</Compile>
750+
<Compile Include="UI\Refactorings\AssignedByValParameterQuickFixDialog.Designer.cs">
751+
<DependentUpon>AssignedByValParameterQuickFixDialog.cs</DependentUpon>
752+
</Compile>
746753
<Compile Include="UI\Refactorings\EncapsulateFieldDialog.cs">
747754
<SubType>Form</SubType>
748755
</Compile>
@@ -1087,6 +1094,9 @@
10871094
<EmbeddedResource Include="UI\FindSymbol\FindSymbolDialog.resx">
10881095
<DependentUpon>FindSymbolDialog.cs</DependentUpon>
10891096
</EmbeddedResource>
1097+
<EmbeddedResource Include="UI\Refactorings\AssignedByValParameterQuickFixDialog.resx">
1098+
<DependentUpon>AssignedByValParameterQuickFixDialog.cs</DependentUpon>
1099+
</EmbeddedResource>
10901100
<EmbeddedResource Include="UI\Refactorings\EncapsulateFieldDialog.resx">
10911101
<DependentUpon>EncapsulateFieldDialog.cs</DependentUpon>
10921102
</EmbeddedResource>

0 commit comments

Comments
 (0)