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.
- package/CHANGELOG.md +44 -0
- package/README.md +99 -78
- package/package.json +21 -5
- package/src/index.d.ts +192 -0
- package/src/index.js +13 -1
- package/src/oxlint.d.ts +20 -0
- package/src/oxlint.js +21 -0
- package/src/rules/consistent-type-specifier-style.js +5 -1
- package/src/rules/default.js +1 -0
- package/src/rules/dynamic-import-chunkname.js +4 -1
- package/src/rules/enforce-dependency-direction.js +2 -1
- package/src/rules/enforce-import-order.js +4 -2
- package/src/rules/enforce-team-boundaries.js +27 -17
- package/src/rules/export.js +3 -0
- package/src/rules/exports-last.js +3 -0
- package/src/rules/extensions.js +1 -0
- package/src/rules/first.js +2 -1
- package/src/rules/group-exports.js +3 -0
- package/src/rules/max-dependencies.js +2 -1
- package/src/rules/named.js +1 -1
- package/src/rules/namespace.js +1 -0
- package/src/rules/newline-after-import.js +1 -0
- package/src/rules/no-absolute-path.js +3 -0
- package/src/rules/no-amd.js +2 -1
- package/src/rules/no-anonymous-default-export.js +2 -0
- package/src/rules/no-barrel-file.js +2 -1
- package/src/rules/no-barrel-import.js +1 -1
- package/src/rules/no-commonjs.js +2 -1
- package/src/rules/no-cross-domain-imports.js +2 -1
- package/src/rules/no-cycle.d.ts +1 -1
- package/src/rules/no-cycle.js +187 -71
- package/src/rules/no-default-export.js +2 -1
- package/src/rules/no-deprecated.js +2 -1
- package/src/rules/no-duplicates.js +1 -0
- package/src/rules/no-dynamic-require.js +3 -0
- package/src/rules/no-empty-named-blocks.js +4 -1
- package/src/rules/no-extraneous-dependencies.js +4 -1
- package/src/rules/no-full-package-import.js +1 -0
- package/src/rules/no-import-module-exports.js +3 -1
- package/src/rules/no-internal-modules.js +1 -0
- package/src/rules/no-legacy-imports.js +1 -0
- package/src/rules/no-mutable-exports.js +2 -1
- package/src/rules/no-named-as-default-member.js +3 -0
- package/src/rules/no-named-as-default.js +3 -0
- package/src/rules/no-named-default.js +3 -0
- package/src/rules/no-named-export.js +2 -1
- package/src/rules/no-namespace.js +12 -6
- package/src/rules/no-nodejs-modules.js +3 -1
- package/src/rules/no-relative-packages.js +4 -1
- package/src/rules/no-relative-parent-imports.js +1 -0
- package/src/rules/no-restricted-paths.js +1 -0
- package/src/rules/no-self-import.js +2 -1
- package/src/rules/no-unassigned-import.js +1 -0
- package/src/rules/no-unresolved.js +3 -2
- package/src/rules/no-unused-modules.js +1 -0
- package/src/rules/no-useless-path-segments.js +3 -0
- package/src/rules/prefer-default-export.js +2 -1
- package/src/rules/prefer-direct-import.js +1 -0
- package/src/rules/prefer-modern-api.js +1 -0
- package/src/rules/prefer-node-protocol.js +2 -0
- package/src/rules/prefer-tree-shakeable-imports.js +1 -0
- package/src/rules/require-import-approval.js +1 -0
- package/src/rules/unambiguous.js +3 -0
package/src/rules/no-cycle.js
CHANGED
|
@@ -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:
|
|
108
|
-
description: 'Maximum depth to traverse when detecting cycles (performance
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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.
|
|
205
|
-
|
|
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?.['
|
|
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.
|
|
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.
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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 (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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:
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
|
|
51
|
-
.replace(
|
|
52
|
-
.replace(
|
|
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.
|
|
194
|
+
currentFile: context.filename,
|
|
193
195
|
alternative,
|
|
194
196
|
suggestion: fixSuggestion,
|
|
195
197
|
},
|