use std::path::PathBuf;
use harn_rules::{load_rule_file, CompiledRule, Rule, RuleKind};
fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
#[test]
fn destructuring_rule_round_trips_to_matches() {
let rule = load_rule_file(fixture("destructure_default.toml")).expect("rule loads");
assert_eq!(rule.id, "destructure-with-defaults");
assert_eq!(rule.kind(), RuleKind::Codemod);
let compiled = CompiledRule::compile(&rule).expect("rule compiles");
let source = "const a = cfg?.timeout ?? 30;\nconst b = opts?.retries ?? 3;\n";
let matches = compiled.run(source).expect("runs");
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].bindings["SRC"].text, "cfg");
assert_eq!(matches[0].bindings["KEY"].text, "timeout");
assert_eq!(matches[0].bindings["DEFAULT"].text, "30");
assert_eq!(matches[0].text, "cfg?.timeout ?? 30");
assert_eq!(matches[1].bindings["SRC"].text, "opts");
assert_eq!(matches[1].bindings["KEY"].text, "retries");
assert_eq!(matches[1].bindings["DEFAULT"].text, "3");
assert_eq!(matches[0].bindings["KEY"].span.start_row, 0);
assert_eq!(matches[1].bindings["KEY"].span.start_row, 1);
}
#[test]
fn typed_placeholder_rule_round_trips_through_toml() {
let rule = Rule::from_toml_str(
r#"
id = "log-of-identifier"
language = "typescript"
[rule]
pattern = "log($ARG:identifier)"
"#,
)
.expect("rule parses");
let compiled = CompiledRule::compile(&rule).expect("rule compiles");
let hit = compiled.run("log(value);\n").expect("runs");
assert_eq!(hit.len(), 1);
assert_eq!(hit[0].bindings["ARG"].text, "value");
let miss = compiled.run("log(compute());\n").expect("runs");
assert!(miss.is_empty(), "`:identifier` must reject a call argument");
}
#[test]
fn the_pattern_is_operator_precise() {
let rule = load_rule_file(fixture("destructure_default.toml")).unwrap();
let compiled = CompiledRule::compile(&rule).unwrap();
let matches = compiled.run("const a = cfg?.timeout || 30;\n").unwrap();
assert!(matches.is_empty(), "`||` must not match the `??` rule");
}