Some syntax errors fixed. Syntax checking added. Release checklist created.

This commit is contained in:
Storm Dragon
2025-07-24 13:52:10 -04:00
parent b6a9e1a692
commit 8c233e0385
15 changed files with 1867 additions and 13 deletions

236
tools/validate_syntax.py Executable file
View File

@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""
Fenrir Syntax Validation Tool
Validates Python syntax across the entire Fenrir codebase without writing
cache files. Designed to catch syntax errors before packaging or releases.
Usage:
python3 tools/validate_syntax.py # Validate all Python files
python3 tools/validate_syntax.py --fix # Fix common issues automatically
python3 tools/validate_syntax.py --check-only # Exit with non-zero if errors found
"""
import ast
import os
import sys
import argparse
import tempfile
from pathlib import Path
class SyntaxValidator:
def __init__(self):
self.errors = []
self.warnings = []
self.fixed = []
def validate_file(self, filepath):
"""Validate syntax of a single Python file."""
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Parse with AST (catches syntax errors)
ast.parse(content, filename=str(filepath))
return True, content
except SyntaxError as e:
error_msg = f"{filepath}:{e.lineno}: {e.msg}"
self.errors.append((filepath, e, content))
return False, content
except UnicodeDecodeError as e:
error_msg = f"{filepath}: Unicode decode error: {e}"
self.errors.append((filepath, e, None))
return False, None
except Exception as e:
error_msg = f"{filepath}: Unexpected error: {e}"
self.errors.append((filepath, e, None))
return False, None
def fix_common_issues(self, filepath, content):
"""Attempt to fix common syntax issues automatically."""
if not content:
return False, content
original_content = content
fixed_issues = []
# Fix unterminated f-strings (the main issue from the email)
lines = content.split('\n')
modified = False
for i, line in enumerate(lines):
# Look for f-strings that span multiple lines incorrectly
if 'f"' in line and line.count('"') % 2 == 1:
# Check if this looks like a broken multi-line f-string
indent = len(line) - len(line.lstrip())
# Look ahead for continuation
j = i + 1
while j < len(lines) and lines[j].strip():
next_line = lines[j]
next_indent = len(next_line) - len(next_line.lstrip())
# If next line is indented more and has closing brace/quote
if (next_indent > indent and
('"' in next_line or '}' in next_line)):
# Try to fix by joining lines properly
combined_line = line.rstrip()
continuation = next_line.strip()
if continuation.startswith(('"', '}', 'str(e)', 'self.', 'fenrirVersion.')):
# Fix common patterns
if 'str(e)}' in continuation:
fixed_line = line.replace('f"', 'f"').rstrip() + '{' + continuation.replace('"', '') + '}'
elif continuation.startswith('"'):
fixed_line = line + continuation
else:
fixed_line = line.rstrip() + continuation
lines[i] = fixed_line
lines[j] = '' # Remove the continuation line
modified = True
fixed_issues.append(f"Line {i+1}: Fixed multi-line f-string")
break
j += 1
if modified:
content = '\n'.join(lines)
# Clean up empty lines that were created
content = '\n'.join(line for line in content.split('\n') if line.strip() or not line)
# Verify the fix worked
try:
ast.parse(content, filename=str(filepath))
self.fixed.append((filepath, fixed_issues))
return True, content
except SyntaxError:
# Fix didn't work, return original
return False, original_content
return False, content
def scan_directory(self, directory, fix_mode=False):
"""Scan directory for Python files and validate them."""
python_files = []
# Find all Python files
for root, dirs, files in os.walk(directory):
# Skip cache and build directories
dirs[:] = [d for d in dirs if not d.startswith(('__pycache__', '.git', 'build', 'dist'))]
for file in files:
if file.endswith('.py'):
python_files.append(Path(root) / file)
print(f"Validating {len(python_files)} Python files...")
valid_count = 0
fixed_count = 0
for filepath in sorted(python_files):
is_valid, content = self.validate_file(filepath)
if is_valid:
valid_count += 1
print(f"{filepath}")
else:
print(f"{filepath}")
if fix_mode and content:
# Try to fix the file
was_fixed, fixed_content = self.fix_common_issues(filepath, content)
if was_fixed:
# Write the fixed content back
with open(filepath, 'w', encoding='utf-8') as f:
f.write(fixed_content)
print(f" → Fixed automatically")
fixed_count += 1
# Re-validate
is_valid_now, _ = self.validate_file(filepath)
if is_valid_now:
valid_count += 1
return valid_count, len(python_files), fixed_count
def print_summary(self, valid_count, total_count, fixed_count=0):
"""Print validation summary."""
print(f"\n{'='*60}")
print(f"SYNTAX VALIDATION SUMMARY")
print(f"{'='*60}")
print(f"Valid files: {valid_count}/{total_count}")
print(f"Invalid files: {total_count - valid_count}")
if fixed_count > 0:
print(f"Auto-fixed: {fixed_count}")
if self.errors:
print(f"\nERRORS ({len(self.errors)}):")
for filepath, error, _ in self.errors:
if isinstance(error, SyntaxError):
print(f" {filepath}:{error.lineno}: {error.msg}")
else:
print(f" {filepath}: {error}")
if self.fixed:
print(f"\nAUTO-FIXES APPLIED ({len(self.fixed)}):")
for filepath, fixes in self.fixed:
print(f" {filepath}:")
for fix in fixes:
print(f" - {fix}")
success_rate = (valid_count / total_count) * 100 if total_count > 0 else 0
print(f"\nSuccess rate: {success_rate:.1f}%")
return len(self.errors) == 0
def main():
parser = argparse.ArgumentParser(description='Validate Python syntax in Fenrir codebase')
parser.add_argument('--fix', action='store_true',
help='Attempt to fix common syntax issues automatically')
parser.add_argument('--check-only', action='store_true',
help='Exit with non-zero code if syntax errors found')
parser.add_argument('--directory', default='src/fenrirscreenreader',
help='Directory to scan (default: src/fenrirscreenreader)')
args = parser.parse_args()
# Find project root
script_dir = Path(__file__).parent
project_root = script_dir.parent
target_dir = project_root / args.directory
if not target_dir.exists():
print(f"Error: Directory {target_dir} does not exist")
sys.exit(1)
print(f"Fenrir Syntax Validator")
print(f"Target directory: {target_dir}")
print(f"Fix mode: {'ON' if args.fix else 'OFF'}")
print()
validator = SyntaxValidator()
valid_count, total_count, fixed_count = validator.scan_directory(target_dir, fix_mode=args.fix)
all_valid = validator.print_summary(valid_count, total_count, fixed_count)
if args.check_only and not all_valid:
print(f"\nValidation failed: {total_count - valid_count} files have syntax errors")
sys.exit(1)
elif not all_valid:
print(f"\nWarning: {total_count - valid_count} files have syntax errors")
if not args.fix:
print("Run with --fix to attempt automatic fixes")
sys.exit(1)
else:
print(f"\n✓ All {total_count} files have valid syntax")
sys.exit(0)
if __name__ == '__main__':
main()