1use crate::error::Result;
4use crate::types::ValidationResult;
5use std::path::Path;
6use std::process::Command;
7
8pub struct ValidationEngine;
10
11impl ValidationEngine {
12 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl Default for ValidationEngine {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24#[derive(Debug, Clone)]
26pub struct TestExecutionResult {
27 pub passed: bool,
29 pub tests_run: usize,
31 pub tests_passed: usize,
33 pub tests_failed: usize,
35 pub output: String,
37 pub errors: Vec<String>,
39}
40
41impl ValidationEngine {
42 pub fn validate_syntax(code: &str, language: &str) -> Result<ValidationResult> {
44 let mut errors = vec![];
45 let mut warnings = vec![];
46
47 match language {
49 "rust" => {
50 Self::validate_rust_syntax(code, &mut errors, &mut warnings);
51 }
52 "typescript" | "javascript" => {
53 Self::validate_typescript_syntax(code, &mut errors, &mut warnings);
54 }
55 "python" => {
56 Self::validate_python_syntax(code, &mut errors, &mut warnings);
57 }
58 _ => {
59 Self::validate_generic_syntax(code, &mut errors, &mut warnings);
61 }
62 }
63
64 Ok(ValidationResult {
65 passed: errors.is_empty(),
66 errors,
67 warnings,
68 })
69 }
70
71 fn validate_rust_syntax(code: &str, errors: &mut Vec<String>, warnings: &mut Vec<String>) {
73 let open_braces = code.matches('{').count();
74 let close_braces = code.matches('}').count();
75 let open_parens = code.matches('(').count();
76 let close_parens = code.matches(')').count();
77
78 if open_braces != close_braces {
79 errors.push(format!(
80 "Brace mismatch: {} open, {} close",
81 open_braces, close_braces
82 ));
83 }
84
85 if open_parens != close_parens {
86 errors.push(format!(
87 "Parenthesis mismatch: {} open, {} close",
88 open_parens, close_parens
89 ));
90 }
91
92 if code.contains("unsafe") {
93 warnings.push("Code contains unsafe block".to_string());
94 }
95 }
96
97 fn validate_typescript_syntax(code: &str, errors: &mut Vec<String>, warnings: &mut Vec<String>) {
99 let open_braces = code.matches('{').count();
100 let close_braces = code.matches('}').count();
101 let open_parens = code.matches('(').count();
102 let close_parens = code.matches(')').count();
103
104 if open_braces != close_braces {
105 errors.push(format!(
106 "Brace mismatch: {} open, {} close",
107 open_braces, close_braces
108 ));
109 }
110
111 if open_parens != close_parens {
112 errors.push(format!(
113 "Parenthesis mismatch: {} open, {} close",
114 open_parens, close_parens
115 ));
116 }
117
118 if code.contains("any") {
119 warnings.push("Code uses 'any' type".to_string());
120 }
121 }
122
123 fn validate_python_syntax(code: &str, errors: &mut Vec<String>, warnings: &mut Vec<String>) {
125 let open_parens = code.matches('(').count();
126 let close_parens = code.matches(')').count();
127 let open_brackets = code.matches('[').count();
128 let close_brackets = code.matches(']').count();
129
130 if open_parens != close_parens {
131 errors.push(format!(
132 "Parenthesis mismatch: {} open, {} close",
133 open_parens, close_parens
134 ));
135 }
136
137 if open_brackets != close_brackets {
138 errors.push(format!(
139 "Bracket mismatch: {} open, {} close",
140 open_brackets, close_brackets
141 ));
142 }
143
144 if code.contains("exec(") {
145 warnings.push("Code uses exec() function".to_string());
146 }
147 }
148
149 fn validate_generic_syntax(code: &str, errors: &mut Vec<String>, _warnings: &mut Vec<String>) {
151 let open_braces = code.matches('{').count();
152 let close_braces = code.matches('}').count();
153 let open_parens = code.matches('(').count();
154 let close_parens = code.matches(')').count();
155
156 if open_braces != close_braces {
157 errors.push(format!(
158 "Brace mismatch: {} open, {} close",
159 open_braces, close_braces
160 ));
161 }
162
163 if open_parens != close_parens {
164 errors.push(format!(
165 "Parenthesis mismatch: {} open, {} close",
166 open_parens, close_parens
167 ));
168 }
169 }
170
171 pub fn validate_semantics(code: &str, _language: &str) -> Result<ValidationResult> {
173 let mut errors = vec![];
174 let warnings = vec![];
175
176 if code.is_empty() {
178 errors.push("Code cannot be empty".to_string());
179 }
180
181 Ok(ValidationResult {
182 passed: errors.is_empty(),
183 errors,
184 warnings,
185 })
186 }
187
188 pub fn run_tests(project_path: &Path, language: &str) -> Result<TestExecutionResult> {
193 match language {
194 "rust" => Self::run_rust_tests(project_path),
195 "typescript" | "javascript" => Self::run_npm_tests(project_path),
196 "python" => Self::run_python_tests(project_path),
197 _ => Self::run_generic_tests(project_path),
198 }
199 }
200
201 fn run_rust_tests(project_path: &Path) -> Result<TestExecutionResult> {
203 let output = Command::new("cargo")
204 .arg("test")
205 .arg("--")
206 .arg("--test-threads=1")
207 .current_dir(project_path)
208 .output()
209 .map_err(|e| crate::error::RefactoringError::ValidationFailed(
210 format!("Failed to run cargo tests: {}", e)
211 ))?;
212
213 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
214 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
215
216 let passed = output.status.success();
217 let (tests_run, tests_passed, tests_failed) = Self::parse_rust_test_output(&stdout);
218
219 let mut errors = vec![];
220 if !passed && !stderr.is_empty() {
221 errors.push(stderr);
222 }
223
224 Ok(TestExecutionResult {
225 passed,
226 tests_run,
227 tests_passed,
228 tests_failed,
229 output: stdout,
230 errors,
231 })
232 }
233
234 fn run_npm_tests(project_path: &Path) -> Result<TestExecutionResult> {
236 let output = Command::new("npm")
237 .arg("test")
238 .current_dir(project_path)
239 .output()
240 .map_err(|e| crate::error::RefactoringError::ValidationFailed(
241 format!("Failed to run npm tests: {}", e)
242 ))?;
243
244 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
245 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
246
247 let passed = output.status.success();
248 let (tests_run, tests_passed, tests_failed) = Self::parse_npm_test_output(&stdout);
249
250 let mut errors = vec![];
251 if !passed && !stderr.is_empty() {
252 errors.push(stderr);
253 }
254
255 Ok(TestExecutionResult {
256 passed,
257 tests_run,
258 tests_passed,
259 tests_failed,
260 output: stdout,
261 errors,
262 })
263 }
264
265 fn run_python_tests(project_path: &Path) -> Result<TestExecutionResult> {
267 let output = Command::new("pytest")
268 .arg("-v")
269 .current_dir(project_path)
270 .output()
271 .map_err(|e| crate::error::RefactoringError::ValidationFailed(
272 format!("Failed to run pytest: {}", e)
273 ))?;
274
275 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
276 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
277
278 let passed = output.status.success();
279 let (tests_run, tests_passed, tests_failed) = Self::parse_pytest_output(&stdout);
280
281 let mut errors = vec![];
282 if !passed && !stderr.is_empty() {
283 errors.push(stderr);
284 }
285
286 Ok(TestExecutionResult {
287 passed,
288 tests_run,
289 tests_passed,
290 tests_failed,
291 output: stdout,
292 errors,
293 })
294 }
295
296 fn run_generic_tests(_project_path: &Path) -> Result<TestExecutionResult> {
298 Ok(TestExecutionResult {
299 passed: true,
300 tests_run: 0,
301 tests_passed: 0,
302 tests_failed: 0,
303 output: "No test runner available for this language".to_string(),
304 errors: vec![],
305 })
306 }
307
308 fn parse_rust_test_output(output: &str) -> (usize, usize, usize) {
310 let mut tests_run = 0;
311 let mut tests_passed = 0;
312 let mut tests_failed = 0;
313
314 for line in output.lines() {
315 if line.contains("test result:") {
316 if let Some(passed_part) = line.split("passed;").next() {
318 if let Some(num_str) = passed_part.split_whitespace().last() {
319 if let Ok(num) = num_str.parse::<usize>() {
320 tests_passed = num;
321 }
322 }
323 }
324
325 if let Some(failed_part) = line.split("failed;").next() {
326 if let Some(num_str) = failed_part.split_whitespace().last() {
327 if let Ok(num) = num_str.parse::<usize>() {
328 tests_failed = num;
329 }
330 }
331 }
332
333 tests_run = tests_passed + tests_failed;
334 }
335 }
336
337 (tests_run, tests_passed, tests_failed)
338 }
339
340 fn parse_npm_test_output(output: &str) -> (usize, usize, usize) {
342 let mut tests_passed = 0;
343 let mut tests_failed = 0;
344
345 for line in output.lines() {
346 let parts: Vec<&str> = line.split(|c: char| c == ',' || c.is_whitespace()).collect();
349 let mut i = 0;
350 while i < parts.len() {
351 if let Ok(num) = parts[i].parse::<usize>() {
352 if i + 1 < parts.len() {
353 match parts[i + 1] {
354 "passed" => tests_passed = num,
355 "failed" => tests_failed = num,
356 _ => {}
357 }
358 }
359 }
360 i += 1;
361 }
362 }
363
364 let tests_run = tests_passed + tests_failed;
365 (tests_run, tests_passed, tests_failed)
366 }
367
368 fn parse_pytest_output(output: &str) -> (usize, usize, usize) {
370 let mut tests_passed = 0;
371 let mut tests_failed = 0;
372
373 for line in output.lines() {
374 let parts: Vec<&str> = line.split(|c: char| c == ',' || c.is_whitespace()).collect();
376 let mut i = 0;
377 while i < parts.len() {
378 if let Ok(num) = parts[i].parse::<usize>() {
379 if i + 1 < parts.len() {
380 match parts[i + 1] {
381 "passed" => tests_passed = num,
382 "failed" => tests_failed = num,
383 _ => {}
384 }
385 }
386 }
387 i += 1;
388 }
389 }
390
391 let tests_run = tests_passed + tests_failed;
392 (tests_run, tests_passed, tests_failed)
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
403 fn test_validate_rust_syntax_valid() -> Result<()> {
404 let code = "fn main() { println!(\"Hello\"); }";
405 let result = ValidationEngine::validate_syntax(code, "rust")?;
406 assert!(result.passed);
407 assert!(result.errors.is_empty());
408 Ok(())
409 }
410
411 #[test]
412 fn test_validate_rust_syntax_invalid_braces() -> Result<()> {
413 let code = "fn main() { println!(\"Hello\"); ";
414 let result = ValidationEngine::validate_syntax(code, "rust")?;
415 assert!(!result.passed);
416 assert!(!result.errors.is_empty());
417 assert!(result.errors[0].contains("Brace mismatch"));
418 Ok(())
419 }
420
421 #[test]
422 fn test_validate_rust_syntax_invalid_parens() -> Result<()> {
423 let code = "fn main() { println!(\"Hello\"; }";
424 let result = ValidationEngine::validate_syntax(code, "rust")?;
425 assert!(!result.passed);
426 assert!(!result.errors.is_empty());
427 assert!(result.errors[0].contains("Parenthesis mismatch"));
428 Ok(())
429 }
430
431 #[test]
432 fn test_validate_rust_syntax_unsafe_warning() -> Result<()> {
433 let code = "unsafe { let x = 5; }";
434 let result = ValidationEngine::validate_syntax(code, "rust")?;
435 assert!(result.passed);
436 assert!(!result.warnings.is_empty());
437 assert!(result.warnings[0].contains("unsafe"));
438 Ok(())
439 }
440
441 #[test]
442 fn test_validate_typescript_syntax_valid() -> Result<()> {
443 let code = "function main() { console.log(\"Hello\"); }";
444 let result = ValidationEngine::validate_syntax(code, "typescript")?;
445 assert!(result.passed);
446 assert!(result.errors.is_empty());
447 Ok(())
448 }
449
450 #[test]
451 fn test_validate_typescript_syntax_invalid_braces() -> Result<()> {
452 let code = "function main() { console.log(\"Hello\"); ";
453 let result = ValidationEngine::validate_syntax(code, "typescript")?;
454 assert!(!result.passed);
455 assert!(!result.errors.is_empty());
456 Ok(())
457 }
458
459 #[test]
460 fn test_validate_typescript_syntax_any_warning() -> Result<()> {
461 let code = "let x: any = 5;";
462 let result = ValidationEngine::validate_syntax(code, "typescript")?;
463 assert!(result.passed);
464 assert!(!result.warnings.is_empty());
465 assert!(result.warnings[0].contains("any"));
466 Ok(())
467 }
468
469 #[test]
470 fn test_validate_python_syntax_valid() -> Result<()> {
471 let code = "def main():\n print(\"Hello\")";
472 let result = ValidationEngine::validate_syntax(code, "python")?;
473 assert!(result.passed);
474 assert!(result.errors.is_empty());
475 Ok(())
476 }
477
478 #[test]
479 fn test_validate_python_syntax_invalid_parens() -> Result<()> {
480 let code = "def main():\n print(\"Hello\"";
481 let result = ValidationEngine::validate_syntax(code, "python")?;
482 assert!(!result.passed);
483 assert!(!result.errors.is_empty());
484 Ok(())
485 }
486
487 #[test]
488 fn test_validate_python_syntax_invalid_brackets() -> Result<()> {
489 let code = "x = [1, 2, 3";
490 let result = ValidationEngine::validate_syntax(code, "python")?;
491 assert!(!result.passed);
492 assert!(!result.errors.is_empty());
493 Ok(())
494 }
495
496 #[test]
497 fn test_validate_python_syntax_exec_warning() -> Result<()> {
498 let code = "exec(\"print('hello')\")";
499 let result = ValidationEngine::validate_syntax(code, "python")?;
500 assert!(result.passed);
501 assert!(!result.warnings.is_empty());
502 assert!(result.warnings[0].contains("exec()"));
503 Ok(())
504 }
505
506 #[test]
507 fn test_validate_generic_syntax_valid() -> Result<()> {
508 let code = "some code { with (parens) }";
509 let result = ValidationEngine::validate_syntax(code, "unknown")?;
510 assert!(result.passed);
511 Ok(())
512 }
513
514 #[test]
515 fn test_validate_generic_syntax_invalid() -> Result<()> {
516 let code = "some code { with (parens }";
517 let result = ValidationEngine::validate_syntax(code, "unknown")?;
518 assert!(!result.passed);
519 Ok(())
520 }
521
522 #[test]
525 fn test_validate_semantics_empty() -> Result<()> {
526 let result = ValidationEngine::validate_semantics("", "rust")?;
527 assert!(!result.passed);
528 assert!(!result.errors.is_empty());
529 assert!(result.errors[0].contains("empty"));
530 Ok(())
531 }
532
533 #[test]
534 fn test_validate_semantics_valid() -> Result<()> {
535 let result = ValidationEngine::validate_semantics("fn main() {}", "rust")?;
536 assert!(result.passed);
537 assert!(result.errors.is_empty());
538 Ok(())
539 }
540
541 #[test]
542 fn test_validate_semantics_valid_typescript() -> Result<()> {
543 let result = ValidationEngine::validate_semantics("function main() {}", "typescript")?;
544 assert!(result.passed);
545 Ok(())
546 }
547
548 #[test]
549 fn test_validate_semantics_valid_python() -> Result<()> {
550 let result = ValidationEngine::validate_semantics("def main(): pass", "python")?;
551 assert!(result.passed);
552 Ok(())
553 }
554
555 #[test]
558 fn test_parse_rust_test_output_success() {
559 let output = "test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out";
560 let (tests_run, tests_passed, tests_failed) = ValidationEngine::parse_rust_test_output(output);
561 assert_eq!(tests_run, 5);
562 assert_eq!(tests_passed, 5);
563 assert_eq!(tests_failed, 0);
564 }
565
566 #[test]
567 fn test_parse_rust_test_output_with_failures() {
568 let output = "test result: FAILED. 3 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out";
569 let (tests_run, tests_passed, tests_failed) = ValidationEngine::parse_rust_test_output(output);
570 assert_eq!(tests_run, 5);
571 assert_eq!(tests_passed, 3);
572 assert_eq!(tests_failed, 2);
573 }
574
575 #[test]
576 fn test_parse_rust_test_output_no_tests() {
577 let output = "test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out";
578 let (tests_run, tests_passed, tests_failed) = ValidationEngine::parse_rust_test_output(output);
579 assert_eq!(tests_run, 0);
580 assert_eq!(tests_passed, 0);
581 assert_eq!(tests_failed, 0);
582 }
583
584 #[test]
585 fn test_parse_npm_test_output_success() {
586 let output = "5 passed";
587 let (_tests_run, tests_passed, tests_failed) = ValidationEngine::parse_npm_test_output(output);
588 assert_eq!(tests_passed, 5);
589 assert_eq!(tests_failed, 0);
590 }
591
592 #[test]
593 fn test_parse_npm_test_output_with_failures() {
594 let output = "3 passed, 2 failed";
595 let (_tests_run, tests_passed, tests_failed) = ValidationEngine::parse_npm_test_output(output);
596 assert_eq!(tests_passed, 3);
597 assert_eq!(tests_failed, 2);
598 }
599
600 #[test]
601 fn test_parse_pytest_output_success() {
602 let output = "5 passed in 0.12s";
603 let (_tests_run, tests_passed, tests_failed) = ValidationEngine::parse_pytest_output(output);
604 assert_eq!(tests_passed, 5);
605 assert_eq!(tests_failed, 0);
606 }
607
608 #[test]
609 fn test_parse_pytest_output_with_failures() {
610 let output = "3 failed, 2 passed in 0.15s";
611 let (_tests_run, tests_passed, tests_failed) = ValidationEngine::parse_pytest_output(output);
612 assert_eq!(tests_passed, 2);
613 assert_eq!(tests_failed, 3);
614 }
615
616 #[test]
619 fn test_test_execution_result_creation() {
620 let result = TestExecutionResult {
621 passed: true,
622 tests_run: 5,
623 tests_passed: 5,
624 tests_failed: 0,
625 output: "All tests passed".to_string(),
626 errors: vec![],
627 };
628
629 assert!(result.passed);
630 assert_eq!(result.tests_run, 5);
631 assert_eq!(result.tests_passed, 5);
632 assert_eq!(result.tests_failed, 0);
633 assert!(result.errors.is_empty());
634 }
635
636 #[test]
637 fn test_test_execution_result_with_errors() {
638 let result = TestExecutionResult {
639 passed: false,
640 tests_run: 5,
641 tests_passed: 3,
642 tests_failed: 2,
643 output: "Some tests failed".to_string(),
644 errors: vec!["Test 1 failed".to_string(), "Test 2 failed".to_string()],
645 };
646
647 assert!(!result.passed);
648 assert_eq!(result.tests_run, 5);
649 assert_eq!(result.tests_passed, 3);
650 assert_eq!(result.tests_failed, 2);
651 assert_eq!(result.errors.len(), 2);
652 }
653}