From ca251fa10276e77a33836e1c4119e41aab20d097 Mon Sep 17 00:00:00 2001 From: Patrick szymkowiak Date: Thu, 26 Mar 2026 09:29:55 +0100 Subject: [PATCH 1/2] feat(swift): add comprehensive Swift ecosystem filters (#848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TOML filters: - swift-test.toml: strip passing tests, show failures + summary - swift-package.toml: strip Fetching/Resolving noise (resolve, update) - swift-run.toml: strip build noise, show app output - swiftlint.toml: strip Linting noise, show violations - swiftformat.toml: strip noise, short-circuit on no changes Rewrite rules: - swift (build|test|run|package) → rtk swift ... - swiftlint → rtk swiftlint - swiftformat → rtk swiftformat Also fixes pre-existing cargo fmt/clippy issues in diff_cmd.rs, init.rs, and rake_cmd.rs (map_or → is_some_and). 1119 tests passed, 0 failed. Signed-off-by: Patrick szymkowiak --- src/diff_cmd.rs | 6 +++- src/discover/registry.rs | 34 ++++++++++++++++++- src/discover/rules.rs | 22 +++++++++++-- src/filters/swift-package.toml | 44 +++++++++++++++++++++++++ src/filters/swift-run.toml | 34 +++++++++++++++++++ src/filters/swift-test.toml | 60 ++++++++++++++++++++++++++++++++++ src/filters/swiftformat.toml | 38 +++++++++++++++++++++ src/filters/swiftlint.toml | 39 ++++++++++++++++++++++ src/init.rs | 10 ++++-- src/rake_cmd.rs | 2 +- src/toml_filter.rs | 10 +++--- 11 files changed, 286 insertions(+), 13 deletions(-) create mode 100644 src/filters/swift-package.toml create mode 100644 src/filters/swift-run.toml create mode 100644 src/filters/swift-test.toml create mode 100644 src/filters/swiftformat.toml create mode 100644 src/filters/swiftlint.toml diff --git a/src/diff_cmd.rs b/src/diff_cmd.rs index 15b433a8..d07e1dc9 100644 --- a/src/diff_cmd.rs +++ b/src/diff_cmd.rs @@ -352,7 +352,11 @@ diff --git a/b.rs b/b.rs let result = compute_diff(&a_refs, &b_refs); // Should have ~167 changes (every 3rd line), all present - assert!(result.changes.len() > 100, "Expected 100+ changes, got {}", result.changes.len()); + assert!( + result.changes.len() > 100, + "Expected 100+ changes, got {}", + result.changes.len() + ); // No truncation — changes count matches what we generate assert!(!result.changes.is_empty()); } diff --git a/src/discover/registry.rs b/src/discover/registry.rs index e15d9bcd..b5286ad6 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -1584,12 +1584,44 @@ mod tests { Classification::Supported { rtk_equivalent: "rtk swift", category: "Build", - estimated_savings_pct: 90.0, + estimated_savings_pct: 85.0, status: RtkStatus::Existing, } )); } + #[test] + fn test_classify_swiftlint() { + assert!(matches!( + classify_command("swiftlint"), + Classification::Supported { + rtk_equivalent: "rtk swiftlint", + category: "Lint", + .. + } + )); + } + + #[test] + fn test_classify_swiftformat() { + assert!(matches!( + classify_command("swiftformat ."), + Classification::Supported { + rtk_equivalent: "rtk swiftformat", + category: "Lint", + .. + } + )); + } + + #[test] + fn test_rewrite_swift_package() { + assert_eq!( + rewrite_command("swift package resolve", &[]), + Some("rtk swift package resolve".into()) + ); + } + #[test] fn test_rewrite_swift_test() { assert_eq!( diff --git a/src/discover/rules.rs b/src/discover/rules.rs index e0be1cf2..da459aac 100644 --- a/src/discover/rules.rs +++ b/src/discover/rules.rs @@ -79,7 +79,9 @@ pub const PATTERNS: &[&str] = &[ r"^shellcheck\b", r"^shopify\s+theme\s+(push|pull)", r"^sops\b", - r"^swift\s+(build|test)\b", + r"^swift\s+(build|test|run|package)\b", + r"^swiftlint\b", + r"^swiftformat\b", r"^systemctl\s+status\b", r"^terraform\s+plan", r"^tofu\s+(fmt|init|plan|validate)(\s|$)", @@ -600,7 +602,23 @@ pub const RULES: &[RtkRule] = &[ rewrite_prefixes: &["swift"], category: "Build", savings_pct: 65.0, - subcmd_savings: &[("test", 90.0)], + subcmd_savings: &[("test", 85.0), ("run", 70.0), ("package", 80.0)], + subcmd_status: &[], + }, + RtkRule { + rtk_cmd: "rtk swiftlint", + rewrite_prefixes: &["swiftlint"], + category: "Lint", + savings_pct: 75.0, + subcmd_savings: &[], + subcmd_status: &[], + }, + RtkRule { + rtk_cmd: "rtk swiftformat", + rewrite_prefixes: &["swiftformat"], + category: "Lint", + savings_pct: 70.0, + subcmd_savings: &[], subcmd_status: &[], }, RtkRule { diff --git a/src/filters/swift-package.toml b/src/filters/swift-package.toml new file mode 100644 index 00000000..e77f7a8c --- /dev/null +++ b/src/filters/swift-package.toml @@ -0,0 +1,44 @@ +[filters.swift-package] +description = "Compact swift package output — strip Fetching/Resolving noise, show summary" +match_command = "^swift\\s+package\\s+(resolve|update)\\b" +strip_ansi = true +strip_lines_matching = [ + "^\\s*$", + "^Fetching ", + "^Fetched ", + "^Computing version ", + "^Creating working copy ", + "^Working copy of ", + "^Cloning ", +] +match_output = [ + { pattern = "^Resolved", message = "ok (dependencies resolved)" }, +] +max_lines = 30 +on_empty = "ok (swift package: no output)" + +[[tests.swift-package]] +name = "resolve strips fetching noise" +input = """ +Fetching https://github.com/apple/swift-argument-parser from cache +Fetching https://github.com/apple/swift-log from cache +Fetched https://github.com/apple/swift-argument-parser from cache (0.42s) +Fetched https://github.com/apple/swift-log from cache (0.38s) +Computing version for https://github.com/apple/swift-argument-parser +Computed https://github.com/apple/swift-argument-parser at 1.3.0 (0.01s) +Computing version for https://github.com/apple/swift-log +Computed https://github.com/apple/swift-log at 1.5.4 (0.01s) +Resolved +""" +expected = "ok (dependencies resolved)" + +[[tests.swift-package]] +name = "update with new versions shows computed lines" +input = """ +Fetching https://github.com/apple/swift-argument-parser +Fetched https://github.com/apple/swift-argument-parser (1.23s) +Computing version for https://github.com/apple/swift-argument-parser +Computed https://github.com/apple/swift-argument-parser at 1.4.0 (0.02s) +Updated https://github.com/apple/swift-argument-parser (1.3.0 -> 1.4.0) +""" +expected = "Computed https://github.com/apple/swift-argument-parser at 1.4.0 (0.02s)\nUpdated https://github.com/apple/swift-argument-parser (1.3.0 -> 1.4.0)" diff --git a/src/filters/swift-run.toml b/src/filters/swift-run.toml new file mode 100644 index 00000000..a640f544 --- /dev/null +++ b/src/filters/swift-run.toml @@ -0,0 +1,34 @@ +[filters.swift-run] +description = "Compact swift run output — strip build noise, show application output" +match_command = "^swift\\s+run\\b" +strip_ansi = true +strip_lines_matching = [ + "^\\s*$", + "^Compiling ", + "^Linking ", + "^Build complete!", +] +max_lines = 80 +on_empty = "ok (swift run: no output)" + +[[tests.swift-run]] +name = "strips build noise, keeps app output" +input = """ +Compiling MyApp main.swift +Compiling MyApp Config.swift +Linking MyApp +Build complete! +Hello, World! +Processing 42 items... +Done. +""" +expected = "Hello, World!\nProcessing 42 items...\nDone." + +[[tests.swift-run]] +name = "build error passes through" +input = """ +Compiling MyApp main.swift +/Users/dev/MyApp/Sources/main.swift:3:5: error: cannot find 'foo' in scope +error: build had 1 command failure +""" +expected = "/Users/dev/MyApp/Sources/main.swift:3:5: error: cannot find 'foo' in scope\nerror: build had 1 command failure" diff --git a/src/filters/swift-test.toml b/src/filters/swift-test.toml new file mode 100644 index 00000000..9d3a508a --- /dev/null +++ b/src/filters/swift-test.toml @@ -0,0 +1,60 @@ +[filters.swift-test] +description = "Compact swift test output — strip passing tests, show failures and summary" +match_command = "^swift\\s+test\\b" +strip_ansi = true +strip_lines_matching = [ + "^\\s*$", + "^Compiling ", + "^Linking ", + "^Build complete!", + "^Test Case .* passed ", + "^Test Suite .* started at ", + "^Test Suite .* passed at ", +] +match_output = [ + { pattern = "^Executed \\d+ tests?, with 0 failures", message = "ok (all tests passed)", unless = "warning:" }, +] +max_lines = 60 +on_empty = "ok (swift test: no output)" + +[[tests.swift-test]] +name = "all tests pass — short-circuit to ok" +input = """ +Compiling MyApp MyApp.swift +Linking MyAppTests +Build complete! +Test Suite 'All tests' started at 2026-03-10 12:00:00 +Test Suite 'MyAppTests' started at 2026-03-10 12:00:00 +Test Case '-[MyAppTests testAddition]' passed (0.001 seconds). +Test Case '-[MyAppTests testSubtraction]' passed (0.002 seconds). +Test Suite 'MyAppTests' passed at 2026-03-10 12:00:01 +Executed 2 tests, with 0 failures in 0.003 seconds +""" +expected = "ok (all tests passed)" + +[[tests.swift-test]] +name = "test failures shown with summary" +input = """ +Compiling MyApp MyApp.swift +Linking MyAppTests +Build complete! +Test Suite 'All tests' started at 2026-03-10 12:00:00 +Test Suite 'MyAppTests' started at 2026-03-10 12:00:00 +Test Case '-[MyAppTests testAddition]' passed (0.001 seconds). +Test Case '-[MyAppTests testFailing]' failed (0.002 seconds). +/Users/dev/MyApp/Tests/MyAppTests.swift:15: error: -[MyAppTests testFailing] : XCTAssertEqual failed: ("1") is not equal to ("2") +Test Suite 'MyAppTests' failed at 2026-03-10 12:00:01 +Executed 2 tests, with 1 failure in 0.003 seconds +""" +expected = "Test Case '-[MyAppTests testFailing]' failed (0.002 seconds).\n/Users/dev/MyApp/Tests/MyAppTests.swift:15: error: -[MyAppTests testFailing] : XCTAssertEqual failed: (\"1\") is not equal to (\"2\")\nTest Suite 'MyAppTests' failed at 2026-03-10 12:00:01\nExecuted 2 tests, with 1 failure in 0.003 seconds" + +[[tests.swift-test]] +name = "compile error before tests" +input = """ +Compiling MyApp MyApp.swift +/Users/dev/MyApp/Sources/MyApp/main.swift:5:1: error: use of unresolved identifier 'foo' +foo() +^~~ +error: build had 1 command failure +""" +expected = "/Users/dev/MyApp/Sources/MyApp/main.swift:5:1: error: use of unresolved identifier 'foo'\nfoo()\n^~~\nerror: build had 1 command failure" diff --git a/src/filters/swiftformat.toml b/src/filters/swiftformat.toml new file mode 100644 index 00000000..ea54d7af --- /dev/null +++ b/src/filters/swiftformat.toml @@ -0,0 +1,38 @@ +[filters.swiftformat] +description = "Compact swiftformat output — show changed files count" +match_command = "^swiftformat\\b" +strip_ansi = true +strip_lines_matching = [ + "^\\s*$", + "^Running SwiftFormat", + "^Reading config", + "^\\d+/\\d+ files", +] +match_output = [ + { pattern = "0 files would have been changed", message = "ok (swiftformat: no changes needed)" }, + { pattern = "SwiftFormat completed. 0/", message = "ok (swiftformat: no changes needed)" }, +] +max_lines = 40 +on_empty = "ok (swiftformat: no output)" + +[[tests.swiftformat]] +name = "no changes needed" +input = """ +Running SwiftFormat... +Reading config file at /Users/dev/App/.swiftformat +0 files would have been changed out of 12 files inspected +SwiftFormat completed. 0/12 files updated. +""" +expected = "ok (swiftformat: no changes needed)" + +[[tests.swiftformat]] +name = "files changed" +input = """ +Running SwiftFormat... +Reading config file at /Users/dev/App/.swiftformat +/Users/dev/App/ViewController.swift +/Users/dev/App/Model.swift +2 files would have been changed out of 12 files inspected +SwiftFormat completed. 2/12 files updated. +""" +expected = "/Users/dev/App/ViewController.swift\n/Users/dev/App/Model.swift\n2 files would have been changed out of 12 files inspected\nSwiftFormat completed. 2/12 files updated." diff --git a/src/filters/swiftlint.toml b/src/filters/swiftlint.toml new file mode 100644 index 00000000..14e94690 --- /dev/null +++ b/src/filters/swiftlint.toml @@ -0,0 +1,39 @@ +[filters.swiftlint] +description = "Compact swiftlint output — strip passing files, show violations grouped" +match_command = "^swiftlint\\b" +strip_ansi = true +strip_lines_matching = [ + "^\\s*$", + "^Linting ", + "^Done linting!", + "^Loading configuration", +] +max_lines = 60 +on_empty = "ok (swiftlint: no violations)" + +[[tests.swiftlint]] +name = "no violations" +input = """ +Loading configuration from '.swiftlint.yml' +Linting 'ViewController.swift' (1/5) +Linting 'AppDelegate.swift' (2/5) +Linting 'Model.swift' (3/5) +Linting 'Service.swift' (4/5) +Linting 'Config.swift' (5/5) +Done linting! Found 0 violations, 0 serious in 5 files. +""" +expected = "ok (swiftlint: no violations)" + +[[tests.swiftlint]] +name = "violations shown" +input = """ +Loading configuration from '.swiftlint.yml' +Linting 'ViewController.swift' (1/3) +Linting 'AppDelegate.swift' (2/3) +Linting 'Model.swift' (3/3) +/Users/dev/App/ViewController.swift:42:1: warning: Line Length Violation: Line should be 120 characters or less; currently it has 145 characters (line_length) +/Users/dev/App/ViewController.swift:58:5: warning: Force Cast Violation: Force casts should be avoided (force_cast) +/Users/dev/App/Model.swift:12:1: error: Function Body Length Violation: Function body should span 50 lines or less (function_body_length) +Done linting! Found 3 violations, 1 serious in 3 files. +""" +expected = "/Users/dev/App/ViewController.swift:42:1: warning: Line Length Violation: Line should be 120 characters or less; currently it has 145 characters (line_length)\n/Users/dev/App/ViewController.swift:58:5: warning: Force Cast Violation: Force casts should be avoided (force_cast)\n/Users/dev/App/Model.swift:12:1: error: Function Body Length Violation: Function body should span 50 lines or less (function_body_length)\nDone linting! Found 3 violations, 1 serious in 3 files." diff --git a/src/init.rs b/src/init.rs index 5bf5071b..53bbe70c 100644 --- a/src/init.rs +++ b/src/init.rs @@ -2356,12 +2356,16 @@ pub fn run_copilot(verbose: u8) -> Result<()> { let github_dir = Path::new(".github"); let hooks_dir = github_dir.join("hooks"); - fs::create_dir_all(&hooks_dir) - .context("Failed to create .github/hooks/ directory")?; + fs::create_dir_all(&hooks_dir).context("Failed to create .github/hooks/ directory")?; // 1. Write hook config let hook_path = hooks_dir.join("rtk-rewrite.json"); - write_if_changed(&hook_path, COPILOT_HOOK_JSON, "Copilot hook config", verbose)?; + write_if_changed( + &hook_path, + COPILOT_HOOK_JSON, + "Copilot hook config", + verbose, + )?; // 2. Write instructions let instructions_path = github_dir.join("copilot-instructions.md"); diff --git a/src/rake_cmd.rs b/src/rake_cmd.rs index e3fba68f..f259cdd9 100644 --- a/src/rake_cmd.rs +++ b/src/rake_cmd.rs @@ -15,7 +15,7 @@ use anyhow::{Context, Result}; /// `rails test` which handles single files, multiple files, and line-number /// syntax (`file.rb:15`) natively. fn select_runner(args: &[String]) -> (&'static str, Vec) { - let has_test_subcommand = args.first().map_or(false, |a| a == "test"); + let has_test_subcommand = args.first().is_some_and(|a| a == "test"); if !has_test_subcommand { return ("rake", args.to_vec()); } diff --git a/src/toml_filter.rs b/src/toml_filter.rs index 0f571626..20ee51a3 100644 --- a/src/toml_filter.rs +++ b/src/toml_filter.rs @@ -1610,8 +1610,8 @@ match_command = "^make\\b" let filters = make_filters(BUILTIN_TOML); assert_eq!( filters.len(), - 58, - "Expected exactly 58 built-in filters, got {}. \ + 63, + "Expected exactly 63 built-in filters, got {}. \ Update this count when adding/removing filters in src/filters/.", filters.len() ); @@ -1668,11 +1668,11 @@ expected = "output line 1\noutput line 2" let combined = format!("{}\n\n{}", BUILTIN_TOML, new_filter); let filters = make_filters(&combined); - // All 58 existing filters still present + 1 new = 59 + // All 63 existing filters still present + 1 new = 64 assert_eq!( filters.len(), - 59, - "Expected 59 filters after concat (58 built-in + 1 new)" + 64, + "Expected 64 filters after concat (63 built-in + 1 new)" ); // New filter is discoverable From b40636857b52fc55cb8cd0d98028e126e4abf9f1 Mon Sep 17 00:00:00 2001 From: Patrick szymkowiak Date: Thu, 26 Mar 2026 09:46:49 +0100 Subject: [PATCH 2/2] fix(swift): add macOS SPM output patterns to TOML filters Strip [N/M] progress lines, 'Building for debugging...', and 'Build of product' lines from macOS swift build/test/run output. Tested on real Swift API project on macOS. Signed-off-by: Patrick szymkowiak --- src/filters/swift-build.toml | 3 +++ src/filters/swift-run.toml | 3 +++ src/filters/swift-test.toml | 11 ++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/filters/swift-build.toml b/src/filters/swift-build.toml index fb866e5f..3bd1e115 100644 --- a/src/filters/swift-build.toml +++ b/src/filters/swift-build.toml @@ -6,6 +6,9 @@ strip_lines_matching = [ "^\\s*$", "^Compiling ", "^Linking ", + "^\\[\\d+/\\d+\\] ", + "^Building for ", + "^Build of product ", ] match_output = [ { pattern = "Build complete!", message = "ok (build complete)", unless = "warning:|error:" }, diff --git a/src/filters/swift-run.toml b/src/filters/swift-run.toml index a640f544..493e1591 100644 --- a/src/filters/swift-run.toml +++ b/src/filters/swift-run.toml @@ -7,6 +7,9 @@ strip_lines_matching = [ "^Compiling ", "^Linking ", "^Build complete!", + "^\\[\\d+/\\d+\\] ", + "^Building for ", + "^Build of product ", ] max_lines = 80 on_empty = "ok (swift run: no output)" diff --git a/src/filters/swift-test.toml b/src/filters/swift-test.toml index 9d3a508a..ba327f0b 100644 --- a/src/filters/swift-test.toml +++ b/src/filters/swift-test.toml @@ -7,12 +7,21 @@ strip_lines_matching = [ "^Compiling ", "^Linking ", "^Build complete!", + "^\\[\\d+/\\d+\\] ", + "^Building for ", + "^Build of product ", "^Test Case .* passed ", + "^Test Case .* started\\.", "^Test Suite .* started at ", "^Test Suite .* passed at ", + "^\\t\\s*Executed \\d+ tests?, with 0 failures", + "^. Test run started", + "^. Testing Library", + "^. Test run with 0 tests", ] match_output = [ - { pattern = "^Executed \\d+ tests?, with 0 failures", message = "ok (all tests passed)", unless = "warning:" }, + { pattern = "^Executed \\d+ tests?, with 0 failures", message = "ok (all tests passed)", unless = "warning:|error:" }, + { pattern = "Executed \\d+ tests?, with 0 failures", message = "ok (all tests passed)", unless = "warning:|error:" }, ] max_lines = 60 on_empty = "ok (swift test: no output)"