use crate::edit_control::{ModifiableEdit, ValidationResult, ValidationSeverity};
use std::collections::{HashMap, HashSet};
pub struct EditValidator {
validators: Vec<Box<dyn Validator>>,
conflict_detector: ConflictDetector,
syntax_checker: SyntaxChecker,
}
impl EditValidator {
pub fn new() -> Self {
let validators: Vec<Box<dyn Validator>> = vec![
Box::new(SyntaxValidator::new()),
Box::new(SecurityValidator::new()),
Box::new(PerformanceValidator::new()),
Box::new(StyleValidator::new()),
Box::new(ConsistencyValidator::new()),
];
Self {
validators,
conflict_detector: ConflictDetector::new(),
syntax_checker: SyntaxChecker::new(),
}
}
pub fn validate_edit(&self, edit: &mut ModifiableEdit) -> ValidationSummary {
let mut results = Vec::new();
let mut conflicts = Vec::new();
if let Some(syntax_result) = self
.syntax_checker
.check(&edit.compute_final_code(), &edit.base_edit.file)
{
results.push(syntax_result);
}
for validator in &self.validators {
match validator.validate(edit) {
Ok(validator_results) => results.extend(validator_results),
Err(e) => results.push(ValidationResult {
validator_name: validator.name().to_string(),
passed: false,
message: format!("Validator error: {}", e),
severity: ValidationSeverity::Error,
}),
}
}
conflicts.extend(self.conflict_detector.detect_conflicts(edit));
edit.processing_metadata.validation_results = results.clone();
ValidationSummary::new(results, conflicts)
}
pub fn validate_incremental(
&self,
_edit: &ModifiableEdit,
change: &str,
) -> Vec<ValidationResult> {
let mut results = Vec::new();
if let Some(result) = self.syntax_checker.check_fragment(change) {
results.push(result);
}
if self.contains_security_risks(change) {
results.push(ValidationResult {
validator_name: "security_quick_check".to_string(),
passed: false,
message: "Potential security risk detected in change".to_string(),
severity: ValidationSeverity::Warning,
});
}
if self.contains_performance_risks(change) {
results.push(ValidationResult {
validator_name: "performance_quick_check".to_string(),
passed: false,
message: "Potential performance issue detected".to_string(),
severity: ValidationSeverity::Warning,
});
}
results
}
fn contains_security_risks(&self, code: &str) -> bool {
let security_patterns = [
"unsafe",
"transmute",
"from_raw",
"exec",
"system",
"sql",
"eval",
"innerHTML",
"setTimeout",
];
security_patterns
.iter()
.any(|pattern| code.contains(pattern))
}
fn contains_performance_risks(&self, code: &str) -> bool {
let performance_patterns = ["clone()", "unwrap()", "expect(", "panic!"];
performance_patterns
.iter()
.any(|pattern| code.contains(pattern))
}
}
impl Default for EditValidator {
fn default() -> Self {
Self::new()
}
}
pub trait Validator: Send + Sync {
fn name(&self) -> &str;
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String>;
}
pub struct SyntaxValidator {
supported_languages: HashSet<String>,
}
impl SyntaxValidator {
pub fn new() -> Self {
let mut supported = HashSet::new();
supported.insert("rs".to_string());
supported.insert("py".to_string());
supported.insert("js".to_string());
supported.insert("ts".to_string());
Self {
supported_languages: supported,
}
}
}
impl Validator for SyntaxValidator {
fn name(&self) -> &str {
"syntax_validator"
}
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String> {
let extension = std::path::Path::new(&edit.base_edit.file)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
if !self.supported_languages.contains(extension) {
return Ok(vec![ValidationResult {
validator_name: self.name().to_string(),
passed: true,
message: format!("Syntax validation skipped for .{} files", extension),
severity: ValidationSeverity::Info,
}]);
}
let code = edit.compute_final_code();
let syntax_errors = self.check_syntax(&code, extension);
Ok(syntax_errors)
}
}
impl SyntaxValidator {
fn check_syntax(&self, code: &str, language: &str) -> Vec<ValidationResult> {
match language {
"rs" => self.check_rust_syntax(code),
"py" => self.check_python_syntax(code),
"js" | "ts" => self.check_javascript_syntax(code),
_ => vec![],
}
}
fn check_rust_syntax(&self, code: &str) -> Vec<ValidationResult> {
let mut results = Vec::new();
let issues = [
(
"unmatched_braces",
self.check_balanced_braces(code),
"Check for balanced braces",
),
(
"unused_variables",
code.contains("let _unused"),
"Consider using variables or prefix with _",
),
(
"missing_semicolons",
self.check_missing_semicolons(code),
"Statements should end with semicolons",
),
];
for (issue_type, has_issue, message) in issues {
if has_issue {
results.push(ValidationResult {
validator_name: "rust_syntax".to_string(),
passed: false,
message: format!("{}: {}", issue_type, message),
severity: if issue_type == "unmatched_braces" {
ValidationSeverity::Error
} else {
ValidationSeverity::Warning
},
});
}
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: "rust_syntax".to_string(),
passed: true,
message: "Rust syntax appears valid".to_string(),
severity: ValidationSeverity::Info,
});
}
results
}
fn check_python_syntax(&self, code: &str) -> Vec<ValidationResult> {
let mut results = Vec::new();
if !self.check_indentation(code) {
results.push(ValidationResult {
validator_name: "python_syntax".to_string(),
passed: false,
message: "Inconsistent indentation detected".to_string(),
severity: ValidationSeverity::Error,
});
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: "python_syntax".to_string(),
passed: true,
message: "Python syntax appears valid".to_string(),
severity: ValidationSeverity::Info,
});
}
results
}
fn check_javascript_syntax(&self, code: &str) -> Vec<ValidationResult> {
let mut results = Vec::new();
if !self.check_balanced_braces(code) {
results.push(ValidationResult {
validator_name: "javascript_syntax".to_string(),
passed: false,
message: "Unbalanced braces detected".to_string(),
severity: ValidationSeverity::Error,
});
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: "javascript_syntax".to_string(),
passed: true,
message: "JavaScript syntax appears valid".to_string(),
severity: ValidationSeverity::Info,
});
}
results
}
fn check_balanced_braces(&self, code: &str) -> bool {
let mut stack = Vec::new();
let pairs = [('(', ')'), ('[', ']'), ('{', '}')];
for ch in code.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' | ']' | '}' => {
if let Some(&open) = stack.last() {
if pairs.iter().any(|&(o, c)| o == open && c == ch) {
stack.pop();
} else {
return false;
}
} else {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
fn check_missing_semicolons(&self, code: &str) -> bool {
code.lines().any(|line| {
let trimmed = line.trim();
!trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.ends_with(';')
&& !trimmed.ends_with('{')
&& !trimmed.ends_with('}')
&& !trimmed.starts_with('#')
})
}
fn check_indentation(&self, code: &str) -> bool {
let mut indent_stack = Vec::new();
for line in code.lines() {
if line.trim().is_empty() || line.trim().starts_with('#') {
continue;
}
let indent_level = line.len() - line.trim_start().len();
if indent_stack.is_empty() {
indent_stack.push(indent_level);
} else {
let last_indent = *indent_stack.last().unwrap();
if indent_level > last_indent {
indent_stack.push(indent_level);
} else if indent_level < last_indent {
while let Some(&stack_indent) = indent_stack.last() {
if stack_indent <= indent_level {
break;
}
indent_stack.pop();
}
if indent_stack.is_empty() || *indent_stack.last().unwrap() != indent_level {
return false;
}
}
}
}
true
}
}
pub struct SecurityValidator;
impl SecurityValidator {
pub fn new() -> Self {
Self
}
}
impl Validator for SecurityValidator {
fn name(&self) -> &str {
"security_validator"
}
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String> {
let code = edit.compute_final_code();
let mut results = Vec::new();
let dangerous_patterns = [
("unsafe blocks", "unsafe", ValidationSeverity::Critical),
("raw pointer usage", "from_raw", ValidationSeverity::Error),
("transmute usage", "transmute", ValidationSeverity::Error),
("system calls", "Command::new", ValidationSeverity::Warning),
("sql injection risk", "sql", ValidationSeverity::Warning),
("eval usage", "eval", ValidationSeverity::Critical),
];
for (description, pattern, severity) in dangerous_patterns {
if code.contains(pattern) {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: format!("Security concern: {} detected", description),
severity,
});
}
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: true,
message: "No security concerns detected".to_string(),
severity: ValidationSeverity::Info,
});
}
Ok(results)
}
}
pub struct PerformanceValidator;
impl PerformanceValidator {
pub fn new() -> Self {
Self
}
}
impl Validator for PerformanceValidator {
fn name(&self) -> &str {
"performance_validator"
}
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String> {
let code = edit.compute_final_code();
let mut results = Vec::new();
if code.contains("clone()") && code.contains("loop") {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Performance concern: cloning in loop detected".to_string(),
severity: ValidationSeverity::Warning,
});
}
if code.contains("unwrap()") {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Robustness concern: unwrap() usage may cause panics".to_string(),
severity: ValidationSeverity::Warning,
});
}
if code.contains("expect(") {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Robustness concern: expect() usage may cause panics".to_string(),
severity: ValidationSeverity::Warning,
});
}
if code.contains("String::new()") && code.contains("push_str") {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message:
"Performance tip: consider using format! macro or Vec::join for string building"
.to_string(),
severity: ValidationSeverity::Info,
});
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: true,
message: "No performance issues detected".to_string(),
severity: ValidationSeverity::Info,
});
}
Ok(results)
}
}
pub struct StyleValidator;
impl StyleValidator {
pub fn new() -> Self {
Self
}
}
impl Validator for StyleValidator {
fn name(&self) -> &str {
"style_validator"
}
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String> {
let code = edit.compute_final_code();
let mut results = Vec::new();
if code.lines().any(|line| line.len() > 100) {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Style: lines longer than 100 characters detected".to_string(),
severity: ValidationSeverity::Info,
});
}
if code.contains(" ") && !code.contains("\t") {
if code.contains("\t") {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Style: mixed tabs and spaces detected".to_string(),
severity: ValidationSeverity::Warning,
});
}
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: true,
message: "Style validation passed".to_string(),
severity: ValidationSeverity::Info,
});
}
Ok(results)
}
}
pub struct ConsistencyValidator;
impl ConsistencyValidator {
pub fn new() -> Self {
Self
}
}
impl Validator for ConsistencyValidator {
fn name(&self) -> &str {
"consistency_validator"
}
fn validate(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>, String> {
let mut results = Vec::new();
let code = edit.compute_final_code();
if self.has_inconsistent_naming(&code) {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: false,
message: "Naming consistency: mixed naming conventions detected".to_string(),
severity: ValidationSeverity::Info,
});
}
if results.is_empty() {
results.push(ValidationResult {
validator_name: self.name().to_string(),
passed: true,
message: "Consistency check passed".to_string(),
severity: ValidationSeverity::Info,
});
}
Ok(results)
}
}
impl ConsistencyValidator {
fn has_inconsistent_naming(&self, code: &str) -> bool {
let has_snake_case = code.contains("_");
let has_camel_case = code.chars().any(|c| c.is_uppercase());
has_snake_case && has_camel_case && code.contains("fn ")
}
}
pub struct ConflictDetector {
active_edits: HashMap<String, Vec<ModifiableEdit>>,
}
impl ConflictDetector {
pub fn new() -> Self {
Self {
active_edits: HashMap::new(),
}
}
pub fn detect_conflicts(&self, edit: &ModifiableEdit) -> Vec<EditConflict> {
let mut conflicts = Vec::new();
if let Some(existing_edits) = self.active_edits.get(&edit.base_edit.file) {
for existing in existing_edits {
if self.ranges_overlap(edit.base_edit.line_range, existing.base_edit.line_range) {
conflicts.push(EditConflict {
conflict_type: ConflictType::OverlappingLines,
description: format!(
"Edit overlaps with existing edit at lines {}-{}",
existing.base_edit.line_range.0, existing.base_edit.line_range.1
),
severity: ConflictSeverity::High,
affected_files: vec![edit.base_edit.file.clone()],
});
}
}
}
conflicts.extend(self.detect_semantic_conflicts(edit));
conflicts
}
fn ranges_overlap(&self, range1: (usize, usize), range2: (usize, usize)) -> bool {
range1.0 <= range2.1 && range2.0 <= range1.1
}
fn detect_semantic_conflicts(&self, _edit: &ModifiableEdit) -> Vec<EditConflict> {
Vec::new()
}
pub fn register_edit(&mut self, edit: ModifiableEdit) {
let file = edit.base_edit.file.clone();
self.active_edits
.entry(file)
.or_insert_with(Vec::new)
.push(edit);
}
pub fn remove_edit(&mut self, file: &str, line_range: (usize, usize)) {
if let Some(edits) = self.active_edits.get_mut(file) {
edits.retain(|edit| edit.base_edit.line_range != line_range);
}
}
}
impl Default for ConflictDetector {
fn default() -> Self {
Self::new()
}
}
pub struct SyntaxChecker;
impl SyntaxChecker {
pub fn new() -> Self {
Self
}
pub fn check(&self, code: &str, filename: &str) -> Option<ValidationResult> {
let extension = std::path::Path::new(filename)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
match extension {
"rs" => Some(self.check_rust_basic(code)),
"py" => Some(self.check_python_basic(code)),
"js" | "ts" => Some(self.check_javascript_basic(code)),
_ => None,
}
}
pub fn check_fragment(&self, code: &str) -> Option<ValidationResult> {
let balanced_braces = self.check_balanced_braces(code);
if !balanced_braces {
Some(ValidationResult {
validator_name: "syntax_fragment".to_string(),
passed: false,
message: "Unbalanced braces in code fragment".to_string(),
severity: ValidationSeverity::Error,
})
} else {
None
}
}
fn check_rust_basic(&self, code: &str) -> ValidationResult {
let has_syntax_errors = code.contains("syntax error") || !self.check_balanced_braces(code);
ValidationResult {
validator_name: "rust_syntax_check".to_string(),
passed: !has_syntax_errors,
message: if has_syntax_errors {
"Rust syntax errors detected".to_string()
} else {
"Basic Rust syntax check passed".to_string()
},
severity: if has_syntax_errors {
ValidationSeverity::Error
} else {
ValidationSeverity::Info
},
}
}
fn check_python_basic(&self, code: &str) -> ValidationResult {
let has_syntax_errors = code.contains("syntax error");
ValidationResult {
validator_name: "python_syntax_check".to_string(),
passed: !has_syntax_errors,
message: if has_syntax_errors {
"Python syntax errors detected".to_string()
} else {
"Basic Python syntax check passed".to_string()
},
severity: if has_syntax_errors {
ValidationSeverity::Error
} else {
ValidationSeverity::Info
},
}
}
fn check_javascript_basic(&self, code: &str) -> ValidationResult {
let has_syntax_errors = code.contains("syntax error") || !self.check_balanced_braces(code);
ValidationResult {
validator_name: "javascript_syntax_check".to_string(),
passed: !has_syntax_errors,
message: if has_syntax_errors {
"JavaScript syntax errors detected".to_string()
} else {
"Basic JavaScript syntax check passed".to_string()
},
severity: if has_syntax_errors {
ValidationSeverity::Error
} else {
ValidationSeverity::Info
},
}
}
fn check_balanced_braces(&self, code: &str) -> bool {
let mut stack = Vec::new();
let pairs = [('(', ')'), ('[', ']'), ('{', '}')];
for ch in code.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' | ']' | '}' => {
if let Some(&open) = stack.last() {
if pairs.iter().any(|&(o, c)| o == open && c == ch) {
stack.pop();
} else {
return false;
}
} else {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
}
impl Default for SyntaxChecker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ValidationSummary {
pub total_validations: usize,
pub passed: usize,
pub warnings: usize,
pub errors: usize,
pub critical: usize,
pub conflicts: Vec<EditConflict>,
pub overall_status: ValidationStatus,
}
impl ValidationSummary {
pub fn new(results: Vec<ValidationResult>, conflicts: Vec<EditConflict>) -> Self {
let total = results.len();
let passed = results.iter().filter(|r| r.passed).count();
let warnings = results
.iter()
.filter(|r| r.severity == ValidationSeverity::Warning)
.count();
let errors = results
.iter()
.filter(|r| r.severity == ValidationSeverity::Error)
.count();
let critical = results
.iter()
.filter(|r| r.severity == ValidationSeverity::Critical)
.count();
let overall_status = if critical > 0 {
ValidationStatus::Failed
} else if errors > 0 {
ValidationStatus::ErrorsFound
} else if warnings > 0 {
ValidationStatus::WarningsFound
} else {
ValidationStatus::Passed
};
Self {
total_validations: total,
passed,
warnings,
errors,
critical,
conflicts,
overall_status,
}
}
pub fn is_passing(&self) -> bool {
matches!(
self.overall_status,
ValidationStatus::Passed | ValidationStatus::WarningsFound
)
}
pub fn has_blocking_issues(&self) -> bool {
self.critical > 0 || self.errors > 0
}
}
#[derive(Debug, Clone)]
pub struct EditConflict {
pub conflict_type: ConflictType,
pub description: String,
pub severity: ConflictSeverity,
pub affected_files: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum ConflictType {
OverlappingLines,
DependencyConflict,
StyleConflict,
SemanticConflict,
}
#[derive(Debug, Clone)]
pub enum ConflictSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone)]
pub enum ValidationStatus {
Passed,
WarningsFound,
ErrorsFound,
Failed,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::gpt4_agent::ProposedEdit;
fn create_test_edit(code: &str) -> ModifiableEdit {
let proposed = ProposedEdit {
file: "test.rs".to_string(),
line_range: (10, 15),
new_code: code.to_string(),
reason: "Test function".to_string(),
confidence: 0.9,
};
ModifiableEdit::from_proposed_edit(proposed)
}
#[test]
fn test_validator_creation() {
let validator = EditValidator::new();
assert_eq!(validator.validators.len(), 5);
}
#[test]
fn test_syntax_validation() {
let validator = SyntaxValidator::new();
let mut edit = create_test_edit("fn test() {\n println!(\"Hello\");\n}");
let results = validator.validate(&mut edit).unwrap();
assert!(!results.is_empty());
assert!(results.iter().any(|r| r.validator_name == "rust_syntax"));
}
#[test]
fn test_security_validation() {
let validator = SecurityValidator::new();
let mut edit = create_test_edit("unsafe { transmute(ptr) }");
let results = validator.validate(&mut edit).unwrap();
let security_issues = results.iter().filter(|r| !r.passed).count();
assert!(security_issues > 0);
}
#[test]
fn test_performance_validation() {
let validator = PerformanceValidator::new();
let mut edit = create_test_edit("loop {\n let x = data.clone();\n}");
let results = validator.validate(&mut edit).unwrap();
let has_clone_warning = results
.iter()
.any(|r| r.message.contains("cloning in loop"));
assert!(has_clone_warning);
}
#[test]
fn test_validation_summary() {
let results = vec![
ValidationResult {
validator_name: "test1".to_string(),
passed: true,
message: "OK".to_string(),
severity: ValidationSeverity::Info,
},
ValidationResult {
validator_name: "test2".to_string(),
passed: false,
message: "Warning".to_string(),
severity: ValidationSeverity::Warning,
},
];
let summary = ValidationSummary::new(results, vec![]);
assert_eq!(summary.total_validations, 2);
assert_eq!(summary.passed, 1);
assert_eq!(summary.warnings, 1);
assert!(summary.is_passing());
}
#[test]
fn test_conflict_detection() {
let detector = ConflictDetector::new();
let edit = create_test_edit("fn test() {}");
let conflicts = detector.detect_conflicts(&edit);
assert!(conflicts.is_empty()); }
#[test]
fn test_syntax_checker() {
let checker = SyntaxChecker::new();
assert!(checker.check_balanced_braces("fn test() { println!(\"hello\"); }"));
assert!(!checker.check_balanced_braces("fn test() { println!(\"hello\");"));
let result = checker.check_fragment("{ incomplete");
assert!(result.is_some());
assert!(!result.unwrap().passed);
}
#[test]
fn test_incremental_validation() {
let validator = EditValidator::new();
let edit = create_test_edit("fn test() {}");
let results = validator.validate_incremental(&edit, "unsafe { code }");
let security_warnings = results.iter().filter(|r| !r.passed).count();
assert!(security_warnings > 0);
}
}