@@ -2,17 +2,26 @@ use rayon::prelude::*;
22use aho_corasick:: { AhoCorasick , MatchKind } ;
33use std:: fs;
44use std:: path:: { Path , PathBuf } ;
5+ use regex:: Regex ;
6+ use std:: sync:: OnceLock ;
7+
8+ static FUNC_REGEX : OnceLock < Regex > = OnceLock :: new ( ) ;
9+ static CLASS_REGEX : OnceLock < Regex > = OnceLock :: new ( ) ;
510
611pub struct AnalyzerStats {
712 pub path : PathBuf ,
813 pub complexity : f64 ,
14+ #[ allow( dead_code) ]
915 pub todo_count : usize ,
16+ #[ allow( dead_code) ]
1017 pub functions_count : usize ,
18+ #[ allow( dead_code) ]
1119 pub naming_violations : usize ,
20+ pub naming_violations_details : Vec < String > ,
1221}
1322
1423// Simplified analyzer that scans concurrently using Aho-Corasick for extreme speed
15- pub fn analyze_files ( paths : & [ PathBuf ] ) -> Vec < AnalyzerStats > {
24+ pub fn analyze_files ( paths : & [ PathBuf ] , check_naming : bool ) -> Vec < AnalyzerStats > {
1625 let patterns = & [
1726 // Complexity (0..10)
1827 " if " , " for " , " while " , " case " , " catch " , " try " , " except " , "&&" , "||" , "?:" ,
@@ -30,15 +39,15 @@ pub fn analyze_files(paths: &[PathBuf]) -> Vec<AnalyzerStats> {
3039
3140 paths
3241 . into_par_iter ( )
33- . filter_map ( |p| analyze_file ( p, & ac) )
42+ . filter_map ( |p| analyze_file ( p, & ac, check_naming ) )
3443 . collect ( )
3544}
3645
3746fn is_word_character ( b : u8 ) -> bool {
3847 b. is_ascii_alphanumeric ( ) || b == b'_'
3948}
4049
41- fn analyze_file ( path : & Path , ac : & AhoCorasick ) -> Option < AnalyzerStats > {
50+ fn analyze_file ( path : & Path , ac : & AhoCorasick , check_naming : bool ) -> Option < AnalyzerStats > {
4251 let content = fs:: read_to_string ( path) . ok ( ) ?;
4352
4453 let mut complexity = 0.0 ;
@@ -77,13 +86,118 @@ fn analyze_file(path: &Path, ac: &AhoCorasick) -> Option<AnalyzerStats> {
7786 }
7887 }
7988
80- let naming_violations = 0 ;
89+ let mut naming_violations_details = Vec :: new ( ) ;
90+
91+ if check_naming {
92+ let func_re = FUNC_REGEX . get_or_init ( || Regex :: new ( r"(?:fn|def|function)\s+([a-zA-Z0-9_]+)" ) . unwrap ( ) ) ;
93+ let class_re = CLASS_REGEX . get_or_init ( || Regex :: new ( r"class\s+([a-zA-Z0-9_]+)" ) . unwrap ( ) ) ;
94+
95+ let ext = path. extension ( ) . and_then ( |e| e. to_str ( ) ) . unwrap_or ( "" ) ;
96+ let is_rust_or_py = ext == "rs" || ext == "py" ;
97+
98+ for cap in func_re. captures_iter ( & content) {
99+ if let Some ( m) = cap. get ( 1 ) {
100+ let name = m. as_str ( ) ;
101+ if is_rust_or_py {
102+ // Check snake_case (lowercase, digits, underscores)
103+ if !name. chars ( ) . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '_' ) {
104+ naming_violations_details. push ( format ! ( "Function '{}' in {:?} should be snake_case" , name, path) ) ;
105+ }
106+ }
107+ }
108+ }
109+
110+ for cap in class_re. captures_iter ( & content) {
111+ if let Some ( m) = cap. get ( 1 ) {
112+ let name = m. as_str ( ) ;
113+ // Check PascalCase (starts with uppercase)
114+ if !name. chars ( ) . next ( ) . map_or ( false , |c| c. is_ascii_uppercase ( ) ) {
115+ naming_violations_details. push ( format ! ( "Class '{}' in {:?} should be PascalCase" , name, path) ) ;
116+ }
117+ }
118+ }
119+ }
120+
121+ let naming_violations = naming_violations_details. len ( ) ;
81122
82123 Some ( AnalyzerStats {
83124 path : path. to_path_buf ( ) ,
84125 complexity,
85126 todo_count,
86127 functions_count,
87128 naming_violations,
129+ naming_violations_details,
88130 } )
89131}
132+
133+ pub fn find_deadcode ( paths : & [ PathBuf ] ) -> Vec < String > {
134+ let func_re = FUNC_REGEX . get_or_init ( || Regex :: new ( r"(?:fn|def|function)\s+([a-zA-Z0-9_]+)" ) . unwrap ( ) ) ;
135+
136+ // Pass 1: Extract all function names
137+ let mut all_functions: Vec < String > = paths. into_par_iter ( )
138+ . filter_map ( |path| {
139+ if let Ok ( content) = fs:: read_to_string ( path) {
140+ let mut local_funcs = Vec :: new ( ) ;
141+ for cap in func_re. captures_iter ( & content) {
142+ if let Some ( m) = cap. get ( 1 ) {
143+ local_funcs. push ( m. as_str ( ) . to_string ( ) ) ;
144+ }
145+ }
146+ Some ( local_funcs)
147+ } else {
148+ None
149+ }
150+ } )
151+ . flatten ( )
152+ . collect ( ) ;
153+
154+ all_functions. sort ( ) ;
155+ all_functions. dedup ( ) ;
156+
157+ if all_functions. is_empty ( ) {
158+ return Vec :: new ( ) ;
159+ }
160+
161+ // Pass 2: Count occurrences
162+ let ac = aho_corasick:: AhoCorasickBuilder :: new ( )
163+ . match_kind ( MatchKind :: Standard )
164+ . build ( & all_functions)
165+ . unwrap ( ) ;
166+
167+ let counts = paths. into_par_iter ( ) . map ( |path| {
168+ let mut local_counts = vec ! [ 0usize ; all_functions. len( ) ] ;
169+ if let Ok ( content) = fs:: read_to_string ( path) {
170+ let bytes = content. as_bytes ( ) ;
171+ for mat in ac. find_iter ( & content) {
172+ let start = mat. start ( ) ;
173+ let end = mat. end ( ) ;
174+
175+ let prev_ok = start == 0 || !is_word_character ( bytes[ start - 1 ] ) ;
176+ let next_ok = end == bytes. len ( ) || !is_word_character ( bytes[ end] ) ;
177+
178+ if prev_ok && next_ok {
179+ local_counts[ mat. pattern ( ) . as_usize ( ) ] += 1 ;
180+ }
181+ }
182+ }
183+ local_counts
184+ } ) . reduce (
185+ || vec ! [ 0usize ; all_functions. len( ) ] ,
186+ |mut a, b| {
187+ for ( i, v) in b. iter ( ) . enumerate ( ) {
188+ a[ i] += v;
189+ }
190+ a
191+ }
192+ ) ;
193+
194+ let mut deadcode = Vec :: new ( ) ;
195+ for ( i, count) in counts. iter ( ) . enumerate ( ) {
196+ if * count == 1 {
197+ deadcode. push ( all_functions[ i] . clone ( ) ) ;
198+ }
199+ }
200+
201+ deadcode
202+ }
203+
0 commit comments