use crate::linter::{Diagnostic, Fix, LintResult, Severity, Span};
pub fn check(source: &str) -> LintResult {
let mut result = LintResult::new();
if source.trim().is_empty() {
return result;
}
if has_delete_on_error(source) {
return result;
}
let span = Span::new(1, 1, 1, 1);
let fix_replacement = create_fix(source);
let diag = Diagnostic::new(
"MAKE015",
Severity::Warning,
"Makefile missing .DELETE_ON_ERROR - partially-built files may be left on error",
span,
)
.with_fix(Fix::new(&fix_replacement));
result.add(diag);
result
}
fn create_fix(source: &str) -> String {
format!(".DELETE_ON_ERROR:\n{}", source)
}
fn has_delete_on_error(source: &str) -> bool {
for line in source.lines() {
let trimmed = line.trim();
if trimmed == ".DELETE_ON_ERROR:" || trimmed == ".DELETE_ON_ERROR" {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_MAKE015_detects_missing_delete_on_error() {
let makefile = ".PHONY: all\nall:\n\techo test";
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 1);
let diag = &result.diagnostics[0];
assert_eq!(diag.code, "MAKE015");
assert_eq!(diag.severity, Severity::Warning);
assert!(diag.message.contains(".DELETE_ON_ERROR"));
}
#[test]
fn test_MAKE015_no_warning_with_delete_on_error() {
let makefile = ".DELETE_ON_ERROR:\n.PHONY: all\nall:\n\techo test";
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_MAKE015_provides_fix() {
let makefile = ".PHONY: all\nall:\n\techo test";
let result = check(makefile);
assert!(result.diagnostics[0].fix.is_some());
let fix = result.diagnostics[0].fix.as_ref().unwrap();
assert!(fix.replacement.contains(".DELETE_ON_ERROR:"));
}
#[test]
fn test_MAKE015_detects_in_complex_makefile() {
let makefile = r#"
# Complex Makefile
CC = gcc
CFLAGS = -Wall
.PHONY: all clean
all: app
app: main.o
\tcc main.o -o app
clean:
\trm -f *.o app
"#;
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_MAKE015_no_warning_with_delete_on_error_anywhere() {
let makefile = r#"
# Makefile
.PHONY: all
all: build
.DELETE_ON_ERROR:
build:
\techo building
"#;
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_MAKE015_case_sensitive() {
let makefile = ".delete_on_error:\n.PHONY: all\nall:\n\techo test";
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_MAKE015_fix_adds_at_top() {
let makefile = "# Comment\n.PHONY: all\nall:\n\techo test";
let result = check(makefile);
assert!(result.diagnostics[0].fix.is_some());
let fix = result.diagnostics[0].fix.as_ref().unwrap();
assert!(fix.replacement.starts_with(".DELETE_ON_ERROR:"));
}
#[test]
fn test_MAKE015_empty_makefile() {
let makefile = "";
let result = check(makefile);
assert_eq!(result.diagnostics.len(), 0);
}
}