eslint-plugin-barrel-rules 1.1.1 → 1.1.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.1.1-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.1.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>
@@ -28,6 +28,10 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
28
28
  내부 파일을 직접 import하는 것을 차단하여
29
29
  **모듈화, 추상화, 유지보수성, 확장성**을 극대화합니다.
30
30
 
31
+ > 💡 Tip:
32
+ > 코드 품질을 더욱 강화하고 싶다면, 이 플러그인과 함께 [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import)의 `no-cycle` 룰을 사용하는 것을 추천합니다.
33
+ > 이를 통해 프로젝트 내의 순환 참조(Import Cycle)도 효과적으로 감지하고 방지할 수 있습니다.
34
+
31
35
  ---
32
36
 
33
37
  ## 지원 환경
@@ -48,6 +52,17 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
48
52
  (예: `import ... from "../domains/foo"`는 허용,
49
53
  `import ... from "../domains/foo/components/Bar"`는 차단)
50
54
 
55
+ - **Isolation Barrel Module**
56
+ 지정한 barrel path 외부의 모듈이 내부 파일을 직접 import하지 못하도록 막을 수 있습니다.
57
+ `isolated: true` 옵션을 사용하면 같은 barrel path 내부에서는 자유롭게 import가 가능하고,
58
+ 외부에서는 해당 barrel path로의 import가 모두 차단됩니다. (barrel(index) 파일을 통한 접근도 불가)
59
+ 만약 특정 공유 import 경로만 허용하고 싶다면 `allowedImportPaths` 옵션을 사용할 수 있습니다.
60
+ 이를 통해 각 모듈의 경계를 엄격하게 보호하고, 모듈의 독립성을 유지할 수 있습니다.
61
+
62
+ - **와일드카드 import/export 방지**
63
+ `import * as foo from "module"` 또는 `export * from "./module"`과 같은 와일드카드(네임스페이스) import/export를 금지합니다.
64
+ 명시적인 이름 기반 import/export만 허용하여 트리쉐이킹과 코드 명확성을 높입니다.
65
+
51
66
  - **고성능 glob 매칭**
52
67
  `src/domains/*`처럼 glob 패턴으로 여러 디렉토리 지정 가능
53
68
 
@@ -56,16 +71,22 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
56
71
  ## 규칙(Rules)
57
72
 
58
73
  1. **enforce-barrel-pattern**
59
- 모듈 임포트배럴 패턴(Barrel Pattern)을 강제합니다.
60
- 지정한 배럴 파일을 통해서만 임포트가 가능하며, 내부 모듈에 직접 접근하는 것을 방지합니다.
74
+ 모듈 importbarrel 패턴을 강제합니다.
75
+ 지정한 barrel 파일(예: index.ts)로만 import를 허용하고, 내부 모듈에 대한 직접 접근을 차단합니다.
76
+ `isolated: true` 옵션을 사용하면 같은 barrel path 내부 파일끼리만 import가 가능하며, 외부에서의 import는 barrel 파일을 통한 접근도 모두 차단됩니다.
77
+ `allowedImportPaths` 옵션을 사용하면 특정 공유 import 경로만 예외적으로 허용할 수 있습니다.
61
78
 
62
79
  - **옵션:**
63
- - `paths`: 배럴 패턴으로 보호할 디렉토리 경로(`baseDir` 기준 상대경로)
64
- - `baseDir` (선택): `paths` 해석 기준이 되는 디렉토리. 기본값은 ESLint 실행 위치입니다.
80
+ - `paths`: barrel 패턴을 적용할 디렉토리 목록(`baseDir` 기준 상대경로)
81
+ - `baseDir` (선택): `paths` 기준이 되는 베이스 디렉토리 (기본값: ESLint 실행 디렉토리)
82
+ - `isolated` (선택): `true`일 경우, barrel path 외부에서의 모든 import를 차단합니다(barrel 파일 통한 접근 포함). 같은 barrel path 내부 또는 `allowedImportPaths`만 허용.
83
+ - `allowedImportPaths` (선택): isolation 모드에서도 직접 import를 허용할 경로 배열
65
84
 
66
85
  2. **no-wildcard**
67
- `import * as foo from "module"`, `export * from "./module"`과 같은 와일드카드(네임스페이스) import/export를 금지합니다.
68
- 트리쉐이킹 코드 명확성을 위해 반드시 개별(named) import/export만 허용합니다.
86
+ `import * as foo from "module"` 또는 `export * from "./module"`과 같은 와일드카드(네임스페이스) import/export를 금지합니다.
87
+ `enforce-barrel-pattern` 룰과 함께 사용하는 것을 적극 추천합니다.
88
+ 두 룰을 함께 적용하면 모듈 경계를 엄격하게 지킬 수 있을 뿐만 아니라,
89
+ 트리쉐이킹을 통한 성능 향상과 코드 추적 및 유지보수의 용이성까지 모두 얻을 수 있습니다.
69
90
 
70
91
  ---
71
92
 
@@ -100,6 +121,11 @@ module.exports = {
100
121
  // (옵션) 설정하지 않으면 기본값은 ESLint를 실행한 위치(작업 디렉토리)입니다.
101
122
  // 예: `npx eslint .`처럼 실행하면, 실행 시점의 현재 디렉토리가 기본값이 됩니다.
102
123
  baseDir: __dirname,
124
+ // isolation 모드 활성화: barrel path 외부에서의 모든 import를 차단합니다.
125
+ isolated: true,
126
+ // "shared" 디렉토리만 직접 import를 허용합니다.
127
+ // 필요에 따라 이 배열에 "node_modules/*" 등 원하는 경로를 자유롭게 추가할 수 있습니다.
128
+ allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
103
129
  },
104
130
  ],
105
131
  // import * 또는 export * 금지
@@ -149,6 +175,11 @@ export default tseslint.config([
149
175
  // (옵션) 설정하지 않으면 기본값은 ESLint를 실행한 위치(작업 디렉토리)입니다.
150
176
  // 예: `npx eslint .`처럼 실행하면, 실행 시점의 현재 디렉토리가 기본값이 됩니다.
151
177
  baseDir: __dirname,
178
+ // isolation 모드 활성화: barrel path 외부에서의 모든 import를 차단합니다.
179
+ isolated: true,
180
+ // "shared" 디렉토리만 직접 import를 허용합니다.
181
+ // 필요에 따라 이 배열에 "node_modules/*" 등 원하는 경로를 자유롭게 추가할 수 있습니다.
182
+ allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
152
183
  },
153
184
  ],
154
185
  // import * 또는 export * 금지
@@ -181,6 +212,7 @@ import { Test } from "../domains/foo";
181
212
  - **번들 플러그인(플러그인 내 모든 기능 통합)** (OK)
182
213
  - **잘못된 경로 설정 검증 기능** (OK)
183
214
  - **와일드카드 import/export 제한 규칙** (OK)
215
+ - **지정한 Barrel 경로 격리** (OK)
184
216
 
185
217
  ---
186
218
 
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.1.1-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.1.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>
@@ -31,6 +31,10 @@ internal implementation details must only be accessed via the directory’s **in
31
31
  Direct imports from internal files are blocked, maximizing
32
32
  **modularity, abstraction, maintainability, and scalability**.
33
33
 
34
+ > 💡 Tip:
35
+ > For even stronger code quality, we recommend using the `no-cycle` rule from [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) together with this plugin.
36
+ > This allows you to detect and prevent circular dependencies (import cycles) in your project.
37
+
34
38
  ---
35
39
 
36
40
  ## Supports
@@ -51,6 +55,17 @@ Direct imports from internal files are blocked, maximizing
51
55
  (e.g., `import ... from "../domains/foo"` is allowed,
52
56
  but `import ... from "../domains/foo/components/Bar"` is blocked)
53
57
 
58
+ - **Isolation Barrel Module**
59
+ You can prevent modules outside the specified barrel path from directly importing internal files.
60
+ By enabling `isolated: true`, only files within the same barrel path can freely import each other.
61
+ Any import from outside the enforced barrel path is completely blocked, even if it tries to import via the barrel (index) file.
62
+ If you want to allow specific shared imports, you can use the `allowedImportPaths` option.
63
+ This helps you strictly protect your module boundaries and keep each module truly independent.
64
+
65
+ - **Prevent Wildcard Import/Export**
66
+ Disallows wildcard (namespace) imports and exports such as `import * as foo from "module"` or `export * from "./module"`.
67
+ This enforces the use of named imports/exports for better tree-shaking and code clarity.
68
+
54
69
  - **High-performance glob matching**
55
70
  Specify multiple directories using glob patterns like `src/domains/*`
56
71
 
@@ -61,14 +76,20 @@ Direct imports from internal files are blocked, maximizing
61
76
  1. **enforce-barrel-pattern**
62
77
  Enforces the barrel pattern for module imports.
63
78
  Only allows imports from designated barrel files and prevents direct access to internal modules.
79
+ When `isolated: true` is set, only files within the same barrel path can import each other, and any import from outside the barrel path is completely blocked (even via the barrel file).
80
+ You can allow specific shared import paths by using the `allowedImportPaths` option.
64
81
 
65
82
  - **Options:**
66
83
  - `paths`: The directories to be protected by the barrel pattern (relative to `baseDir`).
67
84
  - `baseDir` (optional): The base directory for resolving `paths`. Defaults to the ESLint execution directory.
85
+ - `isolated` (optional): If `true`, blocks all imports from outside the barrel path, even via the barrel file. Only allows imports within the same barrel path or from `allowedImportPaths`.
86
+ - `allowedImportPaths` (optional): Array of paths that are allowed to be imported directly, even in isolation mode.
68
87
 
69
88
  2. **no-wildcard**
70
- Disallows wildcard (namespace) imports such as `import * as foo from "module"`, `export * from "./module"`
71
- Forces you to use named imports for better tree-shaking and code clarity.
89
+ Disallows wildcard (namespace) imports such as `import * as foo from "module"` or `export * from "./module"`.
90
+ We highly recommend enabling this rule together with the `enforce-barrel-pattern` rule.
91
+ Using both rules together not only enforces strict module boundaries,
92
+ but also improves performance through better tree-shaking and makes code tracing and maintenance much easier.
72
93
 
73
94
  ---
74
95
 
@@ -103,6 +124,11 @@ module.exports = {
103
124
  // Optional config. The default value is the directory where ESLint is executed.
104
125
  // For example, if you run `npx eslint .`, the default will be the current working directory at the time of execution.
105
126
  baseDir: __dirname,
127
+ // Enable isolation mode: block all imports from outside the barrel path
128
+ isolated: true,
129
+ // Allow direct imports only from the "shared" directory.
130
+ // You can customize this array as needed, e.g., add "node_modules/..." or any other path you want to allow.
131
+ allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
106
132
  },
107
133
  ],
108
134
  // Disallow wildcard (namespace) import/export.
@@ -152,6 +178,11 @@ export default tseslint.config([
152
178
  // Optional config. The default value is the directory where ESLint is executed.
153
179
  // For example, if you run `npx eslint .`, the default will be the current working directory at the time of execution.
154
180
  baseDir: __dirname,
181
+ // Enable isolation mode: block all imports from outside the barrel path
182
+ isolated: true,
183
+ // Allow direct imports only from the "shared" directory.
184
+ // You can customize this array as needed, e.g., add "node_modules/*" or any other path you want to allow.
185
+ allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
155
186
  },
156
187
  ],
157
188
  // Disallow wildcard (namespace) import/export.
@@ -188,6 +219,7 @@ import { Test } from "../domains/foo";
188
219
  (OK)
189
220
  - **Wrong Path Setup Validator** (OK)
190
221
  - **Wildcard Import/Export Protection Rule** (OK)
222
+ - **Isolation Barrel Module** (OK)
191
223
 
192
224
  ---
193
225
 
package/dist/index.cjs CHANGED
@@ -38,7 +38,45 @@ module.exports = __toCommonJS(index_exports);
38
38
  var import_utils = require("@typescript-eslint/utils");
39
39
  var import_path = __toESM(require("path"), 1);
40
40
  var import_resolve = __toESM(require("resolve"), 1);
41
+
42
+ // src/utils/glob.ts
41
43
  var import_fast_glob = __toESM(require("fast-glob"), 1);
44
+ var Glob = class {
45
+ static resolvePath(path2, baseDir) {
46
+ const globResult = import_fast_glob.default.sync(path2, {
47
+ cwd: baseDir,
48
+ onlyDirectories: true,
49
+ absolute: true
50
+ });
51
+ if (globResult.length === 0) {
52
+ throw new Error(
53
+ `[Glob] In baseDir: ${baseDir}, path: ${path2}, any directory was not found`
54
+ );
55
+ }
56
+ return globResult;
57
+ }
58
+ };
59
+
60
+ // src/rules/enforce-barrel-pattern.ts
61
+ var BARREL_ENTRY_POINT_FILE_NAMES = [
62
+ "index.ts",
63
+ "index.tsx",
64
+ "index.js",
65
+ "index.jsx",
66
+ "index.cjs",
67
+ "index.mjs",
68
+ "index.d.ts"
69
+ ];
70
+ var RESOLVE_EXTENSIONS = [
71
+ ".ts",
72
+ ".tsx",
73
+ ".js",
74
+ ".jsx",
75
+ ".json",
76
+ ".d.ts",
77
+ ".mjs",
78
+ ".cjs"
79
+ ];
42
80
  var enforceBarrelPattern = {
43
81
  meta: {
44
82
  type: "problem",
@@ -50,33 +88,36 @@ var enforceBarrelPattern = {
50
88
  type: "object",
51
89
  properties: {
52
90
  paths: { type: "array", items: { type: "string" } },
53
- baseDir: { type: "string" }
91
+ baseDir: { type: "string" },
92
+ isolated: { type: "boolean" },
93
+ allowedImportPaths: { type: "array", items: { type: "string" } }
54
94
  },
55
95
  required: ["paths"],
56
96
  additionalProperties: false
57
97
  }
58
98
  ],
59
99
  messages: {
60
- DirectImportDisallowed: "Please import from '{{matchedTargetPath}}'. Direct access to '{{rawImportPath}}' is not allowed. You must use the barrel pattern and only consume APIs exposed externally. This is to ensure encapsulation of internal logic and maintain module boundaries."
100
+ DirectImportDisallowed: "Please import from '{{matchedTargetPath}}'. Direct access to '{{rawImportPath}}' is not allowed. You must use the barrel pattern and only consume APIs exposed externally. This is to ensure encapsulation of internal logic and maintain module boundaries.",
101
+ IsolatedBarrelImportDisallowed: "This barrel file is isolated. external import is not allowed. if you want to import outside of the barrel file, please add 'allowedImportPaths' to the plugin options."
61
102
  }
62
103
  },
63
104
  //default options(baseDir is current working directory. almost user execute eslint in project root)
64
- defaultOptions: [{ paths: [], baseDir: process.cwd() }],
105
+ defaultOptions: [
106
+ {
107
+ paths: [],
108
+ baseDir: process.cwd(),
109
+ isolated: false,
110
+ allowedImportPaths: []
111
+ }
112
+ ],
65
113
  create(context) {
66
114
  const option = context.options[0];
67
115
  const baseDir = option.baseDir;
68
- const targetPaths = option.paths.flatMap((_path) => {
69
- const globResult = import_fast_glob.default.sync(_path, {
70
- cwd: baseDir,
71
- onlyDirectories: true,
72
- absolute: true
73
- });
74
- if (globResult.length === 0) {
75
- throw new Error(
76
- `[enforce-barrel-pattern] In baseDir: ${baseDir}, path: ${_path}, any directory was not found`
77
- );
78
- }
79
- return globResult;
116
+ const absoluteTargetPaths = option.paths.flatMap((_path) => {
117
+ return Glob.resolvePath(_path, baseDir);
118
+ });
119
+ const allowedImportPaths = option.allowedImportPaths.flatMap((_path) => {
120
+ return Glob.resolvePath(_path, baseDir);
80
121
  });
81
122
  return {
82
123
  //check only import declaration(ESM)
@@ -87,46 +128,69 @@ var enforceBarrelPattern = {
87
128
  try {
88
129
  absoluteImportPath = import_resolve.default.sync(rawImportPath, {
89
130
  basedir: import_path.default.dirname(absoluteCurrentFilePath),
90
- extensions: [
91
- ".ts",
92
- ".tsx",
93
- ".js",
94
- ".jsx",
95
- ".json",
96
- ".d.ts",
97
- ".mjs",
98
- ".cjs"
99
- ]
131
+ extensions: RESOLVE_EXTENSIONS
100
132
  });
101
133
  } catch (e) {
102
134
  return;
103
135
  }
104
- let matchedLatestTargetPath = null;
105
- const invalidDirectedImport = targetPaths.some((targetPath) => {
106
- const targetPathEntryPoints = [
107
- "index.ts",
108
- "index.tsx",
109
- "index.js",
110
- "index.jsx",
111
- "index.cjs",
112
- "index.mjs",
113
- "index.d.ts"
114
- ].map((entry) => import_path.default.resolve(targetPath, entry));
115
- const closedTargetPath = targetPath + "/";
116
- const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
117
- const importedOutsideOfTargetPath = !absoluteCurrentFilePath.startsWith(closedTargetPath) && absoluteImportPath.startsWith(closedTargetPath);
118
- const invalidImported = !targetPathEntryPointed && importedOutsideOfTargetPath;
119
- if (invalidImported) {
120
- matchedLatestTargetPath = targetPath;
136
+ {
137
+ let matchedLatestTargetPath = null;
138
+ const invalidDirectedImport = absoluteTargetPaths.some(
139
+ (absoluteTargetPath) => {
140
+ const targetPathEntryPoints = BARREL_ENTRY_POINT_FILE_NAMES.map(
141
+ (entry) => import_path.default.resolve(absoluteTargetPath, entry)
142
+ );
143
+ const closedTargetPath = absoluteTargetPath + "/";
144
+ const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
145
+ const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
146
+ const currentFileInEnforceBarrel = absoluteCurrentFilePath.startsWith(closedTargetPath);
147
+ const importedOutsideOfTargetPath = !currentFileInEnforceBarrel && importedEnforceBarrelFile;
148
+ const invalidImported = !targetPathEntryPointed && importedOutsideOfTargetPath;
149
+ if (invalidImported) {
150
+ matchedLatestTargetPath = absoluteTargetPath;
151
+ }
152
+ return invalidImported;
153
+ }
154
+ );
155
+ if (invalidDirectedImport) {
156
+ context.report({
157
+ node,
158
+ messageId: "DirectImportDisallowed",
159
+ data: {
160
+ rawImportPath,
161
+ matchedTargetPath: matchedLatestTargetPath
162
+ }
163
+ });
164
+ }
165
+ }
166
+ {
167
+ if (option.isolated) {
168
+ const currentFileInEnforceBarrel = absoluteTargetPaths.some(
169
+ (absoluteTargetPath) => {
170
+ const closedTargetPath = absoluteTargetPath + "/";
171
+ return absoluteCurrentFilePath.startsWith(closedTargetPath);
172
+ }
173
+ );
174
+ const sameBarrel = absoluteTargetPaths.some(
175
+ (absoluteTargetPath) => {
176
+ const closedTargetPath = absoluteTargetPath + "/";
177
+ const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
178
+ const currentFileInEnforceBarrel2 = absoluteCurrentFilePath.startsWith(closedTargetPath);
179
+ return importedEnforceBarrelFile && currentFileInEnforceBarrel2;
180
+ }
181
+ );
182
+ const allowedImportPath = allowedImportPaths.some(
183
+ (allowedImportPath2) => {
184
+ return absoluteImportPath.startsWith(allowedImportPath2);
185
+ }
186
+ );
187
+ if (!allowedImportPath && !sameBarrel && currentFileInEnforceBarrel) {
188
+ context.report({
189
+ node,
190
+ messageId: "IsolatedBarrelImportDisallowed"
191
+ });
192
+ }
121
193
  }
122
- return invalidImported;
123
- });
124
- if (invalidDirectedImport) {
125
- context.report({
126
- node,
127
- messageId: "DirectImportDisallowed",
128
- data: { rawImportPath, matchedTargetPath: matchedLatestTargetPath }
129
- });
130
194
  }
131
195
  }
132
196
  };
package/dist/index.d.cts CHANGED
@@ -2,9 +2,11 @@ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts
2
2
 
3
3
  declare const _default: {
4
4
  rules: {
5
- "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed", {
5
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "IsolatedBarrelImportDisallowed", {
6
6
  paths: string[];
7
7
  baseDir: string;
8
+ isolated: boolean;
9
+ allowedImportPaths: string[];
8
10
  }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
11
  "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
10
12
  };
package/dist/index.d.ts CHANGED
@@ -2,9 +2,11 @@ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts
2
2
 
3
3
  declare const _default: {
4
4
  rules: {
5
- "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed", {
5
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "IsolatedBarrelImportDisallowed", {
6
6
  paths: string[];
7
7
  baseDir: string;
8
+ isolated: boolean;
9
+ allowedImportPaths: string[];
8
10
  }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
11
  "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
10
12
  };
package/dist/index.js CHANGED
@@ -2,7 +2,45 @@
2
2
  import "@typescript-eslint/utils";
3
3
  import path from "path";
4
4
  import resolve from "resolve";
5
- import fastGlob from "fast-glob";
5
+
6
+ // src/utils/glob.ts
7
+ import FastGlob from "fast-glob";
8
+ var Glob = class {
9
+ static resolvePath(path2, baseDir) {
10
+ const globResult = FastGlob.sync(path2, {
11
+ cwd: baseDir,
12
+ onlyDirectories: true,
13
+ absolute: true
14
+ });
15
+ if (globResult.length === 0) {
16
+ throw new Error(
17
+ `[Glob] In baseDir: ${baseDir}, path: ${path2}, any directory was not found`
18
+ );
19
+ }
20
+ return globResult;
21
+ }
22
+ };
23
+
24
+ // src/rules/enforce-barrel-pattern.ts
25
+ var BARREL_ENTRY_POINT_FILE_NAMES = [
26
+ "index.ts",
27
+ "index.tsx",
28
+ "index.js",
29
+ "index.jsx",
30
+ "index.cjs",
31
+ "index.mjs",
32
+ "index.d.ts"
33
+ ];
34
+ var RESOLVE_EXTENSIONS = [
35
+ ".ts",
36
+ ".tsx",
37
+ ".js",
38
+ ".jsx",
39
+ ".json",
40
+ ".d.ts",
41
+ ".mjs",
42
+ ".cjs"
43
+ ];
6
44
  var enforceBarrelPattern = {
7
45
  meta: {
8
46
  type: "problem",
@@ -14,33 +52,36 @@ var enforceBarrelPattern = {
14
52
  type: "object",
15
53
  properties: {
16
54
  paths: { type: "array", items: { type: "string" } },
17
- baseDir: { type: "string" }
55
+ baseDir: { type: "string" },
56
+ isolated: { type: "boolean" },
57
+ allowedImportPaths: { type: "array", items: { type: "string" } }
18
58
  },
19
59
  required: ["paths"],
20
60
  additionalProperties: false
21
61
  }
22
62
  ],
23
63
  messages: {
24
- DirectImportDisallowed: "Please import from '{{matchedTargetPath}}'. Direct access to '{{rawImportPath}}' is not allowed. You must use the barrel pattern and only consume APIs exposed externally. This is to ensure encapsulation of internal logic and maintain module boundaries."
64
+ DirectImportDisallowed: "Please import from '{{matchedTargetPath}}'. Direct access to '{{rawImportPath}}' is not allowed. You must use the barrel pattern and only consume APIs exposed externally. This is to ensure encapsulation of internal logic and maintain module boundaries.",
65
+ IsolatedBarrelImportDisallowed: "This barrel file is isolated. external import is not allowed. if you want to import outside of the barrel file, please add 'allowedImportPaths' to the plugin options."
25
66
  }
26
67
  },
27
68
  //default options(baseDir is current working directory. almost user execute eslint in project root)
28
- defaultOptions: [{ paths: [], baseDir: process.cwd() }],
69
+ defaultOptions: [
70
+ {
71
+ paths: [],
72
+ baseDir: process.cwd(),
73
+ isolated: false,
74
+ allowedImportPaths: []
75
+ }
76
+ ],
29
77
  create(context) {
30
78
  const option = context.options[0];
31
79
  const baseDir = option.baseDir;
32
- const targetPaths = option.paths.flatMap((_path) => {
33
- const globResult = fastGlob.sync(_path, {
34
- cwd: baseDir,
35
- onlyDirectories: true,
36
- absolute: true
37
- });
38
- if (globResult.length === 0) {
39
- throw new Error(
40
- `[enforce-barrel-pattern] In baseDir: ${baseDir}, path: ${_path}, any directory was not found`
41
- );
42
- }
43
- return globResult;
80
+ const absoluteTargetPaths = option.paths.flatMap((_path) => {
81
+ return Glob.resolvePath(_path, baseDir);
82
+ });
83
+ const allowedImportPaths = option.allowedImportPaths.flatMap((_path) => {
84
+ return Glob.resolvePath(_path, baseDir);
44
85
  });
45
86
  return {
46
87
  //check only import declaration(ESM)
@@ -51,46 +92,69 @@ var enforceBarrelPattern = {
51
92
  try {
52
93
  absoluteImportPath = resolve.sync(rawImportPath, {
53
94
  basedir: path.dirname(absoluteCurrentFilePath),
54
- extensions: [
55
- ".ts",
56
- ".tsx",
57
- ".js",
58
- ".jsx",
59
- ".json",
60
- ".d.ts",
61
- ".mjs",
62
- ".cjs"
63
- ]
95
+ extensions: RESOLVE_EXTENSIONS
64
96
  });
65
97
  } catch (e) {
66
98
  return;
67
99
  }
68
- let matchedLatestTargetPath = null;
69
- const invalidDirectedImport = targetPaths.some((targetPath) => {
70
- const targetPathEntryPoints = [
71
- "index.ts",
72
- "index.tsx",
73
- "index.js",
74
- "index.jsx",
75
- "index.cjs",
76
- "index.mjs",
77
- "index.d.ts"
78
- ].map((entry) => path.resolve(targetPath, entry));
79
- const closedTargetPath = targetPath + "/";
80
- const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
81
- const importedOutsideOfTargetPath = !absoluteCurrentFilePath.startsWith(closedTargetPath) && absoluteImportPath.startsWith(closedTargetPath);
82
- const invalidImported = !targetPathEntryPointed && importedOutsideOfTargetPath;
83
- if (invalidImported) {
84
- matchedLatestTargetPath = targetPath;
100
+ {
101
+ let matchedLatestTargetPath = null;
102
+ const invalidDirectedImport = absoluteTargetPaths.some(
103
+ (absoluteTargetPath) => {
104
+ const targetPathEntryPoints = BARREL_ENTRY_POINT_FILE_NAMES.map(
105
+ (entry) => path.resolve(absoluteTargetPath, entry)
106
+ );
107
+ const closedTargetPath = absoluteTargetPath + "/";
108
+ const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
109
+ const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
110
+ const currentFileInEnforceBarrel = absoluteCurrentFilePath.startsWith(closedTargetPath);
111
+ const importedOutsideOfTargetPath = !currentFileInEnforceBarrel && importedEnforceBarrelFile;
112
+ const invalidImported = !targetPathEntryPointed && importedOutsideOfTargetPath;
113
+ if (invalidImported) {
114
+ matchedLatestTargetPath = absoluteTargetPath;
115
+ }
116
+ return invalidImported;
117
+ }
118
+ );
119
+ if (invalidDirectedImport) {
120
+ context.report({
121
+ node,
122
+ messageId: "DirectImportDisallowed",
123
+ data: {
124
+ rawImportPath,
125
+ matchedTargetPath: matchedLatestTargetPath
126
+ }
127
+ });
128
+ }
129
+ }
130
+ {
131
+ if (option.isolated) {
132
+ const currentFileInEnforceBarrel = absoluteTargetPaths.some(
133
+ (absoluteTargetPath) => {
134
+ const closedTargetPath = absoluteTargetPath + "/";
135
+ return absoluteCurrentFilePath.startsWith(closedTargetPath);
136
+ }
137
+ );
138
+ const sameBarrel = absoluteTargetPaths.some(
139
+ (absoluteTargetPath) => {
140
+ const closedTargetPath = absoluteTargetPath + "/";
141
+ const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
142
+ const currentFileInEnforceBarrel2 = absoluteCurrentFilePath.startsWith(closedTargetPath);
143
+ return importedEnforceBarrelFile && currentFileInEnforceBarrel2;
144
+ }
145
+ );
146
+ const allowedImportPath = allowedImportPaths.some(
147
+ (allowedImportPath2) => {
148
+ return absoluteImportPath.startsWith(allowedImportPath2);
149
+ }
150
+ );
151
+ if (!allowedImportPath && !sameBarrel && currentFileInEnforceBarrel) {
152
+ context.report({
153
+ node,
154
+ messageId: "IsolatedBarrelImportDisallowed"
155
+ });
156
+ }
85
157
  }
86
- return invalidImported;
87
- });
88
- if (invalidDirectedImport) {
89
- context.report({
90
- node,
91
- messageId: "DirectImportDisallowed",
92
- data: { rawImportPath, matchedTargetPath: matchedLatestTargetPath }
93
- });
94
158
  }
95
159
  }
96
160
  };
package/package.json CHANGED
@@ -21,9 +21,11 @@
21
21
  "barrel-module",
22
22
  "barrel pattern",
23
23
  "encapsulation directory",
24
- "enforce barrel pattern"
24
+ "enforce barrel pattern",
25
+ "isolated barrel module",
26
+ "no-wildcard"
25
27
  ],
26
- "version": "1.1.1",
28
+ "version": "1.1.3",
27
29
  "type": "module",
28
30
  "main": "dist/index.cjs",
29
31
  "module": "dist/index.js",