Skip to content

Commit 1e69ac1

Browse files
prefer longer doc comments when selecting commented symbols (#1258)
rdar://156595902
1 parent 71032cd commit 1e69ac1

File tree

2 files changed

+158
-6
lines changed

2 files changed

+158
-6
lines changed

Sources/SwiftDocC/Semantics/Symbol/UnifiedSymbol+Extensions.swift

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,31 @@ extension UnifiedSymbolGraph.Symbol {
9393

9494
/// Returns the primary symbol selector to use as documentation source.
9595
var documentedSymbolSelector: UnifiedSymbolGraph.Selector? {
96-
// We'll prioritize the first documented 'swift' symbol, if we have
97-
// one.
98-
return docComment.keys.first { selector in
99-
return selector.interfaceLanguage == "swift"
100-
} ?? docComment.keys.first
96+
// Prioritize the longest doc comment with a "swift" selector,
97+
// if there is one.
98+
return docComment.min(by: { lhs, rhs in
99+
if (lhs.key.interfaceLanguage == "swift") != (rhs.key.interfaceLanguage == "swift") {
100+
// sort swift selectors before non-swift ones
101+
return lhs.key.interfaceLanguage == "swift"
102+
}
103+
104+
// if the comments are equal, bail early without iterating them again
105+
guard lhs.value != rhs.value else {
106+
return false
107+
}
108+
109+
let lhsLength = lhs.value.lines.totalCount
110+
let rhsLength = rhs.value.lines.totalCount
111+
112+
if lhsLength == rhsLength {
113+
// if the comments are the same length, just sort them lexicographically
114+
return lhs.value.lines.isLexicographicallyBefore(rhs.value.lines)
115+
} else {
116+
// otherwise, sort by the length of the doc comment,
117+
// so that `min` returns the longest comment
118+
return lhsLength > rhsLength
119+
}
120+
})?.key
101121
}
102122

103123
func identifier(forLanguage interfaceLanguage: String) -> SymbolGraph.Symbol.Identifier {
@@ -115,3 +135,15 @@ extension UnifiedSymbolGraph.Symbol {
115135
}
116136
}
117137
}
138+
139+
extension [SymbolGraph.LineList.Line] {
140+
fileprivate var totalCount: Int {
141+
return reduce(into: 0) { result, line in
142+
result += line.text.count
143+
}
144+
}
145+
146+
fileprivate func isLexicographicallyBefore(_ other: Self) -> Bool {
147+
self.lexicographicallyPrecedes(other) { $0.text < $1.text }
148+
}
149+
}

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,127 @@ class DocumentationContextTests: XCTestCase {
10561056
└─ Text "Return value"
10571057
""")
10581058
}
1059-
1059+
1060+
func testLoadsConflictingDocComments() async throws {
1061+
let macOSSymbolGraph = makeSymbolGraph(
1062+
moduleName: "TestProject",
1063+
platform: .init(operatingSystem: .init(name: "macOS")),
1064+
symbols: [
1065+
makeSymbol(
1066+
id: "TestSymbol",
1067+
kind: .func,
1068+
pathComponents: ["TestSymbol"],
1069+
docComment: "This is a comment.",
1070+
otherMixins: [
1071+
SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [
1072+
.init(
1073+
kind: .text,
1074+
spelling: "TestSymbol Mac",
1075+
preciseIdentifier: nil)
1076+
])
1077+
])
1078+
])
1079+
let iOSSymbolGraph = makeSymbolGraph(
1080+
moduleName: "TestProject",
1081+
platform: .init(operatingSystem: .init(name: "iOS")),
1082+
symbols: [
1083+
makeSymbol(
1084+
id: "TestSymbol",
1085+
kind: .func,
1086+
pathComponents: ["TestSymbol"],
1087+
docComment: "This is a longer comment that should be shown instead.",
1088+
otherMixins: [
1089+
SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [
1090+
.init(
1091+
kind: .text,
1092+
spelling: "TestSymbol iOS",
1093+
preciseIdentifier: nil)
1094+
])
1095+
])
1096+
])
1097+
1098+
for forwards in [true, false] {
1099+
let catalog = Folder(name: "unit-test.docc", content: [
1100+
InfoPlist(displayName: "TestProject", identifier: "com.test.example"),
1101+
JSONFile(name: "symbols\(forwards ? "1" : "2").symbols.json", content:macOSSymbolGraph),
1102+
JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: iOSSymbolGraph),
1103+
])
1104+
1105+
let (bundle, context) = try await loadBundle(catalog: catalog)
1106+
1107+
let reference = ResolvedTopicReference(
1108+
bundleID: bundle.id,
1109+
path: "/documentation/TestProject/TestSymbol",
1110+
sourceLanguage: .swift
1111+
)
1112+
let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol)
1113+
let abstract = try XCTUnwrap(symbol.abstractSection)
1114+
XCTAssertEqual(
1115+
abstract.paragraph.plainText,
1116+
"This is a longer comment that should be shown instead.")
1117+
}
1118+
}
1119+
1120+
func testLoadsConflictingDocCommentsOfSameLength() async throws {
1121+
let macOSSymbolGraph = makeSymbolGraph(
1122+
moduleName: "TestProject",
1123+
platform: .init(operatingSystem: .init(name: "macOS")),
1124+
symbols: [
1125+
makeSymbol(
1126+
id: "TestSymbol",
1127+
kind: .func,
1128+
pathComponents: ["TestSymbol"],
1129+
docComment: "Comment A.",
1130+
otherMixins: [
1131+
SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [
1132+
.init(
1133+
kind: .text,
1134+
spelling: "TestSymbol Mac",
1135+
preciseIdentifier: nil)
1136+
])
1137+
])
1138+
])
1139+
let iOSSymbolGraph = makeSymbolGraph(
1140+
moduleName: "TestProject",
1141+
platform: .init(operatingSystem: .init(name: "iOS")),
1142+
symbols: [
1143+
makeSymbol(
1144+
id: "TestSymbol",
1145+
kind: .func,
1146+
pathComponents: ["TestSymbol"],
1147+
docComment: "Comment B.",
1148+
otherMixins: [
1149+
SymbolGraph.Symbol.DeclarationFragments(declarationFragments: [
1150+
.init(
1151+
kind: .text,
1152+
spelling: "TestSymbol iOS",
1153+
preciseIdentifier: nil)
1154+
])
1155+
])
1156+
])
1157+
1158+
for forwards in [true, false] {
1159+
let catalog = Folder(name: "unit-test.docc", content: [
1160+
InfoPlist(displayName: "TestProject", identifier: "com.test.example"),
1161+
JSONFile(name: "symbols\(forwards ? "1" : "2").symbols.json", content:macOSSymbolGraph),
1162+
JSONFile(name: "symbols\(forwards ? "2" : "1").symbols.json", content: iOSSymbolGraph),
1163+
])
1164+
1165+
let (bundle, context) = try await loadBundle(catalog: catalog)
1166+
1167+
let reference = ResolvedTopicReference(
1168+
bundleID: bundle.id,
1169+
path: "/documentation/TestProject/TestSymbol",
1170+
sourceLanguage: .swift
1171+
)
1172+
let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol)
1173+
let abstract = try XCTUnwrap(symbol.abstractSection)
1174+
XCTAssertEqual(
1175+
abstract.paragraph.plainText,
1176+
"Comment A.")
1177+
}
1178+
}
1179+
10601180
func testMergesMultipleSymbolDeclarations() async throws {
10611181
let graphContentiOS = try String(contentsOf: Bundle.module.url(
10621182
forResource: "LegacyBundle_DoNotUseInNewTests", withExtension: "docc", subdirectory: "Test Bundles")!

0 commit comments

Comments
 (0)