Skip to content

Commit

Permalink
Merge pull request apple#228 from dylansturg/commas_crash
Browse files Browse the repository at this point in the history
Handle existing commas in single element collections.
  • Loading branch information
allevato committed Sep 1, 2020
2 parents 5986f65 + c405d72 commit a1a4607
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 27 deletions.
17 changes: 12 additions & 5 deletions Sources/SwiftFormatPrettyPrint/PrettyPrint.swift
Expand Up @@ -541,12 +541,18 @@ public class PrettyPrinter {
case .commaDelimitedRegionStart:
commaDelimitedRegionStack.append(openCloseBreakCompensatingLineNumber)

case .commaDelimitedRegionEnd(let hasTrailingComma):
case .commaDelimitedRegionEnd(let hasTrailingComma, let isSingleElement):
guard let startLineNumber = commaDelimitedRegionStack.popLast() else {
fatalError("Found trailing comma end with no corresponding start.")
}

let shouldHaveTrailingComma = startLineNumber != openCloseBreakCompensatingLineNumber
// We need to specifically disable trailing commas on elements of single item collections.
// The syntax library can't distinguish a collection's initializer (where the elements are
// types) from a literal (where the elements are the contents of a collection instance).
// We never want to add a trailing comma in an initializer so we disable trailing commas on
// single element collections.
let shouldHaveTrailingComma =
startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement
if shouldHaveTrailingComma && !hasTrailingComma {
diagnose(.addTrailingComma)
} else if !shouldHaveTrailingComma && hasTrailingComma {
Expand Down Expand Up @@ -653,14 +659,15 @@ public class PrettyPrinter {
case .commaDelimitedRegionStart:
lengths.append(0)

case .commaDelimitedRegionEnd:
case .commaDelimitedRegionEnd(_, let isSingleElement):
// The token's length is only necessary when a comma will be printed, but it's impossible to
// know at this point whether the region-start token will be on the same line as this token.
// Without adding this length to the total, it would be possible for this comma to be
// printed in column `maxLineLength`. Unfortunately, this can cause breaks to fire
// unnecessarily when the enclosed tokens comma would fit within `maxLineLength`.
total += 1
lengths.append(1)
let length = isSingleElement ? 0 : 1
total += length
lengths.append(length)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftFormatPrettyPrint/Token.swift
Expand Up @@ -185,8 +185,8 @@ enum Token {
case commaDelimitedRegionStart

/// Marks the end of a comma delimited collection, where a trailing comma should be inserted
/// if and only if the collection spans multiple lines.
case commaDelimitedRegionEnd(hasTrailingComma: Bool)
/// if and only if the collection spans multiple lines and has multiple elements.
case commaDelimitedRegionEnd(hasTrailingComma: Bool, isSingleElement: Bool)

/// Starts a scope where `contextual` breaks have consistent behavior.
case contextualBreakingStart
Expand Down
32 changes: 12 additions & 20 deletions Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift
Expand Up @@ -795,16 +795,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
if let trailingComma = lastElement.trailingComma {
ignoredTokens.insert(trailingComma)
}
// The syntax library can't distinguish an array initializer (where the elements are types)
// from an array literal (where the elements are the contents of the array). We never want to
// add a trailing comma in the initializer case and there's always exactly 1 element, so we
// never add a trailing comma to 1 element arrays.
if let firstElement = node.first, firstElement != lastElement {
before(firstElement.firstToken, tokens: .commaDelimitedRegionStart)
let endToken =
Token.commaDelimitedRegionEnd(hasTrailingComma: lastElement.trailingComma != nil)
after(lastElement.expression.lastToken, tokens: [endToken])
}
before(node.first?.firstToken, tokens: .commaDelimitedRegionStart)
let endToken =
Token.commaDelimitedRegionEnd(
hasTrailingComma: lastElement.trailingComma != nil,
isSingleElement: node.first == lastElement)
after(lastElement.expression.lastToken, tokens: [endToken])
}
return .visitChildren
}
Expand Down Expand Up @@ -842,16 +838,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
if let trailingComma = lastElement.trailingComma {
ignoredTokens.insert(trailingComma)
}
// The syntax library can't distinguish a dictionary initializer (where the elements are
// types) from a dictionary literal (where the elements are the contents of the dictionary).
// We never want to add a trailing comma in the initializer case and there's always exactly 1
// element, so we never add a trailing comma to 1 element dictionaries.
if let firstElement = node.first, let lastElement = node.last, firstElement != lastElement {
before(firstElement.firstToken, tokens: .commaDelimitedRegionStart)
let endToken =
Token.commaDelimitedRegionEnd(hasTrailingComma: lastElement.trailingComma != nil)
after(lastElement.lastToken, tokens: endToken)
}
before(node.first?.firstToken, tokens: .commaDelimitedRegionStart)
let endToken =
Token.commaDelimitedRegionEnd(
hasTrailingComma: lastElement.trailingComma != nil,
isSingleElement: node.first == node.last)
after(lastElement.lastToken, tokens: endToken)
}
return .visitChildren
}
Expand Down
3 changes: 3 additions & 0 deletions Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift
Expand Up @@ -103,6 +103,9 @@ final class ArrayDeclTests: PrettyPrintTestCase {
func testWhitespaceOnlyDoesNotChangeTrailingComma() {
let input =
"""
let a = [
"String",
]
let a = [1, 2, 3,]
let a: [String] = [
"One", "Two", "Three", "Four", "Five",
Expand Down
3 changes: 3 additions & 0 deletions Tests/SwiftFormatPrettyPrintTests/DictionaryDeclTests.swift
Expand Up @@ -92,6 +92,9 @@ final class DictionaryDeclTests: PrettyPrintTestCase {
func testWhitespaceOnlyDoesNotChangeTrailingComma() {
let input =
"""
let a = [
1: "a",
]
let a = [1: "a", 2: "b", 3: "c",]
let a: [Int: String] = [
1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f",
Expand Down

0 comments on commit a1a4607

Please sign in to comment.