Add CLI and services modules
This commit is contained in:
177
src/codexchange/utils/syntax_check.py
Normal file
177
src/codexchange/utils/syntax_check.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""Syntax verification utilities for CodeXchange CLI."""
|
||||
|
||||
import ast
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Tuple
|
||||
|
||||
from codexchange.models import Language
|
||||
|
||||
|
||||
def check_python_syntax(code: str) -> Tuple[bool, list[str]]:
|
||||
"""Check Python syntax using ast.parse.
|
||||
|
||||
Args:
|
||||
code: Python code to check.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_message).
|
||||
"""
|
||||
try:
|
||||
ast.parse(code)
|
||||
return True, []
|
||||
except SyntaxError as e:
|
||||
return False, [f"Syntax error at line {e.lineno}: {e.msg}"]
|
||||
|
||||
|
||||
def check_typescript_syntax(code: str) -> Tuple[bool, list[str]]:
|
||||
"""Check TypeScript syntax using tsc --noEmit.
|
||||
|
||||
Args:
|
||||
code: TypeScript code to check.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, list of warnings/errors).
|
||||
"""
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
warnings = []
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f:
|
||||
f.write(code)
|
||||
temp_file = f.name
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["npx", "tsc", "--noEmit", "--skipLibCheck", temp_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
for line in result.stderr.split("\n"):
|
||||
line = line.strip()
|
||||
if line and "error" in line.lower():
|
||||
warnings.append(line)
|
||||
|
||||
if not warnings:
|
||||
warnings = result.stderr.strip().split("\n")
|
||||
warnings = [w for w in warnings if w.strip()]
|
||||
|
||||
except FileNotFoundError:
|
||||
warnings.append("TypeScript compiler (tsc) not found. Install Node.js and TypeScript.")
|
||||
except subprocess.TimeoutExpired:
|
||||
warnings.append("TypeScript compilation timed out.")
|
||||
except Exception as e:
|
||||
warnings.append(f"TypeScript check failed: {str(e)}")
|
||||
finally:
|
||||
os.unlink(temp_file)
|
||||
|
||||
return len(warnings) == 0, warnings
|
||||
|
||||
|
||||
def check_javascript_syntax(code: str) -> Tuple[bool, list[str]]:
|
||||
"""Check JavaScript syntax using ESLint or basic parsing.
|
||||
|
||||
Args:
|
||||
code: JavaScript code to check.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, list of warnings/errors).
|
||||
"""
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
warnings = []
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
|
||||
f.write(code)
|
||||
temp_file = f.name
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["npx", "eslint", "--format", "compact", temp_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
for line in result.stderr.split("\n"):
|
||||
line = line.strip()
|
||||
if line:
|
||||
warnings.append(line)
|
||||
|
||||
output = result.stdout.strip()
|
||||
if output:
|
||||
warnings.extend(output.split("\n"))
|
||||
|
||||
except FileNotFoundError:
|
||||
warnings.append("ESLint not found. Install Node.js and ESLint for JavaScript validation.")
|
||||
except subprocess.TimeoutExpired:
|
||||
warnings.append("ESLint check timed out.")
|
||||
except Exception as e:
|
||||
warnings.append(f"JavaScript check failed: {str(e)}")
|
||||
finally:
|
||||
os.unlink(temp_file)
|
||||
|
||||
return len(warnings) == 0, warnings
|
||||
|
||||
|
||||
def check_java_syntax(code: str) -> Tuple[bool, list[str]]:
|
||||
"""Check Java syntax (basic validation).
|
||||
|
||||
Args:
|
||||
code: Java code to check.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, list of warnings/errors).
|
||||
"""
|
||||
warnings = []
|
||||
|
||||
if not code.strip():
|
||||
return False, ["Empty Java file"]
|
||||
|
||||
class_name_pattern = r"public\s+class\s+(\w+)"
|
||||
matches = re.findall(class_name_pattern, code)
|
||||
|
||||
if not matches:
|
||||
warnings.append("No public class found in Java file")
|
||||
|
||||
brace_count = code.count("{") - code.count("}")
|
||||
if brace_count != 0:
|
||||
warnings.append("Mismatched braces in Java file")
|
||||
|
||||
if code.count("(") != code.count(")"):
|
||||
warnings.append("Mismatched parentheses in Java file")
|
||||
|
||||
return len(warnings) == 0, warnings
|
||||
|
||||
|
||||
def verify_syntax(code: str, language: Language) -> Tuple[bool, list[str]]:
|
||||
"""Verify syntax for the given language.
|
||||
|
||||
Args:
|
||||
code: Source code to verify.
|
||||
language: Programming language of the code.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, list of warnings/errors).
|
||||
"""
|
||||
if not code or not code.strip():
|
||||
return False, ["Empty code input"]
|
||||
|
||||
checkers = {
|
||||
Language.PYTHON: check_python_syntax,
|
||||
Language.TYPESCRIPT: check_typescript_syntax,
|
||||
Language.JAVASCRIPT: check_javascript_syntax,
|
||||
Language.JAVA: check_java_syntax,
|
||||
}
|
||||
|
||||
checker = checkers.get(language)
|
||||
if not checker:
|
||||
return True, []
|
||||
|
||||
return checker(code)
|
||||
Reference in New Issue
Block a user