Skip to content

Commit c185099

Browse files
authored
Merge pull request #1531 from ahoppen/6.0/diagnostics-in-secondary-files
[6.0] Handle diagnostics in secondary files correctly
2 parents 0f44f03 + 40ebf7f commit c185099

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

Sources/SourceKitLSP/Swift/Diagnostic.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,27 @@ fileprivate extension String {
154154
extension Diagnostic {
155155

156156
/// Creates a diagnostic from a sourcekitd response dictionary.
157+
///
158+
/// `snapshot` is the snapshot of the document for which the diagnostics are generated.
159+
/// `documentManager` is used to resolve positions of notes in secondary files.
157160
init?(
158161
_ diag: SKDResponseDictionary,
159162
in snapshot: DocumentSnapshot,
163+
documentManager: DocumentManager,
160164
useEducationalNoteAsCode: Bool
161165
) {
162-
// FIXME: this assumes that the diagnostics are all in the same file.
163-
164166
let keys = diag.sourcekitd.keys
165167
let values = diag.sourcekitd.values
166168

169+
guard let filePath: String = diag[keys.filePath] else {
170+
logger.fault("Missing file path in diagnostic")
171+
return nil
172+
}
173+
guard filePath == snapshot.uri.pseudoPath else {
174+
logger.error("Ignoring diagnostic from a different file: \(filePath)")
175+
return nil
176+
}
177+
167178
guard let message: String = diag[keys.description]?.withFirstLetterUppercased() else { return nil }
168179

169180
var range: Range<Position>? = nil
@@ -237,7 +248,13 @@ extension Diagnostic {
237248
if let sknotes: SKDResponseArray = diag[keys.diagnostics] {
238249
notes = []
239250
sknotes.forEach { (_, sknote) -> Bool in
240-
guard let note = DiagnosticRelatedInformation(sknote, in: snapshot) else { return true }
251+
guard
252+
let note = DiagnosticRelatedInformation(
253+
sknote,
254+
primaryDocumentSnapshot: snapshot,
255+
documentManager: documentManager
256+
)
257+
else { return true }
241258
notes?.append(note)
242259
return true
243260
}
@@ -309,9 +326,28 @@ extension Diagnostic {
309326
extension DiagnosticRelatedInformation {
310327

311328
/// Creates related information from a sourcekitd note response dictionary.
312-
init?(_ diag: SKDResponseDictionary, in snapshot: DocumentSnapshot) {
329+
///
330+
/// `primaryDocumentSnapshot` is the snapshot of the document for which the diagnostics are generated.
331+
/// `documentManager` is used to resolve positions of notes in secondary files.
332+
init?(_ diag: SKDResponseDictionary, primaryDocumentSnapshot: DocumentSnapshot, documentManager: DocumentManager) {
313333
let keys = diag.sourcekitd.keys
314334

335+
guard let filePath: String = diag[keys.filePath] else {
336+
logger.fault("Missing file path in related diagnostic information")
337+
return nil
338+
}
339+
let uri = DocumentURI(filePath: filePath, isDirectory: false)
340+
let snapshot: DocumentSnapshot
341+
if filePath == primaryDocumentSnapshot.uri.pseudoPath {
342+
snapshot = primaryDocumentSnapshot
343+
} else if let inMemorySnapshot = try? documentManager.latestSnapshot(uri) {
344+
snapshot = inMemorySnapshot
345+
} else if let snapshotFromDisk = try? DocumentSnapshot(withContentsFromDisk: uri, language: .swift) {
346+
snapshot = snapshotFromDisk
347+
} else {
348+
return nil
349+
}
350+
315351
var position: Position? = nil
316352
if let line: Int = diag[keys.line],
317353
let utf8Column: Int = diag[keys.column],

Sources/SourceKitLSP/Swift/DiagnosticReportManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ actor DiagnosticReportManager {
113113
Diagnostic(
114114
diag,
115115
in: snapshot,
116+
documentManager: documentManager,
116117
useEducationalNoteAsCode: self.clientHasDiagnosticsCodeDescriptionSupport
117118
)
118119
}) ?? []

Tests/SourceKitLSPTests/PullDiagnosticsTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,30 @@ final class PullDiagnosticsTests: XCTestCase {
349349
diagnosticRequestCancelled.fulfill()
350350
try await fulfillmentOfOrThrow([diagnosticResponseReceived])
351351
}
352+
353+
func testNoteInSecondaryFile() async throws {
354+
let project = try await SwiftPMTestProject(files: [
355+
"FileA.swift": """
356+
@available(*, unavailable)
357+
struct 1️⃣Test {}
358+
""",
359+
"FileB.swift": """
360+
func test() {
361+
_ = Test()
362+
}
363+
""",
364+
])
365+
366+
let (uri, _) = try project.openDocument("FileB.swift")
367+
let diagnostics = try await project.testClient.send(
368+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
369+
)
370+
guard case .full(let diagnostics) = diagnostics else {
371+
XCTFail("Expected full diagnostics report")
372+
return
373+
}
374+
let diagnostic = try XCTUnwrap(diagnostics.items.only)
375+
let note = try XCTUnwrap(diagnostic.relatedInformation?.only)
376+
XCTAssertEqual(note.location, try project.location(from: "1️⃣", to: "1️⃣", in: "FileA.swift"))
377+
}
352378
}

0 commit comments

Comments
 (0)