Repository navigation
Commit 67ea334
refactor(devx): move the four inline ESLint rule plugins into a private workspace package (#26177)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi97zYZvbkizsECxcnTpJKKpP)_
Fixes #26093
Clause-②: no
Metadata-protocol: none
## What changed
The four inline rule plugins in `eslint.config.mjs` (slot-lookup,
query-options, verify-stand-in, comment-swallow) move into a private
workspace package, `packages/eslint-plugin`
(`@objectstack/eslint-plugin`, `"private": true`, no build step). The
root config imports it by relative path and keeps only configuration.
- **Plain ESM, not TypeScript.** `pnpm lint` loads the config with
native `import`, and the repo's `engines` floor is Node 22.0; a `.ts`
source would need Node 22.18 type stripping. `tsconfig.json` sets
`allowJs` + `checkJs`, so `tsc` reads the rule sources too (JSDoc types;
AST helpers are deliberately untyped, documented in each file).
- **Rule ids, messages and plugin namespaces are unchanged.** Each rule
keeps its prefix (`slot-lookup/…` etc.), so the ratchet scripts and
their rule-id / message matching see the same thing.
- **Configuration stays in the config.** `SLOT_LOOKUPS`,
`UNCONTRACTED_SLOTS` and `VERIFY_STAND_IN_CHECKS` stay in
`eslint.config.mjs` (the slot-lookup selectors read the first two, and
both the message text and `check:verify-stand-in` say they live there).
Those two rules now get the lists as **required** options: a config that
drops them fails ESLint's schema validation instead of running against
an empty set (pinned in the tests).
- **The export surface of `eslint.config.mjs` is kept.** Every named
export the scripts import is still exported (re-exported from the
package). The only one dropped is `COMMENT_SWALLOW_CASES`, which nothing
imported. It moved into the tests.
- **The self-assert block is now tests.** `assertCommentSwallow()` and
its 17 pinned cases became `test/comment-swallow.test.ts`, along with
the "files glob must cover plain JS" assertion.
- `pnpm-lock.yaml`: only the new importer (+18 lines, from `pnpm
install`). No new resolutions. `pnpm-workspace.yaml` untouched
(`packages/*` glob). No baseline file changed.
## Behaviour preserved: before / after
Before = `origin/main` d1ea236, after = head 4d18cf1, same worktree:
| reading | before | after |
|---|---|---|
| `pnpm lint` (exact root script) | exit 0, no output | exit 0, no
output; byte-identical (diff empty apart from the worktree path in the
pnpm banner) |
| `pnpm check:slot-lookup` | 100 unswept sites in 25 files; self-test 5
shapes / 8 canonical | identical output (diff empty) |
| `pnpm check:query-options-erasure` | 50 non-test sites in 15 files;
test surface 236 in 47 | identical output (diff empty) |
| `pnpm check:verify-stand-in` | 2 guarded, 18 exempt, 10 call sites, 0
asserted | identical output (diff empty) |
`pnpm lint` has nothing to report on either side, so the empty diff is
weak evidence by itself. The three ratchets are the strong reading: they
import this config, lift its grandfathering, and count each rule's
reports per file, and their self-tests drive the live rules through the
config ("reports all 5 erasure shapes"). Their outputs are
byte-identical before and after.
## Comments: moved, not rewritten (#25919 ruling A)
Of the 635 non-blank lines removed from `eslint.config.mjs`, 585 appear
verbatim in the package. Each plugin file has a short header saying the
history moved from the config, so "above" / "below" in it refer to that
file. The other 50 lines are accounted for:
- rule code lines that gained a JSDoc type, an `export`, or read options
instead of the config constants;
- the `assertCommentSwallow()` function body, rewritten as vitest
assertions;
- two pointers that named `assertCommentSwallow` (one in the package,
one left in the config) now point at the test;
- the cases docblock, whose "runs where the config loads" sentence is no
longer true.
Config-block comments that justify configuration (scope, ignores, the
lists) stay where they were. The query-options block's "dedicated plugin
rule, not selectors" + known-residual paragraphs moved, since they
describe the rule. Three new one-line pointers name the rule file.
## Acceptance
- [x] `pnpm lint` identical before/after: 0 errors, same (empty) warning
set. Table above.
- [ ] `wc -l eslint.config.mjs` under about 400: **649** (was 1,271).
Deviation, see below.
- [x] Tests run in the lane `packages/lint` runs in.
`scripts/partition-test-shards.mjs` over `turbo ls --affected` puts
`@objectstack/eslint-plugin` on Test Core shard 1/8. Locally: `pnpm
--filter @objectstack/eslint-plugin test` gives 4 files, 73 tests, all
pass.
- [x] `pnpm typecheck` covers the new package. `turbo run typecheck
--filter=@objectstack/eslint-plugin` runs `tsc --noEmit` with exit 0.
`--listFiles` shows all 5 `src/*.mjs`, all 5 `test/*.ts` and
`vitest.config.ts`. `check:type-check-coverage` and
`check:type-check-debt` are green.
### Why 649 and not about 400
The card's estimate takes 1,271 minus the "906 lines of plugins". But
lines 1–120 of the old file are the lint-script header (`--stack-size`
rationale, ~85 lines) and the subpath / defineX constants, and several
constants in the 1–906 range are configuration that has to stay (slot
lists, both baselines, `QUERY_OPTIONS_TEST_GLOBS`,
`VERIFY_STAND_IN_CHECKS`, the rule ids). What remains is 225 code lines
and 402 comment lines. All of those comments explain configuration, not
rule history. Under ruling A ("存量不横扫") and the dispatch's "其它注释不改",
getting to ~400 would mean sweeping them. That is not done here. If the
seat wants the number, it is a separate comment-budget decision.
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at 4d18cf1: 68 families. Each was run on
that head, with its exit captured before any pipe. All 68 exit 0.
`--ran` reconciliation: "68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN".
Build-reading gates (`check:dts-closure`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`, `check:sourcemap-no-sources-content`) ran
after a full `pnpm build` (119/119 tasks). The plugin-teardown self-test
ran after `git fetch --unshallow`.
The first run had two reds, both from one string: a moved
comment-swallow case contains the example line ` * import {
createHonoApp } from "@objectstack/hono";`.
`check:cross-package-test-inputs` and `check:test-source-alias` scan
test source text and read that string as an undeclared workspace import.
The test now splits the specifier across a `+` (the runtime string is
byte-identical; the original case list already split `/a*` + `/` the
same way). Fixed in 4d18cf1.
## Acceptance notes
- **Gate selection after this move (finding, not fixed here: outside the
file surface).** `scripts/ci/select-gate-families.sh` runs the
`slot_lookup` and `query_options_erasure` families for a `packages/*`
path only when it is TS source (`is_ts_source`). Before, these rules
lived in `eslint.config.mjs`, a root-config path that runs every family.
Now they live in `packages/eslint-plugin/src/*.mjs`, so a later PR that
edits only a rule file would skip both ratchet re-measures in CI. Their
CI coverage would then be `pnpm lint` (always runs) and the package's
own RuleTester suite in Test Core. This PR itself touches
`eslint.config.mjs`, so every family runs on it. Suggested fix for the
seat to file: add `packages/eslint-plugin/*` to those two families'
trigger set.
- `@objectstack/eslint-plugin` is private and has no `dist`; it ships
nothing, hence `skip-changeset`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CHC483oAMSwyfmTu1xajpZ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 43b4715 commit 67ea334
17 files changed
Lines changed: 1130 additions & 662 deletions
File tree
- packages/eslint-plugin
- src
- test
- scripts/ci
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
0 commit comments