use crate::linter::{Diagnostic, Fix, LintResult, Severity, Span};
pub fn check(source: &str) -> LintResult {
let mut result = LintResult::new();
for (line_num, line) in source.lines().enumerate() {
if let Some(col) = line.find("$RANDOM") {
let span = Span::new(
line_num + 1, col + 1, line_num + 1, col + 8, );
let fix = Fix::new_unsafe(vec![
"Option 1: Use version/build ID: SESSION_ID=\"session-${VERSION}\"".to_string(),
"Option 2: Use timestamp as argument: SESSION_ID=\"$1\"".to_string(),
"Option 3: Use hash of input: SESSION_ID=$(echo \"$INPUT\" | sha256sum | cut -c1-8)".to_string(),
]);
let diag = Diagnostic::new(
"DET001",
Severity::Error,
"Non-deterministic $RANDOM usage - requires manual fix (UNSAFE)",
span,
)
.with_fix(fix);
result.add(diag);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_DET001_detects_random_usage() {
let script = "SESSION_ID=$RANDOM";
let result = check(script);
assert_eq!(result.diagnostics.len(), 1);
let diag = &result.diagnostics[0];
assert_eq!(diag.code, "DET001");
assert_eq!(diag.severity, Severity::Error);
assert_eq!(
diag.message,
"Non-deterministic $RANDOM usage - requires manual fix (UNSAFE)"
);
}
#[test]
fn test_DET001_provides_fix() {
let script = "ID=$RANDOM";
let result = check(script);
assert!(result.diagnostics[0].fix.is_some());
let fix = result.diagnostics[0].fix.as_ref().unwrap();
assert_eq!(fix.replacement, "");
assert!(fix.is_unsafe());
assert!(!fix.suggested_alternatives.is_empty());
assert!(fix.suggested_alternatives.len() >= 3);
}
#[test]
fn test_DET001_no_false_positive() {
let script = "ID=\"session-${VERSION}\"";
let result = check(script);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_DET001_multiple_random() {
let script = "A=$RANDOM\nB=$RANDOM";
let result = check(script);
assert_eq!(result.diagnostics.len(), 2);
}
}