Skip to content

Commit 561aad2

Browse files
simplify DocumentableSymbol search
1 parent d0a10de commit 561aad2

File tree

1 file changed

+45
-141
lines changed

1 file changed

+45
-141
lines changed

Sources/SourceKitLSP/Documentation/DocumentationManager.swift

Lines changed: 45 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ package final actor DocumentationManager {
6565
// Search for the nearest documentable symbol at this location
6666
let syntaxTree = await swiftLanguageService.syntaxTreeManager.syntaxTree(for: snapshot)
6767
guard
68-
let nearestDocumentableSymbol = DocumentableSymbolFinder.find(
69-
in: [Syntax(syntaxTree)],
70-
at: snapshot.absolutePosition(of: position)
68+
let nearestDocumentableSymbol = DocumentableSymbol.findNearestSymbol(
69+
syntaxTree: syntaxTree,
70+
position: snapshot.absolutePosition(of: position)
7171
)
7272
else {
7373
throw ResponseError.requestFailed(convertError: .noDocumentation)
@@ -140,149 +140,53 @@ fileprivate extension ResponseError {
140140
}
141141
}
142142

143-
fileprivate final class DocumentableSymbolFinder: SyntaxAnyVisitor {
144-
struct Symbol {
145-
let position: AbsolutePosition
146-
let documentationComments: [String]
147-
let depth: Int
148-
}
149-
150-
private let cursorPosition: AbsolutePosition
151-
152-
/// Accumulating the result in here.
153-
private var result: Symbol? = nil
154-
155-
private var depth: Int = 0
156-
157-
private init(_ cursorPosition: AbsolutePosition) {
158-
self.cursorPosition = cursorPosition
159-
super.init(viewMode: .sourceAccurate)
160-
}
161-
162-
/// Designated entry point for ``DocumentableSymbolFinder``.
163-
static func find(
164-
in nodes: some Sequence<Syntax>,
165-
at cursorPosition: AbsolutePosition
166-
) -> Symbol? {
167-
let visitor = DocumentableSymbolFinder(cursorPosition)
168-
for node in nodes {
169-
visitor.walk(node)
170-
}
171-
return visitor.result
172-
}
173-
174-
private func setResult(node: some SyntaxProtocol, position: AbsolutePosition) {
175-
setResult(
176-
symbol: Symbol(
177-
position: position,
178-
documentationComments: node.leadingTrivia.flatMap { trivia -> [String] in
179-
switch trivia {
180-
case .docLineComment(let comment):
181-
return [String(comment.dropFirst(3).trimmingCharacters(in: .whitespaces))]
182-
case .docBlockComment(let comment):
183-
return comment.dropFirst(3)
184-
.dropLast(2)
185-
.split(separator: "\n")
186-
.map { String($0).trimmingCharacters(in: .whitespaces) }
187-
default:
188-
return []
189-
}
190-
},
191-
depth: depth
192-
)
193-
)
194-
}
195-
196-
private func setResult(symbol: Symbol) {
197-
guard symbol.depth > result?.depth ?? -1 else {
198-
return
199-
}
200-
result = symbol
201-
}
202-
203-
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
204-
guard depth > result?.depth ?? -1 else {
205-
return .skipChildren
206-
}
207-
return .visitChildren
208-
}
209-
210-
private func visitNamedDecl(node: some NamedDeclSyntax) -> SyntaxVisitorContinueKind {
211-
if cursorPosition < node.range.upperBound {
212-
setResult(node: node, position: node.name.positionAfterSkippingLeadingTrivia)
213-
}
214-
return .visitChildren
215-
}
216-
217-
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
218-
visitNamedDecl(node: node)
219-
}
220-
221-
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
222-
visitNamedDecl(node: node)
223-
}
224-
225-
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
226-
visitNamedDecl(node: node)
227-
}
228-
229-
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
230-
visitNamedDecl(node: node)
231-
}
232-
233-
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
234-
visitNamedDecl(node: node)
235-
}
236-
237-
override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind {
238-
depth += 1
239-
let range = node.leftBrace.endPositionBeforeTrailingTrivia..<node.rightBrace.positionAfterSkippingLeadingTrivia
240-
guard range.contains(cursorPosition) else {
241-
return .skipChildren
242-
}
243-
return .visitChildren
244-
}
245-
246-
override func visitPost(_ node: MemberBlockSyntax) {
247-
depth -= 1;
248-
}
249-
250-
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
251-
let symbolPosition = node.name.positionAfterSkippingLeadingTrivia
252-
if cursorPosition < node.range.upperBound {
253-
setResult(node: node, position: symbolPosition)
254-
}
255-
return .skipChildren
256-
}
257-
258-
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
259-
let symbolPosition = node.initKeyword.positionAfterSkippingLeadingTrivia
260-
if node.range.contains(cursorPosition) || cursorPosition < symbolPosition {
261-
setResult(node: node, position: symbolPosition)
262-
}
263-
return .skipChildren
264-
}
265-
266-
override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind {
267-
for element in node.elements {
268-
let symbolPosition = element.name.positionAfterSkippingLeadingTrivia
269-
if element.range.contains(cursorPosition) || cursorPosition < symbolPosition {
270-
setResult(node: node, position: symbolPosition)
143+
fileprivate struct DocumentableSymbol {
144+
let position: AbsolutePosition
145+
let documentationComments: [String]
146+
147+
init(node: any SyntaxProtocol, position: AbsolutePosition) {
148+
self.position = position
149+
self.documentationComments = node.leadingTrivia.flatMap { trivia -> [String] in
150+
switch trivia {
151+
case .docLineComment(let comment):
152+
return [String(comment.dropFirst(3).trimmingCharacters(in: .whitespaces))]
153+
case .docBlockComment(let comment):
154+
return comment.dropFirst(3)
155+
.dropLast(2)
156+
.split(separator: "\n")
157+
.map { String($0).trimmingCharacters(in: .whitespaces) }
158+
default:
159+
return []
271160
}
272161
}
273-
return .skipChildren
274162
}
163+
}
275164

276-
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
277-
// A variable declaration is only documentable if there is only one pattern binding
278-
guard let identifier = node.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
279-
return .skipChildren
280-
}
281-
let symbolPosition = identifier.positionAfterSkippingLeadingTrivia
282-
if node.range.contains(cursorPosition) || cursorPosition < symbolPosition {
283-
setResult(node: node, position: symbolPosition)
165+
fileprivate extension DocumentableSymbol {
166+
static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
167+
guard let token = syntaxTree.token(at: position) else {
168+
return nil
169+
}
170+
return token.ancestorOrSelf { node in
171+
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
172+
return DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
173+
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
174+
return DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
175+
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
176+
return DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
177+
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
178+
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
179+
return nil
180+
}
181+
return DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
182+
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
183+
guard let name = enumCaseDecl.elements.only?.name else {
184+
return nil
185+
}
186+
return DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
187+
}
188+
return nil
284189
}
285-
return .skipChildren
286190
}
287191
}
288192
#endif

0 commit comments

Comments
 (0)