|
3 | 3 | using Rubberduck.CodeAnalysis.Inspections.Abstract; |
4 | 4 | using Rubberduck.CodeAnalysis.Inspections.Attributes; |
5 | 5 | using Rubberduck.Common; |
| 6 | +using Rubberduck.Parsing; |
6 | 7 | using Rubberduck.Parsing.Grammar; |
7 | 8 | using Rubberduck.Parsing.Symbols; |
8 | 9 | using Rubberduck.Parsing.VBA; |
@@ -59,41 +60,104 @@ public SheetAccessedUsingStringInspection(IDeclarationFinderProvider declaration |
59 | 60 | _projectsProvider = projectsProvider; |
60 | 61 | } |
61 | 62 |
|
62 | | - private static readonly string[] InterestingMembers = |
| 63 | + /// <summary> |
| 64 | + /// We're interested in both explicitly and implicitly bound retrievals from a Sheets collection. |
| 65 | + /// </summary> |
| 66 | + private static readonly string[] InterestingProperties = |
63 | 67 | { |
64 | | - "Worksheets", // gets a Sheets object containing Worksheet objects. |
65 | | - "Sheets", // gets a Sheets object containing all sheets (not just Worksheet sheets) in the qualifying workbook. |
66 | | - }; |
67 | | - |
68 | | - private static readonly string[] InterestingClasses = |
69 | | - { |
70 | | - "Workbook", // unqualified member call |
71 | | - "_Workbook", // qualified member call |
| 68 | + "Item", // explicit member call |
| 69 | + "_Default", // default member call (usually implicit) |
72 | 70 | }; |
73 | 71 |
|
74 | 72 | protected override IEnumerable<Declaration> ObjectionableDeclarations(DeclarationFinder finder) |
75 | 73 | { |
76 | 74 | if (!finder.TryFindProjectDeclaration("Excel", out var excel)) |
77 | 75 | { |
78 | | - return Enumerable.Empty<Declaration>(); |
| 76 | + // [RequiredHost] attribute puts this in "should not happen" territory. |
| 77 | + yield break; |
| 78 | + } |
| 79 | + var sheetsClass = (ModuleDeclaration)finder.FindClassModule("Sheets", excel, true); |
| 80 | + if (sheetsClass == null) |
| 81 | + { |
| 82 | + // [RequiredHost] attribute puts this in "should not happen" territory. |
| 83 | + yield break; |
79 | 84 | } |
80 | 85 |
|
81 | | - var relevantClasses = InterestingClasses |
82 | | - .Select(className => finder.FindClassModule(className, excel, true)) |
83 | | - .OfType<ModuleDeclaration>(); |
| 86 | + if (sheetsClass != null) |
| 87 | + { |
| 88 | + foreach (var property in sheetsClass.Members.OfType<PropertyDeclaration>()) |
| 89 | + { |
| 90 | + if (InterestingProperties.Any(name => name.Equals(property.IdentifierName, System.StringComparison.InvariantCultureIgnoreCase))) |
| 91 | + { |
| 92 | + yield return property; |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
84 | 97 |
|
85 | | - var relevantProperties = relevantClasses |
86 | | - .SelectMany(classDeclaration => classDeclaration.Members) |
87 | | - .OfType<PropertyDeclaration>() |
88 | | - .Where(member => InterestingMembers.Contains(member.IdentifierName)); |
| 98 | + private static ClassModuleDeclaration GetHostWorkbookDeclaration(DeclarationFinder finder) |
| 99 | + { |
| 100 | + var documentModuleQMNs = finder.AllModules.Where(m => m.ComponentType == ComponentType.Document); |
| 101 | + ClassModuleDeclaration result = null; |
| 102 | + foreach (var qmn in documentModuleQMNs) |
| 103 | + { |
| 104 | + var declaration = finder.ModuleDeclaration(qmn) as ClassModuleDeclaration; |
| 105 | + if (declaration.Supertypes.Any(t => t.IdentifierName.Equals("Workbook") && t.ProjectName == "Excel" && !t.IsUserDefined)) |
| 106 | + { |
| 107 | + result = declaration; |
| 108 | + break; |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + return result ?? throw new System.InvalidOperationException("Failed to find the host Workbook declaration."); |
| 113 | + } |
89 | 114 |
|
90 | | - return relevantProperties; |
| 115 | + private static ClassModuleDeclaration GetHostApplicationDeclaration(DeclarationFinder finder) |
| 116 | + { |
| 117 | + var result = finder.MatchName("Application").OfType<ClassModuleDeclaration>().FirstOrDefault(t => t.ProjectName == "Excel" && !t.IsUserDefined) as ClassModuleDeclaration; |
| 118 | + return result ?? throw new System.InvalidOperationException("Failed to find the host Application declaration."); |
91 | 119 | } |
92 | 120 |
|
93 | 121 | protected override (bool isResult, string properties) IsResultReferenceWithAdditionalProperties(IdentifierReference reference, DeclarationFinder finder) |
94 | 122 | { |
95 | | - var sheetNameArgumentLiteralExpressionContext = SheetNameArgumentLiteralExpressionContext(reference); |
| 123 | + if (reference.IdentifierName.Equals(Tokens.Me, System.StringComparison.InvariantCultureIgnoreCase)) |
| 124 | + { |
| 125 | + // if Me is a worksheet module, |
| 126 | + return (false, null); |
| 127 | + } |
96 | 128 |
|
| 129 | + var hostWorkbookDeclaration = GetHostWorkbookDeclaration(finder); |
| 130 | + |
| 131 | + var context = reference.Context as VBAParser.MemberAccessExprContext |
| 132 | + ?? reference.Context.Parent as VBAParser.MemberAccessExprContext |
| 133 | + ?? reference.Context.Parent.Parent as VBAParser.MemberAccessExprContext; |
| 134 | + |
| 135 | + if (context is VBAParser.MemberAccessExprContext memberAccess) |
| 136 | + { |
| 137 | + var appObjectDeclaration = GetHostApplicationDeclaration(finder); |
| 138 | + var isApplicationQualifier = appObjectDeclaration.References.Any(appRef => |
| 139 | + context.GetSelection().Contains(appRef.Selection) |
| 140 | + && appRef.QualifiedModuleName.Equals(reference.QualifiedModuleName)); |
| 141 | + |
| 142 | + if (isApplicationQualifier) |
| 143 | + { |
| 144 | + // Application.Sheets(...) is referring to the ActiveWorkbook, not necessarily ThisWorkbook. |
| 145 | + return (false, null); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + var isHostWorkbookQualifier = hostWorkbookDeclaration.References.Any(thisWorkbookRef => |
| 150 | + context.GetSelection().Contains(thisWorkbookRef.Selection) |
| 151 | + && thisWorkbookRef.QualifiedModuleName.Equals(reference.QualifiedModuleName)); |
| 152 | + |
| 153 | + var parentModule = finder.ModuleDeclaration(reference.QualifiedModuleName); |
| 154 | + if (!isHostWorkbookQualifier && parentModule is ProceduralModuleDeclaration) |
| 155 | + { |
| 156 | + // in a standard module the reference is against ActiveWorkbook unless it's explicitly against ThisWorkbook. |
| 157 | + return (false, null); |
| 158 | + } |
| 159 | + |
| 160 | + var sheetNameArgumentLiteralExpressionContext = SheetNameArgumentLiteralExpressionContext(reference); |
97 | 161 | if (sheetNameArgumentLiteralExpressionContext?.STRINGLITERAL() == null) |
98 | 162 | { |
99 | 163 | return (false, null); |
|
0 commit comments