web: make the typecheck a real gate, held to a ratchet (#54)

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.
This commit is contained in:
SatoshiQaziMuhammed authored and GitHub committed 2026-10-09 07:18:26 +04:00
1 parent b6c2ce3470
commit c57e7e0ef4
4 files changed
+191 -8

No files matched your search

+5 -7
View File
@@ -72,13 +72,11 @@ jobs:
working-directory: ./web
run: npm run lint
# Typecheck gate. Currently non-blocking (continue-on-error) because the
# existing codebase still has pre-existing type errors. Once the count
# reaches zero, remove `continue-on-error` to make this a hard merge block.
- name: Typecheck
working-directory: ./web
continue-on-error: true
run: npm run typecheck
# Typecheck gate, held to a ratchet: the type errors that predate the gate
# are recorded in ops/typecheck-baseline.txt, and this fails when any file
# gains one, or when errors were fixed without lowering the baseline.
- name: Typecheck (ratchet)
run: python3 ops/typecheck-ratchet.py
- name: Run Tests
working-directory: ./web
+102
View File
@@ -0,0 +1,102 @@
# count file code — written by ops/typecheck-ratchet.py --update
3 ../shared/lib/multisig-operations.ts TS2339
3 ../shared/lib/perwerde.ts TS2339
6 ../shared/lib/presale.ts TS2339
1 ../shared/lib/referral.ts TS2339
9 ../shared/lib/scores.ts TS2339
3 ../shared/lib/staking.ts TS2339
10 ../shared/lib/validator-pool.ts TS2339
2 ../shared/lib/welati.ts TS2305
24 ../shared/lib/welati.ts TS2339
1 ../shared/lib/xcm-bridge.ts TS2353
1 ../shared/lib/xcm-bridge.ts TS2554
5 ../shared/lib/xcm-wizard.ts TS2339
1 ../shared/lib/xcm-wizard.ts TS2345
1 src/components/AddLiquidityModal.tsx TS2339
4 src/components/AddLiquidityModal.tsx TS2345
2 src/components/AddLiquidityModal.tsx TS2352
3 src/components/LPStakingModal.tsx TS2352
2 src/components/LPStakingModal.tsx TS2554
2 src/components/MultisigMembers.tsx TS2322
5 src/components/MultisigMembers.tsx TS2339
1 src/components/MultisigMembers.tsx TS2345
2 src/components/NetworkStats.tsx TS2339
1 src/components/PoolDashboard.tsx TS2345
3 src/components/PoolDashboard.tsx TS2352
1 src/components/RemoveLiquidityModal.tsx TS2345
16 src/components/TokenSwap.tsx TS2322
7 src/components/TokenSwap.tsx TS2339
8 src/components/TokenSwap.tsx TS2345
6 src/components/TokenSwap.tsx TS2552
1 src/components/TransactionHistory.tsx TS2304
3 src/components/TransactionHistory.tsx TS2339
1 src/components/XCMTeleportModal.tsx TS2339
2 src/components/admin/CommissionSetupTab.tsx TS2339
14 src/components/admin/CommissionVotingTab.tsx TS2339
1 src/components/admin/CommissionVotingTab.tsx TS2345
1 src/components/admin/XCMConfigurationWizard.tsx TS2339
5 src/components/admin/XCMConfigurationWizard.tsx TS2345
14 src/components/dashboard/CommissionProposalsCard.tsx TS2339
1 src/components/delegation/DelegationManager.tsx TS2322
1 src/components/delegation/DelegationManager.tsx TS2345
1 src/components/dex/AddLiquidityModal.tsx TS2345
2 src/components/dex/CreatePoolModal.tsx TS2345
1 src/components/dex/InitializeHezPoolModal.tsx TS2339
1 src/components/dex/InitializeUsdtModal.tsx TS2339
1 src/components/dex/PoolBrowser.tsx TS2339
1 src/components/dex/RemoveLiquidityModal.tsx TS2339
1 src/components/dex/RemoveLiquidityModal.tsx TS2345
3 src/components/dex/SwapInterface.tsx TS2345
1 src/components/forum/DiscussionThread.tsx TS2339
1 src/components/forum/DiscussionThread.tsx TS2740
1 src/components/governance/ElectionsInterface.tsx TS2345
2 src/components/governance/GovernanceHistory.tsx TS2339
10 src/components/governance/MyVotes.tsx TS2339
2 src/components/notifications/NotificationCenter.tsx TS2322
2 src/components/notifications/NotificationCenter.tsx TS2739
2 src/components/p2p/AdList.tsx TS2322
2 src/components/p2p/AdList.tsx TS2339
2 src/components/perwerde/CourseList.tsx TS2353
1 src/components/security/PermissionEditor.tsx TS2304
1 src/components/security/PermissionEditor.tsx TS2552
1 src/components/security/SecurityAudit.tsx TS2304
1 src/components/security/SessionMonitor.tsx TS2552
1 src/components/theme-provider.tsx TS2322
1 src/components/trading/PriceChart.tsx TS2345
1 src/components/trading/PriceChart.tsx TS2352
4 src/components/wallet/WalletButton.tsx TS2339
2 src/contexts/AuthContext.tsx TS2322
1 src/contexts/ReferralContext.tsx TS2345
10 src/hooks/use-toast.ts TS2339
2 src/hooks/use-toast.ts TS2395
3 src/hooks/useDelegation.ts TS2339
6 src/hooks/useGovernance.ts TS2339
1 src/hooks/useMobileBridge.ts TS2345
8 src/pages/AdminPanel.tsx TS2322
3 src/pages/AdminPanel.tsx TS2345
1 src/pages/Citizens.tsx TS2345
5 src/pages/Dashboard.tsx TS2322
2 src/pages/Dashboard.tsx TS2339
1 src/pages/Dashboard.tsx TS2769
1 src/pages/EducationPlatform.tsx TS2353
4 src/pages/Elections.tsx TS2339
3 src/pages/Elections.tsx TS2345
1 src/pages/Elections.tsx TS2352
8 src/pages/Explorer.tsx TS2339
2 src/pages/P2PDispute.tsx TS2554
2 src/pages/P2PTrade.tsx TS2322
3 src/pages/P2PTrade.tsx TS2339
2 src/pages/Presale.tsx TS2339
2 src/pages/Presale.tsx TS2345
4 src/pages/TelegramConnect.tsx TS2339
2 src/pages/WalletDashboard.tsx TS2339
2 src/pages/citizens/CitizensIssues.tsx TS2339
3 src/pages/citizens/GovernmentEntrance.tsx TS2339
1 src/pages/education/CertificatesPage.tsx TS2339
1 src/pages/education/CertificatesPage.tsx TS2769
1 src/pages/finance/TaxZekatPage.tsx TS2339
2 src/pages/launchpad/CreatePresale.tsx TS2345
29 src/pages/launchpad/PresaleDetail.tsx TS2339
6 src/pages/launchpad/PresaleDetail.tsx TS2345
1 src/pages/launchpad/PresaleDetail.tsx TS2362
4 src/pages/launchpad/PresaleList.tsx TS2339
+81
View File
@@ -0,0 +1,81 @@
#!/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())
+3 -1
View File
@@ -29,10 +29,12 @@
"@pezkuwi/lib": ["../shared/lib"],
"@pezkuwi/lib/*": ["../shared/lib/*"],
"@pezkuwi/utils": ["../shared/utils"],
"@pezkuwi/utils/*": ["../shared/utils/*"],
"@pezkuwi/theme": ["../shared/theme"],
"@pezkuwi/types": ["../shared/types"],
"@pezkuwi/components/*": ["../shared/components/*"],
"@shared/*": ["../shared/*"]
"@shared/*": ["../shared/*"],
"*": ["./node_modules/@types/*", "./node_modules/*"]
},
"types": ["vite/client"]
},