Skip to content

Handle indented block comments with ASCII art correctly. #746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions Sources/SwiftFormat/Core/DocumentationCommentText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<TriviaPiece>.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.
Expand Down Expand Up @@ -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<TriviaPiece>) -> Array<TriviaPiece>.Index {
func firstCommentIndex(_ slice: ArraySlice<TriviaPiece>) -> Array<TriviaPiece>.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[...])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.""#),
Expand All @@ -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.""#),
Expand Down