tldr_core/patterns/
detector.rs1use std::path::PathBuf;
7
8use regex::Regex;
9use tree_sitter::{Node, Tree};
10
11use super::signals::*;
12use crate::types::{Evidence, Language};
13
14pub struct PatternDetector {
16 language: Language,
17 file_path: PathBuf,
18}
19
20impl PatternDetector {
21 pub fn new(language: Language, file_path: PathBuf) -> Self {
23 Self {
24 language,
25 file_path,
26 }
27 }
28
29 pub fn detect_all(&self, tree: &Tree, source: &str) -> PatternSignals {
31 let mut signals = PatternSignals::default();
32 self.walk_node(tree.root_node(), source, &mut signals);
33 signals
34 }
35
36 pub fn detect_fallback(&self, source: &str) -> PatternSignals {
38 let mut signals = PatternSignals::default();
39 self.detect_fallback_patterns(source, &mut signals);
40 signals
41 }
42
43 fn walk_node(&self, node: Node, source: &str, signals: &mut PatternSignals) {
45 self.process_node(node, source, signals);
46
47 let mut cursor = node.walk();
48 for child in node.children(&mut cursor) {
49 self.walk_node(child, source, signals);
50 }
51 }
52
53 fn process_node(&self, node: Node, source: &str, signals: &mut PatternSignals) {
55 if let Some(profile) = super::language_profile::language_profile(self.language) {
56 profile.process_node(node, source, &self.file_path, signals);
57 }
58 }
59
60 fn detect_fallback_patterns(&self, source: &str, signals: &mut PatternSignals) {
62 let is_deleted_re = Regex::new(r"(?i)(is_deleted|isDeleted)\s*[=:]").unwrap();
64 let deleted_at_re = Regex::new(r"(?i)(deleted_at|deletedAt)\s*[=:]").unwrap();
65
66 for (line_num, line) in source.lines().enumerate() {
67 if is_deleted_re.is_match(line) {
68 signals.soft_delete.is_deleted_fields.push(Evidence::new(
69 self.file_path.display().to_string(),
70 line_num as u32 + 1,
71 line.to_string(),
72 ));
73 }
74 if deleted_at_re.is_match(line) {
75 signals.soft_delete.deleted_at_fields.push(Evidence::new(
76 self.file_path.display().to_string(),
77 line_num as u32 + 1,
78 line.to_string(),
79 ));
80 }
81 }
82
83 if source.contains("try:") || source.contains("try {") {
85 signals.error_handling.try_except_blocks.push(Evidence::new(
86 self.file_path.display().to_string(),
87 1,
88 "try block detected".to_string(),
89 ));
90 }
91
92 if source.contains("async ") || source.contains("await ") {
94 signals.async_patterns.async_await.push(Evidence::new(
95 self.file_path.display().to_string(),
96 1,
97 "async/await detected".to_string(),
98 ));
99 }
100 }
101}