Skip to content

Commit dc6a487

Browse files
committed
Emit diagnostics for non-symbol pages
The `@AlternateRepresentation` directive is not expected for non-symbol pages, and we now emit diagnostics for this case. For example, if an `@AlternateDeclaration` directive is added to an article, the resulting diagnostic will be: ``` warning: Custom alternate representations are not supported for page kind 'Article' Alternate representations are only supported for symbols. --> ./SynonymSample.docc/Article.md:4:5-4:57 2 | 3 | @metadata { 4 + @AlternateRepresentation(``Synonyms/Synonym-5zxmc``) | ╰─suggestion: Remove this alternate representation 5 | } ``` And if a custom alternate declaration to an article is specified, the resulting dia gnostic will be: ``` warning: Page kind 'Article' is not allowed as a custom alternate language representation Symbols can only specify other symbols as custom language representations. --> ./SynonymSample.docc/Synonym-1wqxt.md:5:5-5:44 3 | @metadata { 4 | @AlternateRepresentation(``Synonyms/Synonym-5zxmc``) 5 + @AlternateRepresentation("doc:Article") | ╰─suggestion: Remove this alternate representation 6 | } ```
1 parent ab507bb commit dc6a487

File tree

2 files changed

+96
-4
lines changed

2 files changed

+96
-4
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3226,12 +3226,44 @@ extension DocumentationContext {
32263226
guard let entity = try? self.entity(with: reference), let alternateRepresentations = entity.metadata?.alternateRepresentations else { continue }
32273227

32283228
var sourceLanguageToReference: [SourceLanguage: AlternateRepresentation] = [:]
3229-
for alternateRepresentation in entity.metadata?.alternateRepresentations ?? [] {
3229+
for alternateRepresentation in alternateRepresentations {
3230+
// Check if the entity is not a symbol, as only symbols are allowed to specify custom alternate representations
3231+
guard entity.symbol != nil else {
3232+
problems.append(Problem(
3233+
diagnostic: Diagnostic(
3234+
source: alternateRepresentation.originalMarkup.range?.source,
3235+
severity: .warning,
3236+
range: alternateRepresentation.originalMarkup.range,
3237+
identifier: "org.swift.docc.AlternateRepresentation.UnsupportedPageKind",
3238+
summary: "Custom alternate representations are not supported for page kind \(entity.kind.name.singleQuoted)",
3239+
explanation: "Alternate representations are only supported for symbols."
3240+
),
3241+
possibleSolutions: removeAlternateRepresentationSolution(alternateRepresentation)
3242+
))
3243+
continue
3244+
}
3245+
32303246
guard case .resolved(.success(let alternateRepresentationReference)) = alternateRepresentation.reference,
32313247
let alternateRepresentationEntity = try? self.entity(with: alternateRepresentationReference) else {
32323248
continue
32333249
}
32343250

3251+
// Check if the resolved entity is not a symbol, as only symbols are allowed as custom alternate representations
3252+
guard alternateRepresentationEntity.symbol != nil else {
3253+
problems.append(Problem(
3254+
diagnostic: Diagnostic(
3255+
source: alternateRepresentation.originalMarkup.range?.source,
3256+
severity: .warning,
3257+
range: alternateRepresentation.originalMarkup.range,
3258+
identifier: "org.swift.docc.AlternateRepresentation.UnsupportedPageKind",
3259+
summary: "Page kind \(alternateRepresentationEntity.kind.name.singleQuoted) is not allowed as a custom alternate language representation",
3260+
explanation: "Symbols can only specify other symbols as custom language representations."
3261+
),
3262+
possibleSolutions: removeAlternateRepresentationSolution(alternateRepresentation)
3263+
))
3264+
continue
3265+
}
3266+
32353267
// Check if the documented symbol already has alternate representations from in-source annotations.
32363268
let duplicateSourceLanguages = alternateRepresentationEntity.availableSourceLanguages.intersection(entity.availableSourceLanguages)
32373269
if !duplicateSourceLanguages.isEmpty {
@@ -3250,7 +3282,6 @@ extension DocumentationContext {
32503282

32513283
let duplicateAlternateLanguages = Set(sourceLanguageToReference.keys).intersection(alternateRepresentationEntity.availableSourceLanguages)
32523284
if !duplicateAlternateLanguages.isEmpty {
3253-
let replacements = alternateRepresentation.originalMarkup.range.flatMap { [Replacement(range: $0, replacement: "")] } ?? []
32543285
let notes: [DiagnosticNote] = duplicateAlternateLanguages.compactMap { duplicateAlternateLanguage in
32553286
guard let alreadyExistingRepresentation = sourceLanguageToReference[duplicateAlternateLanguage],
32563287
let range = alreadyExistingRepresentation.originalMarkup.range,
@@ -3270,7 +3301,7 @@ extension DocumentationContext {
32703301
explanation: "Only one custom alternate language representation can be specified per language.",
32713302
notes: notes
32723303
),
3273-
possibleSolutions: [Solution(summary: "Remove this alternate representation", replacements: replacements)]
3304+
possibleSolutions: removeAlternateRepresentationSolution(alternateRepresentation)
32743305
))
32753306
}
32763307

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5435,7 +5435,7 @@ let expected = """
54355435
XCTAssertEqual(problem.diagnostic.summary, "Can't resolve 'MissingSymbol'")
54365436
}
54375437

5438-
func testDiagnosesAlternateDeclarations() throws {
5438+
func testDiagnosesSymbolAlternateDeclarations() throws {
54395439
let (_, context) = try loadBundle(catalog: Folder(
54405440
name: "unit-test.docc",
54415441
content: [
@@ -5505,6 +5505,67 @@ let expected = """
55055505
XCTAssertEqual(solution.replacements.count, 1)
55065506
XCTAssertEqual(solution.replacements.first?.replacement, "")
55075507
}
5508+
5509+
func testDiagnosesArticleAlternateDeclarations() throws {
5510+
let (_, context) = try loadBundle(catalog: Folder(
5511+
name: "unit-test.docc",
5512+
content: [
5513+
TextFile(name: "Symbol.md", utf8Content: """
5514+
# ``Symbol``
5515+
@Metadata {
5516+
@AlternateRepresentation("doc:Article")
5517+
}
5518+
A symbol extension file specifying an alternate representation which is an article.
5519+
"""),
5520+
TextFile(name: "Article.md", utf8Content: """
5521+
# Article
5522+
@Metadata {
5523+
@AlternateRepresentation(``Symbol``)
5524+
}
5525+
An article specifying a custom alternate representation.
5526+
"""),
5527+
JSONFile(
5528+
name: "unit-test.occ.symbols.json",
5529+
content: makeSymbolGraph(
5530+
moduleName: "unit-test",
5531+
symbols: [
5532+
makeSymbol(id: "symbol-id", kind: .class, pathComponents: ["Symbol"]),
5533+
]
5534+
)
5535+
)
5536+
]
5537+
))
5538+
5539+
let alternateRepresentationProblems = context.problems.sorted(by: \.diagnostic.summary)
5540+
XCTAssertEqual(alternateRepresentationProblems.count, 2)
5541+
5542+
// Verify a problem is reported for trying to define an alternate representation for a language the symbol already supports
5543+
var problem = try XCTUnwrap(alternateRepresentationProblems.first)
5544+
XCTAssertEqual(problem.diagnostic.severity, .warning)
5545+
XCTAssertEqual(problem.diagnostic.summary, "Custom alternate representations are not supported for page kind 'Article'")
5546+
XCTAssertEqual(problem.diagnostic.explanation, "Alternate representations are only supported for symbols.")
5547+
XCTAssertEqual(problem.possibleSolutions.count, 1)
5548+
5549+
// Verify solutions provide context and suggest to remove the invalid directive
5550+
var solution = try XCTUnwrap(problem.possibleSolutions.first)
5551+
XCTAssertEqual(solution.summary, "Remove this alternate representation")
5552+
XCTAssertEqual(solution.replacements.count, 1)
5553+
XCTAssertEqual(solution.replacements.first?.replacement, "")
5554+
5555+
// Verify a problem is reported for having alternate representations with duplicate source languages
5556+
problem = try XCTUnwrap(alternateRepresentationProblems[1])
5557+
XCTAssertEqual(problem.diagnostic.severity, .warning)
5558+
XCTAssertEqual(problem.diagnostic.summary, "Page kind 'Article' is not allowed as a custom alternate language representation")
5559+
XCTAssertEqual(problem.diagnostic.explanation, "Symbols can only specify other symbols as custom language representations.")
5560+
XCTAssertEqual(problem.possibleSolutions.count, 1)
5561+
5562+
// Verify solutions provide context and suggest to remove the invalid directive
5563+
solution = try XCTUnwrap(problem.possibleSolutions.first)
5564+
XCTAssertEqual(solution.summary, "Remove this alternate representation")
5565+
XCTAssertEqual(solution.replacements.count, 1)
5566+
XCTAssertEqual(solution.replacements.first?.replacement, "")
5567+
}
5568+
55085569
}
55095570

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

0 commit comments

Comments
 (0)