eslint-plugin-barrel-rules 1.4.0 → 1.4.3

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/README.ko.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # **Advanced Barrel Pattern Enforcement for JavaScript/TypeScript Projects**
4
4
 
5
5
  <div align="center">
6
- <img src="https://img.shields.io/badge/version-1.4.0-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version"/>
7
7
  <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"/>
8
8
  <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"/>
9
9
  </div>
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # **Advanced Barrel Pattern Enforcement for JavaScript/TypeScript Projects**
4
4
 
5
5
  <div align="center">
6
- <img src="https://img.shields.io/badge/version-1.4.0-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.4.3-blue.svg" alt="Version"/>
7
7
  <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"/>
8
8
  <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"/>
9
9
  </div>
package/dist/index.cjs CHANGED
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- default: () => index_default
33
+ rules: () => rules
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
@@ -42,8 +42,8 @@ var import_resolve = __toESM(require("resolve"), 1);
42
42
  // src/utils/glob.ts
43
43
  var import_fast_glob = __toESM(require("fast-glob"), 1);
44
44
  var Glob = class {
45
- static resolvePath(path4, baseDir) {
46
- const globResult = import_fast_glob.default.sync(path4, {
45
+ static resolvePath(path5, baseDir) {
46
+ const globResult = import_fast_glob.default.sync(path5, {
47
47
  cwd: baseDir,
48
48
  onlyDirectories: true,
49
49
  absolute: true
@@ -106,33 +106,59 @@ var Alias = class {
106
106
  }
107
107
  };
108
108
 
109
- // src/rules/enforce-barrel-pattern.ts
109
+ // src/utils/constants.ts
110
110
  var BARREL_ENTRY_POINT_FILE_NAMES = [
111
111
  "index.ts",
112
+ // TypeScript entry
112
113
  "index.tsx",
114
+ // React TypeScript entry
113
115
  "index.js",
116
+ // JavaScript entry
114
117
  "index.jsx",
118
+ // React JavaScript entry
115
119
  "index.cjs",
116
- "index.mjs",
117
- "index.d.ts"
120
+ // CommonJS entry
121
+ "index.mjs"
122
+ // ES Module entry
118
123
  ];
119
124
  var RESOLVE_EXTENSIONS = [
120
125
  ".ts",
126
+ // TypeScript
121
127
  ".js",
128
+ // JavaScript
122
129
  ".tsx",
130
+ // React TypeScript
123
131
  ".jsx",
132
+ // React JavaScript
124
133
  ".json",
134
+ // JSON file
135
+ // Type declaration and transformed files
125
136
  ".d.js",
137
+ // JS file with type declarations (rare)
126
138
  ".d.ts",
139
+ // Type declaration file
140
+ // Various module systems
127
141
  ".mjs",
142
+ // ES Module
128
143
  ".cjs",
144
+ // CommonJS
145
+ // Module-related TypeScript extensions
129
146
  ".mts",
147
+ // ES Module TypeScript
130
148
  ".cts",
149
+ // CommonJS TypeScript
150
+ // Type declarations for module-related files
131
151
  ".d.mjs",
152
+ // Type declarations for ES Module
132
153
  ".d.cjs",
154
+ // Type declarations for CommonJS
133
155
  ".d.mts",
156
+ // Type declarations for ES Module TS
134
157
  ".d.cts"
158
+ // Type declarations for CommonJS TS
135
159
  ];
160
+
161
+ // src/rules/enforce-barrel-pattern.ts
136
162
  var enforceBarrelPattern = {
137
163
  meta: {
138
164
  type: "problem",
@@ -265,23 +291,6 @@ var enforceBarrelPattern = {
265
291
  var import_types2 = require("@typescript-eslint/types");
266
292
  var import_path3 = __toESM(require("path"), 1);
267
293
  var import_resolve2 = __toESM(require("resolve"), 1);
268
- var RESOLVE_EXTENSIONS2 = [
269
- ".ts",
270
- ".js",
271
- ".tsx",
272
- ".jsx",
273
- ".json",
274
- ".d.js",
275
- ".d.ts",
276
- ".mjs",
277
- ".cjs",
278
- ".mts",
279
- ".cts",
280
- ".d.mjs",
281
- ".d.cjs",
282
- ".d.mts",
283
- ".d.cts"
284
- ];
285
294
  var isolateBarrelFile = {
286
295
  meta: {
287
296
  type: "problem",
@@ -316,13 +325,13 @@ var isolateBarrelFile = {
316
325
  create(context) {
317
326
  const option = context.options[0];
318
327
  const baseDir = option.baseDir;
319
- const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path4) => {
320
- return Glob.resolvePath(path4, baseDir);
328
+ const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path5) => {
329
+ return Glob.resolvePath(path5, baseDir);
321
330
  });
322
331
  const absoluteIsolations = option.isolations.flatMap((isolation) => {
323
332
  const isolationPaths = Glob.resolvePath(isolation.path, baseDir);
324
- const allowedPaths = isolation.allowedPaths.flatMap((path4) => {
325
- return Glob.resolvePath(path4, baseDir);
333
+ const allowedPaths = isolation.allowedPaths.flatMap((path5) => {
334
+ return Glob.resolvePath(path5, baseDir);
326
335
  });
327
336
  return isolationPaths.map((isolationPath) => ({
328
337
  isolationPath,
@@ -334,6 +343,9 @@ var isolateBarrelFile = {
334
343
  //check only import declaration(ESM)
335
344
  ImportDeclaration(node) {
336
345
  const rawImportPath = node.source.value;
346
+ if (rawImportPath.includes("/node_modules/")) {
347
+ return;
348
+ }
337
349
  const absoluteCurrentFilePath = context.getFilename();
338
350
  let absoluteImportPath = null;
339
351
  try {
@@ -343,13 +355,16 @@ var isolateBarrelFile = {
343
355
  );
344
356
  if (aliasResult.type === "success") {
345
357
  absoluteImportPath = aliasResult.absolutePath;
358
+ if (absoluteImportPath.includes("/node_modules/")) {
359
+ return;
360
+ }
346
361
  } else {
347
- if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/") || rawImportPath.includes("/node_modules/")) {
362
+ if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/")) {
348
363
  return;
349
364
  }
350
365
  absoluteImportPath = import_resolve2.default.sync(rawImportPath, {
351
366
  basedir: import_path3.default.dirname(absoluteCurrentFilePath),
352
- extensions: RESOLVE_EXTENSIONS2
367
+ extensions: RESOLVE_EXTENSIONS
353
368
  });
354
369
  }
355
370
  } catch (e) {
@@ -432,11 +447,259 @@ var noWildcard = {
432
447
  }
433
448
  };
434
449
 
450
+ // src/rules/no-cycle.ts
451
+ var import_types4 = require("@typescript-eslint/types");
452
+ var import_path4 = __toESM(require("path"), 1);
453
+ var import_resolve3 = __toESM(require("resolve"), 1);
454
+ var importGraph = /* @__PURE__ */ new Map();
455
+ var fileToImports = /* @__PURE__ */ new Map();
456
+ var barrelExportsCache = /* @__PURE__ */ new Map();
457
+ function detectCycle(currentFile, visited, recStack, path5) {
458
+ visited.add(currentFile);
459
+ recStack.add(currentFile);
460
+ path5.push(currentFile);
461
+ const imports = importGraph.get(currentFile) || /* @__PURE__ */ new Set();
462
+ for (const importedFile of imports) {
463
+ if (!visited.has(importedFile)) {
464
+ const cycle = detectCycle(importedFile, visited, recStack, [...path5]);
465
+ if (cycle) {
466
+ return cycle;
467
+ }
468
+ } else if (recStack.has(importedFile)) {
469
+ const cycleStart = path5.indexOf(importedFile);
470
+ return [...path5.slice(cycleStart), importedFile];
471
+ }
472
+ }
473
+ recStack.delete(currentFile);
474
+ return null;
475
+ }
476
+ function isBarrelFile(filePath) {
477
+ const fileName = import_path4.default.basename(filePath);
478
+ return BARREL_ENTRY_POINT_FILE_NAMES.includes(
479
+ fileName
480
+ );
481
+ }
482
+ function getExportedModulesFromBarrel(barrelFileDir, sourceCode) {
483
+ const exportedModules = [];
484
+ const ast = sourceCode.ast;
485
+ for (const statement of ast.body) {
486
+ if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") {
487
+ if (statement.source) {
488
+ const exportPath = statement.source.value;
489
+ try {
490
+ const resolvedPath = import_resolve3.default.sync(exportPath, {
491
+ basedir: barrelFileDir,
492
+ extensions: RESOLVE_EXTENSIONS
493
+ });
494
+ if (!exportedModules.includes(resolvedPath)) {
495
+ exportedModules.push(resolvedPath);
496
+ }
497
+ } catch (e) {
498
+ }
499
+ }
500
+ }
501
+ if (statement.type === "ImportDeclaration") {
502
+ if (statement.source) {
503
+ const importPath = statement.source.value;
504
+ if (importPath.startsWith(".") || importPath.startsWith("/")) {
505
+ try {
506
+ const resolvedPath = import_resolve3.default.sync(importPath, {
507
+ basedir: barrelFileDir,
508
+ extensions: RESOLVE_EXTENSIONS
509
+ });
510
+ if (!exportedModules.includes(resolvedPath)) {
511
+ exportedModules.push(resolvedPath);
512
+ }
513
+ } catch (e) {
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+ return exportedModules;
520
+ }
521
+ var noCycle = {
522
+ meta: {
523
+ type: "problem",
524
+ docs: {
525
+ description: "Detect circular dependencies and enforce relative imports in barrel files."
526
+ },
527
+ schema: [],
528
+ messages: {
529
+ CircularDependency: "Circular dependency detected: {{cyclePath}}. This creates a dependency cycle that can cause runtime errors and make code harder to maintain.",
530
+ BarrelInternalImportDisallowed: "Barrel files (index.ts) must use relative imports (./ or ../) for internal modules. Importing via barrel file or absolute path is not allowed. Use relative path: '{{relativePath}}'",
531
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config."
532
+ }
533
+ },
534
+ defaultOptions: [],
535
+ create(context) {
536
+ const absoluteCurrentFilePath = context.getFilename();
537
+ if (!importGraph.has(absoluteCurrentFilePath)) {
538
+ importGraph.set(absoluteCurrentFilePath, /* @__PURE__ */ new Set());
539
+ }
540
+ const currentImportsSet = importGraph.get(absoluteCurrentFilePath);
541
+ currentImportsSet.clear();
542
+ if (!fileToImports.has(absoluteCurrentFilePath)) {
543
+ fileToImports.set(absoluteCurrentFilePath, /* @__PURE__ */ new Set());
544
+ }
545
+ if (isBarrelFile(absoluteCurrentFilePath)) {
546
+ const barrelFileDir = import_path4.default.dirname(absoluteCurrentFilePath);
547
+ const sourceCode = context.getSourceCode();
548
+ const exportedModules = getExportedModulesFromBarrel(
549
+ barrelFileDir,
550
+ sourceCode
551
+ );
552
+ barrelExportsCache.set(absoluteCurrentFilePath, exportedModules);
553
+ }
554
+ function resolveImportPath(rawImportPath) {
555
+ try {
556
+ const aliasResult = Alias.resolvePath(
557
+ rawImportPath,
558
+ import_path4.default.dirname(absoluteCurrentFilePath)
559
+ );
560
+ if (aliasResult.type === "success") {
561
+ const resolvedPath = aliasResult.absolutePath;
562
+ try {
563
+ const fs = require("fs");
564
+ const stats = fs.statSync(resolvedPath);
565
+ if (stats.isDirectory()) {
566
+ return import_resolve3.default.sync("index", {
567
+ basedir: resolvedPath,
568
+ extensions: RESOLVE_EXTENSIONS
569
+ });
570
+ }
571
+ } catch (e) {
572
+ try {
573
+ return import_resolve3.default.sync(resolvedPath, {
574
+ basedir: import_path4.default.dirname(absoluteCurrentFilePath),
575
+ extensions: RESOLVE_EXTENSIONS
576
+ });
577
+ } catch (e2) {
578
+ return resolvedPath;
579
+ }
580
+ }
581
+ return resolvedPath;
582
+ } else {
583
+ if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/") || rawImportPath.includes("/node_modules/")) {
584
+ return null;
585
+ }
586
+ return import_resolve3.default.sync(rawImportPath, {
587
+ basedir: import_path4.default.dirname(absoluteCurrentFilePath),
588
+ extensions: RESOLVE_EXTENSIONS
589
+ });
590
+ }
591
+ } catch (e) {
592
+ return null;
593
+ }
594
+ }
595
+ function checker(node) {
596
+ if (!node.source) {
597
+ return;
598
+ }
599
+ const rawImportPath = node.source.value;
600
+ const absoluteImportPath = resolveImportPath(rawImportPath);
601
+ if (!absoluteImportPath) {
602
+ return;
603
+ }
604
+ if (isBarrelFile(absoluteCurrentFilePath)) {
605
+ const barrelFileDir = import_path4.default.dirname(absoluteCurrentFilePath);
606
+ const isInternalModule = absoluteImportPath.startsWith(barrelFileDir + import_path4.default.sep) || absoluteImportPath === barrelFileDir;
607
+ if (isInternalModule) {
608
+ if (!rawImportPath.startsWith("./") && !rawImportPath.startsWith("../")) {
609
+ const relativePath = import_path4.default.relative(
610
+ barrelFileDir,
611
+ absoluteImportPath
612
+ );
613
+ const suggestedRelativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
614
+ context.report({
615
+ node,
616
+ messageId: "BarrelInternalImportDisallowed",
617
+ data: {
618
+ relativePath: suggestedRelativePath.replace(/\\/g, "/")
619
+ }
620
+ });
621
+ return;
622
+ }
623
+ }
624
+ }
625
+ const isBarrelImportingInternal = isBarrelFile(absoluteCurrentFilePath) && absoluteImportPath.startsWith(
626
+ import_path4.default.dirname(absoluteCurrentFilePath) + import_path4.default.sep
627
+ );
628
+ const currentImports = importGraph.get(absoluteCurrentFilePath);
629
+ if (currentImports) {
630
+ currentImports.add(absoluteImportPath);
631
+ if (isBarrelFile(absoluteImportPath)) {
632
+ const exportedModules = barrelExportsCache.get(absoluteImportPath) || [];
633
+ for (const exportedModule of exportedModules) {
634
+ if (exportedModule !== absoluteCurrentFilePath) {
635
+ currentImports.add(exportedModule);
636
+ const exportedModuleImports = importGraph.get(exportedModule);
637
+ if (exportedModuleImports && exportedModuleImports.has(absoluteCurrentFilePath)) {
638
+ const cyclePath = `${absoluteCurrentFilePath} \u2192 ${absoluteImportPath} \u2192 ${exportedModule} \u2192 ${absoluteImportPath} \u2192 ${absoluteCurrentFilePath}`;
639
+ context.report({
640
+ node,
641
+ messageId: "CircularDependency",
642
+ data: {
643
+ cyclePath
644
+ }
645
+ });
646
+ return;
647
+ }
648
+ }
649
+ }
650
+ }
651
+ }
652
+ if (isBarrelImportingInternal) {
653
+ return;
654
+ }
655
+ const importedFileImports = importGraph.get(absoluteImportPath);
656
+ if (importedFileImports && importedFileImports.has(absoluteCurrentFilePath)) {
657
+ const cyclePath = `${absoluteCurrentFilePath} \u2192 ${absoluteImportPath} \u2192 ${absoluteCurrentFilePath}`;
658
+ context.report({
659
+ node,
660
+ messageId: "CircularDependency",
661
+ data: {
662
+ cyclePath
663
+ }
664
+ });
665
+ return;
666
+ }
667
+ const visited = /* @__PURE__ */ new Set();
668
+ const recStack = /* @__PURE__ */ new Set();
669
+ const cycle = detectCycle(absoluteCurrentFilePath, visited, recStack, []);
670
+ if (cycle && cycle.length > 0) {
671
+ const cyclePath = cycle.join(" \u2192 ");
672
+ context.report({
673
+ node,
674
+ messageId: "CircularDependency",
675
+ data: {
676
+ cyclePath
677
+ }
678
+ });
679
+ }
680
+ }
681
+ return {
682
+ ImportDeclaration(node) {
683
+ return checker(node);
684
+ },
685
+ ExportNamedDeclaration(node) {
686
+ return checker(node);
687
+ },
688
+ ExportAllDeclaration(node) {
689
+ return checker(node);
690
+ }
691
+ };
692
+ }
693
+ };
694
+
435
695
  // src/index.ts
436
696
  var rules = {
437
697
  "enforce-barrel-pattern": enforceBarrelPattern,
438
698
  "isolate-barrel-file": isolateBarrelFile,
439
- "no-wildcard": noWildcard
699
+ "no-wildcard": noWildcard,
700
+ "no-cycle": noCycle
440
701
  };
441
- var index_default = { rules };
442
- module.exports = { rules };
702
+ // Annotate the CommonJS export names for ESM import in node:
703
+ 0 && (module.exports = {
704
+ rules
705
+ });
package/dist/index.d.cts CHANGED
@@ -1,21 +1,20 @@
1
1
  import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
2
 
3
- declare const _default: {
4
- rules: {
5
- "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
6
- paths: string[];
7
- baseDir: string;
8
- }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
- "isolate-barrel-file": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "IsolatedBarrelImportDisallowed", {
10
- isolations: {
11
- path: string;
12
- allowedPaths: string[];
13
- }[];
14
- baseDir: string;
15
- globalAllowPaths: string[];
16
- }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
17
- "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
18
- };
3
+ declare const rules: {
4
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
5
+ paths: string[];
6
+ baseDir: string;
7
+ }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
8
+ "isolate-barrel-file": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "IsolatedBarrelImportDisallowed", {
9
+ isolations: {
10
+ path: string;
11
+ allowedPaths: string[];
12
+ }[];
13
+ baseDir: string;
14
+ globalAllowPaths: string[];
15
+ }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
16
+ "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
17
+ "no-cycle": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "CircularDependency" | "BarrelInternalImportDisallowed", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
19
18
  };
20
19
 
21
- export { _default as default };
20
+ export { rules };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,20 @@
1
1
  import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
2
 
3
- declare const _default: {
4
- rules: {
5
- "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
6
- paths: string[];
7
- baseDir: string;
8
- }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
- "isolate-barrel-file": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "IsolatedBarrelImportDisallowed", {
10
- isolations: {
11
- path: string;
12
- allowedPaths: string[];
13
- }[];
14
- baseDir: string;
15
- globalAllowPaths: string[];
16
- }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
17
- "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
18
- };
3
+ declare const rules: {
4
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
5
+ paths: string[];
6
+ baseDir: string;
7
+ }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
8
+ "isolate-barrel-file": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "IsolatedBarrelImportDisallowed", {
9
+ isolations: {
10
+ path: string;
11
+ allowedPaths: string[];
12
+ }[];
13
+ baseDir: string;
14
+ globalAllowPaths: string[];
15
+ }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
16
+ "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
17
+ "no-cycle": _typescript_eslint_utils_ts_eslint.RuleModule<"TransformedAliasResolveFailed" | "CircularDependency" | "BarrelInternalImportDisallowed", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
19
18
  };
20
19
 
21
- export { _default as default };
20
+ export { rules };
package/dist/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/rules/enforce-barrel-pattern.ts
2
9
  import "@typescript-eslint/types";
3
10
  import path2 from "path";
@@ -6,8 +13,8 @@ import resolve from "resolve";
6
13
  // src/utils/glob.ts
7
14
  import FastGlob from "fast-glob";
8
15
  var Glob = class {
9
- static resolvePath(path4, baseDir) {
10
- const globResult = FastGlob.sync(path4, {
16
+ static resolvePath(path5, baseDir) {
17
+ const globResult = FastGlob.sync(path5, {
11
18
  cwd: baseDir,
12
19
  onlyDirectories: true,
13
20
  absolute: true
@@ -70,33 +77,59 @@ var Alias = class {
70
77
  }
71
78
  };
72
79
 
73
- // src/rules/enforce-barrel-pattern.ts
80
+ // src/utils/constants.ts
74
81
  var BARREL_ENTRY_POINT_FILE_NAMES = [
75
82
  "index.ts",
83
+ // TypeScript entry
76
84
  "index.tsx",
85
+ // React TypeScript entry
77
86
  "index.js",
87
+ // JavaScript entry
78
88
  "index.jsx",
89
+ // React JavaScript entry
79
90
  "index.cjs",
80
- "index.mjs",
81
- "index.d.ts"
91
+ // CommonJS entry
92
+ "index.mjs"
93
+ // ES Module entry
82
94
  ];
83
95
  var RESOLVE_EXTENSIONS = [
84
96
  ".ts",
97
+ // TypeScript
85
98
  ".js",
99
+ // JavaScript
86
100
  ".tsx",
101
+ // React TypeScript
87
102
  ".jsx",
103
+ // React JavaScript
88
104
  ".json",
105
+ // JSON file
106
+ // Type declaration and transformed files
89
107
  ".d.js",
108
+ // JS file with type declarations (rare)
90
109
  ".d.ts",
110
+ // Type declaration file
111
+ // Various module systems
91
112
  ".mjs",
113
+ // ES Module
92
114
  ".cjs",
115
+ // CommonJS
116
+ // Module-related TypeScript extensions
93
117
  ".mts",
118
+ // ES Module TypeScript
94
119
  ".cts",
120
+ // CommonJS TypeScript
121
+ // Type declarations for module-related files
95
122
  ".d.mjs",
123
+ // Type declarations for ES Module
96
124
  ".d.cjs",
125
+ // Type declarations for CommonJS
97
126
  ".d.mts",
127
+ // Type declarations for ES Module TS
98
128
  ".d.cts"
129
+ // Type declarations for CommonJS TS
99
130
  ];
131
+
132
+ // src/rules/enforce-barrel-pattern.ts
100
133
  var enforceBarrelPattern = {
101
134
  meta: {
102
135
  type: "problem",
@@ -229,23 +262,6 @@ var enforceBarrelPattern = {
229
262
  import "@typescript-eslint/types";
230
263
  import path3 from "path";
231
264
  import resolve2 from "resolve";
232
- var RESOLVE_EXTENSIONS2 = [
233
- ".ts",
234
- ".js",
235
- ".tsx",
236
- ".jsx",
237
- ".json",
238
- ".d.js",
239
- ".d.ts",
240
- ".mjs",
241
- ".cjs",
242
- ".mts",
243
- ".cts",
244
- ".d.mjs",
245
- ".d.cjs",
246
- ".d.mts",
247
- ".d.cts"
248
- ];
249
265
  var isolateBarrelFile = {
250
266
  meta: {
251
267
  type: "problem",
@@ -280,13 +296,13 @@ var isolateBarrelFile = {
280
296
  create(context) {
281
297
  const option = context.options[0];
282
298
  const baseDir = option.baseDir;
283
- const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path4) => {
284
- return Glob.resolvePath(path4, baseDir);
299
+ const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path5) => {
300
+ return Glob.resolvePath(path5, baseDir);
285
301
  });
286
302
  const absoluteIsolations = option.isolations.flatMap((isolation) => {
287
303
  const isolationPaths = Glob.resolvePath(isolation.path, baseDir);
288
- const allowedPaths = isolation.allowedPaths.flatMap((path4) => {
289
- return Glob.resolvePath(path4, baseDir);
304
+ const allowedPaths = isolation.allowedPaths.flatMap((path5) => {
305
+ return Glob.resolvePath(path5, baseDir);
290
306
  });
291
307
  return isolationPaths.map((isolationPath) => ({
292
308
  isolationPath,
@@ -298,6 +314,9 @@ var isolateBarrelFile = {
298
314
  //check only import declaration(ESM)
299
315
  ImportDeclaration(node) {
300
316
  const rawImportPath = node.source.value;
317
+ if (rawImportPath.includes("/node_modules/")) {
318
+ return;
319
+ }
301
320
  const absoluteCurrentFilePath = context.getFilename();
302
321
  let absoluteImportPath = null;
303
322
  try {
@@ -307,13 +326,16 @@ var isolateBarrelFile = {
307
326
  );
308
327
  if (aliasResult.type === "success") {
309
328
  absoluteImportPath = aliasResult.absolutePath;
329
+ if (absoluteImportPath.includes("/node_modules/")) {
330
+ return;
331
+ }
310
332
  } else {
311
- if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/") || rawImportPath.includes("/node_modules/")) {
333
+ if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/")) {
312
334
  return;
313
335
  }
314
336
  absoluteImportPath = resolve2.sync(rawImportPath, {
315
337
  basedir: path3.dirname(absoluteCurrentFilePath),
316
- extensions: RESOLVE_EXTENSIONS2
338
+ extensions: RESOLVE_EXTENSIONS
317
339
  });
318
340
  }
319
341
  } catch (e) {
@@ -396,14 +418,258 @@ var noWildcard = {
396
418
  }
397
419
  };
398
420
 
421
+ // src/rules/no-cycle.ts
422
+ import "@typescript-eslint/types";
423
+ import path4 from "path";
424
+ import resolve3 from "resolve";
425
+ var importGraph = /* @__PURE__ */ new Map();
426
+ var fileToImports = /* @__PURE__ */ new Map();
427
+ var barrelExportsCache = /* @__PURE__ */ new Map();
428
+ function detectCycle(currentFile, visited, recStack, path5) {
429
+ visited.add(currentFile);
430
+ recStack.add(currentFile);
431
+ path5.push(currentFile);
432
+ const imports = importGraph.get(currentFile) || /* @__PURE__ */ new Set();
433
+ for (const importedFile of imports) {
434
+ if (!visited.has(importedFile)) {
435
+ const cycle = detectCycle(importedFile, visited, recStack, [...path5]);
436
+ if (cycle) {
437
+ return cycle;
438
+ }
439
+ } else if (recStack.has(importedFile)) {
440
+ const cycleStart = path5.indexOf(importedFile);
441
+ return [...path5.slice(cycleStart), importedFile];
442
+ }
443
+ }
444
+ recStack.delete(currentFile);
445
+ return null;
446
+ }
447
+ function isBarrelFile(filePath) {
448
+ const fileName = path4.basename(filePath);
449
+ return BARREL_ENTRY_POINT_FILE_NAMES.includes(
450
+ fileName
451
+ );
452
+ }
453
+ function getExportedModulesFromBarrel(barrelFileDir, sourceCode) {
454
+ const exportedModules = [];
455
+ const ast = sourceCode.ast;
456
+ for (const statement of ast.body) {
457
+ if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") {
458
+ if (statement.source) {
459
+ const exportPath = statement.source.value;
460
+ try {
461
+ const resolvedPath = resolve3.sync(exportPath, {
462
+ basedir: barrelFileDir,
463
+ extensions: RESOLVE_EXTENSIONS
464
+ });
465
+ if (!exportedModules.includes(resolvedPath)) {
466
+ exportedModules.push(resolvedPath);
467
+ }
468
+ } catch (e) {
469
+ }
470
+ }
471
+ }
472
+ if (statement.type === "ImportDeclaration") {
473
+ if (statement.source) {
474
+ const importPath = statement.source.value;
475
+ if (importPath.startsWith(".") || importPath.startsWith("/")) {
476
+ try {
477
+ const resolvedPath = resolve3.sync(importPath, {
478
+ basedir: barrelFileDir,
479
+ extensions: RESOLVE_EXTENSIONS
480
+ });
481
+ if (!exportedModules.includes(resolvedPath)) {
482
+ exportedModules.push(resolvedPath);
483
+ }
484
+ } catch (e) {
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+ return exportedModules;
491
+ }
492
+ var noCycle = {
493
+ meta: {
494
+ type: "problem",
495
+ docs: {
496
+ description: "Detect circular dependencies and enforce relative imports in barrel files."
497
+ },
498
+ schema: [],
499
+ messages: {
500
+ CircularDependency: "Circular dependency detected: {{cyclePath}}. This creates a dependency cycle that can cause runtime errors and make code harder to maintain.",
501
+ BarrelInternalImportDisallowed: "Barrel files (index.ts) must use relative imports (./ or ../) for internal modules. Importing via barrel file or absolute path is not allowed. Use relative path: '{{relativePath}}'",
502
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config."
503
+ }
504
+ },
505
+ defaultOptions: [],
506
+ create(context) {
507
+ const absoluteCurrentFilePath = context.getFilename();
508
+ if (!importGraph.has(absoluteCurrentFilePath)) {
509
+ importGraph.set(absoluteCurrentFilePath, /* @__PURE__ */ new Set());
510
+ }
511
+ const currentImportsSet = importGraph.get(absoluteCurrentFilePath);
512
+ currentImportsSet.clear();
513
+ if (!fileToImports.has(absoluteCurrentFilePath)) {
514
+ fileToImports.set(absoluteCurrentFilePath, /* @__PURE__ */ new Set());
515
+ }
516
+ if (isBarrelFile(absoluteCurrentFilePath)) {
517
+ const barrelFileDir = path4.dirname(absoluteCurrentFilePath);
518
+ const sourceCode = context.getSourceCode();
519
+ const exportedModules = getExportedModulesFromBarrel(
520
+ barrelFileDir,
521
+ sourceCode
522
+ );
523
+ barrelExportsCache.set(absoluteCurrentFilePath, exportedModules);
524
+ }
525
+ function resolveImportPath(rawImportPath) {
526
+ try {
527
+ const aliasResult = Alias.resolvePath(
528
+ rawImportPath,
529
+ path4.dirname(absoluteCurrentFilePath)
530
+ );
531
+ if (aliasResult.type === "success") {
532
+ const resolvedPath = aliasResult.absolutePath;
533
+ try {
534
+ const fs = __require("fs");
535
+ const stats = fs.statSync(resolvedPath);
536
+ if (stats.isDirectory()) {
537
+ return resolve3.sync("index", {
538
+ basedir: resolvedPath,
539
+ extensions: RESOLVE_EXTENSIONS
540
+ });
541
+ }
542
+ } catch (e) {
543
+ try {
544
+ return resolve3.sync(resolvedPath, {
545
+ basedir: path4.dirname(absoluteCurrentFilePath),
546
+ extensions: RESOLVE_EXTENSIONS
547
+ });
548
+ } catch (e2) {
549
+ return resolvedPath;
550
+ }
551
+ }
552
+ return resolvedPath;
553
+ } else {
554
+ if (!rawImportPath.startsWith(".") && !rawImportPath.startsWith("/") || rawImportPath.includes("/node_modules/")) {
555
+ return null;
556
+ }
557
+ return resolve3.sync(rawImportPath, {
558
+ basedir: path4.dirname(absoluteCurrentFilePath),
559
+ extensions: RESOLVE_EXTENSIONS
560
+ });
561
+ }
562
+ } catch (e) {
563
+ return null;
564
+ }
565
+ }
566
+ function checker(node) {
567
+ if (!node.source) {
568
+ return;
569
+ }
570
+ const rawImportPath = node.source.value;
571
+ const absoluteImportPath = resolveImportPath(rawImportPath);
572
+ if (!absoluteImportPath) {
573
+ return;
574
+ }
575
+ if (isBarrelFile(absoluteCurrentFilePath)) {
576
+ const barrelFileDir = path4.dirname(absoluteCurrentFilePath);
577
+ const isInternalModule = absoluteImportPath.startsWith(barrelFileDir + path4.sep) || absoluteImportPath === barrelFileDir;
578
+ if (isInternalModule) {
579
+ if (!rawImportPath.startsWith("./") && !rawImportPath.startsWith("../")) {
580
+ const relativePath = path4.relative(
581
+ barrelFileDir,
582
+ absoluteImportPath
583
+ );
584
+ const suggestedRelativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
585
+ context.report({
586
+ node,
587
+ messageId: "BarrelInternalImportDisallowed",
588
+ data: {
589
+ relativePath: suggestedRelativePath.replace(/\\/g, "/")
590
+ }
591
+ });
592
+ return;
593
+ }
594
+ }
595
+ }
596
+ const isBarrelImportingInternal = isBarrelFile(absoluteCurrentFilePath) && absoluteImportPath.startsWith(
597
+ path4.dirname(absoluteCurrentFilePath) + path4.sep
598
+ );
599
+ const currentImports = importGraph.get(absoluteCurrentFilePath);
600
+ if (currentImports) {
601
+ currentImports.add(absoluteImportPath);
602
+ if (isBarrelFile(absoluteImportPath)) {
603
+ const exportedModules = barrelExportsCache.get(absoluteImportPath) || [];
604
+ for (const exportedModule of exportedModules) {
605
+ if (exportedModule !== absoluteCurrentFilePath) {
606
+ currentImports.add(exportedModule);
607
+ const exportedModuleImports = importGraph.get(exportedModule);
608
+ if (exportedModuleImports && exportedModuleImports.has(absoluteCurrentFilePath)) {
609
+ const cyclePath = `${absoluteCurrentFilePath} \u2192 ${absoluteImportPath} \u2192 ${exportedModule} \u2192 ${absoluteImportPath} \u2192 ${absoluteCurrentFilePath}`;
610
+ context.report({
611
+ node,
612
+ messageId: "CircularDependency",
613
+ data: {
614
+ cyclePath
615
+ }
616
+ });
617
+ return;
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
623
+ if (isBarrelImportingInternal) {
624
+ return;
625
+ }
626
+ const importedFileImports = importGraph.get(absoluteImportPath);
627
+ if (importedFileImports && importedFileImports.has(absoluteCurrentFilePath)) {
628
+ const cyclePath = `${absoluteCurrentFilePath} \u2192 ${absoluteImportPath} \u2192 ${absoluteCurrentFilePath}`;
629
+ context.report({
630
+ node,
631
+ messageId: "CircularDependency",
632
+ data: {
633
+ cyclePath
634
+ }
635
+ });
636
+ return;
637
+ }
638
+ const visited = /* @__PURE__ */ new Set();
639
+ const recStack = /* @__PURE__ */ new Set();
640
+ const cycle = detectCycle(absoluteCurrentFilePath, visited, recStack, []);
641
+ if (cycle && cycle.length > 0) {
642
+ const cyclePath = cycle.join(" \u2192 ");
643
+ context.report({
644
+ node,
645
+ messageId: "CircularDependency",
646
+ data: {
647
+ cyclePath
648
+ }
649
+ });
650
+ }
651
+ }
652
+ return {
653
+ ImportDeclaration(node) {
654
+ return checker(node);
655
+ },
656
+ ExportNamedDeclaration(node) {
657
+ return checker(node);
658
+ },
659
+ ExportAllDeclaration(node) {
660
+ return checker(node);
661
+ }
662
+ };
663
+ }
664
+ };
665
+
399
666
  // src/index.ts
400
667
  var rules = {
401
668
  "enforce-barrel-pattern": enforceBarrelPattern,
402
669
  "isolate-barrel-file": isolateBarrelFile,
403
- "no-wildcard": noWildcard
670
+ "no-wildcard": noWildcard,
671
+ "no-cycle": noCycle
404
672
  };
405
- var index_default = { rules };
406
- module.exports = { rules };
407
673
  export {
408
- index_default as default
674
+ rules
409
675
  };
package/package.json CHANGED
@@ -25,32 +25,32 @@
25
25
  "isolated barrel module",
26
26
  "no-wildcard"
27
27
  ],
28
- "version": "1.4.0",
28
+ "version": "1.4.3",
29
29
  "type": "module",
30
30
  "main": "dist/index.cjs",
31
31
  "module": "dist/index.js",
32
32
  "types": "dist/index.d.ts",
33
33
  "dependencies": {
34
- "@types/jest": "^30.0.0",
35
- "@typescript-eslint/parser": "^8.46.2",
36
- "@typescript-eslint/rule-tester": "^8.46.2",
37
34
  "@typescript-eslint/types": "^8.46.2",
38
35
  "@typescript-eslint/utils": "^8.36.0",
39
36
  "fast-glob": "^3.3.3",
40
- "jest": "^30.2.0",
41
37
  "resolve": "^1.22.10",
42
- "ts-jest": "^29.4.5",
43
38
  "tsconfig-paths": "^4.2.0"
44
39
  },
45
40
  "devDependencies": {
41
+ "jest": "^30.2.0",
46
42
  "@types/node": "^24.0.13",
47
43
  "@types/resolve": "^1.20.6",
48
44
  "tsup": "^8.5.0",
49
- "typescript": "~5.8.3"
45
+ "typescript": "~5.8.3",
46
+ "@types/jest": "^30.0.0",
47
+ "@typescript-eslint/parser": "^8.46.2",
48
+ "@typescript-eslint/rule-tester": "^8.46.2",
49
+ "ts-jest": "^29.4.5"
50
50
  },
51
51
  "scripts": {
52
52
  "lint": "eslint src/**/*.ts",
53
- "build": "pnpm run test && tsup src/index.ts --dts --format cjs,esm",
53
+ "build": "pnpm run test && tsup",
54
54
  "type-check": "tsc --noEmit",
55
55
  "test": "jest",
56
56
  "test:watch": "jest --watch",