Skip to content

Commit 8cb76ba

Browse files
authored
Merge pull request #777 from ahoppen/ahoppen/code-actions-for-pulled-diags
Register diagnostics in `currentDiagnostics` when performing a diagnostic pull request
2 parents 9bccd16 + 8804e95 commit 8cb76ba

File tree

2 files changed

+90
-34
lines changed

2 files changed

+90
-34
lines changed

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,36 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
288288
}
289289
})
290290
}
291-
291+
292+
/// Register the diagnostics returned from sourcekitd in `currentDiagnostics`
293+
/// and returns the corresponding LSP diagnostics.
294+
private func registerDiagnostics(
295+
sourcekitdDiagnostics: SKDResponseArray?,
296+
snapshot: DocumentSnapshot,
297+
stage: DiagnosticStage
298+
) -> [Diagnostic] {
299+
let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport
300+
301+
var newDiags: [CachedDiagnostic] = []
302+
sourcekitdDiagnostics?.forEach { _, diag in
303+
if let diag = CachedDiagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) {
304+
newDiags.append(diag)
305+
}
306+
return true
307+
}
308+
309+
let result = mergeDiagnostics(
310+
old: currentDiagnostics[snapshot.document.uri] ?? [],
311+
new: newDiags,
312+
stage: stage,
313+
isFallback: self.commandsByFile[snapshot.document.uri]?.isFallback ?? true
314+
)
315+
currentDiagnostics[snapshot.document.uri] = result
316+
317+
return result.map(\.diagnostic)
318+
319+
}
320+
292321
/// Publish diagnostics for the given `snapshot`. We withhold semantic diagnostics if we are using
293322
/// fallback arguments.
294323
///
@@ -304,31 +333,22 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
304333
return
305334
}
306335

307-
let isFallback = compileCommand?.isFallback ?? true
308-
309336
let stageUID: sourcekitd_uid_t? = response[sourcekitd.keys.diagnostic_stage]
310337
let stage = stageUID.flatMap { DiagnosticStage($0, sourcekitd: sourcekitd) } ?? .sema
311338

312-
let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport
313-
314-
// Note: we make the notification even if there are no diagnostics to clear the current state.
315-
var newDiags: [CachedDiagnostic] = []
316-
response[keys.diagnostics]?.forEach { _, diag in
317-
if let diag = CachedDiagnostic(diag,
318-
in: snapshot,
319-
useEducationalNoteAsCode: supportsCodeDescription) {
320-
newDiags.append(diag)
321-
}
322-
return true
323-
}
324-
325-
let result = mergeDiagnostics(
326-
old: currentDiagnostics[documentUri] ?? [],
327-
new: newDiags, stage: stage, isFallback: isFallback)
328-
currentDiagnostics[documentUri] = result
329-
330-
client.send(PublishDiagnosticsNotification(
331-
uri: documentUri, version: snapshot.version, diagnostics: result.map { $0.diagnostic }))
339+
let diagnostics = registerDiagnostics(
340+
sourcekitdDiagnostics: response[keys.diagnostics],
341+
snapshot: snapshot,
342+
stage: stage
343+
)
344+
345+
client.send(
346+
PublishDiagnosticsNotification(
347+
uri: documentUri,
348+
version: snapshot.version,
349+
diagnostics: diagnostics
350+
)
351+
)
332352
}
333353

334354
/// Should be called on self.queue.
@@ -1393,20 +1413,16 @@ extension SwiftLanguageServer {
13931413
skreq[keys.compilerargs] = compileCommand.compilerArgs
13941414
}
13951415

1396-
let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport
1397-
13981416
let handle = self.sourcekitd.send(skreq, self.queue) { response in
13991417
guard let dict = response.success else {
14001418
return completion(.failure(ResponseError(response.failure!)))
14011419
}
14021420

1403-
var diagnostics: [Diagnostic] = []
1404-
dict[keys.diagnostics]?.forEach { _, diag in
1405-
if let diagnostic = Diagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) {
1406-
diagnostics.append(diagnostic)
1407-
}
1408-
return true
1409-
}
1421+
let diagnostics = self.registerDiagnostics(
1422+
sourcekitdDiagnostics: dict[keys.diagnostics],
1423+
snapshot: snapshot,
1424+
stage: .sema
1425+
)
14101426

14111427
completion(.success(diagnostics))
14121428
}

Tests/SourceKitLSPTests/PullDiagnosticsTests.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ final class PullDiagnosticsTests: XCTestCase {
3434
rootPath: nil,
3535
rootURI: nil,
3636
initializationOptions: nil,
37-
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
37+
capabilities: ClientCapabilities(
38+
workspace: nil,
39+
textDocument: .init(codeAction: .init(codeActionLiteralSupport: .init(codeActionKind: .init(valueSet: [.quickFix]))))
40+
),
3841
trace: .off,
3942
workspaceFolders: nil
4043
))
@@ -78,6 +81,43 @@ final class PullDiagnosticsTests: XCTestCase {
7881
}
7982
""")
8083
XCTAssertEqual(diagnostics.count, 1)
81-
XCTAssertEqual(diagnostics[0].range, Position(line: 1, utf16index: 2)..<Position(line: 1, utf16index: 9))
84+
let diagnostic = try XCTUnwrap(diagnostics.first)
85+
XCTAssertEqual(diagnostic.range, Position(line: 1, utf16index: 2)..<Position(line: 1, utf16index: 9))
86+
}
87+
88+
/// Test that we can get code actions for pulled diagnostics (https://github.com/apple/sourcekit-lsp/issues/776)
89+
func testCodeActions() throws {
90+
let diagnostics = try performDiagnosticRequest(text: """
91+
protocol MyProtocol {
92+
func bar()
93+
}
94+
95+
struct Test: MyProtocol {}
96+
""")
97+
XCTAssertEqual(diagnostics.count, 1)
98+
let diagnostic = try XCTUnwrap(diagnostics.first)
99+
XCTAssertEqual(diagnostic.range, Position(line: 4, utf16index: 7)..<Position(line: 4, utf16index: 7))
100+
let note = try XCTUnwrap(diagnostic.relatedInformation?.first)
101+
XCTAssertEqual(note.location.range, Position(line: 4, utf16index: 7)..<Position(line: 4, utf16index: 7))
102+
XCTAssertEqual(note.codeActions?.count ?? 0, 1)
103+
104+
let response = try sk.sendSync(CodeActionRequest(
105+
range: note.location.range,
106+
context: CodeActionContext(
107+
diagnostics: diagnostics,
108+
only: [.quickFix],
109+
triggerKind: .invoked
110+
),
111+
textDocument: TextDocumentIdentifier(note.location.uri)
112+
))
113+
114+
guard case .codeActions(let actions) = response else {
115+
XCTFail("Expected codeActions response")
116+
return
117+
}
118+
119+
XCTAssertEqual(actions.count, 1)
120+
let action = try XCTUnwrap(actions.first)
121+
XCTAssertEqual(action.title, "do you want to add protocol stubs?")
82122
}
83123
}

0 commit comments

Comments
 (0)