Skip to content

Commit da16aa9

Browse files
Correct source code locations of link diagnostics for Objective-C doc comments (#734)
* rdar://102855081 Correct the line and character locations of invalid link diagnostics in Objective-C source code doc comments. Previously, DocumentationContext#resolveLinks would use the default symbol to calculate the offset of each doc comment in the source file. However, the default symbol is always set to a Swift symbol even for Objective-C source code doc comments. This lead to a bug when Objective-C diagnostics would always use a zero offset. Instead, save the offset of each doc comment in the DocumentationChunk structure, which is initialized using the documented symbol, i.e. the Swift or Objective-C symbol which actually contains the doc comment. Also decrement the line and character indexes by 1 for both Objective-C doc comments so Xcode displays them in the proper location.
1 parent 856a6ff commit da16aa9

File tree

7 files changed

+706
-11
lines changed

7 files changed

+706
-11
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
574574
case _ where documentationNode.semantic is Article,
575575
.documentationExtension:
576576
source = documentLocationMap[reference]
577-
case .sourceCode(location: let location):
577+
case .sourceCode(let location, _):
578578
// For symbols, first check if we should reference resolve
579579
// inherited docs or not. If we don't inherit the docs
580580
// we should also skip reference resolving the chunk.
@@ -622,11 +622,10 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
622622

623623
var problems = resolver.problems
624624

625-
if case DocumentationNode.DocumentationChunk.Source.sourceCode = doc.source,
626-
let docs = documentationNode.symbol?.docComment {
625+
if case .sourceCode(_, let offset) = doc.source {
627626
// Offset all problem ranges by the start location of the
628627
// source comment in the context of the complete file.
629-
if let docRange = docs.lines.first?.range {
628+
if let docRange = offset {
630629
for i in problems.indices {
631630
problems[i].offsetWithRange(docRange)
632631
}

Sources/SwiftDocC/Model/DocumentationNode.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -79,7 +79,7 @@ public struct DocumentationNode {
7979
/// The documentation comes from a documentation extension file.
8080
case documentationExtension
8181
/// The documentation comes from an in-source documentation comment.
82-
case sourceCode(location: SymbolGraph.Symbol.Location?)
82+
case sourceCode(location: SymbolGraph.Symbol.Location?, offset: SymbolGraph.LineList.SourceRange?)
8383
}
8484

8585
let source: Source
@@ -145,7 +145,7 @@ public struct DocumentationNode {
145145
self.semantic = semantic
146146
self.symbol = nil
147147
self.platformNames = platformNames
148-
self.docChunks = [DocumentationChunk(source: .sourceCode(location: nil), markup: markup)]
148+
self.docChunks = [DocumentationChunk(source: .sourceCode(location: nil, offset: nil), markup: markup)]
149149
self.isVirtual = isVirtual
150150

151151
if let article = semantic as? Article {
@@ -440,7 +440,8 @@ public struct DocumentationNode {
440440

441441
let documentOptions: ParseOptions = [.parseBlockDirectives, .parseSymbolLinks, .parseMinimalDoxygen]
442442
let docCommentMarkup = Document(parsing: docCommentString, options: documentOptions)
443-
443+
let offset = symbol.offsetAdjustedForInterfaceLanguage()
444+
444445
let docCommentDirectives = docCommentMarkup.children.compactMap({ $0 as? BlockDirective })
445446
if !docCommentDirectives.isEmpty {
446447
let location = symbol.mixins.getValueIfPresent(
@@ -475,7 +476,7 @@ public struct DocumentationNode {
475476

476477
var problem = Problem(diagnostic: diagnostic, possibleSolutions: [])
477478

478-
if let offset = docComment.lines.first?.range {
479+
if let offset = offset {
479480
problem.offsetWithRange(offset)
480481
}
481482

@@ -485,7 +486,10 @@ public struct DocumentationNode {
485486

486487
documentationChunks = [
487488
DocumentationChunk(
488-
source: .sourceCode(location: symbol.mixins.getValueIfPresent(for: SymbolGraph.Symbol.Location.self)),
489+
source: .sourceCode(
490+
location: symbol.mixins.getValueIfPresent(for: SymbolGraph.Symbol.Location.self),
491+
offset: offset
492+
),
489493
markup: docCommentMarkup
490494
)
491495
]
@@ -502,7 +506,7 @@ public struct DocumentationNode {
502506
}
503507
} else {
504508
markup = Document()
505-
documentationChunks = [DocumentationChunk(source: .sourceCode(location: nil), markup: markup)]
509+
documentationChunks = [DocumentationChunk(source: .sourceCode(location: nil, offset: nil), markup: markup)]
506510
}
507511

508512
return (markup: markup, docChunks: documentationChunks)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import SymbolKit
12+
13+
extension SymbolGraph.Symbol {
14+
15+
/// Return the offset of this symbol's doc comment in the original source code file,
16+
/// adjusted for the interface language as follows:
17+
///
18+
/// - Objective-C: subtract one from the start/end lines, and the start/end characters
19+
/// - Swift and other languages: return the original offset
20+
///
21+
/// - Returns: A ``SourceRange`` or nil if this symbol has no doc comment.
22+
func offsetAdjustedForInterfaceLanguage() -> SymbolGraph.LineList.SourceRange? {
23+
guard let range = docComment?.lines.first?.range else {
24+
return nil
25+
}
26+
// Return the original range for Swift and other languages
27+
guard SourceLanguage(knownLanguageIdentifier: identifier.interfaceLanguage) == .objectiveC else {
28+
return range
29+
}
30+
// Decrement the line and character indexes for Objective-C
31+
let start = range.start
32+
let end = range.end
33+
return SymbolGraph.LineList.SourceRange(
34+
start: .init(line: start.line-1, character: start.character-1),
35+
end: .init(line: end.line-1, character: end.character-1)
36+
)
37+
}
38+
}

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3908,6 +3908,25 @@ let expected = """
39083908
"Expectedly returned non-nil value for non-symbol content."
39093909
)
39103910
}
3911+
3912+
func testDiagnosticLocations() throws {
3913+
// The ObjCFrameworkWithInvalidLink.docc test bundle contains symbol
3914+
// graphs for both Obj-C and Swift, built after setting:
3915+
// "Build Multi-Language Documentation for Objective-C Only Targets" = true.
3916+
// One doc comment in the Obj-C header file contains an invalid doc
3917+
// link on line 24, columns 56-63:
3918+
// "Log a hello world message. This line contains an ``invalid`` link."
3919+
let (_, _, context) = try testBundleAndContext(copying: "ObjCFrameworkWithInvalidLink")
3920+
let problems = context.problems
3921+
XCTAssertEqual(1, problems.count)
3922+
let problem = try XCTUnwrap(problems.first)
3923+
let basename = try XCTUnwrap(problem.diagnostic.source?.lastPathComponent)
3924+
XCTAssertEqual("HelloWorldFramework.h", basename)
3925+
let start = Markdown.SourceLocation(line: 24, column: 56, source: nil)
3926+
let end = Markdown.SourceLocation(line: 24, column: 63, source: nil)
3927+
let range = try XCTUnwrap(problem.diagnostic.range)
3928+
XCTAssertEqual(start..<end, range)
3929+
}
39113930
}
39123931

39133932
func assertEqualDumps(_ lhs: String, _ rhs: String, file: StaticString = #file, line: UInt = #line) {

0 commit comments

Comments
 (0)