use crate::error::{Result, SpliceError};
use std::path::Path;
use tree_sitter::Parser;
pub fn validate_syntax(file_path: &Path, source: &[u8]) -> Result<bool> {
let language = detect_language(file_path)?;
if language.is_none() {
log::debug!(
"Unknown language for {:?}, skipping syntax validation",
file_path
);
return Ok(true);
}
let language = language.unwrap();
let mut parser = Parser::new();
parser
.set_language(&language)
.map_err(|e| SpliceError::Parse {
file: file_path.to_path_buf(),
message: format!("Failed to set language: {:?}", e),
})?;
let tree = parser.parse(source, None);
match tree {
Some(tree) => {
let has_errors = has_parse_errors(tree.root_node());
if has_errors {
log::warn!("Syntax errors detected in {:?}", file_path);
}
Ok(!has_errors)
}
None => {
log::warn!("Parse failed for {:?}", file_path);
Ok(false)
}
}
}
fn detect_language(file_path: &Path) -> Result<Option<tree_sitter::Language>> {
let extension = file_path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
match extension {
"rs" => Ok(Some(tree_sitter_rust::language())),
"py" => Ok(Some(tree_sitter_python::language())),
"c" | "h" => Ok(Some(tree_sitter_c::language())),
"cpp" | "cc" | "cxx" | "hpp" => Ok(Some(tree_sitter_cpp::language())),
"js" | "mjs" => Ok(Some(tree_sitter_javascript::language())),
"ts" => Ok(Some(tree_sitter_typescript::language_tsx())),
"tsx" => Ok(Some(tree_sitter_typescript::language_tsx())),
"java" => Ok(Some(tree_sitter_java::language())),
_ => Ok(None),
}
}
fn has_parse_errors(node: tree_sitter::Node) -> bool {
if node.kind() == "ERROR" || node.is_missing() {
return true;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if has_parse_errors(child) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
#[test]
fn test_valid_rust_syntax() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "fn main() {{ }}").unwrap();
let source = b"fn main() {}";
let result = validate_syntax(&file_path, source).unwrap();
assert!(result, "Valid Rust syntax should pass validation");
}
#[test]
fn test_invalid_rust_syntax() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let source = b"fn main() {";
let result = validate_syntax(&file_path, source).unwrap();
assert!(!result, "Invalid Rust syntax should fail validation");
}
#[test]
fn test_unknown_language() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.xyz");
let source = b"some code";
let result = validate_syntax(&file_path, source).unwrap();
assert!(result, "Unknown language should pass (skip validation)");
}
}