Spaces:
Sleeping
Sleeping
| """ | |
| VoiceForge Syntax & Import Checker | |
| ----------------------------------- | |
| Validates all Python files for: | |
| - Syntax errors (AST parsing) | |
| - Circular import detection | |
| - Missing __init__.py files | |
| - Undefined imports | |
| """ | |
| import os | |
| import ast | |
| import sys | |
| from pathlib import Path | |
| from collections import defaultdict | |
| def check_syntax(file_path: Path) -> tuple[bool, str]: | |
| """Check if a Python file has valid syntax""" | |
| try: | |
| with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
| source = f.read() | |
| ast.parse(source) | |
| return True, "" | |
| except SyntaxError as e: | |
| return False, f"Line {e.lineno}: {e.msg}" | |
| def check_init_files(root_dir: Path) -> list[Path]: | |
| """Find directories missing __init__.py""" | |
| missing = [] | |
| for dir_path in root_dir.rglob("*"): | |
| if dir_path.is_dir() and '__pycache__' not in str(dir_path): | |
| py_files = list(dir_path.glob("*.py")) | |
| init_file = dir_path / "__init__.py" | |
| if py_files and not init_file.exists(): | |
| missing.append(dir_path) | |
| return missing | |
| def build_import_graph(root_dir: Path) -> dict[str, list[str]]: | |
| """Build a graph of module imports""" | |
| graph = defaultdict(list) | |
| for py_file in root_dir.rglob("*.py"): | |
| if '__pycache__' in str(py_file): | |
| continue | |
| try: | |
| with open(py_file, 'r', encoding='utf-8', errors='ignore') as f: | |
| source = f.read() | |
| tree = ast.parse(source) | |
| module_name = py_file.stem | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.ImportFrom) and node.module: | |
| if node.module.startswith('app.'): | |
| graph[module_name].append(node.module.split('.')[-1]) | |
| except Exception: | |
| pass | |
| return graph | |
| def detect_circular_imports(graph: dict) -> list[tuple]: | |
| """Detect circular imports in the dependency graph""" | |
| cycles = [] | |
| visited = set() | |
| rec_stack = set() | |
| def dfs(node, path): | |
| visited.add(node) | |
| rec_stack.add(node) | |
| for neighbor in graph.get(node, []): | |
| if neighbor not in visited: | |
| if dfs(neighbor, path + [neighbor]): | |
| return True | |
| elif neighbor in rec_stack: | |
| cycle = path[path.index(neighbor):] + [neighbor] | |
| cycles.append(tuple(cycle)) | |
| return True | |
| rec_stack.remove(node) | |
| return False | |
| for node in graph: | |
| if node not in visited: | |
| dfs(node, [node]) | |
| return cycles | |
| def run_checks(root_dir: str = "app"): | |
| """Run all syntax and import checks""" | |
| print("=" * 60) | |
| print("🔧 VoiceForge Syntax & Import Checker") | |
| print("=" * 60) | |
| root = Path(root_dir) | |
| if not root.exists(): | |
| print(f"❌ Directory not found: {root_dir}") | |
| sys.exit(1) | |
| all_files = [f for f in root.rglob("*.py") if '__pycache__' not in str(f)] | |
| print(f"\n📁 Checking {len(all_files)} Python files...\n") | |
| syntax_errors = [] | |
| # Check syntax | |
| print("1️⃣ SYNTAX CHECK") | |
| print("-" * 40) | |
| for py_file in all_files: | |
| valid, error = check_syntax(py_file) | |
| if not valid: | |
| syntax_errors.append((py_file.relative_to(root), error)) | |
| print(f" ❌ {py_file.relative_to(root)}: {error}") | |
| if not syntax_errors: | |
| print(" ✅ All files have valid syntax!") | |
| # Check __init__.py | |
| print("\n2️⃣ MISSING __init__.py") | |
| print("-" * 40) | |
| missing_inits = check_init_files(root) | |
| if missing_inits: | |
| for dir_path in missing_inits: | |
| print(f" ⚠️ {dir_path.relative_to(root)}") | |
| else: | |
| print(" ✅ All packages have __init__.py!") | |
| # Check circular imports | |
| print("\n3️⃣ CIRCULAR IMPORT DETECTION") | |
| print("-" * 40) | |
| graph = build_import_graph(root) | |
| cycles = detect_circular_imports(graph) | |
| if cycles: | |
| for cycle in cycles[:5]: | |
| print(f" ⚠️ Cycle: {' → '.join(cycle)}") | |
| else: | |
| print(" ✅ No circular imports detected!") | |
| print("\n" + "=" * 60) | |
| if syntax_errors: | |
| print("❌ Syntax Check: FAILED") | |
| return 1 | |
| else: | |
| print("✅ Syntax Check: PASSED") | |
| return 0 | |
| if __name__ == "__main__": | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Check VoiceForge syntax and imports") | |
| parser.add_argument("--path", default="app", help="Root directory to check") | |
| args = parser.parse_args() | |
| sys.exit(run_checks(args.path)) | |