Skip to content

Commit f377889

Browse files
committed
2 parents 6c62ae2 + aa59e31 commit f377889

19 files changed

+1153
-814
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Antlr4.Runtime;
2+
using Rubberduck.Parsing.Symbols;
3+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
4+
5+
namespace Rubberduck.Common
6+
{
7+
public static class CodeModuleExtensions
8+
{
9+
public static void ReplaceToken(this ICodeModule module, IToken token, string replacement)
10+
{
11+
var original = module.GetLines(token.Line, 1);
12+
var result = ReplaceStringAtIndex(original, token.Text, replacement, token.Column);
13+
module.ReplaceLine(token.Line, result);
14+
}
15+
16+
public static void ReplaceIdentifierReferenceName(this ICodeModule module, IdentifierReference identifierReference, string replacement)
17+
{
18+
var original = module.GetLines(identifierReference.Selection.StartLine, 1);
19+
var result = ReplaceStringAtIndex(original, identifierReference.IdentifierName, replacement, identifierReference.Context.Start.Column);
20+
module.ReplaceLine(identifierReference.Selection.StartLine, result);
21+
}
22+
23+
public static void InsertLines(this ICodeModule module, int startLine, string[] lines)
24+
{
25+
int lineNumber = startLine;
26+
for ( int idx = 0; idx < lines.Length; idx++ )
27+
{
28+
module.InsertLines(lineNumber, lines[idx]);
29+
lineNumber++;
30+
}
31+
}
32+
private static string ReplaceStringAtIndex(string original, string toReplace, string replacement, int startIndex)
33+
{
34+
var modifiedContent = original.Remove(startIndex, toReplace.Length);
35+
return modifiedContent.Insert(startIndex, replacement);
36+
}
37+
}
38+
}

RetailCoder.VBE/Inspections/AssignedByValParameterInspection.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
using Rubberduck.Parsing.Grammar;
77
using Rubberduck.Parsing.Symbols;
88
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.UI.Refactorings;
910

1011
namespace Rubberduck.Inspections
1112
{
1213
public sealed class AssignedByValParameterInspection : InspectionBase
1314
{
14-
public AssignedByValParameterInspection(RubberduckParserState state)
15+
private IAssignedByValParameterQuickFixDialogFactory _dialogFactory;
16+
public AssignedByValParameterInspection(RubberduckParserState state, IAssignedByValParameterQuickFixDialogFactory dialogFactory)
1517
: base(state)
1618
{
1719
Severity = DefaultSeverity;
20+
_dialogFactory = dialogFactory;
21+
1822
}
1923

2024
public override string Meta { get { return InspectionsUI.AssignedByValParameterInspectionMeta; } }
@@ -31,7 +35,7 @@ public override IEnumerable<InspectionResultBase> GetInspectionResults()
3135
.ToList();
3236

3337
return parameters
34-
.Select(param => new AssignedByValParameterInspectionResult(this, param))
38+
.Select(param => new AssignedByValParameterInspectionResult(this, param, _dialogFactory))
3539
.ToList();
3640
}
3741
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using Rubberduck.Inspections.Abstract;
2+
using System.Linq;
3+
using Rubberduck.Parsing;
4+
using Rubberduck.VBEditor;
5+
using Rubberduck.Inspections.Resources;
6+
using Rubberduck.Parsing.Grammar;
7+
using Rubberduck.Parsing.Symbols;
8+
using System.Windows.Forms;
9+
using Rubberduck.UI.Refactorings;
10+
using Rubberduck.Common;
11+
using Antlr4.Runtime;
12+
using System.Collections.Generic;
13+
using Antlr4.Runtime.Tree;
14+
15+
namespace Rubberduck.Inspections.QuickFixes
16+
{
17+
public class AssignedByValParameterMakeLocalCopyQuickFix : QuickFixBase
18+
{
19+
private readonly Declaration _target;
20+
private readonly IAssignedByValParameterQuickFixDialogFactory _dialogFactory;
21+
private string _localCopyVariableName;
22+
private string[] _variableNamesAccessibleToProcedureContext;
23+
24+
public AssignedByValParameterMakeLocalCopyQuickFix(Declaration target, QualifiedSelection selection, IAssignedByValParameterQuickFixDialogFactory dialogFactory)
25+
: base(target.Context, selection, InspectionsUI.AssignedByValParameterMakeLocalCopyQuickFix)
26+
{
27+
_target = target;
28+
_dialogFactory = dialogFactory;
29+
_variableNamesAccessibleToProcedureContext = GetVariableNamesAccessibleToProcedureContext(_target.Context.Parent.Parent);
30+
SetValidLocalCopyVariableNameSuggestion();
31+
}
32+
33+
public override bool CanFixInModule { get { return false; } }
34+
public override bool CanFixInProject { get { return false; } }
35+
36+
public override void Fix()
37+
{
38+
RequestLocalCopyVariableName();
39+
40+
if (!VariableNameIsValid(_localCopyVariableName) || IsCancelled)
41+
{
42+
return;
43+
}
44+
45+
ReplaceAssignedByValParameterReferences();
46+
47+
InsertLocalVariableDeclarationAndAssignment();
48+
}
49+
50+
private void RequestLocalCopyVariableName()
51+
{
52+
using( var view = _dialogFactory.Create(_target.IdentifierName, _target.DeclarationType.ToString()))
53+
{
54+
view.NewName = _localCopyVariableName;
55+
view.IdentifierNamesAlreadyDeclared = _variableNamesAccessibleToProcedureContext;
56+
view.ShowDialog();
57+
IsCancelled = view.DialogResult == DialogResult.Cancel;
58+
if (!IsCancelled)
59+
{
60+
_localCopyVariableName = view.NewName;
61+
}
62+
}
63+
}
64+
65+
private void SetValidLocalCopyVariableNameSuggestion()
66+
{
67+
_localCopyVariableName = "x" + _target.IdentifierName.CapitalizeFirstLetter();
68+
if (VariableNameIsValid(_localCopyVariableName)) { return; }
69+
70+
//If the initial suggestion is not valid, keep pre-pending x's until it is
71+
for ( int attempt = 2; attempt < 10; attempt++)
72+
{
73+
_localCopyVariableName = "x" + _localCopyVariableName;
74+
if (VariableNameIsValid(_localCopyVariableName))
75+
{
76+
return;
77+
}
78+
}
79+
//if "xxFoo" to "xxxxxxxxxxFoo" isn't unique, give up and go with the original suggestion.
80+
//The QuickFix will leave the code as-is unless it receives a name that is free of conflicts
81+
_localCopyVariableName = "x" + _target.IdentifierName.CapitalizeFirstLetter();
82+
}
83+
84+
private bool VariableNameIsValid(string variableName)
85+
{
86+
var validator = new VariableNameValidator(variableName);
87+
return validator.IsValidName()
88+
&& !_variableNamesAccessibleToProcedureContext
89+
.Any(name => name.Equals(variableName, System.StringComparison.InvariantCultureIgnoreCase));
90+
}
91+
92+
private void ReplaceAssignedByValParameterReferences()
93+
{
94+
var module = Selection.QualifiedName.Component.CodeModule;
95+
foreach (IdentifierReference identifierReference in _target.References)
96+
{
97+
module.ReplaceIdentifierReferenceName(identifierReference, _localCopyVariableName);
98+
}
99+
}
100+
101+
private void InsertLocalVariableDeclarationAndAssignment()
102+
{
103+
var blocks = QuickFixHelper.GetBlockStmtContextsForContext(_target.Context.Parent.Parent);
104+
string[] lines = { BuildLocalCopyDeclaration(), BuildLocalCopyAssignment() };
105+
var module = Selection.QualifiedName.Component.CodeModule;
106+
module.InsertLines(blocks.FirstOrDefault().Start.Line, lines);
107+
}
108+
109+
private string BuildLocalCopyDeclaration()
110+
{
111+
return Tokens.Dim + " " + _localCopyVariableName + " " + Tokens.As
112+
+ " " + _target.AsTypeName;
113+
}
114+
115+
private string BuildLocalCopyAssignment()
116+
{
117+
return (SymbolList.ValueTypes.Contains(_target.AsTypeName) ? string.Empty : Tokens.Set + " ")
118+
+ _localCopyVariableName + " = " + _target.IdentifierName;
119+
}
120+
121+
private string[] GetVariableNamesAccessibleToProcedureContext(RuleContext ruleContext)
122+
{
123+
var allIdentifiers = new HashSet<string>();
124+
125+
var blocks = QuickFixHelper.GetBlockStmtContextsForContext(ruleContext);
126+
127+
var blockStmtIdentifiers = GetIdentifierNames(blocks);
128+
allIdentifiers.UnionWith(blockStmtIdentifiers);
129+
130+
var args = QuickFixHelper.GetArgContextsForContext(ruleContext);
131+
132+
var potentiallyUnreferencedParameters = GetIdentifierNames(args);
133+
allIdentifiers.UnionWith(potentiallyUnreferencedParameters);
134+
135+
//TODO: add module and global scope variableNames to the list.
136+
137+
return allIdentifiers.ToArray();
138+
}
139+
140+
private HashSet<string> GetIdentifierNames(IReadOnlyList<RuleContext> ruleContexts)
141+
{
142+
var identifiers = new HashSet<string>();
143+
foreach (RuleContext ruleContext in ruleContexts)
144+
{
145+
var identifiersForThisContext = GetIdentifierNames(ruleContext);
146+
identifiers.UnionWith(identifiersForThisContext);
147+
}
148+
return identifiers;
149+
}
150+
151+
private HashSet<string> GetIdentifierNames(RuleContext ruleContext)
152+
{
153+
//Recursively work through the tree to get all IdentifierContexts
154+
var results = new HashSet<string>();
155+
var tokenValues = typeof(Tokens).GetFields().Select(item => item.GetValue(null)).Cast<string>().Select(item => item);
156+
var children = GetChildren(ruleContext);
157+
158+
foreach (IParseTree child in children)
159+
{
160+
if (child is VBAParser.IdentifierContext)
161+
{
162+
var childName = Identifier.GetName((VBAParser.IdentifierContext)child);
163+
if (!tokenValues.Contains(childName))
164+
{
165+
results.Add(childName);
166+
}
167+
}
168+
else
169+
{
170+
if (!(child is TerminalNodeImpl))
171+
{
172+
results.UnionWith(GetIdentifierNames((RuleContext)child));
173+
}
174+
}
175+
}
176+
return results;
177+
}
178+
179+
private static List<IParseTree> GetChildren(RuleContext ruleCtx)
180+
{
181+
var result = new List<IParseTree>();
182+
for (int index = 0; index < ruleCtx.ChildCount; index++)
183+
{
184+
result.Add(ruleCtx.GetChild(index));
185+
}
186+
return result;
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)