Skip to content

Commit 2384e28

Browse files
committed
Rename inspections and adjust resources according to comments to PR #5338
1 parent cc99a0b commit 2384e28

26 files changed

+243
-426
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Antlr4.Runtime;
4+
using Rubberduck.Inspections.Abstract;
5+
using Rubberduck.JunkDrawer.Extensions;
6+
using Rubberduck.Parsing;
7+
using Rubberduck.Parsing.Grammar;
8+
using Rubberduck.Resources.Inspections;
9+
using Rubberduck.Parsing.Symbols;
10+
using Rubberduck.Parsing.VBA;
11+
using Rubberduck.Parsing.VBA.DeclarationCaching;
12+
13+
namespace Rubberduck.Inspections.Concrete
14+
{
15+
/// <summary>
16+
/// Warns when a user function's return value is discarded at all its call sites.
17+
/// </summary>
18+
/// <why>
19+
/// A 'Function' procedure normally means its return value to be captured and consumed by the calling code.
20+
/// It's possible that not all call sites need the return value, but if the value is systematically discarded then this
21+
/// means the function is side-effecting, and thus should probably be a 'Sub' procedure instead.
22+
/// </why>
23+
/// <example hasResults="true">
24+
/// <![CDATA[
25+
/// Public Sub DoSomething()
26+
/// GetFoo ' return value is not captured
27+
/// End Sub
28+
///
29+
/// Private Function GetFoo() As Long
30+
/// GetFoo = 42
31+
/// End Function
32+
/// ]]>
33+
/// </example>
34+
/// <example hasResults="false">
35+
/// <![CDATA[
36+
/// Public Sub DoSomething()
37+
/// GetFoo ' return value is discarded
38+
/// End Sub
39+
///
40+
/// Public Sub DoSomethingElse()
41+
/// Dim foo As Long
42+
/// foo = GetFoo ' return value is captured
43+
/// End Sub
44+
///
45+
/// Private Function GetFoo() As Long
46+
/// GetFoo = 42
47+
/// End Function
48+
/// ]]>
49+
/// </example>
50+
public sealed class FunctionReturnValueAlwaysDiscardedInspection : DeclarationInspectionBase
51+
{
52+
public FunctionReturnValueAlwaysDiscardedInspection(RubberduckParserState state)
53+
:base(state, DeclarationType.Function)
54+
{}
55+
56+
protected override bool IsResultDeclaration(Declaration declaration)
57+
{
58+
if (!(declaration is ModuleBodyElementDeclaration moduleBodyElementDeclaration))
59+
{
60+
return false;
61+
}
62+
63+
//We only report the interface itself.
64+
if (moduleBodyElementDeclaration.IsInterfaceImplementation)
65+
{
66+
return false;
67+
}
68+
69+
var finder = DeclarationFinderProvider.DeclarationFinder;
70+
71+
if (moduleBodyElementDeclaration.IsInterfaceMember)
72+
{
73+
return IsInterfaceIssue(moduleBodyElementDeclaration, finder);
74+
}
75+
76+
return IsIssueItself(moduleBodyElementDeclaration);
77+
}
78+
79+
private bool IsIssueItself(ModuleBodyElementDeclaration declaration)
80+
{
81+
var procedureCallReferences = ProcedureCallReferences(declaration).ToHashSet();
82+
if (!procedureCallReferences.Any())
83+
{
84+
return false;
85+
}
86+
87+
return declaration.References
88+
.All(reference => procedureCallReferences.Contains(reference)
89+
|| reference.IsAssignment && IsReturnStatement(declaration, reference));
90+
}
91+
92+
private bool IsReturnStatement(Declaration function, IdentifierReference assignment)
93+
{
94+
return assignment.ParentScoping.Equals(function) && assignment.Declaration.Equals(function);
95+
}
96+
97+
private bool IsInterfaceIssue(ModuleBodyElementDeclaration declaration, DeclarationFinder finder)
98+
{
99+
if (!IsIssueItself(declaration))
100+
{
101+
return false;
102+
}
103+
104+
var implementations = finder.FindInterfaceImplementationMembers(declaration);
105+
return implementations.All(implementation => IsIssueItself(implementation)
106+
|| implementation.References.All(reference =>
107+
reference.IsAssignment
108+
&& IsReturnStatement(implementation, reference)));
109+
}
110+
111+
private static IEnumerable<IdentifierReference> ProcedureCallReferences(Declaration declaration)
112+
{
113+
return declaration.References
114+
.Where(IsProcedureCallReference);
115+
}
116+
117+
private static bool IsProcedureCallReference(IdentifierReference reference)
118+
{
119+
return reference?.Declaration != null
120+
&& !reference.IsAssignment
121+
&& !reference.IsArrayAccess
122+
&& !reference.IsInnerRecursiveDefaultMemberAccess
123+
&& IsCalledAsProcedure(reference.Context);
124+
}
125+
126+
private static bool IsCalledAsProcedure(ParserRuleContext context)
127+
{
128+
var callStmt = context.GetAncestor<VBAParser.CallStmtContext>();
129+
if (callStmt == null)
130+
{
131+
return false;
132+
}
133+
134+
//If we are in an argument list, the value is used somewhere in defining the argument.
135+
var argumentListParent = context.GetAncestor<VBAParser.ArgumentListContext>();
136+
if (argumentListParent != null)
137+
{
138+
return false;
139+
}
140+
141+
//Member accesses are parsed right-to-left, e.g. 'foo.Bar' is the parent of 'foo'.
142+
//Thus, having a member access parent means that the return value is used somehow.
143+
var ownFunctionCallExpression = context.Parent is VBAParser.MemberAccessExprContext methodCall
144+
? methodCall
145+
: context;
146+
var memberAccessParent = ownFunctionCallExpression.GetAncestor<VBAParser.MemberAccessExprContext>();
147+
return memberAccessParent == null;
148+
}
149+
150+
protected override string ResultDescription(Declaration declaration)
151+
{
152+
var functionName = declaration.QualifiedName.ToString();
153+
return string.Format(InspectionResults.FunctionReturnValueAlwaysDiscardedInspection, functionName);
154+
}
155+
}
156+
}

Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueNotUsedInspection.cs renamed to Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueDiscardedInspection.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using Antlr4.Runtime;
1+
using Antlr4.Runtime;
52
using Rubberduck.Inspections.Abstract;
6-
using Rubberduck.Inspections.Results;
73
using Rubberduck.Parsing;
84
using Rubberduck.Parsing.Grammar;
95
using Rubberduck.Parsing.Inspections;
10-
using Rubberduck.Parsing.Inspections.Abstract;
116
using Rubberduck.Resources.Inspections;
127
using Rubberduck.Parsing.Symbols;
138
using Rubberduck.Parsing.VBA;
14-
using Rubberduck.VBEditor;
159

1610
namespace Rubberduck.Inspections.Concrete
1711
{
@@ -44,10 +38,13 @@ namespace Rubberduck.Inspections.Concrete
4438
/// End Function
4539
/// ]]>
4640
/// </example>
47-
public sealed class FunctionReturnValueNotUsedInspection : IdentifierReferenceInspectionBase
41+
public sealed class FunctionReturnValueDiscardedInspection : IdentifierReferenceInspectionBase
4842
{
49-
public FunctionReturnValueNotUsedInspection(RubberduckParserState state)
50-
: base(state) { }
43+
public FunctionReturnValueDiscardedInspection(RubberduckParserState state)
44+
: base(state)
45+
{
46+
Severity = CodeInspectionSeverity.Suggestion;
47+
}
5148

5249
protected override bool IsResultReference(IdentifierReference reference)
5350
{
@@ -91,7 +88,7 @@ private static bool IsCalledAsProcedure(ParserRuleContext context)
9188
protected override string ResultDescription(IdentifierReference reference)
9289
{
9390
var functionName = reference.Declaration.QualifiedName.ToString();
94-
return string.Format(InspectionResults.FunctionReturnValueNotUsedInspection, functionName);
91+
return string.Format(InspectionResults.FunctionReturnValueDiscardedInspection, functionName);
9592
}
9693
}
9794
}

0 commit comments

Comments
 (0)