Skip to content

Commit e75127a

Browse files
committed
Made FindModuleAnnotations independent of the VBE
The DeclarationSymbolsListener previously relied on the VBE to determine the declarations section, in which we allow the module annotations to be. Unfortunately, it is often rather counterintuitive how the VBE determines this section. Thus, this PR introduces our own consistent logic.
1 parent b2aa796 commit e75127a

File tree

2 files changed

+83
-13
lines changed

2 files changed

+83
-13
lines changed

Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class DeclarationSymbolsListener : VBAParserBaseListener
2121
private Declaration _currentScopeDeclaration;
2222
private Declaration _parentDeclaration;
2323

24-
private readonly IEnumerable<IAnnotation> _annotations;
24+
private readonly ICollection<IAnnotation> _annotations;
2525
private readonly IDictionary<(string scopeIdentifier, DeclarationType scopeType), Attributes> _attributes;
2626
private readonly IDictionary<(string scopeIdentifier, DeclarationType scopeType), ParserRuleContext> _membersAllowingAttributes;
2727

@@ -40,7 +40,7 @@ public DeclarationSymbolsListener(
4040
{
4141
_state = state;
4242
_qualifiedModuleName = qualifiedModuleName;
43-
_annotations = annotations;
43+
_annotations = annotations.ToList();
4444
_attributes = attributes;
4545
_membersAllowingAttributes = membersAllowingAttributes;
4646

@@ -61,7 +61,7 @@ public DeclarationSymbolsListener(
6161
projectDeclaration,
6262
_qualifiedModuleName.ComponentName,
6363
true,
64-
FindAnnotations(),
64+
FindModuleAnnotations(),
6565
moduleAttributes);
6666
}
6767
else
@@ -73,7 +73,7 @@ public DeclarationSymbolsListener(
7373
projectDeclaration,
7474
_qualifiedModuleName.ComponentName,
7575
true,
76-
FindAnnotations(),
76+
FindModuleAnnotations(),
7777
moduleAttributes,
7878
hasDefaultInstanceVariable: hasDefaultInstanceVariable);
7979
}
@@ -88,25 +88,72 @@ public DeclarationSymbolsListener(
8888
}
8989
}
9090

91-
private IEnumerable<IAnnotation> FindAnnotations()
91+
private IEnumerable<IAnnotation> FindModuleAnnotations()
9292
{
9393
if (_annotations == null)
9494
{
9595
return null;
9696
}
9797

98-
int lastDeclarationsSectionLine;
99-
var component = _state.ProjectsProvider.Component(_qualifiedModuleName);
100-
using (var codeModule = component.CodeModule)
98+
var lastDeclarationsSectionLine = LastDeclarationsSectionLine();
99+
100+
//There is no module body.
101+
if (lastDeclarationsSectionLine == null)
101102
{
102-
lastDeclarationsSectionLine = codeModule.CountOfDeclarationLines;
103+
return _annotations;
103104
}
104105

105-
var annotations = _annotations.Where(annotation => annotation.QualifiedSelection.QualifiedName.Equals(_qualifiedModuleName)
106-
&& annotation.QualifiedSelection.Selection.EndLine <= lastDeclarationsSectionLine);
106+
var lastPossibleModuleAnnotationLine = lastDeclarationsSectionLine.Value;
107+
var annotations = _annotations.Where(annotation => annotation.QualifiedSelection.Selection.EndLine <= lastPossibleModuleAnnotationLine);
107108
return annotations.ToList();
108109
}
109110

111+
private int? LastDeclarationsSectionLine()
112+
{
113+
var firstModuleBodyElementLine = FirstModuleBodyElementLine();
114+
115+
if (firstModuleBodyElementLine == null)
116+
{
117+
return null;
118+
}
119+
120+
//The VBE uses 1-based lines.
121+
for (var currentLine = firstModuleBodyElementLine.Value - 1; currentLine >= 1; currentLine--)
122+
{
123+
if (_annotations.Any(annotation => annotation.QualifiedSelection.Selection.StartLine <= currentLine
124+
&& annotation.QualifiedSelection.Selection.EndLine >=
125+
currentLine))
126+
{
127+
continue;
128+
}
129+
130+
return currentLine;
131+
}
132+
133+
//There is no declaration section.
134+
return 0;
135+
}
136+
137+
private int? FirstModuleBodyElementLine()
138+
{
139+
var moduleTrees = _state.ParseTrees.Where(kvp => kvp.Key.Equals(_qualifiedModuleName)).ToList();
140+
if (!moduleTrees.Any())
141+
{
142+
return null;
143+
}
144+
145+
var startContext = (ParserRuleContext) moduleTrees.First().Value;
146+
var moduleBody = startContext.GetDescendent<VBAParser.ModuleBodyContext>();
147+
148+
var moduleBodyElements = moduleBody.moduleBodyElement();
149+
if (!moduleBodyElements.Any())
150+
{
151+
return null;
152+
}
153+
154+
return moduleBodyElements.Select(context => context.start.Line).Min();
155+
}
156+
110157
private IEnumerable<IAnnotation> FindMemberAnnotations(int firstMemberLine)
111158
{
112159
if (_annotations == null)

RubberduckTests/Annotations/DeclarationsListenerAnnotationsTests.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,7 @@ Public Function Bar() As Variant
148148
public void AnnotationsRightAboveFirstMemberAreNotModuleAnnotations_WithoutDeclarationOnTop()
149149
{
150150
const string inputCode =
151-
@"
152-
'@TestMethod
151+
@"'@TestMethod
153152
'@Enumerator 17, _
154153
12 _
155154
@DefaultMember
@@ -226,5 +225,29 @@ Public Function Bar() As Variant
226225
Assert.AreEqual(expectedAnnotationCount, actualAnnotationCount);
227226
}
228227
}
228+
229+
[Test]
230+
public void AllAnnotationsAreModuleAnnotationsIfThereIsNoBody()
231+
{
232+
const string inputCode =
233+
@"
234+
'@TestModule
235+
'@Folder(""Test"")
236+
'SomeComment
237+
'@Enumerator 17, _
238+
12 _
239+
@DefaultMember
240+
";
241+
var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out _);
242+
using (var state = MockParser.CreateAndParse(vbe.Object))
243+
{
244+
var moduleDeclaration = state.DeclarationFinder.UserDeclarations(DeclarationType.ProceduralModule).Single();
245+
246+
var expectedAnnotationCount = 4;
247+
var actualAnnotationCount = moduleDeclaration.Annotations.Count();
248+
249+
Assert.AreEqual(expectedAnnotationCount, actualAnnotationCount);
250+
}
251+
}
229252
}
230253
}

0 commit comments

Comments
 (0)