eslint-plugin-barrel-rules 1.1.3 → 1.2.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.1.3-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.2.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>
@@ -39,7 +39,12 @@ JavaScript/TypeScript 프로젝트에서 Barrel Pattern(배럴 패턴)을 강제
39
39
  - ESLint 9
40
40
  > Flat config(eslint.config.js), TypeScript 지원 시 "typescript-eslint" config 사용 필요
41
41
  - ESLint 8
42
+
42
43
  > Legacy config(.eslintrc.js), TypeScript 지원 시 "@typescript-eslint/parser"를 parser로 지정하고, "@typescript-eslint"를 plugin에 추가해야 함
44
+
45
+ - TypeScript Alias Import 지원
46
+ > Import 구문에서 TypeScript 경로 별칭(예: `@ts/barrel/inner`)을 `tsconfig.json` 기반으로 자동 해석합니다.
47
+ > 단, ESLint 플러그인 설정에서는 alias 사용 불가능 - 상대경로나 절대경로만 사용하세요.
43
48
  - Node.js (ES2015 이상)
44
49
  - ES 모듈, CommonJS 모듈 모두 지원
45
50
 
@@ -193,20 +198,43 @@ export default tseslint.config([
193
198
 
194
199
  ## 예시
195
200
 
201
+ ### 1. 배럴 내부파일 직접 접근
202
+
196
203
  ```ts
197
- // 내부 파일을 직접 import하면 차단됩니다.
204
+ file(src / index.ts);
205
+
206
+ // ❌ 내부 파일 직접 import 차단
198
207
  import { Test } from "../domains/foo/components/Test";
199
208
 
200
- // ✅ 반드시 배럴(index) 파일을 통해 import해야 합니다.
209
+ // ✅ barrel(index) 파일을 통한 import 허용
201
210
  import { Test } from "../domains/foo";
202
211
  ```
203
212
 
213
+ ### 2. 격리된 모듈 접근 (TypeScript Alias 지원)
214
+
215
+ ```ts
216
+ file(src / domains / foo / index.ts);
217
+
218
+ // ❌ 격리된 barrel로의 외부 import 차단 (alias 사용해도 차단)
219
+ // barrel 외부에서 접근 (bar의 경로는 src/domains/bar/)
220
+ import { Test } from "@domains/bar/components/Test";
221
+ // 또는
222
+ import { Test } from "../domains/bar";
223
+
224
+ // ✅ 같은 barrel 내부에서의 import는 허용 (alias 지원)
225
+ import { Hook } from "@domains/foo/hooks/useTest"; // 같은 barrel 내부에서
226
+ import { Utils } from "./utils/helper"; // 같은 barrel 내부에서
227
+
228
+ // ✅ 허용된 import 경로는 사용 가능 (alias 지원)
229
+ import { SharedUtil } from "@shared/utils"; // allowedImportPaths에 "src/shared/*"가 있는 경우
230
+ ```
231
+
204
232
  ---
205
233
 
206
234
  ## 앞으로의 계획
207
235
 
208
236
  - 더 다양한 모듈 경계/추상화 관련 룰 추가 예정 (~Ing)
209
- - Alias/tsconfig 지원: TypeScript의 paths, Vite의 resolve.alias, 기타 커스텀 경로 매핑을 완벽하게 지원 (~Ing)
237
+ - **Alias/tsconfig 지원: TypeScript의 paths 맵핑 완벽하게 지원** (OK)
210
238
  - **CJS 지원** (OK)
211
239
  - **ESLint 8 지원** (OK)
212
240
  - **번들 플러그인(플러그인 내 모든 기능 통합)** (OK)
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.3-blue.svg" alt="Version"/>
6
+ <img src="https://img.shields.io/badge/version-1.2.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>
@@ -43,6 +43,9 @@ Direct imports from internal files are blocked, maximizing
43
43
  > Flat config(eslint.config.js), for TypeScript support, use the "typescript-eslint" config
44
44
  - ESLint 8
45
45
  > Legacy config(.eslintrc.js), for TypeScript support, set "@typescript-eslint/parser" as the parser and add "@typescript-eslint" as a plugin
46
+ - TypeScript Alias Import Support
47
+ > Automatically resolves TypeScript path aliases (e.g., `@ts/barrel/inner`) in import statements based on your `tsconfig.json`.
48
+ > Note: ESLint plugin configuration does not support aliases - use relative or absolute paths only.
46
49
  - Node.js (ES2015+)
47
50
  - Supports both ES Modules and CommonJS
48
51
 
@@ -194,9 +197,13 @@ export default tseslint.config([
194
197
 
195
198
  ---
196
199
 
197
- ## Example
200
+ ## Examples
201
+
202
+ ### 1. Direct Access
198
203
 
199
204
  ```ts
205
+ file(src / index.ts);
206
+
200
207
  // ❌ Direct import from internal file is blocked
201
208
  import { Test } from "../domains/foo/components/Test";
202
209
 
@@ -204,6 +211,25 @@ import { Test } from "../domains/foo/components/Test";
204
211
  import { Test } from "../domains/foo";
205
212
  ```
206
213
 
214
+ ### 2. Isolated Module Access (with TypeScript Alias Support)
215
+
216
+ ```ts
217
+ file(src / domains / foo / index.ts);
218
+
219
+ // ❌ External import to isolated barrel is blocked (even with alias)
220
+ // from outside barrel (bar's path is src/domains/bar/)
221
+ import { Test } from "@domains/bar/components/Test";
222
+ // or
223
+ import { Test } from "../domains/bar";
224
+
225
+ // ✅ Internal imports within same barrel are allowed (alias supported)
226
+ import { Hook } from "@domains/foo/hooks/useTest"; // from inside same barrel
227
+ import { Utils } from "./utils/helper"; // from inside same barrel
228
+
229
+ // ✅ Allowed import paths are permitted (alias supported)
230
+ import { SharedUtil } from "@shㅇㅇared/utils"; // if "src/shared/*" is in allowedImportPaths
231
+ ```
232
+
207
233
  ---
208
234
 
209
235
  ## Future Work
@@ -211,8 +237,7 @@ import { Test } from "../domains/foo";
211
237
  - More rules for module boundaries and abstraction (~Ing)
212
238
 
213
239
  - **Alias/tsconfig Support**
214
- Fully supports TypeScript `paths`, Vite `resolve.alias`, and other custom path mappings (~Ing)
215
-
240
+ Fully supports TypeScript `paths` (OK)
216
241
  - **CJS Support** (OK)
217
242
  - **Eslint8 Support** (OK)
218
243
  - **Bundle Plugin(capsure any features in plugin)**
package/dist/index.cjs CHANGED
@@ -36,27 +36,81 @@ module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/rules/enforce-barrel-pattern.ts
38
38
  var import_utils = require("@typescript-eslint/utils");
39
- var import_path = __toESM(require("path"), 1);
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(path2, baseDir) {
46
- const globResult = import_fast_glob.default.sync(path2, {
45
+ static resolvePath(path3, baseDir) {
46
+ const globResult = import_fast_glob.default.sync(path3, {
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: ${path2}, any directory was not found`
53
+ `[Glob] In baseDir: ${baseDir}, path: ${path3}, any directory was not found`
54
54
  );
55
55
  }
56
56
  return globResult;
57
57
  }
58
58
  };
59
59
 
60
+ // src/utils/alias.ts
61
+ var import_tsconfig_paths = require("tsconfig-paths");
62
+ var import_path = __toESM(require("path"), 1);
63
+ var Alias = class {
64
+ constructor() {
65
+ }
66
+ static resolvePath(rawPath, currentFileDir) {
67
+ try {
68
+ const configResult = (0, import_tsconfig_paths.loadConfig)(currentFileDir);
69
+ if (configResult.resultType === "success") {
70
+ for (const [pattern, targets] of Object.entries(configResult.paths)) {
71
+ const origin = targets[0];
72
+ if (pattern.includes("*")) {
73
+ const patternRegex = new RegExp(
74
+ `^${pattern.replace("*", "(.*)")}$`
75
+ );
76
+ const match = rawPath.match(patternRegex);
77
+ if (match) {
78
+ const [, matchedPath] = match;
79
+ const extendedOrigin = origin.replace("*", matchedPath);
80
+ const absolutePath = import_path.default.resolve(
81
+ `${configResult.absoluteBaseUrl}/${extendedOrigin}`
82
+ );
83
+ return {
84
+ absolutePath,
85
+ type: "success"
86
+ };
87
+ }
88
+ } else {
89
+ if (rawPath === pattern) {
90
+ const absolutePath = import_path.default.resolve(
91
+ `${configResult.absoluteBaseUrl}/${origin}`
92
+ );
93
+ return {
94
+ absolutePath,
95
+ type: "success"
96
+ };
97
+ }
98
+ }
99
+ }
100
+ }
101
+ return {
102
+ absolutePath: rawPath,
103
+ type: "fail"
104
+ };
105
+ } catch (e) {
106
+ return {
107
+ absolutePath: rawPath,
108
+ type: "fail"
109
+ };
110
+ }
111
+ }
112
+ };
113
+
60
114
  // src/rules/enforce-barrel-pattern.ts
61
115
  var BARREL_ENTRY_POINT_FILE_NAMES = [
62
116
  "index.ts",
@@ -126,10 +180,18 @@ var enforceBarrelPattern = {
126
180
  const absoluteCurrentFilePath = context.getFilename();
127
181
  let absoluteImportPath = null;
128
182
  try {
129
- absoluteImportPath = import_resolve.default.sync(rawImportPath, {
130
- basedir: import_path.default.dirname(absoluteCurrentFilePath),
131
- extensions: RESOLVE_EXTENSIONS
132
- });
183
+ const aliasResult = Alias.resolvePath(
184
+ rawImportPath,
185
+ import_path2.default.dirname(absoluteCurrentFilePath)
186
+ );
187
+ if (aliasResult.type === "success") {
188
+ absoluteImportPath = aliasResult.absolutePath;
189
+ } else {
190
+ absoluteImportPath = import_resolve.default.sync(rawImportPath, {
191
+ basedir: import_path2.default.dirname(absoluteCurrentFilePath),
192
+ extensions: RESOLVE_EXTENSIONS
193
+ });
194
+ }
133
195
  } catch (e) {
134
196
  return;
135
197
  }
@@ -138,7 +200,7 @@ var enforceBarrelPattern = {
138
200
  const invalidDirectedImport = absoluteTargetPaths.some(
139
201
  (absoluteTargetPath) => {
140
202
  const targetPathEntryPoints = BARREL_ENTRY_POINT_FILE_NAMES.map(
141
- (entry) => import_path.default.resolve(absoluteTargetPath, entry)
203
+ (entry) => import_path2.default.resolve(absoluteTargetPath, entry)
142
204
  );
143
205
  const closedTargetPath = absoluteTargetPath + "/";
144
206
  const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
package/dist/index.js CHANGED
@@ -1,26 +1,80 @@
1
1
  // src/rules/enforce-barrel-pattern.ts
2
2
  import "@typescript-eslint/utils";
3
- import path from "path";
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(path2, baseDir) {
10
- const globResult = FastGlob.sync(path2, {
9
+ static resolvePath(path3, baseDir) {
10
+ const globResult = FastGlob.sync(path3, {
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: ${path2}, any directory was not found`
17
+ `[Glob] In baseDir: ${baseDir}, path: ${path3}, any directory was not found`
18
18
  );
19
19
  }
20
20
  return globResult;
21
21
  }
22
22
  };
23
23
 
24
+ // src/utils/alias.ts
25
+ import { loadConfig } from "tsconfig-paths";
26
+ import path from "path";
27
+ var Alias = class {
28
+ constructor() {
29
+ }
30
+ static resolvePath(rawPath, currentFileDir) {
31
+ try {
32
+ const configResult = loadConfig(currentFileDir);
33
+ if (configResult.resultType === "success") {
34
+ for (const [pattern, targets] of Object.entries(configResult.paths)) {
35
+ const origin = targets[0];
36
+ if (pattern.includes("*")) {
37
+ const patternRegex = new RegExp(
38
+ `^${pattern.replace("*", "(.*)")}$`
39
+ );
40
+ const match = rawPath.match(patternRegex);
41
+ if (match) {
42
+ const [, matchedPath] = match;
43
+ const extendedOrigin = origin.replace("*", matchedPath);
44
+ const absolutePath = path.resolve(
45
+ `${configResult.absoluteBaseUrl}/${extendedOrigin}`
46
+ );
47
+ return {
48
+ absolutePath,
49
+ type: "success"
50
+ };
51
+ }
52
+ } else {
53
+ if (rawPath === pattern) {
54
+ const absolutePath = path.resolve(
55
+ `${configResult.absoluteBaseUrl}/${origin}`
56
+ );
57
+ return {
58
+ absolutePath,
59
+ type: "success"
60
+ };
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return {
66
+ absolutePath: rawPath,
67
+ type: "fail"
68
+ };
69
+ } catch (e) {
70
+ return {
71
+ absolutePath: rawPath,
72
+ type: "fail"
73
+ };
74
+ }
75
+ }
76
+ };
77
+
24
78
  // src/rules/enforce-barrel-pattern.ts
25
79
  var BARREL_ENTRY_POINT_FILE_NAMES = [
26
80
  "index.ts",
@@ -90,10 +144,18 @@ var enforceBarrelPattern = {
90
144
  const absoluteCurrentFilePath = context.getFilename();
91
145
  let absoluteImportPath = null;
92
146
  try {
93
- absoluteImportPath = resolve.sync(rawImportPath, {
94
- basedir: path.dirname(absoluteCurrentFilePath),
95
- extensions: RESOLVE_EXTENSIONS
96
- });
147
+ const aliasResult = Alias.resolvePath(
148
+ rawImportPath,
149
+ path2.dirname(absoluteCurrentFilePath)
150
+ );
151
+ if (aliasResult.type === "success") {
152
+ absoluteImportPath = aliasResult.absolutePath;
153
+ } else {
154
+ absoluteImportPath = resolve.sync(rawImportPath, {
155
+ basedir: path2.dirname(absoluteCurrentFilePath),
156
+ extensions: RESOLVE_EXTENSIONS
157
+ });
158
+ }
97
159
  } catch (e) {
98
160
  return;
99
161
  }
@@ -102,7 +164,7 @@ var enforceBarrelPattern = {
102
164
  const invalidDirectedImport = absoluteTargetPaths.some(
103
165
  (absoluteTargetPath) => {
104
166
  const targetPathEntryPoints = BARREL_ENTRY_POINT_FILE_NAMES.map(
105
- (entry) => path.resolve(absoluteTargetPath, entry)
167
+ (entry) => path2.resolve(absoluteTargetPath, entry)
106
168
  );
107
169
  const closedTargetPath = absoluteTargetPath + "/";
108
170
  const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
package/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "isolated barrel module",
26
26
  "no-wildcard"
27
27
  ],
28
- "version": "1.1.3",
28
+ "version": "1.2.0",
29
29
  "type": "module",
30
30
  "main": "dist/index.cjs",
31
31
  "module": "dist/index.js",