Skip to content

Commit 563b958

Browse files
authored
Support documenting platform specific properties and return values (#979)
* Support documenting platform specific properties and return values rdar://130496794 * Add comment that the redundant `equalElements(_:by:)` check is faster in the common case.
1 parent c11da37 commit 563b958

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

Sources/SwiftDocC/Model/ParametersAndReturnValidator.swift

+29-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,35 @@ struct ParametersAndReturnValidator {
278278
guard let signature = mixin.getValueIfPresent(for: SymbolGraph.Symbol.FunctionSignature.self) else {
279279
continue
280280
}
281-
signatures[DocumentationDataVariantsTrait(for: selector)] = signature
281+
282+
let trait = DocumentationDataVariantsTrait(for: selector)
283+
// Check if we've already encountered a different signature for another platform
284+
guard var existing = signatures.removeValue(forKey: trait) else {
285+
signatures[trait] = signature
286+
continue
287+
}
288+
289+
// An internal helper function that compares parameter names
290+
func hasSameNames(_ lhs: SymbolGraph.Symbol.FunctionSignature.FunctionParameter, _ rhs: SymbolGraph.Symbol.FunctionSignature.FunctionParameter) -> Bool {
291+
lhs.name == rhs.name && lhs.externalName == rhs.externalName
292+
}
293+
// If the two signatures have different parameters, add any missing parameters.
294+
// This allows for documenting parameters that are only available on some platforms.
295+
//
296+
// Note: Doing this redundant `elementsEqual(_:by:)` check is significantly faster in the common case when all platforms have the same signature.
297+
// In the rare case where platforms have different signatures, the overhead of checking `elementsEqual(_:by:)` first is too small to measure.
298+
if !existing.parameters.elementsEqual(signature.parameters, by: hasSameNames) {
299+
for case .insert(offset: let offset, element: let element, _) in signature.parameters.difference(from: existing.parameters, by: hasSameNames) {
300+
existing.parameters.insert(element, at: offset)
301+
}
302+
}
303+
304+
// If the already encountered signature has a void return type, replace it with the non-void return type.
305+
// This allows for documenting the return values that are only available on some platforms.
306+
if existing.returns != signature.returns, existing.returns == knownVoidReturnValuesByLanguage[.init(id: selector.interfaceLanguage)] {
307+
existing.returns = signature.returns
308+
}
309+
signatures[trait] = existing
282310
}
283311

284312
guard !signatures.isEmpty else { return nil }

Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift

+58
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,62 @@ class ParametersAndReturnValidatorTests: XCTestCase {
393393
XCTAssertEqual(returnsSections[.objectiveC]?.content.map({ $0.format() }).joined(), "Some return value description.")
394394
}
395395

396+
func testFunctionWithDifferentSignaturesOnDifferentPlatforms() throws {
397+
let url = try createTempFolder(content: [
398+
Folder(name: "unit-test.docc", content: [
399+
// One parameter, void return
400+
JSONFile(name: "Platform1-ModuleName.symbols.json", content: makeSymbolGraph(
401+
platform: .init(operatingSystem: .init(name: "Platform1")),
402+
docComment: nil,
403+
sourceLanguage: .objectiveC,
404+
parameters: [(name: "first", externalName: nil)],
405+
returnValue: .init(kind: .typeIdentifier, spelling: "void", preciseIdentifier: "c:v")
406+
)),
407+
// Two parameters, void return
408+
JSONFile(name: "Platform2-ModuleName.symbols.json", content: makeSymbolGraph(
409+
platform: .init(operatingSystem: .init(name: "Platform2")),
410+
docComment: nil,
411+
sourceLanguage: .objectiveC,
412+
parameters: [(name: "first", externalName: nil), (name: "second", externalName: nil)],
413+
returnValue: .init(kind: .typeIdentifier, spelling: "void", preciseIdentifier: "c:v")
414+
)),
415+
// One parameter, BOOL return
416+
JSONFile(name: "Platform3-ModuleName.symbols.json", content: makeSymbolGraph(
417+
platform: .init(operatingSystem: .init(name: "Platform3")),
418+
docComment: nil,
419+
sourceLanguage: .objectiveC,
420+
parameters: [(name: "first", externalName: nil),],
421+
returnValue: .init(kind: .typeIdentifier, spelling: "BOOL", preciseIdentifier: "c:@T@BOOL")
422+
)),
423+
TextFile(name: "Extension.md", utf8Content: """
424+
# ``functionName(...)``
425+
426+
A documentation extension that documents both parameters
427+
428+
- Parameters:
429+
- first: Some description of the parameter that is available on all three platforms.
430+
- second: Some description of the parameter that is only available on platform 2.
431+
- Returns: Some description of the return value that is only available on platform 3.
432+
""")
433+
])
434+
])
435+
let (_, bundle, context) = try loadBundle(from: url)
436+
437+
XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))")
438+
439+
let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift)
440+
let node = try context.entity(with: reference)
441+
let symbol = try XCTUnwrap(node.semantic as? Symbol)
442+
443+
let parameterSections = symbol.parametersSectionVariants
444+
XCTAssertEqual(parameterSections[.objectiveC]?.parameters.map(\.name), ["first", "second"])
445+
XCTAssertEqual(parameterSections[.objectiveC]?.parameters.first?.contents.map({ $0.format() }).joined(), "Some description of the parameter that is available on all three platforms.")
446+
XCTAssertEqual(parameterSections[.objectiveC]?.parameters.last?.contents.map({ $0.format() }).joined(), "Some description of the parameter that is only available on platform 2.")
447+
448+
let returnSections = symbol.returnsSectionVariants
449+
XCTAssertEqual(returnSections[.objectiveC]?.content.map({ $0.format() }).joined(), "Some description of the return value that is only available on platform 3.")
450+
}
451+
396452
func testFunctionWithErrorParameterButVoidType() throws {
397453
let url = try createTempFolder(content: [
398454
Folder(name: "unit-test.docc", content: [
@@ -619,6 +675,7 @@ class ParametersAndReturnValidatorTests: XCTestCase {
619675
}
620676

621677
private func makeSymbolGraph(
678+
platform: SymbolGraph.Platform = .init(),
622679
docComment: String?,
623680
sourceLanguage: SourceLanguage,
624681
parameters: [(name: String, externalName: String?)],
@@ -634,6 +691,7 @@ class ParametersAndReturnValidatorTests: XCTestCase {
634691

635692
return makeSymbolGraph(
636693
moduleName: "ModuleName",
694+
platform: platform,
637695
symbols: [
638696
.init(
639697
identifier: .init(precise: "symbol-id", interfaceLanguage: sourceLanguage.id),

0 commit comments

Comments
 (0)