Skip to content

Commit 521146e

Browse files
committed
Address PR comments
Corrects GoTo LineNumber label handling.
1 parent 67dbca5 commit 521146e

File tree

2 files changed

+61
-62
lines changed

2 files changed

+61
-62
lines changed

Rubberduck.CodeAnalysis/Inspections/Concrete/AssignmentNotUsedInspection.cs

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,28 @@ private static IEnumerable<IdentifierReference> FindUnusedAssignmentReferences(D
106106
var tree = walker.GenerateTree(localVariable.ParentScopeDeclaration.Context, localVariable);
107107

108108
var allAssignmentsAndReferences = tree.Nodes(new[] { typeof(AssignmentNode), typeof(ReferenceNode) })
109-
.Where(node => localVariable.References.Contains(node.Reference));
109+
.Where(node => localVariable.References.Contains(node.Reference))
110+
.ToList();
110111

111112
var unusedAssignmentNodes = allAssignmentsAndReferences.Any(n => n is ReferenceNode)
112113
? FindUnusedAssignmentNodes(tree, localVariable, allAssignmentsAndReferences)
113114
: allAssignmentsAndReferences.OfType<AssignmentNode>();
114115

115-
return unusedAssignmentNodes.Except(FindDescendantsOfNeverFlagNodeTypes(unusedAssignmentNodes))
116+
return unusedAssignmentNodes.Where(n => !IsDescendentOfNeverFlagNode(n))
116117
.Select(n => n.Reference);
117118
}
118119

119120
private static IEnumerable<AssignmentNode> FindUnusedAssignmentNodes(INode node, Declaration localVariable, IEnumerable<INode> allAssignmentsAndReferences)
120121
{
121122
var assignmentExprNodes = node.Nodes(new[] { typeof(AssignmentExpressionNode) })
122-
.Where(n => localVariable.References.Contains(n.Children.FirstOrDefault()?.Reference));
123+
.Where(n => localVariable.References.Contains(n.Children.FirstOrDefault()?.Reference))
124+
.ToList();
123125

124126
var usedAssignments = new List<AssignmentNode>();
125127
foreach (var refNode in allAssignmentsAndReferences.OfType<ReferenceNode>().Reverse())
126128
{
127-
var assignmentExprNodesWithReference = assignmentExprNodes.Where(n => n.Nodes(new[] { typeof(ReferenceNode) })
129+
var assignmentExprNodesWithReference = assignmentExprNodes
130+
.Where(n => n.Nodes(new[] { typeof(ReferenceNode) })
128131
.Contains(refNode));
129132

130133
var assignmentsPrecedingReference = assignmentExprNodesWithReference.Any()
@@ -144,30 +147,14 @@ private static IEnumerable<AssignmentNode> FindUnusedAssignmentNodes(INode node,
144147
.Except(usedAssignments);
145148
}
146149

147-
private static IEnumerable<AssignmentNode> FindDescendantsOfNeverFlagNodeTypes(IEnumerable<AssignmentNode> flaggedAssignments)
150+
private static bool IsDescendentOfNeverFlagNode(AssignmentNode assignment)
148151
{
149-
var filteredResults = new List<AssignmentNode>();
150-
151-
foreach (var assignment in flaggedAssignments)
152-
{
153-
if (assignment.TryGetAncestorNode<BranchNode>(out _))
154-
{
155-
filteredResults.Add(assignment);
156-
}
157-
if (assignment.TryGetAncestorNode<LoopNode>(out _))
158-
{
159-
filteredResults.Add(assignment);
160-
}
161-
}
162-
return filteredResults;
152+
return assignment.TryGetAncestorNode<BranchNode>(out _)
153+
|| assignment.TryGetAncestorNode<LoopNode>(out _);
163154
}
164155

165156
private static bool IsAssignmentOfNothing(IdentifierReference reference)
166157
{
167-
if (reference.Context.Parent is VBAParser.SetStmtContext setStmtContext2)
168-
{
169-
var test = setStmtContext2.expression();
170-
}
171158
return reference.IsSetAssignment
172159
&& reference.Context.Parent is VBAParser.SetStmtContext setStmtContext
173160
&& setStmtContext.expression().GetText().Equals(Tokens.Nothing);
@@ -179,25 +166,25 @@ private static bool IsAssignmentOfNothing(IdentifierReference reference)
179166
/// </summary>
180167
/// <remarks>
181168
/// Filters Assignment references that meet the following conditions:
182-
/// 1. Precedes a GoTo or Resume statement that branches execution to a line before the
183-
/// assignment reference, and
169+
/// 1. Reference precedes a GoTo or Resume statement that branches execution to a line before the
170+
/// assignment reference, AND
184171
/// 2. A non-assignment reference is present on a line that is:
185-
/// a) At or below the start of the execution branch, and
172+
/// a) At or below the start of the execution branch, AND
186173
/// b) Above the next ExitStatement line (if one exists) or the end of the procedure
187174
/// </remarks>
188175
private static bool IsPotentiallyUsedViaJump(IdentifierReference resultCandidate, DeclarationFinder finder)
189176
{
190177
if (!resultCandidate.Declaration.References.Any(rf => !rf.IsAssignment)) { return false; }
191178

192-
var labelIdLineNumberPairs = finder.DeclarationsWithType(DeclarationType.LineLabel)
179+
var labelIdLineNumberPairs = finder.Members(resultCandidate.QualifiedModuleName, DeclarationType.LineLabel)
193180
.Where(label => resultCandidate.ParentScoping.Equals(label.ParentDeclaration))
194-
.Select(lbl => (lbl.IdentifierName, lbl.Context.Start.Line));
181+
.ToDictionary(key => key.IdentifierName, v => v.Context.Start.Line);
195182

196183
return JumpStmtPotentiallyUsesVariable<VBAParser.GoToStmtContext>(resultCandidate, labelIdLineNumberPairs)
197184
|| JumpStmtPotentiallyUsesVariable<VBAParser.ResumeStmtContext>(resultCandidate, labelIdLineNumberPairs);
198185
}
199186

200-
private static bool JumpStmtPotentiallyUsesVariable<T>(IdentifierReference resultCandidate, IEnumerable<(string IdentifierName, int Line)> labelIdLineNumberPairs) where T : ParserRuleContext
187+
private static bool JumpStmtPotentiallyUsesVariable<T>(IdentifierReference resultCandidate, Dictionary<string,int> labelIdLineNumberPairs) where T: ParserRuleContext
201188
{
202189
if (TryGetRelevantJumpContext<T>(resultCandidate, out var jumpStmt))
203190
{
@@ -210,25 +197,27 @@ private static bool JumpStmtPotentiallyUsesVariable<T>(IdentifierReference resul
210197
private static bool TryGetRelevantJumpContext<T>(IdentifierReference resultCandidate, out T ctxt) where T : ParserRuleContext
211198
{
212199
ctxt = resultCandidate.ParentScoping.Context.GetDescendents<T>()
213-
.Where(sc => sc.Start.Line > resultCandidate.Context.Start.Line
214-
|| (sc.Start.Line == resultCandidate.Context.Start.Line
215-
&& sc.Start.Column > resultCandidate.Context.Start.Column))
216-
.OrderBy(sc => sc.Start.Line - resultCandidate.Context.Start.Line)
217-
.ThenBy(sc => sc.Start.Column - resultCandidate.Context.Start.Column)
200+
.Where(descendent => descendent.GetSelection() > resultCandidate.Selection)
201+
.OrderBy(descendent => descendent.GetSelection())
218202
.FirstOrDefault();
219203
return ctxt != null;
220204
}
221205

222-
private static bool IsPotentiallyUsedAssignment<T>(T jumpContext, IdentifierReference resultCandidate, IEnumerable<(string, int)> labelIdLineNumberPairs)
206+
private static bool IsPotentiallyUsedAssignment<T>(T jumpContext, IdentifierReference resultCandidate, Dictionary<string, int> labelIdLineNumberPairs) where T : ParserRuleContext
223207
{
224208
int? executionBranchLine = null;
225-
if (jumpContext is VBAParser.GoToStmtContext gotoCtxt)
226-
{
227-
executionBranchLine = DetermineLabeledExecutionBranchLine(gotoCtxt.expression().GetText(), labelIdLineNumberPairs);
228-
}
229-
else
209+
210+
switch (jumpContext)
230211
{
231-
executionBranchLine = DetermineResumeStmtExecutionBranchLine(jumpContext as VBAParser.ResumeStmtContext, resultCandidate, labelIdLineNumberPairs);
212+
case VBAParser.GoToStmtContext gotoStmt:
213+
executionBranchLine = labelIdLineNumberPairs[gotoStmt.expression().GetText()];
214+
break;
215+
case VBAParser.ResumeStmtContext resume:
216+
executionBranchLine = DetermineResumeStmtExecutionBranchLine(resume, resultCandidate, labelIdLineNumberPairs);
217+
break;
218+
default:
219+
executionBranchLine = null;
220+
break;
232221
}
233222

234223
return executionBranchLine.HasValue
@@ -256,49 +245,57 @@ private static bool AssignmentIsUsedPriorToExitStmts(IdentifierReference resultC
256245
return !(sortedContextsAfterBranch.FirstOrDefault() is VBAParser.ExitStmtContext);
257246
}
258247

259-
private static int? DetermineResumeStmtExecutionBranchLine(VBAParser.ResumeStmtContext resumeStmt, IdentifierReference resultCandidate, IEnumerable<(string IdentifierName, int Line)> labelIdLineNumberPairs)
248+
private static int? DetermineResumeStmtExecutionBranchLine(VBAParser.ResumeStmtContext resumeStmt, IdentifierReference resultCandidate, Dictionary<string, int> labelIdLineNumberPairs)
260249
{
261250
var onErrorGotoLabelToLineNumber = resultCandidate.ParentScoping.Context.GetDescendents<VBAParser.OnErrorStmtContext>()
262-
.Where(errorStmtCtxt => !errorStmtCtxt.expression().GetText().Equals("0"))
251+
.Where(errorStmtCtxt => IsBranchingOnErrorGoToLabel(errorStmtCtxt))
263252
.ToDictionary(k => k.expression()?.GetText() ?? "No Label", v => v.Start.Line);
264253

265254
var errorHandlerLabelsAndLines = labelIdLineNumberPairs
266-
.Where(pair => onErrorGotoLabelToLineNumber.ContainsKey(pair.IdentifierName));
255+
.Where(pair => onErrorGotoLabelToLineNumber.ContainsKey(pair.Key));
267256

268257
//Labels must be located at the start of a line.
269258
//If the resultCandidate line precedes all error handling related labels,
270259
//a Resume statement cannot be invoked (successfully) for the resultCandidate
271-
if (!errorHandlerLabelsAndLines.Any(s => s.Line <= resultCandidate.Context.Start.Line))
260+
if (!errorHandlerLabelsAndLines.Any(kvp => kvp.Value <= resultCandidate.Context.Start.Line))
272261
{
273262
return null;
274263
}
275264

276-
var expression = resumeStmt.expression()?.GetText();
265+
var resumeStmtExpression = resumeStmt.expression()?.GetText();
277266

278267
//For Resume and Resume Next, expression() is null
279-
if (string.IsNullOrEmpty(expression))
268+
if (string.IsNullOrEmpty(resumeStmtExpression))
280269
{
281-
//Get errorHandlerLabel for the Resume statement
282-
string errorHandlerLabel = errorHandlerLabelsAndLines
283-
.Where(pair => resumeStmt.Start.Line >= pair.Line)
284-
.OrderBy(pair => resumeStmt.Start.Line - pair.Line)
285-
.Select(pair => pair.IdentifierName)
270+
var errorHandlerLabelForTheResumeStatement = errorHandlerLabelsAndLines
271+
.Where(kvp => resumeStmt.Start.Line >= kvp.Value)
272+
.OrderBy(kvp => resumeStmt.Start.Line - kvp.Value)
273+
.Select(kvp => kvp.Key)
286274
.FirstOrDefault();
287275

288276
//Since the execution branch line for Resume and Resume Next statements
289277
//is indeterminant by static analysis, the On***GoTo statement
290278
//is used as the execution branch line
291-
return onErrorGotoLabelToLineNumber[errorHandlerLabel];
279+
return onErrorGotoLabelToLineNumber[errorHandlerLabelForTheResumeStatement];
292280
}
293281
//Resume <label>
294-
return DetermineLabeledExecutionBranchLine(expression, labelIdLineNumberPairs);
282+
return labelIdLineNumberPairs[resumeStmtExpression];
295283
}
296284

297-
private static int DetermineLabeledExecutionBranchLine(string expression, IEnumerable<(string IdentifierName, int Line)> IDandLinePairs)
298-
=> int.TryParse(expression, out var parsedLineNumber)
299-
? parsedLineNumber
300-
: IDandLinePairs.Single(v => v.IdentifierName.Equals(expression)).Line;
285+
private static bool IsBranchingOnErrorGoToLabel(VBAParser.OnErrorStmtContext errorStmtCtxt)
286+
{
287+
var label = errorStmtCtxt.expression()?.GetText();
288+
if (string.IsNullOrEmpty(label))
289+
{
290+
return false;
291+
}
292+
//The VBE will complain about labels other than:
293+
//1. Numerics less than int.MaxValue (VBA: 'Long' max value). '0' returns false because it cause a branch
294+
//2. Or, Any alphanumeric string beginning with a letter (VBE or the Debugger will choke on special characters, spaces, etc).
295+
return !(int.TryParse(label, out var numberLabel) && numberLabel <= 0);
296+
}
301297

298+
//TODO: Add IsStatic member to VariableDeclaration
302299
private static bool IsStatic(Declaration declaration)
303300
{
304301
var ctxt = declaration.Context.GetAncestor<VBAParser.VariableStmtContext>();

RubberduckTests/Inspections/AssignmentNotUsedInspectionTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,8 @@ private static string TestFailureMsg(IEnumerable<IInspectionResult> results, par
423423
//https://github.com/rubberduck-vba/Rubberduck/issues/5456
424424
[TestCase("Resume CleanExit")]
425425
[TestCase("GoTo CleanExit")]
426-
[TestCase("Resume 8")] //Inverse = ratio
427-
[TestCase("GoTo 8")] //Inverse = ratio
426+
[TestCase("Resume 850")]
427+
[TestCase("GoTo 850")]
428428
public void IgnoresAssignmentWhereUsedByJumpStatement(string statement)
429429
{
430430
string code =
@@ -435,6 +435,7 @@ Dim ratio As Double
435435
On Error Goto ErrorHandler
436436
ratio = 1# / value
437437
CleanExit:
438+
850:
438439
Inverse = ratio
439440
Exit Function
440441
ErrorHandler:
@@ -449,8 +450,8 @@ End Function
449450

450451
[TestCase("Resume CleanExit")]
451452
[TestCase("GoTo CleanExit")]
452-
[TestCase("Resume 8")] //Inverse = ratio
453-
[TestCase("GoTo 8")] //Inverse = ratio
453+
[TestCase("Resume 850")]
454+
[TestCase("GoTo 850")]
454455
public void IgnoresAssignmentWhereUsedByJumpStatement_JumpOnSameLineAsAssignment(string statement)
455456
{
456457
string code =
@@ -461,6 +462,7 @@ Dim ratio As Double
461462
On Error Goto ErrorHandler
462463
ratio = 1# / value
463464
CleanExit:
465+
850:
464466
Inverse = ratio
465467
Exit Function
466468
ErrorHandler:

0 commit comments

Comments
 (0)