/
FunctionReturnValueDiscardedInspection.cs
101 lines (94 loc) · 3.77 KB
/
FunctionReturnValueDiscardedInspection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using Antlr4.Runtime;
using Rubberduck.CodeAnalysis.Inspections.Abstract;
using Rubberduck.Parsing;
using Rubberduck.Parsing.Grammar;
using Rubberduck.Parsing.Symbols;
using Rubberduck.Parsing.VBA;
using Rubberduck.Parsing.VBA.DeclarationCaching;
using Rubberduck.Resources.Inspections;
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
{
/// <summary>
/// Warns when a user function's return value is not used at a call site.
/// </summary>
/// <why>
/// A 'Function' procedure normally means its return value to be captured and consumed by the calling code.
/// </why>
/// <example hasResult="true">
/// <module name="MyModule" type="Standard Module">
/// <![CDATA[
/// Public Sub DoSomething()
/// GetFoo ' return value is not captured
/// End Sub
///
/// Private Function GetFoo() As Long
/// GetFoo = 42
/// End Function
/// ]]>
/// </module>
/// </example>
/// <example hasResult="false">
/// <module name="MyModule" type="Standard Module">
/// <![CDATA[
/// Public Sub DoSomething()
/// Dim foo As Long
/// foo = GetFoo
/// End Sub
///
/// Private Function GetFoo() As Long
/// GetFoo = 42
/// End Function
/// ]]>
/// </module>
/// </example>
internal sealed class FunctionReturnValueDiscardedInspection : IdentifierReferenceInspectionBase
{
public FunctionReturnValueDiscardedInspection(IDeclarationFinderProvider declarationFinderProvider)
: base(declarationFinderProvider)
{
Severity = CodeInspectionSeverity.Suggestion;
}
protected override bool IsResultReference(IdentifierReference reference, DeclarationFinder finder)
{
return reference?.Declaration != null
&& reference.Declaration.IsUserDefined
&& !reference.IsAssignment
&& !reference.IsArrayAccess
&& !reference.IsInnerRecursiveDefaultMemberAccess
&& reference.Declaration.DeclarationType == DeclarationType.Function
&& IsCalledAsProcedure(reference.Context);
}
private static bool IsCalledAsProcedure(ParserRuleContext context)
{
var callStmt = context.GetAncestor<VBAParser.CallStmtContext>();
if (callStmt == null)
{
return false;
}
//If we are in an argument list, the value is used somewhere in defining the argument.
var argumentListParent = context.GetAncestor<VBAParser.ArgumentListContext>();
if (argumentListParent != null)
{
return false;
}
//Member accesses are parsed right-to-left, e.g. 'foo.Bar' is the parent of 'foo'.
//Thus, having a member access parent means that the return value is used somehow.
var ownFunctionCallExpression = context.Parent is VBAParser.MemberAccessExprContext methodCall
? methodCall
: context;
var memberAccessParent = ownFunctionCallExpression.GetAncestor<VBAParser.MemberAccessExprContext>();
if (memberAccessParent != null)
{
return false;
}
//If we are in an output list, the value is used somewhere in defining the argument.
var outputListParent = context.GetAncestor<VBAParser.OutputListContext>();
return outputListParent == null;
}
protected override string ResultDescription(IdentifierReference reference)
{
var functionName = reference.Declaration.QualifiedName.ToString();
return string.Format(InspectionResults.FunctionReturnValueDiscardedInspection, functionName);
}
}
}