Skip to content

Remove ESLint and use independent Oxlint rules - #8211

Open
gonzaloriestra wants to merge 10 commits into
gonzalo/oxlint-06-rule-coveragefrom
gonzalo/oxlint-06-remove-eslint
Open

gonzaloriestra wants to merge 10 commits into
gonzalo/oxlint-06-rule-coveragefrom
gonzalo/oxlint-06-remove-eslint

Conversation

@gonzaloriestra

@gonzaloriestra gonzaloriestra commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Remove ESLint from the CLI lint command and installed dependency tree while retaining the public CLI rules and as many existing checks as practical.

WHAT is this pull request doing?

Use native Oxlint, its type-aware checker, Prettier, and the independent rules prepared in #8725. Remove the compatibility adapter, upstream ESLint plugins, repository ESLint configuration, and associated CI/editor wiring. Workspace boundary checks read package manifests directly, removing Nx graph generation from lint startup.

Keep @shopify/eslint-plugin-cli public and preserve all 14 custom rule exports. Its configs.config entry point now contains only CLI rules and test exemptions. Consumers must supply their own shared configs and TypeScript parser. This breaking public config change has a major changeset.

Coverage audit

Against the original configuration, 243 of 301 lint rule names have replacements: 194 native counterparts, 14 retained implementations, and 35 partial independent replacements. 58 rule names have no dedicated replacement; 11 have identified coverage or overlap elsewhere in the existing toolchain. Prettier covers formatting separately. These counts do not mean 58 wholly missing capabilities, and overlap is not proof of full behavioral parity.

Existing tools cover part of this territory:

  • Knip checks unresolved imports, unlisted dependencies, and unused exports/files, within its configured graph and exclusions.
  • TypeScript and esbuild check resolution in their inputs. TypeScript and Prettier reject legacy octal syntax in TypeScript; Oxlint rejects it in strict ES modules. CommonJS JavaScript still has gaps.
  • Existing Oxlint rules/parser catch duplicate parameters and catch-variable shadowing in tested cases. Its CLI reports unused disable comments in full lint and the PR job.
  • Prettier fixes supported JSDoc indentation; TSDoc catches malformed documentation syntax.

Remaining gaps include redundant type assertions, ambiguous default imports, deprecated imports and redundant paths, import checks outside the tools' scopes, advanced JSDoc semantics, consistent returns and atomic updates, Node API metadata, specialized Shopify/style policies, and Nx cycles/tags/buildability/lazy loading. Knip excludes the plugin and e2e workspaces and selected generated files. Several original rules, including consistent returns and undeclared dependencies, applied only to JavaScript; TypeScript does not replace those scopes. The shared TS config does not enable checkJs or noImplicitReturns.

Benchmark

  • Full lint: 55.26s with ESLint compatibility → 11.00s without ESLint, about 5.0× faster (80.1% less time).
  • Core lint/format toolchain, including the public plugin: 19 direct + 725 transitive package instances / 290.07 MiB → 4 direct + 2 transitive instances / 45.36 MiB. That is 99.2% fewer total package instances (744 → 6), 78.9% fewer direct dependencies, 99.7% fewer transitive instances, and 84.4% less package-file size.
  • This PR removes 4,048 lockfile lines (21.6%): 18,757 → 14,709. Across the complete stack, the lockfile shrinks by 3,767 lines (20.4%) from the original baseline (18,476 → 14,709).

Validation

Full lint passes on this PR and #8725, along with the uncached workspace type-check, 147 focused tests, and the coverage audit. The audit compares 1,606 files and reports no undeclared scope, option, severity, or formatting gaps. All non-e2e GitHub Actions checks pass except Breaking change detection, which flags the public plugin’s major changeset.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type and added a changeset

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action.
→ If there's no activity within a week, then a bot will automatically close this.
Thanks for helping to improve Shopify's dev tooling and experience.

@github-actions github-actions Bot closed this Sep 6, 2026
@gonzaloriestra gonzaloriestra reopened this Sep 9, 2026
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 2b07749 to 24f882f Compare September 9, 2026 11:05
@gonzaloriestra
gonzaloriestra added this pull request to stack #8508 September 9, 2026 11:05
@github-actions github-actions Bot closed this Sep 17, 2026
@gonzaloriestra gonzaloriestra added stale-exempt If added, the PR/issue won't be closed by stale-bot and removed no-pr-activity labels Sep 22, 2026
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 24f882f to 9a488dd Compare September 22, 2026 09:04
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 9a488dd to 0e8ac24 Compare October 1, 2026 12:22
@gonzaloriestra gonzaloriestra changed the title Remove the ESLint compatibility layer Remove the separate ESLint lint pass Oct 1, 2026
@gonzaloriestra
gonzaloriestra removed this pull request from stack #8508 October 1, 2026 12:23
@gonzaloriestra
gonzaloriestra changed the base branch from gonzalo/oxlint-05-parity to gonzalo/oxlint-06-rule-coverage October 1, 2026 12:23
@gonzaloriestra
gonzaloriestra added this pull request to stack #8726 October 1, 2026 12:23
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch 2 times, most recently from 5782533 to 2be3736 Compare October 1, 2026 14:58
@github-actions github-actions Bot removed the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label Oct 1, 2026
@gonzaloriestra

Copy link
Copy Markdown
Contributor Author

@amcaplan thanks, I missed those commands. I only left the important ones now.

@amcaplan amcaplan left a comment

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.

Looks great, thanks for doing this work!

Comment thread .changeset/remove-cli-eslint-config-dependencies.md
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 34e5a78 to 4323fcd Compare October 9, 2026 11:00
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch 2 times, most recently from fec2cbc to 961b8df Compare October 9, 2026 11:54
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch 2 times, most recently from 160c2bd to 11b7277 Compare October 9, 2026 12:04

@isaacroldan isaacroldan left a comment

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.

The package lint targets skip the CLI entry scripts.


🤖 Generated by the review-pr skill

Comment thread packages/cli/project.json Outdated
"executor": "nx:run-commands",
"options": {
"command": "pnpm eslint src 'bin/*.js'",
"command": "pnpm oxlint --config ../../oxlint.json --deny-warnings src 'bin/*.js'",

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.

Oxlint doesn't expand this quoted glob, so the CLI entry scripts are skipped. I added debugger to bin/dev.js: this command passed, but passing bin directly failed as expected.

Can we use src bin in both lint and lint:fix, and add a check that covers a file in bin?


🤖 Generated by the review-pr skill

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated both lint and lint:fix to use src bin. Added an integration test for each configured command using a real temporary CLI with debugger in bin/dev.js. Both commands now report no-debugger; restoring the quoted glob makes both regression tests fail.

@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch 2 times, most recently from 043e549 to 91ee29a Compare October 15, 2026 14:50
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch 2 times, most recently from 0031172 to 9f5d8c0 Compare October 15, 2026 15:26
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 9f5d8c0 to 030ff58 Compare October 15, 2026 16:01
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Potential Breaking Changes Detected

This PR contains changes that may break the existing contract.

@shopify/dev_experience — this PR contains breaking changes that require coordination for the next major release.

📦 Major Version Changesets

The following changesets request a major version bump:

Changeset Package
remove-cli-eslint-config-dependencies.md '@shopify/eslint-plugin-cli': major

@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/public/node/cli.d.ts
@@ -1,3 +1,4 @@
+import type { OptionFlag } from '@oclif/core/interfaces';
 import type { LazyCommandLoader } from './custom-oclif-loader.js';
 /**
  * IMPORTANT NOTE: Imports in this module are dynamic to ensure that "setupEnvironmentVariables" can dynamically
@@ -41,7 +42,7 @@ export declare const jsonFlag: {
     json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
 };
 export declare const authAliasFlag: {
-    'auth-alias': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
+    'auth-alias': OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
 };
 /**
  * Builds a `--port` flag that only accepts a valid port number. The flag parses its
@@ -55,7 +56,7 @@ export declare const portFlag: (options?: {
     description?: string;
     env?: string;
     hidden?: boolean;
-}) => import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
+}) => OptionFlag<number | undefined>;
 /**
  * Marks a flag as required when the CLI cannot prompt for a value.
  *
packages/cli-kit/dist/private/node/ui/components/AutocompletePrompt.d.ts
@@ -28,5 +28,5 @@ export interface AutocompletePromptProps<T> {
      */
     searchDebounceMs?: number;
 }
-declare function AutocompletePrompt<T>({ message, choices, infoTable, onSubmit, search, hasMorePages: initialHasMorePages, abortSignal, infoMessage, groupOrder, searchDebounceMs, }: React.PropsWithChildren<AutocompletePromptProps<T>>): ReactElement | null;
+declare const AutocompletePrompt: <T>({ message, choices, infoTable, onSubmit, search, hasMorePages: initialHasMorePages, abortSignal, infoMessage, groupOrder, searchDebounceMs, }: React.PropsWithChildren<AutocompletePromptProps<T>>) => ReactElement | null;
 export { AutocompletePrompt };
\ No newline at end of file
packages/cli-kit/dist/private/node/ui/components/SelectInput.d.ts
@@ -27,5 +27,5 @@ export interface Item<T> {
     helperText?: string;
     disabled?: boolean;
 }
-declare function SelectInput<T>({ items: rawItems, initialItems, onChange, enableShortcuts, focus, emptyMessage, defaultValue, highlightedTerm, loading, errorMessage, hasMorePages, morePagesMessage, availableLines, onSubmit, inputFixedAreaRef, ref, groupOrder, }: SelectInputProps<T>): React.ReactElement | null;
+declare const SelectInput: <T>({ items: rawItems, initialItems, onChange, enableShortcuts, focus, emptyMessage, defaultValue, highlightedTerm, loading, errorMessage, hasMorePages, morePagesMessage, availableLines, onSubmit, inputFixedAreaRef, ref, groupOrder, }: SelectInputProps<T>) => React.ReactElement | null;
 export { SelectInput };
\ No newline at end of file
packages/cli-kit/dist/private/node/ui/components/SelectPrompt.d.ts
@@ -14,5 +14,5 @@ export interface SelectPromptProps<T> {
     infoMessage?: InfoMessageProps['message'];
     groupOrder?: string[];
 }
-declare function SelectPrompt<T>({ message, choices, infoTable, infoMessage, onSubmit, defaultValue, abortSignal, groupOrder, }: React.PropsWithChildren<SelectPromptProps<T>>): ReactElement | null;
+declare const SelectPrompt: <T>({ message, choices, infoTable, infoMessage, onSubmit, defaultValue, abortSignal, groupOrder, }: React.PropsWithChildren<SelectPromptProps<T>>) => ReactElement | null;
 export { SelectPrompt };
\ No newline at end of file
packages/cli-kit/dist/private/node/ui/components/Tasks.d.ts
@@ -10,5 +10,5 @@ interface TasksProps<TContext> {
     noColor?: boolean;
     noProgressBar?: boolean;
 }
-declare function Tasks<TContext>({ tasks, silent, onComplete, abortSignal, noColor, noProgressBar, }: React.PropsWithChildren<TasksProps<TContext>>): React.JSX.Element | null;
+declare const Tasks: <TContext>({ tasks, silent, onComplete, abortSignal, noColor, noProgressBar, }: React.PropsWithChildren<TasksProps<TContext>>) => React.JSX.Element | null;
 export { Tasks };
\ No newline at end of file
packages/cli-kit/dist/private/node/ui/components/Table/Table.d.ts
@@ -11,5 +11,5 @@ export interface TableProps<T extends ScalarDict> {
     rows: T[];
     columns: TableColumn<T>;
 }
-declare function Table<T extends ScalarDict>({ rows, columns: columnsConfiguration }: TableProps<T>): React.JSX.Element;
+declare const Table: <T extends ScalarDict>({ rows, columns: columnsConfiguration }: TableProps<T>) => React.JSX.Element;
 export { Table };
\ No newline at end of file

This branch has not been deployed

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

Labels

Area: @shopify/cli @shopify/cli package issues stale-exempt If added, the PR/issue won't be closed by stale-bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants