""" 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))