#!/usr/bin/env python3 """ JavaScript syntax validation tool for MarkiTect build process. This tool validates that all generated JavaScript in edit mode is syntactically correct, preventing the regression that caused edit mode to fail completely. """ import subprocess import tempfile import sys import re from pathlib import Path from typing import List, Tuple, Optional class JavaScriptValidator: """Validates JavaScript syntax in MarkiTect templates.""" def __init__(self): self.errors = [] self.warnings = [] def validate_document_manager_templates(self) -> bool: """Validate JavaScript templates in DocumentManager.""" try: # Import the template generation method directly to avoid database dependency from markitect.document_manager import DocumentManager # Create a mock DocumentManager to access the template method class MockDatabaseManager: pass doc_manager = DocumentManager.__new__(DocumentManager) doc_manager.database_manager = MockDatabaseManager() # Test various configurations test_cases = [ { 'title': 'Basic Edit Mode', 'markdown': '# Test\n\nBasic content.', 'edit_mode': True, 'theme': 'github' }, { 'title': 'Complex Content', 'markdown': '''# Header 1 ## Header 2 Paragraph with **bold** and *italic* text. - List item 1 - List item 2 1. Numbered item 2. Another item > Blockquote text ```python code block ``` Final paragraph.''', 'edit_mode': True, 'theme': 'dark' }, { 'title': 'Special Characters', 'markdown': "# Test 'quotes' and \"double quotes\"\n\nContent with $pecial ch@racters & symbols!", 'edit_mode': True, 'theme': 'github' } ] all_valid = True for test_case in test_cases: print(f"Validating: {test_case['title']}") html_content = doc_manager._generate_html_template( title=test_case['title'], markdown_content=test_case['markdown'], edit_mode=test_case['edit_mode'], editor_theme=test_case.get('theme', 'github') ) is_valid, errors = self._validate_html_javascript(html_content, test_case['title']) if not is_valid: all_valid = False self.errors.extend(errors) else: print(f" āœ… {test_case['title']} - JavaScript syntax valid") return all_valid except Exception as e: self.errors.append(f"Failed to validate document manager templates: {e}") return False def _validate_html_javascript(self, html_content: str, context: str) -> Tuple[bool, List[str]]: """Extract and validate JavaScript from HTML content.""" errors = [] # Extract all JavaScript blocks js_blocks = re.findall(r']*>(.*?)', html_content, re.DOTALL) if not js_blocks: errors.append(f"{context}: No JavaScript blocks found") return False, errors for i, js_content in enumerate(js_blocks): # Skip empty blocks or blocks with only external src if not js_content.strip() or 'src=' in js_content: continue is_valid, js_errors = self._validate_javascript_syntax(js_content, f"{context} block {i+1}") if not is_valid: errors.extend(js_errors) return len(errors) == 0, errors def _validate_javascript_syntax(self, js_content: str, context: str) -> Tuple[bool, List[str]]: """Validate JavaScript syntax using Node.js.""" errors = [] try: with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f: f.write(js_content) temp_js_path = f.name # Use Node.js to check syntax result = subprocess.run( ['node', '-c', temp_js_path], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: errors.append(f"{context}: JavaScript syntax error - {result.stderr.strip()}") # Add helpful debugging info lines = js_content.split('\n') for line_num, line in enumerate(lines[:20], 1): # Show first 20 lines if line.strip(): print(f" Line {line_num}: {line[:100]}") # First 100 chars Path(temp_js_path).unlink(missing_ok=True) except subprocess.TimeoutExpired: errors.append(f"{context}: JavaScript validation timeout") except FileNotFoundError: errors.append(f"{context}: Node.js not available for validation") except Exception as e: errors.append(f"{context}: Validation error - {e}") return len(errors) == 0, errors def check_common_issues(self) -> bool: """Check for common JavaScript issues that have caused problems.""" try: from markitect.document_manager import DocumentManager # Create a mock DocumentManager to access the template method class MockDatabaseManager: pass doc_manager = DocumentManager.__new__(DocumentManager) doc_manager.database_manager = MockDatabaseManager() html_content = doc_manager._generate_html_template( title="Issue Check", markdown_content="# Test\n\nTest content.", edit_mode=True ) js_match = re.search(r'', html_content, re.DOTALL) if not js_match: self.errors.append("No JavaScript found for common issues check") return False js_content = js_match.group(1) issues_found = False # Check for specific issues that have caused problems issue_patterns = [ (r"'\s*\n\s*'", "Broken string literal across lines"), (r'"\s*\n\s*"', "Broken string literal across lines"), (r'} else if.*{{.*}} else if.*{[^{]', "Inconsistent brace escaping"), (r'\$\{\{.*?\}\}', "Double-escaped template literals"), (r'reconstructed \+= .*\'\n', "Unescaped newline in string concatenation"), ] for pattern, description in issue_patterns: matches = re.findall(pattern, js_content) if matches: self.errors.append(f"Found {description}: {matches[:3]}...") # Show first 3 matches issues_found = True # Check for required elements required_elements = [ 'MarkitectEditor', 'updateStatus', 'makeContentEditable', 'DOMContentLoaded' ] for element in required_elements: if element not in js_content: self.errors.append(f"Missing required element: {element}") issues_found = True return not issues_found except Exception as e: self.errors.append(f"Failed to check common issues: {e}") return False def validate_all(self) -> bool: """Run all validation checks.""" print("šŸ” Validating JavaScript syntax in MarkiTect templates...") all_checks_passed = True # Run all validation checks checks = [ ("Document Manager Templates", self.validate_document_manager_templates), ("Common Issues", self.check_common_issues), ] for check_name, check_func in checks: print(f"\nšŸ“‹ Running {check_name} check...") try: if not check_func(): all_checks_passed = False print(f" āŒ {check_name} check failed") else: print(f" āœ… {check_name} check passed") except Exception as e: all_checks_passed = False self.errors.append(f"{check_name} check failed with exception: {e}") print(f" āŒ {check_name} check failed with exception") return all_checks_passed def report_results(self) -> None: """Print validation results.""" print("\n" + "="*60) print("JavaScript Validation Results") print("="*60) if self.errors: print(f"\nāŒ {len(self.errors)} Error(s) Found:") for i, error in enumerate(self.errors, 1): print(f" {i}. {error}") if self.warnings: print(f"\nāš ļø {len(self.warnings)} Warning(s):") for i, warning in enumerate(self.warnings, 1): print(f" {i}. {warning}") if not self.errors and not self.warnings: print("\nāœ… All JavaScript validation checks passed!") print("\n" + "="*60) def main(): """Main validation function.""" validator = JavaScriptValidator() success = validator.validate_all() validator.report_results() return 0 if success else 1 if __name__ == "__main__": exit_code = main() sys.exit(exit_code)