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