chore(lint): require a check on JSON parsed in package source
Merged
OK
9d ago+880+880+012345678901234567890123456789-232-232-012345678901234567890123456789
What and why
JSON.parseand.json()returnany. Typing that result (as T,const x: T =,returnfrom a function typedT) skips every check, so a bad body surfaces later as aTypeErroror a wrong value.- New rule
e2e/no-unchecked-jsonin the local plugin, on forpackages/*/src/**. Parsed JSON must be typedunknown(orPromise<unknown>) wherever it lands, until a schema or a check narrows it.- It covers assertions, declarations (an untyped one too), assignment to a typed
let, and returns, including from an untyped function. An inline callback such as.then((r) => r.json())is exempt, because the call it feeds is checked where it lands. - It looks through
await,?.,!,.catch(...)and.then((r) => r.json()). - It is AST-only, so passing parsed JSON straight to a typed parameter still gets through.
- It covers assertions, declarations (an untyped one too), assignment to a typed
- 26 hits fixed, most with a real check:
- oauth: zod schemas plus one
readJsonBody(response, schema, subject)inoauth/errors.ts. A body that is not JSON, or has the wrong shape, becomesFLOW_FAILEDnaming the vendor. - config: typed readers. A malformed inline source map from an earlier loader (
tsx) is dropped instead of failing the import. - eas, limrun: typed readers (no zod there).
isRecordmoves topackages/eas/src/json.ts. - lightpanda:
/json/versionread asunknown. A body without a version now reportsunknowninstead of polling until the startup timeout.
- oauth: zod schemas plus one
- 3 reasoned disables:
agent/act-validation.ts:117: our ownJSON.stringifyoutput.web/src/observation.ts:163: page-reported metadata, as unchecked as the structured branch next to it.web/src/browser.ts:521: the publicjson<T = unknown>(), where the caller namesT.
// packages/e2e/src/oauth/token-endpoint.ts
return readJsonBody(response, TokenResponseSchema, `the ${vendor} token response`, 'is missing access_token');
Verified
Ran it locally: yes
-
Rule tests: 18 unchecked shapes reported, 13
unknown-typed or non-JSON ones pass.srcis clean. -
Each behavior change has a unit test that fails on the base and passes here. For example:
input base this branch token endpoint answers HTML SyntaxErrorFLOW_FAILED: the Acme token response is not JSONdevice-flow poll answers nullTypeErrorFLOW_FAILEDEAS createDeviceRunSessionwithout its fieldsTypeErrorEAS: unexpected response to createDeviceRunSessionChatGPT device grant without code_verifierposts the string "undefined"FLOW_FAILEDbefore the exchange -
Real loader (
node --enable-source-maps --import packages/e2e/src/config/register.ts), a.tsfile carrying an inline map of{"version":3}:base this branch TypeError: Cannot read properties of undefined (reading 'map') at new TraceMapError: boom on line 3, mapped to the file -
Testbed suite on the built packages: 78 passed.
Checklist
- Changeset added (
pnpm changeset) for any change to a published package, or the change is testbed, docs, or CI only. - Breaking change: title carries
!, the changeset body names what breaks and what replaces it, and the deprecation warning landed a window earlier (stability policy in CONTRIBUTING.md). - Docs updated in the same PR: the
docs/**/*.mdxpage for the behavior, andskills/e2e/if the skill describes it. - Contract change: emitted
.d.tsreviewed,tests/types/sdk-types.tsupdated; wire change edits the schema and both fixtures. - Engine contract change (
e2e/engine): changesets fore2e,@e2e-dev/web, and@e2e-dev/mobile.