Skip to content

Commit 2f6a3d9

Browse files
authored
Merge pull request #973 from ahoppen/ahoppen/no-local-vars-in-document-symbols
Don’t include local variables in document symbols
2 parents e6cf723 + b85f3af commit 2f6a3d9

File tree

2 files changed

+134
-15
lines changed

2 files changed

+134
-15
lines changed

Sources/SourceKitLSP/Swift/DocumentSymbols.swift

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import Foundation
1314
import LSPLogging
1415
import LanguageServerProtocol
1516
import SwiftSyntax
@@ -109,6 +110,45 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
109110
)
110111
}
111112

113+
private func visit(_ trivia: Trivia, position: AbsolutePosition) {
114+
let markPrefix = "MARK: "
115+
var position = position
116+
for piece in trivia.pieces {
117+
defer {
118+
position = position.advanced(by: piece.sourceLength.utf8Length)
119+
}
120+
switch piece {
121+
case .lineComment(let commentText), .blockComment(let commentText):
122+
let trimmedComment = commentText.trimmingCharacters(in: CharacterSet(["/", "*"]).union(.whitespaces))
123+
if trimmedComment.starts(with: markPrefix) {
124+
let markText = trimmedComment.dropFirst(markPrefix.count)
125+
guard let rangeLowerBound = snapshot.position(of: position),
126+
let rangeUpperBound = snapshot.position(of: position.advanced(by: piece.sourceLength.utf8Length))
127+
else {
128+
break
129+
}
130+
result.append(
131+
DocumentSymbol(
132+
name: String(markText),
133+
kind: .namespace,
134+
range: rangeLowerBound..<rangeUpperBound,
135+
selectionRange: rangeLowerBound..<rangeUpperBound,
136+
children: nil
137+
)
138+
)
139+
}
140+
default:
141+
break
142+
}
143+
}
144+
}
145+
146+
override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
147+
self.visit(node.leadingTrivia, position: node.position)
148+
self.visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia)
149+
return .skipChildren
150+
}
151+
112152
override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
113153
let rangeEnd =
114154
if let parameterClause = node.parameterClause {
@@ -180,7 +220,9 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
180220
// If there is only one pattern binding within the variable decl, consider the entire variable decl as the
181221
// referenced range. If there are multiple, consider each pattern binding separately since the `var` keyword doesn't
182222
// belong to any pattern binding in particular.
183-
guard let variableDecl = node.parent?.parent?.as(VariableDeclSyntax.self) else {
223+
guard let variableDecl = node.parent?.parent?.as(VariableDeclSyntax.self),
224+
variableDecl.isMemberOrTopLevelDeclaration
225+
else {
184226
return .visitChildren
185227
}
186228
let rangeNode: Syntax = variableDecl.bindings.count == 1 ? Syntax(variableDecl) : Syntax(node)
@@ -228,6 +270,19 @@ fileprivate extension SyntaxProtocol {
228270
var rangeWithoutTrivia: Range<AbsolutePosition> {
229271
return positionAfterSkippingLeadingTrivia..<endPositionBeforeTrailingTrivia
230272
}
273+
274+
/// Whether this is a top-level constant or a member of a type, ie. if this is not a local variable.
275+
var isMemberOrTopLevelDeclaration: Bool {
276+
if self.parent?.is(MemberBlockItemSyntax.self) ?? false {
277+
return true
278+
}
279+
if let codeBlockItem = self.parent?.as(CodeBlockItemSyntax.self),
280+
codeBlockItem.parent?.parent?.is(SourceFileSyntax.self) ?? false
281+
{
282+
return true
283+
}
284+
return false
285+
}
231286
}
232287

233288
fileprivate extension TokenKind {

Tests/SourceKitLSPTests/DocumentSymbolTests.swift

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,8 @@ final class DocumentSymbolTests: XCTestCase {
571571
try await assertDocumentSymbols(
572572
"""
573573
1️⃣func 2️⃣f()3️⃣ {
574-
4️⃣let 5️⃣localConstant6️⃣ = 07️⃣
575-
}8️⃣
574+
let localConstant = 0
575+
}4️⃣
576576
"""
577577
) { positions in
578578
[
@@ -581,19 +581,9 @@ final class DocumentSymbolTests: XCTestCase {
581581
detail: nil,
582582
kind: .function,
583583
deprecated: nil,
584-
range: positions["1️⃣"]..<positions["8️⃣"],
584+
range: positions["1️⃣"]..<positions["4️⃣"],
585585
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
586-
children: [
587-
DocumentSymbol(
588-
name: "localConstant",
589-
detail: nil,
590-
kind: .variable,
591-
deprecated: nil,
592-
range: positions["4️⃣"]..<positions["7️⃣"],
593-
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
594-
children: []
595-
)
596-
]
586+
children: []
597587
)
598588
]
599589
}
@@ -630,6 +620,80 @@ final class DocumentSymbolTests: XCTestCase {
630620
]
631621
}
632622
}
623+
624+
func testIncludeMarkComments() async throws {
625+
try await assertDocumentSymbols(
626+
"""
627+
1️⃣// MARK: Marker2️⃣
628+
"""
629+
) { positions in
630+
[
631+
DocumentSymbol(
632+
name: "Marker",
633+
kind: .namespace,
634+
range: positions["1️⃣"]..<positions["2️⃣"],
635+
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
636+
)
637+
]
638+
}
639+
640+
try await assertDocumentSymbols(
641+
"""
642+
1️⃣// MARK: - Marker2️⃣
643+
"""
644+
) { positions in
645+
[
646+
DocumentSymbol(
647+
name: "- Marker",
648+
kind: .namespace,
649+
range: positions["1️⃣"]..<positions["2️⃣"],
650+
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
651+
)
652+
]
653+
}
654+
655+
try await assertDocumentSymbols(
656+
"""
657+
1️⃣/* MARK: Marker */2️⃣
658+
"""
659+
) { positions in
660+
[
661+
DocumentSymbol(
662+
name: "Marker",
663+
kind: .namespace,
664+
range: positions["1️⃣"]..<positions["2️⃣"],
665+
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
666+
)
667+
]
668+
}
669+
}
670+
671+
func testIncludeNestedMarkComments() async throws {
672+
try await assertDocumentSymbols(
673+
"""
674+
1️⃣struct 2️⃣Foo3️⃣ {
675+
4️⃣// MARK: Marker5️⃣
676+
}6️⃣
677+
"""
678+
) { positions in
679+
[
680+
DocumentSymbol(
681+
name: "Foo",
682+
kind: .struct,
683+
range: positions["1️⃣"]..<positions["6️⃣"],
684+
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
685+
children: [
686+
DocumentSymbol(
687+
name: "Marker",
688+
kind: .namespace,
689+
range: positions["4️⃣"]..<positions["5️⃣"],
690+
selectionRange: positions["4️⃣"]..<positions["5️⃣"]
691+
)
692+
]
693+
)
694+
]
695+
}
696+
}
633697
}
634698

635699
fileprivate func assertDocumentSymbols(

0 commit comments

Comments
 (0)