Skip to content

refactor(linter): migrate @cubejs-backend/linter from ESLint to oxlint - #11790

Open
ovr wants to merge 11 commits into
masterfrom
migrate-linter-to-oxlint
Open

refactor(linter): migrate @cubejs-backend/linter from ESLint to oxlint#11790
ovr wants to merge 11 commits into
masterfrom
migrate-linter-to-oxlint

Conversation

@ovr

@ovr ovr commented Sep 7, 2026

Copy link
Copy Markdown
Member

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Description of Changes Made

@cubejs-backend/linter now ships an oxlint config instead of an eslintrc one: airbnb-base.json is a mechanical port of the old airbnb-base + typescript-eslint rule set (generated from eslint --print-config, so it is verifiable rather than hand-transcribed) and .oxlintrc.json holds the deliberate departures with a reason stated for each; a repo-root .oxlintrc.json extends it and owns the ignore list, which replaces the 49 per-package lint scripts and 46 eslintConfig blocks with one root oxlint run (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-ts because typescript-eslint 8 dropped them. Because oxlint implements no formatting rules, @stylistic/eslint-plugin is loaded via jsPlugins to 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 of airbnb-base.json. Parity was verified by diffing per-file diagnostics against an ESLint run on origin/master — of the 50 previously-linted files that report no-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 are oxlint --fix output and are all formatting: @stylistic understands TypeScript syntax where ESLint's core rules did not, so indent in generic argument lists, object-curly-spacing/quote-props in type literals and space-before-blocks on interface bodies now apply; four files carry a hand-written eslint-disable for intentional code. Two things are deliberately left for follow-up: eslint-plugin-import never actually ran (no TypeScript resolver, import/no-unresolved off), so import/no-cycle and friends fire for the first time and report 173 real dependency cycles plus 2 duplicate exports in client-core — kept off for now — and categories.correctness is pinned off so this change stays a faithful port.

Client packages are covered by nested configs for cubejs-client-react and cubejs-client-dx; cubejs-client-vue3 stays on ESLint because oxlint cannot parse Vue SFCs.

🤖 Generated with Claude Code

@ovr
ovr requested review from a team as code owners September 7, 2026 16:03
@github-actions github-actions Bot added driver:mongodb Issues relating to the MongoBI driver driver:redshift Issues relating to the Redshift driver driver:mysql Issues relating to the MySQL/MariaDB driver driver:bigquery Issues related to the BigQuery driver driver:snowflake Issues relating to the Snowflake driver client:core Issues relating to the JavaScript client SDK driver:clickhouse Issues related to the ClickHouse driver driver:athena Issues related to the AWS Athena driver driver:mssql Issues relating to the MSSQL driver driver:oracle Issues relating to the Oracle driver driver:prestodb Issues relating to the PrestoDB driver driver:postgres Issues relating to the Postgres driver driver:hive Issues relating to the Apache Hive driver client:react Issues relating to the React client SDK client:playground Issues relating to the Developer Playground cube store Issues relating to Cube Store driver:mysql-aurora-serverless Issues relating to the AWS MySQL Aurora Serverless driver driver:sqlite Issues relating to the SQLite driver backend:cli Issues relating to the CLI utility driver:druid Issues relating to the Druid driver driver:crate rust Pull requests that update Rust code driver:dremio driver:questdb javascript Pull requests that update Javascript code labels Sep 7, 2026
Comment thread packages/cubejs-client-ngx/src/query-builder/query-members.ts
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 1m 52s —— View job


Re-review of 7ab3e4b (yarn.lock): lockfile checks out — no new findings, 7 prior still open (2 medium, 5 low)

Full re-review

The commit does what it says

3f9579d..7ab3e4b is yarn.lock only, 17 insertions / 296 deletions. I verified the substantive claims against the tree rather than the message:

  • eslint is gone. No eslint@… entry remains in yarn.lock — the only eslint* top-level keys left are eslint-config-airbnb{,-base}, eslint-plugin-jsx-a11y, eslint-plugin-react (all reachable only from packages/cubejs-playground), plus eslint-scope@5.1.1 and eslint-visitor-keys@^3/^4, which are pulled by non-ESLint consumers. No eslint binary in the tree, matching the message.
  • No orphaned plugin trees. @typescript-eslint/* is down to a single @typescript-eslint/types@^8.56.0 (transitive under @stylistic/eslint-plugin@5.10.0); eslint-plugin-{import,node,unicorn,jest} are absent entirely — correct, since oxlint reimplements those natively.
  • The new deps resolve. @stylistic/eslint-plugin@5.10.0 and the full @oxlint/binding-*@1.82.0 platform set are present, so the jsPlugins wiring has something to load on every CI platform.
  • @cubejs-backend/linter@1.7.35 is still a devDependency of 46 packages even though nothing in those packages runs lint anymore. Harmless (devDeps don't reach consumers, and the workspace resolves it locally), just worth a sweep whenever the follow-ups land.

The eslint-config-airbnb / jsx-a11y / react survivors in cubejs-playground are called out in the commit message as a follow-up, and the reasoning is right: that package has no lint script, is in oxlint's ignorePatterns, and its only ESLint runner came transitively from the old @cubejs-backend/linter. They are now a config and two plugins with no engine — dead, but inert.

One new hygiene note (not worth an inline comment)

Root package.json:58 still carries "prettier": "^2.0.5", and it is now provably unused: no root .prettierrc, no script in any workspace package invokes it, and the two .prettierrc files left in the tree (packages/cubejs-playground, packages/cubejs-client-vue3) belong to packages that don't declare prettier. 3f9579d deleted the one .prettierrc that had a real workflow behind it. Since this commit's whole job is settling dependency state after the migration, dropping root prettier — together with the .prettierrc entries still in the paths: filters at push.yml:15,34 and master.yml:9, which match no root file — would finish the cleanup.

Prior findings — all still open

# 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.

• branch `migrate-linter-to-oxlint`

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.22%. Comparing base (cdcd03a) to head (1d1aab4).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
packages/cubejs-backend-cloud/src/cloud.ts 0.00% 1 Missing ⚠️
packages/cubejs-backend-shared/src/promises.ts 87.50% 1 Missing ⚠️
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     
Flag Coverage Δ
cube-backend 60.15% <88.88%> (?)
cubesql 84.67% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 3m 32s —— View job


Re-review of 9e873e3 (playground → oxlint): 1 high, 2 medium, 1 low new; 7 prior still open (2 medium, 5 low). yarn lint is red at HEAD.

Full re-review
  • Review new commit 9e873e3
  • Verify airbnb-react.json extraction / config wiring
  • Spot-check non-mechanical source changes
  • Re-check prior findings
  • Resolve stale threads & post

The headline

The commit message says it plainly — INCOMPLETE: yarn lint is red, 279 errors left — and taking packages/cubejs-playground/ off the root ignore list in the same commit means that redness is now the PR's state, not the package's. The Oxlint step in Build (push.yml:201-202) runs bare oxlint over the whole repo, so this branch cannot go green until the remaining no-shadow (146), no-nested-ternary (30), react/no-array-index-key (13), no-use-before-define (12), jsx-a11y (9) and no-throw-literal (5) are dealt with. Turning those six "off" in the package's own config with a TODO would let the ~1000 already-fixed findings become enforceable now rather than parking them behind the 279 — details inline. A visible instance of the leftovers, in case it's a useful smoke test: QueryBuilderChart.tsx:30 has a double blank line against @stylistic/no-multiple-empty-lines: max 1.

What's good

  • Hoisting the React rule set into @cubejs-backend/linter/airbnb-react.json is the right call — cubejs-client-react/.oxlintrc.json drops from 928 lines to 17, and both React packages now share one set. The "no oxlint equivalent, not carried over" header lists the PropTypes/class-component family, matching airbnb-base.json's convention.
  • Verifying equivalence by linting a probe .tsx with option-dependent violations (max-len 120, jsx-wrap-multilines, anchor-is-valid with components: ["Link"]) is the right method given --print-config doesn't resolve extends — that's the failure mode that would have made a config diff meaningless.
  • The three departures are each justified by a count and a reason, and the overrides-outranks-top-level-rules note is exactly the kind of thing that costs the next person an hour. The --fix-suggestions warning (no-void rewriting void runQuery();undefined;, silently deleting the call) is a genuinely dangerous fixer and worth having recorded in the history.
  • I read every non-whitespace change in the 127 files (git diff --ignore-all-space 7ab3e4b 9e873e3). The hand-fixed set is what the message claims: 22 catch (e: any)catch { (all bindings genuinely unused), letconst across the react-aria drag/drop code, name: namename, and the dead getJSONValidator removal — I confirmed no remaining reference to it and that convertGraphQLToJsonQuery is still used by getGraphQLValidator, so the import survives correctly. The CHART_HEIGHT/maxHeight removal is safe: maxHeight was destructured but never read even before this commit, and no caller passes it (QueryBuilderInternals.tsx:128 is the only call site).
  • The throw '' sites are left alone, which is right — throw ''; // do not show any error message is load-bearing for the ui-kit form validators.

New findings

# 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 --fix outputs now have arguments hanging at the wrong indent — FilterGroup.tsx:28-35 (), \n [members]);), QueryBuilder.tsx:39-44, QueryBuilderInternals.tsx:83-84, and useCommitPress(() => runQuery(),\n true);. Cosmetically worse than what it replaced, and @stylistic/indent apparently 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-style removing the return but leaving the object literal on its own lines.
  • QueryBuilderChartProps.maxHeight is now a prop nothing reads; since the CHART_HEIGHT default went away with it, dropping it from the interface would keep callers honest.
  • packages/cubejs-playground/.prettierrc survives, same as cubejs-client-vue3's — and .prettierrc is still in the paths: filter of both workflows plus root prettier: ^2.0.5 in devDependencies. 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_modules in this sandbox, so I can't execute oxlint. The "1372 → 279", "10 identical probe findings", "yarn unit 1 passed" and "160 pre-existing tsc errors" numbers are taken as stated; everything above is from reading the tree. Build, Drivers tests, Rust and Build native were all still in progress when I finished; nothing had failed yet.
• branch `migrate-linter-to-oxlint`

Comment thread packages/cubejs-linter/package.json Outdated
Comment thread packages/cubejs-playground/package.json
});

const QueryBuilderInternals = memo(function QueryBuilderInternals() {
const QueryBuilderInternals = memo(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }]

Comment thread packages/cubejs-playground/.oxlintrc.json Outdated
ovr and others added 6 commits September 7, 2026 20:51
`@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.
@ovr
ovr force-pushed the migrate-linter-to-oxlint branch from c24ae38 to f971914 Compare September 7, 2026 18:55
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 6m 17s —— View job


Re-review of f971914 (playground errors cleared): findings #10 and #11 resolved — 2 new (1 medium, 1 low), 6 prior still open (2 medium, 4 low)

Full re-review
  • Review new commit f971914
  • Verify finding Update normalizePivotConfig method #10 (red lint) is actually cleared
  • Trace every non-mechanical source change for semantic equivalence
  • Re-check prior findings
  • Resolve stale threads & post

Findings #10 and #11 are resolved

f971914 is the other half of 0934166: the package's own config no longer parks any rule, and the six rule families that were red (no-shadow 142, no-nested-ternary 29, react/no-array-index-key 13, no-use-before-define 12, jsx-a11y 9, no-throw-literal 5) are all handled in the tree rather than switched off. The only rule the config gains is jsx-a11y/anchor-is-valid with components: [], and the reason recorded there is right — @cube-dev/ui-kit's Link is Omit<CubeButtonProps, 'ref'> driven by onPress, the rule can't distinguish it from react-router's by import, and dropping the list leaves real <a> checking intact. Resolved that thread.

0934166 now also carries the yarn.lock regeneration — no eslint-config-airbnb, eslint-plugin-jsx-a11y or eslint-plugin-react entries survive, so the Build job's "Non empty changeset" guard has nothing to trip on. Resolved that thread too.

The rewrites hold up

I read all 899 changed lines and checked each non-mechanical one against its original. The ones with a real chance of drifting, and why they don't:

  • ValuesInput.tsx suffix — the doubly-nested allowSuggestions && !suggestionError ? (!isSuggestionLoading ? Button : null) : (suggestionError && !hasError ? Grid : null) became three flat branches. I enumerated all eight combinations of the three booleans; every one lands on the same node.
  • SidePanelCubeItem.tsx mapElementsconst arrow → function declaration, moved below hierarchiesElementMap. Hoisting is what makes the useMemo above still able to call it, and the isFolderOpen ? true : XisFolderOpen || X collapse in shownMembers is exact. The filterMembers inner shadow (usedMembers.filter((m) => …).includes(m)) was renamed on the inner binding only, which is the correct one — .includes(m) sits outside the inner arrow.
  • hooks/query-builder.ts — the 30-odd updateQuery((query) => …)(currentQuery) renames are the exact shape the commit message flags as having bitten the renamer, so I checked the shorthand returns specifically: every return { filters } became return { filters: currentFilters }, and none of the surviving shorthands resolve to a renamed binding. connectionId's IIFE-to-find collapse and the c1joined/c2joined comparator rewrite are both value-identical.
  • QueryBuilderResults.tsx / QueryBuilderChart.tsxisLoading ? (isExpanded ? A : undefined) : (outdated ? B : undefined) → two guarded branches; identical on all four combinations. {cond && <X/>} inside antd Space and ui-kit Flex is safe: React.Children normalises false to null before rc-util's toArray sees it, so no phantom ant-space-item appears.
  • RollupDesigner.tsx cubeName, get-member-search-name.ts, TimeListMember.tsx definedGranularities, shared/helpers.ts w/h, ListCube.tsx icons, DatabaseForm.tsx, ListMember.tsx, MemberSection.tsx, PreAggregationStatus.tsx — all ternary-to-branch conversions I walked case by case; all equivalent.
  • utils.ts:271 and grid/Flex.tsx:83 add an explicit return false / return undefined where the original fell off the end — same value, consistent-return satisfied.
  • TableQueryRenderer.tsx:24 value == undefinedvalue == null: loose equality made these already identical.

The a11y additions are genuine fixes rather than rule-appeasement: rel="noreferrer" on three target="_blank" links, title on the Vizard iframe, aria-selected={false} on both role="option" drop indicators (correctly placed before the {...dropIndicatorProps} spread so the hook can still override it).

New findings

# 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.tsx deletes the 47-line BuildRange component. I confirmed it's genuinely dead (no reference anywhere in src/, and ucfirst was 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 the BuildRangeBuildRangeValue type rename above it unnecessary, since nothing shadows that name anymore.
  • The react/no-array-index-key justification is copy-pasted verbatim five times across QueryBuilderFilters.tsx and LogicalFilter.tsx. Stating it once where the filters array is defined and leaving a bare // eslint-disable-next-line react/no-array-index-key at each site would read better; five identical paragraphs in two files is the kind of thing that goes stale unevenly.
  • Settings/Settings.tsx:99 drops the <label> wrapper for aria-label="Limit". Correct for the rule and no association is lost (the label had no text of its own — "Limit" is the input's prefix), just noting it's a DOM change inside a positioned dropdown rather than an attribute-only fix.
  • ButtonDropdown.tsx:87 satisfies the static-element-interaction rule with role="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,55 and QueryBuilderSidePanel.tsx:384 turn throw 'Invalid query' into throw new Error(...). The complementError reasoning in the message is right that the truthy-.message branch returns oe itself, so consumers reading .message see 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_modules in this checkout, so I could not execute oxlint. "0 errors, 2 warnings", "151 warnings repo-wide", "160 pre-existing tsc errors" and "yarn unit passes" are taken as stated. Build (which runs the Oxlint step), Drivers tests, Rust and Build native were all still in progress when I finished; nothing had failed.
• branch [`migrate-linter-to-oxlint`](https://github.com/cube-js/cube/tree/migrate-linter-to-oxlint)

Comment thread packages/cubejs-playground/src/QueryBuilderV2/QueryBuilder.tsx Outdated
Comment thread packages/cubejs-playground/src/QueryBuilderV2/components/FilterMember.tsx Outdated
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

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 2m 2s —— View job


Re-review of 8d9231b: all four fixes verified — findings #5, #12, #14, #15 resolved; #13 settled as author's call; 6 prior still open (2 medium, 4 low)

Full re-review

The four fixes are correct

f971914..8d9231b is 5 files, 7 insertions / 5 deletions. Each change does what the message says:

  • QueryBuilder.tsx:138 — back to props.children || <QueryBuilderInternals />. That restores the original truthiness fallback exactly, and || satisfies no-nested-ternary the same way ?? did. ✅
  • ValuesInput.tsx:359[...new Set(values)].map((value) => …). Set preserves first-occurrence order, so the rendered order is unchanged for the (normal) duplicate-free case, and the index binding that no-array-index-key objected to is gone rather than laundered. The argument in the message is the load-bearing one: onRemove filters by value and already removed every copy, so a second identical tag was never independently actionable — collapsing them is closer to the real behaviour than rendering two. ✅
  • FilterMember.tsx:235 — same shape, and the ?.&& swap is behaviour-preserving: undefined still yields undefined, and [] is truthy so the empty case still yields []. ✅
  • packages/cubejs-linter/package.json:20airbnb-react.json added to files; the published tarball now carries the whole rule set. ✅
  • packages/cubejs-hive-driver/package.json — orphaned "scripts": {} dropped; both manifests still parse. ✅

[...new Set(...)] needs downlevel iteration; playground's tsconfig.json is target: ES2020 with lib: [dom, esnext], so both spreads compile.

On #13 (prefer-arrow-callback / allowNamedFunctions): the reasoning for not taking it is fair and the trade-off is a matter of taste, so that's settled — your call. Worth knowing the cost is still real (anonymous entries in DevTools and in component stacks), and it's recoverable later either by allowNamedFunctions: true or by setting displayName on the handful of memo/forwardRef wrappers. I left the thread open only because I can't reply on it; treat it as closed.

One new note, low

# 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_modules in this checkout, so I could not execute oxlint — the formatting of the new wrapped .map( in FilterMember.tsx (function-paren-newline: consistent, @stylistic/indent) reads fine to me but is unverified. Build (which runs the Oxlint step), Drivers tests, Rust and Build native were all in progress when I finished; nothing had failed, and Lint CI Workflows passed.
• branch [`migrate-linter-to-oxlint`](https://github.com/cube-js/cube/tree/migrate-linter-to-oxlint)

Comment thread packages/cubejs-playground/src/QueryBuilderV2/components/FilterMember.tsx Outdated
ovr added 3 commits September 7, 2026 21:55
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

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 2m 43s —— View job


Re-review of 332bbd2, ac18b10, fdcd3a8: findings #2, #6, #8, #9, #16 all resolved — 1 new (low), 3 prior still open (all low)

Full re-review

Five findings closed, and the reasoning behind each holds

  • ResultSet.normalizePivotConfig() fix #6 import/named (332bbd2) — folded into the off-list, and the recorded reason is the right one: the rule's verdict depends on whether cubejs-client-core/dist exists, CI is the cold case (yarn installlint:npmlint:js, no build), and GRANULARITIES really is exported (client-core/src/time.ts re-exported from src/index.ts), so the warm-tree finding was a false positive. The eslint-disable-next-line import/named in cubejs-client-vue3/src/index.js goes away with it, which is the tell that the rule was never earning its keep. ✅
  • MUTEX for requests #2 no-shadow (332bbd2) — documented rather than remapped, with an A/B against eslint 8.57.1 + typescript-eslint 8.46.0 showing core no-shadow already ignores type-over-value. That matches what the tree shows: the only no-shadow suppression left in ngx is MemberType.Order, which is the enum-member-over-imported-binding case the note says both linters report. ✅
  • Introduce Query class in ResultSet #8 heuristics (ac18b10)disableHeuristics() now sets _disableHeuristics = true, enableHeuristics() sets it false. Correct against all four gates in the file (lines 73, 118, 122, 129), and the field still initialises to false, so the default (heuristics on) is unchanged. ✅
  • Add a method to ResultSet to retrieve the format for query member  #9 ngx CI coverage (fdcd3a8) — the new Build client-ngx step is placed after Oxlint, which is what keeps the lint run cold-tree and consistent with the import/named reasoning above. The build:client-core prerequisite is real and correctly ordered: ngx's tsconfig.json maps @cubejs-client/core../cubejs-client-core, resolved under moduleResolution: "node" via the top-level typings: dist/src/index.d.ts, which only build:client-core produces. ngx pins typescript@~5.4.5 for Angular 18 and yarn.lock carries both that and ~6.0.3, so the TS 6 migration doesn't reach into this build. ✅
  • Add dependency "@cubejs-backend/server-core" installs "@cubejs-backend" #16 uniqArray (fdcd3a8) — helper used at both sites, imports added; Array.from(new Set(...)) is the same semantics as the spread it replaces, so the dedup argument from the previous round is unchanged. The wrapped form comes to 103 chars, under max-len: 120. ✅

New finding

# 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-ngx step adds an ng build (plus a client-core tsc) 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.json sets outDir: "../../out-tsc/lib", and out-tsc isn'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 the import/named/follow-ups paragraph. It's what lands in the squash commit.
  • Same caveat as the previous seven passes: no node_modules in this checkout, so I could not execute oxlint, ng build or the A/B against ESLint. The parity claims in airbnb-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.
• branch [`migrate-linter-to-oxlint`](https://github.com/cube-js/cube/tree/migrate-linter-to-oxlint)

// `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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 1m 6s —— View job


Re-review of 1d1aab4 (comment trimming): comment-only, no semantic change — no new findings; 4 prior still open (all low), one of which this commit could have closed

Full re-review

The commit is comment-only, and I checked that it really is

fdcd3a8..1d1aab4 is 11 files, 12 insertions / 40 deletions. Reading each hunk: every removal is a comment line, and the only thing that could have been mistaken for code is EditQueryDialogForm.tsx:65, where throw ''; // do not show any error message loses its trailing comment — the statement is unchanged and the reason now sits on the line above it. No eslint-disable-next-line directive was dropped, no rule name altered, no key= expression touched.

It's the right set of cuts

  • The three-way restatement of "extends merges rules/plugins/jsPlugins but not env/ignorePatterns" collapses to the single canonical statement in packages/cubejs-linter/.oxlintrc.json:7-9, which is where someone hits the surprise. Root .oxlintrc.json, both React configs and airbnb-react.json just do the restating now without narrating it.
  • The react/no-array-index-key rationale goes from a two-line paragraph copy-pasted six times to a one-liner. It's still repeated six times, but a one-line repeat is cheap and each one is at the site it excuses.
  • QueryBuilderResults.tsx:208,834 and graphql-converters.ts:57 lose comments that restated the line under them ([...Array(n)] is visibly a range; graphQLOperator's body is three lines).
  • The two survivors that got shortened rather than deleted are the right ones to keep: MemberType.Order's "an enum member is not a binding" and EditQueryDialogForm's "async-validator only reads .message when truthy". Both encode a fact about an external library or the TS scoping model that the code cannot state itself, so deleting them would let the next edit re-break them.

Prior findings — four still open, all low

# 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 the import/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_modules in this checkout, so I could not execute oxlint. Since this commit changes no code, the lint state is whatever fdcd3a8 had. CI at the time I finished: Lint CI Workflows and the labeler passed; Build (which runs the Oxlint step), Drivers tests, Rust and Build native were still in progress, nothing failed.
• branch [`migrate-linter-to-oxlint`](https://github.com/cube-js/cube/tree/migrate-linter-to-oxlint)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:cli Issues relating to the CLI utility client:core Issues relating to the JavaScript client SDK client:ngx Issues relating to the Angular client SDK client:playground Issues relating to the Developer Playground client:react Issues relating to the React client SDK client:vue Issues relating to the Vue client SDK cube store Issues relating to Cube Store cubejs-jdbc-driver data source driver driver:athena Issues related to the AWS Athena driver driver:bigquery Issues related to the BigQuery driver driver:clickhouse Issues related to the ClickHouse driver driver:crate driver:databricks driver:dremio driver:druid Issues relating to the Druid driver driver:duckdb driver:firebolt driver:hive Issues relating to the Apache Hive driver driver:materialize driver:mongodb Issues relating to the MongoBI driver driver:mssql Issues relating to the MSSQL driver driver:mysql Issues relating to the MySQL/MariaDB driver driver:mysql-aurora-serverless Issues relating to the AWS MySQL Aurora Serverless driver driver:oracle Issues relating to the Oracle driver driver:pinot driver:postgres Issues relating to the Postgres driver driver:prestodb Issues relating to the PrestoDB driver driver:questdb driver:redshift Issues relating to the Redshift driver driver:snowflake Issues relating to the Snowflake driver driver:sqlite Issues relating to the SQLite driver driver:trino Issues relating to the Trino driver javascript Pull requests that update Javascript code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants