Skip to content

Commit 483580f

Browse files
committed
Merge pull request #228 from dylansturg/commas_crash
Handle existing commas in single element collections.
1 parent 1c2a5a2 commit 483580f

File tree

5 files changed

+32
-27
lines changed

5 files changed

+32
-27
lines changed

Sources/SwiftFormatPrettyPrint/PrettyPrint.swift

+12-5
Original file line numberDiff line numberDiff line change
@@ -541,12 +541,18 @@ public class PrettyPrinter {
541541
case .commaDelimitedRegionStart:
542542
commaDelimitedRegionStack.append(openCloseBreakCompensatingLineNumber)
543543

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

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

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

Sources/SwiftFormatPrettyPrint/Token.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ enum Token {
185185
case commaDelimitedRegionStart
186186

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

191191
/// Starts a scope where `contextual` breaks have consistent behavior.
192192
case contextualBreakingStart

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

+12-20
Original file line numberDiff line numberDiff line change
@@ -795,16 +795,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
795795
if let trailingComma = lastElement.trailingComma {
796796
ignoredTokens.insert(trailingComma)
797797
}
798-
// The syntax library can't distinguish an array initializer (where the elements are types)
799-
// from an array literal (where the elements are the contents of the array). We never want to
800-
// add a trailing comma in the initializer case and there's always exactly 1 element, so we
801-
// never add a trailing comma to 1 element arrays.
802-
if let firstElement = node.first, firstElement != lastElement {
803-
before(firstElement.firstToken, tokens: .commaDelimitedRegionStart)
804-
let endToken =
805-
Token.commaDelimitedRegionEnd(hasTrailingComma: lastElement.trailingComma != nil)
806-
after(lastElement.expression.lastToken, tokens: [endToken])
807-
}
798+
before(node.first?.firstToken, tokens: .commaDelimitedRegionStart)
799+
let endToken =
800+
Token.commaDelimitedRegionEnd(
801+
hasTrailingComma: lastElement.trailingComma != nil,
802+
isSingleElement: node.first == lastElement)
803+
after(lastElement.expression.lastToken, tokens: [endToken])
808804
}
809805
return .visitChildren
810806
}
@@ -842,16 +838,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
842838
if let trailingComma = lastElement.trailingComma {
843839
ignoredTokens.insert(trailingComma)
844840
}
845-
// The syntax library can't distinguish a dictionary initializer (where the elements are
846-
// types) from a dictionary literal (where the elements are the contents of the dictionary).
847-
// We never want to add a trailing comma in the initializer case and there's always exactly 1
848-
// element, so we never add a trailing comma to 1 element dictionaries.
849-
if let firstElement = node.first, let lastElement = node.last, firstElement != lastElement {
850-
before(firstElement.firstToken, tokens: .commaDelimitedRegionStart)
851-
let endToken =
852-
Token.commaDelimitedRegionEnd(hasTrailingComma: lastElement.trailingComma != nil)
853-
after(lastElement.lastToken, tokens: endToken)
854-
}
841+
before(node.first?.firstToken, tokens: .commaDelimitedRegionStart)
842+
let endToken =
843+
Token.commaDelimitedRegionEnd(
844+
hasTrailingComma: lastElement.trailingComma != nil,
845+
isSingleElement: node.first == node.last)
846+
after(lastElement.lastToken, tokens: endToken)
855847
}
856848
return .visitChildren
857849
}

Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift

+3
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ final class ArrayDeclTests: PrettyPrintTestCase {
103103
func testWhitespaceOnlyDoesNotChangeTrailingComma() {
104104
let input =
105105
"""
106+
let a = [
107+
"String",
108+
]
106109
let a = [1, 2, 3,]
107110
let a: [String] = [
108111
"One", "Two", "Three", "Four", "Five",

Tests/SwiftFormatPrettyPrintTests/DictionaryDeclTests.swift

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ final class DictionaryDeclTests: PrettyPrintTestCase {
9292
func testWhitespaceOnlyDoesNotChangeTrailingComma() {
9393
let input =
9494
"""
95+
let a = [
96+
1: "a",
97+
]
9598
let a = [1: "a", 2: "b", 3: "c",]
9699
let a: [Int: String] = [
97100
1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f",

0 commit comments

Comments
 (0)