Skip to content

Commit 159e089

Browse files
committed
Introduce inspections for indexed default member accesses
There are three versions: IndexedDefaultMemberAccessInspection for ordinary default member accesses, IndexedRecursiveDefaultMemberAccessInspection for those requiring a recursive default member resolution and IndexedUnboundDefaultMemberAccessInspection for those on Object and Variant variables.
1 parent 2803935 commit 159e089

17 files changed

+909
-4
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using Rubberduck.Inspections.Abstract;
2+
using Rubberduck.Resources.Inspections;
3+
using Rubberduck.Parsing.Symbols;
4+
using Rubberduck.Parsing.VBA;
5+
using Rubberduck.Inspections.Inspections.Extensions;
6+
using Rubberduck.Parsing.Grammar;
7+
using Rubberduck.Parsing.Inspections;
8+
9+
namespace Rubberduck.Inspections.Concrete
10+
{
11+
/// <summary>
12+
/// Identifies the use indexed default member accesses.
13+
/// </summary>
14+
/// <why>
15+
/// An indexed default member access hides away the actually called member.
16+
/// </why>
17+
/// <example hasResult="true">
18+
/// <![CDATA[
19+
/// Class1:
20+
///
21+
/// Public Function Foo(ByVal arg As Long) As Long
22+
/// Attibute VB_UserMemId = 0
23+
/// Foo = 42
24+
/// End Function
25+
///
26+
/// Module:
27+
///
28+
/// Public Sub DoSomething(ByVal arg As Class1)
29+
/// Dim bar As Variant
30+
/// bar = arg(23)
31+
/// End Sub
32+
/// ]]>
33+
/// </example>
34+
/// <example hasResult="false">
35+
/// <![CDATA[
36+
/// Class1:
37+
///
38+
/// Public Function Foo(ByVal arg As Long) As Long
39+
/// Attibute VB_UserMemId = 0
40+
/// Foo = 42
41+
/// End Function
42+
///
43+
/// Module:
44+
///
45+
/// Public Sub DoSomething(ByVal arg As Class1)
46+
/// Dim bar As Variant
47+
/// bar = arg.Foo(23)
48+
/// End Sub
49+
/// ]]>
50+
/// </example>
51+
public sealed class IndexedDefaultMemberAccessInspection : IdentifierReferenceInspectionBase
52+
{
53+
public IndexedDefaultMemberAccessInspection(RubberduckParserState state)
54+
: base(state)
55+
{
56+
Severity = CodeInspectionSeverity.Hint;
57+
}
58+
59+
protected override bool IsResultReference(IdentifierReference reference)
60+
{
61+
return reference.IsIndexedDefaultMemberAccess
62+
&& reference.DefaultMemberRecursionDepth == 1
63+
&& !(reference.Context is VBAParser.DictionaryAccessContext)
64+
&& !reference.IsIgnoringInspectionResultFor(AnnotationName);
65+
}
66+
67+
protected override string ResultDescription(IdentifierReference reference)
68+
{
69+
var expression = reference.IdentifierName;
70+
var defaultMember = reference.Declaration.QualifiedName.ToString();
71+
return string.Format(InspectionResults.IndexedDefaultMemberAccessInspection, expression, defaultMember);
72+
}
73+
}
74+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Rubberduck.Inspections.Abstract;
2+
using Rubberduck.Resources.Inspections;
3+
using Rubberduck.Parsing.Symbols;
4+
using Rubberduck.Parsing.VBA;
5+
using Rubberduck.Inspections.Inspections.Extensions;
6+
using Rubberduck.Parsing.Grammar;
7+
using Rubberduck.Parsing.Inspections;
8+
9+
namespace Rubberduck.Inspections.Concrete
10+
{
11+
/// <summary>
12+
/// Identifies the use indexed default member accesses that require a recursive default member resolution.
13+
/// </summary>
14+
/// <why>
15+
/// An indexed default member access hides away the actually called member. This is especially problematic if the corresponding parameterized default member is not on the interface of the object itself.
16+
/// </why>
17+
/// <example hasResult="true">
18+
/// <![CDATA[
19+
/// Public Sub DoSomething(ByVal arg As ADODB.Recordset)
20+
/// Dim bar As Variant
21+
/// bar = rst("MyField")
22+
/// End Sub
23+
/// ]]>
24+
/// </example>
25+
/// <example hasResult="false">
26+
/// <![CDATA[
27+
/// Public Sub DoSomething(ByVal arg As ADODB.Recordset)
28+
/// Dim bar As Variant
29+
/// bar = rst.Fields.Item("MyField")
30+
/// End Sub
31+
/// ]]>
32+
/// </example>
33+
public sealed class IndexedRecursiveDefaultMemberAccessInspection : IdentifierReferenceInspectionBase
34+
{
35+
public IndexedRecursiveDefaultMemberAccessInspection(RubberduckParserState state)
36+
: base(state)
37+
{
38+
Severity = CodeInspectionSeverity.Suggestion;
39+
}
40+
41+
protected override bool IsResultReference(IdentifierReference reference)
42+
{
43+
return reference.IsIndexedDefaultMemberAccess
44+
&& reference.DefaultMemberRecursionDepth > 1
45+
&& !(reference.Context is VBAParser.DictionaryAccessContext)
46+
&& !reference.IsIgnoringInspectionResultFor(AnnotationName);
47+
}
48+
49+
protected override string ResultDescription(IdentifierReference reference)
50+
{
51+
var expression = reference.IdentifierName;
52+
var defaultMember = reference.Declaration.QualifiedName.ToString();
53+
return string.Format(InspectionResults.IndexedRecursiveDefaultMemberAccessInspection, expression, defaultMember);
54+
}
55+
}
56+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections.Generic;
2+
using Rubberduck.Inspections.Abstract;
3+
using Rubberduck.Resources.Inspections;
4+
using Rubberduck.Parsing.Symbols;
5+
using Rubberduck.Parsing.VBA;
6+
using Rubberduck.Inspections.Inspections.Extensions;
7+
using Rubberduck.Parsing.Grammar;
8+
using Rubberduck.Parsing.Inspections;
9+
using Rubberduck.VBEditor;
10+
11+
namespace Rubberduck.Inspections.Concrete
12+
{
13+
/// <summary>
14+
/// Identifies the use indexed default member accesses for which the default member cannot be determined at compile time.
15+
/// </summary>
16+
/// <why>
17+
/// An indexed default member access hides away the actually called member. This is especially problematic if the default member cannot be determined from the declared type of the object.
18+
/// Should there not be a suitable default member at runtime, an error 438 'Object doesn't support this property or method' will be raised.
19+
/// </why>
20+
/// <example hasResult="true">
21+
/// <![CDATA[
22+
/// Public Sub DoSomething(ByVal arg As Object)
23+
/// Dim bar As Variant
24+
/// bar = rst("MyField")
25+
/// End Sub
26+
/// ]]>
27+
/// </example>
28+
/// <example hasResult="false">
29+
/// <![CDATA[
30+
/// Public Sub DoSomething(ByVal arg As Object)
31+
/// Dim bar As Variant
32+
/// bar = rst.Fields.Item("MyField")
33+
/// End Sub
34+
/// ]]>
35+
/// </example>
36+
public sealed class IndexedUnboundDefaultMemberAccessInspection : IdentifierReferenceInspectionBase
37+
{
38+
public IndexedUnboundDefaultMemberAccessInspection(RubberduckParserState state)
39+
: base(state)
40+
{
41+
Severity = CodeInspectionSeverity.Warning;
42+
}
43+
44+
protected override IEnumerable<IdentifierReference> ReferencesInModule(QualifiedModuleName module)
45+
{
46+
return DeclarationFinderProvider.DeclarationFinder.UnboundDefaultMemberAccesses(module);
47+
}
48+
49+
protected override bool IsResultReference(IdentifierReference reference)
50+
{
51+
return reference.IsIndexedDefaultMemberAccess
52+
&& !(reference.Context is VBAParser.DictionaryAccessContext)
53+
&& !reference.IsIgnoringInspectionResultFor(AnnotationName);
54+
}
55+
56+
protected override string ResultDescription(IdentifierReference reference)
57+
{
58+
var expression = reference.IdentifierName;
59+
return string.Format(InspectionResults.IndexedUnboundDefaultMemberAccessInspection, expression);
60+
}
61+
}
62+
}

Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private void AddDefaultMemberReference(
302302
bool isSetAssignment)
303303
{
304304
var callSiteContext = expression.LExpression.Context;
305-
var identifier = callSiteContext.GetText();
305+
var identifier = expression.Context.GetText();
306306
var selection = callSiteContext.GetSelection();
307307
var callee = expression.ReferencedDeclaration;
308308
expression.ReferencedDeclaration.AddReference(
@@ -331,7 +331,7 @@ private void AddUnboundDefaultMemberReference(
331331
bool isSetAssignment)
332332
{
333333
var callSiteContext = expression.LExpression.Context;
334-
var identifier = callSiteContext.GetText();
334+
var identifier = expression.Context.GetText();
335335
var selection = callSiteContext.GetSelection();
336336
var callee = expression.ReferencedDeclaration;
337337
var reference = new IdentifierReference(

Rubberduck.Resources/Inspections/InspectionInfo.Designer.cs

Lines changed: 27 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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,13 @@ Falls der Parameter 'null' sein kann, bitte dieses Auftreten ignorieren. 'null'
412412
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
413413
<value>Wenn ein Objekt mit einem Standardmember an einer Stelle verwendet wird, die nach einer Prozedur verlangt, wird implizit der Standardmember aufgerufen. Die ist wahrscheinlich nicht beabsichtigt, minder aber zu Mindest die Lesbarkeit des Programms.</value>
414414
</data>
415+
<data name="IndexedDefaultMemberAccessInspection" xml:space="preserve">
416+
<value>Zugriffe auf Standardmember verstecken, welcher Member aufgerufen wird. Auch wenn im Fall eines paramtrisierten Zugriffs ersichtlich ist, dass ein Zugriff erfolgt, so ist es in der Regel für die Lesbarkeit zuträglich explizit zu sein.</value>
417+
</data>
418+
<data name="IndexedRecursiveDefaultMemberAccessInspection" xml:space="preserve">
419+
<value>Zugriffe auf Standardmember verstecken, welcher Member aufgerufen wird. Auch wenn im Fall eines paramtrisierten Zugriffs ersichtlich ist, dass ein Zugriff erfolgt, so ist es in der Regel für die Lesbarkeit zuträglich explizit zu sein. Dies trifft besonders zu, wenn der Standardmember nicht auf dem Interface des Objekts selber zu finden ist uns stattdessen mittels einer Kette von Standrdmemberzugriffen erreicht werden muss.</value>
420+
</data>
421+
<data name="IndexedUnboundDefaultMemberAccessInspection" xml:space="preserve">
422+
<value>Zugriffe auf Standardmember verstecken, welcher Member aufgerufen wird. Auch wenn im Fall eines paramtrisierten Zugriffs ersichtlich ist, dass ein Zugriff erfolgt, so ist es in der Regel für die Lesbarkeit zuträglich explizit zu sein. Dies trifft insbesondere zu, wenn der Standardmember erst zur Laufzeit ermittelt werden kann. Sollte dann kein passender Standardmember existieren, kommt es zu einem Fehler 438 'Objekt unterstützt diese Eigenschaft oder Methode nicht'.</value>
423+
</data>
415424
</root>

Rubberduck.Resources/Inspections/InspectionInfo.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,4 +415,13 @@ If the parameter can be null, ignore this inspection result; passing a null valu
415415
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
416416
<value>Using an object with a default member in a place that requires a procedure leads to an implicit invocation of the default member. This is most likely unintentional and negatively affects readability.</value>
417417
</data>
418+
<data name="IndexedDefaultMemberAccessInspection" xml:space="preserve">
419+
<value>A default member access hides away which member is actually called. Although it is apparent that some call is made in the case of an indexed default member access being explicit is usually better for readability.</value>
420+
</data>
421+
<data name="IndexedRecursiveDefaultMemberAccessInspection" xml:space="preserve">
422+
<value>A default member access hides away which member is actually called. Although it is apparent that some call is made in the case of an indexed default member access being explicit is usually better for readability. This especially holds if the accessed default member is not on the interface of the object itself but has to be resolved via a chain of default member calls.</value>
423+
</data>
424+
<data name="IndexedUnboundDefaultMemberAccessInspection" xml:space="preserve">
425+
<value>A default member access hides away which member is actually called. Although it is apparent that some call is made in the case of an indexed default member access being explicit is usually better for readability. This is especially true if the default member cannot be determined at compile time. In addition, should there not be a suitable default member at runtime, an error 438 'Object doesn't support this property or method' will be raised.</value>
426+
</data>
418427
</root>

Rubberduck.Resources/Inspections/InspectionNames.Designer.cs

Lines changed: 27 additions & 0 deletions
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,4 +399,13 @@
399399
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
400400
<value>Objekt an Stell einer Prozedur verwendet</value>
401401
</data>
402+
<data name="IndexedDefaultMemberAccessInspection" xml:space="preserve">
403+
<value>Parametrisierter Zugriff auf einen Standardmember</value>
404+
</data>
405+
<data name="IndexedRecursiveDefaultMemberAccessInspection" xml:space="preserve">
406+
<value>Parametrisierter rekursiver Zugriff auf einen Standardmember</value>
407+
</data>
408+
<data name="IndexedUnboundDefaultMemberAccessInspection" xml:space="preserve">
409+
<value>Parametrisierter nicht gebundener Zugriff auf einen Standardmember</value>
410+
</data>
402411
</root>

Rubberduck.Resources/Inspections/InspectionNames.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,13 @@
419419
<data name="ObjectWhereProcedureIsRequiredInspection" xml:space="preserve">
420420
<value>Object used where a procedure is required</value>
421421
</data>
422+
<data name="IndexedDefaultMemberAccessInspection" xml:space="preserve">
423+
<value>Use of an indexed default member access</value>
424+
</data>
425+
<data name="IndexedRecursiveDefaultMemberAccessInspection" xml:space="preserve">
426+
<value>Use of an indexed recursive default member access</value>
427+
</data>
428+
<data name="IndexedUnboundDefaultMemberAccessInspection" xml:space="preserve">
429+
<value>Use of an indexed unbound default member access</value>
430+
</data>
422431
</root>

0 commit comments

Comments
 (0)