Skip to content

Commit ddf8f0a

Browse files
authored
Add support for language specific topic sections (#755)
* Add support for language specific topic sections rdar://118739654 * Allow SupportedLanguage anywhere in the topic section
1 parent ba3d81d commit ddf8f0a

File tree

4 files changed

+294
-11
lines changed

4 files changed

+294
-11
lines changed

Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,14 @@ public struct RenderNodeTranslator: SemanticVisitor {
10221022
contentCompiler: inout RenderContentCompiler
10231023
) -> [TaskGroupRenderSection] {
10241024
return topics.taskGroups.compactMap { group in
1025+
let supportedLanguages = Set(group.directives.compactMap {
1026+
SupportedLanguage(from: $0, source: nil, for: bundle, in: context)?.language
1027+
})
1028+
1029+
// If the task group has a set of supported languages, see if it should render for the allowed traits.
1030+
guard supportedLanguages.isEmpty || supportedLanguages.matchesOneOf(traits: allowedTraits) else {
1031+
return nil
1032+
}
10251033

10261034
let abstractContent = group.abstract.map {
10271035
return visitMarkup($0.content) as! [RenderInlineContent]
@@ -1969,3 +1977,16 @@ extension TutorialArticleSection: RenderTree {}
19691977
extension ContentLayout: RenderTree {}
19701978

19711979
extension ContentRenderSection: RenderTree {}
1980+
1981+
private extension Set where Element == SourceLanguage {
1982+
func matchesOneOf(traits: Set<DocumentationDataVariantsTrait>) -> Bool {
1983+
traits.contains(where: {
1984+
guard let languageID = $0.interfaceLanguage,
1985+
let traitLanguage = SourceLanguage(knownLanguageIdentifier: languageID)
1986+
else {
1987+
return false
1988+
}
1989+
return self.contains(traitLanguage)
1990+
})
1991+
}
1992+
}

Sources/SwiftDocC/Model/TaskGroup.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public struct TaskGroup {
187187

188188
/// An optional abstract for the task group.
189189
public var abstract: AbstractSection? {
190-
if let firstParagraph = originalContent.first as? Paragraph {
190+
if let firstParagraph = originalContent.mapFirst(where: { $0 as? Paragraph }) {
191191
return AbstractSection(paragraph: firstParagraph)
192192
}
193193
return nil
@@ -200,18 +200,15 @@ public struct TaskGroup {
200200
return nil
201201
}
202202

203-
let startIndex: Int
204-
if originalContent.first is Paragraph {
205-
startIndex = 1
206-
} else {
207-
startIndex = 0
208-
}
203+
var discussionChildren = originalContent
204+
.prefix(while: { !($0 is UnorderedList) })
205+
.filter({ !($0 is BlockDirective) })
209206

210-
let endIndex = originalContent.firstIndex(where: {
211-
$0 is UnorderedList
212-
}) ?? originalContent.endIndex
207+
// Drop the abstract
208+
if discussionChildren.first is Paragraph {
209+
discussionChildren.removeFirst()
210+
}
213211

214-
let discussionChildren = originalContent[startIndex..<endIndex]
215212
guard !discussionChildren.isEmpty else { return nil }
216213

217214
return DiscussionSection(content: Array(discussionChildren))
@@ -225,6 +222,10 @@ public struct TaskGroup {
225222
self.heading = heading
226223
self.originalContent = content
227224
}
225+
226+
var directives: [BlockDirective] {
227+
originalContent.compactMap { $0 as? BlockDirective }
228+
}
228229
}
229230

230231
extension TaskGroup {

Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3284,4 +3284,65 @@ Document
32843284
]
32853285
)
32863286
}
3287+
3288+
func testLanguageSpecificTopicSections() throws {
3289+
let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkWithLanguageRefinements") { url in
3290+
try """
3291+
# ``MixedFramework/MyObjectiveCClassObjectiveCName``
3292+
3293+
@Metadata {
3294+
@DocumentationExtension(mergeBehavior: override)
3295+
}
3296+
3297+
Provide different curation in different languages
3298+
3299+
## Topics
3300+
3301+
### Something Swift only
3302+
3303+
This link is only for Swift
3304+
3305+
@SupportedLanguage(swift)
3306+
3307+
- ``MyObjectiveCClassSwiftName/myMethodSwiftName()``
3308+
3309+
### Something Objective-C only
3310+
3311+
This link is only for Objective-C
3312+
3313+
@SupportedLanguage(objc)
3314+
3315+
- ``MyObjectiveCClassObjectiveCName/myMethodWithArgument:``
3316+
""".write(to: url.appendingPathComponent("MyObjectiveCClassObjectiveCName.md"), atomically: true, encoding: .utf8)
3317+
}
3318+
3319+
XCTAssert(context.problems.isEmpty, "\(context.problems.map(\.diagnostic.summary))")
3320+
3321+
let reference = try XCTUnwrap(context.knownIdentifiers.first { $0.path.hasSuffix("MixedFramework/MyObjectiveCClassSwiftName") })
3322+
3323+
let documentationNode = try context.entity(with: reference)
3324+
XCTAssertEqual(documentationNode.availableVariantTraits.count, 2, "This page has Swift and Objective-C variants")
3325+
3326+
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
3327+
let renderNode = try converter.convert(documentationNode, at: nil)
3328+
3329+
let topicSectionsVariants = renderNode.topicSectionsVariants
3330+
3331+
let swiftTopicSection = topicSectionsVariants.defaultValue
3332+
3333+
XCTAssertEqual(swiftTopicSection.first?.title, "Something Swift only")
3334+
XCTAssertEqual(swiftTopicSection.first?.abstract?.plainText, "This link is only for Swift")
3335+
XCTAssertEqual(swiftTopicSection.first?.identifiers, [
3336+
"doc://org.swift.MixedFramework/documentation/MixedFramework/MyObjectiveCClassSwiftName/myMethodSwiftName()"
3337+
])
3338+
3339+
let objcTopicSectionVariant = try XCTUnwrap(topicSectionsVariants.variants.first { $0.traits[0] == .interfaceLanguage("occ") })
3340+
let objcTopicSection = objcTopicSectionVariant.applyingPatchTo(swiftTopicSection)
3341+
3342+
XCTAssertEqual(objcTopicSection.first?.title, "Something Objective-C only")
3343+
XCTAssertEqual(objcTopicSection.first?.abstract?.plainText, "This link is only for Objective-C")
3344+
XCTAssertEqual(objcTopicSection.first?.identifiers, [
3345+
"doc://org.swift.MixedFramework/documentation/MixedFramework/MyObjectiveCClassSwiftName/myMethod(argument:)"
3346+
])
3347+
}
32873348
}

Tests/SwiftDocCTests/Model/TaskGroupTests.swift

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,204 @@ class TaskGroupTests: XCTestCase {
221221
XCTAssertEqual("https://www.example.com", links[1].destination)
222222
}
223223
}
224+
225+
func testSupportedLanguages() throws {
226+
let markupSource = """
227+
# Title
228+
229+
Abstract.
230+
231+
## Topics
232+
233+
### Something Swift only
234+
235+
This link is only for Swift
236+
237+
@SupportedLanguage(swift)
238+
239+
- ``Link1``
240+
241+
### Something Objective-C only
242+
243+
This link is only for Objective-C
244+
245+
@SupportedLanguage(objc)
246+
247+
- ``Link1``
248+
249+
### Something for both
250+
251+
This link is for both Swift and Objective-C
252+
253+
@SupportedLanguage(objc)
254+
@SupportedLanguage(swift)
255+
256+
- ``Link3``
257+
258+
### Something without a language filter
259+
260+
This link is for all languages
261+
262+
- ``Link4``
263+
"""
264+
let document = Document(parsing: markupSource, options: [.parseBlockDirectives, .parseSymbolLinks])
265+
let markupModel = DocumentationMarkup(markup: document)
266+
267+
XCTAssertEqual("Abstract.", Paragraph(markupModel.abstractSection?.content.compactMap { $0 as? InlineMarkup } ?? []).detachedFromParent.format())
268+
269+
let topicSection = try XCTUnwrap(markupModel.topicsSection)
270+
XCTAssertEqual(topicSection.taskGroups.count, 4)
271+
272+
do {
273+
let taskGroup = try XCTUnwrap(topicSection.taskGroups.first)
274+
XCTAssertEqual(taskGroup.heading?.detachedFromParent.format(), "### Something Swift only")
275+
XCTAssertEqual(taskGroup.abstract?.paragraph.detachedFromParent.format(), "This link is only for Swift")
276+
XCTAssertEqual(taskGroup.directives.count, 1)
277+
for directive in taskGroup.directives {
278+
XCTAssertEqual(directive.name, "SupportedLanguage")
279+
XCTAssertEqual(directive.arguments().count, 1)
280+
}
281+
XCTAssertEqual(taskGroup.links.count, 1)
282+
}
283+
284+
do {
285+
let taskGroup = try XCTUnwrap(topicSection.taskGroups.dropFirst().first)
286+
XCTAssertEqual(taskGroup.heading?.detachedFromParent.format(), "### Something Objective-C only")
287+
XCTAssertEqual(taskGroup.abstract?.paragraph.detachedFromParent.format(), "This link is only for Objective-C")
288+
XCTAssertEqual(taskGroup.directives.count, 1)
289+
for directive in taskGroup.directives {
290+
XCTAssertEqual(directive.name, "SupportedLanguage")
291+
XCTAssertEqual(directive.arguments().count, 1)
292+
}
293+
XCTAssertEqual(taskGroup.links.count, 1)
294+
}
295+
296+
do {
297+
let taskGroup = try XCTUnwrap(topicSection.taskGroups.dropFirst(2).first)
298+
XCTAssertEqual(taskGroup.heading?.detachedFromParent.format(), "### Something for both")
299+
XCTAssertEqual(taskGroup.abstract?.paragraph.detachedFromParent.format(), "This link is for both Swift and Objective-C")
300+
XCTAssertEqual(taskGroup.directives.count, 2)
301+
for directive in taskGroup.directives {
302+
XCTAssertEqual(directive.name, "SupportedLanguage")
303+
XCTAssertEqual(directive.arguments().count, 1)
304+
}
305+
XCTAssertEqual(taskGroup.links.count, 1)
306+
}
307+
308+
do {
309+
let taskGroup = try XCTUnwrap(topicSection.taskGroups.dropFirst(3).first)
310+
XCTAssertEqual(taskGroup.heading?.detachedFromParent.format(), "### Something without a language filter")
311+
XCTAssertEqual(taskGroup.abstract?.paragraph.detachedFromParent.format(), "This link is for all languages")
312+
XCTAssert(taskGroup.directives.isEmpty)
313+
XCTAssertEqual(taskGroup.links.count, 1)
314+
}
315+
}
316+
317+
func testTopicContentOrder() throws {
318+
func assertExpectedParsedTaskGroupContent(_ content: String, file: StaticString = #file, line: UInt = #line) throws {
319+
let document = Document(parsing: """
320+
# Title
321+
322+
Abstract.
323+
324+
## Topics
325+
326+
\(content)
327+
328+
""", options: [.parseBlockDirectives, .parseSymbolLinks])
329+
let markupModel = DocumentationMarkup(markup: document)
330+
331+
let topicSection = try XCTUnwrap(markupModel.topicsSection, file: file, line: line)
332+
XCTAssertEqual(topicSection.taskGroups.count, 1, file: file, line: line)
333+
334+
let taskGroup = try XCTUnwrap(topicSection.taskGroups.first, file: file, line: line)
335+
336+
XCTAssertEqual(taskGroup.heading?.title, "Topic name", file: file, line: line)
337+
XCTAssertEqual(taskGroup.abstract?.paragraph.detachedFromParent.format(), "Abstract paragraph", file: file, line: line)
338+
XCTAssertEqual(taskGroup.discussion?.content.map { $0.detachedFromParent.format() }, [
339+
"Discussion paragraph 1",
340+
"Discussion paragraph 2",
341+
], file: file, line: line)
342+
XCTAssertEqual(taskGroup.directives.count, 1, file: file, line: line)
343+
XCTAssertEqual(taskGroup.directives.first?.name, "SupportedLanguage", file: file, line: line)
344+
XCTAssertEqual(taskGroup.directives.first?.arguments().count, 1, file: file, line: line)
345+
XCTAssertEqual(taskGroup.links.map(\.destination), ["Link1", "Link2"], file: file, line: line)
346+
}
347+
348+
try assertExpectedParsedTaskGroupContent("""
349+
### Topic name
350+
351+
Abstract paragraph
352+
353+
Discussion paragraph 1
354+
355+
Discussion paragraph 2
356+
357+
@SupportedLanguage(swift)
358+
359+
- ``Link1``
360+
- ``Link2``
361+
""")
362+
363+
try assertExpectedParsedTaskGroupContent("""
364+
### Topic name
365+
366+
Abstract paragraph
367+
368+
@SupportedLanguage(swift)
369+
370+
Discussion paragraph 1
371+
372+
Discussion paragraph 2
373+
374+
- ``Link1``
375+
- ``Link2``
376+
""")
377+
378+
try assertExpectedParsedTaskGroupContent("""
379+
### Topic name
380+
381+
@SupportedLanguage(swift)
382+
383+
Abstract paragraph
384+
385+
Discussion paragraph 1
386+
387+
Discussion paragraph 2
388+
389+
- ``Link1``
390+
- ``Link2``
391+
""")
392+
393+
try assertExpectedParsedTaskGroupContent("""
394+
### Topic name
395+
396+
Abstract paragraph
397+
398+
Discussion paragraph 1
399+
400+
@SupportedLanguage(swift)
401+
402+
Discussion paragraph 2
403+
404+
- ``Link1``
405+
- ``Link2``
406+
""")
407+
408+
try assertExpectedParsedTaskGroupContent("""
409+
### Topic name
410+
411+
Abstract paragraph
412+
413+
Discussion paragraph 1
414+
415+
Discussion paragraph 2
416+
417+
- ``Link1``
418+
- ``Link2``
419+
420+
@SupportedLanguage(swift)
421+
""")
422+
423+
}
224424
}

0 commit comments

Comments
 (0)