diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index 44ef1a61a..32dd82a18 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -67,24 +67,7 @@ public struct DocumentationCommentText { // comment. We have to copy it into an array since `Trivia` doesn't support bidirectional // indexing. let triviaArray = Array(trivia) - let commentStartIndex: Array.Index - if - let lastNonDocCommentIndex = triviaArray.lastIndex(where: { - switch $0 { - case .docBlockComment, .docLineComment, - .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), - .spaces, .tabs: - return false - default: - return true - } - }), - lastNonDocCommentIndex != trivia.endIndex - { - commentStartIndex = triviaArray.index(after: lastNonDocCommentIndex) - } else { - commentStartIndex = triviaArray.startIndex - } + let commentStartIndex = findCommentStartIndex(triviaArray) // Determine the indentation level of the first line of the comment. This is used to adjust // block comments, whose text spans multiple lines. @@ -216,3 +199,37 @@ private func asciiArtLength(of string: Substring, leadingSpaces: Int) -> Int { } return 0 } + +/// Returns the start index of the earliest comment in the Trivia if we work backwards and +/// skip through comments, newlines, and whitespace. Then we advance a bit forward to be sure +/// the returned index is actually a comment and not whitespace. +private func findCommentStartIndex(_ triviaArray: Array) -> Array.Index { + func firstCommentIndex(_ slice: ArraySlice) -> Array.Index { + return slice.firstIndex(where: { + switch $0 { + case .docLineComment, .docBlockComment: + return true + default: + return false + } + }) ?? slice.endIndex + } + + if + let lastNonDocCommentIndex = triviaArray.lastIndex(where: { + switch $0 { + case .docBlockComment, .docLineComment, + .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), + .spaces, .tabs: + return false + default: + return true + } + }) + { + let nextIndex = triviaArray.index(after: lastNonDocCommentIndex) + return firstCommentIndex(triviaArray[nextIndex...]) + } else { + return firstCommentIndex(triviaArray[...]) + } +} diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift index adda6e4f1..4a1f8302f 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift @@ -54,7 +54,25 @@ final class DocumentationCommentTextTests: XCTestCase { """ ) } - + + func testIndentedDocBlockCommentWithASCIIArt() throws { + let decl: DeclSyntax = """ + /** + * A simple doc comment. + */ + func f() {} + """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) + XCTAssertEqual( + commentText.text, + """ + A simple doc comment. + + """ + ) + } + func testDocBlockCommentWithoutASCIIArt() throws { let decl: DeclSyntax = """ /** diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index e41425930..cfeaa09dd 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -14,33 +14,33 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe assertLint( BeginDocumentationCommentWithOneLineSummary.self, """ - /// Returns a bottle of Dr Pepper from the vending machine. - public func drPepper(from vendingMachine: VendingMachine) -> Soda {} + /// Returns a bottle of Dr Pepper from the vending machine. + public func drPepper(from vendingMachine: VendingMachine) -> Soda {} - /// Contains a comment as description that needs a sentence - /// of two lines of code. - public var twoLinesForOneSentence = "test" + /// Contains a comment as description that needs a sentence + /// of two lines of code. + public var twoLinesForOneSentence = "test" - /// The background color of the view. - var backgroundColor: UIColor + /// The background color of the view. + var backgroundColor: UIColor - /// Returns the sum of the numbers. - /// - /// - Parameter numbers: The numbers to sum. - /// - Returns: The sum of the numbers. - func sum(_ numbers: [Int]) -> Int { - // ... - } + /// Returns the sum of the numbers. + /// + /// - Parameter numbers: The numbers to sum. + /// - Returns: The sum of the numbers. + func sum(_ numbers: [Int]) -> Int { + // ... + } - /// This docline should not succeed. - /// There are two sentences without a blank line between them. - 1️⃣struct Test {} + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} - /// This docline should not succeed. There are two sentences. - 2️⃣public enum Token { case comma, semicolon, identifier } + /// This docline should not succeed. There are two sentences. + 2️⃣public enum Token { case comma, semicolon, identifier } - /// Should fail because it doesn't have a period - 3️⃣public class testNoPeriod {} + /// Should fail because it doesn't have a period + 3️⃣public class testNoPeriod {} """, findings: [ FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), @@ -54,36 +54,36 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe assertLint( BeginDocumentationCommentWithOneLineSummary.self, """ - /** - * Returns the numeric value. - * - * - Parameters: - * - digit: The Unicode scalar whose numeric value should be returned. - * - radix: The radix, between 2 and 36, used to compute the numeric value. - * - Returns: The numeric value of the scalar.*/ - func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int {} - - /** - * This block comment contains a sentence summary - * of two lines of code. - */ - public var twoLinesForOneSentence = "test" - - /** - * This block comment should not succeed, struct. - * There are two sentences without a blank line between them. - */ - 1️⃣struct TestStruct {} - - /** - This block comment should not succeed, class. - Add a blank comment after the first line. - */ - 2️⃣public class TestClass {} - /** This block comment should not succeed, enum. There are two sentences. */ - 3️⃣public enum testEnum {} - /** Should fail because it doesn't have a period */ - 4️⃣public class testNoPeriod {} + /** + * Returns the numeric value. + * + * - Parameters: + * - digit: The Unicode scalar whose numeric value should be returned. + * - radix: The radix, between 2 and 36, used to compute the numeric value. + * - Returns: The numeric value of the scalar.*/ + func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int {} + + /** + * This block comment contains a sentence summary + * of two lines of code. + */ + public var twoLinesForOneSentence = "test" + + /** + * This block comment should not succeed, struct. + * There are two sentences without a blank line between them. + */ + 1️⃣struct TestStruct {} + + /** + This block comment should not succeed, class. + Add a blank comment after the first line. + */ + 2️⃣public class TestClass {} + /** This block comment should not succeed, enum. There are two sentences. */ + 3️⃣public enum testEnum {} + /** Should fail because it doesn't have a period */ + 4️⃣public class testNoPeriod {} """, findings: [ FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, struct.""#),