use super::*;
pub(crate) fn emit_class(verb: &str, operands: &[&str], out: &mut Vec<BashMutation>) {
match verb {
"npx" | "bunx" => {
if let Some((&tool, rest)) = operands.split_first() {
emit_class(tool, rest, out);
}
}
"cargo" => match operands.first().copied() {
Some("fmt") if !has_flag(operands, &["--check"]) => marker(out, "fmt", "cargo"),
Some("add" | "update" | "remove") => marker(out, "pkg", "cargo"),
_ => {}
},
"rustfmt" => {
if !has_flag(operands, &["--check"]) {
fmt_operands_or_marker(operands, "rustfmt", out);
}
}
"prettier" => {
if has_flag(operands, &["--write", "-w"]) {
fmt_operands_or_marker(operands, "prettier", out);
}
}
"black" => {
if !has_flag(operands, &["--check", "--diff"]) {
fmt_operands_or_marker(operands, "black", out);
}
}
"ruff" => emit_ruff(operands, out),
"eslint" => {
if has_flag(operands, &["--fix"]) {
fmt_operands_or_marker(operands, "eslint", out);
}
}
"gofmt" => {
if has_flag(operands, &["-w"]) {
fmt_operands_or_marker(operands, "gofmt", out);
}
}
"clang-format" => {
if has_flag(operands, &["-i"]) {
fmt_operands_or_marker(operands, "clang-format", out);
}
}
"dprint" => {
if operands.first() == Some(&"fmt") {
marker(out, "fmt", "dprint");
}
}
"npm" | "pnpm" | "yarn" | "bun" => emit_node_runner(verb, operands, out),
"pip" | "pip3" => {
if matches!(operands.first().copied(), Some("install" | "uninstall")) {
marker(out, "pkg", "pip");
}
}
"poetry" => {
if matches!(
operands.first().copied(),
Some("install" | "add" | "update" | "lock" | "remove")
) {
marker(out, "pkg", "poetry");
}
}
"uv" => {
if matches!(
operands.first().copied(),
Some("pip" | "sync" | "add" | "lock")
) {
marker(out, "pkg", "uv");
}
}
"unzip" => {
if !has_flag(operands, &["-l", "-t"]) {
marker(out, "extract", "unzip");
}
}
"7z" | "7za" | "7zz" => {
if matches!(operands.first().copied(), Some("x" | "e")) {
marker(out, "extract", "7z");
}
}
"patch" => {
if !has_flag(operands, &["--dry-run"]) {
marker(out, "extract", "patch");
}
}
_ => {}
}
}
pub(crate) fn tar_extract_marker(operands: &[&str], out: &mut Vec<BashMutation>) {
let dash_extract = operands.iter().any(|t| {
*t == "--extract" || (t.starts_with('-') && !t.starts_with("--") && t.contains('x'))
});
let bare_bundle = operands
.first()
.is_some_and(|t| !t.starts_with('-') && t.len() <= 5 && t.contains('x') && t.contains('f'));
if dash_extract || bare_bundle {
marker(out, "extract", "tar");
}
}
fn emit_ruff(operands: &[&str], out: &mut Vec<BashMutation>) {
let (sub, rest) = match operands.split_first() {
Some((&s, r)) if s == "format" || s == "check" => (Some(s), r),
_ => (None, operands),
};
let formats = sub == Some("format") || has_flag(rest, &["--fix"]);
if formats && !has_flag(rest, &["--check", "--diff"]) {
fmt_operands_or_marker(rest, "ruff", out);
}
}
fn emit_node_runner(verb: &str, operands: &[&str], out: &mut Vec<BashMutation>) {
match operands.first().copied() {
Some("run") => {
if operands
.get(1)
.is_some_and(|n| n.contains("format") || n.contains("fix"))
{
marker(out, "fmt", "npm-script");
}
}
Some("install" | "i" | "ci" | "update" | "add" | "remove" | "uninstall") => {
marker(out, "pkg", pkg_manager_name(verb));
}
Some("audit") if operands.get(1) == Some(&"fix") => {
marker(out, "pkg", pkg_manager_name(verb));
}
_ => {}
}
}
fn marker(out: &mut Vec<BashMutation>, class: &str, tool: &str) {
out.push(BashMutation {
path: format!("{class}:{tool}"),
verb: class_verb(class),
cwd_at: CwdAt::Spawn,
});
}
fn class_verb(class: &str) -> &'static str {
match class {
"fmt" => "fmt",
"pkg" => "pkg",
_ => "extract",
}
}
fn fmt_operands_or_marker(operands: &[&str], tool: &str, out: &mut Vec<BashMutation>) {
let before = out.len();
for op in non_flag_operands(operands) {
if let Some(path) = path_operand(op) {
out.push(BashMutation {
path,
verb: "fmt",
cwd_at: CwdAt::Spawn,
});
}
}
if out.len() == before {
marker(out, "fmt", tool);
}
}
fn has_flag(operands: &[&str], flags: &[&str]) -> bool {
operands.iter().any(|t| flags.contains(t))
}
fn pkg_manager_name(verb: &str) -> &'static str {
match verb {
"pnpm" => "pnpm",
"yarn" => "yarn",
"bun" => "bun",
_ => "npm",
}
}