Skip to content

Commit ba41b21

Browse files
committed
Don’t display MARK comments as children of the nodes they are attached to
We used to walk the leading and trailing trivia when walking the token that’s a child of the node. This caused us to eg. report the `MARK` comment in the following as a child of `myFunc` instead of a as a sibling of `myFunc`. ```swift struct Foo { // MARK: Marker func myFunc() {} } ``` rdar://121600714
1 parent 789ec94 commit ba41b21

File tree

2 files changed

+71
-8
lines changed

2 files changed

+71
-8
lines changed

Sources/SourceKitLSP/Swift/DocumentSymbols.swift

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ extension SwiftLanguageServer {
2222
let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot)
2323

2424
try Task.checkCancellation()
25-
return .documentSymbols(DocumentSymbolsFinder.find(in: [Syntax(syntaxTree)], snapshot: snapshot))
25+
return .documentSymbols(
26+
DocumentSymbolsFinder.find(
27+
in: [Syntax(syntaxTree)],
28+
snapshot: snapshot,
29+
range: syntaxTree.position..<syntaxTree.endPosition
30+
)
31+
)
2632
}
2733
}
2834

@@ -32,17 +38,25 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
3238
/// The snapshot of the document for which we are getting document symbols.
3339
private let snapshot: DocumentSnapshot
3440

41+
/// Only document symbols that intersect with this range get reported.
42+
private let range: Range<AbsolutePosition>
43+
3544
/// Accumulating the result in here.
3645
private var result: [DocumentSymbol] = []
3746

38-
private init(snapshot: DocumentSnapshot) {
47+
private init(snapshot: DocumentSnapshot, range: Range<AbsolutePosition>) {
3948
self.snapshot = snapshot
49+
self.range = range
4050
super.init(viewMode: .sourceAccurate)
4151
}
4252

4353
/// Designated entry point for `DocumentSymbolFinder`.
44-
static func find(in nodes: some Sequence<Syntax>, snapshot: DocumentSnapshot) -> [DocumentSymbol] {
45-
let visitor = Self(snapshot: snapshot)
54+
static func find(
55+
in nodes: some Sequence<Syntax>,
56+
snapshot: DocumentSnapshot,
57+
range: Range<AbsolutePosition>
58+
) -> [DocumentSymbol] {
59+
let visitor = DocumentSymbolsFinder(snapshot: snapshot, range: range)
4660
for node in nodes {
4761
visitor.walk(node)
4862
}
@@ -57,6 +71,9 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
5771
range: Range<AbsolutePosition>,
5872
selection: Range<AbsolutePosition>
5973
) -> SyntaxVisitorContinueKind {
74+
if !self.range.overlaps(range) {
75+
return .skipChildren
76+
}
6077
guard let rangeLowerBound = snapshot.position(of: range.lowerBound),
6178
let rangeUpperBound = snapshot.position(of: range.upperBound),
6279
let selectionLowerBound = snapshot.position(of: selection.lowerBound),
@@ -65,8 +82,14 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
6582
return .skipChildren
6683
}
6784

68-
let children = DocumentSymbolsFinder.find(in: node.children(viewMode: .sourceAccurate), snapshot: snapshot)
85+
// Record MARK comments on the node's leading and trailing trivia in `result` not as a child of `node`.
86+
visit(node.leadingTrivia, position: node.position)
6987

88+
let children = DocumentSymbolsFinder.find(
89+
in: node.children(viewMode: .sourceAccurate),
90+
snapshot: snapshot,
91+
range: node.positionAfterSkippingLeadingTrivia..<node.endPositionBeforeTrailingTrivia
92+
)
7093
result.append(
7194
DocumentSymbol(
7295
name: name,
@@ -76,6 +99,7 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
7699
children: children
77100
)
78101
)
102+
visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia)
79103
return .skipChildren
80104
}
81105

@@ -144,8 +168,12 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
144168
}
145169

146170
override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
147-
self.visit(node.leadingTrivia, position: node.position)
148-
self.visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia)
171+
if self.range.overlaps(node.position..<node.positionAfterSkippingLeadingTrivia) {
172+
self.visit(node.leadingTrivia, position: node.position)
173+
}
174+
if range.overlaps(node.endPositionBeforeTrailingTrivia..<node.endPosition) {
175+
self.visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia)
176+
}
149177
return .skipChildren
150178
}
151179

Tests/SourceKitLSPTests/DocumentSymbolTests.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ final class DocumentSymbolTests: XCTestCase {
668668
}
669669
}
670670

671-
func testIncludeNestedMarkComments() async throws {
671+
func testNestedMarkComment() async throws {
672672
try await assertDocumentSymbols(
673673
"""
674674
1️⃣struct 2️⃣Foo3️⃣ {
@@ -694,6 +694,41 @@ final class DocumentSymbolTests: XCTestCase {
694694
]
695695
}
696696
}
697+
698+
func testNestedMarkCommentFollowedAttachedToChild() async throws {
699+
try await assertDocumentSymbols(
700+
"""
701+
1️⃣struct 2️⃣Foo3️⃣ {
702+
4️⃣// MARK: Marker5️⃣
703+
6️⃣func 7️⃣myFunc()8️⃣ { }9️⃣
704+
}🔟
705+
"""
706+
) { positions in
707+
[
708+
DocumentSymbol(
709+
name: "Foo",
710+
kind: .struct,
711+
range: positions["1️⃣"]..<positions["🔟"],
712+
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
713+
children: [
714+
DocumentSymbol(
715+
name: "Marker",
716+
kind: .namespace,
717+
range: positions["4️⃣"]..<positions["5️⃣"],
718+
selectionRange: positions["4️⃣"]..<positions["5️⃣"]
719+
),
720+
DocumentSymbol(
721+
name: "myFunc()",
722+
kind: .method,
723+
range: positions["6️⃣"]..<positions["9️⃣"],
724+
selectionRange: positions["7️⃣"]..<positions["8️⃣"],
725+
children: []
726+
),
727+
]
728+
)
729+
]
730+
}
731+
}
697732
}
698733

699734
fileprivate func assertDocumentSymbols(

0 commit comments

Comments
 (0)