use super::*;
#[test]
fn test_unused_variable() {
let diags = lint_source(
r#"
pipeline default(task) {
const unused = 42
log("hello")
}
"#,
);
assert!(
has_rule(&diags, "unused-variable"),
"expected unused-variable warning, got: {diags:?}"
);
}
#[test]
fn test_public_module_bindings_are_externally_reachable() {
let diags = lint_source(
r#"
pub const EXPORTED_SETTING: string = "configured"
pub let exported_counter = 0
"#,
);
assert!(
!has_rule(&diags, "unused-variable"),
"public module bindings must not be treated as file-local dead code: {diags:?}"
);
}
#[test]
fn test_private_module_and_local_bindings_remain_checked() {
let diags = lint_source(
r#"
const PRIVATE_SETTING = "configured"
fn read_setting() {
const local_setting = "local"
return PRIVATE_SETTING
}
"#,
);
assert!(
diags.iter().any(|diagnostic| {
diagnostic.rule == "unused-variable" && diagnostic.message.contains("`local_setting`")
}),
"unused local binding should still be reported: {diags:?}"
);
assert!(
!diags.iter().any(|diagnostic| {
diagnostic.rule == "unused-variable" && diagnostic.message.contains("`PRIVATE_SETTING`")
}),
"referenced private module binding should remain clean: {diags:?}"
);
let unused_private = lint_source("const PRIVATE_SETTING = \"configured\"");
assert!(
unused_private.iter().any(|diagnostic| {
diagnostic.rule == "unused-variable" && diagnostic.message.contains("`PRIVATE_SETTING`")
}),
"unused private module binding should still be reported: {unused_private:?}"
);
let invalid_public_local = lint_source(
r#"
fn invalid_export() {
pub const local_setting = "local"
}
"#,
);
assert!(
invalid_public_local.iter().any(|diagnostic| {
diagnostic.rule == "unused-variable" && diagnostic.message.contains("`local_setting`")
}),
"a local pub modifier must not make the binding externally reachable: {invalid_public_local:?}"
);
}
#[test]
fn test_unused_underscore_ignored() {
let diags = lint_source(
r#"
pipeline default(task) {
const _ = 42
log("hello")
}
"#,
);
assert!(
!has_rule(&diags, "unused-variable"),
"underscore variables should not trigger unused-variable: {diags:?}"
);
}
#[test]
fn test_unused_underscore_prefixed_local_warns() {
let source = r#"
pipeline default(task) {
const _cleanup = cleanup()
log("hello")
}
"#;
let diags = lint_source(source);
assert!(
diags
.iter()
.any(|d| d.rule == "unused-variable" && d.message.contains("`_cleanup`")),
"expected unused-variable for underscore-prefixed local, got: {diags:?}"
);
let result = apply_fixes(source, &diags);
assert!(
result.contains("const _ = cleanup()"),
"expected underscore-prefixed local to autofix to discard binding, got: {result}"
);
}
#[test]
fn test_used_underscore_prefixed_local_is_not_rewritten() {
let source = r"
pipeline default(task) {
const _totals = record_usage()
log(_totals)
}
";
let diags = lint_source(source);
assert!(
!diags
.iter()
.any(|d| d.rule == "unused-variable" && d.message.contains("`_totals`")),
"used underscore-prefixed locals must not trigger unused-variable: {diags:?}"
);
let result = apply_fixes(source, &diags);
assert!(
result.contains("const _totals = record_usage()"),
"used underscore-prefixed local must not be rewritten: {result}"
);
}
#[test]
fn test_unused_underscore_prefixed_pattern_binding_ignored() {
let diags = lint_source(
r#"
pipeline default(task) {
const { _ignored } = { _ignored: 42 }
log("hello")
}
"#,
);
assert!(
!has_rule(&diags, "unused-pattern-binding"),
"underscore-prefixed pattern bindings should stay intent-preserving: {diags:?}"
);
}
#[test]
fn test_unused_discard_parameter_ignored() {
let diags = lint_source(
r#"
pipeline default() {
fn greet(name, _) {
log(name)
}
const f = { _, value -> log(value) }
greet("hi", "there")
f("ignored", "kept")
}
"#,
);
assert!(
!has_rule(&diags, "unused-parameter"),
"discard parameters should not trigger unused-parameter: {diags:?}"
);
}
#[test]
fn test_unused_fn_param() {
let diags = lint_source(
r#"
pipeline default(task) {
fn greet(name, unused) {
log(name)
}
greet("hi", "there")
}
"#,
);
assert!(
has_rule(&diags, "unused-parameter"),
"expected unused-parameter for unused fn param, got: {diags:?}"
);
assert!(
!has_rule(&diags, "unused-variable"),
"unused fn param should not trigger unused-variable: {diags:?}"
);
let result = apply_fixes(
"pipeline default(task) {\nfn greet(name, unused: HarnessTools) {\n log(name)\n}\ngreet(\"hi\", {})\n}",
&lint_source(
"pipeline default(task) {\nfn greet(name, unused: HarnessTools) {\n log(name)\n}\ngreet(\"hi\", {})\n}",
),
);
assert!(
result.contains("fn greet(name, _unused: HarnessTools)"),
"unused parameter repair must preserve positional arity: {result}"
);
}
#[test]
fn unused_runtime_pipeline_slot_is_removed_instead_of_renamed() {
let source = r#"@test
pipeline test_runtime(harness: Harness, _task: unknown) {
harness.stdio.println("ready")
}"#;
let diagnostics = lint_source(source);
assert!(
has_rule(&diagnostics, "unused-pipeline-input"),
"legacy underscore slots should participate in pipeline removal: {diagnostics:?}"
);
assert_eq!(
apply_fixes(source, &diagnostics),
r#"@test
pipeline test_runtime(harness: Harness) {
harness.stdio.println("ready")
}"#
);
}
#[test]
fn externally_selectable_pipeline_slots_keep_positional_arity() {
for name in ["default", "main", "auto", "helper", "test_helper"] {
let source = format!(
"pipeline {name}(harness: Harness, _task: unknown) {{\n harness.stdio.println(\"ready\")\n}}"
);
let diagnostics = lint_source(&source);
assert!(
!has_rule(&diagnostics, "unused-pipeline-input"),
"host-selected pipeline `{name}` must retain its out-of-band argument contract: {diagnostics:?}"
);
assert_eq!(apply_fixes(&source, &diagnostics), source);
}
}
#[test]
fn pipeline_slot_removal_preserves_neighbors_and_their_comments() {
let source = r"@test
pipeline test_values(first: int, _unused: int, last: int) {
log(first + last)
}";
assert_eq!(
apply_fixes(source, &lint_source(source)),
r"@test
pipeline test_values(first: int, last: int) {
log(first + last)
}"
);
let documented_next = r"@test
pipeline test_documented(_unused: int, // belongs to value
value: int) {
log(value)
}";
assert_eq!(
apply_fixes(documented_next, &lint_source(documented_next)),
r"@test
pipeline test_documented( // belongs to value
value: int) {
log(value)
}"
);
let all_unused = r"@test
pipeline test_all_unused(_first: int, _second: int) {
return 1
}";
assert_eq!(
apply_fixes(all_unused, &lint_source(all_unused)),
r"@test
pipeline test_all_unused() {
return 1
}",
"one fix pass must remove adjacent unused slots without overlap"
);
}
#[test]
fn pipeline_slot_removal_ignores_commas_inside_trivia() {
let first = r"@test
pipeline test_first(_unused: int /* current note, still current */, value: int) {
return value
}";
let first_fixed = apply_fixes(first, &lint_source(first));
assert_eq!(
first_fixed,
r"@test
pipeline test_first(value: int) {
return value
}"
);
let _ = lint_source(&first_fixed);
let last = r"@test
pipeline test_last(value: int, /* removed note, with comma */ _unused: int) {
return value
}";
let last_fixed = apply_fixes(last, &lint_source(last));
assert_eq!(
last_fixed,
r"@test
pipeline test_last(value: int) {
return value
}"
);
let _ = lint_source(&last_fixed);
let adjacent = r"@test
pipeline test_adjacent(_first: int /* first, note */, _second: int) {
return 1
}";
let adjacent_fixed = apply_fixes(adjacent, &lint_source(adjacent));
assert_eq!(
adjacent_fixed,
r"@test
pipeline test_adjacent() {
return 1
}"
);
let _ = lint_source(&adjacent_fixed);
}
#[test]
fn unused_bare_test_pipeline_slot_is_removed() {
let source = r"@test
pipeline test_ready(_task: unknown) {
assert(true)
}";
assert_eq!(
apply_fixes(source, &lint_source(source)),
r"@test
pipeline test_ready() {
assert(true)
}"
);
}
#[test]
fn caller_and_table_bound_pipeline_slots_keep_positional_arity() {
let called = r"@test
pipeline test_helper(_value: int) {
return 1
}
pipeline default() {
return test_helper(2)
}";
assert_eq!(
apply_fixes(called, &lint_source(called)),
called,
"a local caller owns the helper's positional slot and its binding name"
);
let table = r#"@test(cases: [{name: "one", args: [1]}])
pipeline test_case(value: int) {
assert(true)
}"#;
assert_eq!(
apply_fixes(table, &lint_source(table)),
table,
"table rows own the test pipeline's positional slots and binding names"
);
}
#[test]
fn extended_pipeline_slots_keep_positional_arity() {
let extended = r"pipeline child(value: int) extends base {
return 1
}";
assert_eq!(
apply_fixes(extended, &lint_source(extended)),
extended,
"an extended pipeline inherits an opaque positional contract"
);
}
#[test]
fn public_pipeline_slots_keep_positional_arity() {
let source = r"pub pipeline exported(value: int) {
return 1
}";
assert_eq!(
apply_fixes(source, &lint_source(source)),
source,
"external callers may depend on a public pipeline's full declaration contract"
);
}
#[test]
fn test_unused_closure_param() {
let diags = lint_source(
r"
pipeline default(task) {
const f = { x, y -> log(x) }
f(1, 2)
}
",
);
assert!(
has_rule(&diags, "unused-parameter"),
"expected unused-parameter for unused closure param, got: {diags:?}"
);
}
#[test]
fn test_unused_param_underscore_prefix_ignored() {
let diags = lint_source(
r#"
pipeline default() {
fn greet(name, _unused) {
log(name)
}
greet("hi", "there")
}
"#,
);
assert!(
!has_rule(&diags, "unused-parameter"),
"underscore-prefixed params should not trigger unused-parameter: {diags:?}"
);
}
#[test]
fn test_used_fn_param_ok() {
let diags = lint_source(
r"
pipeline default() {
fn add(a, b) {
return a + b
}
log(add(1, 2))
}
",
);
assert!(
!has_rule(&diags, "unused-parameter"),
"used params should not trigger unused-parameter: {diags:?}"
);
}
#[test]
fn test_parallel_options_mark_variables_used() {
let diags = lint_source(
r"
pipeline default(task) {
const concurrency = 2
const results = parallel each [1, 2] with { max_concurrent: concurrency } { n -> n }
log(results)
}
",
);
assert!(
!has_rule(&diags, "unused-variable"),
"parallel options should mark referenced variables used: {diags:?}"
);
}
#[test]
fn test_destructuring_defaults_mark_referenced_variables_used() {
let diags = lint_source(
r#"
pipeline default(task) {
const persona = "p"
const kind = "repair"
const downstream = "review"
const { step_name = "crystallized_${persona}_${kind}_${downstream}", function_name = step_name + "_step" } = {}
log(function_name)
}
"#,
);
assert!(
!has_rule(&diags, "unused-variable"),
"destructuring defaults should mark referenced variables used: {diags:?}"
);
}
#[test]
fn test_parameter_defaults_mark_referenced_parameters_used() {
let diags = lint_source(
r#"
pipeline default(task) {
fn child_path(root, child = path_join(root, "child")) {
return child
}
log(child_path("/tmp"))
}
"#,
);
assert!(
!diags
.iter()
.any(|d| d.rule == "unused-parameter" && d.message.contains("`root`")),
"parameters used by default values should not trigger unused-parameter: {diags:?}"
);
}
#[test]
fn test_mutex_key_marks_variable_used() {
let diags = lint_source(
r#"
pipeline default(task) {
const key = "tenant-a"
mutex(key) {
log("locked")
}
}
"#,
);
assert!(
!has_rule(&diags, "unused-variable"),
"mutex key expressions should mark referenced variables used: {diags:?}"
);
}
#[test]
fn test_attribute_arguments_mark_nested_identifiers_used() {
let diags = lint_source(
r#"const evidence_used = ["https://example.com/spec"]
const metadata_used = "fixture-a"
const genuinely_unused = ["https://example.com/unused"]
const id = "dict keys are not source references"
fn attribute_only_fallback() -> bool { return true }
@invariant
@deterministic
@archivist(evidence: evidence_used, confidence: 0.9, source_date: "2026-08-01", coverage_examples: [{id: metadata_used}], fallback: attribute_only_fallback, trigger: schedule("*/30 * * * *"), autonomy: act_with_approval)
pub fn inspect(_slice, _ctx, _repo) -> bool { return true }
"#,
);
let unused: Vec<_> = diags
.iter()
.filter(|diagnostic| diagnostic.code == Code::LintUnusedVariable)
.collect();
assert_eq!(unused.len(), 2, "only genuine source references count");
assert_eq!(
unused
.iter()
.map(|diagnostic| diagnostic.span.line)
.collect::<Vec<_>>(),
vec![3, 4],
"diagnostics must target the unused declaration and colliding dict key"
);
assert!(
!diags
.iter()
.any(|diagnostic| diagnostic.code == Code::LintUndefinedFunction),
"call-shaped attribute sentinels are metadata, not runtime calls"
);
assert!(
!diags
.iter()
.any(|diagnostic| diagnostic.code == Code::LintUnusedFunction),
"an attribute-only function reference must count as a real use"
);
}
#[test]
fn test_multiple_rules() {
let diags = lint_source(
r#"
pipeline default(task) {
let unused = 1
return 0
log("dead")
}
"#,
);
assert!(has_rule(&diags, "unused-variable"));
assert!(has_rule(&diags, "mutable-never-reassigned"));
assert!(has_rule(&diags, "dead-code-after-return"));
assert_eq!(count_rule(&diags, "dead-code-after-return"), 1);
}
#[test]
fn test_fix_unused_variable_simple_let_binding() {
let source = "pipeline default(task) {\n const unused_thing = 42\n log(\"hi\")\n}";
let diags = lint_source(source);
assert!(has_rule(&diags, "unused-variable"));
let fix = get_fix(&diags, "unused-variable");
assert!(
fix.is_some(),
"expected autofix for simple const binding, got: {diags:?}"
);
let result = apply_fixes(source, &diags);
assert!(
result.contains("const _ = 42"),
"expected discard binding, got: {result}"
);
assert!(
!result.contains("const unused_thing"),
"original name should be replaced, got: {result}"
);
}
#[test]
fn test_fix_unused_variable_simple_let_binding_with_type() {
let source = "pipeline default(task) {\n const leftover: int = 3\n log(\"hi\")\n}";
let diags = lint_source(source);
let fix = get_fix(&diags, "unused-variable").expect("expected autofix");
assert_eq!(fix.len(), 1, "expected single-edit fix");
let edit = &fix[0];
#[expect(clippy::string_slice, reason = "test input is ASCII")]
let renamed = {
let before = &source[..edit.span.start];
let after = &source[edit.span.end..];
format!("{before}{}{after}", edit.replacement)
};
assert!(
renamed.contains("const _: int = 3"),
"expected discard binding with type annotation, got: {renamed}"
);
assert!(
!renamed.contains("const leftover:"),
"original name should be replaced, got: {renamed}"
);
}
#[test]
fn test_no_fix_for_unused_variable_in_dict_destructuring() {
let source = "pipeline default(task) {\n const { a, b } = { a: 1, b: 2 }\n log(a)\n}";
let diags = lint_source(source);
let unused: Vec<_> = diags
.iter()
.filter(|d| d.rule == "unused-pattern-binding")
.collect();
assert!(
unused.iter().any(|d| d.message.contains("`b`")),
"expected unused-pattern-binding for `b`, got: {diags:?}"
);
for diag in &unused {
if diag.message.contains("`b`") {
assert!(
diag.fix.is_none(),
"destructuring unused-pattern-binding must not autofix, got: {:?}",
diag.fix
);
assert!(
diag.suggestion.is_some(),
"destructuring unused-pattern-binding must keep its suggestion"
);
}
}
}
#[test]
fn test_fix_unused_variable_is_word_boundary_safe() {
let source =
"pipeline default(task) {\n const threshold_ms = threshold_ms_default()\n log(\"hi\")\n}";
let diags = lint_source(source);
let fix = get_fix(&diags, "unused-variable");
assert!(fix.is_some(), "expected autofix, got: {diags:?}");
let result = apply_fixes(source, &diags);
assert!(
result.contains("const _ = threshold_ms_default()"),
"expected only the LHS binding renamed, got: {result}"
);
}