494 lines
18 KiB
Python
494 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
FastLED JavaScript Type Enhancement and Linting Strategy Script
|
|
|
|
This script provides multiple approaches for enhancing JavaScript code quality
|
|
while keeping files in .js format. Uses fast Node.js + ESLint for linting.
|
|
|
|
1. Linting Analysis - Analyze current ESLint issues and provide fixes
|
|
2. Type Safety - Add comprehensive JSDoc annotations and type checking
|
|
3. Performance - Identify and fix performance issues (await in loops, etc.)
|
|
4. Code Quality - Consistent formatting and best practices
|
|
5. Config Management - Create ESLint configuration variants
|
|
|
|
Usage:
|
|
uv run scripts/enhance-js-typing.py --approach linting
|
|
uv run scripts/enhance-js-typing.py --approach performance
|
|
uv run scripts/enhance-js-typing.py --approach types --file src/platforms/wasm/compiler/index.js
|
|
uv run scripts/enhance-js-typing.py --approach configs
|
|
uv run scripts/enhance-js-typing.py --approach summary
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import List, Dict, Match, TypedDict, Union
|
|
|
|
class LintIssue(TypedDict):
|
|
"""Type definition for a linting issue"""
|
|
rule: str
|
|
message: str
|
|
file: str
|
|
line: int
|
|
severity: int
|
|
|
|
class LintResult(TypedDict, total=False):
|
|
"""Type definition for lint analysis result"""
|
|
issues: List[LintIssue]
|
|
summary: str
|
|
raw_output: str
|
|
error: str
|
|
|
|
class JSLintingEnhancer:
|
|
"""Comprehensive JavaScript linting and type enhancement tool"""
|
|
|
|
def __init__(self) -> None:
|
|
self.workspace_root: Path = Path.cwd()
|
|
self.wasm_dir: Path = self.workspace_root / "src" / "platforms" / "wasm"
|
|
self.js_files: List[Path] = []
|
|
self._discover_files()
|
|
|
|
def _discover_files(self) -> None:
|
|
"""Discover all JavaScript files in the WASM directory"""
|
|
if self.wasm_dir.exists():
|
|
self.js_files = list(self.wasm_dir.rglob("*.js"))
|
|
|
|
def analyze_current_linting(self) -> LintResult:
|
|
"""Analyze current linting issues using fast ESLint"""
|
|
print("🔍 Analyzing current linting issues...")
|
|
|
|
try:
|
|
# Check if fast linting is available
|
|
import platform
|
|
eslint_exe = ".cache/js-tools/node_modules/.bin/eslint.cmd" if platform.system() == "Windows" else ".cache/js-tools/node_modules/.bin/eslint"
|
|
|
|
if not Path(eslint_exe).exists():
|
|
return LintResult(issues=[], summary="Fast linting not available. Run: uv run ci/setup-js-linting-fast.py", error="eslint_not_found")
|
|
|
|
# Run ESLint with JSON output
|
|
result = subprocess.run(
|
|
[eslint_exe, "--format", "json", "--no-eslintrc", "--no-inline-config", "-c", ".cache/js-tools/.eslintrc.js",
|
|
"src/platforms/wasm/compiler/*.js", "src/platforms/wasm/compiler/modules/*.js"],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=self.workspace_root,
|
|
shell=platform.system() == "Windows"
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
print("✅ No linting issues found!")
|
|
return LintResult(issues=[], summary="clean", raw_output="", error="")
|
|
|
|
# Parse ESLint JSON output
|
|
issues: List[LintIssue] = []
|
|
if result.stdout:
|
|
try:
|
|
lint_data = json.loads(result.stdout)
|
|
for file_result in lint_data:
|
|
for message in file_result.get('messages', []):
|
|
issues.append(LintIssue(
|
|
rule=message.get('ruleId', 'unknown'),
|
|
message=message.get('message', ''),
|
|
file=file_result.get('filePath', ''),
|
|
line=message.get('line', 0),
|
|
severity=message.get('severity', 1)
|
|
))
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
return LintResult(
|
|
issues=issues,
|
|
summary=f"Found {len(issues)} linting issues",
|
|
raw_output=result.stdout,
|
|
error=""
|
|
)
|
|
except Exception as e:
|
|
print(f"❌ Error running linter: {e}")
|
|
return LintResult(issues=[], summary="error", raw_output="", error=str(e))
|
|
|
|
def _parse_text_lint_output(self, output: str) -> List[LintIssue]:
|
|
"""Parse text-based lint output into structured format"""
|
|
issues: List[LintIssue] = []
|
|
lines = output.split('\n')
|
|
|
|
current_issue = {}
|
|
for line in lines:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
|
|
# Look for rule violations
|
|
if line.startswith('(') and ')' in line:
|
|
rule_match = re.match(r'\(([^)]+)\)\s*(.*)', line)
|
|
if rule_match:
|
|
rule_name = rule_match.group(1)
|
|
message = rule_match.group(2)
|
|
current_issue: Dict[str, Union[str, int]] = {
|
|
"rule": rule_name,
|
|
"message": message,
|
|
"file": "",
|
|
"line": 0,
|
|
"severity": 1
|
|
}
|
|
elif 'at /' in line:
|
|
# Extract file and line info
|
|
match = re.search(r'at\s+([^:]+):(\d+)', line)
|
|
if match and current_issue:
|
|
issues.append(LintIssue(
|
|
rule=str(current_issue["rule"]),
|
|
message=str(current_issue["message"]),
|
|
file=match.group(1),
|
|
line=int(match.group(2)),
|
|
severity=1
|
|
))
|
|
current_issue = {}
|
|
|
|
return issues
|
|
|
|
def fix_await_in_loop_issues(self) -> List[Dict[str, Union[str, int]]]:
|
|
"""Identify and provide fixes for await-in-loop issues"""
|
|
print("🔧 Analyzing await-in-loop issues...")
|
|
|
|
fixes: List[Dict[str, Union[str, int]]] = []
|
|
for js_file in self.js_files:
|
|
try:
|
|
content = js_file.read_text()
|
|
lines = content.split('\n')
|
|
|
|
for i, line in enumerate(lines, 1):
|
|
if 'await' in line and ('for' in line or 'while' in line):
|
|
# Simple heuristic for await in loop
|
|
context = '\n'.join(lines[max(0, i-3):i+2])
|
|
fixes.append({
|
|
"file": str(js_file.relative_to(self.workspace_root)),
|
|
"line": i,
|
|
"issue": "await in loop",
|
|
"context": context,
|
|
"suggestion": "Consider using Promise.all() for parallel execution"
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f"Warning: Could not analyze {js_file}: {e}")
|
|
|
|
return fixes
|
|
|
|
def enhance_jsdoc_types(self, file_path: str) -> str:
|
|
"""Add comprehensive JSDoc type annotations to a file"""
|
|
target_file = Path(file_path)
|
|
if not target_file.exists():
|
|
return f"❌ File not found: {file_path}"
|
|
|
|
try:
|
|
content = target_file.read_text()
|
|
|
|
# Add type annotations for common patterns
|
|
enhanced_content = self._add_function_types(content)
|
|
enhanced_content = self._add_variable_types(enhanced_content)
|
|
enhanced_content = self._add_class_types(enhanced_content)
|
|
|
|
# Write enhanced content
|
|
backup_file = target_file.with_suffix('.js.bak')
|
|
target_file.rename(backup_file)
|
|
target_file.write_text(enhanced_content)
|
|
|
|
return f"✅ Enhanced {file_path} with JSDoc types (backup: {backup_file})"
|
|
|
|
except Exception as e:
|
|
return f"❌ Error enhancing {file_path}: {e}"
|
|
|
|
def _add_function_types(self, content: str) -> str:
|
|
"""Add JSDoc types to functions"""
|
|
# Pattern for function declarations
|
|
function_pattern = r'(function\s+(\w+)\s*\([^)]*\)\s*\{)'
|
|
|
|
def add_jsdoc(match: Match[str]) -> str:
|
|
func_name = match.group(2)
|
|
return f'''/**
|
|
* {func_name} function
|
|
* @param {{any}} ...args - Function arguments
|
|
* @returns {{any}} Function result
|
|
*/
|
|
{match.group(1)}'''
|
|
|
|
return re.sub(function_pattern, add_jsdoc, content)
|
|
|
|
def _add_variable_types(self, content: str) -> str:
|
|
"""Add JSDoc types to variables"""
|
|
# Pattern for const/let declarations
|
|
var_pattern = r'(const|let)\s+(\w+)\s*='
|
|
|
|
def add_type_comment(match: Match[str]) -> str:
|
|
return f'{match.group(0)} /** @type {{any}} */'
|
|
|
|
return re.sub(var_pattern, add_type_comment, content)
|
|
|
|
def _add_class_types(self, content: str) -> str:
|
|
"""Add JSDoc types to classes"""
|
|
# Pattern for class declarations
|
|
class_pattern = r'(class\s+(\w+))'
|
|
|
|
def add_class_jsdoc(match: Match[str]) -> str:
|
|
class_name = match.group(2)
|
|
return f'''/**
|
|
* {class_name} class
|
|
* @class
|
|
*/
|
|
{match.group(1)}'''
|
|
|
|
return re.sub(class_pattern, add_class_jsdoc, content)
|
|
|
|
def generate_type_definitions(self) -> str:
|
|
"""Generate enhanced type definitions"""
|
|
print("📝 Generating enhanced type definitions...")
|
|
|
|
types_content = '''/**
|
|
* Enhanced FastLED WASM Type Definitions
|
|
* Auto-generated comprehensive types for improved type safety
|
|
*/
|
|
|
|
// Browser Environment Extensions
|
|
declare global {
|
|
interface Window {
|
|
audioData?: {
|
|
audioBuffers: Record<string, AudioBufferStorage>;
|
|
hasActiveSamples: boolean;
|
|
};
|
|
|
|
// FastLED global functions
|
|
FastLED_onFrame?: (frameData: any, callback: Function) => void;
|
|
FastLED_onStripUpdate?: (data: any) => void;
|
|
FastLED_onStripAdded?: (id: number, length: number) => void;
|
|
FastLED_onUiElementsAdded?: (data: any) => void;
|
|
}
|
|
|
|
// Audio API types
|
|
interface AudioWorkletProcessor {
|
|
process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record<string, Float32Array>): boolean;
|
|
}
|
|
|
|
// WebAssembly Module types
|
|
interface WASMModule {
|
|
_malloc(size: number): number;
|
|
_free(ptr: number): void;
|
|
_extern_setup?: () => void;
|
|
_extern_loop?: () => void;
|
|
ccall(name: string, returnType: string, argTypes: string[], args: any[]): any;
|
|
}
|
|
}
|
|
|
|
// FastLED specific interfaces
|
|
interface FastLEDConfig {
|
|
canvasId: string;
|
|
uiControlsId: string;
|
|
outputId: string;
|
|
frameRate?: number;
|
|
}
|
|
|
|
interface StripData {
|
|
strip_id: number;
|
|
pixel_data: Uint8Array;
|
|
length: number;
|
|
diameter?: number;
|
|
}
|
|
|
|
interface ScreenMapData {
|
|
strips: Record<string, Array<{ x: number; y: number }>>;
|
|
absMin: [number, number];
|
|
absMax: [number, number];
|
|
}
|
|
|
|
export {};
|
|
'''
|
|
|
|
types_file = self.workspace_root / "src" / "platforms" / "wasm" / "types.enhanced.d.ts"
|
|
types_file.write_text(types_content)
|
|
|
|
return f"✅ Generated enhanced types: {types_file}"
|
|
|
|
def create_linting_config_variants(self) -> List[str]:
|
|
"""Create different ESLint configuration variants and return success messages"""
|
|
print("⚙️ Creating ESLint configuration variants...")
|
|
|
|
configs: List[str] = []
|
|
|
|
# Strict ESLint config
|
|
strict_config = '''module.exports = {
|
|
env: {
|
|
browser: true,
|
|
es2022: true,
|
|
worker: true
|
|
},
|
|
parserOptions: {
|
|
ecmaVersion: 2022,
|
|
sourceType: "module"
|
|
},
|
|
rules: {
|
|
// Critical issues
|
|
"no-debugger": "error",
|
|
"no-eval": "error",
|
|
// Code quality
|
|
"eqeqeq": "error",
|
|
"prefer-const": "error",
|
|
"no-var": "error",
|
|
"no-await-in-loop": "error",
|
|
"guard-for-in": "error",
|
|
"camelcase": "warn",
|
|
"default-param-last": "warn"
|
|
}
|
|
};'''
|
|
|
|
# Minimal ESLint config
|
|
minimal_config = '''module.exports = {
|
|
env: {
|
|
browser: true,
|
|
es2022: true,
|
|
worker: true
|
|
},
|
|
parserOptions: {
|
|
ecmaVersion: 2022,
|
|
sourceType: "module"
|
|
},
|
|
rules: {
|
|
// Only critical runtime issues
|
|
"no-debugger": "error",
|
|
"no-eval": "error"
|
|
}
|
|
};'''
|
|
|
|
# Write config variants
|
|
strict_file = self.workspace_root / ".cache/js-tools" / ".eslintrc.strict.js"
|
|
minimal_file = self.workspace_root / ".cache/js-tools" / ".eslintrc.minimal.js"
|
|
|
|
# Ensure .cache/js-tools directory exists
|
|
(self.workspace_root / ".cache/js-tools").mkdir(parents=True, exist_ok=True)
|
|
|
|
strict_file.write_text(strict_config)
|
|
minimal_file.write_text(minimal_config)
|
|
|
|
configs.extend([
|
|
f"✅ Created strict ESLint config: {strict_file}",
|
|
f"✅ Created minimal ESLint config: {minimal_file}"
|
|
])
|
|
|
|
return configs
|
|
|
|
def run_summary(self) -> str:
|
|
"""Generate comprehensive summary of JavaScript codebase and return formatted report"""
|
|
print("📊 Generating comprehensive JavaScript codebase summary...")
|
|
|
|
summary: List[str] = []
|
|
summary.append("=" * 80)
|
|
summary.append("FASTLED JAVASCRIPT LINTING & TYPE SAFETY SUMMARY")
|
|
summary.append("=" * 80)
|
|
|
|
# File statistics
|
|
total_lines = 0
|
|
total_files = len(self.js_files)
|
|
|
|
for js_file in self.js_files:
|
|
try:
|
|
lines = len(js_file.read_text().split('\n'))
|
|
total_lines += lines
|
|
except:
|
|
pass
|
|
|
|
summary.append(f"\n📁 CODEBASE OVERVIEW:")
|
|
summary.append(f" • Total JavaScript files: {total_files}")
|
|
summary.append(f" • Total lines of code: {total_lines:,}")
|
|
summary.append(f" • Average file size: {total_lines // max(total_files, 1):,} lines")
|
|
|
|
# Current linting status
|
|
lint_result = self.analyze_current_linting()
|
|
summary.append(f"\n🔍 CURRENT LINTING STATUS:")
|
|
summary.append(f" • {lint_result.get('summary', 'No summary available')}")
|
|
|
|
issues = lint_result.get('issues', [])
|
|
if issues:
|
|
rule_counts: Dict[str, int] = {}
|
|
for issue in issues:
|
|
rule = issue.get('rule', 'unknown')
|
|
rule_counts[rule] = rule_counts.get(rule, 0) + 1
|
|
|
|
summary.append(f" • Most common issues:")
|
|
# Sort by count
|
|
for rule, count in sorted(rule_counts.items(), key=lambda x: x[1], reverse=True)[:5]:
|
|
summary.append(f" - {rule}: {count} occurrences")
|
|
|
|
# Recommendations
|
|
summary.append(f"\n🎯 RECOMMENDED NEXT STEPS:")
|
|
summary.append(f" 1. Fix critical performance issues (await-in-loop)")
|
|
summary.append(f" 2. Gradually enable stricter type checking per file")
|
|
summary.append(f" 3. Add comprehensive JSDoc annotations")
|
|
summary.append(f" 4. Enable additional linting rules incrementally")
|
|
summary.append(f" 5. Create automated type checking CI pipeline")
|
|
|
|
# Available tools
|
|
summary.append(f"\n🛠️ AVAILABLE ENHANCEMENT TOOLS:")
|
|
summary.append(f" • uv run scripts/enhance-js-typing.py --approach linting")
|
|
summary.append(f" • uv run scripts/enhance-js-typing.py --approach performance")
|
|
summary.append(f" • uv run scripts/enhance-js-typing.py --approach types --file <path>")
|
|
summary.append(f" • uv run scripts/enhance-js-typing.py --approach configs")
|
|
summary.append(f" • bash .cache/js-tools/lint-js-fast # Fast ESLint linting")
|
|
summary.append(f" • bash lint # Full project linting")
|
|
|
|
summary.append("\n" + "=" * 80)
|
|
|
|
return "\n".join(summary)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="FastLED JavaScript Enhancement Tool")
|
|
parser.add_argument("--approach", choices=["summary", "linting", "performance", "types", "configs"],
|
|
default="summary", help="Enhancement approach")
|
|
parser.add_argument("--file", help="Specific file to enhance (for types approach)")
|
|
|
|
args = parser.parse_args()
|
|
enhancer = JSLintingEnhancer()
|
|
|
|
if args.approach == "summary":
|
|
print(enhancer.run_summary())
|
|
|
|
elif args.approach == "linting":
|
|
result = enhancer.analyze_current_linting()
|
|
print(f"\n🔍 LINTING ANALYSIS:")
|
|
print(f" {result.get('summary', 'No summary available')}")
|
|
|
|
issues = result.get('issues', [])
|
|
if issues:
|
|
print(f"\n📋 ISSUES BREAKDOWN:")
|
|
for issue in issues[:10]: # Show first 10
|
|
print(f" • {issue.get('rule', 'unknown')}: {issue.get('message', 'No message')}")
|
|
if issue.get('file'):
|
|
print(f" File: {issue.get('file', '')}:{issue.get('line', '?')}")
|
|
|
|
elif args.approach == "performance":
|
|
fixes = enhancer.fix_await_in_loop_issues()
|
|
print(f"\n⚡ PERFORMANCE ANALYSIS:")
|
|
print(f" Found {len(fixes)} potential await-in-loop issues")
|
|
|
|
for fix in fixes[:5]: # Show first 5
|
|
print(f"\n 📁 {fix['file']}:{fix['line']}")
|
|
print(f" Issue: {fix['issue']}")
|
|
print(f" Suggestion: {fix['suggestion']}")
|
|
|
|
elif args.approach == "types":
|
|
if not args.file:
|
|
print("❌ --file argument required for types approach")
|
|
return
|
|
|
|
result = enhancer.enhance_jsdoc_types(args.file)
|
|
print(result)
|
|
|
|
# Also generate enhanced type definitions
|
|
type_result = enhancer.generate_type_definitions()
|
|
print(type_result)
|
|
|
|
elif args.approach == "configs":
|
|
configs = enhancer.create_linting_config_variants()
|
|
for config in configs:
|
|
print(config)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|