use crate::linter::diagnostic::FixSafetyLevel;
use crate::linter::{Diagnostic, Fix, LintResult, Severity, Span};
use regex::Regex;
static PATTERN: std::sync::LazyLock<Regex> =
std::sync::LazyLock::new(|| Regex::new(r"ps\s+[^|]*\|\s*grep").unwrap());
pub fn check(source: &str) -> LintResult {
let mut result = LintResult::new();
let pattern = &*PATTERN;
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with('#') {
continue;
}
if let Some(mat) = pattern.find(line) {
let start = mat.start();
let end = mat.end();
result.diagnostics.push(Diagnostic {
code: "SC2009".to_string(),
severity: Severity::Info,
message: "Consider using pgrep instead of grepping ps output. pgrep is more reliable and efficient.".to_string(),
span: Span {
start_line: line_num + 1, end_line: line_num + 1, start_col: start + 1, end_col: end + 1, },
fix: Some(Fix {
replacement: "pgrep".to_string(),
safety_level: FixSafetyLevel::SafeWithAssumptions,
assumptions: vec![
"pgrep is available on the system".to_string(),
"Simple process name matching is sufficient".to_string(),
],
suggested_alternatives: vec![
"pgrep -f pattern # Match full command line".to_string(),
"pgrep -u user pattern # Match specific user".to_string(),
],
}),
});
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sc2009_basic_ps_aux_grep() {
let source = "ps aux | grep nginx";
let result = check(source);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].code, "SC2009");
assert!(result.diagnostics[0].message.contains("pgrep"));
}
#[test]
fn test_sc2009_ps_ef_grep() {
let source = "ps -ef | grep myapp";
let result = check(source);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].code, "SC2009");
}
#[test]
fn test_sc2009_ps_with_multiple_pipes() {
let source = "ps aux | grep -v grep | grep process";
let result = check(source);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc2009_ps_without_grep() {
let source = "ps aux";
let result = check(source);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc2009_grep_without_ps() {
let source = "cat file.txt | grep pattern";
let result = check(source);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc2009_pgrep_usage() {
let source = "pgrep nginx";
let result = check(source);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc2009_in_comment() {
let source = "# Bad: ps aux | grep nginx";
let result = check(source);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc2009_multiline_pipeline() {
let source = r#"ps aux \
| grep nginx"#;
let result = check(source);
assert!(result.diagnostics.len() <= 1);
}
#[test]
fn test_sc2009_auto_fix_suggests_pgrep() {
let source = "ps aux | grep nginx";
let result = check(source);
assert_eq!(result.diagnostics.len(), 1);
let fix = &result.diagnostics[0].fix;
assert!(fix.is_some());
let fix = fix.as_ref().unwrap();
assert!(fix.replacement.contains("pgrep"));
}
#[test]
fn test_sc2009_severity_info() {
let source = "ps aux | grep nginx";
let result = check(source);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].severity, Severity::Info);
}
}