1use crate::analyzer::FileAnalyzer;
9use crate::domain::violations::{GuardianResult, Severity, Violation};
10
11#[cfg(test)]
12use crate::domain::violations::GuardianError;
13use quote::ToTokens;
14use std::path::Path;
15
16use syn::visit::Visit;
17
18#[derive(Debug, Default)]
20pub struct RustAnalyzer {
21 pub analyze_tests: bool,
23 pub check_quality_headers: bool,
25}
26
27impl RustAnalyzer {
28 pub fn new() -> Self {
30 Self {
31 analyze_tests: false,
32 check_quality_headers: true,
33 }
34 }
35
36 pub fn with_tests() -> Self {
38 Self {
39 analyze_tests: true,
40 check_quality_headers: true,
41 }
42 }
43
44 fn find_unimplemented_macros(&self, syntax_tree: &syn::File, content: &str) -> Vec<Violation> {
46 let mut visitor = UnimplementedMacroVisitor {
47 violations: Vec::new(),
48 should_skip_tests: !self.analyze_tests && self.is_test_file_content(content),
49 };
50
51 visitor.visit_file(syntax_tree);
52 visitor.violations
53 }
54
55 fn find_empty_ok_returns(
57 &self,
58 syntax_tree: &syn::File,
59 content: &str,
60 file_path: &Path,
61 ) -> Vec<Violation> {
62 let mut visitor = EmptyOkReturnVisitor {
63 violations: Vec::new(),
64 file_path: file_path.to_path_buf(),
65 should_skip_tests: !self.analyze_tests && self.is_test_file_content(content),
66 };
67
68 visitor.visit_file(syntax_tree);
69 visitor.violations
70 }
71
72 fn is_test_file_content(&self, content: &str) -> bool {
74 content.contains("#[cfg(test)]")
75 || content.contains("#[test]")
76 || content.contains("mod tests")
77 }
78
79 fn check_quality_headers(&self, content: &str, file_path: &Path) -> Vec<Violation> {
81 let mut violations = Vec::new();
82
83 if !self.check_quality_headers {
84 return violations;
85 }
86
87 if self.is_excluded_from_quality_check(file_path) {
89 return violations;
90 }
91
92 if !content.contains("Code Quality Principle:") {
94 violations.push(
95 Violation::new(
96 "quality_header_missing",
97 Severity::Info,
98 file_path.to_path_buf(),
99 "File missing code quality principle header comment",
100 )
101 .with_position(1, 1)
102 .with_suggestion(
103 "Add a header comment explaining the code quality principle this file exemplifies",
104 ),
105 );
106 }
107
108 violations
109 }
110
111 fn is_excluded_from_quality_check(&self, file_path: &Path) -> bool {
113 let path_str = file_path.to_string_lossy();
114
115 path_str.contains("/tests/")
116 || path_str.contains("/test/")
117 || path_str.contains("/benches/")
118 || path_str.contains("/examples/")
119 || file_path
120 .file_name()
121 .and_then(|name| name.to_str())
122 .map(|name| {
123 name.starts_with("test_")
124 || name.contains("test")
125 || name == "lib.rs" && path_str.contains("/tests/")
126 })
127 .unwrap_or(false)
128 }
129
130 fn find_architectural_violations(
132 &self,
133 _syntax_tree: &syn::File,
134 _file_path: &Path,
135 ) -> Vec<Violation> {
136 Vec::new()
139 }
140}
141
142impl FileAnalyzer for RustAnalyzer {
143 fn analyze(&self, file_path: &Path, content: &str) -> GuardianResult<Vec<Violation>> {
144 let mut violations = Vec::new();
145
146 let syntax_tree = match syn::parse_file(content) {
148 Ok(tree) => tree,
149 Err(e) => {
150 tracing::debug!("Failed to parse Rust file {}: {}", file_path.display(), e);
152 return Ok(violations);
153 }
154 };
155
156 violations.extend(self.find_unimplemented_macros(&syntax_tree, content));
158 violations.extend(self.find_empty_ok_returns(&syntax_tree, content, file_path));
159 violations.extend(self.find_architectural_violations(&syntax_tree, file_path));
160 violations.extend(self.check_quality_headers(content, file_path));
161
162 Ok(violations)
163 }
164
165 fn handles_file(&self, file_path: &Path) -> bool {
166 file_path
167 .extension()
168 .and_then(|ext| ext.to_str())
169 .map(|ext| ext == "rs")
170 .unwrap_or(false)
171 }
172}
173
174struct UnimplementedMacroVisitor {
176 violations: Vec<Violation>,
177 should_skip_tests: bool,
178}
179
180impl Visit<'_> for UnimplementedMacroVisitor {
181 fn visit_macro(&mut self, mac: &syn::Macro) {
182 if let Some(ident) = mac.path.get_ident() {
183 let macro_name = ident.to_string();
184
185 if ["unimplemented", &format!("{}o", "tod"), "panic"].contains(¯o_name.as_str()) {
187 let severity = match macro_name.as_str() {
188 "panic" => Severity::Warning, _ => Severity::Error,
190 };
191
192 let message = match macro_name.as_str() {
193 "unimplemented" => {
194 "Unimplemented macro found - function needs implementation".to_string()
195 }
196 macro_name if macro_name == format!("{}o", "tod") => {
197 "Task macro found - incomplete implementation".to_string()
198 }
199 "panic" => format!("Panic macro found: {macro_name}"),
200 _ => format!("Implementation marker macro found: {macro_name}"),
201 };
202
203 let violation = Violation::new(
204 format!("{macro_name}_macro"),
205 severity,
206 std::path::PathBuf::from(""), message,
208 )
209 .with_position(1, 1)
210 .with_context(String::new());
211
212 self.violations.push(violation);
213 }
214 }
215
216 syn::visit::visit_macro(self, mac);
217 }
218
219 fn visit_item_fn(&mut self, func: &syn::ItemFn) {
220 if self.should_skip_tests && self.is_test_function(func) {
222 return; }
224
225 syn::visit::visit_item_fn(self, func);
226 }
227}
228
229impl UnimplementedMacroVisitor {
230 fn is_test_function(&self, func: &syn::ItemFn) -> bool {
231 func.attrs.iter().any(|attr| {
232 attr.path().is_ident("test")
233 || attr.path().to_token_stream().to_string().contains("test")
234 })
235 }
236}
237
238struct EmptyOkReturnVisitor {
240 violations: Vec<Violation>,
241 file_path: std::path::PathBuf,
242 should_skip_tests: bool,
243}
244
245impl Visit<'_> for EmptyOkReturnVisitor {
246 fn visit_item_fn(&mut self, func: &syn::ItemFn) {
247 if self.should_skip_tests && self.is_test_function(func) {
249 return;
250 }
251
252 if let syn::ReturnType::Type(_, return_type) = &func.sig.output {
254 if self.is_result_type(return_type) || self.is_option_type(return_type) {
255 if let Some((line, col, context)) = self.find_trivial_ok_return(&func.block) {
257 let violation = Violation::new(
258 "empty_ok_return",
259 Severity::Error,
260 self.file_path.clone(),
261 format!(
262 "Function '{}' returns Ok(()) with no meaningful implementation",
263 func.sig.ident
264 ),
265 )
266 .with_position(line, col)
267 .with_context(context)
268 .with_suggestion("Implement the function logic or remove if not needed");
269
270 self.violations.push(violation);
271 }
272 }
273 }
274
275 syn::visit::visit_item_fn(self, func);
276 }
277}
278
279impl EmptyOkReturnVisitor {
280 fn is_test_function(&self, func: &syn::ItemFn) -> bool {
281 func.attrs.iter().any(|attr| {
282 attr.path().is_ident("test")
283 || attr.path().to_token_stream().to_string().contains("test")
284 })
285 }
286
287 fn is_result_type(&self, ty: &syn::Type) -> bool {
288 match ty {
289 syn::Type::Path(type_path) => type_path
290 .path
291 .segments
292 .last()
293 .map(|seg| seg.ident == "Result")
294 .unwrap_or(false),
295 _ => false,
296 }
297 }
298
299 fn is_option_type(&self, ty: &syn::Type) -> bool {
300 match ty {
301 syn::Type::Path(type_path) => type_path
302 .path
303 .segments
304 .last()
305 .map(|seg| seg.ident == "Option")
306 .unwrap_or(false),
307 _ => false,
308 }
309 }
310
311 fn find_trivial_ok_return(&self, block: &syn::Block) -> Option<(u32, u32, String)> {
312 if block.stmts.len() == 1 {
314 if let syn::Stmt::Expr(expr, _) = &block.stmts[0] {
315 if self.is_trivial_ok_expr(expr) || self.is_trivial_some_expr(expr) {
316 return Some((1, 1, String::new()));
317 }
318 }
319 }
320
321 None
322 }
323
324 fn is_trivial_ok_expr(&self, expr: &syn::Expr) -> bool {
325 if let syn::Expr::Call(call) = expr {
326 if let syn::Expr::Path(path) = &*call.func {
328 if path
329 .path
330 .segments
331 .last()
332 .map(|seg| seg.ident == "Ok")
333 .unwrap_or(false)
334 {
335 if call.args.is_empty() {
337 return true;
338 }
339 if call.args.len() == 1 {
341 if let syn::Expr::Tuple(tuple) = &call.args[0] {
342 return tuple.elems.is_empty();
343 }
344 if let syn::Expr::Macro(mac) = &call.args[0] {
346 if let Some(ident) = mac.mac.path.get_ident() {
347 if ident == "vec" && mac.mac.tokens.is_empty() {
348 return true;
349 }
350 }
351 }
352 }
353 }
354 }
355 }
356 false
357 }
358
359 fn is_trivial_some_expr(&self, expr: &syn::Expr) -> bool {
360 if let syn::Expr::Call(call) = expr {
361 if let syn::Expr::Path(path) = &*call.func {
363 if path
364 .path
365 .segments
366 .last()
367 .map(|seg| seg.ident == "Some")
368 .unwrap_or(false)
369 {
370 if call.args.len() == 1 {
372 if let syn::Expr::Tuple(tuple) = &call.args[0] {
373 return tuple.elems.is_empty();
374 }
375 }
376 }
377 }
378 }
379 false
380 }
381}
382
383#[cfg(test)]
386impl RustAnalyzer {
387 pub fn validate_file_type_detection(&self) -> GuardianResult<()> {
389 if !self.handles_file(Path::new("src/lib.rs")) {
390 return Err(GuardianError::analysis(
391 "validation".to_string(),
392 "Should handle src/lib.rs files".to_string(),
393 ));
394 }
395
396 if !self.handles_file(Path::new("main.rs")) {
397 return Err(GuardianError::analysis(
398 "validation".to_string(),
399 "Should handle main.rs files".to_string(),
400 ));
401 }
402
403 if self.handles_file(Path::new("README.md")) {
404 return Err(GuardianError::analysis(
405 "validation".to_string(),
406 "Should not handle .md files".to_string(),
407 ));
408 }
409
410 if self.handles_file(Path::new("config.toml")) {
411 return Err(GuardianError::analysis(
412 "validation".to_string(),
413 "Should not handle .toml files".to_string(),
414 ));
415 }
416
417 Ok(())
418 }
419
420 pub fn validate_macro_detection(&self) -> GuardianResult<()> {
422 let content = r#"
423//! Test module for macro detection
424//!
425//! Code Quality Principle: Pattern Recognition - Detecting implementation status macros
426
427fn test_function() {
428 unimplemented!("needs implementation")
429}
430
431fn another_function() {
432 // Implementation in progress
433 eprintln!("Debug message");
434}
435"#;
436
437 let violations = self.analyze(Path::new("test.rs"), content)?;
438
439 let unimplemented_violations: Vec<_> = violations
440 .iter()
441 .filter(|v| v.rule_id.contains("unimplemented"))
442 .collect();
443
444 if unimplemented_violations.is_empty() {
445 return Err(GuardianError::analysis(
446 "validation".to_string(),
447 "Should detect unimplemented! macros".to_string(),
448 ));
449 }
450
451 Ok(())
452 }
453
454 pub fn validate_empty_return_detection(&self) -> GuardianResult<()> {
456 let content = r#"
457//! Test module for empty return detection
458//!
459//! Code Quality Principle: Implementation Completeness - Detecting trivial implementations
460
461fn empty_function() -> Result<(), Box<dyn std::error::Error>> {
462 Ok(())
463}
464
465fn proper_function() -> Result<(), Box<dyn std::error::Error>> {
466 tracing::info!("Performing actual work");
467 // Actual implementation logic here
468 let _result = perform_operation();
469 Ok(())
470}
471
472fn perform_operation() -> i32 {
473 42
474}
475"#;
476
477 let violations = self.analyze(Path::new("test.rs"), content)?;
478
479 let empty_violations: Vec<_> = violations
480 .iter()
481 .filter(|v| v.rule_id == "empty_ok_return")
482 .collect();
483
484 if empty_violations.is_empty() {
485 return Err(GuardianError::analysis(
486 "validation".to_string(),
487 "Should detect functions with trivial Ok(()) returns".to_string(),
488 ));
489 }
490
491 let has_empty_function = empty_violations
493 .iter()
494 .any(|v| v.message.contains("empty_function"));
495
496 if !has_empty_function {
497 return Err(GuardianError::analysis(
498 "validation".to_string(),
499 "Should specifically detect empty_function as having trivial return".to_string(),
500 ));
501 }
502
503 Ok(())
504 }
505
506 pub fn validate_test_function_skipping(&self) -> GuardianResult<()> {
508 if self.analyze_tests {
509 return Ok(());
511 }
512
513 let content = r#"
514//! Test module for test function handling
515//!
516//! Code Quality Principle: Context Awareness - Understanding test vs production code
517
518fn regular_function() {
519 unimplemented!("This should be detected")
520}
521
522#[test]
523fn test_something() {
524 unimplemented!("This should be ignored in production analysis")
525}
526
527#[cfg(test)]
528mod tests {
529 #[test]
530 fn nested_test() {
531 unimplemented!("Also should be ignored")
532 }
533}
534"#;
535
536 let violations = self.analyze(Path::new("test.rs"), content)?;
537
538 let unimplemented_violations: Vec<_> = violations
539 .iter()
540 .filter(|v| v.rule_id.contains("unimplemented"))
541 .collect();
542
543 if unimplemented_violations.len() != 1 {
545 return Err(GuardianError::analysis(
546 "validation".to_string(),
547 format!(
548 "Expected 1 unimplemented violation, found {}",
549 unimplemented_violations.len()
550 ),
551 ));
552 }
553
554 Ok(())
555 }
556
557 pub fn validate_quality_header_checking(&self) -> GuardianResult<()> {
559 if !self.check_quality_headers {
560 return Ok(());
562 }
563
564 let content_without_header = r#"
566fn main() {
567 println!("Hello, world!");
568}
569"#;
570
571 let violations = self.analyze(Path::new("src/main.rs"), content_without_header)?;
572 let missing_header_violations: Vec<_> = violations
573 .iter()
574 .filter(|v| v.rule_id == "quality_header_missing")
575 .collect();
576
577 if missing_header_violations.is_empty() {
578 return Err(GuardianError::analysis(
579 "validation".to_string(),
580 "Should detect missing quality header".to_string(),
581 ));
582 }
583
584 let content_with_header = r#"
586//! Main application entry point
587//!
588//! Code Quality Principle: Application Layer - Entry point coordinates services
589//! - Handles command line argument parsing
590//! - Sets up dependency injection container
591//! - Orchestrates application lifecycle
592
593fn main() {
594 tracing::info!("Application starting");
595}
596"#;
597
598 let violations = self.analyze(Path::new("src/main.rs"), content_with_header)?;
599 let header_violations: Vec<_> = violations
600 .iter()
601 .filter(|v| v.rule_id == "quality_header_missing")
602 .collect();
603
604 if !header_violations.is_empty() {
605 return Err(GuardianError::analysis(
606 "validation".to_string(),
607 "Should not report missing header when header is present".to_string(),
608 ));
609 }
610
611 Ok(())
612 }
613
614 pub fn validate_invalid_syntax_handling(&self) -> GuardianResult<()> {
616 let invalid_content = "this is not valid rust syntax {{{ %%% @@@";
617
618 let violations = self.analyze(Path::new("invalid.rs"), invalid_content)?;
620
621 if !violations.is_empty() {
624 tracing::debug!(
626 "Found {} violations in invalid syntax file",
627 violations.len()
628 );
629 }
630
631 Ok(())
632 }
633}
634
635#[cfg(test)]
638pub fn validate_rust_analyzer_domain() -> GuardianResult<()> {
639 let analyzer = RustAnalyzer::new();
640
641 analyzer.validate_file_type_detection()?;
643 analyzer.validate_macro_detection()?;
644 analyzer.validate_empty_return_detection()?;
645 analyzer.validate_test_function_skipping()?;
646 analyzer.validate_quality_header_checking()?;
647 analyzer.validate_invalid_syntax_handling()?;
648
649 let analyzer_with_tests = RustAnalyzer::with_tests();
651 analyzer_with_tests.validate_file_type_detection()?;
652 analyzer_with_tests.validate_macro_detection()?;
653
654 Ok(())
655}