Skip to content

Commit 7b72288

Browse files
authored
Add source URL to diagnostic about extra content in @links directive (#753)
* Add source info to diagnostics about extra content in @links rdar://118478839 * Assert that directive parsing problems have a source URL
1 parent aa6b25b commit 7b72288

File tree

2 files changed

+40
-20
lines changed

2 files changed

+40
-20
lines changed

Sources/SwiftDocC/Model/TaskGroup.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct ExtractLinks: MarkupRewriter {
2626

2727
/// Creates a warning with a suggestion to remove all paragraph elements but the first.
2828
private func problemForTrailingContent(_ paragraph: Paragraph) -> Problem {
29+
let range = paragraph.range ?? paragraph.firstChildRange()
2930
// An unexpected non-link list item found, suggest to remove it
3031
let trailingContent = Document(Paragraph(paragraph.inlineChildren.dropFirst()))
3132
let replacements = trailingContent.children.range.map({ [Replacement(range: $0, replacement: "")] }) ?? []
@@ -34,17 +35,17 @@ struct ExtractLinks: MarkupRewriter {
3435
switch mode {
3536
case .taskGroup:
3637
diagnostic = Diagnostic(
37-
source: nil,
38+
source: range?.source,
3839
severity: .warning,
39-
range: paragraph.range,
40+
range: range,
4041
identifier: "org.swift.docc.ExtraneousTaskGroupItemContent",
4142
summary: "Extraneous content found after a link in task group list item"
4243
)
4344
case .linksDirective:
4445
diagnostic = Diagnostic(
45-
source: nil,
46+
source: range?.source,
4647
severity: .warning,
47-
range: paragraph.range,
48+
range: range,
4849
identifier: "org.swift.docc.ExtraneousLinksDirectiveItemContent",
4950
summary: "Extraneous content found after a link",
5051
explanation: "\(Links.directiveName.singleQuoted) can only contain a bulleted list of documentation links"
@@ -64,15 +65,15 @@ struct ExtractLinks: MarkupRewriter {
6465
switch mode {
6566
case .taskGroup:
6667
diagnostic = Diagnostic(
67-
source: nil,
68+
source: range?.source,
6869
severity: .warning,
6970
range: range,
7071
identifier: "org.swift.docc.UnexpectedTaskGroupItem",
7172
summary: "Only links are allowed in task group list items"
7273
)
7374
case .linksDirective:
7475
diagnostic = Diagnostic(
75-
source: nil,
76+
source: range?.source,
7677
severity: .warning,
7778
range: range,
7879
identifier: "org.swift.docc.UnexpectedLinksDirectiveListItem",
@@ -125,6 +126,7 @@ struct ExtractLinks: MarkupRewriter {
125126

126127
// Warn if there is a trailing content after the link
127128
if containsInvalidContent {
129+
128130
problems.append(problemForTrailingContent(paragraph))
129131
}
130132
return false
@@ -239,3 +241,8 @@ extension TaskGroup {
239241
}
240242
}
241243

244+
private extension SourceRange {
245+
var source: URL? {
246+
lowerBound.source ?? upperBound.source
247+
}
248+
}

Tests/SwiftDocCTests/XCTestCase+LoadingTestData.swift

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,24 +133,28 @@ extension XCTestCase {
133133

134134
func parseDirective<Directive: DirectiveConvertible>(
135135
_ directive: Directive.Type,
136-
source: () -> String
136+
content: () -> String,
137+
file: StaticString = #file,
138+
line: UInt = #line
137139
) throws -> (problemIdentifiers: [String], directive: Directive?) {
138140
let (bundle, context) = try testBundleAndContext()
139141

140-
let document = Document(parsing: source(), options: .parseBlockDirectives)
142+
let source = URL(fileURLWithPath: "/path/to/test-source-\(ProcessInfo.processInfo.globallyUniqueString)")
143+
let document = Document(parsing: content(), source: source, options: .parseBlockDirectives)
141144

142-
let blockDirectiveContainer = try XCTUnwrap(document.child(at: 0) as? BlockDirective)
145+
let blockDirectiveContainer = try XCTUnwrap(document.child(at: 0) as? BlockDirective, file: file, line: line)
143146

144147
var problems = [Problem]()
145148
let directive = directive.init(
146149
from: blockDirectiveContainer,
147-
source: nil,
150+
source: source,
148151
for: bundle,
149152
in: context,
150153
problems: &problems
151154
)
152155

153156
let problemIDs = problems.map { problem -> String in
157+
XCTAssertNotNil(problem.diagnostic.source, "Problem \(problem.diagnostic.identifier) is missing a source URL.", file: file, line: line)
154158
let line = problem.diagnostic.range?.lowerBound.line.description ?? "unknown-line"
155159

156160
return "\(line): \(problem.diagnostic.severity)\(problem.diagnostic.identifier)"
@@ -162,12 +166,16 @@ extension XCTestCase {
162166
func parseDirective<Directive: RenderableDirectiveConvertible>(
163167
_ directive: Directive.Type,
164168
in bundleName: String? = nil,
165-
source: () -> String
169+
content: () -> String,
170+
file: StaticString = #file,
171+
line: UInt = #line
166172
) throws -> (renderBlockContent: [RenderBlockContent], problemIdentifiers: [String], directive: Directive?) {
167173
let (renderedContent, problems, directive, _) = try parseDirective(
168174
directive,
169175
in: bundleName,
170-
source: source
176+
content: content,
177+
file: file,
178+
line: line
171179
)
172180

173181
return (renderedContent, problems, directive)
@@ -176,7 +184,9 @@ extension XCTestCase {
176184
func parseDirective<Directive: RenderableDirectiveConvertible>(
177185
_ directive: Directive.Type,
178186
in bundleName: String? = nil,
179-
source: () -> String
187+
content: () -> String,
188+
file: StaticString = #file,
189+
line: UInt = #line
180190
) throws -> (
181191
renderBlockContent: [RenderBlockContent],
182192
problemIdentifiers: [String],
@@ -193,18 +203,19 @@ extension XCTestCase {
193203
}
194204
context.diagnosticEngine.clearDiagnostics()
195205

196-
let document = Document(parsing: source(), options: [.parseBlockDirectives, .parseSymbolLinks])
206+
let source = URL(fileURLWithPath: "/path/to/test-source-\(ProcessInfo.processInfo.globallyUniqueString)")
207+
let document = Document(parsing: content(), source: source, options: [.parseBlockDirectives, .parseSymbolLinks])
197208

198-
let blockDirectiveContainer = try XCTUnwrap(document.child(at: 0) as? BlockDirective)
209+
let blockDirectiveContainer = try XCTUnwrap(document.child(at: 0) as? BlockDirective, file: file, line: line)
199210

200-
var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle)
211+
var analyzer = SemanticAnalyzer(source: source, context: context, bundle: bundle)
201212
let result = analyzer.visit(blockDirectiveContainer)
202213
context.diagnosticEngine.emit(analyzer.problems)
203214

204215
var referenceResolver = MarkupReferenceResolver(
205216
context: context,
206217
bundle: bundle,
207-
source: nil,
218+
source: source,
208219
rootReference: bundle.rootReference
209220
)
210221

@@ -213,7 +224,8 @@ extension XCTestCase {
213224

214225
func problemIDs() throws -> [String] {
215226
try context.problems.map { problem -> (line: Int, severity: String, id: String) in
216-
let line = try XCTUnwrap(problem.diagnostic.range).lowerBound.line
227+
XCTAssertNotNil(problem.diagnostic.source, "Problem \(problem.diagnostic.identifier) is missing a source URL.", file: file, line: line)
228+
let line = try XCTUnwrap(problem.diagnostic.range, file: file, line: line).lowerBound.line
217229
return (line, problem.diagnostic.severity.description, problem.diagnostic.identifier)
218230
}
219231
.sorted { lhs, rhs in
@@ -246,15 +258,16 @@ extension XCTestCase {
246258
)
247259

248260
let renderedContent = try XCTUnwrap(
249-
directive.render(with: &contentCompiler) as? [RenderBlockContent]
261+
directive.render(with: &contentCompiler) as? [RenderBlockContent],
262+
file: file, line: line
250263
)
251264

252265
let collectedReferences = contentCompiler.videoReferences
253266
.mapValues { $0 as RenderReference }
254267
.merging(
255268
contentCompiler.imageReferences,
256269
uniquingKeysWith: { videoReference, _ in
257-
XCTFail("Non-unique references.")
270+
XCTFail("Non-unique references.", file: file, line: line)
258271
return videoReference
259272
}
260273
)

0 commit comments

Comments
 (0)