cha_core/plugins/
todo_tracker.rs1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3pub struct TodoTrackerAnalyzer;
8
9impl Plugin for TodoTrackerAnalyzer {
10 fn name(&self) -> &str {
11 "todo_tracker"
12 }
13
14 fn description(&self) -> &str {
15 "Leftover TODO/FIXME/HACK/XXX comments"
16 }
17
18 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
19 ctx.model
20 .comments
21 .iter()
22 .filter_map(|c| check_comment(c, ctx))
23 .collect()
24 }
25}
26
27fn check_comment(c: &crate::CommentInfo, ctx: &AnalysisContext) -> Option<Finding> {
28 let upper = c.text.to_uppercase();
29 let (tag, severity) = if has_tag(&upper, "HACK") {
30 ("HACK", Severity::Warning)
31 } else if has_tag(&upper, "XXX") {
32 ("XXX", Severity::Warning)
33 } else if has_tag(&upper, "FIXME") {
34 ("FIXME", Severity::Hint)
35 } else if has_tag(&upper, "TODO") {
36 ("TODO", Severity::Hint)
37 } else {
38 return None;
39 };
40 let col = upper.find(tag).unwrap_or(0);
41 Some(Finding {
42 smell_name: "todo_comment".into(),
43 category: SmellCategory::Dispensables,
44 severity,
45 location: Location {
46 path: ctx.file.path.clone(),
47 start_line: c.line,
48 start_col: col,
49 end_line: c.line,
50 end_col: col + tag.len(),
51 name: None,
52 },
53 message: format!(
54 "{tag}: {}",
55 c.text.trim_start_matches(['/', '#', '*', ' ', '-'])
56 ),
57 suggested_refactorings: vec!["Resolve or create a tracking issue".into()],
58 ..Default::default()
59 })
60}
61
62fn has_tag(line: &str, tag: &str) -> bool {
64 if let Some(pos) = line.find(tag) {
65 let after = pos + tag.len();
66 after >= line.len() || !line.as_bytes()[after].is_ascii_alphabetic()
67 } else {
68 false
69 }
70}