Skip to content

Fix type mismatch error in reflowMultilineStringLiterals #979

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

25 changes: 24 additions & 1 deletion Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,30 @@ switch someValue {
---

### `reflowMultilineStringLiterals`
**type:** `string`

> [!NOTE]
> This setting should be specified as a string value (e.g. `"never"`)
> For backward compatibility with swift-format version 601.0.0, the configuration also accepts the legacy object format where the setting is specified as an object with a single key (e.g., ⁠`{ "never": {} }`).

**type:** `string` or `object` (legacy)

**example:**

For all versions above 601.0.0, the configuration should be specified as a string, for example:
```json
{
"reflowMultilineStringLiterals": "never"
}
```

For version 601.0.0, the configuration should be specified as an object, for example:
```json
{
"reflowMultilineStringLiterals": {
"never": {}
}
}
```

**description:** Determines how multiline string literals should reflow when formatted.

Expand Down
66 changes: 52 additions & 14 deletions Sources/SwiftFormat/API/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public struct Configuration: Codable, Equatable {
public var multiElementCollectionTrailingCommas: Bool

/// Determines how multiline string literals should reflow when formatted.
public enum MultilineStringReflowBehavior: Codable {
public enum MultilineStringReflowBehavior: String, Codable {
/// Never reflow multiline string literals.
case never
/// Reflow lines in string literal that exceed the maximum line length. For example with a line length of 10:
Expand Down Expand Up @@ -241,20 +241,36 @@ public struct Configuration: Codable, Equatable {
case always

var isNever: Bool {
switch self {
case .never:
return true
default:
return false
}
self == .never
}

var isAlways: Bool {
self == .always
}
}

/// A private enum created to maintain backward compatibility with swift-format version 601.0.0,
/// which had a `MultilineStringReflowBehavior` enum without a String raw type.
///
/// In version 601.0.0, the `reflowMultilineStringLiterals` configuration was encoded as an object
/// with a single key (e.g., `{ "never": {} }`) rather than as a string (e.g., `"never"`). This
/// enum allows decoding from both formats:
/// - First, we attempt to decode as a String using `MultilineStringReflowBehavior`
/// - If that fails, we fall back to this legacy format
/// - If both attempts fail, an error will be thrown
///
/// This approach preserves compatibility without requiring a configuration version bump.
private enum LegacyMultilineStringReflowBehavior: Codable {
case never
case onlyLinesOverLength
case always

/// Converts this legacy enum to the corresponding `MultilineStringReflowBehavior` value.
func toMultilineStringReflowBehavior() -> MultilineStringReflowBehavior {
switch self {
case .always:
return true
default:
return false
case .never: .never
case .always: .always
case .onlyLinesOverLength: .onlyLinesOverLength
}
}
}
Expand Down Expand Up @@ -371,9 +387,31 @@ public struct Configuration: Codable, Equatable {
)
?? defaults.multiElementCollectionTrailingCommas

self.reflowMultilineStringLiterals =
try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals)
?? defaults.reflowMultilineStringLiterals
self.reflowMultilineStringLiterals = try {
// Try to decode `reflowMultilineStringLiterals` as a string
// This handles configurations using the String raw value format (e.g. "never").
// If an error occurs, we'll silently bypass it and fall back to the legacy behavior.
if let behavior = try? container.decodeIfPresent(
MultilineStringReflowBehavior.self,
forKey: .reflowMultilineStringLiterals
) {
return behavior
}

// If the modern format fails, try to decode as an object with a single key.
// This handles configurations from swift-format v601.0.0 (e.g. { "never": {} }).
// If an error occurs in this step, we'll propagate it to the caller.
if let legacyBehavior = try container.decodeIfPresent(
LegacyMultilineStringReflowBehavior.self,
forKey: .reflowMultilineStringLiterals
) {
return legacyBehavior.toMultilineStringReflowBehavior()
}

// If the key is not present in the configuration at all, use the default value.
return defaults.reflowMultilineStringLiterals
}()

self.indentBlankLines =
try container.decodeIfPresent(
Bool.self,
Expand Down
40 changes: 40 additions & 0 deletions Tests/SwiftFormatTests/API/ConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,44 @@ final class ConfigurationTests: XCTestCase {
let path = #"\\mount\test.swift"#
XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path)))
}

func testDecodingReflowMultilineStringLiteralsAsString() throws {
let testCases: [String: Configuration.MultilineStringReflowBehavior] = [
"never": .never,
"always": .always,
"onlyLinesOverLength": .onlyLinesOverLength,
]

for (jsonString, expectedBehavior) in testCases {
let jsonData = """
{
"reflowMultilineStringLiterals": "\(jsonString)"
}
""".data(using: .utf8)!

let config = try JSONDecoder().decode(Configuration.self, from: jsonData)
XCTAssertEqual(config.reflowMultilineStringLiterals, expectedBehavior)
}
}

func testDecodingReflowMultilineStringLiteralsAsObject() throws {

let testCases: [String: Configuration.MultilineStringReflowBehavior] = [
"{ \"never\": {} }": .never,
"{ \"always\": {} }": .always,
"{ \"onlyLinesOverLength\": {} }": .onlyLinesOverLength,
]

for (jsonString, expectedBehavior) in testCases {
let jsonData = """
{
"reflowMultilineStringLiterals": \(jsonString)
}
""".data(using: .utf8)!

let config = try JSONDecoder().decode(Configuration.self, from: jsonData)
XCTAssertEqual(config.reflowMultilineStringLiterals, expectedBehavior)
}
}

}
6 changes: 5 additions & 1 deletion api-breakages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ API breakage: enum Finding.Severity has been removed
API breakage: var Finding.severity has been removed
API breakage: var FindingCategorizing.defaultSeverity has been removed
API breakage: var FindingCategorizing.defaultSeverity has been removed
API breakage: func Rule.diagnose(_:on:severity:anchor:notes:) has been renamed to func diagnose(_:on:anchor:notes:)
API breakage: func Rule.diagnose(_:on:severity:anchor:notes:) has been renamed to func diagnose(_:on:anchor:notes:)
API breakage: func Configuration.MultilineStringReflowBehavior.hash(into:) has been removed
API breakage: func Configuration.MultilineStringReflowBehavior.encode(to:) has been removed
API breakage: var Configuration.MultilineStringReflowBehavior.hashValue has been removed
API breakage: constructor Configuration.MultilineStringReflowBehavior.init(from:) has been removed