#![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)]
use bashrs::linter::rules::lint_makefile;
use bashrs::linter::Severity;
#[test]
fn test_issue_016_sc2168_local_in_quoted_string() {
let makefile = r#"
serve:
@printf 'Starting local server on port 8080...\n'
@echo "Connecting to local database"
"#;
let result = lint_makefile(makefile);
let sc2168_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2168")
.collect();
assert_eq!(
sc2168_errors.len(),
0,
"SC2168 should not trigger on 'local' in quoted strings. Found {} errors: {:?}",
sc2168_errors.len(),
sc2168_errors
);
}
#[test]
fn test_issue_016_sc2168_local_in_various_contexts() {
let makefile = r#"
test:
@echo "local variable"
@printf 'local server\n'
@echo 'localhost'
@echo "locale settings"
"#;
let result = lint_makefile(makefile);
let sc2168_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2168")
.collect();
assert_eq!(
sc2168_errors.len(),
0,
"SC2168 should not trigger on 'local' as part of other words or in strings. Found {} errors: {:?}",
sc2168_errors.len(),
sc2168_errors
);
}
#[test]
fn test_issue_016_sc2082_dollar_dollar_var_in_makefile() {
let makefile = r#"
deploy:
@VERSION=1.0.0
@echo "Deploying version $$VERSION"
@IMAGE=myapp:$$VERSION
"#;
let result = lint_makefile(makefile);
let sc2082_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2082")
.collect();
assert_eq!(
sc2082_errors.len(),
0,
"SC2082 should not trigger on $$VAR in Makefile recipes. Found {} errors: {:?}",
sc2082_errors.len(),
sc2082_errors
);
}
#[test]
fn test_issue_016_sc2086_unquoted_dollar_dollar_var() {
let makefile = r#"
build:
@CORES=$$(nproc)
@echo "Using $$CORES cores"
@make -j$$CORES
"#;
let result = lint_makefile(makefile);
let sc2086_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2086")
.collect();
assert_eq!(
sc2086_errors.len(),
0,
"SC2086 should not trigger on unquoted $$VAR in Makefiles. Found {} errors: {:?}",
sc2086_errors.len(),
sc2086_errors
);
}
#[test]
fn test_issue_016_sc2154_undefined_dollar_dollar_var() {
let makefile = r#"
check:
@STATUS=$$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080)
@if [ "$$STATUS" = "200" ]; then echo "OK"; fi
"#;
let result = lint_makefile(makefile);
let sc2154_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2154")
.collect();
assert_eq!(
sc2154_errors.len(),
0,
"SC2154 should not trigger on $$VAR in Makefiles. Found {} errors: {:?}",
sc2154_errors.len(),
sc2154_errors
);
}
#[test]
fn test_issue_016_sc2102_awk_regex_pattern() {
let makefile = r#"
analyze:
@awk '/[a-zA-Z_-]+/ {print $$1}' input.txt
@sed 's/[0-9]+/NUM/g' data.txt
"#;
let result = lint_makefile(makefile);
let sc2102_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2102")
.collect();
assert_eq!(
sc2102_errors.len(),
0,
"SC2102 should not trigger on regex patterns in awk/sed. Found {} errors: {:?}",
sc2102_errors.len(),
sc2102_errors
);
}
#[test]
fn test_issue_016_sc2095_makefile_redirection_context() {
let makefile = r#"
backup:
@if [ -f data.txt ]; then
@ cat data.txt > backup.txt
@fi
"#;
let result = lint_makefile(makefile);
let sc2095_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2095")
.collect();
assert_eq!(
sc2095_errors.len(),
0,
"SC2095 should not trigger on Makefile recipe patterns. Found {} errors: {:?}",
sc2095_errors.len(),
sc2095_errors
);
}
#[test]
fn test_issue_016_all_false_positives_comprehensive() {
let makefile = r#"
PROJECT := myapp
serve:
@printf 'Starting local server on port 8080...\n'
@echo "Connecting to local database"
deploy:
@VERSION=1.0.0
@echo "Deploying version $$VERSION"
@IMAGE=myapp:$$VERSION
build:
@CORES=$$(nproc)
@echo "Using $$CORES cores"
@make -j$$CORES
analyze:
@awk '/[a-zA-Z_-]+/ {print $$1}' input.txt
@sed 's/[0-9]+/NUM/g' data.txt
backup:
@if [ -f data.txt ]; then cat data.txt > backup.txt; fi
"#;
let result = lint_makefile(makefile);
let sc2168_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2168")
.count();
let sc2082_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2082")
.count();
let sc2086_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2086")
.count();
let sc2154_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2154")
.count();
let sc2102_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2102")
.count();
let sc2095_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2095")
.count();
println!("\n=== Issue #16 False Positives Count ===");
println!("SC2168 ('local' in strings): {}", sc2168_count);
println!("SC2082 ($$VAR indirection): {}", sc2082_count);
println!("SC2086 (unquoted $$VAR): {}", sc2086_count);
println!("SC2154 (undefined $$VAR): {}", sc2154_count);
println!("SC2102 (awk regex): {}", sc2102_count);
println!("SC2095 (redirection): {}", sc2095_count);
println!("=======================================\n");
assert_eq!(sc2168_count, 0, "SC2168 false positives remain");
assert_eq!(sc2082_count, 0, "SC2082 false positives remain");
assert_eq!(sc2086_count, 0, "SC2086 false positives remain");
assert_eq!(sc2154_count, 0, "SC2154 false positives remain");
assert_eq!(sc2102_count, 0, "SC2102 false positives remain");
assert_eq!(sc2095_count, 0, "SC2095 false positives remain");
let error_count = result
.diagnostics
.iter()
.filter(|d| {
d.severity == Severity::Error
&& ["SC2168", "SC2082", "SC2086", "SC2154", "SC2102", "SC2095"]
.contains(&d.code.as_str())
})
.count();
assert_eq!(
error_count, 0,
"Found {} ERROR-level diagnostics for Issue #16 patterns",
error_count
);
}