eslint-plugin-import-next 2.3.6 → 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  ## [2.3.6] - 2026-05-03
2
2
 
3
+ ## 2.3.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [#141](https://github.com/ofri-peretz/eslint/pull/141) [`38ab670`](https://github.com/ofri-peretz/eslint/commit/38ab670a0221684f4fd3d5dc3c05ddec7458ca2b) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix: remove false `meta.fixable: 'code'` declarations from 21 rules that had no `fix()` function
8
+
9
+ Rules that declared `fixable: 'code'` in their ESLint meta without an actual `fix()` implementation would show the ⚡ auto-fix icon in editors and CI formatters but apply no change when `--fix` was run. This patch removes the misleading declaration from:
10
+ - `browser-security/no-clickjacking`
11
+ - `import-next/first`, `named`, `no-barrel-import`, `no-import-module-exports`, `no-namespace`
12
+ - `node-security/no-buffer-overread`, `no-unsafe-dynamic-require`, `no-zip-slip`
13
+ - `react-features/react-no-inline-functions`
14
+ - `reliability/no-jsdoc-terminator-in-example` (uses `suggest`, not auto-fix; corrected to `hasSuggestions: true` only)
15
+ - `secure-coding/no-directive-injection`, `no-electron-security-issues`, `no-graphql-injection`, `no-improper-sanitization`, `no-improper-type-validation`, `no-ldap-injection`, `no-unchecked-loop-condition`, `no-unlimited-resource-allocation`, `no-weak-password-recovery`, `no-xpath-injection`
16
+
17
+ - [#186](https://github.com/ofri-peretz/eslint/pull/186) [`edf208d`](https://github.com/ofri-peretz/eslint/commit/edf208d67ac2357312c97d8964fcf6a462e407eb) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - Consolidation cleanup — no rule behavior change:
18
+ - **react-features**: the README rules table now lists the 8 `componentApi`
19
+ preset rules. The README generator (`sync-readme-rules.ts`) and the
20
+ `plugin-rule-source-drift` validator now recurse into nested
21
+ `docs/rules/<category>/` subfolders, so every documented rule is advertised
22
+ consistently (previously the nested componentApi docs were silently dropped,
23
+ which an earlier `readme` exception had papered over — that exception is now
24
+ removed in favour of the real fix).
25
+ - **node-security**: remove the orphaned `no-pii-in-logs` rule source — the rule
26
+ was migrated to `eslint-plugin-secure-coding` and is no longer exported here;
27
+ the dead source was still compiling into `dist`.
28
+ - **import-next**: restore the `no-cycle` unit test after [#180](https://github.com/ofri-peretz/eslint/issues/180)'s SCC refactor
29
+ (`computeSCCsFromFile` + `findShortestCyclePath` are now bridged in the mock).
30
+
31
+ Also fixes `scripts/ilb-plugin-scope-audit.ts` to stop mis-reading config-preset
32
+ keys (`'recommended-strict': {`) as rules.
33
+
34
+ - Updated dependencies [[`736a5fe`](https://github.com/ofri-peretz/eslint/commit/736a5fed47e673f6157ea900b29fe2a54e4bc7df)]:
35
+ - @interlace/eslint-devkit@1.4.1
36
+
3
37
  ### Changed
4
38
 
5
39
  - `no-cycle` rewritten to per-import targeted DFS (replaces upfront full-graph BFS+Tarjan SCC). The `nonCyclicFiles` cache provides O(1) rejection after first visit; only files along the actual DFS path are read. Detection parity with the previous algorithm is preserved.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="https://eslint.interlace.tools" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
2
+ <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -14,6 +14,8 @@
14
14
  <a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
15
15
  </p>
16
16
 
17
+ > ⭐ If this plugin caught a real bug for you, [**star the repo**](https://github.com/ofri-peretz/eslint) — it's the signal that keeps these rules maintained.
18
+
17
19
  ## Description
18
20
 
19
21
  This plugin provides Next-generation import sorting, validation, and architectural boundaries.
@@ -24,12 +26,12 @@ This plugin provides Next-generation import sorting, validation, and architectur
24
26
 
25
27
  ## Getting Started
26
28
 
27
- - To check out the [guide](https://eslint.interlace.tools/docs/quality/plugin-import-next), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
28
- - 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-import-next), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools). 📚
29
- - [가이드](https://eslint.interlace.tools/docs/quality/plugin-import-next) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools)에서 확인하실 수 있습니다. 📚
30
- - [ガイド](https://eslint.interlace.tools/docs/quality/plugin-import-next)は [eslint.interlace.tools](https://eslint.interlace.tools)でご確認ください。 📚
31
- - Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-import-next), visita [eslint.interlace.tools](https://eslint.interlace.tools). 📚
32
- - للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-import-next)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools). 📚
29
+ - To check out the [guide](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next). 📚
30
+ - 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next). 📚
31
+ - [가이드](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next)에서 확인하실 수 있습니다. 📚
32
+ - [ガイド](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next)は [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next)でご確認ください。 📚
33
+ - Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next), visita [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next). 📚
34
+ - للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next). 📚
33
35
 
34
36
  ```bash
35
37
  npm install eslint-plugin-import-next --save-dev
@@ -81,62 +83,62 @@ See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) fo
81
83
  <!-- AUTO-GENERATED:RULES_TABLE:START - Do not edit manually -->
82
84
  | Rule | CWE | OWASP | CVSS | Description | 🧠 | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
83
85
  | :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
84
- | [consistent-type-specifier-style](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/consistent-type-specifier-style) | | | | | 🟢 | | | | | |
85
- | [default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/default) | | | | | 🟡 | 💼 | | | | |
86
- | [dynamic-import-chunkname](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/dynamic-import-chunkname) | | | | | 🟢 | | | | | |
87
- | [enforce-dependency-direction](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-dependency-direction) | | | | | 🟢 | | | | | |
88
- | [enforce-import-order](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-import-order) | | | | | 🟢 | | | | | |
89
- | [enforce-team-boundaries](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-team-boundaries) | | | | | 🟢 | | | | | |
90
- | [export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/export) | | | | | 🟢 | 💼 | | | | |
91
- | [exports-last](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/exports-last) | | | | | 🟢 | | | | | |
92
- | [extensions](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/extensions) | | | | | 🟢 | | | | | |
93
- | [first](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/first) | | | | | 🟢 | | ⚠️ | | | |
94
- | [group-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/group-exports) | | | | | 🟢 | | | | | |
95
- | [max-dependencies](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/max-dependencies) | | | | | 🟢 | | | | | |
96
- | [named](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/named) | | | | | 🟡 | 💼 | | | | |
97
- | [namespace](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/namespace) | | | | | 🟡 | | ⚠️ | | | |
98
- | [newline-after-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/newline-after-import) | | | | | 🟢 | | ⚠️ | | | |
99
- | [no-absolute-path](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-absolute-path) | | | | | 🟢 | | | | | |
100
- | [no-amd](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-amd) | | | | | 🟢 | | ⚠️ | | | |
101
- | [no-anonymous-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-anonymous-default-export) | | | | | 🟢 | | | | | |
102
- | [no-barrel-file](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-barrel-file) | | | | | 🟢 | | | | | |
103
- | [no-barrel-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-barrel-import) | | | | | 🟢 | | | | | |
104
- | [no-commonjs](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-commonjs) | | | | | 🟢 | | | | | |
105
- | [no-cross-domain-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-cross-domain-imports) | | | | | 🟢 | | | | | |
106
- | [no-cycle](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-cycle) | | A05:2021 | | | 🟢 | 💼 | | | | |
107
- | [no-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-default-export) | | | | | 🟢 | | | | | |
108
- | [no-deprecated](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-deprecated) | | A09:2021 | | | 🟢 | | | | | |
109
- | [no-duplicates](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-duplicates) | | | | | 🟢 | 💼 | | | | |
110
- | [no-dynamic-require](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-dynamic-require) | | | | | 🟢 | | | | | |
111
- | [no-empty-named-blocks](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-empty-named-blocks) | | | | | 🟢 | | ⚠️ | | | |
112
- | [no-extraneous-dependencies](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-extraneous-dependencies) | | | | | 🟢 | | ⚠️ | | | |
113
- | [no-full-package-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-full-package-import) | | | | | 🟢 | | | | | |
114
- | [no-import-module-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-import-module-exports) | | | | | 🟢 | | | | | |
115
- | [no-internal-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-internal-modules) | | | | | 🟢 | | | | | |
116
- | [no-legacy-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-legacy-imports) | | | | | 🟢 | | | | | |
117
- | [no-mutable-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-mutable-exports) | | | | | 🟢 | | | | | |
118
- | [no-named-as-default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-as-default) | | | | | 🟢 | | ⚠️ | | | |
119
- | [no-named-as-default-member](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-as-default-member) | | | | | 🟢 | | ⚠️ | | | |
120
- | [no-named-default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-default) | | | | | 🟢 | | | | | |
121
- | [no-named-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-export) | | | | | 🟢 | | | | | |
122
- | [no-namespace](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-namespace) | | | | | 🟢 | | | | | |
123
- | [no-nodejs-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-nodejs-modules) | | | | | 🟢 | | | | | |
124
- | [no-relative-packages](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-relative-packages) | | | | | 🟢 | | | | | |
125
- | [no-relative-parent-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-relative-parent-imports) | | | | | 🟢 | | | | | |
126
- | [no-restricted-paths](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-restricted-paths) | | | | | 🟢 | | | | | |
127
- | [no-self-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-self-import) | | | | | 🟢 | 💼 | | | | |
128
- | [no-unassigned-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unassigned-import) | | | | | 🟢 | | | | | |
129
- | [no-unresolved](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unresolved) | | A03:2021 | | | 🟢 | 💼 | | | | |
130
- | [no-unused-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unused-modules) | | | | | 🟢 | | | | | |
131
- | [no-useless-path-segments](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-useless-path-segments) | | | | | 🟢 | | | | | |
132
- | [order](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/order) | | | | | 🟢 | | ⚠️ | | | |
133
- | [prefer-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-default-export) | | | | | 🟢 | | | | | |
134
- | [prefer-direct-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-direct-import) | | | | | 🟢 | | | | | |
135
- | [prefer-modern-api](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-modern-api) | | | | | 🟢 | | | | | |
136
- | [prefer-node-protocol](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-node-protocol) | | | | | 🟢 | | | | | |
137
- | [prefer-tree-shakeable-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-tree-shakeable-imports) | | | | | 🟢 | | | | | |
138
- | [require-import-approval](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/require-import-approval) | | | | | 🟢 | | | | | |
139
- | [unambiguous](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/unambiguous) | | | | | 🟢 | | | | | |
86
+ | [consistent-type-specifier-style](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/consistent-type-specifier-style?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
87
+ | [default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/default?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟡 | 💼 | | | | |
88
+ | [dynamic-import-chunkname](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/dynamic-import-chunkname?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
89
+ | [enforce-dependency-direction](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-dependency-direction?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
90
+ | [enforce-import-order](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-import-order?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
91
+ | [enforce-team-boundaries](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/enforce-team-boundaries?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
92
+ | [export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/export?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | 💼 | | | | |
93
+ | [exports-last](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/exports-last?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
94
+ | [extensions](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/extensions?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
95
+ | [first](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/first?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
96
+ | [group-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/group-exports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
97
+ | [max-dependencies](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/max-dependencies?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
98
+ | [named](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/named?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟡 | 💼 | | | | |
99
+ | [namespace](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/namespace?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟡 | | ⚠️ | | | |
100
+ | [newline-after-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/newline-after-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
101
+ | [no-absolute-path](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-absolute-path?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
102
+ | [no-amd](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-amd?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
103
+ | [no-anonymous-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-anonymous-default-export?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
104
+ | [no-barrel-file](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-barrel-file?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
105
+ | [no-barrel-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-barrel-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
106
+ | [no-commonjs](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-commonjs?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
107
+ | [no-cross-domain-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-cross-domain-imports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
108
+ | [no-cycle](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-cycle?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | A05:2021 | | | 🟢 | 💼 | | | | |
109
+ | [no-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-default-export?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
110
+ | [no-deprecated](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-deprecated?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | A09:2021 | | | 🟢 | | | | | |
111
+ | [no-duplicates](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-duplicates?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | 💼 | | | | |
112
+ | [no-dynamic-require](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-dynamic-require?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
113
+ | [no-empty-named-blocks](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-empty-named-blocks?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
114
+ | [no-extraneous-dependencies](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-extraneous-dependencies?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
115
+ | [no-full-package-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-full-package-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
116
+ | [no-import-module-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-import-module-exports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
117
+ | [no-internal-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-internal-modules?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
118
+ | [no-legacy-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-legacy-imports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
119
+ | [no-mutable-exports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-mutable-exports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
120
+ | [no-named-as-default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-as-default?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
121
+ | [no-named-as-default-member](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-as-default-member?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
122
+ | [no-named-default](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-default?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
123
+ | [no-named-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-named-export?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
124
+ | [no-namespace](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-namespace?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
125
+ | [no-nodejs-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-nodejs-modules?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
126
+ | [no-relative-packages](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-relative-packages?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
127
+ | [no-relative-parent-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-relative-parent-imports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
128
+ | [no-restricted-paths](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-restricted-paths?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
129
+ | [no-self-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-self-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | 💼 | | | | |
130
+ | [no-unassigned-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unassigned-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
131
+ | [no-unresolved](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unresolved?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | A03:2021 | | | 🟢 | 💼 | | | | |
132
+ | [no-unused-modules](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-unused-modules?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
133
+ | [no-useless-path-segments](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/no-useless-path-segments?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
134
+ | [order](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/order?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | ⚠️ | | | |
135
+ | [prefer-default-export](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-default-export?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
136
+ | [prefer-direct-import](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-direct-import?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
137
+ | [prefer-modern-api](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-modern-api?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
138
+ | [prefer-node-protocol](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-node-protocol?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
139
+ | [prefer-tree-shakeable-imports](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/prefer-tree-shakeable-imports?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
140
+ | [require-import-approval](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/require-import-approval?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
141
+ | [unambiguous](https://eslint.interlace.tools/docs/quality/plugin-import-next/rules/unambiguous?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next) | | | | | 🟢 | | | | | |
140
142
  <!-- AUTO-GENERATED:RULES_TABLE:END -->
141
143
  ## 🔗 Related ESLint Plugins
142
144
 
@@ -156,10 +158,19 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
156
158
  | [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-vercel-ai-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | Vercel AI SDK security hardening. |
157
159
  | [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-import-next) | Next-gen import sorting & architecture. |
158
160
 
161
+ <!-- INTERLACE:STAR_CTA:START -->
162
+ ## ⭐ Support & follow
163
+
164
+ If this plugin caught a real bug for you, **[star the repo](https://github.com/ofri-peretz/eslint)** — stars are the signal that keeps the Interlace ESLint ecosystem maintained — and **[follow the writeups on Dev.to](https://dev.to/ofri-peretz)** for the benchmarks and security research behind these rules.
165
+
166
+ [![GitHub stars](https://img.shields.io/github/stars/ofri-peretz/eslint?style=social)](https://github.com/ofri-peretz/eslint)
167
+
168
+ <!-- INTERLACE:STAR_CTA:END -->
169
+
159
170
  ## 📄 License
160
171
 
161
172
  MIT © [Ofri Peretz](https://github.com/ofri-peretz)
162
173
 
163
174
  <p align="center">
164
- <a href="https://eslint.interlace.tools/docs/quality/plugin-import-next"><img src="https://eslint.interlace.tools/images/og-import-next.png" alt="ESLint Interlace Plugin" width="100%" /></a>
175
+ <a href="https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-import-next"><img src="https://eslint.interlace.tools/images/og-import-next.png" alt="ESLint Interlace Plugin" width="100%" /></a>
165
176
  </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-import-next",
3
- "version": "2.3.6",
3
+ "version": "2.3.7",
4
4
  "description": "Drop-in replacement for eslint-plugin-import. 100x faster no-cycle detection, AI-optimized fixes, zero config migration.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "author": "Ofri Peretz <ofriperetzdev@gmail.com>",
23
23
  "license": "MIT",
24
- "homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-import-next#readme",
24
+ "homepage": "https://eslint.interlace.tools/docs/quality/plugin-import-next?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-import-next",
25
25
  "repository": {
26
26
  "type": "git",
27
27
  "url": "https://github.com/ofri-peretz/eslint",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "tslib": "^2.3.0",
76
- "@interlace/eslint-devkit": "^1.2.1"
76
+ "@interlace/eslint-devkit": "^1.4.1"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@typescript-eslint/parser": "^8.46.2",
@@ -84,5 +84,9 @@
84
84
  "test": "vitest run",
85
85
  "test:coverage": "vitest run --coverage",
86
86
  "typecheck": "tsc -p tsconfig.lib.json --noEmit"
87
+ },
88
+ "funding": {
89
+ "type": "github",
90
+ "url": "https://github.com/ofri-peretz/eslint"
87
91
  }
88
92
  }
package/src/index.js CHANGED
@@ -146,7 +146,7 @@ exports.rules = {
146
146
  exports.plugin = {
147
147
  meta: {
148
148
  name: 'eslint-plugin-import-next',
149
- version: '2.3.6',
149
+ version: '2.3.7',
150
150
  },
151
151
  rules: exports.rules,
152
152
  };
@@ -16,7 +16,6 @@ exports.first = (0, eslint_devkit_1.createRule)({
16
16
  url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/first.md',
17
17
  description: 'Ensure all imports appear before other statements',
18
18
  },
19
- fixable: 'code',
20
19
  messages: {
21
20
  first: (0, eslint_devkit_2.formatLLMMessage)({
22
21
  icon: eslint_devkit_2.MessageIcons.ARCHITECTURE,
@@ -18,7 +18,6 @@ exports.named = (0, eslint_devkit_1.createRule)({
18
18
  url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/named.md',
19
19
  description: 'Ensure named imports correspond to a named export in the remote file',
20
20
  },
21
- fixable: 'code',
22
21
  messages: {
23
22
  named: (0, eslint_devkit_2.formatLLMMessage)({
24
23
  icon: eslint_devkit_2.MessageIcons.SECURITY, // Using SECURITY as incorrect imports can be dangerous or break build
@@ -48,7 +48,6 @@ exports.noBarrelImport = (0, eslint_devkit_1.createRule)({
48
48
  url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-barrel-import.md',
49
49
  description: 'Disallow imports from barrel files to improve build performance and tree-shaking',
50
50
  },
51
- fixable: 'code',
52
51
  hasSuggestions: true,
53
52
  messages: {
54
53
  barrelImportDetected: (0, eslint_devkit_1.formatLLMMessage)({
@@ -50,7 +50,7 @@ export interface IncrementalOptions {
50
50
  maxCacheAge?: number;
51
51
  }
52
52
  export interface Options {
53
- /** Maximum allowed import depth. Default: 5 */
53
+ /** Maximum allowed import depth. Default: Infinity (unlimited) */
54
54
  maxDepth?: number;
55
55
  /** Patterns to ignore when checking for cycles (glob patterns) */
56
56
  ignorePatterns?: string[];
@@ -27,6 +27,7 @@ exports.clearCircularDependencyCache = clearCircularDependencyCache;
27
27
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
28
28
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
29
29
  const eslint_devkit_3 = require("@interlace/eslint-devkit");
30
+ const node_fs_1 = require("node:fs");
30
31
  const eslint_devkit_4 = require("@interlace/eslint-devkit");
31
32
  /**
32
33
  * Module-level shared cache for performance optimization
@@ -177,7 +178,7 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
177
178
  // deeper than 10 hops — verified on next.js's webpack-config.ts
178
179
  // (~12 hops). The nonCyclicFiles cache + the depth-limit-truncation
179
180
  // fix in eslint-devkit's dependency-analysis make unlimited safe.
180
- maxDepth: Number.MAX_SAFE_INTEGER,
181
+ maxDepth: Infinity,
181
182
  ignorePatterns: [],
182
183
  barrelExports: ['index.ts', 'index.tsx', 'index.js', 'index.jsx'],
183
184
  reportAllCycles: true,
@@ -189,7 +190,7 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
189
190
  ],
190
191
  create(context) {
191
192
  const options = context.options[0] || {};
192
- const maxDepth = options.maxDepth ?? Number.MAX_SAFE_INTEGER;
193
+ const maxDepth = options.maxDepth ?? Infinity;
193
194
  const ignorePatterns = options.ignorePatterns ?? [
194
195
  '**/*.test.ts',
195
196
  '**/*.test.tsx',
@@ -205,12 +206,23 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
205
206
  'index.js',
206
207
  'index.jsx',
207
208
  ];
208
- const reportAllCycles = options.reportAllCycles ?? true;
209
+ const _reportAllCycles = options.reportAllCycles ?? true;
209
210
  const fixStrategy = options.fixStrategy ?? 'auto';
210
211
  const moduleNamingConvention = options.moduleNamingConvention ?? 'semantic';
211
212
  const coreModuleSuffix = options.coreModuleSuffix ?? 'core';
212
213
  const extendedModuleSuffix = options.extendedModuleSuffix ?? 'extended';
213
214
  const filename = context.filename;
215
+ // Normalize once per file — on macOS /tmp is a symlink to /private/tmp.
216
+ // ESLint provides the symlink path; the resolver returns real paths.
217
+ // Without normalization, path comparisons always fail on macOS.
218
+ const normalizedFilename = (() => {
219
+ try {
220
+ return (0, node_fs_1.realpathSync)(filename);
221
+ }
222
+ catch {
223
+ return filename;
224
+ }
225
+ })();
214
226
  const workspaceRoot = context.cwd;
215
227
  // Get resolver settings from ESLint settings (compatible with eslint-plugin-import)
216
228
  // eslint-plugin-import uses settings['import/resolver']; allow either the
@@ -223,6 +235,16 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
223
235
  if ((0, eslint_devkit_3.shouldIgnoreFile)(filename, ignorePatterns, sharedCache.compiledPatterns)) {
224
236
  return {};
225
237
  }
238
+ // Do NOT clear sharedCache.nonCyclicFiles — it is now populated directly
239
+ // from SCC results (singleton SCCs → provably non-cyclic) and persists
240
+ // across all files for the entire lint run. Clearing it per-file would
241
+ // destroy the O(1) fast path for known non-cyclic files.
242
+ sharedCache.pendingCycleReports.clear();
243
+ // sharedCache.sccComputed, sccIndex, sccs, nonCyclicFiles — NOT reset.
244
+ // Per-file dedup set — keyed by import node to ensure each import
245
+ // statement produces at most one error, even when multiple cycle paths
246
+ // exist through the same import.
247
+ const reportedImportNodes = new Set();
226
248
  /**
227
249
  * Select fix strategy based on cycle characteristics
228
250
  */
@@ -345,11 +367,27 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
345
367
  }
346
368
  }
347
369
  return {
370
+ // eslint-disable-next-line @typescript-eslint/naming-convention
371
+ 'Program'() {
372
+ // Program() is now a no-op for SCC — fully lazy SCC extension.
373
+ // SCC is computed on-demand in ImportDeclaration when source or target
374
+ // is not yet covered. This matches eslint-plugin-import's lazy
375
+ // ExportMap approach: no upfront file reads, defer to actual imports.
376
+ // The global SCC persists across files so each file's SCC is computed
377
+ // at most once per connected component across the whole lint run.
378
+ },
348
379
  ImportDeclaration(node) {
380
+ // Type-only imports are erased at compile time — no runtime cycle risk.
381
+ if (node.importKind === 'type')
382
+ return;
383
+ // Each import node should produce at most ONE error regardless of how
384
+ // many distinct cycle paths pass through this import.
385
+ if (reportedImportNodes.has(node))
386
+ return;
349
387
  const importSource = node.source.value;
350
- // Resolve the import target to an absolute path
388
+ // Resolve using normalizedFilename so resolver output paths match.
351
389
  const resolved = (0, eslint_devkit_4.resolveImportPath)(importSource, {
352
- fromFile: filename,
390
+ fromFile: normalizedFilename,
353
391
  workspaceRoot,
354
392
  barrelExports,
355
393
  cache: sharedCache,
@@ -358,80 +396,137 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
358
396
  if (!resolved)
359
397
  return;
360
398
  // =====================================================================
361
- // PER-IMPORT CYCLE DETECTION (Phase 3 optimization)
399
+ // FAST PATH 1: nonCyclicFiles O(1) lookup
362
400
  //
363
- // Instead of computing all cycles upfront in Program() via full-graph
364
- // BFS + Tarjan SCC (which reads ALL ~5K reachable files), we check
365
- // each import individually with a targeted DFS.
401
+ // Files in singleton SCCs are added to nonCyclicFiles during SCC
402
+ // computation. This check is slightly faster than the sccIndex lookup
403
+ // because it short-circuits before any SCC arithmetic.
404
+ // =====================================================================
405
+ if (sharedCache.nonCyclicFiles.has(resolved))
406
+ return;
407
+ // =====================================================================
408
+ // SCC PRE-CHECK (O(1)) — the proven eslint-plugin-import approach
366
409
  //
367
- // Performance advantage:
368
- // - Only reads files along the actual DFS path (not all reachable)
369
- // - nonCyclicFiles cache provides O(1) rejection after first visit
370
- // - getFileImports cache reuses parsed results across files
410
+ // If source and target are in DIFFERENT SCCs, no cycle can exist
411
+ // between them skip immediately without any graph traversal.
412
+ // Eliminates ~99% of imports without reading a single file.
413
+ //
414
+ // If the target file isn't in the SCC yet, extend the SCC from it
415
+ // lazily so we can make the correct determination.
371
416
  // =====================================================================
372
- // Fast path: if the target file is known to be non-cyclic, skip
373
- if (sharedCache.nonCyclicFiles.has(resolved)) {
417
+ // Fully lazy SCC: extend on-demand when source or target not yet covered.
418
+ // Extending from the TARGET is preferred — if target → source (cycle),
419
+ // extending from target will discover source and assign both to the same SCC.
420
+ // Only extend from source as a fallback when target extension doesn't cover it.
421
+ let tgtSCC = sharedCache.sccIndex.get(resolved);
422
+ if (tgtSCC === undefined) {
423
+ (0, eslint_devkit_4.computeSCCsFromFile)(resolved, {
424
+ maxDepth: Infinity,
425
+ workspaceRoot,
426
+ barrelExports,
427
+ cache: sharedCache,
428
+ resolverSettings,
429
+ });
430
+ tgtSCC = sharedCache.sccIndex.get(resolved);
431
+ }
432
+ let srcSCC = sharedCache.sccIndex.get(normalizedFilename);
433
+ if (srcSCC === undefined) {
434
+ // Source still not covered after extending from target — source is in a
435
+ // different connected component. Extend from source to cover it.
436
+ (0, eslint_devkit_4.computeSCCsFromFile)(normalizedFilename, {
437
+ maxDepth: Infinity,
438
+ workspaceRoot,
439
+ barrelExports,
440
+ cache: sharedCache,
441
+ resolverSettings,
442
+ });
443
+ srcSCC = sharedCache.sccIndex.get(normalizedFilename);
444
+ }
445
+ if (srcSCC === undefined || tgtSCC === undefined || srcSCC !== tgtSCC) {
374
446
  return;
375
447
  }
376
- // Check if following this import leads back to the current file
377
- const cycles = (0, eslint_devkit_4.detectCycleFromImport)(filename, resolved, {
448
+ // =====================================================================
449
+ // BFS PATH-FINDING only runs when SCC guarantees a cycle exists.
450
+ //
451
+ // findShortestCyclePath does BFS from `resolved` back to
452
+ // `normalizedFilename`, only traversing files within the same SCC.
453
+ // This is bounded by the SCC size, not the entire codebase.
454
+ // =====================================================================
455
+ const cyclePath = (0, eslint_devkit_4.findShortestCyclePath)(normalizedFilename, resolved, {
378
456
  maxDepth,
379
- reportAllCycles,
380
457
  workspaceRoot,
381
458
  barrelExports,
382
459
  cache: sharedCache,
383
460
  resolverSettings,
384
461
  });
385
- if (cycles.length === 0)
462
+ if (!cyclePath)
386
463
  return;
387
- for (const cycle of cycles) {
388
- // Extract the minimal cycle (the actual loop)
389
- const minimalCycle = (0, eslint_devkit_4.getMinimalCycle)(cycle);
390
- // Only report if current file is part of the minimal cycle
391
- if (!minimalCycle.includes(filename)) {
392
- continue;
393
- }
394
- const cycleStart = minimalCycle.indexOf(filename);
395
- if (cycleStart === -1)
396
- continue;
397
- const relevantCycle = [
398
- ...minimalCycle.slice(cycleStart),
399
- ...minimalCycle.slice(0, cycleStart),
400
- ];
401
- // Dedupe by (file, cycle) instead of cycle alone. Without the
402
- // file in the key, the cycle [A, B] reports only on whichever
403
- // of A or B is linted first; the other gets silently skipped.
404
- // Per-file keying matches oxlint's behavior — every file in a
405
- // cycle gets its own diagnostic. The user can navigate from any
406
- // entry point in the cycle and see the report there.
407
- const cycleHash = `${filename}::${(0, eslint_devkit_4.getCycleHash)(relevantCycle)}`;
408
- // Skip only if THIS file already reported THIS cycle (e.g. the
409
- // same import statement appearing twice in the same file).
410
- // (Earlier we experimented with a `pendingCycleReports` fan-out
411
- // that emits on every cycle member regardless of which one's
412
- // DFS discovered it — closes the with-router.tsx presentational
413
- // gap but caused a 10× slowdown and 23× finding-count explosion
414
- // because each cycle member iterates every other member of every
415
- // cycle it touches. The cycle IS still reported on its other
416
- // end; the one-finding loss isn't worth the perf hit.)
417
- if (sharedCache.reportedCycles.has(cycleHash)) {
418
- continue;
419
- }
420
- sharedCache.reportedCycles.add(cycleHash);
421
- // Select the appropriate fix strategy
422
- const strategy = selectFixStrategy(relevantCycle, fixStrategy);
423
- // Generate the appropriate message based on strategy
424
- const sourceImport = {
425
- path: resolved,
426
- source: importSource,
427
- };
428
- const { messageId, data } = generateMessageData(relevantCycle, strategy, sourceImport);
429
- context.report({
430
- node,
431
- messageId,
432
- data,
464
+ // Deduplicate: this import node produces exactly one error.
465
+ reportedImportNodes.add(node);
466
+ // Build the relevant cycle starting from normalizedFilename.
467
+ const cycleStart = cyclePath.indexOf(normalizedFilename);
468
+ const relevantCycle = cycleStart >= 0
469
+ ? [...cyclePath.slice(cycleStart), ...cyclePath.slice(0, cycleStart)]
470
+ : cyclePath;
471
+ const strategy = selectFixStrategy(relevantCycle, fixStrategy);
472
+ const sourceImport = { path: resolved, source: importSource };
473
+ const { messageId, data } = generateMessageData(relevantCycle, strategy, sourceImport);
474
+ context.report({ node, messageId, data });
475
+ },
476
+ // Re-export statements (`export { X } from '...'`) create import edges
477
+ // just like `import { X } from '...'`. Without this handler, cycles
478
+ // that close through a re-export are invisible to the rule.
479
+ // eslint-disable-next-line @typescript-eslint/naming-convention
480
+ ExportNamedDeclaration(node) {
481
+ if (!node.source)
482
+ return; // no 'from' clause not a re-export
483
+ // Reuse the same logic as ImportDeclaration
484
+ if (reportedImportNodes.has(node))
485
+ return;
486
+ const importSource = node.source.value;
487
+ const resolved = (0, eslint_devkit_4.resolveImportPath)(importSource, {
488
+ fromFile: normalizedFilename,
489
+ workspaceRoot,
490
+ barrelExports,
491
+ cache: sharedCache,
492
+ resolverSettings,
493
+ });
494
+ if (!resolved)
495
+ return;
496
+ if (sharedCache.nonCyclicFiles.has(resolved))
497
+ return;
498
+ let tgtSCC = sharedCache.sccIndex.get(resolved);
499
+ if (tgtSCC === undefined) {
500
+ (0, eslint_devkit_4.computeSCCsFromFile)(resolved, {
501
+ maxDepth: Infinity,
502
+ workspaceRoot,
503
+ barrelExports,
504
+ cache: sharedCache,
505
+ resolverSettings,
433
506
  });
507
+ tgtSCC = sharedCache.sccIndex.get(resolved);
434
508
  }
509
+ const srcSCC = sharedCache.sccIndex.get(normalizedFilename);
510
+ if (srcSCC === undefined || tgtSCC === undefined || srcSCC !== tgtSCC)
511
+ return;
512
+ const cyclePath = (0, eslint_devkit_4.findShortestCyclePath)(normalizedFilename, resolved, {
513
+ maxDepth,
514
+ workspaceRoot,
515
+ barrelExports,
516
+ cache: sharedCache,
517
+ resolverSettings,
518
+ });
519
+ if (!cyclePath)
520
+ return;
521
+ reportedImportNodes.add(node);
522
+ const cycleStart = cyclePath.indexOf(normalizedFilename);
523
+ const relevantCycle = cycleStart >= 0
524
+ ? [...cyclePath.slice(cycleStart), ...cyclePath.slice(0, cycleStart)]
525
+ : cyclePath;
526
+ const strategy = selectFixStrategy(relevantCycle, fixStrategy);
527
+ const sourceImport = { path: resolved, source: importSource };
528
+ const { messageId, data } = generateMessageData(relevantCycle, strategy, sourceImport);
529
+ context.report({ node, messageId, data });
435
530
  },
436
531
  };
437
532
  },
@@ -17,7 +17,6 @@ exports.noImportModuleExports = (0, eslint_devkit_1.createRule)({
17
17
  cwe: 'CWE-1078',
18
18
  cvss: 7.5,
19
19
  },
20
- fixable: 'code',
21
20
  messages: {
22
21
  importModuleExports: (0, eslint_devkit_1.formatLLMMessage)({
23
22
  icon: eslint_devkit_1.MessageIcons.ARCHITECTURE,
@@ -17,7 +17,6 @@ exports.noNamespace = (0, eslint_devkit_1.createRule)({
17
17
  cwe: 'CWE-1078',
18
18
  cvss: 2.5,
19
19
  },
20
- fixable: 'code',
21
20
  messages: {
22
21
  noNamespace: (0, eslint_devkit_1.formatLLMMessage)({
23
22
  icon: eslint_devkit_1.MessageIcons.ARCHITECTURE,