From 2d764a601bc594eafc747a7d90c1c29d53203f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 5 Dec 2025 14:01:23 +0100 Subject: [PATCH 1/3] Add a helper function for rendering a returns sections as HTML rdar://163326857 --- .../DocCHTML/MarkdownRenderer+Returns.swift | 52 ++++++++++++++++ .../MarkdownRenderer+PageElementsTests.swift | 60 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 Sources/DocCHTML/MarkdownRenderer+Returns.swift diff --git a/Sources/DocCHTML/MarkdownRenderer+Returns.swift b/Sources/DocCHTML/MarkdownRenderer+Returns.swift new file mode 100644 index 000000000..d4c1c9171 --- /dev/null +++ b/Sources/DocCHTML/MarkdownRenderer+Returns.swift @@ -0,0 +1,52 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +#if canImport(FoundationXML) +// TODO: Consider other HTML rendering options as a future improvement (rdar://165755530) +package import FoundationXML +#else +package import Foundation +#endif + +package import Markdown +package import DocCCommon + +package extension MarkdownRenderer { + /// Creates a "returns" section that describes all return values of a symbol. + /// + /// If each language representation of the symbol have their own language-specific return values, pass the return value content for all language representations. + /// + /// If all language representations of the symbol have the _same_ return value, only pass the return value content for one language. + /// This produces a "returns" section that doesn't hide the return value content for any of the languages (same as if the symbol only had one language representation) + func returns(_ languageSpecificSections: [SourceLanguage: [any Markup]]) -> [XMLNode] { + let info = RenderHelpers.sortedLanguageSpecificValues(languageSpecificSections) + let items: [XMLNode] = if info.count == 1 { + info.first!.value.map { visit($0) } + } else { + info.flatMap { language, content in + let attributes = ["class": "\(language.id)-only"] + // Most return sections only have 1 paragraph of content with 2 and 3 paragraphs being increasingly uncommon. + // Avoid wrapping that content in a `
` or other container element and instead add the language specific class attribute to each paragraph. + return content.map { markup in + let node = visit(markup) + if let element = node as? XMLElement { + element.addAttributes(attributes) + return element + } else { + // Any text _should_ already be contained in a markdown paragraph, but if the input is unexpected, wrap the raw text in a paragraph here. + return .element(named: "p", children: [node], attributes: attributes) + } + } + } + } + + return selfReferencingSection(named: "Return Value", content: items) + } +} diff --git a/Tests/DocCHTMLTests/MarkdownRenderer+PageElementsTests.swift b/Tests/DocCHTMLTests/MarkdownRenderer+PageElementsTests.swift index 2ab9c7cd5..51f943733 100644 --- a/Tests/DocCHTMLTests/MarkdownRenderer+PageElementsTests.swift +++ b/Tests/DocCHTMLTests/MarkdownRenderer+PageElementsTests.swift @@ -269,6 +269,66 @@ struct MarkdownRenderer_PageElementsTests { """) } + @Test(arguments: RenderGoal.allCases) + func testRenderSingleLanguageReturnSections(goal: RenderGoal) { + let returns = makeRenderer(goal: goal).returns([ + .swift: parseMarkup(string: "First paragraph\n\nSecond paragraph") + ]) + + let commonHTML = """ +

First paragraph

+

Second paragraph

+ """ + + switch goal { + case .richness: + returns.assertMatches(prettyFormatted: true, expectedXMLString: """ +
+

+ Return Value +

+ \(commonHTML) +
+ """) + case .conciseness: + returns.assertMatches(prettyFormatted: true, expectedXMLString: """ +

Return Value

+ \(commonHTML) + """) + } + } + + @Test(arguments: RenderGoal.allCases) + func testRenderLanguageSpecificReturnSections(goal: RenderGoal) { + let returns = makeRenderer(goal: goal).returns([ + .swift: parseMarkup(string: "First paragraph\n\nSecond paragraph"), + .objectiveC: parseMarkup(string: "Other language's paragraph"), + ]) + + let commonHTML = """ +

First paragraph

+

Second paragraph

+

Other language’s paragraph

+ """ + + switch goal { + case .richness: + returns.assertMatches(prettyFormatted: true, expectedXMLString: """ +
+

+ Return Value +

+ \(commonHTML) +
+ """) + case .conciseness: + returns.assertMatches(prettyFormatted: true, expectedXMLString: """ +

Return Value

+ \(commonHTML) + """) + } + } + // MARK: - private func makeRenderer( From e73a61f332f7ff4a283bf79ad5783ffea7b76d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 5 Dec 2025 14:04:38 +0100 Subject: [PATCH 2/3] Minor phrasing updates to the parameter section helper's documentation --- Sources/DocCHTML/MarkdownRenderer+Parameters.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/DocCHTML/MarkdownRenderer+Parameters.swift b/Sources/DocCHTML/MarkdownRenderer+Parameters.swift index 46e2ac32b..df511e6d8 100644 --- a/Sources/DocCHTML/MarkdownRenderer+Parameters.swift +++ b/Sources/DocCHTML/MarkdownRenderer+Parameters.swift @@ -34,9 +34,9 @@ package extension MarkdownRenderer { /// Creates a "parameters" section that describes all the parameters for a symbol. /// - /// If each language representation of the API has their own language-specific parameters, pass each language representation's parameter information. + /// If each language representation of the symbol have their own language-specific parameters, pass the parameter information for all language representations. /// - /// If the API has the _same_ parameters in all language representations, only pass the parameters for one language. + /// If all language representations of the symbol have the _same_ parameters, only pass the parameter information for one language. /// This produces a "parameters" section that doesn't hide any parameters for any of the languages (same as if the symbol only had one language representation) func parameters(_ info: [SourceLanguage: [ParameterInfo]]) -> [XMLNode] { let info = RenderHelpers.sortedLanguageSpecificValues(info) From 70f6b468c5cbe4446ad10fcd4196a3902300d369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 5 Dec 2025 14:31:10 +0100 Subject: [PATCH 3/3] Add new source file to Windows CMake file list --- Sources/DocCHTML/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/DocCHTML/CMakeLists.txt b/Sources/DocCHTML/CMakeLists.txt index 531ccff0b..6c15d451e 100644 --- a/Sources/DocCHTML/CMakeLists.txt +++ b/Sources/DocCHTML/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(DocCHTML STATIC MarkdownRenderer+Availability.swift MarkdownRenderer+Breadcrumbs.swift MarkdownRenderer+Parameters.swift + MarkdownRenderer+Returns.swift MarkdownRenderer.swift WordBreak.swift XMLNode+element.swift)