Skip to content

Commit 6136377

Browse files
committed
new QuickFix files reintroduced
1 parent d687a03 commit 6136377

File tree

3 files changed

+836
-0
lines changed

3 files changed

+836
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
13+
namespace Rubberduck.Inspections.QuickFixes
14+
{
15+
public class AssignedByValParameterMakeLocalCopyQuickFix : QuickFixBase
16+
{
17+
private readonly Declaration _target;
18+
private string _localCopyVariableName;
19+
private bool _isQuickFixUnitTest;
20+
private QuickFixHelper _quickFixHelper;
21+
22+
public AssignedByValParameterMakeLocalCopyQuickFix(Declaration target, QualifiedSelection selection)
23+
: base(target.Context, selection, InspectionsUI.AssignedByValParameterQuickFix)
24+
{
25+
_target = target;
26+
_isQuickFixUnitTest = false;
27+
_localCopyVariableName = "x" + _target.IdentifierName.CapitalizeFirstLetter();
28+
_quickFixHelper = new QuickFixHelper(_target, selection);
29+
}
30+
31+
public override bool CanFixInModule { get { return false; } }
32+
public override bool CanFixInProject { get { return false; } }
33+
34+
//This function exists solely to support unit testing
35+
public void TESTONLY_FixUsingAutoGeneratedName(string suggestedName = "")
36+
{
37+
//Prevent the popup dialog and forces the use of the Suggested Name
38+
_isQuickFixUnitTest = true;
39+
if (suggestedName.Length > 0)
40+
{
41+
_localCopyVariableName = suggestedName; //unit test poses as user input
42+
}
43+
44+
Fix();
45+
}
46+
47+
public override void Fix()
48+
{
49+
50+
SetLocalCopyVariableName();
51+
52+
if (!ProposedLocalVariableNameIsValid()
53+
|| IsCancelled)
54+
{
55+
return;
56+
}
57+
58+
ModifyBlockToUseLocalCopyVariable();
59+
}
60+
61+
private void SetLocalCopyVariableName()
62+
{
63+
using (var view = new AssignedByValParameterQuickFixDialog(_target, Selection))
64+
{
65+
view.NewName = _localCopyVariableName;
66+
view.IdentifierNamesAlreadyDeclared = _quickFixHelper.GetIdentifierNamesAccessibleToProcedureContext();
67+
if (!_isQuickFixUnitTest)
68+
{
69+
view.ShowDialog();
70+
IsCancelled = view.DialogResult == DialogResult.Cancel;
71+
}
72+
if (!IsCancelled)
73+
{
74+
_localCopyVariableName = view.NewName;
75+
}
76+
}
77+
}
78+
79+
private bool ProposedLocalVariableNameIsValid()
80+
{
81+
var validator = new VariableNameValidator(_localCopyVariableName);
82+
return validator.IsValidName()
83+
&& !_quickFixHelper.GetIdentifierNamesAccessibleToProcedureContext().Any(name => name.ToUpper().Equals(_localCopyVariableName.ToUpper()));
84+
}
85+
86+
private void ModifyBlockToUseLocalCopyVariable()
87+
{
88+
ReplaceAssignedByValParameterReferences();
89+
90+
InsertLocalVariableDeclarationAndAssignment(GetFirstBlockStartLine());
91+
}
92+
93+
private void ReplaceAssignedByValParameterReferences()
94+
{
95+
foreach (IdentifierReference identifierReference in _target.References)
96+
{
97+
_quickFixHelper.ReplaceIdentifierReferenceNameInModule(identifierReference, _localCopyVariableName);
98+
}
99+
}
100+
private int GetFirstBlockStartLine()
101+
{
102+
var blocks = _quickFixHelper.GetBlockStmtContextsForContext(_target.Context.Parent.Parent);
103+
return blocks.FirstOrDefault().Start.Line;
104+
}
105+
private void InsertLocalVariableDeclarationAndAssignment(int firstBlockLineNumber)
106+
{
107+
string[] newLines = { BuildLocalCopyDeclaration(), BuildLocalCopyAssignment() };
108+
_quickFixHelper.InsertAfterCodeModuleLine(firstBlockLineNumber - 1, newLines);
109+
}
110+
private string BuildLocalCopyDeclaration()
111+
{
112+
return Tokens.Dim + " " + _localCopyVariableName + " " + Tokens.As
113+
+ " " + _target.AsTypeName;
114+
}
115+
116+
private string BuildLocalCopyAssignment()
117+
{
118+
return (SymbolList.ValueTypes.Contains(_target.AsTypeName) ? string.Empty : Tokens.Set + " ")
119+
+ _localCopyVariableName + " = " + _target.IdentifierName;
120+
}
121+
}
122+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
using Antlr4.Runtime;
2+
using Antlr4.Runtime.Tree;
3+
using Rubberduck.Parsing.Grammar;
4+
using Rubberduck.Parsing.Symbols;
5+
using Rubberduck.VBEditor;
6+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
11+
namespace Rubberduck.Inspections.QuickFixes
12+
{
13+
class QuickFixHelper
14+
{
15+
private Declaration _target;
16+
private QualifiedSelection _selection;
17+
private HashSet<string> _identifierNamesDeclaredInProcedureScope;
18+
private ICodeModule _module;
19+
public QuickFixHelper(Declaration target, QualifiedSelection selection)
20+
{
21+
_target = target;
22+
_selection = selection;
23+
_identifierNamesDeclaredInProcedureScope = new HashSet<string>();
24+
_module = selection.QualifiedName.Component.CodeModule;
25+
}
26+
/// <summary>
27+
/// Returns the CodeModule string located at lineNumber
28+
/// of the newly inserted line.
29+
/// </summary>
30+
public string GetModuleLine(int lineNumber)
31+
{
32+
return _module.GetLines(lineNumber, 1);
33+
}
34+
/// <summary>
35+
/// Inserts a string/line in a Code Module after the lineNumber provided. Returns the line number
36+
/// of the newly inserted line.
37+
/// </summary>
38+
public int InsertAfterCodeModuleLine(int lineNumber, string[] newContent)
39+
{
40+
foreach( string line in newContent)
41+
{
42+
_module.InsertLines(++lineNumber, line);
43+
}
44+
return lineNumber;
45+
}
46+
/// <summary>
47+
/// Replaces a string/line in a Code Module and returns the line number
48+
/// of the replaced line.
49+
/// </summary>
50+
public int ReplaceModuleLine(int lineNumber, string newContent)
51+
{
52+
_module.ReplaceLine(lineNumber, newContent);
53+
return lineNumber;
54+
}
55+
/// <summary>
56+
/// Replaces a the TerminalNode Text value found at the location specified by the
57+
/// TerminalNode context.
58+
/// </summary>
59+
public void ReplaceTerminalNodeTextInCodeModule(ITerminalNode terminalNode, string replacement)
60+
{
61+
var newCode = GenerateTerminalNodeTextReplacementLine(terminalNode, replacement);
62+
ReplaceModuleLine(terminalNode.Symbol.Line, newCode);
63+
}
64+
/// <summary>
65+
/// Replaces a the identifierReference.Name found at the location specified
66+
/// by the identifierReference context.
67+
/// </summary>
68+
public void ReplaceIdentifierReferenceNameInModule(IdentifierReference identifierReference, string replacementName)
69+
{
70+
var newCode = GenerateIdentifierReferenceReplacementLine(identifierReference, replacementName);
71+
ReplaceModuleLine(identifierReference.Selection.StartLine, newCode);
72+
}
73+
/// <summary>
74+
/// Returns an array of IdentifierContext Names used within the procedure context.
75+
/// </summary>
76+
public string[] GetIdentifierNamesAccessibleToProcedureContext()
77+
{
78+
return GetVariableNamesAccessibleToProcedureContext(_target.Context.Parent.Parent).ToArray();
79+
}
80+
public IReadOnlyList<VBAParser.BlockStmtContext> GetBlockStmtContextsForContext(RuleContext context)
81+
{
82+
if (context is VBAParser.SubStmtContext)
83+
{
84+
return ((VBAParser.SubStmtContext)context).block().blockStmt();
85+
}
86+
else if (context is VBAParser.FunctionStmtContext)
87+
{
88+
return ((VBAParser.FunctionStmtContext)context).block().blockStmt();
89+
}
90+
else if (context is VBAParser.PropertyLetStmtContext)
91+
{
92+
return ((VBAParser.PropertyLetStmtContext)context).block().blockStmt();
93+
}
94+
else if (context is VBAParser.PropertyGetStmtContext)
95+
{
96+
return ((VBAParser.PropertyGetStmtContext)context).block().blockStmt();
97+
}
98+
else if (context is VBAParser.PropertySetStmtContext)
99+
{
100+
return ((VBAParser.PropertySetStmtContext)context).block().blockStmt();
101+
}
102+
return Enumerable.Empty<VBAParser.BlockStmtContext>().ToArray();
103+
}
104+
105+
public IReadOnlyList<VBAParser.ArgContext> GetArgContextsForContext(RuleContext context)
106+
{
107+
if (context is VBAParser.SubStmtContext)
108+
{
109+
return ((VBAParser.SubStmtContext)context).argList().arg();
110+
}
111+
else if (context is VBAParser.FunctionStmtContext)
112+
{
113+
return ((VBAParser.FunctionStmtContext)context).argList().arg();
114+
}
115+
else if (context is VBAParser.PropertyLetStmtContext)
116+
{
117+
return ((VBAParser.PropertyLetStmtContext)context).argList().arg();
118+
}
119+
else if (context is VBAParser.PropertyGetStmtContext)
120+
{
121+
return ((VBAParser.PropertyGetStmtContext)context).argList().arg();
122+
}
123+
else if (context is VBAParser.PropertySetStmtContext)
124+
{
125+
return ((VBAParser.PropertySetStmtContext)context).argList().arg();
126+
}
127+
return Enumerable.Empty<VBAParser.ArgContext>().ToArray();
128+
}
129+
private string GenerateIdentifierReferenceReplacementLine(IdentifierReference identifierReference, string replacement)
130+
{
131+
var currentCode = GetModuleLine(identifierReference.Selection.StartLine);
132+
return ReplaceStringAtIndex(currentCode, identifierReference.IdentifierName, replacement, identifierReference.Context.Start.Column);
133+
}
134+
private string GenerateTerminalNodeTextReplacementLine(ITerminalNode terminalNode, string replacement)
135+
{
136+
var currentCode = GetModuleLine(terminalNode.Symbol.Line);
137+
return ReplaceStringAtIndex(currentCode, terminalNode.GetText(), replacement, terminalNode.Symbol.Column);
138+
}
139+
private string ReplaceStringAtIndex(string original, string toReplace, string replacement, int startIndex)
140+
{
141+
Debug.Assert(startIndex >= 0);
142+
Debug.Assert(original.Contains(toReplace));
143+
144+
int stopIndex = startIndex + toReplace.Length - 1;
145+
var prefix = original.Substring(0, startIndex);
146+
var suffix = (stopIndex >= original.Length) ? string.Empty : original.Substring(stopIndex + 1);
147+
var toBeReplaced = original.Substring(startIndex, stopIndex - startIndex + 1);
148+
149+
Debug.Assert(toBeReplaced.IndexOf(toReplace) == 0);
150+
return prefix + toBeReplaced.Replace(toReplace, replacement) + suffix;
151+
}
152+
private HashSet<string> GetVariableNamesAccessibleToProcedureContext(RuleContext ruleContext)
153+
{
154+
var allIdentifiers = new HashSet<string>();
155+
156+
var blockStmtIdentifiers = GetBlockStmtIdentifiers(ruleContext);
157+
allIdentifiers.UnionWith(blockStmtIdentifiers);
158+
159+
var potentiallyUnreferencedParameters = GetArgContextIdentifiers(ruleContext);
160+
allIdentifiers.UnionWith(potentiallyUnreferencedParameters);
161+
162+
//TODO: add module and global scope variableNames
163+
164+
return allIdentifiers;
165+
}
166+
private HashSet<string> GetBlockStmtIdentifiers(RuleContext ruleContext)
167+
{
168+
var blocks = GetBlockStmtContextsForContext(ruleContext);
169+
170+
return GetIdentifierNames(blocks);
171+
}
172+
private HashSet<string> GetArgContextIdentifiers(RuleContext ruleContext)
173+
{
174+
var args = GetArgContextsForContext(ruleContext);
175+
176+
return GetIdentifierNames(args);
177+
}
178+
private HashSet<string> GetIdentifierNames(IReadOnlyList<RuleContext> ruleContexts)
179+
{
180+
var identifiers = new HashSet<string>();
181+
foreach (RuleContext ruleContext in ruleContexts)
182+
{
183+
var identifiersForThisContext = GetIdentifierNames(ruleContext);
184+
identifiers.UnionWith(identifiersForThisContext);
185+
}
186+
return identifiers;
187+
}
188+
private HashSet<string> GetIdentifierNames(RuleContext ruleContext)
189+
{
190+
//Recursively work through the tree to get all IdentifierContexts
191+
var results = new HashSet<string>();
192+
var tokenValues = typeof(Tokens).GetFields().Select(item => item.GetValue(null)).Cast<string>().Select(item => item);
193+
var children = GetChildren(ruleContext);
194+
foreach(IParseTree child in children)
195+
{
196+
if(child is VBAParser.IdentifierContext)
197+
{
198+
var childName = Identifier.GetName((VBAParser.IdentifierContext)child);
199+
if (!tokenValues.Contains(childName))
200+
{
201+
results.Add(childName);
202+
}
203+
}
204+
else
205+
{
206+
if(!(child is TerminalNodeImpl))
207+
{
208+
results.UnionWith(GetIdentifierNames((RuleContext)child));
209+
}
210+
}
211+
}
212+
return results;
213+
}
214+
private List<IParseTree> GetChildren(RuleContext ruleCtx)
215+
{
216+
var result = new List<IParseTree>();
217+
for(int index = 0; index < ruleCtx.ChildCount; index++)
218+
{
219+
result.Add(ruleCtx.GetChild(index));
220+
}
221+
return result;
222+
}
223+
}
224+
}

0 commit comments

Comments
 (0)