From 58807b9061310e626e8be9a2831be2fb7ef99858 Mon Sep 17 00:00:00 2001 From: ttozzi Date: Fri, 4 Apr 2025 03:36:06 +0900 Subject: [PATCH 1/4] Add concurrency control to cancel in-progress PR workflows --- .github/workflows/pull_request.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ab7f274bf..b09ca0d56 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -4,6 +4,10 @@ on: pull_request: types: [opened, reopened, synchronize] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: tests: name: Test From 661aa9ef98706192daddc0d77d6e786c17ef2aa3 Mon Sep 17 00:00:00 2001 From: Dong <93483694+dongdong867@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:18:47 +0800 Subject: [PATCH 2/4] Fix type mismatch error in `reflowMultilineStringLiterals` (#979) --- Documentation/Configuration.md | 25 ++++++- Sources/SwiftFormat/API/Configuration.swift | 66 +++++++++++++++---- .../API/ConfigurationTests.swift | 40 +++++++++++ api-breakages.txt | 6 +- 4 files changed, 121 insertions(+), 16 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 8decd52e6..dbdb171a3 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -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. diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index ac1c742d8..cb5dfb025 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -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: @@ -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 } } } @@ -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, diff --git a/Tests/SwiftFormatTests/API/ConfigurationTests.swift b/Tests/SwiftFormatTests/API/ConfigurationTests.swift index 8fd982f5d..9c6977db8 100644 --- a/Tests/SwiftFormatTests/API/ConfigurationTests.swift +++ b/Tests/SwiftFormatTests/API/ConfigurationTests.swift @@ -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) + } + } + } diff --git a/api-breakages.txt b/api-breakages.txt index 0b04b73c8..519c9a091 100644 --- a/api-breakages.txt +++ b/api-breakages.txt @@ -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:) \ No newline at end of file +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 From 9c64bc27f05d31134a316ff0bf489746726b0de4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 9 Apr 2025 15:20:47 -0700 Subject: [PATCH 3/4] Change default release version for GitHub action releases to 603.0.0 --- .github/workflows/publish_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 96f9f4aea..0a6d51ae0 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -11,13 +11,13 @@ on: required: true swift_format_version: type: string - default: 601.0.0 + default: 603.0.0 description: "swift-format version" # The version of swift-format to tag. If this is a prerelease, `-prerelease-` is added to this version. required: true swift_syntax_tag: type: string - default: 601.0.0 + default: 603.0.0 description: "swift-syntax version" # The swift-syntax version to depend on. If this is a prerelease, the latest swift-syntax prerelease tag for this version is used. required: true From f3253da194722beebfdeb9e8935531788eb07111 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 9 Apr 2025 16:00:44 -0700 Subject: [PATCH 4/4] Add GitHub action that automatically creates a PR to merge main into a release branch In the first period after branching the release branch, we typically want to include all changes from `main` also in the release branch. This workflow automatically creates a PR every Monday to merge main into the release branch. Later in the release cycle we should stop this practice to avoid landing risky changes by disabling this workflow. --- .github/workflows/automerge.yml | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/automerge.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 000000000..086a7c6e6 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,53 @@ +name: Create PR to merge main into release branch + +# In the first period after branching the release branch, we typically want to include all changes from `main` also in the release branch. This workflow automatically creates a PR every Monday to merge main into the release branch. +# Later in the release cycle we should stop this practice to avoid landing risky changes by disabling this workflow. To do so, disable the workflow as described in https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/disabling-and-enabling-a-workflow + +on: + schedule: + - cron: '0 0 * * MON' + workflow_dispatch: + +jobs: + create_merge_pr: + name: Create PR to merge main into release branch + runs-on: ubuntu-latest + if: (github.event_name == 'schedule' && github.repository == 'swiftlang/swift-format') || (github.event_name != 'schedule') # Ensure that we don't run this on a schedule in a fork + steps: + - name: Set up variables + id: variables + run: | + echo "release_branch=release/6.2" >> "$GITHUB_OUTPUT" + echo "pr_branch=automerge/merge-main-$(date +%Y-%m-%d)" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Create merge commit + id: create_merge_commit + run: | + # Without this, we can't perform git operations in GitHub actions. + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + + git checkout ${{ steps.variables.outputs.release_branch }} + git merge main + + if [[ "$(git rev-parse HEAD)" = "$(git rev-parse main)" ]]; then + echo "has_merged_commits=true" >> "$GITHUB_OUTPUT" + else + echo "has_merged_commits=false" >> "$GITHUB_OUTPUT" + fi + - name: Push branch and create PR + id: push_branch + if: ${{ steps.create_merge_commit.outputs.has_merged_commits }} + env: + GH_TOKEN: ${{ github.token }} + run: | + git checkout -b "${{ steps.variables.outputs.pr_branch }}" + git push --set-upstream origin "${{ steps.variables.outputs.pr_branch }}" + + gh pr create -B "${{ steps.variables.outputs.release_branch }}" -H "${{ steps.variables.outputs.pr_branch }}" \ + --title 'Merge `main` into `${{ steps.variables.outputs.release_branch }}`' \ + --body 'This PR was automatically opened by a GitHub action. Review the changes included in this PR and determine if they should be included in the release branch. If yes, merge the PR. Otherwise revert changes that should not be included on this branch.'