Skip to content

Commit

Permalink
Emit diagnostics for non-symbol pages
Browse files Browse the repository at this point in the history
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 | }
```
  • Loading branch information
anferbui committed Dec 13, 2024
1 parent 213ee04 commit 711469a
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
37 changes: 34 additions & 3 deletions Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3226,12 +3226,44 @@ extension DocumentationContext {
guard let entity = try? self.entity(with: reference), let alternateRepresentations = entity.metadata?.alternateRepresentations else { continue }

var sourceLanguageToReference: [SourceLanguage: AlternateRepresentation] = [:]
for alternateRepresentation in entity.metadata?.alternateRepresentations ?? [] {
for alternateRepresentation in alternateRepresentations {
// Check if the entity is not a symbol, as only symbols are allowed to specify custom alternate representations
guard entity.symbol != nil else {
problems.append(Problem(
diagnostic: Diagnostic(
source: alternateRepresentation.originalMarkup.range?.source,
severity: .warning,
range: alternateRepresentation.originalMarkup.range,
identifier: "org.swift.docc.AlternateRepresentation.UnsupportedPageKind",
summary: "Custom alternate representations are not supported for page kind \(entity.kind.name.singleQuoted)",
explanation: "Alternate representations are only supported for symbols."
),
possibleSolutions: removeAlternateRepresentationSolution(alternateRepresentation)
))
continue
}

guard case .resolved(.success(let alternateRepresentationReference)) = alternateRepresentation.reference,
let alternateRepresentationEntity = try? self.entity(with: alternateRepresentationReference) else {
continue
}

// Check if the resolved entity is not a symbol, as only symbols are allowed as custom alternate representations
guard alternateRepresentationEntity.symbol != nil else {
problems.append(Problem(
diagnostic: Diagnostic(
source: alternateRepresentation.originalMarkup.range?.source,
severity: .warning,
range: alternateRepresentation.originalMarkup.range,
identifier: "org.swift.docc.AlternateRepresentation.UnsupportedPageKind",
summary: "Page kind \(alternateRepresentationEntity.kind.name.singleQuoted) is not allowed as a custom alternate language representation",
explanation: "Symbols can only specify other symbols as custom language representations."
),
possibleSolutions: removeAlternateRepresentationSolution(alternateRepresentation)
))
continue
}

// Check if the documented symbol already has alternate representations from in-source annotations.
let duplicateSourceLanguages = alternateRepresentationEntity.availableSourceLanguages.intersection(entity.availableSourceLanguages)
if !duplicateSourceLanguages.isEmpty {
Expand All @@ -3250,7 +3282,6 @@ extension DocumentationContext {

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5435,7 +5435,7 @@ let expected = """
XCTAssertEqual(problem.diagnostic.summary, "Can't resolve 'MissingSymbol'")
}

func testDiagnosesAlternateDeclarations() throws {
func testDiagnosesSymbolAlternateDeclarations() throws {
let (_, context) = try loadBundle(catalog: Folder(
name: "unit-test.docc",
content: [
Expand Down Expand Up @@ -5505,6 +5505,67 @@ let expected = """
XCTAssertEqual(solution.replacements.count, 1)
XCTAssertEqual(solution.replacements.first?.replacement, "")
}

func testDiagnosesArticleAlternateDeclarations() throws {
let (_, context) = try loadBundle(catalog: Folder(
name: "unit-test.docc",
content: [
TextFile(name: "Symbol.md", utf8Content: """
# ``Symbol``
@Metadata {
@AlternateRepresentation("doc:Article")
}
A symbol extension file specifying an alternate representation which is an article.
"""),
TextFile(name: "Article.md", utf8Content: """
# Article
@Metadata {
@AlternateRepresentation(``Symbol``)
}
An article specifying a custom alternate representation.
"""),
JSONFile(
name: "unit-test.occ.symbols.json",
content: makeSymbolGraph(
moduleName: "unit-test",
symbols: [
makeSymbol(id: "symbol-id", kind: .class, pathComponents: ["Symbol"]),
]
)
)
]
))

let alternateRepresentationProblems = context.problems.sorted(by: \.diagnostic.summary)
XCTAssertEqual(alternateRepresentationProblems.count, 2)

// Verify a problem is reported for trying to define an alternate representation for a language the symbol already supports
var problem = try XCTUnwrap(alternateRepresentationProblems.first)
XCTAssertEqual(problem.diagnostic.severity, .warning)
XCTAssertEqual(problem.diagnostic.summary, "Custom alternate representations are not supported for page kind 'Article'")
XCTAssertEqual(problem.diagnostic.explanation, "Alternate representations are only supported for symbols.")
XCTAssertEqual(problem.possibleSolutions.count, 1)

// Verify solutions provide context and suggest to remove the invalid directive
var solution = try XCTUnwrap(problem.possibleSolutions.first)
XCTAssertEqual(solution.summary, "Remove this alternate representation")
XCTAssertEqual(solution.replacements.count, 1)
XCTAssertEqual(solution.replacements.first?.replacement, "")

// Verify a problem is reported for having alternate representations with duplicate source languages
problem = try XCTUnwrap(alternateRepresentationProblems[1])
XCTAssertEqual(problem.diagnostic.severity, .warning)
XCTAssertEqual(problem.diagnostic.summary, "Page kind 'Article' is not allowed as a custom alternate language representation")
XCTAssertEqual(problem.diagnostic.explanation, "Symbols can only specify other symbols as custom language representations.")
XCTAssertEqual(problem.possibleSolutions.count, 1)

// Verify solutions provide context and suggest to remove the invalid directive
solution = try XCTUnwrap(problem.possibleSolutions.first)
XCTAssertEqual(solution.summary, "Remove this alternate representation")
XCTAssertEqual(solution.replacements.count, 1)
XCTAssertEqual(solution.replacements.first?.replacement, "")
}

}

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

0 comments on commit 711469a

Please sign in to comment.