mollify_core/
commented.rs1use crate::fingerprint::fingerprint;
8use mollify_graph::ModuleGraph;
9use mollify_types::{Action, Category, Confidence, Finding, Location, Severity};
10
11const DIRECTIVES: &[&str] = &[
13 "noqa", "type:", "mypy", "pylint", "pyright", "ruff", "flake8", "isort", "todo", "fixme",
14 "xxx", "hack", "note", "mollify", "nosec", "pragma", "!",
15];
16
17fn looks_like_code(body: &str) -> bool {
19 let b = body.trim();
20 if b.len() < 3 {
21 return false;
22 }
23 let lower = b.to_ascii_lowercase();
24 if DIRECTIVES.iter().any(|d| lower.starts_with(d)) {
25 return false;
26 }
27 let starters = [
29 "import ", "from ", "def ", "class ", "return", "if ", "elif ", "else:", "for ", "while ",
30 "try:", "except", "finally:", "with ", "raise ", "assert ", "print(", "del ", "yield ",
31 "async ", "await ", "lambda ",
32 ];
33 if starters.iter().any(|s| b.starts_with(s)) {
34 return true;
35 }
36 let codeish = (b.contains(" = ") || b.contains("=="))
39 || (b.ends_with(':') && !b.contains(' '))
40 || (b.ends_with(')') && b.contains('('))
41 || b.ends_with('\\');
42 codeish && !b.ends_with('.') && b.split_whitespace().count() <= 12
43}
44
45pub fn analyze(graph: &ModuleGraph) -> Vec<Finding> {
47 let mut findings = Vec::new();
48 for m in &graph.modules {
49 if let Some(src) = mollify_graph::read_source(&m.path) {
50 findings.extend(analyze_source(&m.path, &src));
51 }
52 }
53 findings
54}
55
56pub fn analyze_source(path: &camino::Utf8Path, src: &str) -> Vec<Finding> {
58 let mut findings = Vec::new();
59 for (i, line) in src.lines().enumerate() {
60 let trimmed = line.trim_start();
61 let Some(body) = trimmed.strip_prefix('#') else {
62 continue;
63 };
64 if !looks_like_code(body) {
65 continue;
66 }
67 let rule = "commented-code";
68 let line_no = i as u32 + 1;
69 findings.push(Finding {
70 fingerprint: fingerprint(rule, &[path.as_str(), &line_no.to_string()]),
71 rule: rule.into(),
72 category: Category::DeadCode,
73 severity: Severity::Warn,
74 confidence: Confidence::Likely,
75 attribution: None,
76 reason: format!("commented-out code: `{}`", body.trim()),
77 location: Location {
78 path: path.to_owned(),
79 line: line_no,
80 column: 0,
81 end_line: None,
82 },
83 actions: vec![Action {
84 kind: "remove-commented-code".into(),
85 description: "Delete the commented-out code (version control remembers it)".into(),
86 auto_fixable: false,
87 suppression_comment: Some("# mollify: ignore[commented-code]".into()),
88 }],
89 });
90 }
91 findings
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn flags_code_not_prose_or_directives() {
100 assert!(looks_like_code(" import os"));
101 assert!(looks_like_code(" return x + 1"));
102 assert!(looks_like_code(" x = compute()"));
103 assert!(looks_like_code(" def helper():"));
104 assert!(!looks_like_code(" this explains why we do the thing."));
105 assert!(!looks_like_code(" noqa: F401"));
106 assert!(!looks_like_code(" type: ignore"));
107 assert!(!looks_like_code(" TODO: fix this later"));
108 }
109}