Skip to content

fix(lint): cover .tsx files in the lint globs - #1218

Merged
TaprootFreak merged 7 commits into
developfrom
fix/lint-glob-tsx
Jul 30, 2026
Merged

fix(lint): cover .tsx files in the lint globs#1218
TaprootFreak merged 7 commits into
developfrom
fix/lint-glob-tsx

Conversation

@Danswar

@Danswar Danswar commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Closes #1215.

Problem

npm run lint matched {src,apps,libs,test}/**/*.ts. That glob does not match .tsx, so all
261 .tsx files under src/ were outside the standalone lint run.

Verified both directions with a throwaway src/components/__lint_probe__.tsx containing a single
@typescript-eslint/no-empty-function violation:

glob result
**/*.ts (before) exit 0 — file never read
**/*.{ts,tsx} (after) 1 problem (1 error, 0 warnings), exit 1

What the widened glob turned up

Less than the issue expected — 15 problems in 5 files, all of them in src/__tests__/, none
in a component, screen or hook:

rule count severity
@typescript-eslint/no-var-requires 9 error
@typescript-eslint/no-empty-function 5 error
@typescript-eslint/no-unused-vars 1 warning

The reason the app code is already clean is that it is not, in fact, unlinted: CRA's
eslint-webpack-plugin lints every file webpack compiles (extensions: ['js','mjs','jsx','ts','tsx'],
context: src) using this repository's .eslintrc.js, and it runs on every build:dev /
widget:dev. Confirmed by experiment — appending export const __lintProbe = () => {}; to
src/App.tsx makes npm run build:dev fail 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 the
numbers: 0 no-non-null-assertion findings across 261 .tsx files under src/, versus 68 under
e2e/, which no build ever touches.

So the practical effect: the lint step becomes meaningful on its own instead of being a no-op
silently covered by a full webpack build, test files get linted at all, and npm run lint:fix
becomes usable on components.

Changes

  • package.jsonlint and lint:fix globs widened to *.{ts,tsx}.
  • 3 × dead const React = require('react'); removed from jest.mock() factories — the automatic
    JSX runtime ("jsx": "react-jsx") means the mock components never referenced it. Verified against
    the actual babel output, not just the type level. Removing that line left the
    transaction-document-error.test.tsx factory as the only one in the suite with a bare return
    body, 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.
  • 5 × new Promise(() => {})new Promise(() => undefined). Same never-settling promise, no
    empty function body. This is already the idiom used elsewhere in the suite.
  • 6 × inline // eslint-disable-next-line @typescript-eslint/no-var-requires on the require()
    calls that remain. jest.mock() factories are hoisted above the imports, so their dependencies
    cannot 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.js
    overrides block, would have left those three directives dead and would have missed
    src/util/__tests__ and src/hooks/wallets/__tests__.
  • .eslintrc.js**/*.stories.tsx added to ignorePatterns. tsconfig.json excludes story
    files, 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 linter step in dev.yml and prd.yml, i.e. the
    deploys, not just PR CI. Reproduced with a scratch probe.stories.tsx: Parsing error: ESLint was configured to run on … using parserOptions.project without the entry, clean with it. No story
    file exists today, so this is purely a guard — paired with the matching tsconfig.build.json entry
    below, so it covers the lint program and the build program alike.
  • package.json--max-warnings 0 added to lint. Two rules are configured as warnings
    (no-unused-vars, and no-non-null-assertion inherited from plugin:@typescript-eslint/recommended).
    Neither existing gate covers them: scripts/build.sh:58 hard-codes CI=false react-app-rewired build, so the app build never promotes warnings to errors even on Actions, and only
    scripts/build-widget.sh:69 inherits CI=true — and only for modules in the widget entry's graph.
    So npm run lint is the first warning gate for test files and for any src file that build:dev
    compiles 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 lint step; happy to
    split 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 substrings warning / error, and calls npm with
    --silent. This is a regression --max-warnings 0 introduced above: npm echoes the script line
    into the captured output, so grep -c "warning" counted the flag itself and the bot posted
    ⚠️ ESLint: 0 errors, 1 warnings on a completely clean tree — it did exactly that on this PR before
    the fix. The same anchoring also repairs a pre-existing miscount: grep -c "error" was matching the
    file name transaction-document-error.test.tsx and the summary line. Verified over four cases —
    clean tree 0/0; one planted warning 1/0; one planted error 0/1; an error in a file whose name
    contains "error" 0/1, where the old counter reported 1 warning / 3 errors. The
    ⚠️ ESLint: 0 errors, 1 warnings comment above is the pre-fix artefact from the abd3d21 run — the
    first after --max-warnings 0 landed; every run since posts nothing. It is left in place as the
    evidence.
  • .github/workflows/pr-review-bot.yml — the TypeScript step had the same $(grep -c … || echo "0")
    shape. grep -c prints 0 and exits 1 when nothing matches, so on every green run it captured
    0\n0 and appended a bare 0 line to $GITHUB_OUTPUT, which the runner rejects
    (Invalid format '0') and marks the step failed — visible as red annotations on the review check
    in this PR's own earlier runs, masked from the verdict only by continue-on-error. Changed to
    ERRORS=$(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 broken npm audit as a
    clean one. HIGH=$(jq …) / CRITICAL=$(jq …) were unguarded, so under the runner's bash -e a
    missing or unparseable audit-output.json aborted the step before either echo; both outputs stayed
    unset, and the bot's parseInt('') || 0 reported no critical vulnerabilities. npm audit runs
    nowhere else and there is no dependabot.yml, so that comment is the repo's only dependency-vuln
    signal in CI. The step now emits status=ok|failed and the bot says vulnerability status unknown
    rather than staying silent. Three deliberate details:
    • jq -e without a // 0 fallback — 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, .metadata without vulnerabilities, 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
      // 0 fallback would have called a clean audit.
    • jq -s + .[0] — two concatenated JSON documents would otherwise yield a two-line value and
      corrupt $GITHUB_OUTPUT, the same class as the tsc fix above. Not reachable from npm audit
      today (its output is a single document, checked), but free to rule out.
    • one retry, gated on the output rather than the exit code. npm audit exits non-zero whenever it
      finds 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 ones
      make 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, so status=failed is
      diagnosable rather than just visible.
  • tsconfig.build.json**/*.stories.tsx re-added. extends replaces exclude rather than
    merging, 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/ by build:lib and,
    with no @storybook/* dependency in the repo, would red the bot's tsc -p tsconfig.build.json step.
    Confirmed excluded again with tsc --listFiles.
  • tsconfig.build.json — the exclude list also carried the same .ts-only bug: **/*spec.ts and
    **/*.test.ts do not match .tsx. Inert today only because all 21 .test.tsx files live under
    src/__tests__, which a separate entry catches — but a .test.tsx placed next to its component would
    be compiled into the published dist/ by build:lib. Probed both ways with tsc --listFiles: on the
    old list a src/components/probe/thing.test.tsx is included, on the new one it is not. Written as four
    explicit patterns rather than {ts,tsx} because TypeScript's include/exclude do not support
    brace expansion — the brace form silently matched nothing, which the probe caught.
  • package.json — jest collectCoverageFrom widened from src/**/*.(t|j)s to
    src/**/*.{ts,tsx,js,jsx}. It carried the identical .ts-only bug. Coverage is not run in CI, so
    this 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-requires is suppressed only per-occurrence, on
six 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 deliberate
    exclusion as things stand — CONTRIBUTING.md documents that the Playwright suite intentionally
    does not run in CI. Pulling it in would mean 36 errors (28 no-empty-function, 4 ban-types,
    4 no-inferrable-types) and 112 warnings (68 no-non-null-assertion, 44 no-unused-vars) spread
    over 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.ts is in
    the lint glob but in no tsconfig include; it parses only because e2e/synpress/fixtures.ts
    imports it into the e2e program. A future test/ file that no e2e spec imports would be a fatal
    parse error, the same failure class the stories guard covers. This predates the PR — develop's
    glob already covered test/**/*.ts — and the durable fix is a tsconfig.eslint.json spanning
    src, test and e2e, which is more than this change should carry.
  • The review bot cannot clear a stale verdict. pr-review-bot.yml:190-193 returns early when a run
    finds 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 ⚠️ ESLint comment above survives its own fix. The
    remedy 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 ESLint and TypeScript steps still read "did not run" as "clean" — if the binary is missing or
    the glob matches nothing, both counters are 0 and the bot stays quiet. I fixed that shape for
    npm audit and deliberately not for these two: npm run lint fails a job elsewhere, npm audit is
    not checked anywhere. On feature PRs it is pr.yml's Build and test that goes red; on the
    auto-created develop -> main release PRs that job is skipped (pr.yml:19,
    if: github.head_ref != 'develop' — visibly SKIPPED on Release: develop -> main #1211), but the code it carries has already
    been through dev.yml's Run linter on the push to develop, which aborts that job before the
    deploy. (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 audit
    reds nothing anywhere. Extending status=ok|failed
    to all three would still be reasonable — as its own change, where the added comment noise can be
    judged on its merits.
  • e2e/tsconfig.json:17 include: ["./**/*.ts"] is the last remaining .ts-only glob after this
    PR. Inert — e2e/ holds 84 .ts and 0 .tsx — and e2e/ is out of the lint scope anyway.
  • Six dead eslint-disable directives for @typescript-eslint/no-explicit-any in
    refund-creditor-fields.tsx:8,10,12 and limit-request-fields.tsx:10,12,14 — that rule is off
    repo-wide, so the directives suppress nothing. Both files are .tsx, which is why the widened
    scope surfaces them. Left alone to keep this PR to the lint plumbing; they cost nothing unless
    --report-unused-disable-directives is ever switched on.

Verification

Node 20.20.2, npm 10.8.2 (matching CI).

  • npm run lint → exit 0, 409 files, 0 errors, 0 warnings
  • planted unused var → ESLint found too many warnings (maximum: 0), exit 1 (the --max-warnings
    gate actually bites)
  • planted probe.stories.tsx → exit 0 with the ignorePatterns entry, fatal parse error without it
  • --report-unused-disable-directives → none of the six new directives is unused
  • patched bot counter → correct across all four cases above, and confirmed on the real runs of this
    PR: the review check carried four failure annotations before the fixes
    (Invalid format '0' / Unable to process file command 'output' successfully., one pair from each
    step), two once 6a1429c had fixed the ESLint step, and none from 94d36e0 onward — leaving only
    the unrelated, pre-existing Node 20 deprecation warning
  • the new "status unknown" branch does not cry wolf: the real CI run on the head commit posted no
    comment at all, so npm audit there resolved to status=ok
  • npm run test → 60 suites, 604 tests passed
  • npm run build:dev → success
  • npm run widget:dev → success

Danswar added 2 commits July 30, 2026 12:35
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.
@github-actions

Copy link
Copy Markdown

🤖 PR Review Bot

⚠️ ESLint: 0 errors, 1 warnings


This is an automated review. Please address the issues above.

Danswar added 5 commits July 30, 2026 13:04
--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.
@Danswar

Danswar commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

7 review passes to zero findings. What they caught, since most of it is not legible from the final diff:

  • The first attempt suppressed no-var-requires with an .eslintrc.js overrides block. The repo already had a convention for exactly this case — inline directives with a hoisting comment at support-issue-receiver-iban.test.tsx:120,123,309 — which the override contradicted, left dead, and which it also failed to cover for src/util/__tests__ and src/hooks/wallets/__tests__. Replaced with six inline directives.
  • Story files needed guarding in both .eslintrc.js and tsconfig.build.json: tsconfig.json excludes them, so under the widened glob one would be a fatal parse error and red the Run linter step that gates the dev/prd deploys. The tsconfig.build.json half is easy to miss because extends replaces exclude rather than merging it.
  • --max-warnings 0 regressed this repo's review bot: npm echoes the flag into the output the bot greps, so grep -c "warning" counted the command line and it reported 0 errors, 1 warnings on a clean tree. Fixing that surfaced two more counting defects in the same workflow — a stray line written into $GITHUB_OUTPUT on every green run (Invalid format '0', visible as failure annotations on this PR's earlier runs, now zero), and a broken npm audit reading as a clean audit.

Deliberately not done, both under Out of scope in the description: extending status=ok|failed to the ESLint and TypeScript steps, and fixing the bot's inability to clear a stale comment.

On that last point — the ⚠️ ESLint: 0 errors, 1 warnings comment above is the pre-fix artefact of that regression, posted by the abd3d21 run. Every run since posts nothing, and the branch is at 0 errors / 0 warnings across all 409 linted files. I left it in place because it is the in-thread evidence for the bug the fix addresses, and the bot cannot clear it itself — happy to delete it if you would rather the thread read clean.

@Danswar
Danswar marked this pull request as ready for review July 30, 2026 17:27
@TaprootFreak
TaprootFreak merged commit 84bfedb into develop Jul 30, 2026
6 checks passed
@TaprootFreak
TaprootFreak deleted the fix/lint-glob-tsx branch July 30, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

npm run lint covers no .tsx file — 261 of 408 source files are never linted

2 participants