use once_cell::sync::Lazy;
use regex::Regex;
struct DestructivePattern {
pattern: Regex,
warning: &'static str,
}
fn get_destructive_patterns() -> Vec<DestructivePattern> {
vec![
DestructivePattern {
pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Recurse\b[^|;&\n}]*-Force\b").unwrap(),
warning: "Note: may recursively force-remove files",
},
DestructivePattern {
pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Force\b[^|;&\n}]*-Recurse\b").unwrap(),
warning: "Note: may recursively force-remove files",
},
DestructivePattern {
pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Recurse\b").unwrap(),
warning: "Note: may recursively remove files",
},
DestructivePattern {
pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Force\b").unwrap(),
warning: "Note: may force-remove files",
},
DestructivePattern {
pattern: Regex::new(r"\bClear-Content\b[^|;&\n]*\*").unwrap(),
warning: "Note: may clear content of multiple files",
},
DestructivePattern {
pattern: Regex::new(r"\bFormat-Volume\b").unwrap(),
warning: "Note: may format a disk volume",
},
DestructivePattern {
pattern: Regex::new(r"\bClear-Disk\b").unwrap(),
warning: "Note: may clear a disk",
},
DestructivePattern {
pattern: Regex::new(r"\bgit\s+reset\s+--hard\b").unwrap(),
warning: "Note: may discard uncommitted changes",
},
DestructivePattern {
pattern: Regex::new(r"\bgit\s+push\b[^|;&\n]*\s+(--force|--force-with-lease|-f)\b").unwrap(),
warning: "Note: may overwrite remote history",
},
DestructivePattern {
pattern: Regex::new(r"\bgit\s+stash\s+(drop|clear)\b").unwrap(),
warning: "Note: may permanently remove stashed changes",
},
DestructivePattern {
pattern: Regex::new(r"\b(DROP|TRUNCATE)\s+(TABLE|DATABASE|SCHEMA)\b").unwrap(),
warning: "Note: may drop or truncate database objects",
},
DestructivePattern {
pattern: Regex::new(r"\bStop-Computer\b").unwrap(),
warning: "Note: will shut down the computer",
},
DestructivePattern {
pattern: Regex::new(r"\bRestart-Computer\b").unwrap(),
warning: "Note: will restart the computer",
},
DestructivePattern {
pattern: Regex::new(r"\bClear-RecycleBin\b").unwrap(),
warning: "Note: permanently deletes recycled files",
},
]
}
static DESTRUCTIVE_PATTERNS: Lazy<Vec<DestructivePattern>> = Lazy::new(get_destructive_patterns);
pub fn get_destructive_command_warning(command: &str) -> Option<&'static str> {
for pattern in DESTRUCTIVE_PATTERNS.iter() {
if pattern.pattern.is_match(command) {
return Some(pattern.warning);
}
}
let lower = command.to_lowercase();
if lower.contains("git") && lower.contains("clean") {
let has_f = lower.contains(" -f ")
|| lower.contains(" -f\n")
|| lower.contains(" -f\t")
|| lower.contains(" --force")
|| lower.contains(" -fd");
let has_dry_run = lower.contains(" -n ") || lower.contains(" --dry-run");
if has_f && !has_dry_run {
return Some("Note: may permanently delete untracked files");
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_item_recursive_force() {
let warning = get_destructive_command_warning("Remove-Item -Path ./foo -Recurse -Force");
assert!(warning.is_some());
}
#[test]
fn test_git_reset_hard() {
let warning = get_destructive_command_warning("git reset --hard");
assert!(warning.is_some());
}
#[test]
fn test_safe_command() {
let warning = get_destructive_command_warning("Get-ChildItem");
assert!(warning.is_none());
}
}