eslint-plugin-barrel-rules 1.2.0 → 1.3.0

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.2.0-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.3.0-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>
@@ -58,11 +58,7 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
58
58
  `import ... from "../domains/foo/components/Bar"`는 차단)
59
59
 
60
60
  - **Isolation Barrel Module**
61
- 지정한 barrel path 외부의 모듈이 내부 파일을 직접 import하지 못하도록 막을 수 있습니다.
62
- `isolated: true` 옵션을 사용하면 같은 barrel path 내부에서는 자유롭게 import가 가능하고,
63
- 외부에서는 해당 barrel path로의 import가 모두 차단됩니다. (barrel(index) 파일을 통한 접근도 불가)
64
- 만약 특정 공유 import 경로만 허용하고 싶다면 `allowedImportPaths` 옵션을 사용할 수 있습니다.
65
- 이를 통해 각 모듈의 경계를 엄격하게 보호하고, 모듈의 독립성을 유지할 수 있습니다.
61
+ 지정한 barrel path 외부의 모듈이 내부 파일을 직접 import하지 못하도록 막을 수 있습니다.
66
62
 
67
63
  - **와일드카드 import/export 방지**
68
64
  `import * as foo from "module"` 또는 `export * from "./module"`과 같은 와일드카드(네임스페이스) import/export를 금지합니다.
@@ -75,17 +71,13 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
75
71
 
76
72
  ## 규칙(Rules)
77
73
 
78
- 1. **enforce-barrel-pattern**
74
+ 1. **enforce-barrel-pattern** (isolate 옵션이 제거되었습니다.)
79
75
  모듈 import 시 barrel 패턴을 강제합니다.
80
76
  지정한 barrel 파일(예: index.ts)로만 import를 허용하고, 내부 모듈에 대한 직접 접근을 차단합니다.
81
- `isolated: true` 옵션을 사용하면 같은 barrel path 내부 파일끼리만 import가 가능하며, 외부에서의 import는 barrel 파일을 통한 접근도 모두 차단됩니다.
82
- `allowedImportPaths` 옵션을 사용하면 특정 공유 import 경로만 예외적으로 허용할 수 있습니다.
83
77
 
84
78
  - **옵션:**
85
79
  - `paths`: barrel 패턴을 적용할 디렉토리 목록(`baseDir` 기준 상대경로)
86
- - `baseDir` (선택): `paths` 기준이 되는 베이스 디렉토리 (기본값: ESLint 실행 디렉토리)
87
- - `isolated` (선택): `true`일 경우, barrel path 외부에서의 모든 import를 차단합니다(barrel 파일 통한 접근 포함). 같은 barrel path 내부 또는 `allowedImportPaths`만 허용.
88
- - `allowedImportPaths` (선택): isolation 모드에서도 직접 import를 허용할 경로 배열
80
+ - `baseDir`: `paths` 기준이 되는 베이스 디렉토리 (기본값: ESLint 실행 디렉토리)
89
81
 
90
82
  2. **no-wildcard**
91
83
  `import * as foo from "module"` 또는 `export * from "./module"`과 같은 와일드카드(네임스페이스) import/export를 금지합니다.
@@ -93,6 +85,14 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
93
85
  두 룰을 함께 적용하면 모듈 경계를 엄격하게 지킬 수 있을 뿐만 아니라,
94
86
  트리쉐이킹을 통한 성능 향상과 코드 추적 및 유지보수의 용이성까지 모두 얻을 수 있습니다.
95
87
 
88
+ 3. **isolate-barrel-file** (isolated 기능을 새로운 룰로 제작했습니다.)
89
+ 모듈 import 시 barrel 패턴을 강제합니다.
90
+ 지정한 barrel 파일(예: index.ts)로만 import를 허용하고, 내부 모듈에 대한 직접 접근을 차단합니다.
91
+ - **옵션:**
92
+ - `isolations(Array<{ path: string, allowedPaths: string[] }>)`: `path`와 `allowedPaths`로 구성된 isolation을 추가할 수 있습니다.
93
+ - `baseDir`: `paths` 기준이 되는 베이스 디렉토리 (기본값: ESLint 실행 디렉토리)
94
+ - `globalAllowedPaths` : 모든 isolations에 공통적으로 허용할 경로를 지정합니다(node_modules ... etc)
95
+
96
96
  ---
97
97
 
98
98
  ## 설치
@@ -118,23 +118,43 @@ module.exports = {
118
118
  parser: "@typescript-eslint/parser",
119
119
  plugins: ["@typescript-eslint", "barrel-rules"],
120
120
  rules: {
121
- "barrel-rules/enforce-barrel-pattern": [
122
- "error",
123
- {
124
- // 배럴 파일로 보호할 디렉토리의 경로입니다. baseDir을 기준으로 상대 경로로 설정합니다.
125
- paths: ["src/typescript/barrel/*", "src/javascript/barrel/*"],
126
- // (옵션) 설정하지 않으면 기본값은 ESLint를 실행한 위치(작업 디렉토리)입니다.
127
- // 예: `npx eslint .`처럼 실행하면, 실행 시점의 현재 디렉토리가 기본값이 됩니다.
128
- baseDir: __dirname,
129
- // isolation 모드 활성화: barrel path 외부에서의 모든 import를 차단합니다.
130
- isolated: true,
131
- // "shared" 디렉토리만 직접 import를 허용합니다.
132
- // 필요에 따라 배열에 "node_modules/*" 등 원하는 경로를 자유롭게 추가할 수 있습니다.
133
- allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
134
- },
135
- ],
136
- // import * 또는 export * 금지
137
- "barrel-rules/no-wildcard": ["error"],
121
+
122
+ //barrel-file 캡슐화
123
+ "barrel-rules/enforce-barrel-pattern": [
124
+ "error",
125
+ {
126
+ // encapsulation barrel file
127
+ paths: ["src/pages/*", "src/features/*", "src/entities/*"],
128
+ baseDir: __dirname,
129
+ },
130
+ ],
131
+
132
+ //barrel file내부에서 외부 모듈 사용 제한
133
+ "barrel-rules/isolate-barrel-file": [
134
+ "error",
135
+ {
136
+ //isolation options
137
+ isolations: [
138
+ {
139
+ path: "src/pages/*",
140
+ allowedPaths: ["src/features/*", "src/entities/*"],
141
+ },
142
+ {
143
+ path: "src/features/*",
144
+ allowedPaths: ["src/entities/*"],
145
+ },
146
+ {
147
+ path: "src/entities/*",
148
+ allowedPaths: [],
149
+ },
150
+ ],
151
+ baseDir: __dirname,
152
+ globalAllowPaths: ["src/shares/*", "node_modules/*"],
153
+ },
154
+ ],
155
+
156
+ // "*"를 사용한 불 분명한 import/export 방지
157
+ "barrel-rules/no-wildcard": ["error"],
138
158
  },
139
159
  };
140
160
  ```
@@ -172,22 +192,41 @@ export default tseslint.config([
172
192
  },
173
193
  // barrel-rules에 대한 설정만 추가하면 됩니다.
174
194
  rules: {
195
+ //barrel-file 캡슐화
175
196
  "barrel-rules/enforce-barrel-pattern": [
176
197
  "error",
177
198
  {
178
- // 배럴 파일로 보호할 디렉토리의 경로입니다. baseDir을 기준으로 상대 경로로 설정합니다.
179
- paths: ["src/typescript/barrel/*"],
180
- // (옵션) 설정하지 않으면 기본값은 ESLint를 실행한 위치(작업 디렉토리)입니다.
181
- // 예: `npx eslint .`처럼 실행하면, 실행 시점의 현재 디렉토리가 기본값이 됩니다.
199
+ // encapsulation barrel file
200
+ paths: ["src/pages/*", "src/features/*", "src/entities/*"],
182
201
  baseDir: __dirname,
183
- // isolation 모드 활성화: barrel path 외부에서의 모든 import를 차단합니다.
184
- isolated: true,
185
- // "shared" 디렉토리만 직접 import를 허용합니다.
186
- // 필요에 따라 이 배열에 "node_modules/*" 등 원하는 경로를 자유롭게 추가할 수 있습니다.
187
- allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
188
202
  },
189
203
  ],
190
- // import * 또는 export * 금지
204
+
205
+ //barrel file내부에서 외부 모듈 사용 제한
206
+ "barrel-rules/isolate-barrel-file": [
207
+ "error",
208
+ {
209
+ //isolation options
210
+ isolations: [
211
+ {
212
+ path: "src/pages/*",
213
+ allowedPaths: ["src/features/*", "src/entities/*"],
214
+ },
215
+ {
216
+ path: "src/features/*",
217
+ allowedPaths: ["src/entities/*"],
218
+ },
219
+ {
220
+ path: "src/entities/*",
221
+ allowedPaths: [],
222
+ },
223
+ ],
224
+ baseDir: __dirname,
225
+ globalAllowPaths: ["src/shares/*", "node_modules/*"],
226
+ },
227
+ ],
228
+
229
+ // "*"를 사용한 불 분명한 import/export 방지
191
230
  "barrel-rules/no-wildcard": ["error"],
192
231
  },
193
232
  },
@@ -218,8 +257,6 @@ file(src / domains / foo / index.ts);
218
257
  // ❌ 격리된 barrel로의 외부 import 차단 (alias 사용해도 차단)
219
258
  // barrel 외부에서 접근 (bar의 경로는 src/domains/bar/)
220
259
  import { Test } from "@domains/bar/components/Test";
221
- // 또는
222
- import { Test } from "../domains/bar";
223
260
 
224
261
  // ✅ 같은 barrel 내부에서의 import는 허용 (alias 지원)
225
262
  import { Hook } from "@domains/foo/hooks/useTest"; // 같은 barrel 내부에서
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.2.0-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.3.0-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>
@@ -60,10 +60,9 @@ Direct imports from internal files are blocked, maximizing
60
60
 
61
61
  - **Isolation Barrel Module**
62
62
  You can prevent modules outside the specified barrel path from directly importing internal files.
63
- By enabling `isolated: true`, only files within the same barrel path can freely import each other.
63
+ By enabling `isolate-barrel-file`, only files within the same barrel path can freely import each other.
64
64
  Any import from outside the enforced barrel path is completely blocked, even if it tries to import via the barrel (index) file.
65
- If you want to allow specific shared imports, you can use the `allowedImportPaths` option.
66
- This helps you strictly protect your module boundaries and keep each module truly independent.
65
+ If you want to allow specific shared imports, you can use the `allowedPaths` or `globalAllowedPaths` option.
67
66
 
68
67
  - **Prevent Wildcard Import/Export**
69
68
  Disallows wildcard (namespace) imports and exports such as `import * as foo from "module"` or `export * from "./module"`.
@@ -76,17 +75,13 @@ Direct imports from internal files are blocked, maximizing
76
75
 
77
76
  ## Rules
78
77
 
79
- 1. **enforce-barrel-pattern**
78
+ 1. **enforce-barrel-pattern** (Isolation is exracted as new rule :))
80
79
  Enforces the barrel pattern for module imports.
81
80
  Only allows imports from designated barrel files and prevents direct access to internal modules.
82
- 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).
83
- You can allow specific shared import paths by using the `allowedImportPaths` option.
84
81
 
85
82
  - **Options:**
86
83
  - `paths`: The directories to be protected by the barrel pattern (relative to `baseDir`).
87
- - `baseDir` (optional): The base directory for resolving `paths`. Defaults to the ESLint execution directory.
88
- - `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`.
89
- - `allowedImportPaths` (optional): Array of paths that are allowed to be imported directly, even in isolation mode.
84
+ - `baseDir`: The base directory for resolving `paths`. Defaults to the ESLint execution directory.
90
85
 
91
86
  2. **no-wildcard**
92
87
  Disallows wildcard (namespace) imports such as `import * as foo from "module"` or `export * from "./module"`.
@@ -94,6 +89,14 @@ Direct imports from internal files are blocked, maximizing
94
89
  Using both rules together not only enforces strict module boundaries,
95
90
  but also improves performance through better tree-shaking and makes code tracing and maintenance much easier.
96
91
 
92
+ 3. **isolate-barrel-file** (New Rules!!!)
93
+ 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).
94
+ You can allow specific shared import paths by using the `allowedPaths` option.
95
+ - **Options:**
96
+ - `isolations(Array<{ path: string, allowedPaths: string[] }>)`: If you set isolation path, blocks all imports from outside the barrel path, even via the barrel file. Only allows imports within the same barrel path or from `allowedPaths` or `globalAllowedPaths`.
97
+ - `baseDir`: The base directory for resolving `paths`. Defaults to the ESLint execution directory.
98
+ - `globalAllowedPaths` : Array of paths that are allowed to be imported directly, even in isolation mode.
99
+
97
100
  ---
98
101
 
99
102
  ## Install
@@ -119,23 +122,44 @@ module.exports = {
119
122
  parser: "@typescript-eslint/parser",
120
123
  plugins: ["@typescript-eslint", "barrel-rules"],
121
124
  rules: {
122
- "barrel-rules/enforce-barrel-pattern": [
123
- "error",
124
- {
125
- // The path to the directory that should be protected by using a barrel file. This path is relative to baseDir.
126
- paths: ["src/typescript/barrel/*", "src/javascript/barrel/*"],
127
- // Optional config. The default value is the directory where ESLint is executed.
128
- // For example, if you run `npx eslint .`, the default will be the current working directory at the time of execution.
129
- baseDir: __dirname,
130
- // Enable isolation mode: block all imports from outside the barrel path
131
- isolated: true,
132
- // Allow direct imports only from the "shared" directory.
133
- // You can customize this array as needed, e.g., add "node_modules/..." or any other path you want to allow.
134
- allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
135
- },
136
- ],
137
- // Disallow wildcard (namespace) import/export.
138
- "barrel-rules/no-wildcard": ["error"],
125
+
126
+ //enforce barrel capsuling
127
+ "barrel-rules/enforce-barrel-pattern": [
128
+ "error",
129
+ {
130
+ // encapsulation barrel file
131
+ paths: ["src/pages/*", "src/features/*", "src/entities/*"],
132
+ baseDir: __dirname,
133
+ },
134
+ ],
135
+
136
+ //protect barrel file from outside module
137
+ "barrel-rules/isolate-barrel-file": [
138
+ "error",
139
+ {
140
+ //isolation options
141
+ isolations: [
142
+ {
143
+ path: "src/pages/*",
144
+ allowedPaths: ["src/features/*", "src/entities/*"],
145
+ },
146
+ {
147
+ path: "src/features/*",
148
+ allowedPaths: ["src/entities/*"],
149
+ },
150
+ {
151
+ path: "src/entities/*",
152
+ allowedPaths: [],
153
+ },
154
+ ],
155
+ baseDir: __dirname,
156
+ globalAllowPaths: ["src/shares/*", "node_modules/*"],
157
+ },
158
+ ],
159
+
160
+ // protect wildcard import/export
161
+ "barrel-rules/no-wildcard": ["error"],
162
+
139
163
  },
140
164
  };
141
165
  ```
@@ -173,23 +197,44 @@ export default tseslint.config([
173
197
  },
174
198
  //just set your setting for barrel-rules
175
199
  rules: {
200
+
201
+ //enforce barrel capsuling
176
202
  "barrel-rules/enforce-barrel-pattern": [
177
203
  "error",
178
204
  {
179
- // The path to the directory that should be protected by using a barrel file. This path is relative to baseDir.
180
- paths: ["src/typescript/barrel/*"],
181
- // Optional config. The default value is the directory where ESLint is executed.
182
- // For example, if you run `npx eslint .`, the default will be the current working directory at the time of execution.
205
+ // encapsulation barrel file
206
+ paths: ["src/pages/*", "src/features/*", "src/entities/*"],
183
207
  baseDir: __dirname,
184
- // Enable isolation mode: block all imports from outside the barrel path
185
- isolated: true,
186
- // Allow direct imports only from the "shared" directory.
187
- // You can customize this array as needed, e.g., add "node_modules/*" or any other path you want to allow.
188
- allowedImportPaths: ["src/typescript/shared", "node_modules/*"],
189
208
  },
190
209
  ],
191
- // Disallow wildcard (namespace) import/export.
210
+
211
+ //protect barrel file from outside module
212
+ "barrel-rules/isolate-barrel-file": [
213
+ "error",
214
+ {
215
+ //isolation options
216
+ isolations: [
217
+ {
218
+ path: "src/pages/*",
219
+ allowedPaths: ["src/features/*", "src/entities/*"],
220
+ },
221
+ {
222
+ path: "src/features/*",
223
+ allowedPaths: ["src/entities/*"],
224
+ },
225
+ {
226
+ path: "src/entities/*",
227
+ allowedPaths: [],
228
+ },
229
+ ],
230
+ baseDir: __dirname,
231
+ globalAllowPaths: ["src/shares/*", "node_modules/*"],
232
+ },
233
+ ],
234
+
235
+ // protect wildcard import/export
192
236
  "barrel-rules/no-wildcard": ["error"],
237
+
193
238
  },
194
239
  },
195
240
  ]);
@@ -219,15 +264,13 @@ file(src / domains / foo / index.ts);
219
264
  // ❌ External import to isolated barrel is blocked (even with alias)
220
265
  // from outside barrel (bar's path is src/domains/bar/)
221
266
  import { Test } from "@domains/bar/components/Test";
222
- // or
223
- import { Test } from "../domains/bar";
224
267
 
225
268
  // ✅ Internal imports within same barrel are allowed (alias supported)
226
269
  import { Hook } from "@domains/foo/hooks/useTest"; // from inside same barrel
227
270
  import { Utils } from "./utils/helper"; // from inside same barrel
228
271
 
229
272
  // ✅ Allowed import paths are permitted (alias supported)
230
- import { SharedUtil } from "@shㅇㅇared/utils"; // if "src/shared/*" is in allowedImportPaths
273
+ import { SharedUtil } from "@shared/utils"; // if "src/shared/*" is in allowedPaths or globalAllowedPaths
231
274
  ```
232
275
 
233
276
  ---
package/dist/index.cjs CHANGED
@@ -35,22 +35,22 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/rules/enforce-barrel-pattern.ts
38
- var import_utils = require("@typescript-eslint/utils");
38
+ var import_types = require("@typescript-eslint/types");
39
39
  var import_path2 = __toESM(require("path"), 1);
40
40
  var import_resolve = __toESM(require("resolve"), 1);
41
41
 
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(path3, baseDir) {
46
- const globResult = import_fast_glob.default.sync(path3, {
45
+ static resolvePath(path4, baseDir) {
46
+ const globResult = import_fast_glob.default.sync(path4, {
47
47
  cwd: baseDir,
48
48
  onlyDirectories: true,
49
49
  absolute: true
50
50
  });
51
51
  if (globResult.length === 0) {
52
52
  throw new Error(
53
- `[Glob] In baseDir: ${baseDir}, path: ${path3}, any directory was not found`
53
+ `[Glob] In baseDir: ${baseDir}, path: ${path4}, any directory was not found`
54
54
  );
55
55
  }
56
56
  return globResult;
@@ -142,37 +142,47 @@ var enforceBarrelPattern = {
142
142
  type: "object",
143
143
  properties: {
144
144
  paths: { type: "array", items: { type: "string" } },
145
- baseDir: { type: "string" },
146
- isolated: { type: "boolean" },
147
- allowedImportPaths: { type: "array", items: { type: "string" } }
145
+ baseDir: { type: "string" }
148
146
  },
149
- required: ["paths"],
147
+ required: ["paths", "baseDir"],
150
148
  additionalProperties: false
151
149
  }
152
150
  ],
153
151
  messages: {
152
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config.",
154
153
  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.",
155
- 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."
154
+ EmptyEslintConfig: "Please set the eslint config '{{property}}' to the eslint config. if you want to use this rule, please set the eslint config."
156
155
  }
157
156
  },
158
157
  //default options(baseDir is current working directory. almost user execute eslint in project root)
159
158
  defaultOptions: [
160
159
  {
161
160
  paths: [],
162
- baseDir: process.cwd(),
163
- isolated: false,
164
- allowedImportPaths: []
161
+ baseDir: process.cwd()
165
162
  }
166
163
  ],
167
164
  create(context) {
168
165
  const option = context.options[0];
166
+ if (!option) {
167
+ return {
168
+ Program(node) {
169
+ const option2 = context.options[0];
170
+ if (!option2) {
171
+ return context.report({
172
+ node,
173
+ messageId: "EmptyEslintConfig",
174
+ data: {
175
+ property: "{ path: Array<string>, baseDir: string }"
176
+ }
177
+ });
178
+ }
179
+ }
180
+ };
181
+ }
169
182
  const baseDir = option.baseDir;
170
183
  const absoluteTargetPaths = option.paths.flatMap((_path) => {
171
184
  return Glob.resolvePath(_path, baseDir);
172
185
  });
173
- const allowedImportPaths = option.allowedImportPaths.flatMap((_path) => {
174
- return Glob.resolvePath(_path, baseDir);
175
- });
176
186
  return {
177
187
  //check only import declaration(ESM)
178
188
  ImportDeclaration(node) {
@@ -193,6 +203,10 @@ var enforceBarrelPattern = {
193
203
  });
194
204
  }
195
205
  } catch (e) {
206
+ context.report({
207
+ node,
208
+ messageId: "TransformedAliasResolveFailed"
209
+ });
196
210
  return;
197
211
  }
198
212
  {
@@ -225,34 +239,127 @@ var enforceBarrelPattern = {
225
239
  });
226
240
  }
227
241
  }
228
- {
229
- if (option.isolated) {
230
- const currentFileInEnforceBarrel = absoluteTargetPaths.some(
231
- (absoluteTargetPath) => {
232
- const closedTargetPath = absoluteTargetPath + "/";
233
- return absoluteCurrentFilePath.startsWith(closedTargetPath);
234
- }
235
- );
236
- const sameBarrel = absoluteTargetPaths.some(
237
- (absoluteTargetPath) => {
238
- const closedTargetPath = absoluteTargetPath + "/";
239
- const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
240
- const currentFileInEnforceBarrel2 = absoluteCurrentFilePath.startsWith(closedTargetPath);
241
- return importedEnforceBarrelFile && currentFileInEnforceBarrel2;
242
- }
243
- );
244
- const allowedImportPath = allowedImportPaths.some(
245
- (allowedImportPath2) => {
246
- return absoluteImportPath.startsWith(allowedImportPath2);
247
- }
248
- );
249
- if (!allowedImportPath && !sameBarrel && currentFileInEnforceBarrel) {
250
- context.report({
251
- node,
252
- messageId: "IsolatedBarrelImportDisallowed"
253
- });
254
- }
242
+ }
243
+ };
244
+ }
245
+ };
246
+
247
+ // src/rules/isolate-barrel-file.ts
248
+ var import_types2 = require("@typescript-eslint/types");
249
+ var import_path3 = __toESM(require("path"), 1);
250
+ var import_resolve2 = __toESM(require("resolve"), 1);
251
+ var RESOLVE_EXTENSIONS2 = [
252
+ ".ts",
253
+ ".tsx",
254
+ ".js",
255
+ ".jsx",
256
+ ".d.ts",
257
+ ".mjs",
258
+ ".cjs"
259
+ ];
260
+ var isolateBarrelFile = {
261
+ meta: {
262
+ type: "problem",
263
+ docs: {
264
+ description: "Isolate barrel file is not allowed to import outside of the barrel file"
265
+ },
266
+ schema: [
267
+ {
268
+ type: "object",
269
+ properties: {
270
+ isolations: { type: "array", items: { type: "object" } },
271
+ baseDir: { type: "string" },
272
+ globalAllowPaths: { type: "array", items: { type: "string" } }
273
+ },
274
+ required: ["isolations", "baseDir", "globalAllowPaths"],
275
+ additionalProperties: false
276
+ }
277
+ ],
278
+ messages: {
279
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config.",
280
+ IsolatedBarrelImportDisallowed: "This barrel file is isolated. external import is not allowed. if you want to import outside of the barrel file, please add 'allowedPath' to the plugin options."
281
+ }
282
+ },
283
+ //default options(baseDir is current working directory. almost user execute eslint in project root)
284
+ defaultOptions: [
285
+ {
286
+ isolations: [],
287
+ baseDir: process.cwd(),
288
+ globalAllowPaths: []
289
+ }
290
+ ],
291
+ create(context) {
292
+ const option = context.options[0];
293
+ const baseDir = option.baseDir;
294
+ const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path4) => {
295
+ return Glob.resolvePath(path4, baseDir);
296
+ });
297
+ const absoluteIsolations = option.isolations.flatMap((isolation) => {
298
+ const isolationPaths = Glob.resolvePath(isolation.path, baseDir);
299
+ const allowedPaths = isolation.allowedPaths.flatMap((path4) => {
300
+ return Glob.resolvePath(path4, baseDir);
301
+ });
302
+ return isolationPaths.map((isolationPath) => ({
303
+ isolationPath,
304
+ //self isolatedPath also allowed to be imported
305
+ allowedPaths: [...allowedPaths, isolationPath]
306
+ }));
307
+ });
308
+ return {
309
+ //check only import declaration(ESM)
310
+ ImportDeclaration(node) {
311
+ const rawImportPath = node.source.value;
312
+ const absoluteCurrentFilePath = context.getFilename();
313
+ let absoluteImportPath = null;
314
+ try {
315
+ const aliasResult = Alias.resolvePath(
316
+ rawImportPath,
317
+ import_path3.default.dirname(absoluteCurrentFilePath)
318
+ );
319
+ if (aliasResult.type === "success") {
320
+ absoluteImportPath = aliasResult.absolutePath;
321
+ } else {
322
+ absoluteImportPath = import_resolve2.default.sync(rawImportPath, {
323
+ basedir: import_path3.default.dirname(absoluteCurrentFilePath),
324
+ extensions: RESOLVE_EXTENSIONS2
325
+ });
255
326
  }
327
+ } catch (e) {
328
+ context.report({
329
+ node,
330
+ messageId: "TransformedAliasResolveFailed"
331
+ });
332
+ return;
333
+ }
334
+ const isolationIndex = absoluteIsolations.findIndex((isolation) => {
335
+ const absoluteIsolationPath = isolation.isolationPath;
336
+ const closedIsolationPath = absoluteIsolationPath + "/";
337
+ return absoluteCurrentFilePath.startsWith(closedIsolationPath);
338
+ });
339
+ const matchedIsolation = absoluteIsolations[isolationIndex];
340
+ if (!matchedIsolation) return;
341
+ const isAllowedImport = matchedIsolation.allowedPaths.some(
342
+ (allowedPath) => {
343
+ const same = absoluteImportPath === allowedPath;
344
+ const closedAllowedPath = allowedPath + "/";
345
+ const sub = absoluteImportPath.startsWith(closedAllowedPath);
346
+ return same || sub;
347
+ }
348
+ );
349
+ const isGlobalAllowedImport = absoluteGlobalAllowPaths.some(
350
+ (allowedPath) => {
351
+ const same = absoluteImportPath === allowedPath;
352
+ const closedAllowedPath = allowedPath + "/";
353
+ const sub = absoluteImportPath.startsWith(closedAllowedPath);
354
+ return same || sub;
355
+ }
356
+ );
357
+ const allowedImport = isAllowedImport || isGlobalAllowedImport;
358
+ if (!allowedImport) {
359
+ context.report({
360
+ node,
361
+ messageId: "IsolatedBarrelImportDisallowed"
362
+ });
256
363
  }
257
364
  }
258
365
  };
@@ -260,7 +367,7 @@ var enforceBarrelPattern = {
260
367
  };
261
368
 
262
369
  // src/rules/no-wildcard.ts
263
- var import_utils2 = require("@typescript-eslint/utils");
370
+ var import_types3 = require("@typescript-eslint/types");
264
371
  var noWildcard = {
265
372
  meta: {
266
373
  type: "problem",
@@ -300,6 +407,7 @@ var noWildcard = {
300
407
  // src/index.ts
301
408
  var rules = {
302
409
  "enforce-barrel-pattern": enforceBarrelPattern,
410
+ "isolate-barrel-file": isolateBarrelFile,
303
411
  "no-wildcard": noWildcard
304
412
  };
305
413
  var index_default = { rules };
package/dist/index.d.cts CHANGED
@@ -2,11 +2,17 @@ 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" | "IsolatedBarrelImportDisallowed", {
5
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
6
6
  paths: string[];
7
7
  baseDir: string;
8
- isolated: boolean;
9
- allowedImportPaths: 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[];
10
16
  }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
11
17
  "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
18
  };
package/dist/index.d.ts CHANGED
@@ -2,11 +2,17 @@ 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" | "IsolatedBarrelImportDisallowed", {
5
+ "enforce-barrel-pattern": _typescript_eslint_utils_ts_eslint.RuleModule<"DirectImportDisallowed" | "TransformedAliasResolveFailed" | "EmptyEslintConfig", {
6
6
  paths: string[];
7
7
  baseDir: string;
8
- isolated: boolean;
9
- allowedImportPaths: 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[];
10
16
  }[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
11
17
  "no-wildcard": _typescript_eslint_utils_ts_eslint.RuleModule<"NoWildcardImport" | "NoExportAll", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
18
  };
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  // src/rules/enforce-barrel-pattern.ts
2
- import "@typescript-eslint/utils";
2
+ import "@typescript-eslint/types";
3
3
  import path2 from "path";
4
4
  import resolve from "resolve";
5
5
 
6
6
  // src/utils/glob.ts
7
7
  import FastGlob from "fast-glob";
8
8
  var Glob = class {
9
- static resolvePath(path3, baseDir) {
10
- const globResult = FastGlob.sync(path3, {
9
+ static resolvePath(path4, baseDir) {
10
+ const globResult = FastGlob.sync(path4, {
11
11
  cwd: baseDir,
12
12
  onlyDirectories: true,
13
13
  absolute: true
14
14
  });
15
15
  if (globResult.length === 0) {
16
16
  throw new Error(
17
- `[Glob] In baseDir: ${baseDir}, path: ${path3}, any directory was not found`
17
+ `[Glob] In baseDir: ${baseDir}, path: ${path4}, any directory was not found`
18
18
  );
19
19
  }
20
20
  return globResult;
@@ -106,37 +106,47 @@ var enforceBarrelPattern = {
106
106
  type: "object",
107
107
  properties: {
108
108
  paths: { type: "array", items: { type: "string" } },
109
- baseDir: { type: "string" },
110
- isolated: { type: "boolean" },
111
- allowedImportPaths: { type: "array", items: { type: "string" } }
109
+ baseDir: { type: "string" }
112
110
  },
113
- required: ["paths"],
111
+ required: ["paths", "baseDir"],
114
112
  additionalProperties: false
115
113
  }
116
114
  ],
117
115
  messages: {
116
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config.",
118
117
  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.",
119
- 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."
118
+ EmptyEslintConfig: "Please set the eslint config '{{property}}' to the eslint config. if you want to use this rule, please set the eslint config."
120
119
  }
121
120
  },
122
121
  //default options(baseDir is current working directory. almost user execute eslint in project root)
123
122
  defaultOptions: [
124
123
  {
125
124
  paths: [],
126
- baseDir: process.cwd(),
127
- isolated: false,
128
- allowedImportPaths: []
125
+ baseDir: process.cwd()
129
126
  }
130
127
  ],
131
128
  create(context) {
132
129
  const option = context.options[0];
130
+ if (!option) {
131
+ return {
132
+ Program(node) {
133
+ const option2 = context.options[0];
134
+ if (!option2) {
135
+ return context.report({
136
+ node,
137
+ messageId: "EmptyEslintConfig",
138
+ data: {
139
+ property: "{ path: Array<string>, baseDir: string }"
140
+ }
141
+ });
142
+ }
143
+ }
144
+ };
145
+ }
133
146
  const baseDir = option.baseDir;
134
147
  const absoluteTargetPaths = option.paths.flatMap((_path) => {
135
148
  return Glob.resolvePath(_path, baseDir);
136
149
  });
137
- const allowedImportPaths = option.allowedImportPaths.flatMap((_path) => {
138
- return Glob.resolvePath(_path, baseDir);
139
- });
140
150
  return {
141
151
  //check only import declaration(ESM)
142
152
  ImportDeclaration(node) {
@@ -157,6 +167,10 @@ var enforceBarrelPattern = {
157
167
  });
158
168
  }
159
169
  } catch (e) {
170
+ context.report({
171
+ node,
172
+ messageId: "TransformedAliasResolveFailed"
173
+ });
160
174
  return;
161
175
  }
162
176
  {
@@ -189,34 +203,127 @@ var enforceBarrelPattern = {
189
203
  });
190
204
  }
191
205
  }
192
- {
193
- if (option.isolated) {
194
- const currentFileInEnforceBarrel = absoluteTargetPaths.some(
195
- (absoluteTargetPath) => {
196
- const closedTargetPath = absoluteTargetPath + "/";
197
- return absoluteCurrentFilePath.startsWith(closedTargetPath);
198
- }
199
- );
200
- const sameBarrel = absoluteTargetPaths.some(
201
- (absoluteTargetPath) => {
202
- const closedTargetPath = absoluteTargetPath + "/";
203
- const importedEnforceBarrelFile = absoluteImportPath.startsWith(closedTargetPath);
204
- const currentFileInEnforceBarrel2 = absoluteCurrentFilePath.startsWith(closedTargetPath);
205
- return importedEnforceBarrelFile && currentFileInEnforceBarrel2;
206
- }
207
- );
208
- const allowedImportPath = allowedImportPaths.some(
209
- (allowedImportPath2) => {
210
- return absoluteImportPath.startsWith(allowedImportPath2);
211
- }
212
- );
213
- if (!allowedImportPath && !sameBarrel && currentFileInEnforceBarrel) {
214
- context.report({
215
- node,
216
- messageId: "IsolatedBarrelImportDisallowed"
217
- });
218
- }
206
+ }
207
+ };
208
+ }
209
+ };
210
+
211
+ // src/rules/isolate-barrel-file.ts
212
+ import "@typescript-eslint/types";
213
+ import path3 from "path";
214
+ import resolve2 from "resolve";
215
+ var RESOLVE_EXTENSIONS2 = [
216
+ ".ts",
217
+ ".tsx",
218
+ ".js",
219
+ ".jsx",
220
+ ".d.ts",
221
+ ".mjs",
222
+ ".cjs"
223
+ ];
224
+ var isolateBarrelFile = {
225
+ meta: {
226
+ type: "problem",
227
+ docs: {
228
+ description: "Isolate barrel file is not allowed to import outside of the barrel file"
229
+ },
230
+ schema: [
231
+ {
232
+ type: "object",
233
+ properties: {
234
+ isolations: { type: "array", items: { type: "object" } },
235
+ baseDir: { type: "string" },
236
+ globalAllowPaths: { type: "array", items: { type: "string" } }
237
+ },
238
+ required: ["isolations", "baseDir", "globalAllowPaths"],
239
+ additionalProperties: false
240
+ }
241
+ ],
242
+ messages: {
243
+ TransformedAliasResolveFailed: "Transformed alias resolve failed. please check the alias config.",
244
+ IsolatedBarrelImportDisallowed: "This barrel file is isolated. external import is not allowed. if you want to import outside of the barrel file, please add 'allowedPath' to the plugin options."
245
+ }
246
+ },
247
+ //default options(baseDir is current working directory. almost user execute eslint in project root)
248
+ defaultOptions: [
249
+ {
250
+ isolations: [],
251
+ baseDir: process.cwd(),
252
+ globalAllowPaths: []
253
+ }
254
+ ],
255
+ create(context) {
256
+ const option = context.options[0];
257
+ const baseDir = option.baseDir;
258
+ const absoluteGlobalAllowPaths = option.globalAllowPaths.flatMap((path4) => {
259
+ return Glob.resolvePath(path4, baseDir);
260
+ });
261
+ const absoluteIsolations = option.isolations.flatMap((isolation) => {
262
+ const isolationPaths = Glob.resolvePath(isolation.path, baseDir);
263
+ const allowedPaths = isolation.allowedPaths.flatMap((path4) => {
264
+ return Glob.resolvePath(path4, baseDir);
265
+ });
266
+ return isolationPaths.map((isolationPath) => ({
267
+ isolationPath,
268
+ //self isolatedPath also allowed to be imported
269
+ allowedPaths: [...allowedPaths, isolationPath]
270
+ }));
271
+ });
272
+ return {
273
+ //check only import declaration(ESM)
274
+ ImportDeclaration(node) {
275
+ const rawImportPath = node.source.value;
276
+ const absoluteCurrentFilePath = context.getFilename();
277
+ let absoluteImportPath = null;
278
+ try {
279
+ const aliasResult = Alias.resolvePath(
280
+ rawImportPath,
281
+ path3.dirname(absoluteCurrentFilePath)
282
+ );
283
+ if (aliasResult.type === "success") {
284
+ absoluteImportPath = aliasResult.absolutePath;
285
+ } else {
286
+ absoluteImportPath = resolve2.sync(rawImportPath, {
287
+ basedir: path3.dirname(absoluteCurrentFilePath),
288
+ extensions: RESOLVE_EXTENSIONS2
289
+ });
219
290
  }
291
+ } catch (e) {
292
+ context.report({
293
+ node,
294
+ messageId: "TransformedAliasResolveFailed"
295
+ });
296
+ return;
297
+ }
298
+ const isolationIndex = absoluteIsolations.findIndex((isolation) => {
299
+ const absoluteIsolationPath = isolation.isolationPath;
300
+ const closedIsolationPath = absoluteIsolationPath + "/";
301
+ return absoluteCurrentFilePath.startsWith(closedIsolationPath);
302
+ });
303
+ const matchedIsolation = absoluteIsolations[isolationIndex];
304
+ if (!matchedIsolation) return;
305
+ const isAllowedImport = matchedIsolation.allowedPaths.some(
306
+ (allowedPath) => {
307
+ const same = absoluteImportPath === allowedPath;
308
+ const closedAllowedPath = allowedPath + "/";
309
+ const sub = absoluteImportPath.startsWith(closedAllowedPath);
310
+ return same || sub;
311
+ }
312
+ );
313
+ const isGlobalAllowedImport = absoluteGlobalAllowPaths.some(
314
+ (allowedPath) => {
315
+ const same = absoluteImportPath === allowedPath;
316
+ const closedAllowedPath = allowedPath + "/";
317
+ const sub = absoluteImportPath.startsWith(closedAllowedPath);
318
+ return same || sub;
319
+ }
320
+ );
321
+ const allowedImport = isAllowedImport || isGlobalAllowedImport;
322
+ if (!allowedImport) {
323
+ context.report({
324
+ node,
325
+ messageId: "IsolatedBarrelImportDisallowed"
326
+ });
220
327
  }
221
328
  }
222
329
  };
@@ -224,7 +331,7 @@ var enforceBarrelPattern = {
224
331
  };
225
332
 
226
333
  // src/rules/no-wildcard.ts
227
- import "@typescript-eslint/utils";
334
+ import "@typescript-eslint/types";
228
335
  var noWildcard = {
229
336
  meta: {
230
337
  type: "problem",
@@ -264,6 +371,7 @@ var noWildcard = {
264
371
  // src/index.ts
265
372
  var rules = {
266
373
  "enforce-barrel-pattern": enforceBarrelPattern,
374
+ "isolate-barrel-file": isolateBarrelFile,
267
375
  "no-wildcard": noWildcard
268
376
  };
269
377
  var index_default = { rules };
package/package.json CHANGED
@@ -25,15 +25,21 @@
25
25
  "isolated barrel module",
26
26
  "no-wildcard"
27
27
  ],
28
- "version": "1.2.0",
28
+ "version": "1.3.0",
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
+ "@typescript-eslint/types": "^8.46.2",
34
38
  "@typescript-eslint/utils": "^8.36.0",
35
39
  "fast-glob": "^3.3.3",
40
+ "jest": "^30.2.0",
36
41
  "resolve": "^1.22.10",
42
+ "ts-jest": "^29.4.5",
37
43
  "tsconfig-paths": "^4.2.0"
38
44
  },
39
45
  "devDependencies": {
@@ -43,8 +49,11 @@
43
49
  "typescript": "~5.8.3"
44
50
  },
45
51
  "scripts": {
46
- "build": "tsup src/index.ts --dts --format cjs,esm",
52
+ "build": "pnpm run test && tsup src/index.ts --dts --format cjs,esm",
47
53
  "type-check": "tsc --noEmit",
54
+ "test": "jest",
55
+ "test:watch": "jest --watch",
56
+ "test:coverage": "jest --coverage",
48
57
  "release": "pnpm run build && pnpm publish --access=public"
49
58
  }
50
59
  }