Skip to content

Remove ESLint and use independent Oxlint rules - #8211

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

gonzaloriestra wants to merge 4 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, including command-json-output. 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.

Preserve existing type assertions, default imports, and suppression names through dependency-free rule aliases. This reduces the final PR from 257 to 85 changed files (192 to 26 under src). Remaining source edits remove obsolete/unused suppressions. The codegen formatter preserves the existing generated import spacing, eliminating all nine whitespace-only generated-file diffs. Repeated uncached code generation produces identical files; this formatting change does not alter lint rules or coverage.

Disable three native checks that report code accepted by the previous ESLint pipeline: typescript/no-unnecessary-type-assertion, import/no-named-as-default, and import/no-named-as-default-member. Their available options do not give equivalent behavior for these cases. This is an explicit coverage tradeoff to minimize migration changes. Declared type parameters remain checked; inferred bindings keep their existing names.

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.

The ESLint-free audit compares 1,633 surviving files against a frozen baseline. It reports no lint-rule scope, severity, option, or formatting gaps beyond the declared limitations. It also lists manually verified tool overlap separately; it does not prove equivalent scopes across tools. See docs/cli/oxlint-rule-coverage.md and configurations/oxlint-rule-mapping.json for the full mapping and counterexamples.

Benchmark

Apple M3 Pro / macOS arm64, Node 24.15.0, pnpm 10.11.1. Medians of three serial pnpm lint runs per side after one warm-up, alternating order, with Nx local/remote caches disabled and the daemon off. Compared #8725 (e8e0270c0d) with this PR (6daafb361b). No tests or code generation ran concurrently.

  • 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).

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
@github-actions

github-actions Bot commented Oct 1, 2026

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/cli/api/graphql/admin/generated/find_development_theme_by_name.d.ts
@@ -5,12 +5,12 @@ export type FindDevelopmentThemeByNameQueryVariables = Types.Exact<{
 }>;
 export type FindDevelopmentThemeByNameQuery = {
     themes?: {
-        nodes: {
+        nodes: Array<{
             id: string;
             name: string;
             role: Types.ThemeRole;
             processing: boolean;
-        }[];
+        }>;
     } | null;
 };
 export declare const FindDevelopmentThemeByName: DocumentNode<FindDevelopmentThemeByNameQuery, FindDevelopmentThemeByNameQueryVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/get_theme_file_bodies.d.ts
@@ -3,12 +3,12 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
 export type GetThemeFileBodiesQueryVariables = Types.Exact<{
     id: Types.Scalars['ID']['input'];
     after?: Types.InputMaybe<Types.Scalars['String']['input']>;
-    filenames?: Types.InputMaybe<Types.Scalars['String']['input'][] | Types.Scalars['String']['input']>;
+    filenames?: Types.InputMaybe<Array<Types.Scalars['String']['input']> | Types.Scalars['String']['input']>;
 }>;
 export type GetThemeFileBodiesQuery = {
     theme?: {
         files?: {
-            nodes: {
+            nodes: Array<{
                 filename: string;
                 size: unknown;
                 checksumMd5?: string | null;
@@ -22,11 +22,11 @@ export type GetThemeFileBodiesQuery = {
                     __typename: 'OnlineStoreThemeFileBodyUrl';
                     url: string;
                 };
-            }[];
-            userErrors: {
+            }>;
+            userErrors: Array<{
                 filename: string;
                 code: Types.OnlineStoreThemeFileResultType;
-            }[];
+            }>;
             pageInfo: {
                 hasNextPage: boolean;
                 endCursor?: string | null;
packages/cli-kit/dist/cli/api/graphql/admin/generated/get_theme_file_checksums.d.ts
@@ -7,15 +7,15 @@ export type GetThemeFileChecksumsQueryVariables = Types.Exact<{
 export type GetThemeFileChecksumsQuery = {
     theme?: {
         files?: {
-            nodes: {
+            nodes: Array<{
                 filename: string;
                 size: unknown;
                 checksumMd5?: string | null;
-            }[];
-            userErrors: {
+            }>;
+            userErrors: Array<{
                 filename: string;
                 code: Types.OnlineStoreThemeFileResultType;
-            }[];
+            }>;
             pageInfo: {
                 hasNextPage: boolean;
                 endCursor?: string | null;
packages/cli-kit/dist/cli/api/graphql/admin/generated/get_themes.d.ts
@@ -5,12 +5,12 @@ export type GetThemesQueryVariables = Types.Exact<{
 }>;
 export type GetThemesQuery = {
     themes?: {
-        nodes: {
+        nodes: Array<{
             id: string;
             name: string;
             role: Types.ThemeRole;
             processing: boolean;
-        }[];
+        }>;
         pageInfo: {
             hasNextPage: boolean;
             endCursor?: string | null;
packages/cli-kit/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.d.ts
@@ -5,7 +5,7 @@ export type MetafieldDefinitionsByOwnerTypeQueryVariables = Types.Exact<{
 }>;
 export type MetafieldDefinitionsByOwnerTypeQuery = {
     metafieldDefinitions: {
-        nodes: {
+        nodes: Array<{
             key: string;
             name: string;
             namespace: string;
@@ -14,7 +14,7 @@ export type MetafieldDefinitionsByOwnerTypeQuery = {
                 category: string;
                 name: string;
             };
-        }[];
+        }>;
     };
 };
 export declare const MetafieldDefinitionsByOwnerType: DocumentNode<MetafieldDefinitionsByOwnerTypeQuery, MetafieldDefinitionsByOwnerTypeQueryVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/public_api_versions.d.ts
@@ -4,9 +4,9 @@ export type PublicApiVersionsQueryVariables = Types.Exact<{
     [key: string]: never;
 }>;
 export type PublicApiVersionsQuery = {
-    publicApiVersions: {
+    publicApiVersions: Array<{
         handle: string;
         supported: boolean;
-    }[];
+    }>;
 };
 export declare const PublicApiVersions: DocumentNode<PublicApiVersionsQuery, PublicApiVersionsQueryVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_create.d.ts
@@ -12,10 +12,10 @@ export type ThemeCreateMutation = {
             name: string;
             role: Types.ThemeRole;
         } | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeCreate: DocumentNode<ThemeCreateMutation, ThemeCreateMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_delete.d.ts
@@ -6,10 +6,10 @@ export type ThemeDeleteMutationVariables = Types.Exact<{
 export type ThemeDeleteMutation = {
     themeDelete?: {
         deletedThemeId?: string | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeDelete: DocumentNode<ThemeDeleteMutation, ThemeDeleteMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_duplicate.d.ts
@@ -11,10 +11,10 @@ export type ThemeDuplicateMutation = {
             name: string;
             role: Types.ThemeRole;
         } | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeDuplicate: DocumentNode<ThemeDuplicateMutation, ThemeDuplicateMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_files_delete.d.ts
@@ -2,18 +2,18 @@ import * as Types from './types.js';
 import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
 export type ThemeFilesDeleteMutationVariables = Types.Exact<{
     themeId: Types.Scalars['ID']['input'];
-    files: Types.Scalars['String']['input'][] | Types.Scalars['String']['input'];
+    files: Array<Types.Scalars['String']['input']> | Types.Scalars['String']['input'];
 }>;
 export type ThemeFilesDeleteMutation = {
     themeFilesDelete?: {
-        deletedThemeFiles?: {
+        deletedThemeFiles?: Array<{
             filename: string;
-        }[] | null;
-        userErrors: {
+        }> | null;
+        userErrors: Array<{
             filename?: string | null;
             code?: Types.OnlineStoreThemeFilesUserErrorsCode | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeFilesDelete: DocumentNode<ThemeFilesDeleteMutation, ThemeFilesDeleteMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_files_upsert.d.ts
@@ -1,18 +1,18 @@
 import * as Types from './types.js';
 import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
 export type ThemeFilesUpsertMutationVariables = Types.Exact<{
-    files: Types.OnlineStoreThemeFilesUpsertFileInput[] | Types.OnlineStoreThemeFilesUpsertFileInput;
+    files: Array<Types.OnlineStoreThemeFilesUpsertFileInput> | Types.OnlineStoreThemeFilesUpsertFileInput;
     themeId: Types.Scalars['ID']['input'];
 }>;
 export type ThemeFilesUpsertMutation = {
     themeFilesUpsert?: {
-        upsertedThemeFiles?: {
+        upsertedThemeFiles?: Array<{
             filename: string;
-        }[] | null;
-        userErrors: {
+        }> | null;
+        userErrors: Array<{
             filename?: string | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeFilesUpsert: DocumentNode<ThemeFilesUpsertMutation, ThemeFilesUpsertMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_publish.d.ts
@@ -10,10 +10,10 @@ export type ThemePublishMutation = {
             name: string;
             role: Types.ThemeRole;
         } | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemePublish: DocumentNode<ThemePublishMutation, ThemePublishMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_update.d.ts
@@ -11,10 +11,10 @@ export type ThemeUpdateMutation = {
             name: string;
             role: Types.ThemeRole;
         } | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const ThemeUpdate: DocumentNode<ThemeUpdateMutation, ThemeUpdateMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-cancel.d.ts
@@ -19,10 +19,10 @@ export type BulkOperationCancelMutation = {
             type: Types.BulkOperationType;
             url?: string | null;
         } | null;
-        userErrors: {
-            field?: string[] | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const BulkOperationCancel: DocumentNode<BulkOperationCancelMutation, BulkOperationCancelMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.d.ts
@@ -18,11 +18,11 @@ export type BulkOperationRunMutationMutation = {
             status: Types.BulkOperationStatus;
             url?: string | null;
         } | null;
-        userErrors: {
+        userErrors: Array<{
             code?: Types.BulkMutationErrorCode | null;
-            field?: string[] | null;
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const BulkOperationRunMutation: DocumentNode<BulkOperationRunMutationMutation, BulkOperationRunMutationMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.d.ts
@@ -16,11 +16,11 @@ export type BulkOperationRunQueryMutation = {
             status: Types.BulkOperationStatus;
             url?: string | null;
         } | null;
-        userErrors: {
+        userErrors: Array<{
             code?: Types.BulkOperationUserErrorCode | null;
-            field?: string[] | null;
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const BulkOperationRunQuery: DocumentNode<BulkOperationRunQueryMutation, BulkOperationRunQueryMutationVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/list-bulk-operations.d.ts
@@ -7,7 +7,7 @@ export type ListBulkOperationsQueryVariables = Types.Exact<{
 }>;
 export type ListBulkOperationsQuery = {
     bulkOperations: {
-        nodes: {
+        nodes: Array<{
             id: string;
             status: Types.BulkOperationStatus;
             errorCode?: Types.BulkOperationErrorCode | null;
@@ -16,7 +16,7 @@ export type ListBulkOperationsQuery = {
             completedAt?: unknown | null;
             url?: string | null;
             partialDataUrl?: string | null;
-        }[];
+        }>;
     };
 };
 export declare const ListBulkOperations: DocumentNode<ListBulkOperationsQuery, ListBulkOperationsQueryVariables>;
\ No newline at end of file
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/staged-uploads-create.d.ts
@@ -1,22 +1,22 @@
 import * as Types from './types.js';
 import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
 export type StagedUploadsCreateMutationVariables = Types.Exact<{
-    input: Types.StagedUploadInput[] | Types.StagedUploadInput;
+    input: Array<Types.StagedUploadInput> | Types.StagedUploadInput;
 }>;
 export type StagedUploadsCreateMutation = {
     stagedUploadsCreate?: {
-        stagedTargets?: {
+        stagedTargets?: Array<{
             url?: string | null;
             resourceUrl?: string | null;
-            parameters: {
+            parameters: Array<{
                 name: string;
                 value: string;
-            }[];
-        }[] | null;
-        userErrors: {
-            field?: string[] | null;
+            }>;
+        }> | null;
+        userErrors: Array<{
+            field?: Array<string> | null;
             message: string;
-        }[];
+        }>;
     } | null;
 };
 export declare const StagedUploadsCreate: DocumentNode<StagedUploadsCreateMutation, StagedUploadsCreateMutationVariables>;
\ No newline at end of file

@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 added Area: @shopify/cli @shopify/cli package issues and removed no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. labels Oct 1, 2026
@gonzaloriestra gonzaloriestra changed the title Remove the separate ESLint lint pass Remove ESLint and use independent Oxlint rules Oct 1, 2026
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from cbbfb0f to 5e5074d Compare October 1, 2026 16:10
@gonzaloriestra
gonzaloriestra force-pushed the gonzalo/oxlint-06-remove-eslint branch from 5e5074d to 5261c59 Compare October 1, 2026 16:17
@github-actions

github-actions Bot commented Oct 1, 2026

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

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.

1 participant