137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Fix TS6133 unused variable/import errors reported by tsc."""
|
||
|
|
import re
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parent.parent
|
||
|
|
|
||
|
|
|
||
|
|
def run_tsc() -> list[str]:
|
||
|
|
result = subprocess.run(
|
||
|
|
[str(ROOT / "node_modules/.bin/tsc"), "--noEmit"],
|
||
|
|
cwd=ROOT,
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
)
|
||
|
|
lines = (result.stdout + result.stderr).splitlines()
|
||
|
|
return [l for l in lines if "error TS6133" in l or "error TS6198" in l]
|
||
|
|
|
||
|
|
|
||
|
|
def parse_error(line: str) -> tuple[str, int, str, str] | None:
|
||
|
|
m = re.match(
|
||
|
|
r"^(src/[^:]+)\((\d+),(\d+)\): error TS(6133|6198): '([^']+)'",
|
||
|
|
line,
|
||
|
|
)
|
||
|
|
if not m:
|
||
|
|
return None
|
||
|
|
path, row, col, code, name = m.groups()
|
||
|
|
return path, int(row), int(col), name, code
|
||
|
|
|
||
|
|
|
||
|
|
def fix_file(path: Path, row: int, name: str, code: str) -> bool:
|
||
|
|
lines = path.read_text().splitlines(keepends=True)
|
||
|
|
if row < 1 or row > len(lines):
|
||
|
|
return False
|
||
|
|
idx = row - 1
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if code == "6198":
|
||
|
|
# All destructured elements unused — prefix each binding with _
|
||
|
|
new_line = re.sub(r"\{([^}]+)\}", lambda m: "{" + ", ".join(
|
||
|
|
(p.strip() if p.strip().startswith("_") else "_" + p.strip().split(":")[0].strip() + (":" + ":".join(p.strip().split(":")[1:]) if ":" in p.strip() else ""))
|
||
|
|
for p in m.group(1).split(",")
|
||
|
|
) + "}", line, count=1)
|
||
|
|
if new_line != line:
|
||
|
|
lines[idx] = new_line
|
||
|
|
path.write_text("".join(lines))
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
# Unused import — remove named import or whole import line
|
||
|
|
if re.search(rf"\bimport\b.*\b{re.escape(name)}\b", line):
|
||
|
|
if re.match(r"import\s+\{" , line):
|
||
|
|
names = re.search(r"\{([^}]+)\}", line)
|
||
|
|
if names:
|
||
|
|
parts = [p.strip() for p in names.group(1).split(",")]
|
||
|
|
kept = []
|
||
|
|
for p in parts:
|
||
|
|
base = p.split(" as ")[0].strip()
|
||
|
|
if base != name:
|
||
|
|
kept.append(p)
|
||
|
|
if not kept:
|
||
|
|
lines[idx] = ""
|
||
|
|
path.write_text("".join(lines))
|
||
|
|
return True
|
||
|
|
new_import = re.sub(r"\{[^}]+\}", "{" + ", ".join(kept) + "}", line)
|
||
|
|
lines[idx] = new_import
|
||
|
|
path.write_text("".join(lines))
|
||
|
|
return True
|
||
|
|
if re.match(rf"import\s+{re.escape(name)}\s", line) or re.match(rf"import\s+\*\s+as\s+{re.escape(name)}\s", line):
|
||
|
|
lines[idx] = ""
|
||
|
|
path.write_text("".join(lines))
|
||
|
|
return True
|
||
|
|
|
||
|
|
# Parameter or local — prefix with underscore
|
||
|
|
patterns = [
|
||
|
|
(rf"\(({re.escape(name)})\:", rf"(_{name}:"),
|
||
|
|
(rf",\s*{re.escape(name)}\:", rf", _{name}:"),
|
||
|
|
(rf"\(\s*{re.escape(name)}\s*\)", rf"(_{name})"),
|
||
|
|
(rf"\(\s*{re.escape(name)}\s*,", rf"(_{name},"),
|
||
|
|
(rf",\s*{re.escape(name)}\s*\)", rf", _{name})"),
|
||
|
|
(rf"\b(const|let)\s+{re.escape(name)}\b", rf"\1 _{name}"),
|
||
|
|
(rf"\basync\s+{re.escape(name)}\b", rf"async _{name}"),
|
||
|
|
(rf"function\s+{re.escape(name)}\b", rf"function _{name}"),
|
||
|
|
]
|
||
|
|
new_line = line
|
||
|
|
for pat, repl in patterns:
|
||
|
|
new_line2 = re.sub(pat, repl, new_line)
|
||
|
|
if new_line2 != new_line:
|
||
|
|
new_line = new_line2
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
# fallback: word boundary prefix in destructuring { name }
|
||
|
|
new_line = re.sub(rf"\{{([^}}]*)\b{re.escape(name)}\b", rf"{{\1_{name}", line, count=1)
|
||
|
|
if new_line == line:
|
||
|
|
new_line = re.sub(rf"\b{re.escape(name)}\b", f"_{name}", line, count=1)
|
||
|
|
|
||
|
|
if new_line != line:
|
||
|
|
lines[idx] = new_line
|
||
|
|
path.write_text("".join(lines))
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
fixed = 0
|
||
|
|
for _ in range(5):
|
||
|
|
errors = run_tsc()
|
||
|
|
if not errors:
|
||
|
|
break
|
||
|
|
changed = False
|
||
|
|
seen: set[tuple] = set()
|
||
|
|
for err in errors:
|
||
|
|
parsed = parse_error(err)
|
||
|
|
if not parsed:
|
||
|
|
continue
|
||
|
|
rel, row, col, name, code = parsed
|
||
|
|
key = (rel, row, name)
|
||
|
|
if key in seen:
|
||
|
|
continue
|
||
|
|
seen.add(key)
|
||
|
|
path = ROOT / rel
|
||
|
|
if fix_file(path, row, name, code):
|
||
|
|
fixed += 1
|
||
|
|
changed = True
|
||
|
|
if not changed:
|
||
|
|
break
|
||
|
|
print(f"Fixed {fixed} TS6133/6198 issues")
|
||
|
|
remaining = run_tsc()
|
||
|
|
print(f"Remaining TS6133/6198: {len(remaining)}")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main())
|