Repository navigation
chore(lint): replace ESLint with type-aware Oxlint and Sketchi guardrails - #359
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
1be0038 to
6dd75ce
Compare
9847b20 to
9d27d4e
Compare
6dd75ce to
5087b20
Compare
Merge activity
|
…ails (#359) ## What changed Replaces ESLint with one type-aware Oxlint gate over the whole repository, and adds a tested Sketchi plugin for the one durable policy the built-in rules don't cover. - **Tooling:** - Pinned `oxlint` 1.87.0, `@oxlint/plugins` 1.87.0, and `oxlint-tsgolint` 7.0.2003. 7.0.2004 was published today, inside the repo's 24-hour `minimumReleaseAge`. - Removed `eslint`, `@nx/eslint`, `@typescript-eslint/parser`, `eslint-config-prettier`, `eslint.config.mjs`, and the inferred Nx `lint` target. - `@nx/eslint-plugin` stays only as the Oxlint JS plugin that provides `@nx/enforce-module-boundaries`. ESLint itself is not installed. - **`.oxlintrc.json`:** the correctness category as errors, plus a curated set of type-aware TypeScript, import, React-hooks, Unicorn, and Vitest rules. Options: `typeAware`, `denyWarnings`, and `reportUnusedDisableDirectives: error`. Every rule turned off is commented with its reason. `.oxlintrc.json` replaces `eslint.config.mjs` in Nx `sharedGlobals`, so architecture changes still mark every project affected. - **`pnpm run lint`:** builds the package declarations that type-aware rules resolve (cached Nx `build` of `tag:scope:package`, the same prerequisite `typecheck` already has), then runs `oxlint`. It takes about 3 seconds over roughly 600 files. CI runs it after the build gate. - **`sketchi/no-react-effects`** in `tools/oxlint/sketchi-plugin.ts`: - Covers `useEffect`, `useLayoutEffect`, and `useInsertionEffect`. - Resolves aliases through scope analysis: renamed imports, namespace and default imports, computed members, destructuring, namespace alias chains, detached references, awaited `import("react")`, and hook re-exports. - Re-exporting React's default or namespace (`export { default as R } from "react"`, `export * as R from "react"`) is reported. - So is any other escape of the namespace: exporting it, spreading it, assigning it, passing it to a function, putting it in an array, or laundering it through an assertion. Types, `typeof React`, `void`, and JSX member tags such as `<React.Fragment>` stay allowed. - **`sketchi/no-restricted-import-types`:** `no-restricted-imports` does not inspect TypeScript import types, so `typeof import("effect")` used to slip past the Effect-free policy. This rule mirrors the import policy for `import("…")` types in every scope; each restricted module covers its subpaths, and `@effect` covers the whole scope. - Both rules are tested with Oxlint's `RuleTester` (`pnpm run test:lint-rules`, 48 cases). The cases cover valid code, violations, false positives, and alias and escape forms. False positives include same-named locals and other modules, shadowed parameters, `Effect.gen`, type-only imports, and prefix look-alikes such as `effects-polyfill`. - **Remaining effects as reasoned exceptions:** - Icon detail: a modal focus trap and focus restore on a window `keydown` listener. - The Code Mode export harness: the window bridge that Browser Rendering polls. - An Excalidraw test double. - Vendored AI Elements, through a directory override that also relaxes upstream's React idioms. - **Suppressions:** every suppression is a one-line `oxlint-disable-next-line <rule> -- <reason>`. A graph test fails on any directive without a reason. - **`tools/project-graph.test.ts`:** - The ESLint `lintText` probes become probes through the real Oxlint CLI and config. They cover boundaries in source, Vitest config, and Storybook files; the workspace-input allowances; Effect-free projects, including deep subpaths and import types; stable versus unstable Effect; the reviewed-adapter path; Zod, including deep subpaths; and React effects. A mutation check confirmed these probes fail when a guardrail is removed. - The graph test also checks that every scope restricting imports restricts exactly the same modules for import types. - The regex scans for Effect, Zod, and unstable imports are replaced by lint rules plus assertions that the lint overrides match the project classification and the schema-boundary file list exactly. - Zod is also barred from every workspace manifest, and unstable adapters are an explicit file inventory. - **Docs:** README, the architecture guide, and AGENTS.md describe the gate. AGENTS.md Proof gains `pnpm run lint`, and its delegated-agent model line now says `gpt-6.1-sol` (`gpt-6-astra` for harder slices). ## Decisions - **Nx boundaries:** - The `scope:*`/`type:*` constraints and `enforceBuildableLibDependency` are unchanged. - Both workspace-input `allow` entries are still required; without them, all five Vite configs fail. - The Studio `checkDynamicDependenciesExceptions` entry is removed. #341 deleted the `import("@sketchi/studio-projects/server")` type queries that made Nx record a dynamic Playground→Studio edge. That edge is now static, and lint is clean without the exception. - **Effect guardrails:** - Import policies use Oxlint's built-in `no-restricted-imports`: no Zod; unstable Effect only in `src/internal/effect-unstable-*.ts`; no Effect in pure or framework-native projects (now including `packages/icons/catalog` from #354) except diagram-core's schema-boundary files. The rule also catches re-exports and dynamic `import()`. - Patterns are `effect/**`, `@effect/**`, `zod/**`, and `effect/unstable/**`. Oxlint's `group` globs match one segment per `*`, unlike ESLint's gitignore matching, so deep paths such as `@effect/platform-node/NodeRuntime` need `**`. A graph test rejects single-segment patterns, and CLI probes cover deep paths and import types. - The runtime-boundary inventory (`run*`, `ManagedRuntime`, `NodeRuntime.runMain`) and the Promise-site inventory stay as graph tests. They resolve signatures through the TypeScript checker, which catches cross-module aliases and parameters typed as the Effect namespace. Oxlint JS plugins have no type information and tsgolint has no custom-rule API, so moving them would weaken them. - **Prismantix rules evaluated and not adopted:** - `no-legacy-schema-classes`: `Schema.Class` is Sketchi's record convention (115 uses) and a supported Effect v4 API. - `no-effect-sleep-in-tests`: Sketchi's test sleeps run under `TestClock`, so they are deterministic, or poll real child processes in `it.live`. ## Open questions from #281 - **Strict rules now versus later.** Enabled now: correctness plus `no-explicit-any`, `no-floating-promises`, `no-misused-promises`, `switch-exhaustiveness-check`, `return-await`, `only-throw-error`, `prefer-promise-reject-errors`, `no-unnecessary-type-assertion`, `import/no-cycle`, `import/no-duplicates`, `rules-of-hooks`, `eqeqeq`, and `prefer-node-protocol`. These were clean or remediated in the previous PR. Deferred until measured remediation (counts on this tree): `no-unsafe-*` about 1,800, `strict-boolean-expressions` 633, `require-await` 129, `no-confusing-void-expression` 113, `unicorn/no-array-sort` 92, `node/no-process-env` 90, `no-await-in-loop` 76, `no-unnecessary-condition` 72, `no-non-null-assertion` 25, `no-deprecated` 21. - **Nx target or repo-wide gate.** Repo-wide gate only. Oxlint lints the whole tree in one process faster than Nx can schedule per-project targets, so a second per-project target would only add duplicate work. It also covers `scripts/` and the root `tools/` files, which the per-project ESLint target never linted. - **Oxfmt exclusions.** Handled in the Oxfmt PR on top of this one. Lint ignores only generated output: `routeTree.gen.ts`, Wrangler `cloudflare-workers.d.ts`, `dist`, `.output`, `.wrangler`, coverage, Storybook builds, and `.memory`. - **React effects.** The adapters and violations are listed above. The product violations were refactored in the first PR of this stack. ## How it was verified - `pnpm run lint`: zero diagnostics. - `pnpm run test:lint-rules`: 48 tests. `tools/tsconfig.json` (from #352) now includes `tools/oxlint`, so `pnpm run test:tools` typechecks the plugin and its tests. - `pnpm run test:tools`: 183 tests, including the Oxlint probes. - `nx affected -t typecheck,test`: all projects. - `pnpm run toolchain:verify`. Refs #281
9d27d4e to
af2123b
Compare
5087b20 to
8dd19fb
Compare
Sketchi Eval Harness PreviewStatus:
|
Sketchi Excalidraw Workspace PreviewStatus:
|
Sketchi Icons PreviewStatus:
|
Sketchi Web PreviewStatus:
|
Sketchi Playground / Studio PreviewStatus:
|

What changed
Replaces ESLint with one type-aware Oxlint gate over the whole repository, and adds a tested Sketchi plugin for the one durable policy the built-in rules don't cover.
oxlint1.87.0,@oxlint/plugins1.87.0, andoxlint-tsgolint7.0.2003. 7.0.2004 was published today, inside the repo's 24-hourminimumReleaseAge.eslint,@nx/eslint,@typescript-eslint/parser,eslint-config-prettier,eslint.config.mjs, and the inferred Nxlinttarget.@nx/eslint-pluginstays only as the Oxlint JS plugin that provides@nx/enforce-module-boundaries. ESLint itself is not installed..oxlintrc.json: the correctness category as errors, plus a curated set of type-aware TypeScript, import, React-hooks, Unicorn, and Vitest rules. Options:typeAware,denyWarnings, andreportUnusedDisableDirectives: error. Every rule turned off is commented with its reason..oxlintrc.jsonreplaceseslint.config.mjsin NxsharedGlobals, so architecture changes still mark every project affected.pnpm run lint: builds the package declarations that type-aware rules resolve (cached Nxbuildoftag:scope:package, the same prerequisitetypecheckalready has), then runsoxlint. It takes about 3 seconds over roughly 600 files. CI runs it after the build gate.sketchi/no-react-effectsintools/oxlint/sketchi-plugin.ts:useEffect,useLayoutEffect, anduseInsertionEffect.import("react"), and hook re-exports.export { default as R } from "react",export * as R from "react") is reported.typeof React,void, and JSX member tags such as<React.Fragment>stay allowed.sketchi/no-restricted-import-types:no-restricted-importsdoes not inspect TypeScript import types, sotypeof import("effect")used to slip past the Effect-free policy. This rule mirrors the import policy forimport("…")types in every scope; each restricted module covers its subpaths, and@effectcovers the whole scope.RuleTester(pnpm run test:lint-rules, 48 cases). The cases cover valid code, violations, false positives, and alias and escape forms. False positives include same-named locals and other modules, shadowed parameters,Effect.gen, type-only imports, and prefix look-alikes such aseffects-polyfill.keydownlistener.oxlint-disable-next-line <rule> -- <reason>. A graph test fails on any directive without a reason.tools/project-graph.test.ts:lintTextprobes become probes through the real Oxlint CLI and config. They cover boundaries in source, Vitest config, and Storybook files; the workspace-input allowances; Effect-free projects, including deep subpaths and import types; stable versus unstable Effect; the reviewed-adapter path; Zod, including deep subpaths; and React effects. A mutation check confirmed these probes fail when a guardrail is removed.pnpm run lint, and its delegated-agent model line now saysgpt-6.1-sol(gpt-6-astrafor harder slices).Decisions
scope:*/type:*constraints andenforceBuildableLibDependencyare unchanged.allowentries are still required; without them, all five Vite configs fail.checkDynamicDependenciesExceptionsentry is removed. refactor(playground): dedupe server handlers and build layers once #341 deleted theimport("@sketchi/studio-projects/server")type queries that made Nx record a dynamic Playground→Studio edge. That edge is now static, and lint is clean without the exception.no-restricted-imports: no Zod; unstable Effect only insrc/internal/effect-unstable-*.ts; no Effect in pure or framework-native projects (now includingpackages/icons/catalogfrom refactor(icons): extract the icon catalog into @sketchi/icon-catalog #354) except diagram-core's schema-boundary files. The rule also catches re-exports and dynamicimport().effect/**,@effect/**,zod/**, andeffect/unstable/**. Oxlint'sgroupglobs match one segment per*, unlike ESLint's gitignore matching, so deep paths such as@effect/platform-node/NodeRuntimeneed**. A graph test rejects single-segment patterns, and CLI probes cover deep paths and import types.run*,ManagedRuntime,NodeRuntime.runMain) and the Promise-site inventory stay as graph tests. They resolve signatures through the TypeScript checker, which catches cross-module aliases and parameters typed as the Effect namespace. Oxlint JS plugins have no type information and tsgolint has no custom-rule API, so moving them would weaken them.no-legacy-schema-classes:Schema.Classis Sketchi's record convention (115 uses) and a supported Effect v4 API.no-effect-sleep-in-tests: Sketchi's test sleeps run underTestClock, so they are deterministic, or poll real child processes init.live.Open questions from #281
no-explicit-any,no-floating-promises,no-misused-promises,switch-exhaustiveness-check,return-await,only-throw-error,prefer-promise-reject-errors,no-unnecessary-type-assertion,import/no-cycle,import/no-duplicates,rules-of-hooks,eqeqeq, andprefer-node-protocol. These were clean or remediated in the previous PR. Deferred until measured remediation (counts on this tree):no-unsafe-*about 1,800,strict-boolean-expressions633,require-await129,no-confusing-void-expression113,unicorn/no-array-sort92,node/no-process-env90,no-await-in-loop76,no-unnecessary-condition72,no-non-null-assertion25,no-deprecated21.scripts/and the roottools/files, which the per-project ESLint target never linted.routeTree.gen.ts, Wranglercloudflare-workers.d.ts,dist,.output,.wrangler, coverage, Storybook builds, and.memory.How it was verified
pnpm run lint: zero diagnostics.pnpm run test:lint-rules: 48 tests.tools/tsconfig.json(from chore(tools): typecheck repository tools in CI #352) now includestools/oxlint, sopnpm run test:toolstypechecks the plugin and its tests.pnpm run test:tools: 183 tests, including the Oxlint probes.nx affected -t typecheck,test: all projects.pnpm run toolchain:verify.Refs #281