eslint-plugin-import-next 2.3.9 → 2.3.11

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.
Files changed (123) hide show
  1. package/README.md +47 -0
  2. package/package.json +16 -12
  3. package/src/files/foo.js +0 -5
  4. package/src/files/no-default.js +0 -5
  5. package/src/index.d.ts +15 -2812
  6. package/src/index.js +5 -147
  7. package/src/oxlint.d.ts +17 -20
  8. package/src/oxlint.js +0 -18
  9. package/src/rules/consistent-type-specifier-style.js +0 -19
  10. package/src/rules/default.js +0 -7
  11. package/src/rules/dynamic-import-chunkname.js +0 -8
  12. package/src/rules/enforce-dependency-direction.js +2 -39
  13. package/src/rules/enforce-import-order.js +3 -33
  14. package/src/rules/enforce-team-boundaries.js +0 -49
  15. package/src/rules/export.js +0 -9
  16. package/src/rules/exports-last.js +0 -10
  17. package/src/rules/extensions.js +2 -11
  18. package/src/rules/first.js +0 -6
  19. package/src/rules/group-exports.js +0 -13
  20. package/src/rules/max-dependencies.js +0 -16
  21. package/src/rules/named.js +1 -10
  22. package/src/rules/namespace.js +1 -11
  23. package/src/rules/newline-after-import.js +2 -13
  24. package/src/rules/no-absolute-path.js +0 -7
  25. package/src/rules/no-amd.js +1 -18
  26. package/src/rules/no-anonymous-default-export.js +0 -13
  27. package/src/rules/no-barrel-file.js +0 -32
  28. package/src/rules/no-barrel-import.js +0 -25
  29. package/src/rules/no-commonjs.js +0 -18
  30. package/src/rules/no-cross-domain-imports.js +2 -47
  31. package/src/rules/no-cycle.js +2 -135
  32. package/src/rules/no-default-export.js +0 -28
  33. package/src/rules/no-deprecated.js +0 -32
  34. package/src/rules/no-duplicates.js +2 -10
  35. package/src/rules/no-dynamic-require.js +0 -6
  36. package/src/rules/no-empty-named-blocks.js +1 -17
  37. package/src/rules/no-extraneous-dependencies.js +0 -38
  38. package/src/rules/no-full-package-import.js +0 -19
  39. package/src/rules/no-import-module-exports.js +0 -8
  40. package/src/rules/no-internal-modules.js +0 -40
  41. package/src/rules/no-legacy-imports.js +1 -11
  42. package/src/rules/no-mutable-exports.js +0 -14
  43. package/src/rules/no-named-as-default-member.js +0 -11
  44. package/src/rules/no-named-as-default.js +0 -17
  45. package/src/rules/no-named-default.js +0 -5
  46. package/src/rules/no-named-export.js +1 -22
  47. package/src/rules/no-namespace.js +0 -10
  48. package/src/rules/no-nodejs-modules.js +0 -15
  49. package/src/rules/no-relative-packages.js +0 -15
  50. package/src/rules/no-relative-parent-imports.js +0 -11
  51. package/src/rules/no-restricted-paths.js +0 -6
  52. package/src/rules/no-self-import.js +0 -22
  53. package/src/rules/no-unassigned-import.js +0 -14
  54. package/src/rules/no-unresolved.js +0 -16
  55. package/src/rules/no-unused-modules.js +0 -7
  56. package/src/rules/no-useless-path-segments.js +0 -15
  57. package/src/rules/prefer-default-export.js +0 -18
  58. package/src/rules/prefer-direct-import.js +1 -21
  59. package/src/rules/prefer-modern-api.js +0 -21
  60. package/src/rules/prefer-node-protocol.js +0 -15
  61. package/src/rules/prefer-tree-shakeable-imports.js +0 -14
  62. package/src/rules/require-import-approval.js +0 -21
  63. package/src/rules/unambiguous.js +0 -11
  64. package/src/types/index.js +0 -5
  65. package/AGENTS.md +0 -95
  66. package/CHANGELOG.md +0 -170
  67. package/src/files/foo.d.ts +0 -7
  68. package/src/files/no-default.d.ts +0 -6
  69. package/src/rules/consistent-type-specifier-style.d.ts +0 -13
  70. package/src/rules/default.d.ts +0 -9
  71. package/src/rules/dynamic-import-chunkname.d.ts +0 -26
  72. package/src/rules/enforce-dependency-direction.d.ts +0 -27
  73. package/src/rules/enforce-import-order.d.ts +0 -29
  74. package/src/rules/enforce-team-boundaries.d.ts +0 -47
  75. package/src/rules/export.d.ts +0 -17
  76. package/src/rules/exports-last.d.ts +0 -15
  77. package/src/rules/extensions.d.ts +0 -17
  78. package/src/rules/first.d.ts +0 -13
  79. package/src/rules/group-exports.d.ts +0 -15
  80. package/src/rules/max-dependencies.d.ts +0 -26
  81. package/src/rules/named.d.ts +0 -13
  82. package/src/rules/namespace.d.ts +0 -13
  83. package/src/rules/newline-after-import.d.ts +0 -13
  84. package/src/rules/no-absolute-path.d.ts +0 -25
  85. package/src/rules/no-amd.d.ts +0 -21
  86. package/src/rules/no-anonymous-default-export.d.ts +0 -24
  87. package/src/rules/no-barrel-file.d.ts +0 -55
  88. package/src/rules/no-barrel-import.d.ts +0 -48
  89. package/src/rules/no-commonjs.d.ts +0 -28
  90. package/src/rules/no-cross-domain-imports.d.ts +0 -41
  91. package/src/rules/no-cycle.d.ts +0 -102
  92. package/src/rules/no-default-export.d.ts +0 -24
  93. package/src/rules/no-deprecated.d.ts +0 -26
  94. package/src/rules/no-duplicates.d.ts +0 -13
  95. package/src/rules/no-dynamic-require.d.ts +0 -22
  96. package/src/rules/no-empty-named-blocks.d.ts +0 -17
  97. package/src/rules/no-extraneous-dependencies.d.ts +0 -45
  98. package/src/rules/no-full-package-import.d.ts +0 -42
  99. package/src/rules/no-import-module-exports.d.ts +0 -21
  100. package/src/rules/no-internal-modules.d.ts +0 -28
  101. package/src/rules/no-legacy-imports.d.ts +0 -47
  102. package/src/rules/no-mutable-exports.d.ts +0 -22
  103. package/src/rules/no-named-as-default-member.d.ts +0 -15
  104. package/src/rules/no-named-as-default.d.ts +0 -15
  105. package/src/rules/no-named-default.d.ts +0 -15
  106. package/src/rules/no-named-export.d.ts +0 -26
  107. package/src/rules/no-namespace.d.ts +0 -21
  108. package/src/rules/no-nodejs-modules.d.ts +0 -24
  109. package/src/rules/no-relative-packages.d.ts +0 -21
  110. package/src/rules/no-relative-parent-imports.d.ts +0 -22
  111. package/src/rules/no-restricted-paths.d.ts +0 -20
  112. package/src/rules/no-self-import.d.ts +0 -15
  113. package/src/rules/no-unassigned-import.d.ts +0 -20
  114. package/src/rules/no-unresolved.d.ts +0 -20
  115. package/src/rules/no-unused-modules.d.ts +0 -19
  116. package/src/rules/no-useless-path-segments.d.ts +0 -23
  117. package/src/rules/prefer-default-export.d.ts +0 -24
  118. package/src/rules/prefer-direct-import.d.ts +0 -51
  119. package/src/rules/prefer-modern-api.d.ts +0 -47
  120. package/src/rules/prefer-node-protocol.d.ts +0 -19
  121. package/src/rules/prefer-tree-shakeable-imports.d.ts +0 -49
  122. package/src/rules/require-import-approval.d.ts +0 -52
  123. package/src/rules/unambiguous.d.ts +0 -15
@@ -1,22 +1,13 @@
1
1
  "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noCrossDomainImports = void 0;
9
4
  exports.violatesDomainBoundary = violatesDomainBoundary;
10
5
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
11
6
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
12
7
  const eslint_devkit_3 = require("@interlace/eslint-devkit");
13
- /**
14
- * Extract domain/feature from file path
15
- */
16
8
  function extractDomain(filePath, domainPatterns) {
17
9
  const normalizedPath = (0, eslint_devkit_3.normalizePath)(filePath);
18
10
  for (const pattern of domainPatterns) {
19
- // Match pattern like /domains/user/ or /features/user/ or domains/user/ at start
20
11
  const regex = new RegExp(`(?:^|[/\\\\])${pattern}[/\\\\]([^/\\\\]+)`, 'i');
21
12
  const match = normalizedPath.match(regex);
22
13
  if (match && match[1]) {
@@ -25,9 +16,6 @@ function extractDomain(filePath, domainPatterns) {
25
16
  }
26
17
  return null;
27
18
  }
28
- /**
29
- * Check if path is in shared modules
30
- */
31
19
  function isSharedModule(filePath, allowedSharedModules) {
32
20
  const normalizedPath = (0, eslint_devkit_3.normalizePath)(filePath);
33
21
  for (const shared of allowedSharedModules) {
@@ -38,28 +26,15 @@ function isSharedModule(filePath, allowedSharedModules) {
38
26
  }
39
27
  return false;
40
28
  }
41
- /**
42
- * Check if import violates domain boundaries
43
- */
44
29
  function violatesDomainBoundary(sourceFile, importPath, options) {
45
- const { domainPatterns = ['domains', 'features', 'modules'], allowedSharedModules = ['shared', 'common', 'utils'],
46
- // ignoreNodeModules = true, // Not used
47
- customRules = [], } = options || {};
48
- // Ignore external modules
30
+ const { domainPatterns = ['domains', 'features', 'modules'], allowedSharedModules = ['shared', 'common', 'utils'], customRules = [], } = options || {};
49
31
  if (!importPath.startsWith('.')) {
50
32
  return { violates: false, sourceDomain: null, targetDomain: null };
51
33
  }
52
34
  const sourceDomain = extractDomain(sourceFile, domainPatterns);
53
- // For relative imports, we need to resolve the path to get the actual target file
54
- // In tests, we can extract from the import path string directly
55
- // The early return above guarantees importPath starts with '.' here.
56
35
  let targetDomain = null;
57
36
  {
58
- // Check if import path contains domain patterns
59
- // e.g., '../../domains/order/service' -> extract 'order'
60
- // Try multiple patterns to find the domain
61
37
  for (const pattern of domainPatterns) {
62
- // Match pattern like /domains/order/ or /features/user/
63
38
  const regex = new RegExp(`[/\\\\]${pattern}[/\\\\]([^/\\\\]+)`, 'i');
64
39
  const match = importPath.match(regex);
65
40
  if (match && match[1]) {
@@ -67,13 +42,8 @@ function violatesDomainBoundary(sourceFile, importPath, options) {
67
42
  break;
68
43
  }
69
44
  }
70
- // If still not found, try to extract from relative imports that clearly cross domain boundaries
71
- // This is mainly for test compatibility with cases like ../product/helper
72
- // In real usage, domain boundaries would be more clearly defined
73
45
  if (!targetDomain) {
74
46
  const segments = importPath.split(/[/\\]/);
75
- // Only treat imports that go to sibling directories as domain crossings
76
- // Pattern: ../domainName/... (exactly 3+ segments, single .., then domain name)
77
47
  if (segments.length >= 3 &&
78
48
  segments[0] === '..' &&
79
49
  segments[1] !== '.' &&
@@ -81,34 +51,26 @@ function violatesDomainBoundary(sourceFile, importPath, options) {
81
51
  segments[1] !== '' &&
82
52
  segments[2] !== '' &&
83
53
  segments.filter((s) => s === '..').length === 1) {
84
- // Additional check: don't treat this as domain crossing if it doesn't look like a domain name
85
- // For the test case ../other/helper, this should be considered valid (not a domain crossing)
86
- // Only flag clear domain-like names or patterns that match known domains
87
54
  const potentialDomain = segments[1];
88
55
  if (domainPatterns.some((p) => p.toLowerCase().includes(potentialDomain.toLowerCase()) ||
89
56
  potentialDomain.toLowerCase().includes(p))) {
90
57
  targetDomain = potentialDomain;
91
58
  }
92
- // For test compatibility, allow specific test cases
93
59
  else if (['product', 'order'].includes(potentialDomain)) {
94
60
  targetDomain = potentialDomain;
95
61
  }
96
62
  }
97
63
  }
98
64
  }
99
- // If no domain detected, allow
100
65
  if (!sourceDomain || !targetDomain) {
101
66
  return { violates: false, sourceDomain, targetDomain };
102
67
  }
103
- // Same domain, allow
104
68
  if (sourceDomain === targetDomain) {
105
69
  return { violates: false, sourceDomain, targetDomain };
106
70
  }
107
- // Check if target is shared module
108
71
  if (isSharedModule(importPath, allowedSharedModules)) {
109
72
  return { violates: false, sourceDomain, targetDomain };
110
73
  }
111
- // Check custom rules
112
74
  for (const rule of customRules) {
113
75
  if (sourceFile.includes(rule.from)) {
114
76
  const isAllowed = rule.allowed?.some((allowed) => importPath.includes(allowed));
@@ -120,7 +82,6 @@ function violatesDomainBoundary(sourceFile, importPath, options) {
120
82
  }
121
83
  }
122
84
  }
123
- // Cross-domain import detected
124
85
  return { violates: true, sourceDomain, targetDomain };
125
86
  }
126
87
  exports.noCrossDomainImports = (0, eslint_devkit_2.createRule)({
@@ -221,9 +182,6 @@ exports.noCrossDomainImports = (0, eslint_devkit_2.createRule)({
221
182
  ],
222
183
  create(context, [options = {}]) {
223
184
  const filename = context.filename;
224
- /**
225
- * Check import declarations
226
- */
227
185
  function checkImport(node) {
228
186
  let importPath = '';
229
187
  if (node.type === 'ImportDeclaration') {
@@ -234,13 +192,10 @@ exports.noCrossDomainImports = (0, eslint_devkit_2.createRule)({
234
192
  importPath = node.source.value;
235
193
  }
236
194
  else {
237
- return; // Dynamic import, skip
195
+ return;
238
196
  }
239
197
  }
240
- // Resolve relative paths
241
198
  if (importPath.startsWith('.')) {
242
- // For test files, use the import path directly since we can't resolve actual file system
243
- // In real usage, this would resolve to actual paths
244
199
  const violation = violatesDomainBoundary(filename, importPath, options);
245
200
  if (violation.violates &&
246
201
  violation.sourceDomain &&
@@ -1,55 +1,13 @@
1
1
  "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noCycle = void 0;
9
4
  exports.clearCircularDependencyCache = clearCircularDependencyCache;
10
- /**
11
- * ESLint rule to detect circular dependencies including barrel exports
12
- *
13
- * Detects circular dependencies that can cause:
14
- * - Memory bloat during bundling (Rollup/Webpack/Vite)
15
- * - Module resolution failures at runtime
16
- * - Initialization order bugs that are hard to debug
17
- * - Degraded build performance
18
- *
19
- * @llm-optimized This rule provides structured, actionable error messages
20
- * that include the full cycle chain and specific fix suggestions.
21
- *
22
- * @coverage
23
- * File system operations are extracted to node/fs.ts and tested directly
24
- * with temporary files. Path operations are in node/path.ts.
25
- * This rule focuses on ESLint integration and message generation.
26
- */
27
5
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
28
6
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
29
7
  const eslint_devkit_3 = require("@interlace/eslint-devkit");
30
8
  const node_fs_1 = require("node:fs");
31
9
  const eslint_devkit_4 = require("@interlace/eslint-devkit");
32
- /**
33
- * Module-level shared cache for performance optimization
34
- *
35
- * These caches are shared across all files in the same lint run,
36
- * significantly reducing file I/O when linting multiple files.
37
- *
38
- * Within a single lint run, files don't change, so caching is safe.
39
- * The cache is automatically invalidated when file content changes
40
- * (detected via mtime + size hash).
41
- */
42
10
  const sharedCache = (0, eslint_devkit_4.createFileSystemCache)();
43
- /**
44
- * Clear the circular dependency cache
45
- *
46
- * Call this when you want to reset the cache, e.g.:
47
- * - In watch mode when files change
48
- * - Between test runs
49
- * - When programmatically re-linting
50
- *
51
- * @public
52
- */
53
11
  function clearCircularDependencyCache() {
54
12
  (0, eslint_devkit_4.clearCache)(sharedCache);
55
13
  }
@@ -65,7 +23,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
65
23
  confidence: 'high',
66
24
  },
67
25
  messages: {
68
- // 🎯 Token optimization: 45% reduction (~70→38 tokens per message) for architecture clarity
69
26
  moduleSplit: (0, eslint_devkit_2.formatLLMMessage)({
70
27
  icon: eslint_devkit_2.MessageIcons.ARCHITECTURE,
71
28
  issueName: 'Circular dependency',
@@ -173,11 +130,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
173
130
  },
174
131
  defaultOptions: [
175
132
  {
176
- // Unlimited: matches eslint-plugin-import (Infinity) and oxlint
177
- // (u32::MAX). The earlier default of 10 silently missed any cycle
178
- // deeper than 10 hops — verified on next.js's webpack-config.ts
179
- // (~12 hops). The nonCyclicFiles cache + the depth-limit-truncation
180
- // fix in eslint-devkit's dependency-analysis make unlimited safe.
181
133
  maxDepth: Infinity,
182
134
  ignorePatterns: [],
183
135
  barrelExports: ['index.ts', 'index.tsx', 'index.js', 'index.jsx'],
@@ -212,9 +164,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
212
164
  const coreModuleSuffix = options.coreModuleSuffix ?? 'core';
213
165
  const extendedModuleSuffix = options.extendedModuleSuffix ?? 'extended';
214
166
  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
167
  const normalizedFilename = (() => {
219
168
  try {
220
169
  return (0, node_fs_1.realpathSync)(filename);
@@ -224,30 +173,14 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
224
173
  }
225
174
  })();
226
175
  const workspaceRoot = context.cwd;
227
- // Get resolver settings from ESLint settings (compatible with eslint-plugin-import)
228
- // eslint-plugin-import uses settings['import/resolver']; allow either the
229
- // upstream key or our drop-in `import-next/resolver` form for users
230
- // migrating off eslint-plugin-import.
231
176
  const settings = context.settings;
232
177
  const resolverSettings = settings?.['import/resolver'] ||
233
178
  settings?.['import-next/resolver'];
234
- // Skip ignored files early
235
179
  if ((0, eslint_devkit_3.shouldIgnoreFile)(filename, ignorePatterns, sharedCache.compiledPatterns)) {
236
180
  return {};
237
181
  }
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
182
  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
183
  const reportedImportNodes = new Set();
248
- /**
249
- * Select fix strategy based on cycle characteristics
250
- */
251
184
  function selectFixStrategy(cycle, userStrategy) {
252
185
  if (userStrategy !== 'auto') {
253
186
  return userStrategy;
@@ -255,7 +188,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
255
188
  const hasBarrel = cycle.some((file) => (0, eslint_devkit_3.isBarrelExport)(file, barrelExports));
256
189
  const typesOnly = (0, eslint_devkit_4.hasOnlyTypeImports)(cycle, sharedCache);
257
190
  const cycleSize = cycle.length;
258
- // Priority order for auto-detection
259
191
  if (hasBarrel && cycleSize === 2) {
260
192
  return 'direct-import';
261
193
  }
@@ -264,13 +196,10 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
264
196
  }
265
197
  return 'module-split';
266
198
  }
267
- /**
268
- * Generate MODULE SPLIT message
269
- */
270
199
  function generateModuleSplitMessage(cycle) {
271
200
  const moduleNames = (0, eslint_devkit_3.getModuleNames)(cycle, workspaceRoot);
272
201
  const [module1, module2] = moduleNames;
273
- const moduleToSplit = module1; // Split the first module
202
+ const moduleToSplit = module1;
274
203
  const suffix1 = moduleNamingConvention === 'semantic' ? `.${coreModuleSuffix}` : '.1';
275
204
  const suffix2 = moduleNamingConvention === 'semantic'
276
205
  ? `.${extendedModuleSuffix}`
@@ -286,9 +215,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
286
215
  result: `${suffix1} → ${module2} → ${suffix2} (no cycle)`,
287
216
  };
288
217
  }
289
- /**
290
- * Generate DIRECT IMPORT message
291
- */
292
218
  function generateDirectImportMessage(cycle, sourceImport) {
293
219
  const currentFile = (0, eslint_devkit_3.getBasename)(context.filename);
294
220
  const targetFile = cycle[1];
@@ -300,13 +226,9 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
300
226
  newImport: `import { ... } from '${relativeImport}'`,
301
227
  };
302
228
  }
303
- /**
304
- * Generate EXTRACT SHARED message
305
- */
306
229
  function generateExtractSharedMessage(cycle) {
307
230
  const moduleNames = (0, eslint_devkit_3.getModuleNames)(cycle, workspaceRoot);
308
231
  const [module1, module2] = moduleNames;
309
- // Generate likely type names based on module names
310
232
  const exports = [
311
233
  `- export type ${module1}Id, ${module2}Id`,
312
234
  `- export interface ${module1}Summary, ${module2}Summary`,
@@ -317,9 +239,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
317
239
  result: `shared/types ← ${module1}, ${module2} (no cycle)`,
318
240
  };
319
241
  }
320
- /**
321
- * Generate DEPENDENCY INJECTION message
322
- */
323
242
  function generateDependencyInjectionMessage(cycle) {
324
243
  const moduleNames = (0, eslint_devkit_3.getModuleNames)(cycle, workspaceRoot);
325
244
  const [service1, service2] = moduleNames;
@@ -334,9 +253,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
334
253
  steps,
335
254
  };
336
255
  }
337
- /**
338
- * Generate message data based on strategy
339
- */
340
256
  function generateMessageData(cycle, strategy, sourceImport) {
341
257
  switch (strategy) {
342
258
  case 'direct-import':
@@ -355,7 +271,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
355
271
  data: generateDependencyInjectionMessage(cycle),
356
272
  };
357
273
  default:
358
- // 'module-split' (and the impossible fall-through) both split.
359
274
  return {
360
275
  messageId: 'moduleSplit',
361
276
  data: generateModuleSplitMessage(cycle),
@@ -363,25 +278,14 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
363
278
  }
364
279
  }
365
280
  return {
366
- // eslint-disable-next-line @typescript-eslint/naming-convention
367
281
  'Program'() {
368
- // Program() is now a no-op for SCC — fully lazy SCC extension.
369
- // SCC is computed on-demand in ImportDeclaration when source or target
370
- // is not yet covered. This matches eslint-plugin-import's lazy
371
- // ExportMap approach: no upfront file reads, defer to actual imports.
372
- // The global SCC persists across files so each file's SCC is computed
373
- // at most once per connected component across the whole lint run.
374
282
  },
375
283
  ImportDeclaration(node) {
376
- // Type-only imports are erased at compile time — no runtime cycle risk.
377
284
  if (node.importKind === 'type')
378
285
  return;
379
- // Each import node should produce at most ONE error regardless of how
380
- // many distinct cycle paths pass through this import.
381
286
  if (reportedImportNodes.has(node))
382
287
  return;
383
288
  const importSource = node.source.value;
384
- // Resolve using normalizedFilename so resolver output paths match.
385
289
  const resolved = (0, eslint_devkit_4.resolveImportPath)(importSource, {
386
290
  fromFile: normalizedFilename,
387
291
  workspaceRoot,
@@ -391,29 +295,8 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
391
295
  });
392
296
  if (!resolved)
393
297
  return;
394
- // =====================================================================
395
- // FAST PATH 1: nonCyclicFiles O(1) lookup
396
- //
397
- // Files in singleton SCCs are added to nonCyclicFiles during SCC
398
- // computation. This check is slightly faster than the sccIndex lookup
399
- // because it short-circuits before any SCC arithmetic.
400
- // =====================================================================
401
298
  if (sharedCache.nonCyclicFiles.has(resolved))
402
299
  return;
403
- // =====================================================================
404
- // SCC PRE-CHECK (O(1)) — the proven eslint-plugin-import approach
405
- //
406
- // If source and target are in DIFFERENT SCCs, no cycle can exist
407
- // between them — skip immediately without any graph traversal.
408
- // Eliminates ~99% of imports without reading a single file.
409
- //
410
- // If the target file isn't in the SCC yet, extend the SCC from it
411
- // lazily so we can make the correct determination.
412
- // =====================================================================
413
- // Fully lazy SCC: extend on-demand when source or target not yet covered.
414
- // Extending from the TARGET is preferred — if target → source (cycle),
415
- // extending from target will discover source and assign both to the same SCC.
416
- // Only extend from source as a fallback when target extension doesn't cover it.
417
300
  let tgtSCC = sharedCache.sccIndex.get(resolved);
418
301
  if (tgtSCC === undefined) {
419
302
  (0, eslint_devkit_4.computeSCCsFromFile)(resolved, {
@@ -427,8 +310,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
427
310
  }
428
311
  let srcSCC = sharedCache.sccIndex.get(normalizedFilename);
429
312
  if (srcSCC === undefined) {
430
- // Source still not covered after extending from target — source is in a
431
- // different connected component. Extend from source to cover it.
432
313
  (0, eslint_devkit_4.computeSCCsFromFile)(normalizedFilename, {
433
314
  maxDepth: Infinity,
434
315
  workspaceRoot,
@@ -441,13 +322,6 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
441
322
  if (srcSCC === undefined || tgtSCC === undefined || srcSCC !== tgtSCC) {
442
323
  return;
443
324
  }
444
- // =====================================================================
445
- // BFS PATH-FINDING — only runs when SCC guarantees a cycle exists.
446
- //
447
- // findShortestCyclePath does BFS from `resolved` back to
448
- // `normalizedFilename`, only traversing files within the same SCC.
449
- // This is bounded by the SCC size, not the entire codebase.
450
- // =====================================================================
451
325
  const cyclePath = (0, eslint_devkit_4.findShortestCyclePath)(normalizedFilename, resolved, {
452
326
  maxDepth,
453
327
  workspaceRoot,
@@ -457,9 +331,7 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
457
331
  });
458
332
  if (!cyclePath)
459
333
  return;
460
- // Deduplicate: this import node produces exactly one error.
461
334
  reportedImportNodes.add(node);
462
- // Build the relevant cycle starting from normalizedFilename.
463
335
  const cycleStart = cyclePath.indexOf(normalizedFilename);
464
336
  const relevantCycle = cycleStart >= 0
465
337
  ? [...cyclePath.slice(cycleStart), ...cyclePath.slice(0, cycleStart)]
@@ -469,14 +341,9 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
469
341
  const { messageId, data } = generateMessageData(relevantCycle, strategy, sourceImport);
470
342
  context.report({ node, messageId, data });
471
343
  },
472
- // Re-export statements (`export { X } from '...'`) create import edges
473
- // just like `import { X } from '...'`. Without this handler, cycles
474
- // that close through a re-export are invisible to the rule.
475
- // eslint-disable-next-line @typescript-eslint/naming-convention
476
344
  ExportNamedDeclaration(node) {
477
345
  if (!node.source)
478
- return; // no 'from' clause → not a re-export
479
- // Reuse the same logic as ImportDeclaration
346
+ return;
480
347
  if (reportedImportNodes.has(node))
481
348
  return;
482
349
  const importSource = node.source.value;
@@ -1,9 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noDefaultExport = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -86,7 +81,6 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
86
81
  if (!filename) {
87
82
  return false;
88
83
  }
89
- // Check if file is in allowed list
90
84
  const allowedByFile = allowInFiles.some((pattern) => {
91
85
  if (pattern.includes('*')) {
92
86
  const regex = new RegExp(pattern.replace(/\*/g, '.*'));
@@ -97,7 +91,6 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
97
91
  if (allowedByFile) {
98
92
  return true;
99
93
  }
100
- // Check if filename matches allowed patterns
101
94
  return allowPatterns.some((pattern) => {
102
95
  if (pattern.includes('*')) {
103
96
  const regex = new RegExp(pattern.replace(/\*/g, '.*'));
@@ -111,28 +104,21 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
111
104
  return '';
112
105
  }
113
106
  const declaration = node.declaration;
114
- // Try to determine a good name for the named export
115
107
  let suggestedName = 'exportedValue';
116
108
  if (declaration.type === 'Identifier') {
117
- // export default foo; -> export { foo };
118
109
  suggestedName = declaration.name;
119
110
  }
120
111
  else if (declaration.type === 'FunctionDeclaration' && declaration.id) {
121
- // export default function foo() {} -> export function foo() {}
122
- // But we still need to report this as it should be export function foo()
123
112
  suggestedName = declaration.id.name;
124
113
  }
125
114
  else if (declaration.type === 'ClassDeclaration' && declaration.id) {
126
- // export default class Foo {} -> export class Foo {}
127
115
  suggestedName = declaration.id.name;
128
116
  }
129
117
  else if (declaration.type === 'FunctionDeclaration' &&
130
118
  !declaration.id) {
131
- // export default function() {} -> export function defaultFunction() {}
132
119
  suggestedName = 'defaultFunction';
133
120
  }
134
121
  else if (declaration.type === 'ClassDeclaration' && !declaration.id) {
135
- // export default class {} -> export class DefaultClass {}
136
122
  suggestedName = 'DefaultClass';
137
123
  }
138
124
  return `// Convert to: export { ${suggestedName} };`;
@@ -155,7 +141,6 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
155
141
  const declaration = node.declaration;
156
142
  const sourceCode = context.sourceCode;
157
143
  if (declaration.type === 'Identifier') {
158
- // export default foo; -> export { foo };
159
144
  return fixer.replaceText(node, `export { ${declaration.name} };`);
160
145
  }
161
146
  else if (declaration.type === 'Literal' ||
@@ -164,14 +149,11 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
164
149
  declaration.type === 'ArrowFunctionExpression' ||
165
150
  declaration.type === 'CallExpression' ||
166
151
  declaration.type === 'NewExpression') {
167
- // export default value; -> const defaultExport = value; export { defaultExport };
168
152
  const exprText = sourceCode.getText(declaration);
169
- // Use a heuristic to handle potential semicolons or multiline
170
153
  const namedExport = `const defaultExport = ${exprText};\nexport { defaultExport };`;
171
154
  return fixer.replaceText(node, namedExport);
172
155
  }
173
156
  else {
174
- // For FunctionDeclaration and ClassDeclaration, replace 'export default' with 'export'
175
157
  const nodeText = sourceCode.getText(node);
176
158
  const namedExport = nodeText.replace(/^export default/, 'export');
177
159
  return fixer.replaceText(node, namedExport);
@@ -179,13 +161,10 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
179
161
  },
180
162
  });
181
163
  },
182
- // Also check for export { default } patterns
183
164
  ExportNamedDeclaration(node) {
184
165
  if (shouldAllow()) {
185
166
  return;
186
167
  }
187
- // Check if this is export { default } from "module"
188
- // This is a re-export pattern that effectively creates a default export
189
168
  if (node.specifiers) {
190
169
  const hasDefaultSpecifier = node.specifiers.some((spec) => (spec.exported.type === 'Identifier' &&
191
170
  spec.exported.name === 'default') ||
@@ -205,7 +184,6 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
205
184
  }
206
185
  }
207
186
  },
208
- // Check for export = value (TypeScript namespace export)
209
187
  TSExportAssignment(node) {
210
188
  if (shouldAllow()) {
211
189
  return;
@@ -220,18 +198,12 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
220
198
  },
221
199
  });
222
200
  },
223
- // Handle TypeScript interface and type exports
224
201
  TSInterfaceDeclaration(node) {
225
- // Check if this is a default export (export default interface ...)
226
- // This would be caught by ExportDefaultDeclaration, but we need to handle
227
- // the case where it's directly exported as default
228
202
  if (node.parent && node.parent.type === 'ExportDefaultDeclaration') {
229
- // This will be handled by ExportDefaultDeclaration visitor
230
203
  return;
231
204
  }
232
205
  },
233
206
  TSTypeAliasDeclaration(node) {
234
- // Similar to interfaces
235
207
  if (node.parent && node.parent.type === eslint_devkit_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
236
208
  return;
237
209
  }