From 86a46bd68f14a6f8ef82b9ea11d48941828da24e Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Mon, 15 Apr 2024 17:52:55 +0100 Subject: [PATCH 1/3] Changed withFirstWordCapitalized from a computed property to a function. --- .../RenderBlockContent+Capitalization.swift | 32 ++++++++--------- .../Rendering/RenderContentCompiler.swift | 2 +- .../DiscussionSectionTranslator.swift | 2 +- .../ParametersSectionTranslator.swift | 2 +- .../ReturnsSectionTranslator.swift | 2 +- ...nderBlockContent+CapitalizationTests.swift | 34 +++++++++---------- 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift index 06614e5621..36bcba74d7 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift @@ -12,26 +12,24 @@ protocol AutoCapitalizable { /// Any type that conforms to the AutoCapitalizable protocol will have the first letter of the first word capitalized (if applicable). - var withFirstWordCapitalized: Self { - get - } + func capitalizeFirstWord() -> Self } extension AutoCapitalizable { - var withFirstWordCapitalized: Self { return self } + func capitalizeFirstWord() -> Self { return self } } extension RenderInlineContent: AutoCapitalizable { /// Capitalize the first word for normal text content, as well as content that has emphasis or strong applied. - var withFirstWordCapitalized: Self { + func capitalizeFirstWord() -> Self { switch self { case .text(let text): return .text(text.capitalizeFirstWord()) case .emphasis(inlineContent: let embeddedContent): - return .emphasis(inlineContent: [embeddedContent[0].withFirstWordCapitalized] + embeddedContent[1...]) + return .emphasis(inlineContent: [embeddedContent[0].capitalizeFirstWord()] + embeddedContent[1...]) case .strong(inlineContent: let embeddedContent): - return .strong(inlineContent: [embeddedContent[0].withFirstWordCapitalized] + embeddedContent[1...]) + return .strong(inlineContent: [embeddedContent[0].capitalizeFirstWord()] + embeddedContent[1...]) default: return self } @@ -41,14 +39,14 @@ extension RenderInlineContent: AutoCapitalizable { extension RenderBlockContent: AutoCapitalizable { /// Capitalize the first word for paragraphs, asides, headings, and small content. - var withFirstWordCapitalized: Self { + func capitalizeFirstWord() -> Self { switch self { case .paragraph(let paragraph): - return .paragraph(paragraph.withFirstWordCapitalized) + return .paragraph(paragraph.capitalizeFirstWord()) case .aside(let aside): - return .aside(aside.withFirstWordCapitalized) + return .aside(aside.capitalizeFirstWord()) case .small(let small): - return .small(small.withFirstWordCapitalized) + return .small(small.capitalizeFirstWord()) case .heading(let heading): return .heading(.init(level: heading.level, text: heading.text.capitalizeFirstWord(), anchor: heading.anchor)) default: @@ -58,34 +56,34 @@ extension RenderBlockContent: AutoCapitalizable { } extension RenderBlockContent.Paragraph: AutoCapitalizable { - var withFirstWordCapitalized: RenderBlockContent.Paragraph { + func capitalizeFirstWord() -> RenderBlockContent.Paragraph { guard !self.inlineContent.isEmpty else { return self } - let inlineContent = [self.inlineContent[0].withFirstWordCapitalized] + self.inlineContent[1...] + let inlineContent = [self.inlineContent[0].capitalizeFirstWord()] + self.inlineContent[1...] return .init(inlineContent: inlineContent) } } extension RenderBlockContent.Aside: AutoCapitalizable { - var withFirstWordCapitalized: RenderBlockContent.Aside { + func capitalizeFirstWord() -> RenderBlockContent.Aside { guard !self.content.isEmpty else { return self } - let content = [self.content[0].withFirstWordCapitalized] + self.content[1...] + let content = [self.content[0].capitalizeFirstWord()] + self.content[1...] return .init(style: self.style, content: content) } } extension RenderBlockContent.Small: AutoCapitalizable { - var withFirstWordCapitalized: RenderBlockContent.Small { + func capitalizeFirstWord() -> RenderBlockContent.Small { guard !self.inlineContent.isEmpty else { return self } - let inlineContent = [self.inlineContent[0].withFirstWordCapitalized] + self.inlineContent[1...] + let inlineContent = [self.inlineContent[0].capitalizeFirstWord()] + self.inlineContent[1...] return .init(inlineContent: inlineContent) } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift index 8d70847b26..73202fd7c5 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift @@ -42,7 +42,7 @@ struct RenderContentCompiler: MarkupVisitor { content: aside.content.reduce(into: [], { result, child in result.append(contentsOf: visit(child))}) as! [RenderBlockContent] ) - return [RenderBlockContent.aside(newAside.withFirstWordCapitalized)] + return [RenderBlockContent.aside(newAside.capitalizeFirstWord())] } mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> [RenderContent] { diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift index 5b6594a5b7..b7db640b46 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift @@ -27,7 +27,7 @@ struct DiscussionSectionTranslator: RenderSectionTranslator { return nil } - let capitalizedDiscussionContent = [discussionContent[0].withFirstWordCapitalized] + discussionContent[1...] + let capitalizedDiscussionContent = [discussionContent[0].capitalizeFirstWord()] + discussionContent[1...] let title: String? if let first = discussionContent.first, case RenderBlockContent.heading = first { diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift index 66c4238806..5e0189e694 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift @@ -33,7 +33,7 @@ struct ParametersSectionTranslator: RenderSectionTranslator { return ParameterRenderSection(name: parameter.name, content: parameterContent) } - let capitalizedParameterContent = [parameterContent[0].withFirstWordCapitalized] + parameterContent[1...] + let capitalizedParameterContent = [parameterContent[0].capitalizeFirstWord()] + parameterContent[1...] return ParameterRenderSection(name: parameter.name, content: capitalizedParameterContent) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift index e10985f0b0..c1d0878c26 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift @@ -28,7 +28,7 @@ struct ReturnsSectionTranslator: RenderSectionTranslator { return nil } - let capitalizedReturnsContent = [returnsContent[0].withFirstWordCapitalized] + returnsContent[1...] + let capitalizedReturnsContent = [returnsContent[0].capitalizeFirstWord()] + returnsContent[1...] return ContentRenderSection(kind: .content, content: capitalizedReturnsContent, heading: "Return Value") } diff --git a/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift b/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift index cb1e312f97..6d7396f79d 100644 --- a/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift @@ -19,52 +19,52 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { // Text, Emphasis, Strong are all auto-capitalized, and everything else defaults to not capitalized. func testRenderInlineContentText() { - let text = RenderInlineContent.text("hello, world!").withFirstWordCapitalized + let text = RenderInlineContent.text("hello, world!").capitalizeFirstWord() XCTAssertEqual("Hello, world!", text.plainText) } func testRenderInlineContentEmphasis() { - let emphasis = RenderInlineContent.emphasis(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let emphasis = RenderInlineContent.emphasis(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("Hello, world!", emphasis.plainText) } func testRenderInlineContentStrong() { - let strong = RenderInlineContent.strong(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let strong = RenderInlineContent.strong(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("Hello, world!", strong.plainText) } func testRenderInlineContentCodeVoice() { - let codeVoice = RenderInlineContent.codeVoice(code: "code voice").withFirstWordCapitalized + let codeVoice = RenderInlineContent.codeVoice(code: "code voice").capitalizeFirstWord() XCTAssertEqual("code voice", codeVoice.plainText) } func testRenderInlineContentReference() { - let reference = RenderInlineContent.reference(identifier: .init("Test"), isActive: true, overridingTitle: "hello, world!", overridingTitleInlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let reference = RenderInlineContent.reference(identifier: .init("Test"), isActive: true, overridingTitle: "hello, world!", overridingTitleInlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("hello, world!", reference.plainText) } func testRenderInlineContentNewTerm() { - let newTerm = RenderInlineContent.newTerm(inlineContent: [.text("helloWorld")]).withFirstWordCapitalized + let newTerm = RenderInlineContent.newTerm(inlineContent: [.text("helloWorld")]).capitalizeFirstWord() XCTAssertEqual("helloWorld", newTerm.plainText) } func testRenderInlineContentInlineHead() { - let inlineHead = RenderInlineContent.inlineHead(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let inlineHead = RenderInlineContent.inlineHead(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("hello, world!", inlineHead.plainText) } func testRenderInlineContentSubscript() { - let subscriptContent = RenderInlineContent.subscript(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let subscriptContent = RenderInlineContent.subscript(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("hello, world!", subscriptContent.plainText) } func testRenderInlineContentSuperscript() { - let superscriptContent = RenderInlineContent.superscript(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let superscriptContent = RenderInlineContent.superscript(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("hello, world!", superscriptContent.plainText) } func testRenderInlineContentStrikethrough() { - let strikethrough = RenderInlineContent.strikethrough(inlineContent: [.text("hello, world!")]).withFirstWordCapitalized + let strikethrough = RenderInlineContent.strikethrough(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() XCTAssertEqual("hello, world!", strikethrough.plainText) } @@ -72,22 +72,22 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { // Paragraphs, asides, headings, and small content are all auto-capitalized, and everything else defaults to not capitalized. func testRenderBlockContentParagraph() { - let paragraph = RenderBlockContent.paragraph(.init(inlineContent: [.text("hello, world!")])).withFirstWordCapitalized + let paragraph = RenderBlockContent.paragraph(.init(inlineContent: [.text("hello, world!")])).capitalizeFirstWord() XCTAssertEqual("Hello, world!", paragraph.rawIndexableTextContent(references: [:])) } func testRenderBlockContentAside() { - let aside = RenderBlockContent.aside(.init(style: .init(rawValue: "Experiment"), content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))])).withFirstWordCapitalized + let aside = RenderBlockContent.aside(.init(style: .init(rawValue: "Experiment"), content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))])).capitalizeFirstWord() XCTAssertEqual("Hello, world!", aside.rawIndexableTextContent(references: [:])) } func testRenderBlockContentSmall() { - let small = RenderBlockContent.small(.init(inlineContent: [.text("hello, world!")])).withFirstWordCapitalized + let small = RenderBlockContent.small(.init(inlineContent: [.text("hello, world!")])).capitalizeFirstWord() XCTAssertEqual("Hello, world!", small.rawIndexableTextContent(references: [:])) } func testRenderBlockContentHeading() { - let heading = RenderBlockContent.heading(.init(level: 1, text: "hello, world!", anchor: "hi")).withFirstWordCapitalized + let heading = RenderBlockContent.heading(.init(level: 1, text: "hello, world!", anchor: "hi")).capitalizeFirstWord() XCTAssertEqual("Hello, world!", heading.rawIndexableTextContent(references: [:])) } @@ -99,12 +99,12 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { .init(content: [ .paragraph(.init(inlineContent: [.text("world!")])), ]), - ])).withFirstWordCapitalized + ])).capitalizeFirstWord() XCTAssertEqual("hello, world!", list.rawIndexableTextContent(references: [:])) } func testRenderBlockContentStep() { - let step = RenderBlockContent.step(.init(content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))], caption: [.paragraph(.init(inlineContent: [.text("Step caption")]))], media: RenderReferenceIdentifier("Media"), code: RenderReferenceIdentifier("Code"), runtimePreview: RenderReferenceIdentifier("Preview"))).withFirstWordCapitalized + let step = RenderBlockContent.step(.init(content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))], caption: [.paragraph(.init(inlineContent: [.text("Step caption")]))], media: RenderReferenceIdentifier("Media"), code: RenderReferenceIdentifier("Code"), runtimePreview: RenderReferenceIdentifier("Preview"))).capitalizeFirstWord() XCTAssertEqual("hello, world! Step caption", step.rawIndexableTextContent(references: [:])) } @@ -117,7 +117,7 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { .init(content: [ .paragraph(.init(inlineContent: [.text("world!")])), ]), - ])).withFirstWordCapitalized + ])).capitalizeFirstWord() XCTAssertEqual("hello, world!", list.rawIndexableTextContent(references: [:])) } From d7e487b84abc7e26acc2f1dce78ff1dfd0c3e376 Mon Sep 17 00:00:00 2001 From: Emily Chen Date: Tue, 16 Apr 2024 11:50:21 +0100 Subject: [PATCH 2/3] Remove unused protocol and extension, and extracted repeated capitalization logic into [RenderBlockContent] and [RenderInlineContent] extensions. --- .../RenderBlockContent+Capitalization.swift | 62 ++++++++----------- .../String+Capitalization.swift | 9 +-- .../AutoCapitalizationTests.swift | 2 +- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift index 36bcba74d7..9ae73b3880 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift @@ -8,36 +8,41 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -/// For auto capitalizing the first letter of a sentence following a colon (e.g. asides, sections such as parameters, returns). -protocol AutoCapitalizable { - - /// Any type that conforms to the AutoCapitalizable protocol will have the first letter of the first word capitalized (if applicable). - func capitalizeFirstWord() -> Self - -} - -extension AutoCapitalizable { - func capitalizeFirstWord() -> Self { return self } -} -extension RenderInlineContent: AutoCapitalizable { +extension RenderInlineContent { /// Capitalize the first word for normal text content, as well as content that has emphasis or strong applied. func capitalizeFirstWord() -> Self { switch self { case .text(let text): return .text(text.capitalizeFirstWord()) case .emphasis(inlineContent: let embeddedContent): - return .emphasis(inlineContent: [embeddedContent[0].capitalizeFirstWord()] + embeddedContent[1...]) + return .emphasis(inlineContent: embeddedContent.capitalizeFirstWord()) case .strong(inlineContent: let embeddedContent): - return .strong(inlineContent: [embeddedContent[0].capitalizeFirstWord()] + embeddedContent[1...]) + return .strong(inlineContent: embeddedContent.capitalizeFirstWord()) default: return self } } } +extension [RenderBlockContent] { + func capitalizeFirstWord() -> Self { + guard let first else { return [] } + + return [first.capitalizeFirstWord()] + dropFirst() + } +} + +extension [RenderInlineContent] { + func capitalizeFirstWord() -> Self { + guard let first else { return [] } + + return [first.capitalizeFirstWord()] + dropFirst() + } +} + -extension RenderBlockContent: AutoCapitalizable { +extension RenderBlockContent { /// Capitalize the first word for paragraphs, asides, headings, and small content. func capitalizeFirstWord() -> Self { switch self { @@ -55,36 +60,21 @@ extension RenderBlockContent: AutoCapitalizable { } } -extension RenderBlockContent.Paragraph: AutoCapitalizable { +extension RenderBlockContent.Paragraph { func capitalizeFirstWord() -> RenderBlockContent.Paragraph { - guard !self.inlineContent.isEmpty else { - return self - } - - let inlineContent = [self.inlineContent[0].capitalizeFirstWord()] + self.inlineContent[1...] - return .init(inlineContent: inlineContent) + return .init(inlineContent: inlineContent.capitalizeFirstWord()) } } -extension RenderBlockContent.Aside: AutoCapitalizable { +extension RenderBlockContent.Aside { func capitalizeFirstWord() -> RenderBlockContent.Aside { - guard !self.content.isEmpty else { - return self - } - - let content = [self.content[0].capitalizeFirstWord()] + self.content[1...] - return .init(style: self.style, content: content) + return .init(style: self.style, content: self.content.capitalizeFirstWord()) } } -extension RenderBlockContent.Small: AutoCapitalizable { +extension RenderBlockContent.Small { func capitalizeFirstWord() -> RenderBlockContent.Small { - guard !self.inlineContent.isEmpty else { - return self - } - - let inlineContent = [self.inlineContent[0].capitalizeFirstWord()] + self.inlineContent[1...] - return .init(inlineContent: inlineContent) + return .init(inlineContent: self.inlineContent.capitalizeFirstWord()) } } diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift index 3f27074c22..e567b1fed0 100644 --- a/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift @@ -26,12 +26,9 @@ extension String { return self } - var resultString = String() - resultString.reserveCapacity(self.count) - resultString.append(contentsOf: self[.. Date: Tue, 16 Apr 2024 13:47:19 +0100 Subject: [PATCH 3/3] Rename function from capitalizeFirstWord() to capitalizingFirstWord() since this is a non-mutating function. --- .../RenderBlockContent+Capitalization.swift | 38 +++++++++---------- .../Rendering/RenderContentCompiler.swift | 2 +- .../DiscussionSectionTranslator.swift | 4 +- .../ParametersSectionTranslator.swift | 4 +- .../ReturnsSectionTranslator.swift | 4 +- .../String+Capitalization.swift | 4 +- ...nderBlockContent+CapitalizationTests.swift | 34 ++++++++--------- .../Utility/String+CapitalizationTests.swift | 36 +++++++++--------- 8 files changed, 60 insertions(+), 66 deletions(-) diff --git a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift index 9ae73b3880..e18b3e3d3f 100644 --- a/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift +++ b/Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent+Capitalization.swift @@ -11,14 +11,14 @@ extension RenderInlineContent { /// Capitalize the first word for normal text content, as well as content that has emphasis or strong applied. - func capitalizeFirstWord() -> Self { + func capitalizingFirstWord() -> Self { switch self { case .text(let text): - return .text(text.capitalizeFirstWord()) + return .text(text.capitalizingFirstWord()) case .emphasis(inlineContent: let embeddedContent): - return .emphasis(inlineContent: embeddedContent.capitalizeFirstWord()) + return .emphasis(inlineContent: embeddedContent.capitalizingFirstWord()) case .strong(inlineContent: let embeddedContent): - return .strong(inlineContent: embeddedContent.capitalizeFirstWord()) + return .strong(inlineContent: embeddedContent.capitalizingFirstWord()) default: return self } @@ -26,34 +26,34 @@ extension RenderInlineContent { } extension [RenderBlockContent] { - func capitalizeFirstWord() -> Self { + func capitalizingFirstWord() -> Self { guard let first else { return [] } - return [first.capitalizeFirstWord()] + dropFirst() + return [first.capitalizingFirstWord()] + dropFirst() } } extension [RenderInlineContent] { - func capitalizeFirstWord() -> Self { + func capitalizingFirstWord() -> Self { guard let first else { return [] } - return [first.capitalizeFirstWord()] + dropFirst() + return [first.capitalizingFirstWord()] + dropFirst() } } extension RenderBlockContent { /// Capitalize the first word for paragraphs, asides, headings, and small content. - func capitalizeFirstWord() -> Self { + func capitalizingFirstWord() -> Self { switch self { case .paragraph(let paragraph): - return .paragraph(paragraph.capitalizeFirstWord()) + return .paragraph(paragraph.capitalizingFirstWord()) case .aside(let aside): - return .aside(aside.capitalizeFirstWord()) + return .aside(aside.capitalizingFirstWord()) case .small(let small): - return .small(small.capitalizeFirstWord()) + return .small(small.capitalizingFirstWord()) case .heading(let heading): - return .heading(.init(level: heading.level, text: heading.text.capitalizeFirstWord(), anchor: heading.anchor)) + return .heading(.init(level: heading.level, text: heading.text.capitalizingFirstWord(), anchor: heading.anchor)) default: return self } @@ -61,20 +61,20 @@ extension RenderBlockContent { } extension RenderBlockContent.Paragraph { - func capitalizeFirstWord() -> RenderBlockContent.Paragraph { - return .init(inlineContent: inlineContent.capitalizeFirstWord()) + func capitalizingFirstWord() -> RenderBlockContent.Paragraph { + return .init(inlineContent: inlineContent.capitalizingFirstWord()) } } extension RenderBlockContent.Aside { - func capitalizeFirstWord() -> RenderBlockContent.Aside { - return .init(style: self.style, content: self.content.capitalizeFirstWord()) + func capitalizingFirstWord() -> RenderBlockContent.Aside { + return .init(style: self.style, content: self.content.capitalizingFirstWord()) } } extension RenderBlockContent.Small { - func capitalizeFirstWord() -> RenderBlockContent.Small { - return .init(inlineContent: self.inlineContent.capitalizeFirstWord()) + func capitalizingFirstWord() -> RenderBlockContent.Small { + return .init(inlineContent: self.inlineContent.capitalizingFirstWord()) } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift index 73202fd7c5..253527b9a1 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift @@ -42,7 +42,7 @@ struct RenderContentCompiler: MarkupVisitor { content: aside.content.reduce(into: [], { result, child in result.append(contentsOf: visit(child))}) as! [RenderBlockContent] ) - return [RenderBlockContent.aside(newAside.capitalizeFirstWord())] + return [RenderBlockContent.aside(newAside.capitalizingFirstWord())] } mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> [RenderContent] { diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift index b7db640b46..1c2ba22db6 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DiscussionSectionTranslator.swift @@ -27,8 +27,6 @@ struct DiscussionSectionTranslator: RenderSectionTranslator { return nil } - let capitalizedDiscussionContent = [discussionContent[0].capitalizeFirstWord()] + discussionContent[1...] - let title: String? if let first = discussionContent.first, case RenderBlockContent.heading = first { // There's already an authored heading. Don't add another heading. @@ -44,7 +42,7 @@ struct DiscussionSectionTranslator: RenderSectionTranslator { } } - return ContentRenderSection(kind: .content, content: capitalizedDiscussionContent, heading: title) + return ContentRenderSection(kind: .content, content: discussionContent.capitalizingFirstWord(), heading: title) } } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift index 5e0189e694..26503f0399 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ParametersSectionTranslator.swift @@ -33,9 +33,7 @@ struct ParametersSectionTranslator: RenderSectionTranslator { return ParameterRenderSection(name: parameter.name, content: parameterContent) } - let capitalizedParameterContent = [parameterContent[0].capitalizeFirstWord()] + parameterContent[1...] - - return ParameterRenderSection(name: parameter.name, content: capitalizedParameterContent) + return ParameterRenderSection(name: parameter.name, content: parameterContent.capitalizingFirstWord()) } ) } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift index c1d0878c26..772b317fc1 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/ReturnsSectionTranslator.swift @@ -28,9 +28,7 @@ struct ReturnsSectionTranslator: RenderSectionTranslator { return nil } - let capitalizedReturnsContent = [returnsContent[0].capitalizeFirstWord()] + returnsContent[1...] - - return ContentRenderSection(kind: .content, content: capitalizedReturnsContent, heading: "Return Value") + return ContentRenderSection(kind: .content, content: returnsContent.capitalizingFirstWord(), heading: "Return Value") } } } diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift index e567b1fed0..47ae628163 100644 --- a/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/String+Capitalization.swift @@ -12,13 +12,13 @@ import Foundation extension String { - // Precomputes the CharacterSet to use in capitalizeFirstWord(). + // Precomputes the CharacterSet to use in capitalizingFirstWord(). private static let charactersPreventingWordCapitalization = CharacterSet.lowercaseLetters.union(.punctuationCharacters).inverted /// Returns the string with the first letter capitalized. /// This auto-capitalization only occurs if the first word is all lowercase and contains only lowercase letters. /// The first word can also contain punctuation (e.g. a period, comma, hyphen, semi-colon, colon). - func capitalizeFirstWord() -> String { + func capitalizingFirstWord() -> String { guard let firstWordStartIndex = self.firstIndex(where: { !$0.isWhitespace && !$0.isNewline }) else { return self } let firstWord = self[firstWordStartIndex...].prefix(while: { !$0.isWhitespace && !$0.isNewline}) diff --git a/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift b/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift index 6d7396f79d..89ae9d2102 100644 --- a/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderBlockContent+CapitalizationTests.swift @@ -19,52 +19,52 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { // Text, Emphasis, Strong are all auto-capitalized, and everything else defaults to not capitalized. func testRenderInlineContentText() { - let text = RenderInlineContent.text("hello, world!").capitalizeFirstWord() + let text = RenderInlineContent.text("hello, world!").capitalizingFirstWord() XCTAssertEqual("Hello, world!", text.plainText) } func testRenderInlineContentEmphasis() { - let emphasis = RenderInlineContent.emphasis(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let emphasis = RenderInlineContent.emphasis(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("Hello, world!", emphasis.plainText) } func testRenderInlineContentStrong() { - let strong = RenderInlineContent.strong(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let strong = RenderInlineContent.strong(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("Hello, world!", strong.plainText) } func testRenderInlineContentCodeVoice() { - let codeVoice = RenderInlineContent.codeVoice(code: "code voice").capitalizeFirstWord() + let codeVoice = RenderInlineContent.codeVoice(code: "code voice").capitalizingFirstWord() XCTAssertEqual("code voice", codeVoice.plainText) } func testRenderInlineContentReference() { - let reference = RenderInlineContent.reference(identifier: .init("Test"), isActive: true, overridingTitle: "hello, world!", overridingTitleInlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let reference = RenderInlineContent.reference(identifier: .init("Test"), isActive: true, overridingTitle: "hello, world!", overridingTitleInlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("hello, world!", reference.plainText) } func testRenderInlineContentNewTerm() { - let newTerm = RenderInlineContent.newTerm(inlineContent: [.text("helloWorld")]).capitalizeFirstWord() + let newTerm = RenderInlineContent.newTerm(inlineContent: [.text("helloWorld")]).capitalizingFirstWord() XCTAssertEqual("helloWorld", newTerm.plainText) } func testRenderInlineContentInlineHead() { - let inlineHead = RenderInlineContent.inlineHead(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let inlineHead = RenderInlineContent.inlineHead(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("hello, world!", inlineHead.plainText) } func testRenderInlineContentSubscript() { - let subscriptContent = RenderInlineContent.subscript(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let subscriptContent = RenderInlineContent.subscript(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("hello, world!", subscriptContent.plainText) } func testRenderInlineContentSuperscript() { - let superscriptContent = RenderInlineContent.superscript(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let superscriptContent = RenderInlineContent.superscript(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("hello, world!", superscriptContent.plainText) } func testRenderInlineContentStrikethrough() { - let strikethrough = RenderInlineContent.strikethrough(inlineContent: [.text("hello, world!")]).capitalizeFirstWord() + let strikethrough = RenderInlineContent.strikethrough(inlineContent: [.text("hello, world!")]).capitalizingFirstWord() XCTAssertEqual("hello, world!", strikethrough.plainText) } @@ -72,22 +72,22 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { // Paragraphs, asides, headings, and small content are all auto-capitalized, and everything else defaults to not capitalized. func testRenderBlockContentParagraph() { - let paragraph = RenderBlockContent.paragraph(.init(inlineContent: [.text("hello, world!")])).capitalizeFirstWord() + let paragraph = RenderBlockContent.paragraph(.init(inlineContent: [.text("hello, world!")])).capitalizingFirstWord() XCTAssertEqual("Hello, world!", paragraph.rawIndexableTextContent(references: [:])) } func testRenderBlockContentAside() { - let aside = RenderBlockContent.aside(.init(style: .init(rawValue: "Experiment"), content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))])).capitalizeFirstWord() + let aside = RenderBlockContent.aside(.init(style: .init(rawValue: "Experiment"), content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))])).capitalizingFirstWord() XCTAssertEqual("Hello, world!", aside.rawIndexableTextContent(references: [:])) } func testRenderBlockContentSmall() { - let small = RenderBlockContent.small(.init(inlineContent: [.text("hello, world!")])).capitalizeFirstWord() + let small = RenderBlockContent.small(.init(inlineContent: [.text("hello, world!")])).capitalizingFirstWord() XCTAssertEqual("Hello, world!", small.rawIndexableTextContent(references: [:])) } func testRenderBlockContentHeading() { - let heading = RenderBlockContent.heading(.init(level: 1, text: "hello, world!", anchor: "hi")).capitalizeFirstWord() + let heading = RenderBlockContent.heading(.init(level: 1, text: "hello, world!", anchor: "hi")).capitalizingFirstWord() XCTAssertEqual("Hello, world!", heading.rawIndexableTextContent(references: [:])) } @@ -99,12 +99,12 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { .init(content: [ .paragraph(.init(inlineContent: [.text("world!")])), ]), - ])).capitalizeFirstWord() + ])).capitalizingFirstWord() XCTAssertEqual("hello, world!", list.rawIndexableTextContent(references: [:])) } func testRenderBlockContentStep() { - let step = RenderBlockContent.step(.init(content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))], caption: [.paragraph(.init(inlineContent: [.text("Step caption")]))], media: RenderReferenceIdentifier("Media"), code: RenderReferenceIdentifier("Code"), runtimePreview: RenderReferenceIdentifier("Preview"))).capitalizeFirstWord() + let step = RenderBlockContent.step(.init(content: [.paragraph(.init(inlineContent: [.text("hello, world!")]))], caption: [.paragraph(.init(inlineContent: [.text("Step caption")]))], media: RenderReferenceIdentifier("Media"), code: RenderReferenceIdentifier("Code"), runtimePreview: RenderReferenceIdentifier("Preview"))).capitalizingFirstWord() XCTAssertEqual("hello, world! Step caption", step.rawIndexableTextContent(references: [:])) } @@ -117,7 +117,7 @@ class RenderBlockContent_CapitalizationTests: XCTestCase { .init(content: [ .paragraph(.init(inlineContent: [.text("world!")])), ]), - ])).capitalizeFirstWord() + ])).capitalizingFirstWord() XCTAssertEqual("hello, world!", list.rawIndexableTextContent(references: [:])) } diff --git a/Tests/SwiftDocCTests/Utility/String+CapitalizationTests.swift b/Tests/SwiftDocCTests/Utility/String+CapitalizationTests.swift index 0c9c6f15f7..1312bc0277 100644 --- a/Tests/SwiftDocCTests/Utility/String+CapitalizationTests.swift +++ b/Tests/SwiftDocCTests/Utility/String+CapitalizationTests.swift @@ -15,7 +15,7 @@ class String_CapitalizationTests: XCTestCase { func testAllLowerCase() { let testString = "hello world" - XCTAssertEqual("Hello world", testString.capitalizeFirstWord()) + XCTAssertEqual("Hello world", testString.capitalizingFirstWord()) } func testAllLowerCaseWithPunctuation() { @@ -24,30 +24,30 @@ class String_CapitalizationTests: XCTestCase { let testString3 = "hello! world" let testString4 = "hello: world" let testString5 = "l'ocean world" - XCTAssertEqual("Hello, world", testString1.capitalizeFirstWord()) - XCTAssertEqual("Twenty-One", testString2.capitalizeFirstWord()) - XCTAssertEqual("Hello! world", testString3.capitalizeFirstWord()) - XCTAssertEqual("Hello: world", testString4.capitalizeFirstWord()) - XCTAssertEqual("L'ocean world", testString5.capitalizeFirstWord()) + XCTAssertEqual("Hello, world", testString1.capitalizingFirstWord()) + XCTAssertEqual("Twenty-One", testString2.capitalizingFirstWord()) + XCTAssertEqual("Hello! world", testString3.capitalizingFirstWord()) + XCTAssertEqual("Hello: world", testString4.capitalizingFirstWord()) + XCTAssertEqual("L'ocean world", testString5.capitalizingFirstWord()) } func testInvalidPunctuation() { let testString = "h`ello world" - XCTAssertEqual(testString, testString.capitalizeFirstWord()) + XCTAssertEqual(testString, testString.capitalizingFirstWord()) } func testHasUppercase() { let testString = "iPad iOS visionOS" - XCTAssertEqual(testString, testString.capitalizeFirstWord()) + XCTAssertEqual(testString, testString.capitalizingFirstWord()) } func testWhiteSpaces() { let testString1 = " has many spaces" let testString2 = " has a tab" let testString3 = " has many spaces " - XCTAssertEqual(" Has many spaces", testString1.capitalizeFirstWord()) - XCTAssertEqual(" Has a tab", testString2.capitalizeFirstWord()) - XCTAssertEqual(" Has many spaces ", testString3.capitalizeFirstWord()) + XCTAssertEqual(" Has many spaces", testString1.capitalizingFirstWord()) + XCTAssertEqual(" Has a tab", testString2.capitalizingFirstWord()) + XCTAssertEqual(" Has many spaces ", testString3.capitalizingFirstWord()) } @@ -59,13 +59,13 @@ class String_CapitalizationTests: XCTestCase { let testString5 = "牛奶" let testString6 = "i don't like 牛奶" let testString7 = "牛奶 is tasty" - XCTAssertEqual("L'amérique du nord", testString1.capitalizeFirstWord()) - XCTAssertEqual("Ça va?", testString2.capitalizeFirstWord()) - XCTAssertEqual("À", testString3.capitalizeFirstWord()) - XCTAssertEqual("チーズ", testString4.capitalizeFirstWord()) - XCTAssertEqual("牛奶", testString5.capitalizeFirstWord()) - XCTAssertEqual("I don't like 牛奶", testString6.capitalizeFirstWord()) - XCTAssertEqual("牛奶 is tasty", testString7.capitalizeFirstWord()) + XCTAssertEqual("L'amérique du nord", testString1.capitalizingFirstWord()) + XCTAssertEqual("Ça va?", testString2.capitalizingFirstWord()) + XCTAssertEqual("À", testString3.capitalizingFirstWord()) + XCTAssertEqual("チーズ", testString4.capitalizingFirstWord()) + XCTAssertEqual("牛奶", testString5.capitalizingFirstWord()) + XCTAssertEqual("I don't like 牛奶", testString6.capitalizingFirstWord()) + XCTAssertEqual("牛奶 is tasty", testString7.capitalizingFirstWord()) } }