refactor(linter): migrate @cubejs-backend/linter from ESLint to oxlint - #11790
refactor(linter): migrate @cubejs-backend/linter from ESLint to oxlint#11790ovr wants to merge 11 commits into
Conversation
|
Claude finished @ovr's task in 1m 52s —— View job Re-review of
|
| # | Severity | Where | Status |
|---|---|---|---|
| 2 | Medium | packages/cubejs-linter/airbnb-base.json:535 |
Still applies. Core no-shadow: "error" unqualified, TS override doesn't remap it, still absent from the "no equivalent" list |
| 6 | Medium | packages/cubejs-linter/.oxlintrc.json:23 |
Still applies. import/named still on and still build-order dependent |
| 3 | Low | packages/cubejs-backend-maven/src/maven.ts:30-40 |
Still applies. Live chain one call per line; commented block still .ele().txt().up() |
| 4 | Low | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings |
| 5 | Low | packages/cubejs-hive-driver/package.json:15 |
Still applies. "scripts": {} still there |
| 8 | Low | query-builder.service.ts:179-184 |
Still applies. Inverted disableHeuristics()/enableHeuristics() |
| 9 | Low | query-members.ts:47 |
Still applies. No CI coverage for ngx |
Re-affirmed all seven rather than posting duplicates. Findings #1 and #7 remain resolved. No human threads touched.
Caveat
Still no node_modules in this sandbox, so I could not execute yarn install --frozen-lockfile, yarn lint or oxlint. The "exit 0, 78.8s", "frozen-lockfile passes" and "0 errors, 149 warnings" numbers are taken as stated; everything above is from reading yarn.lock and the package manifests directly.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #11790 +/- ##
==========================================
- Coverage 84.67% 80.22% -4.46%
==========================================
Files 261 500 +239
Lines 86451 105649 +19198
Branches 0 3885 +3885
==========================================
+ Hits 73204 84752 +11548
- Misses 13247 20348 +7101
- Partials 0 549 +549
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Claude finished @ovr's task in 3m 32s —— View job Re-review of
|
| # | Severity | Where | Issue |
|---|---|---|---|
| 10 | High | packages/cubejs-playground/.oxlintrc.json:1 |
Package un-ignored while 279 errors remain, so yarn lint:js / the Oxlint CI step is red for the whole PR |
| 11 | Medium | packages/cubejs-playground/package.json:80 |
Three devDeps removed without regenerating yarn.lock; the Build job's "Non empty changeset" guard trips on a lockfile yarn wants to prune |
| 12 | Medium | packages/cubejs-linter/package.json:20 |
airbnb-react.json missing from files — the new shared rule set isn't in the published tarball |
| 13 | Low | QueryBuilderInternals.tsx:44 |
prefer-arrow-callback stripped the names off 7 memo/forwardRef components; they now show as Anonymous in DevTools and component stacks |
Prior findings — all seven still open
| # | Severity | Where | Status |
|---|---|---|---|
| 2 | Medium | packages/cubejs-linter/airbnb-base.json:535 |
Still applies. no-shadow: "error" unqualified; not in airbnb-react.json's or airbnb-base.json's off-list either. Note this is now the single largest blocker in the playground count above — 146 of the 279 |
| 6 | Medium | packages/cubejs-linter/.oxlintrc.json:23 |
Still applies. import/named still "error" (airbnb-base.json:418), still build-order dependent |
| 3 | Low | packages/cubejs-backend-maven/src/maven.ts:30-40 |
Still applies. Unchanged; commented block still .ele().txt().up() |
| 4 | Low | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings |
| 5 | Low | packages/cubejs-hive-driver/package.json:15 |
Still applies. "scripts": {} |
| 8 | Low | query-builder.service.ts:179-184 |
Still applies. Inverted disableHeuristics()/enableHeuristics() |
| 9 | Low | query-members.ts:47 |
Still applies. No CI coverage for ngx |
Re-affirmed all seven rather than posting duplicates. #1 and #7 remain resolved; nothing new became stale, so no threads were resolved this round. No human threads touched.
Smaller notes
- Several
--fixoutputs now have arguments hanging at the wrong indent —FilterGroup.tsx:28-35(), \n [members]);),QueryBuilder.tsx:39-44,QueryBuilderInternals.tsx:83-84, anduseCommitPress(() => runQuery(),\n true);. Cosmetically worse than what it replaced, and@stylistic/indentapparently accepts both, so it's a "fixer produced this" artefact rather than a rule anyone would ask for. MemberDropdown.tsx:60-65's.map((cube) => (\n {is the same shape —arrow-body-styleremoving thereturnbut leaving the object literal on its own lines.QueryBuilderChartProps.maxHeightis now a prop nothing reads; since theCHART_HEIGHTdefault went away with it, dropping it from the interface would keep callers honest.packages/cubejs-playground/.prettierrcsurvives, same ascubejs-client-vue3's — and.prettierrcis still in thepaths:filter of both workflows plus rootprettier: ^2.0.5indevDependencies. This commit is the one that makes playground's copy provably unused.- PR description still says playground was "never covered" and
cubejs-client-vue3"stays on ESLint"; both are now out of date, and the description is what lands in the squash commit. - Same caveat as the previous four passes: no
node_modulesin this sandbox, so I can't executeoxlint. The "1372 → 279", "10 identical probe findings", "yarn unit1 passed" and "160 pre-existing tsc errors" numbers are taken as stated; everything above is from reading the tree.Build,Drivers tests,RustandBuild nativewere all still in progress when I finished; nothing had failed yet.
| }); | ||
|
|
||
| const QueryBuilderInternals = memo(function QueryBuilderInternals() { | ||
| const QueryBuilderInternals = memo(() => { |
There was a problem hiding this comment.
prefer-arrow-callback (airbnb's default is allowNamedFunctions: false) rewrote every memo(function Name() {...}) / forwardRef(function Name() {...}) in this package into an anonymous arrow: AccordionDetails, AccordionItemIcon, AccordionItemContent, Arrow, CopyIcon, OrderListItem and this one. The named function expression was the mechanism that gave these components a name — memo() and forwardRef() wrappers have no variable-name inference, so they now render as Anonymous/ForwardRef in React DevTools and in component stacks in error boundaries and warnings, which is the main tool for reading this UI's tree.
Since the whole point of that idiom is the name, allowNamedFunctions: true in the package's own config (or in airbnb-react.json, since it's React-specific) would let the autofix keep it, and it's the setting that matches how these files were written:
"prefer-arrow-callback": ["error", { "allowNamedFunctions": true }]`@cubejs-backend/linter` was an eslintrc shareable config extending `airbnb-base`, consumed by 45 packages plus `rust/cubestore` through a per-package `eslintConfig` block. CI ran `lerna run --concurrency 1 lint`, i.e. 49 sequential ESLint 8 processes. That toolchain is also at the end of the road: ESLint 8.57.1 is out of support, and #11767 already had to shuffle four formatting rules into `@stylistic/eslint-plugin-ts` because typescript-eslint 8 dropped them. The package now ships an oxlint config instead. `airbnb-base.json` is the mechanical port of the old rule set, generated from `eslint --print-config` so it is verifiable rather than hand-transcribed; `.oxlintrc.json` sits on top of it and holds the deliberate departures, each with its reason. The repo-root `.oxlintrc.json` extends that and owns the ignore list, so linting is a single root operation and the 49 per-package `lint` scripts and `eslintConfig` blocks are gone. oxlint implements no formatting rules, so `@stylistic/eslint-plugin` is loaded through `jsPlugins` to keep the 48 whitespace rules airbnb-base relies on. | | before (ESLint) | after (oxlint) | | --- | --- | --- | | invocations | 49 sequential processes | 1 | | wall clock, whole repo | minutes | 2.7s (`yarn lint` 4.3s incl. `lint:npm`) | | files linted | 739 | 873 | | errors | 0 | 0 | | warnings | 178 | 149 | Rule coverage of the 192 rules the old config resolved to: 125 map onto oxlint natively with identical options, 48 move to `@stylistic/*`, 8 are renamed or re-homed (`no-new-object` to `no-object-constructor`, `global-require` to `node/global-require`, `no-buffer-constructor` to `unicorn/no-new-buffer`, and the `@typescript-eslint` twins of `no-unused-vars` / `no-shadow` / `semi` collapse onto the TS-aware core rules; the four formatting rules #11767 moved to `@stylistic/ts/*` land on the same `@stylistic/*` targets, so that change is absorbed). 14 have no equivalent and are listed at the top of `airbnb-base.json`; the ones worth chasing later are `camelcase`, `import/order`, `import/no-extraneous-dependencies`, and `consistent-return` / `dot-notation` / `no-return-await`, which exist only as type-aware `typescript/*` rules. Parity was checked by diffing per-file diagnostics against an ESLint run on the pre-migration tree. Of the 50 previously-linted files involved, 46 match exactly; the 4 that differ are all oxlint being more lenient about destructuring placeholders and rest siblings. The 40 `quotes` warnings in the baseline are gone because `--fix` resolved them. Three things needed configuring rather than porting, all commented in `packages/cubejs-linter/.oxlintrc.json`: eslint-plugin-import had no TypeScript resolver, so `import/no-cycle`, `import/export` and the two `no-named-as-default` rules never actually ran (they now report 173 dependency cycles and 2 real duplicate exports -- left off, to be fixed separately); oxlint honours neither `/* globals ... */` nor `/* eslint-env jest */`, so the jest globals come from an `overrides` entry; and `@stylistic/quotes` gets `allowTemplateLiterals` because `--fix` otherwise rewrites the driver parameter-escaping tests into backslash soup. The source changes are `oxlint --fix` output. They are all formatting, and they exist because `@stylistic` understands TypeScript syntax where ESLint's core rules did not -- `indent` in generic argument lists and type annotations, `object-curly-spacing` and `quote-props` in type literals, `space-before-blocks` on interface bodies. Four files carry a hand-written `eslint-disable` for intentional code: bit twiddling in the zip helper test, a never-resolving promise in the native test server, and a lazy `require` in the cypress config. Also drops the stale `.eslintrc.js` path filters from the workflows (that file has not existed for some time) and fixes the linter package's `repository.directory`, which pointed at `packages/cubejs-mssql-driver`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The package was left on ESLint by the oxlint migration with the note that
"oxlint cannot parse Vue SFCs" -- but it has no .vue files at all, just nine
plain .js ones, and there are no SFCs anywhere in the repo. Drop it from the
root ignorePatterns and delete the ESLint plumbing: the eslint devDependency,
the in-package eslintConfig, the lint script and tests/unit/.eslintrc.js.
No nested .oxlintrc.json is needed -- the root env plus the jest `overrides`
already in @cubejs-backend/linter cover the package.
Its own config was a lone `eslint:recommended`, so it had never seen the shared
airbnb-base rules; enabling them reported 50 findings. `oxlint --fix` (three
passes) cleared 47. The rest, and two spots the autofix left less readable:
- validateFilters: the inner reduce shadowed the outer `acc` and `filters`
- render(): the `isQueryPresent` computed shadowed the import of the same name;
read it off `this` at the use site, like the neighbouring `this.*` props, so
the slot prop keeps its name
- reduceOrderMembers / resolveMembers: implicit-arrow-linebreak had collapsed
one into a ~125-char line and wrapped the other in `(\n {...}\n)`
`import/named` false-positives on the `GRANULARITIES` re-export in index.js: it
does not follow client-core's `export * from './time.js'`, though the name
resolves at runtime. Suppressed inline. Worth noting the finding only appears
once packages/cubejs-client-core/dist exists -- oxlint resolves imports
natively, so with dist absent it reports nothing, which makes the import/*
rules build-order dependent.
Verified: oxlint clean with client-core's dist both present and absent, repo
`yarn lint:js` exits 0, and the package's 36 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cubejs-client-ngx` was never covered by the ESLint setup — no `lint` script, no `eslintConfig` block — so 1c11110 carried it straight into oxlint's ignore list. Its only style tooling was a `.prettierrc` that no script ever invoked. Drop the unused prettier config and take the package off the ignore list, so it lints with the rest of the repo. 114 violations: `oxlint --fix` cleared ~26, the rest are here. Most of the volume is mechanical — `public` on 67 class members (`typescript/explicit-member-accessibility`) and the blank lines `lines-between-class-members` wants — but four are not: | file | rule | what was wrong | | --- | --- | --- | | `src/client.ts` | `no-shadow` | `watch()`'s `next: async (query)` shadowed the outer `query` param | | `src/query-builder/query-members.ts` | `no-unexpected-multiline` | `BaseMember.remove()` indexed `asCubeQuery()` on the next line, skipping the `\|\| []` the `members` getter already applies | | `src/query-builder/query-members.ts` | `no-shadow` | `handleOrderMembersChange` destructured `order` twice inside `const order = ...` | | `src/query-builder/query-builder.service.ts` | `no-return-assign` | both promise executors were `(resolve) => (this._resolveX = resolve)` | `remove()` changes behaviour slightly: removing from an absent member list now writes `[]` instead of throwing a TypeError. The two angle-bracket casts became `as` casts. Left alone, `keyword-spacing` demands a space before `this` and renders them `<TCubeMeasure[]> this.meta`. One suppression, for an oxlint false positive: `no-shadow` flags the `MemberType.Order` enum member against the imported `Order` class. Enum members are not bindings, so nothing is shadowed. `ng build` still passes.
The migration commits dropped `eslint` and its shareable configs from 49 package.json files, but the lockfile was only partially rewritten and still pinned the whole ESLint 8 tree. Wiping every node_modules and reinstalling from scratch settles it. | | | | --- | --- | | `yarn.lock` | 170 insertions(+), 751 deletions(-) | | `yarn install` | exit 0, postinstall hooks not skipped | | `yarn install --frozen-lockfile` | passes -- lockfile agrees with every package.json | | `yarn lint` | 0 errors | `eslint@^8.57.1` is gone along with its transitive tree: `@eslint/js`, `@eslint/eslintrc`, `@eslint-community/eslint-utils`, `@eslint-community/regexpp`, `@humanwhocodes/config-array`, `@humanwhocodes/object-schema`, `@humanwhocodes/module-importer`, and all of `@typescript-eslint/*` -- plus the shareable configs and plugins that hung off them (`eslint-config-airbnb-base`, `eslint-plugin-cypress`, `eslint-import-resolver-node`, `eslint-module-utils`, `doctrine`, `enquirer`, `astral-regex`, `confusing-browser-globals`). No `eslint` binary is left in the tree at all -- only `oxlint`. The remaining deletions are not dropped packages but merged resolution keys being re-narrowed (`ajv`, `debug`, `cross-spawn`, `escape-string-regexp`), because some specifiers no longer have a requester. Five `eslint`-ish entries survive on purpose: they are transitive dependencies of `@stylistic/eslint-plugin`, which the oxlint config loads as a jsPlugin. `cubejs-playground` still declares `eslint-config-airbnb`, `eslint-plugin-jsx-a11y` and `eslint-plugin-react` at this point, which is why those trees are still here; the commit that enables oxlint for that package removes them and prunes the rest.
`cubejs-playground` declared `eslint-config-airbnb`, `eslint-plugin-jsx-a11y` and `eslint-plugin-react` but had no `lint` script and no eslintrc, so nothing ever ran them; 1c11110 then put the package in oxlint's ignore list and dropped `eslint` itself, leaving a config and two plugins with no runner. Take the package off the ignore list, delete the three dead devDependencies, and give it a real config. The React rule set moves out of `cubejs-client-react/.oxlintrc.json` (928 lines) into `@cubejs-backend/linter/airbnb-react.json`, so both React packages extend one set instead of a copy. Equivalence was checked empirically, not by reading: `--print-config` does not fully resolve `extends` (it reports 138 rules instead of 204 and loses `jsPlugins` entirely), so a probe .tsx with option-dependent violations -- max-len 120, jsx-wrap-multilines, anchor-is-valid with `components: ["Link"]` -- was linted under both configs. Ten findings, identical. | | | | --- | --- | | oxlint on the package | 1372 -> 279 errors | | `yarn unit` | 1 passed | | `npx tsc` | 160 errors, byte-identical to the same run on HEAD | The 160 type errors are pre-existing: the clean reinstall wiped `@cubejs-client/react`'s `dist`, so its declarations do not resolve. No new one appeared, which is what makes the ~1000 autofixed findings reviewable at all. Three deliberate rule departures, all in the package's own config: - `prefer-const` moves to airbnb's `destructuring: "all"`. With `"any"` the rule fires on a `let { ... } = props` pattern when any single binding is never reassigned, and this package follows the @cube-dev/ui-kit idiom of reassigning some props-derived locals (`titleStyles = useMemo(...)`), so the pattern needs `let` and no autofix could ever touch the 44 reports. - `no-use-before-define` gets `functions: false`. Files here are laid out top-down -- exported component first, local helpers below -- which hoisted function declarations make legal. 43 -> 12. - `@stylistic/jsx-one-expression-per-line` is off. Its autofix splits `{cubeName} <b>{name}</b>` across lines and has to inject a bare `{' '}` line to preserve the space, which it did 61 times. Cosmetic rule, anti-cosmetic fix. Note for whoever touches this config next: the last two had to go in an `overrides` entry, not top-level `rules`. airbnb-react.json declares 102 rules inside an `overrides` block for `**/*.ts,**/*.tsx`, and an extended `overrides` entry outranks the extending config's own top-level `rules` -- tuning `no-use-before-define` there had no effect at all, with either setting. Do not run `oxlint --fix-suggestions` on this package. Its `no-void` fixer rewrites `void expr;` to `undefined;`, deleting the call: - void runQuery(); + undefined; It also collapsed a 20-line `void parseAndPrepareQuery(...).then(...)` chain to `undefined;`. Those edits were reverted; only plain `--fix` was applied. INCOMPLETE: `yarn lint` is red, 279 errors left. What is fixed by hand is the mechanically safe set -- 22 unused `catch` bindings to `catch {`, all 15 `no-unused-vars` including the dead `getJSONValidator` and the `Meta` and `CHART_HEIGHT` it orphaned. What is left needs judgment in a 262-file package with one test file: | rule | count | | --- | --- | | `no-shadow` | 146 | | `no-nested-ternary` | 30 | | `react/no-array-index-key` | 13 | | `no-use-before-define` | 12 | | jsx-a11y | 9 | | `no-throw-literal` | 5 | | assorted mechanical, not yet reached | ~64 | `no-throw-literal` is last on the list on purpose: the thrown strings are load-bearing for the form validators' displayed messages, `throw ''; // do not show any error message` among them.
9e873e3 left `yarn lint` red with 279 errors. This takes it to zero: the package now reports 0 errors and 2 warnings, and `yarn lint` exits 0 repo-wide (151 warnings, up from 149 by exactly those two). Nothing regressed while doing it. `npx tsc` still reports 160 errors, the same set it reports on the parent commit -- they are pre-existing, caused by the clean reinstall wiping `@cubejs-client/react`'s `dist` so its declarations do not resolve. `yarn unit` passes. That comparison was re-run after every batch below, which is the only reason a change this size is reviewable. | rule | count | how | | --- | --- | --- | | `no-shadow` | 142 | renamed, scope-aware | | `no-nested-ternary` | 29 | if/else, `\|\|`, or a named local | | `react/no-array-index-key` | 13 | 5 real keys, 8 documented | | `no-use-before-define` | 12 | declarations moved | | `no-void` / `no-unused-expressions` / `no-return-assign` | 17 | statements | | `default-case` | 5 | airbnb's `// no default` marker | | `no-lonely-if` | 5 | `else if` | | `no-throw-literal` | 5 | 3 became Errors, 2 documented | | jsx-a11y | 9 | markup, plus one config correction | | formatting, `no-unused-vars`, misc | ~40 | mechanical | The 142 renames went through a scope-aware renamer built on the TypeScript compiler (resolve the identifier to its symbol, rewrite every identifier in the file that resolves to that same symbol) rather than textual substitution. Two bugs in that renamer are worth knowing about if it gets reused: - For `{ foo }` shorthand, `getSymbolAtLocation` returns the *property* symbol, not the value binding, so those references were skipped and silently kept resolving to the outer name. `return { filters }` inside a renamed `updateQuery((currentQuery) => ...)` then picked up the hook's `filters` API object instead of the local array -- 27 new tsc errors, which is how it was caught. The fix is `getShorthandAssignmentValueSymbol`. - Locating the arrow parameter by the first `(name` match found the call argument instead in `parseAndPrepareQuery(query, type).then((query) => ...)`, renaming the wrong one of the two. Deliberate departures, all in the package's own config and each with its reason recorded there: `jsx-a11y/anchor-is-valid` drops `Link` from `components` (@cube-dev/ui-kit's `Link` is typed `Omit<CubeButtonProps, 'ref'>` -- a Button alias driven by `onPress`, not an anchor, and the rule cannot tell it from react-router's by import). Two things are suppressed inline rather than fixed, because fixing them properly is a product change, not a lint pass: - 6 `react/no-array-index-key` in the filter lists. The filters array carries no stable id, and a content-derived key would remount the filter's inputs on every keystroke. Giving filters ids is the real fix. The other two are cases where the index *is* the identity: a `[...Array(n)]` page range, and a table row id that `selectedCell` stores. - 2 `no-throw-literal` on `throw ''`. async-validator's `complementError` only reads `.message` when it is truthy, so `new Error('')` falls through and puts the Error object itself in `message` instead of rendering nothing -- the empty string is load-bearing. The three throws with real messages did become `new Error(...)`, which renders identically. `react/no-danger` stays as the 2 warnings: both `dangerouslySetInnerHTML` uses are intentional, and airbnb ships that rule at warn level.
c24ae38 to
f971914
Compare
|
Claude finished @ovr's task in 6m 17s —— View job Re-review of
|
| # | Severity | Where | Issue |
|---|---|---|---|
| 14 | Medium | QueryBuilderV2/QueryBuilder.tsx:138 |
props.children ? … : <Internals/> became props.children ?? <Internals/>; falsy-but-not-nullish children (the {cond && <X/>} idiom) now render an empty builder instead of the default UI |
| 15 | Low | components/FilterMember.tsx:235 |
key={value} on filter.values assumes dedup that validateQuery doesn't enforce; same at ValuesInput.tsx:360 where the tags are interactive |
Prior findings — status
| # | Severity | Where | Status |
|---|---|---|---|
| 2 | Medium | packages/cubejs-linter/airbnb-base.json:535 |
Still applies. no-shadow: "error" is still unqualified and still absent from the "no equivalent" list. This commit is the strongest evidence yet for documenting it: 142 of the 279 errors were no-shadow, and a good share are the TS-specific shapes ignoreTypeValueShadow existed for — type BuildRange vs the component, type Tab vs the Tab component, type QueryTabs vs the QueryTabs component |
| 6 | Medium | packages/cubejs-linter/.oxlintrc.json:23 |
Still applies. import/named still "error" (airbnb-base.json:418), still build-order dependent |
| 3 | Low | packages/cubejs-backend-maven/src/maven.ts:40 |
Still applies. Unchanged; commented block still .ele().txt().up() |
| 4 | Low | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings — and the baseline moved again (149 → 151), which is the drift the flag would catch |
| 5 | Low | packages/cubejs-hive-driver/package.json:15 |
Still applies. "scripts": {} |
| 12 | Low | packages/cubejs-linter/package.json:20 |
Still applies. files is [".oxlintrc.json", "airbnb-base.json"]; airbnb-react.json still isn't published |
| 13 | Low | QueryBuilderV2/QueryBuilderInternals.tsx:44 |
Still applies. No memo(function …) / forwardRef(function …) remains anywhere in the package; AccordionItemIcon and AccordionItemContent, both moved in this commit, are still anonymous arrows |
| 8 | Low | query-builder.service.ts:184 |
Still applies. Inverted disableHeuristics()/enableHeuristics() |
| 9 | Low | query-members.ts:47 |
Still applies. No CI coverage for ngx |
Re-affirmed those rather than posting duplicates. #1, #7, #10 and #11 are resolved. No human threads touched.
Smaller notes
rollup-designer/components/Settings.tsxdeletes the 47-lineBuildRangecomponent. I confirmed it's genuinely dead (no reference anywhere insrc/, anducfirstwas its only importer here) — but a UI component removal is a different kind of change from the rest of the commit and lands under "formatting,no-unused-vars, misc | ~40 | mechanical" in the table. Worth its own line. Its removal also makes theBuildRange→BuildRangeValuetype rename above it unnecessary, since nothing shadows that name anymore.- The
react/no-array-index-keyjustification is copy-pasted verbatim five times acrossQueryBuilderFilters.tsxandLogicalFilter.tsx. Stating it once where the filters array is defined and leaving a bare// eslint-disable-next-line react/no-array-index-keyat each site would read better; five identical paragraphs in two files is the kind of thing that goes stale unevenly. Settings/Settings.tsx:99drops the<label>wrapper foraria-label="Limit". Correct for the rule and no association is lost (the label had no text of its own — "Limit" is the input'sprefix), just noting it's a DOM change inside a positioned dropdown rather than an attribute-only fix.ButtonDropdown.tsx:87satisfies the static-element-interaction rule withrole="presentation"on a clickable<div>. That silences the rule by removing semantics rather than adding them; the click target is still not keyboard-reachable. Fine as a lint pass, not an a11y improvement.EditQueryDialogForm.tsx:26,55andQueryBuilderSidePanel.tsx:384turnthrow 'Invalid query'intothrow new Error(...). ThecomplementErrorreasoning in the message is right that the truthy-.messagebranch returnsoeitself, so consumers reading.messagesee the same string — the residual risk is any consumer that renders the error entry directly, which would now get an object. Cheap to confirm by opening the JSON editor with malformed input once.- PR description is now stale on three counts: playground "never covered",
cubejs-client-vue3"stays on ESLint", and the follow-ups paragraph. It's what lands in the squash commit, so worth a refresh. - Same caveat as the previous five passes: no
node_modulesin this checkout, so I could not executeoxlint. "0 errors, 2 warnings", "151 warnings repo-wide", "160 pre-existing tsc errors" and "yarn unitpasses" are taken as stated.Build(which runs theOxlintstep),Drivers tests,RustandBuild nativewere all still in progress when I finished; nothing had failed.
Four findings from the review, all in this branch's own diff.
| finding | fix |
| --- | --- |
| `??` is not `\|\|` | `QueryBuilder.tsx` restored to truthiness |
| duplicate React keys | `ValuesInput` / `FilterMember` render distinct values |
| `airbnb-react.json` unpublished | added to the linter package's `files` |
| orphaned `"scripts": {}` | dropped from `cubejs-hive-driver` |
The `no-nested-ternary` rewrite in `QueryBuilder.tsx` swapped truthiness for
nullish: the original `props.children ? props.children : <QueryBuilderInternals/>`
fell back on *any* falsy children, `??` only on null/undefined. The idiom that
differs is `<QueryBuilder>{isReady && <Custom/>}</QueryBuilder>`, which passes
`false` -- that used to render the default builder and rendered nothing instead.
Neither in-repo call site passes children, but `QueryBuilder` is re-exported from
`QueryBuilderV2/index.ts` and the package publishes `lib/`, so this was reachable
from outside.
`key={i}` -> `key={value}` assumed `filter.values` was deduplicated, and nothing
enforces that: `validateQuery`'s `isValidBinaryFilter` only checks
`isArrayOfStrings`, so `values: ['a', 'a']` round-trips from the JSON/GraphQL
editor. The suggested `key={`${i}:${value}`}` does not work -- oxlint's
`no-array-index-key` matches the index inside a template literal too, verified
both that form and `${value}-${i}`. Rendering one tag per distinct value fixes
the keys and also matches `onRemove`, which filters by value and already drops
every copy -- so a second identical tag was never independently removable.
`airbnb-react.json` was missing from `files`, so the published tarball carried
only half the rule set. Nothing broke in-repo because the React configs resolve
it by relative path, but the stated reason for keeping these rules in a package
is that they stay publishable.
Not taken: the review also proposed `prefer-arrow-callback` with
`allowNamedFunctions: true`, to keep `memo(function Foo() {})` naming the
component for React DevTools. Tried it and reverted -- `memo(function InfoIcon(...))`
under `export const ItemInfoIcon` reads worse than the anonymous arrow, and the
wrapper name did not have to match the binding. airbnb's default stands.
|
Claude finished @ovr's task in 2m 2s —— View job Re-review of
|
| # | Severity | Where | Issue |
|---|---|---|---|
| 16 | Low | FilterMember.tsx:235 |
[...new Set(x)] duplicates the package's existing uniqArray helper (QueryBuilderV2/utils/uniq-array.ts, already used at graphql-converters.ts:213,582); same at ValuesInput.tsx:359 |
Correction to the suggestion block I posted inline: the one-line uniqArray(...) form comes to 122 characters with the indent, which trips max-len: 120, so it needs to stay wrapped across two lines — and both files need the uniqArray import added. The point is only the helper, not the layout.
Prior findings — six still open
| # | Severity | Where | Status |
|---|---|---|---|
| 2 | Medium | packages/cubejs-linter/airbnb-base.json:535 |
Still applies. no-shadow: "error" still unqualified, TS override doesn't remap it, still absent from the "no equivalent" list |
| 6 | Medium | packages/cubejs-linter/.oxlintrc.json:23 |
Still applies. import/named still "error" (airbnb-base.json:418), still build-order dependent |
| 3 | Low | packages/cubejs-backend-maven/src/maven.ts:30-40 |
Still applies. Live chain one call per line; commented block still .ele().txt().up() |
| 4 | Low | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings |
| 8 | Low | query-builder.service.ts:184 |
Still applies. Inverted disableHeuristics()/enableHeuristics() |
| 9 | Low | query-members.ts:47 |
Still applies. No CI coverage for ngx |
Re-affirmed those rather than posting duplicates. Resolved the four threads this commit addressed (#5, #12, #14, #15); #1, #7, #10 and #11 remain resolved. No human threads touched.
Smaller notes
- PR description is still stale on three counts — playground "never covered",
cubejs-client-vue3"stays on ESLint because oxlint cannot parse Vue SFCs", and the follow-ups paragraph. It's what lands in the squash commit, so worth a refresh before merge. - Same caveat as the previous six passes: no
node_modulesin this checkout, so I could not executeoxlint— the formatting of the new wrapped.map(inFilterMember.tsx(function-paren-newline: consistent,@stylistic/indent) reads fine to me but is unverified.Build(which runs theOxlintstep),Drivers tests,RustandBuild nativewere all in progress when I finished; nothing had failed, andLint CI Workflowspassed.
Two review findings about the config itself. They look similar and are not: one is a rule that cannot gate anything, the other was only ever a documentation gap. `import/named` is off. Its verdict depends on build state, measured both ways on the same file with nothing else changed: | tree | `packages/cubejs-client-vue3/src/index.js:4` | | --- | --- | | no `cubejs-client-core/dist` | silent | | `dist` built | `named import "GRANULARITIES" not found` | The warm-tree finding is a false positive -- `GRANULARITIES` is exported from client-core `src/time.ts:55` and re-exported by `export * from './time.js'` at `src/index.ts:1026`, which oxlint does not follow across a package boundary; it is also present at runtime. And CI only ever sees the cold case, because the lint job runs `yarn install` -> `lint:npm` -> `lint:js` with no build in between. So the rule reported nothing where it gates merges, and a false positive wherever someone had built the workspace. It joins the four already off for the related reason that eslint-plugin-import never had a TypeScript resolver here. That also retires the inline `eslint-disable-next-line import/named` in vue3's `index.js`, which existed solely for this. `no-shadow` needed no change, only the note it never got. The ESLint config set `no-shadow: 'off'` plus `@typescript-eslint/no-shadow` with `ignoreTypeValueShadow` in its `*.ts` override; oxlint has no `typescript/no-shadow` at all -- configuring that key routes straight to core `no-shadow`, and the diagnostic comes back labelled `eslint(no-shadow)`, identically with and without `--type-aware`. Since the generated port lists the rules it dropped but said nothing about this remap, it read as an unintended tightening. It is not one. A/B against the previous toolchain (eslint 8.57.1 + typescript-eslint 8.46.0, the versions this branch removed) agrees on every shape tried, including the real `cubejs-client-ngx/src/query-builder/query.ts`: | shape | old `@typescript-eslint/no-shadow` | oxlint core `no-shadow` | | --- | --- | --- | | `ngx/query.ts` enum member vs imported class | error at 14:3 | error at 14:3 | | `type Foo` + `const Foo` | clean | clean | | local `type X` over outer `const X` | clean | clean | | `<X>` type param over outer `const X` | clean | clean | | function-type parameter name over outer value | clean | clean | | `<T>` type param over outer `type T` | error at 10:19 | error at 10:19 | | local `interface T` over outer `type T` | error at 17:13 | error at 17:13 | So the `eslint-disable-next-line no-shadow` on `MemberType.Order` in ngx would have been needed before this migration too. The header now records the remap and the equivalence instead of leaving the reader to re-derive it. For the record, since it came up: adopting `oxlint-tsgolint` would not change any of this. `no-shadow` is scope analysis, not type analysis, and tsgolint implements only the type-aware subset (59/61 by its own README).
`QueryBuilderService` gates every heuristic branch on `!this._disableHeuristics` (lines 73, 118, 122, 129), so the flag means what its name says: true turns heuristics off. The two setters had it backwards. The flag starts out `false`, so `disableHeuristics()` was a no-op on a fresh service and `enableHeuristics()` was the only way to actually turn heuristics off. Both methods did the opposite of their name, and neither had a caller. The sibling packages settle the intended polarity: `cubejs-client-react` `QueryBuilder.tsx:721` returns the unmodified state `if (disableHeuristics)`, and `cubejs-client-vue3` `QueryBuilder.js:347` guards on `!this.disableHeuristics`. This is a behaviour change for anyone who worked around the bug by calling `enableHeuristics()` to switch heuristics off -- nothing in the repo does, but the service is exported from `@cubejs-client/ngx`, so an external Angular consumer could have. Flagged in the review as out of scope for a lint migration; taking it because it is two lines and both were already in this branch's diff from the `public` sweep. `ng build` passes and oxlint reports 0 for the package.
Two follow-ups from the review. `cubejs-client-ngx` had oxlint as its only automated check. Taking it off the ignore list covered the lint half, but the package has no `test` script and is not in the root `tsconfig.json` references, so nothing in CI compiled it -- and none of the hand-written edits this branch made there are lint-visible: the `BaseMember.remove()` semantics change, the `watch()` parameter rename, the two angle-bracket to `as` casts, the promise-executor rewrites, and the inverted heuristics setters. `ng build` cannot run on a fresh checkout by itself: without `packages/cubejs-client-core/dist` it fails to resolve `@cubejs-client/core`, which is exactly the state the lint job is in (`yarn install` with no build). Verified from a cold tree that `yarn lerna run build:client-core` first is enough, and both steps together take about 6s. The second one is reuse: `[...new Set(x)]` was a third spelling of `QueryBuilderV2/utils/uniq-array.ts`, already used in `graphql-converters.ts`. Both new call sites now use the helper. The suggested one-liner for `FilterMember` came to 122 characters, 2 over `@stylistic/max-len`, so it wraps at the `&&`, which is where `operator-linebreak: "before"` wants it anyway.
|
Claude finished @ovr's task in 2m 43s —— View job Re-review of
|
| # | Severity | Where | Issue |
|---|---|---|---|
| 17 | Low | packages/cubejs-linter/airbnb-base.json:13-23 |
The no-shadow paragraph was inserted inside the "no equivalent, deliberately not carried over" list, orphaning the no-dupe-args, no-octal, no-octal-escape entry below it; both it and the new import/named block are ~10 lines where two sentences carry the weight |
Prior findings — three still open, all low
| # | Severity | Where | Status |
|---|---|---|---|
| 3 | Low | packages/cubejs-backend-maven/src/maven.ts:30-40 |
Still applies. Live chain is one call per line; the commented block below still uses .ele().txt().up() |
| 4 | Low | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings |
| 13 | Low | QueryBuilderV2/QueryBuilderInternals.tsx:44 |
Settled as your call last round (prefer-arrow-callback / allowNamedFunctions); thread left open only because I can't reply on it |
Resolved the five threads this round addressed (#2, #6, #8, #9, #16); #1, #5, #7, #10, #11, #12, #14, #15 remain resolved. No human threads touched.
Smaller notes
- The
Build client-ngxstep adds anng build(plus a client-coretsc) to the job that gates every push. Worth knowing it's a couple of minutes on the critical path of a job otherwise named for linting — an acceptable price for the coverage, but if it starts hurting, a separate job would parallelise it. - ngx's
tsconfig.jsonsetsoutDir: "../../out-tsc/lib", andout-tscisn't in any.gitignore. ng-packagr normally redirects output to its own build dir so nothing should appear, but if it ever does it lands untracked at the repo root. Harmless in CI (the changeset guard runs before this step) — it'd only bite local runs. - PR description is still stale on three counts: playground "never covered",
cubejs-client-vue3"stays on ESLint because oxlint cannot parse Vue SFCs", and theimport/named/follow-ups paragraph. It's what lands in the squash commit. - Same caveat as the previous seven passes: no
node_modulesin this checkout, so I could not executeoxlint,ng buildor the A/B against ESLint. The parity claims inairbnb-base.json's new note and the "0 errors" state are taken as stated; everything above is from reading the tree. CI was still in progress when I finished; nothing had failed.
| // `const Foo`, a local `type X` or `<X>` type parameter over an outer `const X`, a | ||
| // function-type parameter name over an outer value) and both report a type shadowing a | ||
| // type (`<T>` or a local `interface T` over an outer `type T`) and an enum member | ||
| // shadowing an imported binding, at identical positions. |
There was a problem hiding this comment.
Documenting the remap is the right answer, but it landed inside the "no equivalent, deliberately not carried over" list rather than after it — no-dupe-args, no-octal, no-octal-escape -- parse errors under ESM/strict now sits below the paragraph (lines 24-25) and reads as if it belongs to the no-shadow discussion instead of to the list ten lines above it.
Moving the paragraph below the list would fix that, and it's also a chance to shorten it: the load-bearing sentences are "oxlint has no typescript/no-shadow" and "core no-shadow already behaves the way ignoreTypeValueShadow asked for, verified by A/B against eslint 8.57.1 + typescript-eslint 8.46.0". The per-shape enumeration is the evidence for the A/B, which belongs in the commit message where you already recorded it — the config only needs the conclusion.
Same shape in .oxlintrc.json:20-29: the import/named rationale is now 10 lines where the two that matter are "verdict depends on build state, and CI is the cold case" plus "GRANULARITIES is genuinely exported, so the warm-tree finding is a false positive".
Drop comments that restate the code they sit on, and de-duplicate the `extends` merge semantics -- stated in six places, now only in the shared base config where its consumers look. Comments that are load-bearing stay: `// no default` and the comment inside an empty `catch` are what make `default-case` and `no-empty` pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @ovr's task in 1m 6s —— View job Re-review of
|
| # | Where | Status |
|---|---|---|
| 17 | packages/cubejs-linter/airbnb-base.json:13-25 |
Still applies, and it's the one this commit was in the best position to close. The no-shadow paragraph is still lines 14-23 inside the "no equivalent, deliberately not carried over" list, with no-dupe-args, no-octal, no-octal-escape orphaned below it at lines 24-25. .oxlintrc.json:14-28 is the same shape — the import/no-cycle + import/named rationale is 15 lines. Not a correctness problem; just the two comments the trim pass skipped |
| 3 | packages/cubejs-backend-maven/src/maven.ts:30-40 |
Still applies. Live chain is one call per line; the commented block below still uses .ele().txt().up() |
| 4 | package.json:27 |
Still applies. "lint:js": "oxlint", no --max-warnings |
| 13 | QueryBuilderV2/QueryBuilderInternals.tsx:44 |
Settled as your call two rounds ago (prefer-arrow-callback / allowNamedFunctions); the thread is open only because I can't reply on it |
Re-affirmed those rather than posting duplicates. Nothing became stale this round, so no threads were resolved; #1, #2, #5–#12, #14–#16 remain resolved. No human threads touched.
Smaller notes
- PR description is still stale on three counts: playground "never covered",
cubejs-client-vue3"stays on ESLint because oxlint cannot parse Vue SFCs", and theimport/named/follow-ups paragraph. It's what lands in the squash commit, so worth a refresh before merge. - Same caveat as the previous eight passes: no
node_modulesin this checkout, so I could not executeoxlint. Since this commit changes no code, the lint state is whateverfdcd3a8had. CI at the time I finished:Lint CI Workflowsand the labeler passed;Build(which runs theOxlintstep),Drivers tests,RustandBuild nativewere still in progress, nothing failed.
Check List
Description of Changes Made
@cubejs-backend/linternow ships an oxlint config instead of an eslintrc one:airbnb-base.jsonis a mechanical port of the old airbnb-base + typescript-eslint rule set (generated fromeslint --print-config, so it is verifiable rather than hand-transcribed) and.oxlintrc.jsonholds the deliberate departures with a reason stated for each; a repo-root.oxlintrc.jsonextends it and owns the ignore list, which replaces the 49 per-packagelintscripts and 46eslintConfigblocks with one rootoxlintrun (2.7s over 873 files, versus 49 sequential ESLint 8 processes in CI), and gets us off a toolchain that is out of runway — ESLint 8.57.1 is unsupported, and #11767 already had to shuffle four formatting rules into@stylistic/eslint-plugin-tsbecause typescript-eslint 8 dropped them. Because oxlint implements no formatting rules,@stylistic/eslint-pluginis loaded viajsPluginsto keep the 48 whitespace rules airbnb-base relies on; of the 192 rules the old config resolved to, 125 map natively with identical options, 48 move to@stylistic/*, 8 are renamed, and the 14 with no equivalent are listed at the top ofairbnb-base.json. Parity was verified by diffing per-file diagnostics against an ESLint run onorigin/master— of the 50 previously-linted files that reportno-unused-vars, 46 match exactly and the 4 that differ are oxlint being more lenient — leaving 0 errors and 149 warnings against a baseline of 0 errors and 178 warnings. The source changes areoxlint --fixoutput and are all formatting:@stylisticunderstands TypeScript syntax where ESLint's core rules did not, soindentin generic argument lists,object-curly-spacing/quote-propsin type literals andspace-before-blockson interface bodies now apply; four files carry a hand-writteneslint-disablefor intentional code. Two things are deliberately left for follow-up:eslint-plugin-importnever actually ran (no TypeScript resolver,import/no-unresolvedoff), soimport/no-cycleand friends fire for the first time and report 173 real dependency cycles plus 2 duplicate exports in client-core — kept off for now — andcategories.correctnessis pinned off so this change stays a faithful port.Client packages are covered by nested configs for
cubejs-client-reactandcubejs-client-dx;cubejs-client-vue3stays on ESLint because oxlint cannot parse Vue SFCs.🤖 Generated with Claude Code