Skip to content

Commit 09baabb

Browse files
always try to render some documentation for Swift files
1 parent d8a2a24 commit 09baabb

File tree

3 files changed

+92
-42
lines changed

3 files changed

+92
-42
lines changed

Sources/DocCDocumentation/DoccDocumentationError.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import Foundation
1414
package import LanguageServerProtocol
1515

1616
package enum DocCDocumentationError: LocalizedError {
17-
case noDocumentation
17+
case noDocumentableSymbols
1818
case indexNotAvailable
1919
case symbolNotFound(String)
2020

2121
var errorDescription: String? {
2222
switch self {
23-
case .noDocumentation:
24-
return "No documentation could be rendered for the position in this document"
23+
case .noDocumentableSymbols:
24+
return "No documentable symbols were found in this Swift file"
2525
case .indexNotAvailable:
2626
return "The index is not availble to complete the request"
2727
case .symbolNotFound(let symbolName):

Sources/SourceKitLSP/Swift/DoccDocumentation.swift

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ extension SwiftLanguageService {
4646
let nearestDocumentableSymbol = DocumentableSymbol.findNearestSymbol(
4747
syntaxTree: syntaxTree,
4848
position: snapshot.absolutePosition(of: position)
49-
)
49+
) ?? DocumentableSymbol.findTopLevelSymbol(syntaxTree: syntaxTree)
5050
else {
51-
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
51+
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentableSymbols)
5252
}
5353
// Retrieve the symbol graph as well as information about the symbol
5454
let symbolPosition = await adjustPositionToStartOfIdentifier(
@@ -130,30 +130,56 @@ fileprivate struct DocumentableSymbol {
130130
}
131131
}
132132

133-
static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
134-
guard let token = syntaxTree.token(at: position) else {
133+
init?(node: any SyntaxProtocol) {
134+
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
135+
self = DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
136+
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
137+
self = DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
138+
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
139+
self = DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
140+
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
141+
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
142+
return nil
143+
}
144+
self = DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
145+
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
146+
guard let name = enumCaseDecl.elements.only?.name else {
147+
return nil
148+
}
149+
self = DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
150+
} else {
135151
return nil
136152
}
137-
return token.ancestorOrSelf { node in
138-
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
139-
return DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
140-
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
141-
return DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
142-
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
143-
return DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
144-
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
145-
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
146-
return nil
153+
}
154+
155+
static func findTopLevelSymbol(syntaxTree: SourceFileSyntax) -> DocumentableSymbol? {
156+
class Visitor: SyntaxAnyVisitor {
157+
var topLevelSymbol: DocumentableSymbol? = nil
158+
159+
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
160+
guard topLevelSymbol == nil else {
161+
return .skipChildren
147162
}
148-
return DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
149-
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
150-
guard let name = enumCaseDecl.elements.only?.name else {
151-
return nil
163+
164+
if let symbol = DocumentableSymbol(node: node) {
165+
topLevelSymbol = symbol
166+
return .skipChildren
152167
}
153-
return DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
168+
return .visitChildren
154169
}
170+
}
171+
172+
let visitor = Visitor(viewMode: .all)
173+
visitor.walk(syntaxTree)
174+
return visitor.topLevelSymbol
175+
}
176+
177+
static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
178+
guard let token = syntaxTree.token(at: position) else {
155179
return nil
156180
}
181+
// Walk up the tree until we find a documentable symbol
182+
return token.ancestorOrSelf { DocumentableSymbol(node: $0) }
157183
}
158184
}
159185
#endif

Tests/SourceKitLSPTests/DoccDocumentationTests.swift

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class DoccDocumentationTests: XCTestCase {
2727
try await renderDocumentation(
2828
swiftFile: "1️⃣",
2929
expectedResponses: [
30-
"1️⃣": .error(.noDocumentation)
30+
"1️⃣": .error(.noDocumentableSymbols)
3131
]
3232
)
3333
}
@@ -44,7 +44,7 @@ final class DoccDocumentationTests: XCTestCase {
4444
"1️⃣": .renderNode(kind: .symbol, path: "test/function()"),
4545
"2️⃣": .renderNode(kind: .symbol, path: "test/function()"),
4646
"3️⃣": .renderNode(kind: .symbol, path: "test/function()"),
47-
"4️⃣": .error(.noDocumentation),
47+
"4️⃣": .renderNode(kind: .symbol, path: "test/function()"),
4848
]
4949
)
5050
}
@@ -76,7 +76,7 @@ final class DoccDocumentationTests: XCTestCase {
7676
"6️⃣": .renderNode(kind: .symbol, path: "test/Structure/bar"),
7777
"7️⃣": .renderNode(kind: .symbol, path: "test/Structure/init(_:bar:)"),
7878
"8️⃣": .renderNode(kind: .symbol, path: "test/Structure/init(_:bar:)"),
79-
"9️⃣": .error(.noDocumentation),
79+
"9️⃣": .renderNode(kind: .symbol, path: "test/Structure"),
8080
]
8181
)
8282
}
@@ -92,7 +92,7 @@ final class DoccDocumentationTests: XCTestCase {
9292
"1️⃣": .renderNode(kind: .symbol, path: "test/Structure"),
9393
"2️⃣": .renderNode(kind: .symbol, path: "test/Structure"),
9494
"3️⃣": .renderNode(kind: .symbol, path: "test/Structure"),
95-
"4️⃣": .error(.noDocumentation),
95+
"4️⃣": .renderNode(kind: .symbol, path: "test/Structure"),
9696
]
9797
)
9898
}
@@ -125,7 +125,7 @@ final class DoccDocumentationTests: XCTestCase {
125125
"7️⃣": .renderNode(kind: .symbol, path: "test/Class/init(_:bar:)"),
126126
"8️⃣": .renderNode(kind: .symbol, path: "test/Class/init(_:bar:)"),
127127
"9️⃣": .renderNode(kind: .symbol, path: "test/Class"),
128-
"0️⃣": .error(.noDocumentation),
128+
"0️⃣": .renderNode(kind: .symbol, path: "test/Class"),
129129
]
130130
)
131131
}
@@ -141,7 +141,7 @@ final class DoccDocumentationTests: XCTestCase {
141141
"1️⃣": .renderNode(kind: .symbol, path: "test/Class"),
142142
"2️⃣": .renderNode(kind: .symbol, path: "test/Class"),
143143
"3️⃣": .renderNode(kind: .symbol, path: "test/Class"),
144-
"4️⃣": .error(.noDocumentation),
144+
"4️⃣": .renderNode(kind: .symbol, path: "test/Class"),
145145
]
146146
)
147147
}
@@ -173,7 +173,7 @@ final class DoccDocumentationTests: XCTestCase {
173173
"6️⃣": .renderNode(kind: .symbol, path: "test/Actor/bar"),
174174
"7️⃣": .renderNode(kind: .symbol, path: "test/Actor/init(_:bar:)"),
175175
"8️⃣": .renderNode(kind: .symbol, path: "test/Actor/init(_:bar:)"),
176-
"9️⃣": .error(.noDocumentation),
176+
"9️⃣": .renderNode(kind: .symbol, path: "test/Actor"),
177177
]
178178
)
179179
}
@@ -189,7 +189,7 @@ final class DoccDocumentationTests: XCTestCase {
189189
"1️⃣": .renderNode(kind: .symbol, path: "test/Actor"),
190190
"2️⃣": .renderNode(kind: .symbol, path: "test/Actor"),
191191
"3️⃣": .renderNode(kind: .symbol, path: "test/Actor"),
192-
"4️⃣": .error(.noDocumentation),
192+
"4️⃣": .renderNode(kind: .symbol, path: "test/Actor"),
193193
]
194194
)
195195
}
@@ -218,7 +218,7 @@ final class DoccDocumentationTests: XCTestCase {
218218
"6️⃣": .renderNode(kind: .symbol, path: "test/Enum/second"),
219219
"7️⃣": .renderNode(kind: .symbol, path: "test/Enum/third(_:)"),
220220
"8️⃣": .renderNode(kind: .symbol, path: "test/Enum/third(_:)"),
221-
"9️⃣": .error(.noDocumentation),
221+
"9️⃣": .renderNode(kind: .symbol, path: "test/Enum"),
222222
]
223223
)
224224
}
@@ -289,7 +289,7 @@ final class DoccDocumentationTests: XCTestCase {
289289
"4️⃣": .renderNode(kind: .symbol, path: "test/Protocol/foo"),
290290
"5️⃣": .renderNode(kind: .symbol, path: "test/Protocol/bar"),
291291
"6️⃣": .renderNode(kind: .symbol, path: "test/Protocol/bar"),
292-
"7️⃣": .error(.noDocumentation),
292+
"7️⃣": .renderNode(kind: .symbol, path: "test/Protocol"),
293293
]
294294
)
295295
}
@@ -306,19 +306,21 @@ final class DoccDocumentationTests: XCTestCase {
306306
"1️⃣": .renderNode(kind: .symbol, path: "test/Protocol"),
307307
"2️⃣": .renderNode(kind: .symbol, path: "test/Protocol"),
308308
"3️⃣": .renderNode(kind: .symbol, path: "test/Protocol"),
309-
"4️⃣": .error(.noDocumentation),
309+
"4️⃣": .renderNode(kind: .symbol, path: "test/Protocol"),
310310
]
311311
)
312312
}
313313

314314
func testExtension() async throws {
315-
try await renderDocumentation(
316-
swiftFile: """
315+
let project = try await SwiftPMTestProject(
316+
files: [
317+
"MyLibrary/Structure.swift": """
317318
/// A structure containing important information
318319
public struct Structure {
319320
let number: Int
320321
}
321-
322+
""",
323+
"MyLibrary/Extension.swift": """
322324
extension Stru1️⃣cture {
323325
/// One more than the number
324326
var numberPlusOne: Int {2️⃣ number + 1 }
@@ -332,13 +334,35 @@ final class DoccDocumentationTests: XCTestCase {
332334
}
333335
}6️⃣
334336
""",
337+
],
338+
enableBackgroundIndexing: true
339+
)
340+
try await renderDocumentation(
341+
fileName: "Extension.swift",
342+
project: project,
343+
expectedResponses: [
344+
"1️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/numberPlusOne"),
345+
"2️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/numberPlusOne"),
346+
"3️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/Kind"),
347+
"4️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/Kind/first"),
348+
"5️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/Kind/second"),
349+
"6️⃣": .renderNode(kind: .symbol, path: "MyLibrary/Structure/numberPlusOne"),
350+
]
351+
)
352+
}
353+
354+
func testCursorInImport() async throws {
355+
try await renderDocumentation(
356+
swiftFile: """
357+
import Found1️⃣ation
358+
359+
/// A structure containing important information
360+
public struct Structure {
361+
let number: Int
362+
}
363+
""",
335364
expectedResponses: [
336-
"1️⃣": .error(.noDocumentation),
337-
"2️⃣": .renderNode(kind: .symbol, path: "test/Structure/numberPlusOne"),
338-
"3️⃣": .renderNode(kind: .symbol, path: "test/Structure/Kind"),
339-
"4️⃣": .renderNode(kind: .symbol, path: "test/Structure/Kind/first"),
340-
"5️⃣": .renderNode(kind: .symbol, path: "test/Structure/Kind/second"),
341-
"6️⃣": .error(.noDocumentation),
365+
"1️⃣": .renderNode(kind: .symbol, path: "test/Structure")
342366
]
343367
)
344368
}

0 commit comments

Comments
 (0)