Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/diff_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
34 changes: 33 additions & 1 deletion src/discover/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
22 changes: 20 additions & 2 deletions src/discover/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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|$)",
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/filters/swift-build.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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:" },
Expand Down
44 changes: 44 additions & 0 deletions src/filters/swift-package.toml
Original file line number Diff line number Diff line change
@@ -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)"
37 changes: 37 additions & 0 deletions src/filters/swift-run.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[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!",
"^\\[\\d+/\\d+\\] ",
"^Building for ",
"^Build of product ",
]
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"
69 changes: 69 additions & 0 deletions src/filters/swift-test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[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!",
"^\\[\\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:|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)"

[[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"
38 changes: 38 additions & 0 deletions src/filters/swiftformat.toml
Original file line number Diff line number Diff line change
@@ -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."
39 changes: 39 additions & 0 deletions src/filters/swiftlint.toml
Original file line number Diff line number Diff line change
@@ -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."
10 changes: 7 additions & 3 deletions src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion src/rake_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) {
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());
}
Expand Down
10 changes: 5 additions & 5 deletions src/toml_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
Expand Down Expand Up @@ -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
Expand Down
Loading