mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-10-20 18:56:16 +00:00
The typecheck ran with continue-on-error, so it could not fail, and it did not see much of the code it was meant to check: shared/ resolved none of its imports (61 TS2307), which hid every type error behind them. - tsconfig.app.json resolves packages that shared/ imports from web's node_modules (types first), and @pezkuwi/utils subpaths as vite does. Unresolved imports: 61 -> 0. The real errors behind them are now visible: 353 in total, most in production code with little test cover. - Fixing those in one change would be a large edit to that code. Instead ops/typecheck-ratchet.py records them per file and error code in ops/typecheck-baseline.txt and fails when any file gains one, or when errors were fixed without lowering the baseline in the same change. Line numbers are not part of the signature, so unrelated edits do not trip it. Checked: a new error in App.tsx fails (exit 1); an unlowered baseline fails (exit 1); the current tree passes. The CI step runs the ratchet instead of the non-blocking typecheck.
82 lines
3.2 KiB
Python
Executable File
82 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Hold web's type errors to a ratchet.
|
|
|
|
web/ has type errors that predate its typecheck gate; fixing them all at once
|
|
would mean editing hundreds of lines of production code with little test
|
|
cover, so the gate used to run with continue-on-error and could not fail.
|
|
This makes it a real gate without that big bang: the errors that exist are
|
|
recorded per file and error code in ops/typecheck-baseline.txt, and the check
|
|
fails when any file gains an error of any code. It also fails when errors were
|
|
fixed but the baseline was not lowered with them, so a gain cannot quietly be
|
|
given back later.
|
|
|
|
python3 ops/typecheck-ratchet.py # check (run from the repo root)
|
|
python3 ops/typecheck-ratchet.py --update # rewrite the baseline (only ever lower it)
|
|
|
|
Line numbers are left out of the signature on purpose: they move with every
|
|
edit above an error, and a gate that fails on that is a gate people disable.
|
|
"""
|
|
import collections
|
|
import pathlib
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
BASELINE = ROOT / 'ops' / 'typecheck-baseline.txt'
|
|
ERROR = re.compile(r'^(?P<file>[^(\s][^(]*)\(\d+,\d+\): error (?P<code>TS\d+):')
|
|
|
|
|
|
def current() -> collections.Counter:
|
|
out = subprocess.run(['npm', 'run', '--silent', 'typecheck'], cwd=ROOT / 'web',
|
|
capture_output=True, text=True)
|
|
counts = collections.Counter()
|
|
for line in (out.stdout + out.stderr).splitlines():
|
|
m = ERROR.match(line)
|
|
if m:
|
|
counts[f"{m['file']} {m['code']}"] += 1
|
|
if out.returncode != 0 and not counts:
|
|
sys.exit(f'typecheck failed without reporting type errors:\n{out.stdout}{out.stderr}')
|
|
return counts
|
|
|
|
|
|
def baseline() -> collections.Counter:
|
|
counts = collections.Counter()
|
|
for line in BASELINE.read_text().splitlines():
|
|
if line and not line.startswith('#'):
|
|
n, sig = line.split(' ', 1)
|
|
counts[sig] = int(n)
|
|
return counts
|
|
|
|
|
|
def main() -> int:
|
|
now = current()
|
|
if '--update' in sys.argv:
|
|
lines = [f'{n} {sig}' for sig, n in sorted(now.items())]
|
|
BASELINE.write_text('# count file code — written by ops/typecheck-ratchet.py --update\n'
|
|
+ '\n'.join(lines) + '\n')
|
|
print(f'baseline: {sum(now.values())} errors in {len(now)} file/code pairs')
|
|
return 0
|
|
|
|
base = baseline()
|
|
worse = {sig: (base.get(sig, 0), n) for sig, n in now.items() if n > base.get(sig, 0)}
|
|
better = {sig: (n, now.get(sig, 0)) for sig, n in base.items() if now.get(sig, 0) < n}
|
|
for sig, (was, isnow) in sorted(worse.items()):
|
|
print(f'NEW {sig}: {was} -> {isnow}')
|
|
for sig, (was, isnow) in sorted(better.items()):
|
|
print(f'fixed {sig}: {was} -> {isnow}')
|
|
print(f'type errors: {sum(now.values())} (baseline {sum(base.values())})')
|
|
if worse:
|
|
print('New type errors. Fix them; the baseline only ever goes down.')
|
|
return 1
|
|
if better:
|
|
# Fail here too: a gain that is not written into the baseline can be
|
|
# given back later without the gate noticing.
|
|
print('Fewer errors than the baseline: run with --update and commit the new baseline in this change.')
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|