Skip to content

Commit 7ae947c

Browse files
committed
Closes #2799
1 parent 9afe3bb commit 7ae947c

File tree

8 files changed

+145
-12
lines changed

8 files changed

+145
-12
lines changed

Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,23 +1199,34 @@ private List<Declaration> FindAllEventHandlers()
11991199

12001200
var handlers = DeclarationsWithType(DeclarationType.Procedure)
12011201
.Where(item =>
1202-
// class module built-in events
1203-
(item.ParentDeclaration.DeclarationType == DeclarationType.ClassModule && (
1204-
item.IdentifierName.Equals("Class_Initialize", StringComparison.InvariantCultureIgnoreCase) ||
1205-
item.IdentifierName.Equals("Class_Terminate", StringComparison.InvariantCultureIgnoreCase))) ||
1206-
// standard module built-in handlers (Excel specific):
1207-
(_hostApp != null &&
1208-
_hostApp.ApplicationName.Equals("Excel", StringComparison.InvariantCultureIgnoreCase) &&
1209-
item.ParentDeclaration.DeclarationType == DeclarationType.ProceduralModule && (
1210-
item.IdentifierName.Equals("auto_open", StringComparison.InvariantCultureIgnoreCase) ||
1211-
item.IdentifierName.Equals("auto_close", StringComparison.InvariantCultureIgnoreCase))))
1202+
IsVBAClassSpecificHandler(item) ||
1203+
IsHostSpecificHandler(item))
12121204
.Concat(
12131205
UserDeclarations(DeclarationType.Procedure)
12141206
.Where(item => handlerNames.Contains(item.IdentifierName))
12151207
)
12161208
.Concat(_handlersByWithEventsField.Value.AllValues())
12171209
.Concat(FindAllFormControlHandlers());
12181210
return handlers.ToList();
1211+
1212+
// Local functions to help break up the complex logic in finding built-in handlers
1213+
bool IsVBAClassSpecificHandler(Declaration item)
1214+
{
1215+
return item.ParentDeclaration.DeclarationType == DeclarationType.ClassModule && (
1216+
item.IdentifierName.Equals("Class_Initialize",
1217+
StringComparison.InvariantCultureIgnoreCase) ||
1218+
item.IdentifierName.Equals("Class_Terminate", StringComparison.InvariantCultureIgnoreCase));
1219+
}
1220+
1221+
bool IsHostSpecificHandler(Declaration item)
1222+
{
1223+
return _hostApp?.AutoMacroIdentifiers.Any(i =>
1224+
i.ComponentTypes.Any(t => t == item.QualifiedModuleName.ComponentType) &&
1225+
(item.Accessibility != Accessibility.Private || i.MayBePrivate) &&
1226+
(i.ModuleName == null || i.ModuleName == item.QualifiedModuleName.ComponentName) &&
1227+
(i.ProcedureName == null || i.ProcedureName == item.IdentifierName)
1228+
) ?? false;
1229+
}
12191230
}
12201231

12211232
/// <summary>

Rubberduck.VBEEditor/SafeComWrappers/Abstract/HostApplicationBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ public virtual bool TryOpenDocumentDesigner(QualifiedModuleName moduleName)
183183
return false;
184184
}
185185

186+
public virtual IEnumerable<HostAutoMacro> AutoMacroIdentifiers => null;
187+
186188
private static string GetName(IVBComponent component)
187189
{
188190
var name = string.Empty;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections.Generic;
2+
3+
namespace Rubberduck.VBEditor.SafeComWrappers.Abstract
4+
{
5+
/// <summary>
6+
/// Provide information about naming schemes that the host
7+
/// may use to identify a VBA procedure used as part of
8+
/// automatic execution via macros (e.g. Excel's auto_open or
9+
/// Word's AutoOpen
10+
/// </summary>
11+
public readonly struct HostAutoMacro
12+
{
13+
/// <summary>
14+
/// Enumerates all component types that the host may search for such auto macro
15+
/// </summary>
16+
public IEnumerable<ComponentType> ComponentTypes { get; }
17+
18+
/// <summary>
19+
/// Indicates whether host will require public access or may ignore the access modifier
20+
/// </summary>
21+
public bool MayBePrivate { get; }
22+
23+
/// <summary>
24+
/// If the host requires the module to have a specific name, this should be specified.
25+
/// Otherwise leave null. The procedure name must be specified in this case.
26+
/// </summary>
27+
public string ModuleName { get; }
28+
29+
/// <summary>
30+
/// If the host requires the procedure to have a specific name, this should be
31+
/// specified. Otherwise leave null. The module name must be specified in this case.
32+
/// </summary>
33+
public string ProcedureName { get; }
34+
35+
public HostAutoMacro(IEnumerable<ComponentType> componentTypes, bool mayBePrivate, string moduleName,
36+
string procedureName)
37+
{
38+
ComponentTypes = componentTypes;
39+
MayBePrivate = mayBePrivate;
40+
ModuleName = moduleName;
41+
ProcedureName = procedureName;
42+
}
43+
}
44+
}

Rubberduck.VBEEditor/SafeComWrappers/VB/Abstract/IHostApplication.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,14 @@ public interface IHostApplication : IDisposable
4747
/// <param name="moduleName">The qualified name of the document module</param>
4848
/// <returns>True if the document was opened in design view, false otherwise</returns>
4949
bool TryOpenDocumentDesigner(QualifiedModuleName moduleName);
50+
51+
/// <summary>
52+
/// Get a list of host-specific auto macro identifiers where
53+
/// applicable. The component type may be used as a bitmask
54+
/// for where host allows multiple component types. The names
55+
/// may be left null, indicating that any matches is accepted, but
56+
/// only one of either may be left null.
57+
/// </summary>
58+
IEnumerable<HostAutoMacro> AutoMacroIdentifiers { get; }
5059
}
5160
}
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1+
using System.Collections.Generic;
2+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
23

34
// ReSharper disable once CheckNamespace - Special dispensation due to conflicting file vs namespace priorities
45
namespace Rubberduck.VBEditor.SafeComWrappers.VBA
56
{
67
public class ExcelApp : HostApplicationBase<Microsoft.Office.Interop.Excel.Application>
78
{
8-
public ExcelApp(IVBE vbe) : base(vbe, "Excel", true) { }
9+
private IEnumerable<(ComponentType componentType, string moduleName, string procedureName)>
10+
_autoMacroIdentifiers;
11+
12+
public ExcelApp(IVBE vbe) : base(vbe, "Excel", true)
13+
{
14+
}
15+
16+
public override IEnumerable<HostAutoMacro> AutoMacroIdentifiers => new HostAutoMacro[]
17+
{
18+
new HostAutoMacro(new[] {ComponentType.StandardModule}, true, null, "auto_open"),
19+
new HostAutoMacro(new[] {ComponentType.StandardModule}, true, null, "auto_close")
20+
};
921
}
1022
}

Rubberduck.VBEditor.VBA/SafeComWrappers/Application/FallbackApp.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public HostDocument GetDocument(QualifiedModuleName moduleName)
1515
}
1616
public bool CanOpenDocumentDesigner(QualifiedModuleName moduleName) => false;
1717
public bool TryOpenDocumentDesigner(QualifiedModuleName moduleName) => false;
18+
public IEnumerable<HostAutoMacro> AutoMacroIdentifiers => null;
1819
public void Dispose() { }
1920
}
2021
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
2+
using System.Collections.Generic;
23

34
// ReSharper disable once CheckNamespace - Special dispensation due to conflicting file vs namespace priorities
45
namespace Rubberduck.VBEditor.SafeComWrappers.VBA
56
{
67
public class WordApp : HostApplicationBase<Microsoft.Office.Interop.Word.Application>
78
{
89
public WordApp(IVBE vbe) : base(vbe, "Word", true) { }
10+
11+
public override IEnumerable<HostAutoMacro> AutoMacroIdentifiers => new HostAutoMacro[]
12+
{
13+
//ref: https://docs.microsoft.com/en-us/office/vba/word/concepts/customizing-word/auto-macros
14+
new HostAutoMacro(new[] {ComponentType.StandardModule, ComponentType.Document}, false, null, "AutoExec"),
15+
new HostAutoMacro(new[] {ComponentType.StandardModule, ComponentType.Document}, false, null, "AutoNew"),
16+
new HostAutoMacro(new[] {ComponentType.StandardModule, ComponentType.Document}, false, null, "AutoOpen"),
17+
new HostAutoMacro(new[] {ComponentType.StandardModule, ComponentType.Document}, false, null, "AutoClose"),
18+
new HostAutoMacro(new[] {ComponentType.StandardModule, ComponentType.Document}, false, null, "AutoExit"),
19+
new HostAutoMacro(new[] {ComponentType.StandardModule}, false, "AutoExec", "Main"),
20+
new HostAutoMacro(new[] {ComponentType.StandardModule}, false, "AutoNew", "Main"),
21+
new HostAutoMacro(new[] {ComponentType.StandardModule}, false, "AutoOpen", "Main"),
22+
new HostAutoMacro(new[] {ComponentType.StandardModule}, false, "AutoClose", "Main"),
23+
new HostAutoMacro(new[] {ComponentType.StandardModule}, false, "AutoExit", "Main")
24+
};
925
}
1026
}

RubberduckTests/Inspections/ProcedureNotUsedInspectionTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
using System;
12
using System.Linq;
23
using System.Threading;
4+
using Moq;
35
using NUnit.Framework;
46
using Rubberduck.Inspections.Concrete;
57
using Rubberduck.Parsing.Symbols;
68
using Rubberduck.VBEditor.SafeComWrappers;
9+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
10+
using Rubberduck.VBEditor.SafeComWrappers.VBA;
711
using RubberduckTests.Mocks;
812

913
namespace RubberduckTests.Inspections
@@ -239,6 +243,40 @@ public void ProcedureNotUsed_NoResultForCasedClassTerminate()
239243
}
240244
}
241245

246+
[Test]
247+
[TestCase(ComponentType.StandardModule, "auto_open", "module1", "Excel")]
248+
[TestCase(ComponentType.StandardModule, "auto_close", "module1", "Excel")]
249+
[TestCase(ComponentType.StandardModule, "AutoExec", "module1", "Word")]
250+
[TestCase(ComponentType.StandardModule, "AutoNew", "module1", "Word")]
251+
[TestCase(ComponentType.StandardModule, "AutoOpen", "module1", "Word")]
252+
[TestCase(ComponentType.StandardModule, "AutoClose", "module1", "Word")]
253+
[TestCase(ComponentType.StandardModule, "AutoExit", "module1", "Word")]
254+
[TestCase(ComponentType.Document, "AutoExec", "module1", "Word")]
255+
[TestCase(ComponentType.Document, "AutoNew", "module1", "Word")]
256+
[TestCase(ComponentType.StandardModule, "Main", "AutoClose", "Word")]
257+
[TestCase(ComponentType.StandardModule, "Main", "AutoExit", "Word")]
258+
[Category("Inspections")]
259+
public void ProcedureNotUsed_NoResultForHostSpecificAutoMacros(ComponentType componentType, string macroName, string moduleName, string hostName)
260+
{
261+
//Input
262+
var inputCode =
263+
$@"Private Sub {macroName}()
264+
End Sub";
265+
266+
var vbe = MockVbeBuilder.BuildFromSingleModule(inputCode, moduleName, componentType, out _);
267+
vbe.Setup(v => v.HostApplication().AutoMacroIdentifiers).Returns(new []
268+
{
269+
new HostAutoMacro(new[] {componentType}, true, moduleName, macroName)
270+
});
271+
using (var state = MockParser.CreateAndParse(vbe.Object))
272+
{
273+
274+
var inspection = new ProcedureNotUsedInspection(state);
275+
var inspectionResults = inspection.GetInspectionResults(CancellationToken.None);
276+
277+
Assert.AreEqual(0, inspectionResults.Count());
278+
}
279+
}
242280

243281
[Test]
244282
[Category("Inspections")]

0 commit comments

Comments
 (0)