Lint the full JS package in CI, not just src - #984
Conversation
The lint script only covered src/**, so eslint errors in test files and the Node build scripts were invisible to the CI lint gate in format.yml. Widen the script to eslint . and make the widened surface clean: - Disable @typescript-eslint/no-explicit-any for test files — tests routinely poke at private state and mock boundaries via any, and the rule produced 249 errors there; drop the per-line disables it makes redundant. - Declare Node globals for the .mjs build scripts and Node-run test harnesses via the globals package. - Honor the underscore convention for intentionally unused parameters in @typescript-eslint/no-unused-vars. - Fix the remaining import/order and prefer-const violations.
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Widens the JS lint gate from src/** to the whole package and cleans the newly-covered surface. Behavior-neutral: no production src/ file is touched — every code edit is a comment removal, an import blank line, or the let→const restructure in the prebid mock. Verified locally: npx eslint . exits 0 (1.7s), and the dist ignore holds (dropped a deliberately-broken probe file into dist/, still exit 0). Approving; the findings below are all non-blocking.
Non-blocking
🤔 thinking
- Blanket
no-explicit-anyoff fortest/**: defensible at 249/280 and it matches the existing file convention, but it also covers shared helpers/fixtures where types are cheap, and newanyin tests now lands with no signal. (eslint.config.js:53) .mjsgets a weaker gate than "full package" implies: project rules are scoped to**/*.ts(x), so the newly-covered build scripts only getjs.configs.recommended+ tseslint defaults. (eslint.config.js:46)
♻️ refactor
- The
^_convention does not reach.mjs: theno-unused-varsoptions live in the**/*.tsblock, but the rule is active with default options on.mjs. (eslint.config.js:38)
🌱 seedling
linthas no--max-warnings=0: 0 warnings today, so no live gap — but any future'warn'-level rule passes CI silently, which is the same class of hole this PR exists to close. (package.json:13)
📌 out of scope
- A second TypeScript package is still unlinted:
crates/trusted-server-integration-tests/browser/(Playwright specs,helpers/,global-setup.ts,playwright.config.ts) has nolintscript and no format.yml job. If the intent of #985 is "no unlinted TS in CI", that is the remaining gap — worth a follow-up issue rather than scope creep here.
⛏ nitpick
- Stray blank lines where disable directives were removed: ten spots in
test/integrations/gpt/ad_init.test.ts. Seven (1408, 1491, 1523, 1559, 1759, 1820, 1895) read fine as paragraph breaks; three inside two-line arrow bodies (1344, 1374, 1731) read oddly. Prettier will not collapse either.
👍 praise
- Forward-reference comment in the prebid mock: the
let→constrestructure could read as a TDZ hazard; the comment states exactly why it is not. (test/integrations/prebid/index.test.ts:25) - Behavior-neutral diff: keeping the gate widening free of any production change makes "widen the gate, then clean the surface" easy to trust in review.
CI Status
All 19 checks pass on 959f4b9.
- cargo fmt / clippy: PASS
- rust tests (fastly, axum, cloudflare, spin, parity, CLI): PASS
- vitest: PASS
- format-typescript (eslint + prettier): PASS
- format-docs: PASS
- integration + browser integration tests: PASS
| }, | ||
| // Tests routinely poke at private state and mock boundaries via `any` | ||
| { | ||
| files: ['test/**/*.ts', 'test/**/*.tsx'], |
There was a problem hiding this comment.
🤔 thinking — No change requested; recording the tradeoff. Turning the rule fully off across test/** also covers shared helpers and fixtures where a real type is cheap, and it means new any in tests lands with no signal at all — not even a warning.
The narrower alternative would have been a file-level /* eslint-disable @typescript-eslint/no-explicit-any */ in the handful of GPT/prebid mock files that account for most of the 249. Given the count and that any is already the file convention here, the blanket approach seems like the right call for this PR.
There was a problem hiding this comment.
Went with the narrower alternative in 7aef9a5. Measured first: 255 occurrences, 246 of them in six mock-heavy files — those get a file-level disable with a reason. The tail got real types instead: fetch stubs are as unknown as typeof fetch, registry uses as AdUnit via a type-only import, and config's 'info' as any cast was dropped entirely ('info' is already in the logLevel union). One scoped disable-next-line remains for gpt_bootstrap's AnyRecord alias. New test files now get full signal, and since ESLint 9 reports unused disable directives as warnings, --max-warnings=0 fails CI if any directive goes stale. Lint/format clean, 488 tests pass, no new tsc --noEmit errors vs baseline.
There was a problem hiding this comment.
Superseded by 23ae86e: the per-file disables are gone too. All 247 test-tree anys are now really typed. The main mechanisms: Omit<Window, 'tsjs'> intersections with Partial<TsjsApi> for the window mocks (which was the actual fix for the intersection problem the old ad_init comment cited as the reason for any), structural TestPbjs/TestAdUnit types over the prebid hoisted mock, and deleting casts that turned out unnecessary (untyped vi.fn() already assigns to typeof fetch). One documented as unknown as double-cast survives, for a test that deliberately feeds malformed bids input. no-explicit-any now holds across the whole package with zero exceptions in test/. Verified: eslint/prettier clean, 488 tests pass, repo tsc --noEmit errors drop 50 -> 12 (all remaining pre-existing in untouched files).
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Reviewed the lint-surface expansion against main. The change is low risk and behavior-neutral overall; all CI checks and clean-checkout JS validation pass. I found one medium-severity lint-environment gap, noted inline.
…fail on warnings - Use globals.nodeBuiltin instead of globals.node for .mjs files so CommonJS-only names (__dirname, require, module) still fail no-undef in ES modules - Lift the no-unused-vars underscore ignore patterns to a top-level rules block so the convention also covers the .mjs build scripts, where tseslint recommended otherwise applies pattern-less defaults - Add --max-warnings=0 to the lint scripts so rules landing at 'warn' cannot pass CI silently - Collapse leftover blank lines in two-line arrow bodies in ad_init.test.ts from the eslint-disable directive removals
Remove the test/** rule-off block so the rule stays live for new test
files, and handle the 255 existing occurrences by tier:
- File-level eslint-disable in the six files where `any` is structural
to the mocking approach (prebid/index, gpt/ad_init, gpt/index,
core/request, core/index, didomi/index — 246 occurrences)
- Real types where they were cheap: fetch stubs cast via
`as unknown as typeof fetch` (auction, click, proxy_sign), `as
AdUnit` with a type-only import in registry, and the config cast
dropped entirely ('info' is already in the logLevel union)
- A scoped eslint-disable-next-line for gpt_bootstrap's AnyRecord alias
ESLint 9 reports unused disable directives as warnings by default, so
with --max-warnings=0 any directive made redundant later fails CI.
Replace the six file-level eslint-disable headers and the one scoped directive with actual types, eliminating all 247 remaining any usages in test/. The rule now applies to the whole test tree with no exceptions. - Window mocks: Omit<Window, 'tsjs'> intersections with Partial<TsjsApi> from src/core/types, replacing the any-typed tsjs surfaces; this was the real fix for the intersection problem an old ad_init comment used to justify any - Prebid: structural TestPbjs/TestAdUnit/TestBid types over the hoisted mock; ~50 argument-site casts collapse into declaration-site as TestPbjs casts (a plain annotation cannot compile against Prebid's contravariant requestBids signature) - core/index: drop the local Window.tsjs declare-global that conflicted with the src declaration - didomi: local TestDidomiConfig mirroring the non-exported src type - Many casts turned out gratuitous (untyped vi.fn() already assigns to typeof fetch; ad-unit literals already satisfy AdUnit) and were deleted outright One deliberate double-cast survives: prebid feeds a non-array bids value through as unknown as TestAdUnit[] to prove the shim normalizes malformed input. Type-only change: no statement, assertion, or mock behavior touched. Side effect: repo tsc --noEmit errors drop from 50 to 12, all remaining ones pre-existing in files this PR does not touch.
Fixes #985.
Problem
The CI lint gate (
npm run lintin format.yml) only coveredsrc/**/*.{ts,tsx}, so eslint errors in test files, the Node build scripts, and config files were invisible: 280 of them had accumulated (npx eslint .on main), and new ones land unnoticed.Fix
Widen the lint script to
eslint . --max-warnings=0and make the widened surface clean, with no rule exceptions:no-explicit-anyviolations are fixed with real types rather than suppressed. Window mocks are typed asOmit<Window, 'tsjs'>intersections withPartial<TsjsApi>fromsrc/core/types; the prebid hoisted mock gets structuralTestPbjs/TestAdUnit/TestBidtypes; many casts were simply unnecessary and are deleted (untypedvi.fn()already assigns totypeof fetch). One documentedas unknown asdouble-cast remains, for a test that deliberately feeds malformed input. Side effect: repo-widetsc --noEmiterrors drop from 50 to 12, all remaining ones pre-existing in files this PR does not touch.globals.nodeBuiltin) for*.mjsbuild scripts and Node-run test harnesses, soprocess/console/Bufferresolve but CommonJS-only names (__dirname,require) still failno-undef.^_underscore convention in@typescript-eslint/no-unused-varsvia a top-level rules block covering every linted file, not just*.ts(x).--max-warnings=0so rules landing atwarnlevel cannot pass CI silently; this also fails on unused eslint-disable directives, keeping the remainingsrc/directives honest.import/orderin five files, oneprefer-const(mockPbjsdeclaration merged with its single assignment).No workflow changes: format.yml already runs
npm run lint; the script now covers the whole package.Verification
npx eslint . --max-warnings=0exits 0npm run formatcleannpx vitest run: 488 passed, 0 failed (32 files)npx tsc --noEmit: no new errors vs main (50 -> 12)