Skip to content

Commit

Permalink
add assertions for solutions in SymbolTests/testUnresolvedReferenceWa…
Browse files Browse the repository at this point in the history
…rningsInDocComment() + improve improve documentation about indices in source locations
  • Loading branch information
theMomax committed Feb 8, 2023
1 parent 1a916a6 commit f54a17c
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ extension Replacement {
/// Offsets the replacement using a certain `SourceRange`.
///
/// Useful when validating a doc comment that needs to be projected in its containing file "space".
///
/// - Warning: Absolute `SourceRange`s index line and column from 1. Thus, at least one
/// of `self` or `range` must be a relative range indexed from 0.
mutating func offsetWithRange(_ range: SourceRange) {
self.range.offsetWithRange(range)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extension SourceRange {

extension SourceRange {
/// Offsets the `SourceRange` using another `SourceRange`.
///
/// - Warning: Absolute `SourceRange`s index line and column from 1. Thus, at least one
/// of `self` or `range` must be a relative range indexed from 0.
mutating func offsetWithRange(_ range: SourceRange) {
let start = SourceLocation(line: lowerBound.line + range.lowerBound.line, column: lowerBound.column + range.lowerBound.column, source: nil)
let end = SourceLocation(line: upperBound.line + range.lowerBound.line, column: upperBound.column + range.lowerBound.column, source: nil)
Expand Down
137 changes: 110 additions & 27 deletions Tests/SwiftDocCTests/Semantics/SymbolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ class SymbolTests: XCTestCase {
["Insert disambiguation suffix for 'init()'", "-33vaw"],
["Insert disambiguation suffix for 'init()'", "-3743d"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -633,7 +633,7 @@ class SymbolTests: XCTestCase {
["Insert disambiguation suffix for 'init()'", "-33vaw"],
["Insert disambiguation suffix for 'init()'", "-3743d"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -679,7 +679,7 @@ class SymbolTests: XCTestCase {
["Insert disambiguation suffix for 'init()'", "-33vaw"],
["Insert disambiguation suffix for 'init()'", "-3743d"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -724,7 +724,7 @@ class SymbolTests: XCTestCase {
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to myFunction().", "myFunction()"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -769,7 +769,7 @@ class SymbolTests: XCTestCase {
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to /MyKit/MyClass.", "MyClass"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -814,7 +814,7 @@ class SymbolTests: XCTestCase {
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to MyKit/MyClass/myFunction().", "MyClass"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -859,7 +859,7 @@ class SymbolTests: XCTestCase {
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to MyKit/MyClass/myFunction().", "MyClass"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -904,7 +904,7 @@ class SymbolTests: XCTestCase {
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to MyKit/MyClass/myFunction().", "MyClass"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.apply(to: url.appendingPathComponent("documentation/myclass.md")), """
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(contentsOf: url.appendingPathComponent("documentation/myclass.md")), """
# ``MyKit/MyClass``
@Metadata {
Expand Down Expand Up @@ -950,14 +950,55 @@ class SymbolTests: XCTestCase {
}

func testUnresolvedReferenceWarningsInDocComment() throws {
let docComment = """
A cool API to call.
This overview has an ``UnresolvableSymbolLinkInMyClassOverview``.
- Parameters:
- name: A parameter
- Returns: Return value
# Topics
## Unresolvable curation
- ``UnresolvableClassInMyClassTopicCuration``
- ``MyClass/unresolvablePropertyInMyClassTopicCuration``
- <doc://com.test.external/ExternalPage>
"""

let (_, _, context) = try testBundleAndContext(copying: "TestBundle") { url in
var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent("mykit-iOS.symbols.json")))
let myFunctionUSR = "s:5MyKit0A5ClassC10myFunctionyyF"

// SymbolKit.SymbolGraph.LineList.SourceRange.Position is indexed from 0, whereas
// (absolute) Markdown.SourceLocations are indexed from 1
let newDocComment = SymbolGraph.LineList(docComment.components(separatedBy: .newlines).enumerated().map { lineNumber, lineText in
.init(text: lineText, range: .init(start: .init(line: lineNumber, character: 0), end: .init(line: lineNumber, character: lineText.count)))
})
graph.symbols[myFunctionUSR]?.docComment = newDocComment

let newGraphData = try JSONEncoder().encode(graph)
try newGraphData.write(to: url.appendingPathComponent("mykit-iOS.symbols.json"))
}

let unresolvedTopicProblems = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.unresolvedTopicReference" }

if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver {
var problem: Problem

let docComment = """
problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableSymbolLinkInMyClassOverview'. Did you mean Unresolvable-curation?" }))
XCTAssert(problem.diagnostic.notes.isEmpty)
XCTAssertEqual(problem.possibleSolutions.count, 1)
XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 })
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to Unresolvable-curation.", "Unresolvable-curation"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(docComment), """
A cool API to call.
This overview has an ``UnresolvableSymbolLinkInMyClassOverview``.
This overview has an ``Unresolvable-curation``.
- Parameters:
- name: A parameter
Expand All @@ -970,22 +1011,59 @@ class SymbolTests: XCTestCase {
- ``UnresolvableClassInMyClassTopicCuration``
- ``MyClass/unresolvablePropertyInMyClassTopicCuration``
- <doc://com.test.external/ExternalPage>
"""
""")

problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableClassInMyClassTopicCuration'. Did you mean Unresolvable-curation?" }))
XCTAssert(problem.diagnostic.notes.isEmpty)
XCTAssertEqual(problem.possibleSolutions.count, 1)
XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 })
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to Unresolvable-curation.", "Unresolvable-curation"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(docComment), """
A cool API to call.
let position: SymbolGraph.LineList.SourceRange.Position = .init(line: 1, character: 1)
let newDocComment = SymbolGraph.LineList(docComment.components(separatedBy: .newlines).map { .init(text: $0, range: .init(start: position, end: position)) })
graph.symbols[myFunctionUSR]?.docComment = newDocComment
This overview has an ``UnresolvableSymbolLinkInMyClassOverview``.
- Parameters:
- name: A parameter
- Returns: Return value
# Topics
## Unresolvable curation
- ``Unresolvable-curation``
- ``MyClass/unresolvablePropertyInMyClassTopicCuration``
- <doc://com.test.external/ExternalPage>
""")

let newGraphData = try JSONEncoder().encode(graph)
try newGraphData.write(to: url.appendingPathComponent("mykit-iOS.symbols.json"))
}

let unresolvedTopicProblems = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.unresolvedTopicReference" }

if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver {
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableSymbolLinkInMyClassOverview'. Did you mean Unresolvable-curation?" }))
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass/myFunction()' can't resolve 'UnresolvableClassInMyClassTopicCuration'. Did you mean Unresolvable-curation?" }))
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." }))

problem = try XCTUnwrap(unresolvedTopicProblems.first(where: { $0.diagnostic.localizedSummary == "Topic reference 'MyClass/unresolvablePropertyInMyClassTopicCuration' couldn't be resolved. Reference at '/MyKit/MyClass' can't resolve 'unresolvablePropertyInMyClassTopicCuration'. No similar pages." }))
XCTAssert(problem.diagnostic.notes.isEmpty)
XCTAssertEqual(problem.possibleSolutions.count, 2)
XCTAssert(problem.possibleSolutions.map(\.replacements.count).allSatisfy { $0 == 1 })
XCTAssertEqual(problem.possibleSolutions.map { [$0.summary, $0.replacements.first!.replacement] }, [
["Correct reference to MyClass/init().", "init()"],
["Correct reference to MyClass/myFunction().", "myFunction()"],
])
XCTAssertEqual(try problem.possibleSolutions.first!.applyTo(docComment), """
A cool API to call.
This overview has an ``UnresolvableSymbolLinkInMyClassOverview``.
- Parameters:
- name: A parameter
- Returns: Return value
# Topics
## Unresolvable curation
- ``UnresolvableClassInMyClassTopicCuration``
- ``MyClass/init()``
- <doc://com.test.external/ExternalPage>
""")
} else {
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableSymbolLinkInMyClassOverview' couldn't be resolved. No local documentation matches this reference." }))
XCTAssertTrue(unresolvedTopicProblems.contains(where: { $0.diagnostic.localizedSummary == "Topic reference 'UnresolvableClassInMyClassTopicCuration' couldn't be resolved. No local documentation matches this reference." }))
Expand Down Expand Up @@ -1117,8 +1195,13 @@ class SymbolTests: XCTestCase {


extension Solution {
func apply(to url: URL) throws -> String {
var content = String(data: try Data(contentsOf: url), encoding: .utf8)!
func applyTo(contentsOf url: URL) throws -> String {
let content = String(data: try Data(contentsOf: url), encoding: .utf8)!
return try self.applyTo(content)
}

func applyTo(_ content: String) throws -> String {
var content = content

// We have to make sure we don't change the indices for later replacements while applying
// earlier ones. As long as replacement ranges don't overlap it's enough to apply
Expand All @@ -1138,7 +1221,7 @@ extension SourceLocation {
for index in string.indices {
let character = string[index]

if line == self.line && column == self.column {
if line == self.line && column == self.column || line > self.line {
return index
}

Expand Down

0 comments on commit f54a17c

Please sign in to comment.