eslint-plugin-import-next 2.3.5 → 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.
Files changed (63) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +99 -78
  3. package/package.json +21 -5
  4. package/src/index.d.ts +192 -0
  5. package/src/index.js +13 -1
  6. package/src/oxlint.d.ts +20 -0
  7. package/src/oxlint.js +21 -0
  8. package/src/rules/consistent-type-specifier-style.js +5 -1
  9. package/src/rules/default.js +1 -0
  10. package/src/rules/dynamic-import-chunkname.js +4 -1
  11. package/src/rules/enforce-dependency-direction.js +2 -1
  12. package/src/rules/enforce-import-order.js +4 -2
  13. package/src/rules/enforce-team-boundaries.js +27 -17
  14. package/src/rules/export.js +3 -0
  15. package/src/rules/exports-last.js +3 -0
  16. package/src/rules/extensions.js +1 -0
  17. package/src/rules/first.js +2 -1
  18. package/src/rules/group-exports.js +3 -0
  19. package/src/rules/max-dependencies.js +2 -1
  20. package/src/rules/named.js +1 -1
  21. package/src/rules/namespace.js +1 -0
  22. package/src/rules/newline-after-import.js +1 -0
  23. package/src/rules/no-absolute-path.js +3 -0
  24. package/src/rules/no-amd.js +2 -1
  25. package/src/rules/no-anonymous-default-export.js +2 -0
  26. package/src/rules/no-barrel-file.js +2 -1
  27. package/src/rules/no-barrel-import.js +1 -1
  28. package/src/rules/no-commonjs.js +2 -1
  29. package/src/rules/no-cross-domain-imports.js +2 -1
  30. package/src/rules/no-cycle.d.ts +1 -1
  31. package/src/rules/no-cycle.js +187 -71
  32. package/src/rules/no-default-export.js +2 -1
  33. package/src/rules/no-deprecated.js +2 -1
  34. package/src/rules/no-duplicates.js +1 -0
  35. package/src/rules/no-dynamic-require.js +3 -0
  36. package/src/rules/no-empty-named-blocks.js +4 -1
  37. package/src/rules/no-extraneous-dependencies.js +4 -1
  38. package/src/rules/no-full-package-import.js +1 -0
  39. package/src/rules/no-import-module-exports.js +3 -1
  40. package/src/rules/no-internal-modules.js +1 -0
  41. package/src/rules/no-legacy-imports.js +1 -0
  42. package/src/rules/no-mutable-exports.js +2 -1
  43. package/src/rules/no-named-as-default-member.js +3 -0
  44. package/src/rules/no-named-as-default.js +3 -0
  45. package/src/rules/no-named-default.js +3 -0
  46. package/src/rules/no-named-export.js +2 -1
  47. package/src/rules/no-namespace.js +12 -6
  48. package/src/rules/no-nodejs-modules.js +3 -1
  49. package/src/rules/no-relative-packages.js +4 -1
  50. package/src/rules/no-relative-parent-imports.js +1 -0
  51. package/src/rules/no-restricted-paths.js +1 -0
  52. package/src/rules/no-self-import.js +2 -1
  53. package/src/rules/no-unassigned-import.js +1 -0
  54. package/src/rules/no-unresolved.js +3 -2
  55. package/src/rules/no-unused-modules.js +1 -0
  56. package/src/rules/no-useless-path-segments.js +3 -0
  57. package/src/rules/prefer-default-export.js +2 -1
  58. package/src/rules/prefer-direct-import.js +1 -0
  59. package/src/rules/prefer-modern-api.js +1 -0
  60. package/src/rules/prefer-node-protocol.js +2 -0
  61. package/src/rules/prefer-tree-shakeable-imports.js +1 -0
  62. package/src/rules/require-import-approval.js +1 -0
  63. package/src/rules/unambiguous.js +3 -0
@@ -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
@@ -57,7 +58,11 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
57
58
  meta: {
58
59
  type: 'problem',
59
60
  docs: {
61
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-cycle.md',
60
62
  description: 'Detect circular dependencies that cause bundle memory bloat and initialization issues',
63
+ cwe: 'CWE-407',
64
+ cvss: 9.5,
65
+ confidence: 'high',
61
66
  },
62
67
  messages: {
63
68
  // 🎯 Token optimization: 45% reduction (~70→38 tokens per message) for architecture clarity
@@ -104,8 +109,8 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
104
109
  properties: {
105
110
  maxDepth: {
106
111
  type: 'number',
107
- default: 5,
108
- description: 'Maximum depth to traverse when detecting cycles (performance optimization)',
112
+ default: Number.MAX_SAFE_INTEGER,
113
+ description: 'Maximum depth to traverse when detecting cycles. Default: unlimited (matches eslint-plugin-import and oxlint). Lower values are a performance escape hatch — but with our nonCyclicFiles cache, traversal cost is amortized, and a low cap silently misses cycles deeper than the limit. Set to a finite number only on huge graphs where the bench latency hurts you.',
109
114
  },
110
115
  ignorePatterns: {
111
116
  type: 'array',
@@ -168,7 +173,12 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
168
173
  },
169
174
  defaultOptions: [
170
175
  {
171
- maxDepth: 10,
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
+ maxDepth: Infinity,
172
182
  ignorePatterns: [],
173
183
  barrelExports: ['index.ts', 'index.tsx', 'index.js', 'index.jsx'],
174
184
  reportAllCycles: true,
@@ -180,7 +190,7 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
180
190
  ],
181
191
  create(context) {
182
192
  const options = context.options[0] || {};
183
- const maxDepth = options.maxDepth ?? 10;
193
+ const maxDepth = options.maxDepth ?? Infinity;
184
194
  const ignorePatterns = options.ignorePatterns ?? [
185
195
  '**/*.test.ts',
186
196
  '**/*.test.tsx',
@@ -196,22 +206,45 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
196
206
  'index.js',
197
207
  'index.jsx',
198
208
  ];
199
- const reportAllCycles = options.reportAllCycles ?? true;
209
+ const _reportAllCycles = options.reportAllCycles ?? true;
200
210
  const fixStrategy = options.fixStrategy ?? 'auto';
201
211
  const moduleNamingConvention = options.moduleNamingConvention ?? 'semantic';
202
212
  const coreModuleSuffix = options.coreModuleSuffix ?? 'core';
203
213
  const extendedModuleSuffix = options.extendedModuleSuffix ?? 'extended';
204
- const filename = context.getFilename();
205
- const workspaceRoot = context.getCwd();
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
+ })();
226
+ const workspaceRoot = context.cwd;
206
227
  // Get resolver settings from ESLint settings (compatible with eslint-plugin-import)
207
- // eslint-plugin-import uses settings['import/resolver']
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.
208
231
  const settings = context.settings;
209
232
  const resolverSettings = settings?.['import/resolver'] ||
210
- settings?.['eslint-plugin-llm-optimized/resolver'];
233
+ settings?.['import-next/resolver'];
211
234
  // Skip ignored files early
212
235
  if ((0, eslint_devkit_3.shouldIgnoreFile)(filename, ignorePatterns, sharedCache.compiledPatterns)) {
213
236
  return {};
214
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();
215
248
  /**
216
249
  * Select fix strategy based on cycle characteristics
217
250
  */
@@ -257,9 +290,9 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
257
290
  * Generate DIRECT IMPORT message
258
291
  */
259
292
  function generateDirectImportMessage(cycle, sourceImport) {
260
- const currentFile = (0, eslint_devkit_3.getBasename)(context.getFilename());
293
+ const currentFile = (0, eslint_devkit_3.getBasename)(context.filename);
261
294
  const targetFile = cycle[1];
262
- const relativeImport = (0, eslint_devkit_3.getRelativeImportPath)(context.getFilename(), targetFile);
295
+ const relativeImport = (0, eslint_devkit_3.getRelativeImportPath)(context.filename, targetFile);
263
296
  return {
264
297
  cycle: (0, eslint_devkit_3.formatCycleDisplay)(cycle, workspaceRoot),
265
298
  currentFile,
@@ -333,61 +366,126 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
333
366
  };
334
367
  }
335
368
  }
336
- // Store found cycles to report on specific imports
337
- let detectedCycles = [];
338
369
  return {
339
- Program() {
340
- // Clear per-file state
341
- detectedCycles = [];
342
- // Find ALL circular dependencies starting from current file
343
- const cycles = (0, eslint_devkit_4.findAllCircularDependencies)(filename, {
344
- maxDepth,
345
- reportAllCycles,
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
+ },
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;
387
+ const importSource = node.source.value;
388
+ // Resolve using normalizedFilename so resolver output paths match.
389
+ const resolved = (0, eslint_devkit_4.resolveImportPath)(importSource, {
390
+ fromFile: normalizedFilename,
346
391
  workspaceRoot,
347
392
  barrelExports,
348
393
  cache: sharedCache,
349
394
  resolverSettings,
350
395
  });
351
- if (cycles.length > 0) {
352
- for (const cycle of cycles) {
353
- // Extract the minimal cycle (the actual loop)
354
- const minimalCycle = (0, eslint_devkit_4.getMinimalCycle)(cycle);
355
- // Only report if current file is part of the minimal cycle
356
- // (not just a file that imports into the cycle)
357
- if (!minimalCycle.includes(filename)) {
358
- continue;
359
- }
360
- const cycleStart = minimalCycle.indexOf(filename);
361
- if (cycleStart === -1)
362
- continue;
363
- const relevantCycle = [
364
- ...minimalCycle.slice(cycleStart),
365
- ...minimalCycle.slice(0, cycleStart),
366
- ];
367
- const cycleHash = (0, eslint_devkit_4.getCycleHash)(relevantCycle);
368
- // Skip if we've already reported this cycle (shared across all files)
369
- if (sharedCache.reportedCycles.has(cycleHash)) {
370
- continue;
371
- }
372
- sharedCache.reportedCycles.add(cycleHash);
373
- // Select the appropriate fix strategy
374
- const strategy = selectFixStrategy(relevantCycle, fixStrategy);
375
- // Find the target of the first import that causes the cycle
376
- const cycleTarget = relevantCycle[1] || relevantCycle[0]; // The next file in the cycle
377
- detectedCycles.push({
378
- cycleTarget,
379
- strategy,
380
- relevantCycle,
381
- cycleHash,
382
- });
383
- }
396
+ if (!resolved)
397
+ return;
398
+ // =====================================================================
399
+ // FAST PATH 1: nonCyclicFiles O(1) lookup
400
+ //
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
409
+ //
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.
416
+ // =====================================================================
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);
384
444
  }
445
+ if (srcSCC === undefined || tgtSCC === undefined || srcSCC !== tgtSCC) {
446
+ return;
447
+ }
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, {
456
+ maxDepth,
457
+ workspaceRoot,
458
+ barrelExports,
459
+ cache: sharedCache,
460
+ resolverSettings,
461
+ });
462
+ if (!cyclePath)
463
+ return;
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 });
385
475
  },
386
- ImportDeclaration(node) {
387
- // Check if this import is part of any detected circular dependency
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;
388
486
  const importSource = node.source.value;
389
487
  const resolved = (0, eslint_devkit_4.resolveImportPath)(importSource, {
390
- fromFile: filename,
488
+ fromFile: normalizedFilename,
391
489
  workspaceRoot,
392
490
  barrelExports,
393
491
  cache: sharedCache,
@@ -395,22 +493,40 @@ exports.noCycle = (0, eslint_devkit_1.createRule)({
395
493
  });
396
494
  if (!resolved)
397
495
  return;
398
- for (const cycle of detectedCycles) {
399
- if (resolved === cycle.cycleTarget) {
400
- // This is an import that causes a circular dependency
401
- const sourceImport = {
402
- path: resolved,
403
- source: importSource,
404
- };
405
- // Generate the appropriate message based on strategy
406
- const { messageId, data } = generateMessageData(cycle.relevantCycle, cycle.strategy, sourceImport);
407
- context.report({
408
- node,
409
- messageId,
410
- data,
411
- });
412
- }
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,
506
+ });
507
+ tgtSCC = sharedCache.sccIndex.get(resolved);
413
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 });
414
530
  },
415
531
  };
416
532
  },
@@ -13,6 +13,7 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  docs: {
16
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-default-export.md',
16
17
  description: 'Prevents default exports',
17
18
  },
18
19
  fixable: 'code',
@@ -80,7 +81,7 @@ exports.noDefaultExport = (0, eslint_devkit_1.createRule)({
80
81
  create(context) {
81
82
  const [options] = context.options;
82
83
  const { allowInFiles = [], allowPatterns = [], suggestNamed = true, } = options || {};
83
- const filename = context.getFilename();
84
+ const filename = context.filename;
84
85
  function shouldAllow() {
85
86
  if (!filename) {
86
87
  return false;
@@ -13,6 +13,7 @@ exports.noDeprecated = (0, eslint_devkit_1.createRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  docs: {
16
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-deprecated.md',
16
17
  description: 'Forbid imported names marked with @deprecated documentation tag',
17
18
  },
18
19
  hasSuggestions: true,
@@ -83,7 +84,7 @@ exports.noDeprecated = (0, eslint_devkit_1.createRule)({
83
84
  create(context) {
84
85
  const [options] = context.options;
85
86
  const { allow = [], deprecationMarkers = ['@deprecated'], checkJSDoc = true, checkDecorators = true, } = options || {};
86
- const filename = context.getFilename();
87
+ const filename = context.filename;
87
88
  if (!filename) {
88
89
  return {};
89
90
  }
@@ -13,6 +13,7 @@ exports.noDuplicates = (0, eslint_devkit_1.createRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  docs: {
16
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-duplicates.md',
16
17
  description: 'Reports duplicate imports',
17
18
  },
18
19
  fixable: 'code',
@@ -12,7 +12,10 @@ exports.noDynamicRequire = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-dynamic-require.md',
15
16
  description: 'Forbid require() calls with expressions',
17
+ cwe: 'CWE-829',
18
+ cvss: 5,
16
19
  },
17
20
  messages: {
18
21
  dynamicRequire: (0, eslint_devkit_1.formatLLMMessage)({
@@ -12,7 +12,10 @@ exports.noEmptyNamedBlocks = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-empty-named-blocks.md',
15
16
  description: 'Forbid empty named import blocks',
17
+ cwe: 'CWE-561',
18
+ cvss: 2.5,
16
19
  },
17
20
  fixable: 'code',
18
21
  hasSuggestions: true,
@@ -40,7 +43,7 @@ exports.noEmptyNamedBlocks = (0, eslint_devkit_1.createRule)({
40
43
  const hasNamespaceImport = node.specifiers.some((spec) => spec.type === 'ImportNamespaceSpecifier');
41
44
  const namedImports = node.specifiers.filter((spec) => spec.type === 'ImportSpecifier');
42
45
  // Get the source code
43
- const sourceCode = context.getSourceCode();
46
+ const sourceCode = context.sourceCode;
44
47
  const importText = sourceCode.getText(node);
45
48
  // Check for empty braces pattern: {}
46
49
  const hasEmptyBraces = /\{\s*\}/.test(importText);
@@ -15,6 +15,7 @@ exports.noExtraneousDependencies = (0, eslint_devkit_1.createRule)({
15
15
  meta: {
16
16
  type: 'problem',
17
17
  docs: {
18
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-extraneous-dependencies.md',
18
19
  description: 'Forbid the use of extraneous packages not listed in package.json',
19
20
  },
20
21
  hasSuggestions: true,
@@ -134,7 +135,7 @@ exports.noExtraneousDependencies = (0, eslint_devkit_1.createRule)({
134
135
  create(context) {
135
136
  const [options] = context.options;
136
137
  const { devDependencies = true, optionalDependencies = true, peerDependencies = true, bundledDependencies = true, packageJsonPath, packageJson, resolutionStrategy = 'strict', allowPatterns = [], } = options || {};
137
- const filename = context.getFilename();
138
+ const filename = context.filename;
138
139
  if (!filename) {
139
140
  return {};
140
141
  }
@@ -188,6 +189,7 @@ exports.noExtraneousDependencies = (0, eslint_devkit_1.createRule)({
188
189
  if (bundledDependencies && resolvedPackageJson?.bundledDependencies) {
189
190
  resolvedPackageJson.bundledDependencies.forEach((pkg) => allowedPackages.add(pkg));
190
191
  }
192
+ // oxlint-disable-next-line consistent-function-scoping
191
193
  function isExternalPackage(importPath) {
192
194
  // Skip relative imports
193
195
  if (importPath.startsWith('./') || importPath.startsWith('../')) {
@@ -265,6 +267,7 @@ exports.noExtraneousDependencies = (0, eslint_devkit_1.createRule)({
265
267
  : importPath.split('/')[0];
266
268
  return packageName;
267
269
  }
270
+ // oxlint-disable-next-line consistent-function-scoping
268
271
  function isWorkspacePackage(packageName) {
269
272
  // For testing purposes, consider packages with @workspace/ or @company/ as workspace packages
270
273
  return (packageName.startsWith('@workspace/') ||
@@ -79,6 +79,7 @@ exports.noFullPackageImport = (0, eslint_devkit_1.createRule)({
79
79
  meta: {
80
80
  type: 'suggestion',
81
81
  docs: {
82
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-full-package-import.md',
82
83
  description: 'Disallow full package imports from known large packages that prevent tree-shaking',
83
84
  },
84
85
  hasSuggestions: true,
@@ -12,9 +12,11 @@ exports.noImportModuleExports = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'problem',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-import-module-exports.md',
15
16
  description: 'Forbid import statements with CommonJS module.exports',
17
+ cwe: 'CWE-1078',
18
+ cvss: 7.5,
16
19
  },
17
- fixable: 'code',
18
20
  messages: {
19
21
  importModuleExports: (0, eslint_devkit_1.formatLLMMessage)({
20
22
  icon: eslint_devkit_1.MessageIcons.ARCHITECTURE,
@@ -87,6 +87,7 @@ exports.noInternalModules = (0, eslint_devkit_1.createRule)({
87
87
  meta: {
88
88
  type: 'problem',
89
89
  docs: {
90
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-internal-modules.md',
90
91
  description: 'Forbid importing the submodules of other modules',
91
92
  },
92
93
  fixable: 'code',
@@ -25,6 +25,7 @@ exports.noLegacyImports = (0, eslint_devkit_1.createRule)({
25
25
  meta: {
26
26
  type: 'suggestion',
27
27
  docs: {
28
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-legacy-imports.md',
28
29
  description: 'Detect imports from deprecated internal paths and suggest alternatives',
29
30
  },
30
31
  fixable: 'code',
@@ -13,6 +13,7 @@ exports.noMutableExports = (0, eslint_devkit_1.createRule)({
13
13
  meta: {
14
14
  type: 'suggestion',
15
15
  docs: {
16
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-mutable-exports.md',
16
17
  description: 'Forbid the use of mutable exports with `var` or `let`',
17
18
  },
18
19
  hasSuggestions: false,
@@ -70,7 +71,7 @@ exports.noMutableExports = (0, eslint_devkit_1.createRule)({
70
71
  create(context) {
71
72
  const [options] = context.options;
72
73
  const { allowInFiles = [], ignoreExports = [] } = options || {};
73
- const filename = context.getFilename();
74
+ const filename = context.filename;
74
75
  if (!filename) {
75
76
  return {};
76
77
  }
@@ -12,7 +12,10 @@ exports.noNamedAsDefaultMember = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-named-as-default-member.md',
15
16
  description: 'Forbid use of exported name as property of default export',
17
+ cwe: 'CWE-1078',
18
+ cvss: 2.5,
16
19
  },
17
20
  messages: {
18
21
  namedAsDefaultMember: (0, eslint_devkit_1.formatLLMMessage)({
@@ -12,7 +12,10 @@ exports.noNamedAsDefault = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'problem',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-named-as-default.md',
15
16
  description: 'Forbid use of exported name as identifier of default export',
17
+ cwe: 'CWE-1078',
18
+ cvss: 5,
16
19
  },
17
20
  messages: {
18
21
  namedAsDefault: (0, eslint_devkit_1.formatLLMMessage)({
@@ -12,7 +12,10 @@ exports.noNamedDefault = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-named-default.md',
15
16
  description: 'Forbid named default exports',
17
+ cwe: 'CWE-1078',
18
+ cvss: 2.5,
16
19
  },
17
20
  messages: {
18
21
  namedDefault: (0, eslint_devkit_1.formatLLMMessage)({
@@ -13,6 +13,7 @@ exports.noNamedExport = (0, eslint_devkit_1.createRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  docs: {
16
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-named-export.md',
16
17
  description: 'Prevents named exports',
17
18
  },
18
19
  hasSuggestions: false,
@@ -82,7 +83,7 @@ exports.noNamedExport = (0, eslint_devkit_1.createRule)({
82
83
  const { allowInFiles = [], allowNames = [], allowPatterns = [],
83
84
  // suggestDefault // unused option
84
85
  } = options || {};
85
- const filename = context.getFilename();
86
+ const filename = context.filename;
86
87
  function shouldAllow(specifier) {
87
88
  if (!filename) {
88
89
  return false;
@@ -12,9 +12,11 @@ exports.noNamespace = (0, eslint_devkit_1.createRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  docs: {
15
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-namespace.md',
15
16
  description: 'Forbid namespace (a.k.a. "wildcard" *) imports',
17
+ cwe: 'CWE-1078',
18
+ cvss: 2.5,
16
19
  },
17
- fixable: 'code',
18
20
  messages: {
19
21
  noNamespace: (0, eslint_devkit_1.formatLLMMessage)({
20
22
  icon: eslint_devkit_1.MessageIcons.ARCHITECTURE,
@@ -45,12 +47,16 @@ exports.noNamespace = (0, eslint_devkit_1.createRule)({
45
47
  const ignorePatterns = options.ignore || [];
46
48
  function isIgnored(source) {
47
49
  return ignorePatterns.some((pattern) => {
48
- // Simple glob matching
50
+ // Escape ALL regex metacharacters — including `*`, `?`, and `\` — so
51
+ // the subsequent replaces can find the now-escaped `\*` and `\?` and
52
+ // translate them back to glob meanings. Without escaping `*` and `?`,
53
+ // the inner replaces don't match anything and raw `*` / `?` reach
54
+ // `new RegExp()` (CodeQL: `js/incomplete-sanitization`).
55
+ const escaped = pattern.replace(/[.+^${}()|[\]\\*?]/g, '\\$&');
49
56
  const regex = new RegExp('^' +
50
- pattern
51
- .replace(/\*/g, '.*')
52
- .replace(/\?/g, '.')
53
- .replace(/\//g, '\\/') +
57
+ escaped
58
+ .replace(/\\\*/g, '.*')
59
+ .replace(/\\\?/g, '.') +
54
60
  '$');
55
61
  return regex.test(source);
56
62
  });
@@ -64,6 +64,7 @@ exports.noNodejsModules = (0, eslint_devkit_1.createRule)({
64
64
  meta: {
65
65
  type: 'problem',
66
66
  docs: {
67
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-import-next/docs/rules/no-nodejs-modules.md',
67
68
  description: 'Prevents Node.js builtin imports',
68
69
  },
69
70
  messages: {
@@ -146,6 +147,7 @@ exports.noNodejsModules = (0, eslint_devkit_1.createRule)({
146
147
  }
147
148
  return false;
148
149
  }
150
+ // oxlint-disable-next-line consistent-function-scoping
149
151
  function getBuiltinName(moduleName) {
150
152
  if (moduleName.startsWith('node:')) {
151
153
  return moduleName.slice(5);
@@ -189,7 +191,7 @@ exports.noNodejsModules = (0, eslint_devkit_1.createRule)({
189
191
  data: {
190
192
  moduleName,
191
193
  builtinName,
192
- currentFile: context.getFilename(),
194
+ currentFile: context.filename,
193
195
  alternative,
194
196
  suggestion: fixSuggestion,
195
197
  },