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 +32 -4
- package/README.md +29 -4
- package/dist/index.cjs +71 -9
- package/dist/index.js +71 -9
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
204
|
+
file(src / index.ts);
|
|
205
|
+
|
|
206
|
+
// ❌ 내부 파일 직접 import 차단
|
|
198
207
|
import { Test } from "../domains/foo/components/Test";
|
|
199
208
|
|
|
200
|
-
// ✅
|
|
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
|
|
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.
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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(
|
|
46
|
-
const globResult = import_fast_glob.default.sync(
|
|
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: ${
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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) =>
|
|
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
|
|
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(
|
|
10
|
-
const globResult = FastGlob.sync(
|
|
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: ${
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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) =>
|
|
167
|
+
(entry) => path2.resolve(absoluteTargetPath, entry)
|
|
106
168
|
);
|
|
107
169
|
const closedTargetPath = absoluteTargetPath + "/";
|
|
108
170
|
const targetPathEntryPointed = targetPathEntryPoints.includes(absoluteImportPath);
|