use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn alint() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_alint"))
}
fn run(dir: &Path, args: &[&str]) -> Output {
Command::new(alint())
.args(args)
.current_dir(dir)
.output()
.expect("spawn alint")
}
fn code(o: &Output) -> i32 {
o.status.code().unwrap_or(-1)
}
fn write(dir: &Path, rel: &str, content: &str) {
let p = dir.join(rel);
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(p, content).unwrap();
}
#[test]
fn structured_query_new_finding_is_not_masked() {
let d = tempfile::tempdir().unwrap();
let root = d.path();
write(
root,
".alint.yml",
"version: 1\n\
rules:\n\
\x20 - id: scripts-echo-only\n\
\x20 kind: json_path_matches\n\
\x20 paths: package.json\n\
\x20 path: \"$.scripts[*]\"\n\
\x20 matches: \"^echo \"\n\
\x20 level: error\n",
);
write(
root,
"package.json",
"{\"scripts\":{\"build\":\"webpack\"}}\n",
);
assert_eq!(
code(&run(root, &["check"])),
1,
"build fails before baseline"
);
assert_eq!(code(&run(root, &["baseline"])), 0, "snapshot it");
assert_eq!(
code(&run(root, &["check", "--baseline", ".alint-baseline.json"])),
0,
"the baselined 'webpack' finding is suppressed",
);
write(
root,
"package.json",
"{\"scripts\":{\"build\":\"webpack\",\"test\":\"jest\"}}\n",
);
let out = run(root, &["check", "--baseline", ".alint-baseline.json"]);
assert_eq!(
code(&out),
1,
"the NEW 'jest' finding must surface, not be masked"
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("jest"), "new finding reported: {stdout}");
assert!(
!stdout.contains("webpack"),
"the baselined finding stays suppressed: {stdout}",
);
}
#[test]
fn threshold_stays_suppressed_as_file_grows() {
let d = tempfile::tempdir().unwrap();
let root = d.path();
write(
root,
".alint.yml",
"version: 1\n\
rules:\n\
\x20 - id: not-too-long\n\
\x20 kind: file_max_lines\n\
\x20 paths: [\"*.txt\"]\n\
\x20 max_lines: 3\n\
\x20 level: error\n",
);
write(root, "big.txt", "1\n2\n3\n4\n5\n"); assert_eq!(
code(&run(root, &["check"])),
1,
"over the limit before baseline"
);
assert_eq!(code(&run(root, &["baseline"])), 0, "snapshot it");
write(root, "big.txt", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n");
assert_eq!(
code(&run(root, &["check", "--baseline", ".alint-baseline.json"])),
0,
"still the same accepted finding as the file grows — no churn",
);
}
#[test]
fn first_offender_keyed_on_file_survives_the_first_fix() {
let d = tempfile::tempdir().unwrap();
let root = d.path();
write(
root,
".alint.yml",
"version: 1\n\
rules:\n\
\x20 - id: no-trailing-ws\n\
\x20 kind: no_trailing_whitespace\n\
\x20 paths: [\"*.txt\"]\n\
\x20 level: error\n",
);
write(root, "code.txt", "line1 \nline2\nline3 \n");
assert_eq!(code(&run(root, &["check"])), 1, "dirty before baseline");
assert_eq!(code(&run(root, &["baseline"])), 0, "snapshot it");
write(root, "code.txt", "line1\nline2\nline3 \n");
assert_eq!(
code(&run(root, &["check", "--baseline", ".alint-baseline.json"])),
0,
"the file-level key keeps it suppressed while any offender remains",
);
write(root, "code.txt", "line1\nline2\nline3\n");
let clean = run(root, &["check", "--baseline", ".alint-baseline.json"]);
assert_eq!(code(&clean), 0, "fully fixed → clean");
let stderr = String::from_utf8_lossy(&clean.stderr);
assert!(
stderr.contains("no longer fire") || stderr.contains("stale"),
"a fully-fixed baselined finding warns as stale: {stderr}",
);
}
#[test]
fn line_max_width_first_offender_keyed_on_file_no_content_churn() {
let d = tempfile::tempdir().unwrap();
let root = d.path();
write(
root,
".alint.yml",
"version: 1\n\
rules:\n\
\x20 - id: max-width\n\
\x20 kind: line_max_width\n\
\x20 paths: [\"*.txt\"]\n\
\x20 max_width: 10\n\
\x20 level: error\n",
);
write(root, "code.txt", "0123456789ABCDEF\n"); assert_eq!(code(&run(root, &["check"])), 1, "dirty before baseline");
assert_eq!(code(&run(root, &["baseline"])), 0, "snapshot it");
write(root, "code.txt", "FEDCBA9876543210\n");
assert_eq!(
code(&run(root, &["check", "--baseline", ".alint-baseline.json"])),
0,
"path-keyed: editing the over-wide line's content must not re-fire",
);
}