Skip to content

Commit d444d35

Browse files
committed
Add option to configure multiline string reflow.
New option `.reflowMultilineStringLiterals` configures whether or not existing escaped newlines should be respected. When true, existing escaped newlines will not be respected and strings will reflow. When false, existing strings will not reflow. This means when false, once reflow happens it will not occur again unless the newlines are manually removed.
1 parent f07b27d commit d444d35

File tree

4 files changed

+70
-3
lines changed

4 files changed

+70
-3
lines changed

Sources/SwiftFormat/API/Configuration+Default.swift

+1
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ extension Configuration {
4040
self.spacesAroundRangeFormationOperators = false
4141
self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
4242
self.multiElementCollectionTrailingCommas = true
43+
self.reflowMultilineStringLiterals = false
4344
}
4445
}

Sources/SwiftFormat/API/Configuration.swift

+30
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public struct Configuration: Codable, Equatable {
4545
case spacesAroundRangeFormationOperators
4646
case noAssignmentInExpressions
4747
case multiElementCollectionTrailingCommas
48+
case reflowMultilineStringLiterals
4849
}
4950

5051
/// A dictionary containing the default enabled/disabled states of rules, keyed by the rules'
@@ -194,6 +195,30 @@ public struct Configuration: Codable, Equatable {
194195
/// ```
195196
public var multiElementCollectionTrailingCommas: Bool
196197

198+
/// Determines if multiline strings literals should be reflowed, or if user inserted esacped newlines should be respected.
199+
///
200+
/// Given the string literal:
201+
/// ```swift
202+
/// """
203+
/// lines are \
204+
/// kept short \
205+
/// ```
206+
///
207+
/// When `false` (default), the literal will not be reflowed:
208+
/// ```swift
209+
/// """
210+
/// lines are \
211+
/// kept short \
212+
/// ```
213+
///
214+
/// When `true`, the literal will reflow to fill up line length:
215+
/// ```swift
216+
/// """
217+
/// lines are kept short
218+
/// """
219+
/// ```
220+
public var reflowMultilineStringLiterals: Bool
221+
197222
/// Creates a new `Configuration` by loading it from a configuration file.
198223
public init(contentsOf url: URL) throws {
199224
let data = try Data(contentsOf: url)
@@ -287,6 +312,10 @@ public struct Configuration: Codable, Equatable {
287312
Bool.self, forKey: .multiElementCollectionTrailingCommas)
288313
?? defaults.multiElementCollectionTrailingCommas
289314

315+
self.reflowMultilineStringLiterals =
316+
try container.decodeIfPresent(Bool.self, forKey: .reflowMultilineStringLiterals)
317+
?? defaults.reflowMultilineStringLiterals
318+
290319
// If the `rules` key is not present at all, default it to the built-in set
291320
// so that the behavior is the same as if the configuration had been
292321
// default-initialized. To get an empty rules dictionary, one can explicitly
@@ -321,6 +350,7 @@ public struct Configuration: Codable, Equatable {
321350
try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels)
322351
try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions)
323352
try container.encode(multiElementCollectionTrailingCommas, forKey: .multiElementCollectionTrailingCommas)
353+
try container.encode(reflowMultilineStringLiterals, forKey: .reflowMultilineStringLiterals)
324354
try container.encode(rules, forKey: .rules)
325355
}
326356

Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -2523,9 +2523,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
25232523

25242524
let emitSegmentTextTokens =
25252525
isMultiLineString
2526-
? { segment in self.emitMultilineSegmentTextTokens(breakKind: breakKind, segment: segment) }
2526+
? { (segment) in self.emitMultilineSegmentTextTokens(breakKind: breakKind, segment: segment) }
25272527
// For single line strings we don't allow line breaks, so emit the string as a single `.syntax` token
2528-
: { segment in self.appendToken(.syntax(String(segment))) }
2528+
: { (segment) in self.appendToken(.syntax(String(segment))) }
25292529

25302530
let segmentText = node.content.text
25312531
if segmentText.hasSuffix("\n") {
@@ -2543,6 +2543,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
25432543
emitSegmentTextTokens(segmentText[...])
25442544
}
25452545

2546+
if !config.reflowMultilineStringLiterals && node.trailingTrivia.containsBackslashes {
2547+
// Segments with trailing backslashes won't end with a literal newline; the backslash is
2548+
// considered trivia. To preserve the original text and wrapping, we need to manually render
2549+
// the backslash and a break into the token stream.
2550+
appendToken(.syntax("\\"))
2551+
appendToken(.break(breakKind, newlines: .hard(count: 1)))
2552+
}
25462553
return .skipChildren
25472554
}
25482555

Tests/SwiftFormatTests/PrettyPrint/StringTests.swift

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@_spi(Rules) @_spi(Testing) import SwiftFormat
2+
13
final class StringTests: PrettyPrintTestCase {
24
func testStrings() {
35
let input =
@@ -75,6 +77,31 @@ final class StringTests: PrettyPrintTestCase {
7577
assertPrettyPrintEqual(input: input, expected: expected, linelength: 30)
7678
}
7779

80+
func testMultilineStringIsNotReformattedWithReflowDisabled() {
81+
let input =
82+
#"""
83+
let someString =
84+
"""
85+
lines \
86+
are \
87+
short.
88+
"""
89+
"""#
90+
91+
let expected =
92+
#"""
93+
let someString =
94+
"""
95+
lines \
96+
are \
97+
short.
98+
"""
99+
100+
"""#
101+
102+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 30)
103+
}
104+
78105
func testMultilineStringWithInterpolations() {
79106
let input =
80107
#"""
@@ -392,7 +419,9 @@ final class StringTests: PrettyPrintTestCase {
392419
393420
"""#
394421

395-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 20)
422+
var config = Configuration.forTesting
423+
config.reflowMultilineStringLiterals = true
424+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: config)
396425
}
397426

398427
func testMultilineStringInParenthesizedExpression() {

0 commit comments

Comments
 (0)