chore: Enforce extraneous dependencies in Oxlint (no-changelog) (#39281)

This commit is contained in:
Matsu
2026-09-22 12:48:49 +00:00
committed by GitHub
parent dfe014a2ba
commit 1bb166dbaa
10 changed files with 71 additions and 67 deletions
@@ -1,6 +1,6 @@
# Oxlint migration blockers
Last reviewed: 2026-09-18
Last reviewed: 2026-09-22
Oxlint version: 1.78.0
This file is the working backlog for the ESLint to Oxlint migration. It focuses on rules that block an Oxlint-only lint command for many packages. `oxlint-gap.json` is the machine-readable source of truth for shared-layer parity.
@@ -21,7 +21,6 @@ These rules come from the shared base or backend layers. Resolve these first bec
| Priority | Rule | Blocker | Suggested next action |
| --- | --- | --- | --- |
| P0 | `import-x/no-extraneous-dependencies` | Oxlint reserves the `import-x` plugin name. The JS plugin bridge has not been validated with an alias. | Test `eslint-plugin-import-x` under an alias. If that is unstable, implement a native package-manifest check. |
| P1 | `no-restricted-syntax` | Oxlint has no AST-selector rule. The base layer uses it for the raw-enum restriction. | Add a purpose-built syntax rule for each active selector. Start with the raw-enum rule. |
| P1 | `typescript/consistent-type-imports` | Oxlint does not honor the `emitDecoratorMetadata` exception. Its fixer can remove imports that DI needs at runtime. | Wait for upstream support or add a tested shim that skips decorated files. Do not enable the current fixer. |
| P1 | `typescript/prefer-optional-chain` | The native nursery rule changes guarded undeclared globals incorrectly. | Re-test after the rule leaves nursery or after the upstream fixer issue is resolved. |
@@ -150,13 +149,12 @@ The main exceptions are:
## Work order
1. Resolve or isolate `import-x/no-extraneous-dependencies`.
2. Replace active `no-restricted-syntax` selectors with focused rules.
3. Resolve decorator-safe `consistent-type-imports` behavior.
4. Audit and reduce `naming-convention` package overrides.
5. Re-test the two nursery rules.
6. Run a JS-plugin bridge spike for `n8n-nodes-base`.
7. Keep a template-only ESLint pass for Vue until native template support exists.
1. Replace active `no-restricted-syntax` selectors with focused rules.
2. Resolve decorator-safe `consistent-type-imports` behavior.
3. Audit and reduce `naming-convention` package overrides.
4. Re-test the two nursery rules.
5. Run a JS-plugin bridge spike for `n8n-nodes-base`.
6. Keep a template-only ESLint pass for Vue until native template support exists.
## Validation commands
-7
View File
@@ -21,13 +21,6 @@
"impact": "Near zero.",
"replacement": "No action unless non-module source becomes supported."
},
"import-x/no-extraneous-dependencies": {
"layer": "base",
"native": null,
"reason": "`import-x` is a reserved plugin name in oxlint, so `eslint-plugin-import-x` cannot load as a jsPlugin under its own name (`Plugin name 'import-x' is reserved`). An alias would permanently change the rule ids, and these rules are the slowest in ESLint and unvalidated through the alpha bridge.",
"impact": "A package can import a dependency it does not declare. Left out of the spike deliberately.",
"replacement": "Validate eslint-plugin-import-x with an alias, or replace the dependency check with a native package-manifest rule."
},
"typescript/prefer-optional-chain": {
"layer": "base",
"native": "typescript/prefer-optional-chain",
+6 -1
View File
@@ -61,6 +61,7 @@ const SCOPE_BY_NAMESPACE = {
/** Namespaces oxlint can only reach through a jsPlugin, so ids stay as ESLint wrote them. */
const JS_PLUGIN_NAMESPACES = new Set([
'@stylistic',
'import-x-alias',
'lodash',
'unused-imports',
'n8n-local-rules',
@@ -77,13 +78,17 @@ function oxlintRuleIds() {
encoding: 'utf8',
maxBuffer: 32 * 1024 * 1024,
});
const start = out.stdout.indexOf('[');
const arrayLine = out.stdout.lastIndexOf('\n[');
const start = arrayLine === -1 ? out.stdout.indexOf('[') : arrayLine + 1;
if (start === -1) throw new Error(`oxlint --rules failed: ${(out.stderr || out.stdout).trim()}`);
return new Set(JSON.parse(out.stdout.slice(start)).map((r) => `${r.scope}/${r.value}`));
}
/** ESLint id -> the oxlint id we would write in a config, or null if oxlint has no such rule. */
function toOxlintId(eslintId, native) {
if (eslintId === 'import-x/no-extraneous-dependencies') {
return 'import-x-alias/no-extraneous-dependencies';
}
const [ns, name] = splitId(eslintId);
if (JS_PLUGIN_NAMESPACES.has(ns)) return eslintId;
if (ns === '') return native.has(`eslint/${name}`) ? name : null;