Skip to content

Commit 2e35427

Browse files
Fix a crash when configuring a @DisplayName for a non-symbol (#783)
* Preserve all other parsed metadata when article has @DisplayName rdar://114654594 * Hide metadata initializer that only exist to workaround #468 * Update year in header comments * Apply suggestions from code review Co-authored-by: mayaepps <[email protected]> * Clarify test assertion descriptions --------- Co-authored-by: mayaepps <[email protected]>
1 parent 8db2336 commit 2e35427

File tree

5 files changed

+121
-24
lines changed

5 files changed

+121
-24
lines changed

Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -276,7 +276,7 @@ public class OutOfProcessReferenceResolver: ExternalReferenceResolver, FallbackR
276276
// with a render node.
277277

278278
if let topicImages = resolvedInformation.topicImages, !topicImages.isEmpty {
279-
let metadata = node.metadata ?? Metadata(originalMarkup: BlockDirective(name: "Metadata", children: []), documentationExtension: nil, technologyRoot: nil, displayName: nil, titleHeading: nil)
279+
let metadata = node.metadata ?? Metadata._make(originalMarkup: BlockDirective(name: "Metadata", children: []))
280280

281281
metadata.pageImages = topicImages.map { topicImage in
282282
let purpose: PageImage.Purpose

Sources/SwiftDocC/Semantics/Article/Article.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -217,8 +217,8 @@ public final class Article: Semantic, MarkupConvertible, Abstracted, Redirected,
217217

218218
problems.append(Problem(diagnostic: diagnostic, possibleSolutions: solutions))
219219

220-
// Remove the display name customization from the article's metadata.
221-
optionalMetadata = Metadata(originalMarkup: metadata.originalMarkup, documentationExtension: metadata.documentationOptions, technologyRoot: metadata.technologyRoot, displayName: nil, titleHeading: metadata.titleHeading)
220+
metadata.displayName = nil
221+
optionalMetadata = metadata
222222
}
223223

224224
self.init(

Sources/SwiftDocC/Semantics/Metadata/Metadata.swift

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -89,21 +89,6 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
8989
"titleHeading" : \Metadata._titleHeading,
9090
]
9191

92-
/// Creates a metadata object with a given markup, documentation extension, and technology root.
93-
/// - Parameters:
94-
/// - originalMarkup: The original markup for this metadata directive.
95-
/// - documentationExtension: Optional configuration that describes how this documentation extension file merges or overrides the in-source documentation.
96-
/// - technologyRoot: Optional configuration to make this page root-level documentation.
97-
/// - displayName: Optional configuration to customize this page's symbol's display name.
98-
/// - titleHeading: Optional configuration to customize the text of this page's title heading.
99-
init(originalMarkup: BlockDirective, documentationExtension: DocumentationExtension?, technologyRoot: TechnologyRoot?, displayName: DisplayName?, titleHeading: TitleHeading?) {
100-
self.originalMarkup = originalMarkup
101-
self.documentationOptions = documentationExtension
102-
self.technologyRoot = technologyRoot
103-
self.displayName = displayName
104-
self.titleHeading = titleHeading
105-
}
106-
10792
@available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.")
10893
init(originalMarkup: BlockDirective) {
10994
self.originalMarkup = originalMarkup
@@ -203,5 +188,76 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
203188

204189
return true
205190
}
191+
192+
// MARK: Private API for OutOfProcessReferenceResolver
193+
194+
/// Don't use this outside of ``OutOfProcessReferenceResolver/entity(with:)`` .
195+
///
196+
/// Directives aren't meant to be created from non-markup but the out-of-process resolver needs to create a ``Metadata`` to hold the ``PageImage``
197+
/// values that it creates to associate topic images with external pages. This is because DocC renders external content in the local context. (rdar://78718811)
198+
/// https://github.com/apple/swift-docc/issues/468
199+
///
200+
/// This is intentionally defined as an underscore prefixed static function instead of an initializer to make it less likely that it's used in other places.
201+
static func _make(
202+
originalMarkup: BlockDirective,
203+
documentationOptions: DocumentationExtension? = nil,
204+
technologyRoot: TechnologyRoot? = nil,
205+
displayName: DisplayName? = nil,
206+
pageImages: [PageImage] = [],
207+
customMetadata: [CustomMetadata] = [],
208+
callToAction: CallToAction? = nil,
209+
availability: [Metadata.Availability] = [],
210+
pageKind: Metadata.PageKind? = nil,
211+
supportedLanguages: [SupportedLanguage] = [],
212+
_pageColor: PageColor? = nil,
213+
titleHeading: TitleHeading? = nil
214+
) -> Metadata {
215+
// FIXME: https://github.com/apple/swift-docc/issues/468
216+
return Metadata(
217+
originalMarkup: originalMarkup,
218+
documentationOptions: documentationOptions,
219+
technologyRoot: technologyRoot,
220+
displayName: displayName,
221+
pageImages: pageImages,
222+
customMetadata: customMetadata,
223+
callToAction: callToAction,
224+
availability: availability,
225+
pageKind: pageKind,
226+
supportedLanguages: supportedLanguages,
227+
_pageColor: _pageColor,
228+
titleHeading: titleHeading
229+
)
230+
}
231+
232+
// This initializer only exists to be called by `_make` above.
233+
private init(
234+
originalMarkup: BlockDirective,
235+
documentationOptions: DocumentationExtension?,
236+
technologyRoot: TechnologyRoot?,
237+
displayName: DisplayName?,
238+
pageImages: [PageImage],
239+
customMetadata: [CustomMetadata],
240+
callToAction: CallToAction?,
241+
availability: [Metadata.Availability],
242+
pageKind: Metadata.PageKind?,
243+
supportedLanguages: [SupportedLanguage],
244+
_pageColor: PageColor?,
245+
titleHeading: TitleHeading?
246+
) {
247+
self.originalMarkup = originalMarkup
248+
self.documentationOptions = documentationOptions
249+
self.technologyRoot = technologyRoot
250+
self.displayName = displayName
251+
self.callToAction = callToAction
252+
self._pageColor = _pageColor
253+
self.pageKind = pageKind
254+
self.titleHeading = titleHeading
255+
// Non-optional child directives need to be set after `super.init()`.
256+
super.init()
257+
self.customMetadata = customMetadata
258+
self.pageImages = pageImages
259+
self.availability = availability
260+
self.supportedLanguages = supportedLanguages
261+
}
206262
}
207263

Tests/SwiftDocCTests/Infrastructure/TestExternalReferenceResolvers.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2023-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -159,7 +159,7 @@ class TestMultiResultExternalReferenceResolver: ExternalReferenceResolver, Fallb
159159
// This is a workaround for how external content is processed. See details in OutOfProcessReferenceResolver.addImagesAndCacheMediaReferences(to:from:)
160160

161161
if let topicImages = entityInfo.topicImages {
162-
let metadata = node.metadata ?? Metadata(originalMarkup: BlockDirective(name: "Metadata", children: []), documentationExtension: nil, technologyRoot: nil, displayName: nil, titleHeading: nil)
162+
let metadata = node.metadata ?? Metadata._make(originalMarkup: BlockDirective(name: "Metadata", children: []))
163163

164164
metadata.pageImages = topicImages.map { topicImage, alt in
165165
let purpose: PageImage.Purpose

Tests/SwiftDocCTests/Semantics/ArticleTests.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -208,4 +208,45 @@ class ArticleTests: XCTestCase {
208208

209209
XCTAssertEqual(article?.options[.local]?.automaticSeeAlsoEnabled, false)
210210
}
211+
212+
func testDisplayNameDirectiveIsRemoved() throws {
213+
let source = """
214+
# Root
215+
216+
@Metadata {
217+
@TechnologyRoot
218+
@PageColor(purple)
219+
@DisplayName("Example")
220+
}
221+
222+
Adding @DisplayName to an article will result in a warning.
223+
"""
224+
let document = Document(parsing: source, options: [.parseBlockDirectives])
225+
let (bundle, context) = try testBundleAndContext()
226+
var problems = [Problem]()
227+
let article = Article(from: document, source: nil, for: bundle, in: context, problems: &problems)
228+
229+
XCTAssertEqual(problems.map(\.diagnostic.summary), [
230+
"A 'DisplayName' directive is only supported in documentation extension files. To customize the display name of an article, change the content of the level-1 heading."
231+
])
232+
233+
let semantic = try XCTUnwrap(article)
234+
XCTAssertNotNil(semantic.metadata, "Article should have a metadata container since the markup has a @Metadata directive")
235+
XCTAssertNotNil(semantic.metadata?.technologyRoot, "Article should have a technology root configuration since the markup has a @TechnologyRoot directive")
236+
XCTAssertNotNil(semantic.metadata?.pageColor, "Article should have a page color configuration since the markup has a @PageColor directive")
237+
238+
XCTAssertNil(semantic.metadata?.displayName, "Articles shouldn't have a display name metadata configuration, even though the markup has a @DisplayName directive. Article names are specified by the level-1 header instead of a metadata directive.")
239+
240+
// Non-optional child directives should be initialized.
241+
XCTAssertEqual(semantic.metadata?.pageImages, [])
242+
XCTAssertEqual(semantic.metadata?.customMetadata, [])
243+
XCTAssertEqual(semantic.metadata?.availability, [])
244+
XCTAssertEqual(semantic.metadata?.supportedLanguages, [])
245+
246+
// Optional child directives should default to nil
247+
XCTAssertNil(semantic.metadata?.documentationOptions)
248+
XCTAssertNil(semantic.metadata?.callToAction)
249+
XCTAssertNil(semantic.metadata?.pageKind)
250+
XCTAssertNil(semantic.metadata?.titleHeading)
251+
}
211252
}

0 commit comments

Comments
 (0)