1use crate::models::{ValidationError, ValidationWarning};
11use tracing::debug;
12
13pub trait LanguageValidator: Send + Sync {
15 fn validate(
17 &self,
18 content: &str,
19 file_path: &str,
20 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String>;
21}
22
23#[derive(Debug, Clone)]
25pub struct RustValidator;
26
27impl RustValidator {
28 pub fn new() -> Self {
30 Self
31 }
32
33 pub fn check_common_issues(&self, content: &str, file_path: &str) -> Vec<ValidationError> {
35 let mut errors = Vec::new();
36
37 for (line_num, line) in content.lines().enumerate() {
39 if line.contains("unsafe") && !line.trim().starts_with("//") {
40 let has_comment = if line_num > 0 {
42 content
43 .lines()
44 .nth(line_num - 1)
45 .map(|l| l.trim().starts_with("//"))
46 .unwrap_or(false)
47 } else {
48 false
49 };
50
51 if !has_comment {
52 errors.push(ValidationError {
53 file: file_path.to_string(),
54 line: line_num + 1,
55 column: 1,
56 message: "unsafe block without documentation comment".to_string(),
57 code: Some("W0001".to_string()),
58 });
59 }
60 }
61 }
62
63 errors
64 }
65
66 pub fn check_unwrap_calls(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
68 let mut warnings = Vec::new();
69
70 for (line_num, line) in content.lines().enumerate() {
71 if line.contains(".unwrap()") {
72 warnings.push(ValidationWarning {
73 file: file_path.to_string(),
74 line: line_num + 1,
75 column: line.find(".unwrap()").unwrap_or(0) + 1,
76 message: "unwrap() call may panic".to_string(),
77 code: Some("W0002".to_string()),
78 });
79 }
80 }
81
82 warnings
83 }
84
85 pub fn check_panic_calls(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
87 let mut warnings = Vec::new();
88
89 for (line_num, line) in content.lines().enumerate() {
90 if line.contains("panic!") {
91 warnings.push(ValidationWarning {
92 file: file_path.to_string(),
93 line: line_num + 1,
94 column: line.find("panic!").unwrap_or(0) + 1,
95 message: "panic! call may crash the application".to_string(),
96 code: Some("W0003".to_string()),
97 });
98 }
99 }
100
101 warnings
102 }
103}
104
105impl Default for RustValidator {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111impl LanguageValidator for RustValidator {
112 fn validate(
113 &self,
114 content: &str,
115 file_path: &str,
116 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String> {
117 debug!("Validating Rust code: {}", file_path);
118
119 let errors = self.check_common_issues(content, file_path);
120 let warnings = self.check_unwrap_calls(content, file_path);
121 let mut all_warnings = warnings;
122 all_warnings.extend(self.check_panic_calls(content, file_path));
123
124 Ok((errors, all_warnings))
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct TypeScriptValidator;
131
132impl TypeScriptValidator {
133 pub fn new() -> Self {
135 Self
136 }
137
138 pub fn check_common_issues(&self, content: &str, file_path: &str) -> Vec<ValidationError> {
140 let mut errors = Vec::new();
141
142 for (line_num, line) in content.lines().enumerate() {
144 if line.contains(": any") || line.contains(": any,") || line.contains(": any)") {
145 errors.push(ValidationError {
146 file: file_path.to_string(),
147 line: line_num + 1,
148 column: line.find(": any").unwrap_or(0) + 1,
149 message: "Use of 'any' type is not allowed".to_string(),
150 code: Some("TS7006".to_string()),
151 });
152 }
153 }
154
155 errors
156 }
157
158 pub fn check_error_handling(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
160 let mut warnings = Vec::new();
161
162 for (line_num, line) in content.lines().enumerate() {
163 if line.contains("throw ") && !line.contains("Error") {
164 warnings.push(ValidationWarning {
165 file: file_path.to_string(),
166 line: line_num + 1,
167 column: line.find("throw").unwrap_or(0) + 1,
168 message: "throw without Error type".to_string(),
169 code: Some("W0001".to_string()),
170 });
171 }
172 }
173
174 warnings
175 }
176
177 pub fn check_console_usage(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
179 let mut warnings = Vec::new();
180
181 for (line_num, line) in content.lines().enumerate() {
182 if line.contains("console.") && !line.trim().starts_with("//") {
183 warnings.push(ValidationWarning {
184 file: file_path.to_string(),
185 line: line_num + 1,
186 column: line.find("console.").unwrap_or(0) + 1,
187 message: "console usage in production code".to_string(),
188 code: Some("W0002".to_string()),
189 });
190 }
191 }
192
193 warnings
194 }
195}
196
197impl Default for TypeScriptValidator {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl LanguageValidator for TypeScriptValidator {
204 fn validate(
205 &self,
206 content: &str,
207 file_path: &str,
208 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String> {
209 debug!("Validating TypeScript code: {}", file_path);
210
211 let errors = self.check_common_issues(content, file_path);
212 let mut warnings = self.check_error_handling(content, file_path);
213 warnings.extend(self.check_console_usage(content, file_path));
214
215 Ok((errors, warnings))
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct PythonValidator;
222
223impl PythonValidator {
224 pub fn new() -> Self {
226 Self
227 }
228
229 pub fn check_common_issues(&self, content: &str, file_path: &str) -> Vec<ValidationError> {
231 let mut errors = Vec::new();
232
233 for (line_num, line) in content.lines().enumerate() {
235 if line.trim() == "except:" {
236 errors.push(ValidationError {
237 file: file_path.to_string(),
238 line: line_num + 1,
239 column: 1,
240 message: "Bare except clause is not allowed".to_string(),
241 code: Some("E0001".to_string()),
242 });
243 }
244 }
245
246 errors
247 }
248
249 pub fn check_type_hints(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
251 let mut warnings = Vec::new();
252
253 for (line_num, line) in content.lines().enumerate() {
254 if (line.trim().starts_with("def ") || line.trim().starts_with("class "))
255 && !line.contains("->")
256 && !line.trim().starts_with("def _")
257 {
258 warnings.push(ValidationWarning {
259 file: file_path.to_string(),
260 line: line_num + 1,
261 column: 1,
262 message: "Missing type hints".to_string(),
263 code: Some("W0001".to_string()),
264 });
265 }
266 }
267
268 warnings
269 }
270
271 pub fn check_print_usage(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
273 let mut warnings = Vec::new();
274
275 for (line_num, line) in content.lines().enumerate() {
276 if line.contains("print(") && !line.trim().starts_with("#") {
277 warnings.push(ValidationWarning {
278 file: file_path.to_string(),
279 line: line_num + 1,
280 column: line.find("print(").unwrap_or(0) + 1,
281 message: "print() usage in production code".to_string(),
282 code: Some("W0002".to_string()),
283 });
284 }
285 }
286
287 warnings
288 }
289}
290
291impl Default for PythonValidator {
292 fn default() -> Self {
293 Self::new()
294 }
295}
296
297impl LanguageValidator for PythonValidator {
298 fn validate(
299 &self,
300 content: &str,
301 file_path: &str,
302 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String> {
303 debug!("Validating Python code: {}", file_path);
304
305 let errors = self.check_common_issues(content, file_path);
306 let mut warnings = self.check_type_hints(content, file_path);
307 warnings.extend(self.check_print_usage(content, file_path));
308
309 Ok((errors, warnings))
310 }
311}
312
313#[derive(Debug, Clone)]
315pub struct GoValidator;
316
317impl GoValidator {
318 pub fn new() -> Self {
320 Self
321 }
322
323 pub fn check_common_issues(&self, content: &str, file_path: &str) -> Vec<ValidationError> {
325 let mut errors = Vec::new();
326
327 for (line_num, line) in content.lines().enumerate() {
329 if line.contains("_ = ") && line.contains("err") {
330 errors.push(ValidationError {
331 file: file_path.to_string(),
332 line: line_num + 1,
333 column: 1,
334 message: "Error ignored with blank identifier".to_string(),
335 code: Some("E0001".to_string()),
336 });
337 }
338 }
339
340 errors
341 }
342
343 pub fn check_panic_usage(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
345 let mut warnings = Vec::new();
346
347 for (line_num, line) in content.lines().enumerate() {
348 if line.contains("panic(") {
349 warnings.push(ValidationWarning {
350 file: file_path.to_string(),
351 line: line_num + 1,
352 column: line.find("panic(").unwrap_or(0) + 1,
353 message: "panic() call may crash the application".to_string(),
354 code: Some("W0001".to_string()),
355 });
356 }
357 }
358
359 warnings
360 }
361}
362
363impl Default for GoValidator {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369impl LanguageValidator for GoValidator {
370 fn validate(
371 &self,
372 content: &str,
373 file_path: &str,
374 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String> {
375 debug!("Validating Go code: {}", file_path);
376
377 let errors = self.check_common_issues(content, file_path);
378 let warnings = self.check_panic_usage(content, file_path);
379
380 Ok((errors, warnings))
381 }
382}
383
384#[derive(Debug, Clone)]
386pub struct JavaValidator;
387
388impl JavaValidator {
389 pub fn new() -> Self {
391 Self
392 }
393
394 pub fn check_common_issues(&self, content: &str, file_path: &str) -> Vec<ValidationError> {
396 let mut errors = Vec::new();
397
398 if !content.contains("class ") && !content.contains("interface ") {
400 errors.push(ValidationError {
401 file: file_path.to_string(),
402 line: 1,
403 column: 1,
404 message: "Missing class or interface declaration".to_string(),
405 code: Some("E0001".to_string()),
406 });
407 }
408
409 errors
410 }
411
412 pub fn check_raw_types(&self, content: &str, file_path: &str) -> Vec<ValidationWarning> {
414 let mut warnings = Vec::new();
415
416 for (line_num, line) in content.lines().enumerate() {
417 if (line.contains("List ") || line.contains("Map ") || line.contains("Set "))
418 && !line.contains("<")
419 {
420 warnings.push(ValidationWarning {
421 file: file_path.to_string(),
422 line: line_num + 1,
423 column: 1,
424 message: "Raw type usage without generics".to_string(),
425 code: Some("W0001".to_string()),
426 });
427 }
428 }
429
430 warnings
431 }
432}
433
434impl Default for JavaValidator {
435 fn default() -> Self {
436 Self::new()
437 }
438}
439
440impl LanguageValidator for JavaValidator {
441 fn validate(
442 &self,
443 content: &str,
444 file_path: &str,
445 ) -> Result<(Vec<ValidationError>, Vec<ValidationWarning>), String> {
446 debug!("Validating Java code: {}", file_path);
447
448 let errors = self.check_common_issues(content, file_path);
449 let warnings = self.check_raw_types(content, file_path);
450
451 Ok((errors, warnings))
452 }
453}
454
455pub fn get_validator(language: &str) -> Option<Box<dyn LanguageValidator>> {
457 match language {
458 "rust" | "rs" => Some(Box::new(RustValidator::new())),
459 "typescript" | "ts" => Some(Box::new(TypeScriptValidator::new())),
460 "python" | "py" => Some(Box::new(PythonValidator::new())),
461 "go" => Some(Box::new(GoValidator::new())),
462 "java" => Some(Box::new(JavaValidator::new())),
463 _ => None,
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_rust_validator_unwrap() {
473 let validator = RustValidator::new();
474 let content = "let x = result.unwrap();";
475 let warnings = validator.check_unwrap_calls(content, "main.rs");
476 assert!(!warnings.is_empty());
477 assert!(warnings[0].message.contains("unwrap"));
478 }
479
480 #[test]
481 fn test_rust_validator_panic() {
482 let validator = RustValidator::new();
483 let content = "panic!(\"error\");";
484 let warnings = validator.check_panic_calls(content, "main.rs");
485 assert!(!warnings.is_empty());
486 assert!(warnings[0].message.contains("panic"));
487 }
488
489 #[test]
490 fn test_typescript_validator_any_type() {
491 let validator = TypeScriptValidator::new();
492 let content = "let x: any = 5;";
493 let errors = validator.check_common_issues(content, "main.ts");
494 assert!(!errors.is_empty());
495 assert!(errors[0].message.contains("any"));
496 }
497
498 #[test]
499 fn test_typescript_validator_console() {
500 let validator = TypeScriptValidator::new();
501 let content = "console.log(\"test\");";
502 let warnings = validator.check_console_usage(content, "main.ts");
503 assert!(!warnings.is_empty());
504 assert!(warnings[0].message.contains("console"));
505 }
506
507 #[test]
508 fn test_python_validator_bare_except() {
509 let validator = PythonValidator::new();
510 let content = "try:\n pass\nexcept:";
511 let errors = validator.check_common_issues(content, "main.py");
512 assert!(!errors.is_empty());
513 assert!(errors[0].message.contains("Bare except"));
514 }
515
516 #[test]
517 fn test_python_validator_print() {
518 let validator = PythonValidator::new();
519 let content = "print(\"test\")";
520 let warnings = validator.check_print_usage(content, "main.py");
521 assert!(!warnings.is_empty());
522 assert!(warnings[0].message.contains("print"));
523 }
524
525 #[test]
526 fn test_go_validator_panic() {
527 let validator = GoValidator::new();
528 let content = "panic(\"error\")";
529 let warnings = validator.check_panic_usage(content, "main.go");
530 assert!(!warnings.is_empty());
531 assert!(warnings[0].message.contains("panic"));
532 }
533
534 #[test]
535 fn test_java_validator_raw_type() {
536 let validator = JavaValidator::new();
537 let content = "List items = new ArrayList();";
538 let warnings = validator.check_raw_types(content, "Main.java");
539 assert!(!warnings.is_empty());
540 assert!(warnings[0].message.contains("Raw type"));
541 }
542
543 #[test]
544 fn test_get_validator_rust() {
545 let validator = get_validator("rust");
546 assert!(validator.is_some());
547 }
548
549 #[test]
550 fn test_get_validator_typescript() {
551 let validator = get_validator("typescript");
552 assert!(validator.is_some());
553 }
554
555 #[test]
556 fn test_get_validator_python() {
557 let validator = get_validator("python");
558 assert!(validator.is_some());
559 }
560
561 #[test]
562 fn test_get_validator_unknown() {
563 let validator = get_validator("unknown");
564 assert!(validator.is_none());
565 }
566
567 #[test]
568 fn test_rust_validator_trait() {
569 let validator = RustValidator::new();
570 let content = "fn main() {}";
571 let result = validator.validate(content, "main.rs");
572 assert!(result.is_ok());
573 }
574
575 #[test]
576 fn test_typescript_validator_trait() {
577 let validator = TypeScriptValidator::new();
578 let content = "function main() {}";
579 let result = validator.validate(content, "main.ts");
580 assert!(result.is_ok());
581 }
582}