forked from rubberduck-vba/Rubberduck
-
Notifications
You must be signed in to change notification settings - Fork 12
/
NonReturningFunctionInspection.cs
153 lines (136 loc) · 6.1 KB
/
NonReturningFunctionInspection.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
using System.Linq;
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 about 'Function' and 'Property Get' procedures whose return value is not assigned.
/// </summary>
/// <why>
/// Both 'Function' and 'Property Get' accessors should always return something. Omitting the return assignment is likely a bug.
/// </why>
/// <example hasResult="true">
/// <module name="MyModule" type="Standard Module">
/// <![CDATA[
/// Public Function GetFoo() As Long
/// Dim foo As Long
/// foo = 42
/// 'function will always return 0
/// End Function
/// ]]>
/// </module>
/// </example>
/// <example hasResult="false">
/// <module name="MyModule" type="Standard Module">
/// <![CDATA[
/// Public Function GetFoo() As Long
/// Dim foo As Long
/// foo = 42
/// GetFoo = foo
/// End Function
/// ]]>
/// </module>
/// </example>
internal sealed class NonReturningFunctionInspection : DeclarationInspectionBase
{
public NonReturningFunctionInspection(IDeclarationFinderProvider declarationFinderProvider)
: base(declarationFinderProvider, DeclarationType.Function)
{}
protected override bool IsResultDeclaration(Declaration declaration, DeclarationFinder finder)
{
return declaration is ModuleBodyElementDeclaration member
&& !member.IsInterfaceMember
&& (IsReturningUserDefinedType(member)
&& !IsUserDefinedTypeAssigned(member)
|| !IsReturningUserDefinedType(member)
&& !IsAssigned(member, finder));
}
private bool IsAssigned(Declaration member, DeclarationFinder finder)
{
var inScopeIdentifierReferences = member.References
.Where(reference => reference.ParentScoping.Equals(member));
return inScopeIdentifierReferences
.Any(reference => reference.IsAssignment
|| IsAssignedByRefArgument(member, reference, finder));
}
private bool IsAssignedByRefArgument(Declaration enclosingProcedure, IdentifierReference reference, DeclarationFinder finder)
{
var argExpression = ImmediateArgumentExpressionContext(reference);
if (argExpression is null)
{
return false;
}
var argument = argExpression.GetAncestor<VBAParser.ArgumentContext>();
var parameter = finder.FindParameterOfNonDefaultMemberFromSimpleArgumentNotPassedByValExplicitly(argument, enclosingProcedure);
// note: not recursive, by design.
return parameter != null
&& (parameter.IsImplicitByRef || parameter.IsByRef)
&& parameter.References.Any(r => r.IsAssignment);
}
private static VBAParser.ArgumentExpressionContext ImmediateArgumentExpressionContext(IdentifierReference reference)
{
var context = reference.Context;
//The context is either already a simpleNameExprContext or an IdentifierValueContext used in a sub-rule of some other lExpression alternative.
var lExpressionNameContext = context is VBAParser.SimpleNameExprContext simpleName
? simpleName
: context.GetAncestor<VBAParser.LExpressionContext>();
//To be an immediate argument and, thus, assignable by ref, the structure must be argumentExpression -> expression -> lExpression.
return lExpressionNameContext?
.Parent?
.Parent as VBAParser.ArgumentExpressionContext;
}
private static bool IsReturningUserDefinedType(Declaration member)
{
return member.AsTypeDeclaration != null
&& member.AsTypeDeclaration.DeclarationType == DeclarationType.UserDefinedType;
}
private static bool IsUserDefinedTypeAssigned(Declaration member)
{
// ref. #2257:
// A function returning a UDT type shouldn't trip this inspection if
// at least one UDT member is assigned a value.
var block = member.Context.GetChild<VBAParser.BlockContext>(0);
var visitor = new FunctionReturnValueAssignmentLocator(member.IdentifierName);
var result = visitor.VisitBlock(block);
return result;
}
protected override string ResultDescription(Declaration declaration)
{
return string.Format(InspectionResults.NonReturningFunctionInspection, declaration.IdentifierName);
}
/// <summary>
/// A visitor that visits a member's body and returns <c>true</c> if any <c>LET</c> statement (assignment) is assigning the specified <c>name</c>.
/// </summary>
private class FunctionReturnValueAssignmentLocator : VBAParserBaseVisitor<bool>
{
private readonly string _name;
private bool _result;
public FunctionReturnValueAssignmentLocator(string name)
{
_name = name;
}
public override bool VisitBlock(VBAParser.BlockContext context)
{
base.VisitBlock(context);
return _result;
}
public override bool VisitLetStmt(VBAParser.LetStmtContext context)
{
var leftmost = context.lExpression().GetChild(0).GetText();
_result = _result || leftmost == _name;
return _result;
}
public override bool VisitSetStmt(VBAParser.SetStmtContext context)
{
var leftmost = context.lExpression().GetChild(0).GetText();
_result = _result || leftmost == _name;
return _result;
}
}
}
}