Skip to content

Commit 2ce14ae

Browse files
committed
Add ObjectWhereProcedureIsRequiredInspection
1 parent 9d78450 commit 2ce14ae

11 files changed

+631
-3
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Rubberduck.Inspections.Abstract;
4+
using Rubberduck.Resources.Inspections;
5+
using Rubberduck.Parsing.Symbols;
6+
using Rubberduck.Parsing.VBA;
7+
using Rubberduck.Inspections.Inspections.Extensions;
8+
using Rubberduck.Inspections.Results;
9+
using Rubberduck.Parsing.Grammar;
10+
using Rubberduck.Parsing.Inspections;
11+
using Rubberduck.Parsing.Inspections.Abstract;
12+
using Rubberduck.Parsing.VBA.DeclarationCaching;
13+
using Rubberduck.VBEditor;
14+
15+
namespace Rubberduck.Inspections.Concrete
16+
{
17+
/// <summary>
18+
/// Identifies places in which an object is used but a procedure is required and a default member exists on the object.
19+
/// </summary>
20+
/// <why>
21+
/// Providing an object in a place in which a procedure is required leads to a call to the objects default member.
22+
/// This behavior is not obvious and most likely an error.
23+
/// </why>
24+
/// <example hasResult="true">
25+
/// <![CDATA[
26+
/// Class1:
27+
///
28+
/// Public Function Foo() As Long
29+
/// Attibute VB_UserMemId = 0
30+
/// Foo = 42
31+
/// End Function
32+
///
33+
/// Module:
34+
///
35+
/// Public Sub DoSomething(ByVal arg As Class1)
36+
/// arg
37+
/// End Sub
38+
/// ]]>
39+
/// </example>
40+
/// <example hasResult="false">
41+
/// <![CDATA[
42+
/// Class1:
43+
///
44+
/// Public Function Foo() As Long
45+
/// Attibute VB_UserMemId = 0
46+
/// Foo = 42
47+
/// End Function
48+
///
49+
/// Module:
50+
///
51+
/// Public Sub DoSomething(ByVal arg As Class1)
52+
/// arg.Foo
53+
/// End Sub
54+
/// ]]>
55+
/// </example>
56+
public sealed class ObjectWhereProcedureIsRequiredInspection : InspectionBase
57+
{
58+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
59+
60+
public ObjectWhereProcedureIsRequiredInspection(RubberduckParserState state)
61+
: base(state)
62+
{
63+
_declarationFinderProvider = state;
64+
Severity = CodeInspectionSeverity.Warning;
65+
}
66+
67+
protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
68+
{
69+
var results = new List<IInspectionResult>();
70+
foreach (var moduleDeclaration in State.DeclarationFinder.UserDeclarations(DeclarationType.Module))
71+
{
72+
if (moduleDeclaration == null || moduleDeclaration.IsIgnoringInspectionResultFor(AnnotationName))
73+
{
74+
continue;
75+
}
76+
77+
var module = moduleDeclaration.QualifiedModuleName;
78+
results.AddRange(DoGetInspectionResults(module));
79+
}
80+
81+
return results;
82+
}
83+
84+
private IEnumerable<IInspectionResult> DoGetInspectionResults(QualifiedModuleName module)
85+
{
86+
var finder = _declarationFinderProvider.DeclarationFinder;
87+
return BoundInspectionResults(module, finder)
88+
.Concat(UnboundInspectionResults(module, finder));
89+
}
90+
91+
private IEnumerable<IInspectionResult> BoundInspectionResults(QualifiedModuleName module, DeclarationFinder finder)
92+
{
93+
var objectionableReferences = finder
94+
.IdentifierReferences(module)
95+
.Where(IsResultReference);
96+
97+
return objectionableReferences
98+
.Select(reference => BoundInspectionResult(reference, _declarationFinderProvider))
99+
.ToList();
100+
}
101+
102+
private bool IsResultReference(IdentifierReference reference)
103+
{
104+
return reference.IsProcedureCoercion
105+
&& !reference.IsIgnoringInspectionResultFor(AnnotationName);
106+
}
107+
108+
private IInspectionResult BoundInspectionResult(IdentifierReference reference, IDeclarationFinderProvider declarationFinderProvider)
109+
{
110+
return new IdentifierReferenceInspectionResult(
111+
this,
112+
BoundResultDescription(reference),
113+
declarationFinderProvider,
114+
reference);
115+
}
116+
117+
private string BoundResultDescription(IdentifierReference reference)
118+
{
119+
var expression = reference.IdentifierName;
120+
var defaultMember = reference.Declaration.QualifiedName.ToString();
121+
return string.Format(InspectionResults.ObjectWhereProcedureIsRequiredInspection, expression, defaultMember);
122+
}
123+
124+
private IEnumerable<IInspectionResult> UnboundInspectionResults(QualifiedModuleName module, DeclarationFinder finder)
125+
{
126+
var objectionableReferences = finder
127+
.UnboundDefaultMemberAccesses(module)
128+
.Where(IsResultReference);
129+
130+
return objectionableReferences
131+
.Select(reference => UnboundInspectionResult(reference, _declarationFinderProvider))
132+
.ToList();
133+
}
134+
135+
private IInspectionResult UnboundInspectionResult(IdentifierReference reference, IDeclarationFinderProvider declarationFinderProvider)
136+
{
137+
var result = new IdentifierReferenceInspectionResult(
138+
this,
139+
UnboundResultDescription(reference),
140+
declarationFinderProvider,
141+
reference);
142+
result.Properties.DisableFixes = "ExpandDefaultMemberQuickFix";
143+
return result;
144+
}
145+
146+
private string UnboundResultDescription(IdentifierReference reference)
147+
{
148+
var expression = reference.IdentifierName;
149+
return string.Format(InspectionResults.ObjectWhereProcedureIsRequiredInspection_Unbound, expression);
150+
}
151+
}
152+
}

Rubberduck.Resources/Inspections/InspectionInfo.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/Inspections/InspectionInfo.de.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,7 @@ Falls der Parameter 'null' sein kann, bitte dieses Auftreten ignorieren. 'null'
409409
<data name="UseOfUnboundBangNotationInspection" xml:space="preserve">
410410
<value>Die Ausrufezeichennotation erweckt den Eindruck, dass es sich um einen Zugriff handelt, der Typchecks unterliegt. Allerdings handelt es sich lediglich um einen Zugriff auf den Standardmember des Objekts, auf das sie angewendet wird, bei dem das Argument als Zeichenkette übergeben wird. Dies ist besonders verwirrend, wenn der Standardmember nicht zum Zeitpunkt des Kompilierens ermittelt werden kann.</value>
411411
</data>
412+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
413+
<value>Wenn ein Objekt mit einem Standardmember an einer Stelle verwendet wird, die nach einer Prozedur verlangt, wird der Standardmember aufgerufen. Die ist wahrscheinlich ein Fehler, zu Mindest aber nicht offensichtlich.</value>
414+
</data>
412415
</root>

Rubberduck.Resources/Inspections/InspectionInfo.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,7 @@ If the parameter can be null, ignore this inspection result; passing a null valu
412412
<data name="UseOfUnboundBangNotationInspection" xml:space="preserve">
413413
<value>Bang notation, formally known as dictionary access expression, looks like it is strongly typed. However, it is actually a stringly-typed access to the parameterized default member of the object it is used on. This is especially misleading the default member cannot be determined at compile time.</value>
414414
</data>
415+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
416+
<value>Using an object with a default member in a place that requires a procedure leads to a call to the default member. This is most likely an error, but at least not obvious.</value>
417+
</data>
415418
</root>

Rubberduck.Resources/Inspections/InspectionNames.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/Inspections/InspectionNames.de.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@
382382
<value>Objekt statt Wert verwendet</value>
383383
</data>
384384
<data name="ProcedureRequiredInspection" xml:space="preserve">
385-
<value>Objekt statt Prozedur verwendet</value>
385+
<value>Objekt ohne Standardmember statt Prozedur verwendet</value>
386386
</data>
387387
<data name="DefaultMemberRequiredInspection" xml:space="preserve">
388388
<value>Standardmemberzugriff ohne Standardmember</value>
@@ -396,4 +396,7 @@
396396
<data name="UseOfUnboundBangNotationInspection" xml:space="preserve">
397397
<value>Verwendung nicht gebundener Ausrufezeichennotation</value>
398398
</data>
399+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
400+
<value>Objekt an Stell einer Prozedur verwendet</value>
401+
</data>
399402
</root>

Rubberduck.Resources/Inspections/InspectionNames.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@
402402
<value>Object used where a value is required</value>
403403
</data>
404404
<data name="ProcedureRequiredInspection" xml:space="preserve">
405-
<value>Object used where a procedure is required</value>
405+
<value>Object without default member used where a procedure is required</value>
406406
</data>
407407
<data name="DefaultMemberRequiredInspection" xml:space="preserve">
408408
<value>Indexed default member access without default member</value>
@@ -416,4 +416,7 @@
416416
<data name="UseOfUnboundBangNotationInspection" xml:space="preserve">
417417
<value>Use of unbound bang notation</value>
418418
</data>
419+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
420+
<value>Object used where a procedure is required</value>
421+
</data>
419422
</root>

Rubberduck.Resources/Inspections/InspectionResults.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/Inspections/InspectionResults.de.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,10 @@ In Memoriam, 1972-2018</value>
419419
<data name="UseOfUnboundBangNotationInspection" xml:space="preserve">
420420
<value>Der Ausdruck '{0}' verwendet nicht gebundene Ausrufezeichennotation.</value>
421421
</data>
422+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
423+
<value>Der Ausdruck '{0}' wird an einer Stelle verwendet, die nach einer Prozedur verlangt, was zu einem Aufruf des Standardmembers '{1}' führt.</value>
424+
</data>
425+
<data name="ObjectWhereProcedureIsRequiredInspection_Unbound" xml:space="preserve">
426+
<value>Der Ausdruck '{0}' wird an einer Stelle verwendet, die nach einer Prozedur verlangt, was zu einem Aufruf eines Standardmembers führt, welcher erst zur Laufzeit bestimmt werden kann.</value>
427+
</data>
422428
</root>

Rubberduck.Resources/Inspections/InspectionResults.resx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,4 +470,12 @@ In memoriam, 1972-2018</value>
470470
<value>The expression '{0}' uses an unbound bang operator.</value>
471471
<comment>{0} expression</comment>
472472
</data>
473+
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
474+
<value>The expression '{0}' is used in a context that requires a procedure, which leads to a call to the default member '{1}'.</value>
475+
<comment>{0} expression; {1} default member</comment>
476+
</data>
477+
<data name="ObjectWhereProcedureIsRequiredInspection_Unbound" xml:space="preserve">
478+
<value>The expression '{0}' is used in a context that requires a procedure, which leads to a call to a default member that cannot be determined at compile time.</value>
479+
<comment>{0} expression</comment>
480+
</data>
473481
</root>

0 commit comments

Comments
 (0)