fix(lint): cover .tsx files in the lint globs - #1218
Conversation
eslint "{src,apps,libs,test}/**/*.ts" never matched a .tsx file, so all 261
.tsx files under src/ stayed outside the standalone lint run. Widen both globs
to *.{ts,tsx} and clear the 15 findings that surfaced -- all of them in
src/__tests__/, none in app code:
- drop three dead `const React = require('react')` from jest.mock factories
(the automatic JSX runtime makes them unnecessary)
- replace the never-settling `new Promise(() => {})` executors
- turn no-var-requires off for src/__tests__/** only, where jest.mock hoisting
forces require() inside the factory
No rule was downgraded or disabled to make the run pass.
Follow-ups on the widened lint glob: - Replace the .eslintrc.js overrides block with the inline eslint-disable-next-line directives the repo already uses for hoisted jest.mock factories (see support-issue-receiver-iban.test.tsx). The blanket override contradicted that convention, left those three directives dead, and missed src/util/__tests__ and src/hooks/wallets/__tests__ anyway. - Ignore **/*.stories.tsx. tsconfig.json excludes them, so under the widened glob the type-aware parser turns any story file into a fatal parse error -- which would red the lint step that gates the dev and prd deploys. - Add --max-warnings 0. Test files are not compiled by webpack, so npm run lint is now their only gate and warnings would otherwise never fail it. Verified a no-op today: 0 warnings across all 409 files. - Widen the jest collectCoverageFrom glob the same way; it carried the identical .ts-only bug. - Convert the last jest.mock factory with a bare return body to the concise form used by the other two.
🤖 PR Review Bot
|
--max-warnings 0 made npm echo the flag into the output the review bot greps, so grep -c "warning" counted the command line itself and the bot posted "ESLint: 0 errors, 1 warnings" on a clean tree. Anchor both counters to real ESLint message lines and call npm with --silent. The same anchoring repairs a pre-existing miscount: grep -c "error" was also matching the file name transaction-document-error.test.tsx and the summary line, reporting 3 errors where there was 1. Verified over four cases -- clean tree 0/0, one planted warning 1/0, one planted error 0/1, and an error in a file whose name contains "error" 0/1, where the old counter reported 1 warning and 3 errors.
grep -c prints 0 and exits 1 when nothing matches, so `$(grep -c … || echo "0")` captures "0\n0" on every green run. The step then appends a bare "0" line to $GITHUB_OUTPUT, which the runner rejects as an invalid file command and marks the step failed -- masked only by continue-on-error. Same pattern the ESLint counter two steps above just moved away from; this was the last instance in the file.
Two more instances of the classes this branch is already fixing:
- The Security Audit step's HIGH/CRITICAL substitutions were unguarded, so
under bash -e a missing or unparseable audit-output.json aborts the step
before either echo. Both outputs stay unset, the bot reads parseInt('') || 0
and reports no critical vulnerabilities -- and npm audit runs nowhere else,
so that comment is the only audit signal there is. Simulated valid, empty,
invalid and missing input: the old form exits 5 and 2 on the last two and
writes nothing.
- tsconfig.build.json excluded **/*spec.ts and **/*.test.ts, which do not
match .tsx -- the same bug as the lint glob. Inert only because every
.test.tsx currently sits under src/__tests__, caught by another entry; a
.test.tsx next to its component would be emitted into dist/ by build:lib.
Written as explicit patterns because TypeScript's include/exclude do not
support brace expansion, so the {ts,tsx} form matches nothing.
Verified with tsc --listFiles both ways, and build:lib still passes.
Guarding the audit counters in ff3ce70 fixed the aborted step but hardened the wrong side: a broken audit became green AND silent, where the abort had at least been visible. Report it instead. jq -e without a `// 0` fallback separates a genuine 0 from an unavailable metric, so the step now emits status=ok|failed and the bot says "vulnerability status unknown" rather than nothing. Verified across seven inputs -- clean, real vulns, npm error object, empty, invalid JSON, array, missing file: only the first two report ok, and the error-object case is one a `// 0` fallback would have reported as a clean audit. Also add **/*.stories.tsx to tsconfig.build.json: extends REPLACES exclude rather than merging it, so rewriting that array dropped the parent's story exclusion. A story file would otherwise land in the published dist/ and, since there is no @storybook dependency, red the bot's tsc step. Confirmed with tsc --listFiles that it is excluded again.
Follow-ups on the audit signal: - Retry once, gated on whether the output is usable rather than on the exit code. npm audit exits non-zero whenever it finds vulnerabilities -- 180 in this repo -- so retrying on exit code would run it twice on every normal PR. Verified with a stub npm: the healthy shapes make one call, the broken ones two. This matters because the audit is the only network-dependent check in the job, and a transient blip posts a security warning that a later clean run cannot clear. - Keep stderr and print it, plus the head of the output, on the failure path, and emit ::warning::. "Vulnerability status unknown" with an empty log gives a maintainer nothing to act on. - jq -s with .[0] so two concatenated JSON documents cannot produce a two-line value and corrupt GITHUB_OUTPUT -- the same class the tsc counter hit. Not reachable from npm audit today, but free to rule out. The real npm audit output still resolves to status=ok high=67 critical=0.
|
7 review passes to zero findings. What they caught, since most of it is not legible from the final diff:
Deliberately not done, both under Out of scope in the description: extending On that last point — the |
Closes #1215.
Problem
npm run lintmatched{src,apps,libs,test}/**/*.ts. That glob does not match.tsx, so all261
.tsxfiles undersrc/were outside the standalone lint run.Verified both directions with a throwaway
src/components/__lint_probe__.tsxcontaining a single@typescript-eslint/no-empty-functionviolation:**/*.ts(before)**/*.{ts,tsx}(after)1 problem (1 error, 0 warnings), exit 1What the widened glob turned up
Less than the issue expected — 15 problems in 5 files, all of them in
src/__tests__/, nonein a component, screen or hook:
@typescript-eslint/no-var-requires@typescript-eslint/no-empty-function@typescript-eslint/no-unused-varsThe reason the app code is already clean is that it is not, in fact, unlinted: CRA's
eslint-webpack-pluginlints every file webpack compiles (extensions: ['js','mjs','jsx','ts','tsx'],context: src) using this repository's.eslintrc.js, and it runs on everybuild:dev/widget:dev. Confirmed by experiment — appendingexport const __lintProbe = () => {};tosrc/App.tsxmakesnpm run build:devfail with@typescript-eslint/no-empty-function.What that build-time lint never sees is anything webpack does not compile — above all
src/__tests__/**. That is exactly where all 15 findings sit. The same boundary shows up in thenumbers: 0
no-non-null-assertionfindings across 261.tsxfiles undersrc/, versus 68 undere2e/, which no build ever touches.So the practical effect: the
lintstep becomes meaningful on its own instead of being a no-opsilently covered by a full webpack build, test files get linted at all, and
npm run lint:fixbecomes usable on components.
Changes
package.json—lintandlint:fixglobs widened to*.{ts,tsx}.const React = require('react');removed fromjest.mock()factories — the automaticJSX runtime (
"jsx": "react-jsx") means the mock components never referenced it. Verified againstthe actual babel output, not just the type level. Removing that line left the
transaction-document-error.test.tsxfactory as the only one in the suite with a barereturnbody, so it became a concise object arrow like the other two — that re-indent is the 73-line hunk,
and it is the bulk of the diff. Mock keys before and after: 24, same set and same order.
new Promise(() => {})→new Promise(() => undefined). Same never-settling promise, noempty function body. This is already the idiom used elsewhere in the suite.
// eslint-disable-next-line @typescript-eslint/no-var-requireson therequire()calls that remain.
jest.mock()factories are hoisted above the imports, so their dependenciescannot be written as imports. This matches the convention the repo already uses for exactly this
case in
support-issue-receiver-iban.test.tsx:120,123,309— the alternative, an.eslintrc.jsoverridesblock, would have left those three directives dead and would have missedsrc/util/__tests__andsrc/hooks/wallets/__tests__..eslintrc.js—**/*.stories.tsxadded toignorePatterns.tsconfig.jsonexcludes storyfiles, and the config uses a type-aware parser, so under the widened glob any story file becomes a
fatal parse error — which would red the
Run linterstep indev.ymlandprd.yml, i.e. thedeploys, not just PR CI. Reproduced with a scratch
probe.stories.tsx:Parsing error: ESLint was configured to run on … using parserOptions.projectwithout the entry, clean with it. No storyfile exists today, so this is purely a guard — paired with the matching
tsconfig.build.jsonentrybelow, so it covers the lint program and the build program alike.
package.json—--max-warnings 0added tolint. Two rules are configured as warnings(
no-unused-vars, andno-non-null-assertioninherited fromplugin:@typescript-eslint/recommended).Neither existing gate covers them:
scripts/build.sh:58hard-codesCI=false react-app-rewired build, so the app build never promotes warnings to errors even on Actions, and onlyscripts/build-widget.sh:69inheritsCI=true— and only for modules in the widget entry's graph.So
npm run lintis the first warning gate for test files and for anysrcfile thatbuild:devcompiles but the widget entry does not pull in. Verified a no-op today: 0 warnings across all 409
files. Flagging this explicitly because it does tighten the contract of the
lintstep; happy tosplit it out if you would rather land it separately.
.github/workflows/pr-review-bot.yml— the ESLint counter now matches ESLint message lines(
^ +[0-9]+:[0-9]+ +warning) instead of the bare substringswarning/error, and calls npm with--silent. This is a regression--max-warnings 0introduced above: npm echoes the script lineinto the captured output, so
grep -c "warning"counted the flag itself and the bot posted⚠️ ESLint: 0 errors, 1 warningson a completely clean tree — it did exactly that on this PR beforethe fix. The same anchoring also repairs a pre-existing miscount:
grep -c "error"was matching thefile name
transaction-document-error.test.tsxand the summary line. Verified over four cases —clean tree
0/0; one planted warning1/0; one planted error0/1; an error in a file whose namecontains "error"
0/1, where the old counter reported1 warning / 3 errors. The⚠️ ESLint: 0 errors, 1 warningscomment above is the pre-fix artefact from theabd3d21run — thefirst after
--max-warnings 0landed; every run since posts nothing. It is left in place as theevidence.
.github/workflows/pr-review-bot.yml— the TypeScript step had the same$(grep -c … || echo "0")shape.
grep -cprints0and exits 1 when nothing matches, so on every green run it captured0\n0and appended a bare0line to$GITHUB_OUTPUT, which the runner rejects(
Invalid format '0') and marks the step failed — visible as red annotations on thereviewcheckin this PR's own earlier runs, masked from the verdict only by
continue-on-error. Changed toERRORS=$(grep -c "error TS" tsc-output.txt) || ERRORS=0; that was the last instance in the file..github/workflows/pr-review-bot.yml— the Security Audit step reported a brokennpm auditas aclean one.
HIGH=$(jq …)/CRITICAL=$(jq …)were unguarded, so under the runner'sbash -eamissing or unparseable
audit-output.jsonaborted the step before eitherecho; both outputs stayedunset, and the bot's
parseInt('') || 0reported no critical vulnerabilities.npm auditrunsnowhere else and there is no
dependabot.yml, so that comment is the repo's only dependency-vulnsignal in CI. The step now emits
status=ok|failedand the bot says vulnerability status unknownrather than staying silent. Three deliberate details:
jq -ewithout a// 0fallback — the fallback turns a missing key path into a reported zero.Verified across the input space (clean, real vulns, npm error object, empty file, invalid JSON,
array, missing file,
.metadatawithoutvulnerabilities, one key present and the other absent):only a genuinely parseable report says
ok, and the npm-error-object case is exactly the one a// 0fallback would have called a clean audit.jq -s+.[0]— two concatenated JSON documents would otherwise yield a two-line value andcorrupt
$GITHUB_OUTPUT, the same class as thetscfix above. Not reachable fromnpm audittoday (its output is a single document, checked), but free to rule out.
npm auditexits non-zero whenever itfinds vulnerabilities — 180 in this repo — so retrying on exit code would re-run it on every normal
PR. Confirmed with a stub
npm: the three healthy shapes make exactly one call, the broken onesmake two. It matters because this is the only network-dependent check in the job, and a transient
blip would post a security warning that a later clean run cannot clear (see Out of scope).
On failure the step now also emits
::warning::and the head of stderr/output, sostatus=failedisdiagnosable rather than just visible.
tsconfig.build.json—**/*.stories.tsxre-added.extendsreplacesexcluderather thanmerging, so this file's list is the complete set and rewriting it dropped the parent's story
exclusion. A story file would otherwise be emitted into the published
dist/bybuild:liband,with no
@storybook/*dependency in the repo, would red the bot'stsc -p tsconfig.build.jsonstep.Confirmed excluded again with
tsc --listFiles.tsconfig.build.json— theexcludelist also carried the same.ts-only bug:**/*spec.tsand**/*.test.tsdo not match.tsx. Inert today only because all 21.test.tsxfiles live undersrc/__tests__, which a separate entry catches — but a.test.tsxplaced next to its component wouldbe compiled into the published
dist/bybuild:lib. Probed both ways withtsc --listFiles: on theold list a
src/components/probe/thing.test.tsxis included, on the new one it is not. Written as fourexplicit patterns rather than
{ts,tsx}because TypeScript'sinclude/excludedo not supportbrace expansion — the brace form silently matched nothing, which the probe caught.
package.json— jestcollectCoverageFromwidened fromsrc/**/*.(t|j)stosrc/**/*.{ts,tsx,js,jsx}. It carried the identical.ts-only bug. Coverage is not run in CI, sothis is not a gate; checked with micromatch that the new glob drops no file and adds the 261
.tsx.No rule was weakened for application code.
no-var-requiresis suppressed only per-occurrence, onsix
require()calls that jest hoisting makes unavoidable.Out of scope
e2e/(the open question in the issue): still outside the glob, and it is a deliberateexclusion as things stand —
CONTRIBUTING.mddocuments that the Playwright suite intentionallydoes not run in CI. Pulling it in would mean 36 errors (28
no-empty-function, 4ban-types,4
no-inferrable-types) and 112 warnings (68no-non-null-assertion, 44no-unused-vars) spreadover 46 of its 84 files. Worth a separate issue rather than a rider on this one.
test/**files are only in a tsconfig transitively.test/wallet-setup/basic.setup.tsis inthe lint glob but in no tsconfig
include; it parses only becausee2e/synpress/fixtures.tsimports it into the e2e program. A future
test/file that no e2e spec imports would be a fatalparse error, the same failure class the stories guard covers. This predates the PR —
develop'sglob already covered
test/**/*.ts— and the durable fix is atsconfig.eslint.jsonspanningsrc,testande2e, which is more than this change should carry.pr-review-bot.yml:190-193returns early when a runfinds nothing, before it looks up its previous comment — so a clean run never corrects or removes a
comment a dirty run left behind. That is why the
⚠️ ESLintcomment above survives its own fix. Theremedy is to move the comment lookup above the early return and update it in place, which changes the
bot's comment lifecycle rather than its counting, so it belongs in its own change. Worth doing soon:
the audit retry above reduces the odds of a transient false warning but cannot remove them, and until
the lifecycle is fixed any such warning is permanent.
the glob matches nothing, both counters are 0 and the bot stays quiet. I fixed that shape for
npm auditand deliberately not for these two:npm run lintfails a job elsewhere,npm auditisnot checked anywhere. On feature PRs it is
pr.yml's Build and test that goes red; on theauto-created
develop -> mainrelease PRs that job is skipped (pr.yml:19,if: github.head_ref != 'develop'— visiblySKIPPEDon Release: develop -> main #1211), but the code it carries has alreadybeen through
dev.yml's Run linter on the push todevelop, which aborts that job before thedeploy. (Neither is a required check — this repo has no required status contexts — but both are
visibly red.) So an ESLint that fails to run reds something either way, while a broken
npm auditreds nothing anywhere. Extending
status=ok|failedto all three would still be reasonable — as its own change, where the added comment noise can be
judged on its merits.
e2e/tsconfig.json:17include: ["./**/*.ts"]is the last remaining.ts-only glob after thisPR. Inert —
e2e/holds 84.tsand 0.tsx— ande2e/is out of the lint scope anyway.eslint-disabledirectives for@typescript-eslint/no-explicit-anyinrefund-creditor-fields.tsx:8,10,12andlimit-request-fields.tsx:10,12,14— that rule isoffrepo-wide, so the directives suppress nothing. Both files are
.tsx, which is why the widenedscope surfaces them. Left alone to keep this PR to the lint plumbing; they cost nothing unless
--report-unused-disable-directivesis ever switched on.Verification
Node 20.20.2, npm 10.8.2 (matching CI).
npm run lint→ exit 0, 409 files, 0 errors, 0 warningsESLint found too many warnings (maximum: 0), exit 1 (the--max-warningsgate actually bites)
probe.stories.tsx→ exit 0 with theignorePatternsentry, fatal parse error without it--report-unused-disable-directives→ none of the six new directives is unusedPR: the
reviewcheck carried four failure annotations before the fixes(
Invalid format '0'/Unable to process file command 'output' successfully., one pair from eachstep), two once
6a1429chad fixed the ESLint step, and none from94d36e0onward — leaving onlythe unrelated, pre-existing Node 20 deprecation warning
comment at all, so
npm auditthere resolved tostatus=oknpm run test→ 60 suites, 604 tests passednpm run build:dev→ successnpm run widget:dev→ success