eslint-plugin-weld 0.0.4-rc.2 → 0.1.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.md +64 -12
- package/dist/index.d.ts +7 -33
- package/dist/index.js +701 -5
- package/dist/index.js.map +1 -1
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -2,22 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Подход к организации кода во frontend-приложениях и инструменты для его соблюдения.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Ключевые идеи подхода
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
так, чтобы эти границы держались со временем. Ключевая идея — строгая инкапсуляция: модуль публикует
|
|
9
|
-
наружу узкий, осознанно спроектированный интерфейс, а всё остальное остаётся его внутренним делом.
|
|
10
|
-
Слои задают направление зависимостей, инкапсуляция не даёт им расползтись.
|
|
7
|
+
### 1. Баррель = граница модуля
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
Папка с `index.ts` — модуль с публичным интерфейсом: что в барреле — публично, остальное недоступно
|
|
10
|
+
снаружи.
|
|
11
|
+
|
|
12
|
+
Префикс `_` и папка `internal/` прячут содержимое даже там, где барреля нет.
|
|
13
|
+
|
|
14
|
+
### 2. Однонаправленность зависимостей
|
|
15
|
+
|
|
16
|
+
Слой не импортирует ничего из слоев выше.
|
|
17
|
+
|
|
18
|
+
### 3. Слои задает проект
|
|
19
|
+
|
|
20
|
+
Список и порядок слоёв задаёт проект.
|
|
21
|
+
|
|
22
|
+
### 4. Общие слои и фрактальность модулей
|
|
23
|
+
|
|
24
|
+
Общие слои наверху, модули в `modules/` — со слоями или без, с вложенными модулями в собственной
|
|
25
|
+
`modules/`. Правила одинаковы на любой глубине.
|
|
15
26
|
|
|
16
27
|
## Статус
|
|
17
28
|
|
|
18
|
-
Ранняя стадия. Документация пишется; ESLint
|
|
19
|
-
|
|
20
|
-
обратной совместимости.
|
|
29
|
+
Ранняя стадия. Документация подхода пишется; в ESLint-плагине реализовано первое правило —
|
|
30
|
+
[`weld/no-barrel-bypass`](docs/rules/no-barrel-bypass.md). Правила и их именование могут меняться
|
|
31
|
+
без обратной совместимости.
|
|
21
32
|
|
|
22
33
|
## ESLint-плагин
|
|
23
34
|
|
|
@@ -35,6 +46,14 @@ import weld from 'eslint-plugin-weld';
|
|
|
35
46
|
export default [weld.configs.recommended];
|
|
36
47
|
```
|
|
37
48
|
|
|
49
|
+
Плагин предоставляет готовые наборы правил `recommended` и `strict`:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import weld from 'eslint-plugin-weld';
|
|
53
|
+
|
|
54
|
+
export default [weld.configs.strict];
|
|
55
|
+
```
|
|
56
|
+
|
|
38
57
|
Можно подключить и сам плагин, включая правила поштучно:
|
|
39
58
|
|
|
40
59
|
```js
|
|
@@ -44,12 +63,45 @@ export default [
|
|
|
44
63
|
{
|
|
45
64
|
plugins: { weld },
|
|
46
65
|
rules: {
|
|
47
|
-
|
|
66
|
+
'weld/no-barrel-bypass': 'error',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Правила
|
|
73
|
+
|
|
74
|
+
- [`weld/no-barrel-bypass`](docs/rules/no-barrel-bypass.md) — запрещает импорты, которые входят
|
|
75
|
+
внутрь модуля мимо его точки входа (`index.*`), минуя баррель.
|
|
76
|
+
|
|
77
|
+
Правило учитывает алиасы путей и корень репозитория через `settings.weld`:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
export default [
|
|
81
|
+
{
|
|
82
|
+
settings: {
|
|
83
|
+
weld: {
|
|
84
|
+
repoRoot: '.', // необязательно; по умолчанию — авто-поиск
|
|
85
|
+
aliasesBaseUrl: 'packages/app', // необязательно; по умолчанию '.'
|
|
86
|
+
aliases: { '@/*': ['src/*'] },
|
|
87
|
+
},
|
|
48
88
|
},
|
|
49
89
|
},
|
|
50
90
|
];
|
|
51
91
|
```
|
|
52
92
|
|
|
93
|
+
Формат и разбор этих настроек описаны в [документации настроек](docs/settings.md); подробности
|
|
94
|
+
самого правила — в [документации правила](docs/rules/no-barrel-bypass.md).
|
|
95
|
+
|
|
96
|
+
### Отладка
|
|
97
|
+
|
|
98
|
+
Плагин использует пакет [`debug`](https://www.npmjs.com/package/debug) под namespace
|
|
99
|
+
`eslint-plugin-weld:*`:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
DEBUG=eslint-plugin-weld:* npx eslint .
|
|
103
|
+
```
|
|
104
|
+
|
|
53
105
|
## Разработка
|
|
54
106
|
|
|
55
107
|
Нужен Node 20.19+ и Yarn 4 (через corepack: `corepack enable`).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,44 +1,18 @@
|
|
|
1
|
+
import * as node_modules__eslint_core_dist_cjs_types_cjs from 'node_modules/@eslint/core/dist/cjs/types.cjs';
|
|
2
|
+
import * as eslint from 'eslint';
|
|
1
3
|
import { Linter } from 'eslint';
|
|
2
4
|
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview Shared types for ESLint Core.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The human readable severity level used in a configuration.
|
|
9
|
-
*/
|
|
10
|
-
type SeverityName = "off" | "warn" | "error";
|
|
11
|
-
/**
|
|
12
|
-
* The numeric severity level for a rule.
|
|
13
|
-
*
|
|
14
|
-
* - `0` means off.
|
|
15
|
-
* - `1` means warn.
|
|
16
|
-
* - `2` means error.
|
|
17
|
-
*/
|
|
18
|
-
type SeverityLevel = 0 | 1 | 2;
|
|
19
|
-
/**
|
|
20
|
-
* The severity of a rule in a configuration.
|
|
21
|
-
*/
|
|
22
|
-
type Severity = SeverityName | SeverityLevel;
|
|
23
|
-
/**
|
|
24
|
-
* The configuration for a rule.
|
|
25
|
-
*/
|
|
26
|
-
type RuleConfig<RuleOptions extends unknown[] = unknown[]> = Severity | [Severity, ...Partial<RuleOptions>];
|
|
27
|
-
/**
|
|
28
|
-
* A collection of rules and their configurations.
|
|
29
|
-
*/
|
|
30
|
-
interface RulesConfig {
|
|
31
|
-
[key: string]: RuleConfig;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
5
|
declare const plugin: {
|
|
35
6
|
meta: {
|
|
36
7
|
name: string;
|
|
37
8
|
version: string;
|
|
38
9
|
};
|
|
39
|
-
rules: {
|
|
10
|
+
rules: {
|
|
11
|
+
'no-barrel-bypass': eslint.Rule.RuleModule;
|
|
12
|
+
};
|
|
40
13
|
configs: {
|
|
41
|
-
recommended: Linter.Config<RulesConfig>;
|
|
14
|
+
recommended: Linter.Config<node_modules__eslint_core_dist_cjs_types_cjs.RulesConfig>;
|
|
15
|
+
strict: Linter.Config<node_modules__eslint_core_dist_cjs_types_cjs.RulesConfig>;
|
|
42
16
|
};
|
|
43
17
|
};
|
|
44
18
|
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,718 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
|
+
import createDebug from 'debug';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
7
|
+
var PACKAGE_NAME = "eslint-plugin-weld";
|
|
8
|
+
function createLogger(scope) {
|
|
9
|
+
return createDebug(`${PACKAGE_NAME}:${scope}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/imports/nodes.ts
|
|
13
|
+
function isSpecifierNode(node) {
|
|
14
|
+
return node != null && node.type === "Literal" && typeof node.value === "string";
|
|
15
|
+
}
|
|
16
|
+
function createSpecifierVisitor(onSpecifier) {
|
|
17
|
+
return {
|
|
18
|
+
"ImportDeclaration, ImportExpression"(node) {
|
|
19
|
+
if (isSpecifierNode(node.source)) {
|
|
20
|
+
onSpecifier(node.source);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
CallExpression(node) {
|
|
24
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "require") {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const arg = node.arguments[0];
|
|
28
|
+
if (isSpecifierNode(arg)) {
|
|
29
|
+
onSpecifier(arg);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function replaceSpecifier(node, specifier) {
|
|
35
|
+
const quote = node.raw[0] ?? "'";
|
|
36
|
+
return (fixer) => fixer.replaceTextRange(node.range, `${quote}${specifier}${quote}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/extensions.ts
|
|
40
|
+
var ENTRY_BASENAME = "index";
|
|
41
|
+
var ENTRY_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"];
|
|
42
|
+
var MODULE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"];
|
|
43
|
+
var ENTRY_EXTENSION_SET = new Set(ENTRY_EXTENSIONS);
|
|
44
|
+
var MODULE_EXTENSION_SET = new Set(MODULE_EXTENSIONS);
|
|
45
|
+
function isEntryExtension(ext) {
|
|
46
|
+
return ENTRY_EXTENSION_SET.has(ext);
|
|
47
|
+
}
|
|
48
|
+
function entryFileName(ext) {
|
|
49
|
+
return `${ENTRY_BASENAME}.${ext}`;
|
|
50
|
+
}
|
|
51
|
+
var ENTRY_FILE_NAMES = ENTRY_EXTENSIONS.map(entryFileName);
|
|
52
|
+
function isEntryFileName(name, ext) {
|
|
53
|
+
return isEntryBasename(name) && isEntryExtension(ext);
|
|
54
|
+
}
|
|
55
|
+
function isEntryBasename(name) {
|
|
56
|
+
return name === ENTRY_BASENAME;
|
|
57
|
+
}
|
|
58
|
+
function isModuleExtension(ext) {
|
|
59
|
+
return MODULE_EXTENSION_SET.has(ext);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/path/posix.ts
|
|
63
|
+
function toPosix(path2) {
|
|
64
|
+
return path2.replace(/\\/g, "/");
|
|
65
|
+
}
|
|
66
|
+
function segments(path2) {
|
|
67
|
+
return path2.split("/").filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
function joinSegments(segs) {
|
|
70
|
+
return segs.length === 0 ? "/" : `/${segs.join("/")}`;
|
|
71
|
+
}
|
|
72
|
+
function joinPath(dir, segment) {
|
|
73
|
+
return dir === "/" ? `/${segment}` : `${dir}/${segment}`;
|
|
74
|
+
}
|
|
75
|
+
function basename(path2) {
|
|
76
|
+
return path2.slice(path2.lastIndexOf("/") + 1);
|
|
77
|
+
}
|
|
78
|
+
function splitExtension(base) {
|
|
79
|
+
const dot = base.lastIndexOf(".");
|
|
80
|
+
if (dot <= 0 || dot === base.length - 1) {
|
|
81
|
+
return { name: base, ext: "" };
|
|
82
|
+
}
|
|
83
|
+
return { name: base.slice(0, dot), ext: base.slice(dot + 1) };
|
|
84
|
+
}
|
|
85
|
+
function dirname(path2) {
|
|
86
|
+
const segs = segments(path2);
|
|
87
|
+
if (segs.length === 0) {
|
|
88
|
+
return "/";
|
|
89
|
+
}
|
|
90
|
+
return joinSegments(segs.slice(0, -1));
|
|
91
|
+
}
|
|
92
|
+
function resolvePath(base, path2) {
|
|
93
|
+
const segs = path2.startsWith("/") ? [] : segments(base);
|
|
94
|
+
for (const part of path2.split("/")) {
|
|
95
|
+
if (part === "" || part === ".") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (part === "..") {
|
|
99
|
+
if (segs.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
segs.pop();
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
segs.push(part);
|
|
106
|
+
}
|
|
107
|
+
return joinSegments(segs);
|
|
108
|
+
}
|
|
109
|
+
function commonPrefixLength(segsA, segsB) {
|
|
110
|
+
let i = 0;
|
|
111
|
+
while (i < segsA.length && i < segsB.length && segsA[i] === segsB[i]) {
|
|
112
|
+
i += 1;
|
|
113
|
+
}
|
|
114
|
+
return i;
|
|
115
|
+
}
|
|
116
|
+
function commonDepth(a, b) {
|
|
117
|
+
return commonPrefixLength(segments(a), segments(b));
|
|
118
|
+
}
|
|
119
|
+
function relativePath(from, to) {
|
|
120
|
+
const segsFrom = segments(from);
|
|
121
|
+
const segsTo = segments(to);
|
|
122
|
+
const i = commonPrefixLength(segsFrom, segsTo);
|
|
123
|
+
const ups = Array(segsFrom.length - i).fill("..");
|
|
124
|
+
const downs = segsTo.slice(i);
|
|
125
|
+
return [...ups, ...downs].join("/");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/imports/specifier.ts
|
|
129
|
+
function parseSpecifier(specifier, fromDir, aliases) {
|
|
130
|
+
if (specifier.includes("?") || specifier.includes("!")) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (isRelativeSpecifier(specifier)) {
|
|
134
|
+
return finalize(resolvePath(fromDir, specifier), { kind: "relative" });
|
|
135
|
+
}
|
|
136
|
+
const alias = matchAlias(specifier, aliases);
|
|
137
|
+
if (alias !== null) {
|
|
138
|
+
const suffix = specifier.slice(alias.prefix.length).replace(/^\/+/, "");
|
|
139
|
+
return finalize(resolvePath(alias.anchor, suffix), { kind: "alias", alias });
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function isRelativeSpecifier(specifier) {
|
|
144
|
+
return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
|
|
145
|
+
}
|
|
146
|
+
function pickLongest(aliases, key) {
|
|
147
|
+
let best = null;
|
|
148
|
+
let bestLength = -1;
|
|
149
|
+
for (const alias of aliases) {
|
|
150
|
+
const matched = key(alias);
|
|
151
|
+
if (matched === null || matched.length <= bestLength) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
best = alias;
|
|
155
|
+
bestLength = matched.length;
|
|
156
|
+
}
|
|
157
|
+
return best;
|
|
158
|
+
}
|
|
159
|
+
function matchAlias(specifier, aliases) {
|
|
160
|
+
return pickLongest(aliases, (alias) => covers(alias.prefix, specifier) ? alias.prefix : null);
|
|
161
|
+
}
|
|
162
|
+
function finalize(path2, form) {
|
|
163
|
+
if (path2 === null) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const { ext } = splitExtension(basename(path2));
|
|
167
|
+
if (ext !== "" && !isModuleExtension(ext)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return { path: path2, form, extension: ext === "" ? null : ext };
|
|
171
|
+
}
|
|
172
|
+
function renderSpecifier(form, fromDir, barrier, aliases, extension = null) {
|
|
173
|
+
const dir = renderDirectory(form, fromDir, barrier, aliases);
|
|
174
|
+
return extension === null ? dir : withEntry(dir, extension);
|
|
175
|
+
}
|
|
176
|
+
function renderDirectory(form, fromDir, barrier, aliases) {
|
|
177
|
+
if (form.kind === "relative") {
|
|
178
|
+
return renderRelative(fromDir, barrier);
|
|
179
|
+
}
|
|
180
|
+
if (covers(form.alias.anchor, barrier)) {
|
|
181
|
+
return renderAlias(form.alias, barrier);
|
|
182
|
+
}
|
|
183
|
+
const longest = longestCoveringAlias(aliases, barrier);
|
|
184
|
+
return longest === null ? renderRelative(fromDir, barrier) : renderAlias(longest, barrier);
|
|
185
|
+
}
|
|
186
|
+
function withEntry(dir, extension) {
|
|
187
|
+
const separator = dir.endsWith("/") ? "" : "/";
|
|
188
|
+
return `${dir}${separator}${entryFileName(extension)}`;
|
|
189
|
+
}
|
|
190
|
+
function covers(prefix, value) {
|
|
191
|
+
return value === prefix || value.startsWith(`${prefix}/`);
|
|
192
|
+
}
|
|
193
|
+
function longestCoveringAlias(aliases, dir) {
|
|
194
|
+
return pickLongest(aliases, (alias) => covers(alias.anchor, dir) ? alias.anchor : null);
|
|
195
|
+
}
|
|
196
|
+
function renderAlias(alias, barrier) {
|
|
197
|
+
if (barrier === alias.anchor) {
|
|
198
|
+
return alias.prefix;
|
|
199
|
+
}
|
|
200
|
+
return `${alias.prefix}/${relativePath(alias.anchor, barrier)}`;
|
|
201
|
+
}
|
|
202
|
+
function renderRelative(fromDir, barrier) {
|
|
203
|
+
const rel = relativePath(fromDir, barrier);
|
|
204
|
+
return rel.startsWith("./") || rel.startsWith("../") ? rel : `./${rel}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/settings/aliases.ts
|
|
208
|
+
var debug = createLogger("aliases");
|
|
209
|
+
function parseAliases(rawAliases, aliasesBaseUrl, source) {
|
|
210
|
+
if (typeof rawAliases !== "object" || rawAliases === null || Array.isArray(rawAliases)) {
|
|
211
|
+
throw new Error(`${source} must be an object`);
|
|
212
|
+
}
|
|
213
|
+
const base = resolvePath("/", aliasesBaseUrl) ?? "/";
|
|
214
|
+
const result = [];
|
|
215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const [key, rawValue] of Object.entries(rawAliases)) {
|
|
217
|
+
if (!hasValidStarShape(key)) {
|
|
218
|
+
debug('%s: dropped %s \u2014 key must have no "*" or end with "/*"', source, key);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const anchorsRaw = toAnchorList(key, rawValue, source);
|
|
222
|
+
const prefix = stripStarSuffix(key);
|
|
223
|
+
for (const anchorRaw of anchorsRaw) {
|
|
224
|
+
if (!hasValidStarShape(anchorRaw)) {
|
|
225
|
+
debug(
|
|
226
|
+
'%s: dropped %s -> %s \u2014 target must have no "*" or end with "/*"',
|
|
227
|
+
source,
|
|
228
|
+
key,
|
|
229
|
+
anchorRaw
|
|
230
|
+
);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const anchor = normalizeAnchor(anchorRaw, base);
|
|
234
|
+
if (anchor === null) {
|
|
235
|
+
debug(
|
|
236
|
+
"%s: dropped %s -> %s \u2014 not a directory (only index.<ext> stands for one) or above the repository root",
|
|
237
|
+
source,
|
|
238
|
+
key,
|
|
239
|
+
anchorRaw
|
|
240
|
+
);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const dedupeKey = `${prefix}\0${anchor}`;
|
|
244
|
+
if (seen.has(dedupeKey)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
seen.add(dedupeKey);
|
|
248
|
+
result.push({ prefix, anchor });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (debug.enabled) {
|
|
252
|
+
debug(
|
|
253
|
+
`${source} (aliasesBaseUrl ${aliasesBaseUrl}) resolved to:
|
|
254
|
+
${formatAliases(result)}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
function formatAliases(aliases) {
|
|
260
|
+
if (aliases.length === 0) {
|
|
261
|
+
return "(none)";
|
|
262
|
+
}
|
|
263
|
+
return aliases.map(({ prefix, anchor }) => `${prefix} - ${anchor}`).join("\n");
|
|
264
|
+
}
|
|
265
|
+
function toAnchorList(key, value, source) {
|
|
266
|
+
if (typeof value === "string") {
|
|
267
|
+
return [value];
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
for (const item of value) {
|
|
271
|
+
if (typeof item !== "string") {
|
|
272
|
+
throw new Error(`${source}['${key}'] must contain only strings`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
throw new Error(`${source}['${key}'] must be a string or an array of strings`);
|
|
278
|
+
}
|
|
279
|
+
function hasValidStarShape(value) {
|
|
280
|
+
const starCount = value.split("*").length - 1;
|
|
281
|
+
if (starCount > 1 || starCount === 1 && !value.endsWith("/*")) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
return stripStarSuffix(value) !== "";
|
|
285
|
+
}
|
|
286
|
+
function stripStarSuffix(value) {
|
|
287
|
+
return value.endsWith("/*") ? value.slice(0, -2) : value;
|
|
288
|
+
}
|
|
289
|
+
function normalizeAnchor(anchorRaw, base) {
|
|
290
|
+
if (anchorRaw.endsWith("/*")) {
|
|
291
|
+
return resolvePath(base, anchorRaw.slice(0, -2));
|
|
292
|
+
}
|
|
293
|
+
const lastSegment = basename(anchorRaw);
|
|
294
|
+
const { name, ext } = splitExtension(lastSegment);
|
|
295
|
+
if (ext === "") {
|
|
296
|
+
return resolvePath(base, anchorRaw);
|
|
297
|
+
}
|
|
298
|
+
if (!isEntryFileName(name, ext)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return resolvePath(base, anchorRaw.slice(0, -lastSegment.length));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/settings/weld.ts
|
|
305
|
+
var DEFAULT_ALIASES_BASE_URL = ".";
|
|
306
|
+
function getWeldSettings(settings) {
|
|
307
|
+
if (typeof settings !== "object" || settings === null) {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
const weld = settings.weld;
|
|
311
|
+
if (weld === void 0) {
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
if (typeof weld !== "object" || weld === null) {
|
|
315
|
+
throw new Error("settings.weld must be an object");
|
|
316
|
+
}
|
|
317
|
+
return weld;
|
|
318
|
+
}
|
|
319
|
+
function requireString(value, source) {
|
|
320
|
+
if (typeof value !== "string") {
|
|
321
|
+
throw new Error(`${source} must be a string, got ${typeof value}`);
|
|
322
|
+
}
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
function getRepoRoot(settings, override) {
|
|
326
|
+
if (override !== void 0) {
|
|
327
|
+
return requireString(override, "options.repoRoot");
|
|
328
|
+
}
|
|
329
|
+
const repoRoot = getWeldSettings(settings)?.repoRoot;
|
|
330
|
+
if (repoRoot === void 0) {
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
return requireString(repoRoot, "settings.weld.repoRoot");
|
|
334
|
+
}
|
|
335
|
+
function getAliasesBaseUrl(settings, override) {
|
|
336
|
+
if (override !== void 0) {
|
|
337
|
+
return requireString(override, "options.aliasesBaseUrl");
|
|
338
|
+
}
|
|
339
|
+
const aliasesBaseUrl = getWeldSettings(settings)?.aliasesBaseUrl;
|
|
340
|
+
if (aliasesBaseUrl === void 0) {
|
|
341
|
+
return DEFAULT_ALIASES_BASE_URL;
|
|
342
|
+
}
|
|
343
|
+
return requireString(aliasesBaseUrl, "settings.weld.aliasesBaseUrl");
|
|
344
|
+
}
|
|
345
|
+
function readAliases(settings, override, baseUrlOverride) {
|
|
346
|
+
const fromOverride = override !== void 0;
|
|
347
|
+
const rawAliases = fromOverride ? override : getWeldSettings(settings)?.aliases;
|
|
348
|
+
if (rawAliases === void 0) {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
const source = fromOverride ? "options.aliases" : "settings.weld.aliases";
|
|
352
|
+
return parseAliases(rawAliases, getAliasesBaseUrl(settings, baseUrlOverride), source);
|
|
353
|
+
}
|
|
354
|
+
var TTL_MS = 6e4;
|
|
355
|
+
var aliasesCache = [];
|
|
356
|
+
function peekAliases(settings, override, baseUrlOverride, time) {
|
|
357
|
+
for (const entry of aliasesCache) {
|
|
358
|
+
if (entry.expiresAt > time && entry.settings === settings && entry.override === override && entry.baseUrlOverride === baseUrlOverride) {
|
|
359
|
+
return entry.result;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
function rememberAliases(entry, time) {
|
|
365
|
+
aliasesCache = aliasesCache.filter(
|
|
366
|
+
(existing) => existing.expiresAt > time && !(existing.settings === entry.settings && existing.override === entry.override && existing.baseUrlOverride === entry.baseUrlOverride)
|
|
367
|
+
);
|
|
368
|
+
aliasesCache.push(entry);
|
|
369
|
+
return entry.result;
|
|
370
|
+
}
|
|
371
|
+
function getAliases(settings, override, baseUrlOverride) {
|
|
372
|
+
const time = Date.now();
|
|
373
|
+
const cached = peekAliases(settings, override, baseUrlOverride, time);
|
|
374
|
+
if (cached !== void 0) {
|
|
375
|
+
return cached;
|
|
376
|
+
}
|
|
377
|
+
const result = readAliases(settings, override, baseUrlOverride);
|
|
378
|
+
return rememberAliases(
|
|
379
|
+
{ settings, override, baseUrlOverride, result, expiresAt: time + TTL_MS },
|
|
380
|
+
time
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
var DRIVE_PREFIX = /^[A-Za-z]:/;
|
|
384
|
+
function isDriveRoot(realPath) {
|
|
385
|
+
return realPath.length === 2 && DRIVE_PREFIX.test(realPath);
|
|
386
|
+
}
|
|
387
|
+
function isAbsoluteRealPath(realPath) {
|
|
388
|
+
return realPath.startsWith("/") || DRIVE_PREFIX.test(realPath);
|
|
389
|
+
}
|
|
390
|
+
function normalizeRoot(root) {
|
|
391
|
+
const posix = toPosix(root);
|
|
392
|
+
if (posix === "/") {
|
|
393
|
+
return "";
|
|
394
|
+
}
|
|
395
|
+
return posix.endsWith("/") ? posix.slice(0, -1) : posix;
|
|
396
|
+
}
|
|
397
|
+
function joinReal(dir, name) {
|
|
398
|
+
return dir.endsWith("/") ? `${dir}${name}` : `${dir}/${name}`;
|
|
399
|
+
}
|
|
400
|
+
function parentOf(dir) {
|
|
401
|
+
const idx = dir.lastIndexOf("/");
|
|
402
|
+
if (idx === -1) {
|
|
403
|
+
return dir;
|
|
404
|
+
}
|
|
405
|
+
const head = dir.slice(0, idx);
|
|
406
|
+
if (head === "") {
|
|
407
|
+
return "/";
|
|
408
|
+
}
|
|
409
|
+
if (isDriveRoot(head)) {
|
|
410
|
+
return `${head}/`;
|
|
411
|
+
}
|
|
412
|
+
return head;
|
|
413
|
+
}
|
|
414
|
+
function sameSegment(left, right, index) {
|
|
415
|
+
if (index === 0 && DRIVE_PREFIX.test(left) && DRIVE_PREFIX.test(right)) {
|
|
416
|
+
return left.toUpperCase() === right.toUpperCase();
|
|
417
|
+
}
|
|
418
|
+
return left === right;
|
|
419
|
+
}
|
|
420
|
+
function resolveRealPath(base, target) {
|
|
421
|
+
const posixTarget = toPosix(target);
|
|
422
|
+
if (isAbsoluteRealPath(posixTarget)) {
|
|
423
|
+
return posixTarget;
|
|
424
|
+
}
|
|
425
|
+
const posixBase = toPosix(base);
|
|
426
|
+
const resolveBase = isDriveRoot(posixBase) ? `${posixBase}/` : posixBase;
|
|
427
|
+
const resolve = DRIVE_PREFIX.test(resolveBase) ? path.win32.resolve : path.posix.resolve;
|
|
428
|
+
return toPosix(resolve(resolveBase, posixTarget));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/host/root.ts
|
|
432
|
+
var debug2 = createLogger("root");
|
|
433
|
+
function findRepoRoot(startDir, exists) {
|
|
434
|
+
let current = toPosix(startDir);
|
|
435
|
+
let found = null;
|
|
436
|
+
debug2("searching repo root from %s", current);
|
|
437
|
+
for (; ; ) {
|
|
438
|
+
if (exists(joinReal(current, "package.json"))) {
|
|
439
|
+
found = current;
|
|
440
|
+
debug2("found package.json at %s", current);
|
|
441
|
+
}
|
|
442
|
+
if (exists(joinReal(current, ".git"))) {
|
|
443
|
+
debug2("found .git at %s, stopping", current);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
const parent = parentOf(current);
|
|
447
|
+
if (parent === current) {
|
|
448
|
+
debug2("reached filesystem root at %s", current);
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
current = parent;
|
|
452
|
+
}
|
|
453
|
+
debug2("resolved repo root: %o", found);
|
|
454
|
+
return found;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/host/fs.ts
|
|
458
|
+
var debug3 = createLogger("fs");
|
|
459
|
+
var TTL_MS2 = 6e5;
|
|
460
|
+
var NEGATIVE_TTL_MS = 5e3;
|
|
461
|
+
function peek(cache, key, time) {
|
|
462
|
+
const entry = cache.get(key);
|
|
463
|
+
return entry !== void 0 && entry.expiresAt > time ? entry.value : void 0;
|
|
464
|
+
}
|
|
465
|
+
function remember(cache, key, time, value) {
|
|
466
|
+
cache.set(key, { value, expiresAt: time + (value ? TTL_MS2 : NEGATIVE_TTL_MS) });
|
|
467
|
+
return value;
|
|
468
|
+
}
|
|
469
|
+
function createFsHost(root, options = {}) {
|
|
470
|
+
const normalizedRoot = normalizeRoot(root);
|
|
471
|
+
const rootSegments = segments(normalizedRoot);
|
|
472
|
+
const now = options.now ?? Date.now;
|
|
473
|
+
const nativeExists = options.exists ?? existsSync;
|
|
474
|
+
const entryPointCache = /* @__PURE__ */ new Map();
|
|
475
|
+
const directoryCache = /* @__PURE__ */ new Map();
|
|
476
|
+
function toRealDir(dir) {
|
|
477
|
+
return `${normalizedRoot}${dir}`;
|
|
478
|
+
}
|
|
479
|
+
function toVirtual(realPath) {
|
|
480
|
+
const posixPath = toPosix(realPath);
|
|
481
|
+
if (!isAbsoluteRealPath(posixPath)) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const pathSegments = segments(posixPath);
|
|
485
|
+
if (pathSegments.length < rootSegments.length) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
for (let i = 0; i < rootSegments.length; i += 1) {
|
|
489
|
+
if (!sameSegment(pathSegments[i], rootSegments[i], i)) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return joinSegments(pathSegments.slice(rootSegments.length));
|
|
494
|
+
}
|
|
495
|
+
function directoryExists(dir) {
|
|
496
|
+
const time = now();
|
|
497
|
+
const cached = peek(directoryCache, dir, time);
|
|
498
|
+
if (cached !== void 0) {
|
|
499
|
+
return cached;
|
|
500
|
+
}
|
|
501
|
+
if (missingParent(dir, time)) {
|
|
502
|
+
return remember(directoryCache, dir, time, false);
|
|
503
|
+
}
|
|
504
|
+
const found = nativeExists(toRealDir(dir));
|
|
505
|
+
debug3("check directory %s: %o", dir, found);
|
|
506
|
+
return remember(directoryCache, dir, time, found);
|
|
507
|
+
}
|
|
508
|
+
function missingParent(dir, time) {
|
|
509
|
+
const parent = dirname(dir);
|
|
510
|
+
if (parent === dir) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
return peek(directoryCache, parent, time) === false;
|
|
514
|
+
}
|
|
515
|
+
function hasEntryPoint(dir) {
|
|
516
|
+
if (!dir.startsWith("/")) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const time = now();
|
|
520
|
+
const cached = peek(entryPointCache, dir, time);
|
|
521
|
+
if (cached !== void 0) {
|
|
522
|
+
return cached;
|
|
523
|
+
}
|
|
524
|
+
if (!directoryExists(dir)) {
|
|
525
|
+
return remember(entryPointCache, dir, time, false);
|
|
526
|
+
}
|
|
527
|
+
const realDir = toRealDir(dir);
|
|
528
|
+
const found = ENTRY_FILE_NAMES.some((name) => nativeExists(joinReal(realDir, name)));
|
|
529
|
+
debug3("check entry point %s: %o", dir, found);
|
|
530
|
+
return remember(entryPointCache, dir, time, found);
|
|
531
|
+
}
|
|
532
|
+
return { hasEntryPoint, toVirtual };
|
|
533
|
+
}
|
|
534
|
+
var instanceCache = /* @__PURE__ */ new Map();
|
|
535
|
+
var repoRootCache = /* @__PURE__ */ new Map();
|
|
536
|
+
function findRepoRootCached(cwd) {
|
|
537
|
+
const cached = repoRootCache.get(cwd);
|
|
538
|
+
if (cached !== void 0) {
|
|
539
|
+
return cached;
|
|
540
|
+
}
|
|
541
|
+
const found = findRepoRoot(cwd, existsSync);
|
|
542
|
+
repoRootCache.set(cwd, found);
|
|
543
|
+
return found;
|
|
544
|
+
}
|
|
545
|
+
function resolveRoot(settings, cwd, repoRootOverride) {
|
|
546
|
+
const explicitRoot = getRepoRoot(settings, repoRootOverride);
|
|
547
|
+
if (explicitRoot !== void 0) {
|
|
548
|
+
return resolveRealPath(cwd, explicitRoot);
|
|
549
|
+
}
|
|
550
|
+
return findRepoRootCached(cwd) ?? cwd;
|
|
551
|
+
}
|
|
552
|
+
function getFsHost(settings, cwd, repoRootOverride) {
|
|
553
|
+
const root = resolveRoot(settings, cwd, repoRootOverride);
|
|
554
|
+
const cached = instanceCache.get(root);
|
|
555
|
+
if (cached) {
|
|
556
|
+
return cached;
|
|
557
|
+
}
|
|
558
|
+
debug3("getFsHost: creating instance for root %s", root);
|
|
559
|
+
const created = createFsHost(root);
|
|
560
|
+
instanceCache.set(root, created);
|
|
561
|
+
return created;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/rules/context.ts
|
|
565
|
+
var WELD_OPTION_PROPERTIES = {
|
|
566
|
+
repoRoot: { type: "string" },
|
|
567
|
+
aliasesBaseUrl: { type: "string" },
|
|
568
|
+
aliases: { type: "object" }
|
|
569
|
+
};
|
|
570
|
+
function resolveWeldContext(context, fsHostOverride) {
|
|
571
|
+
const options = context.options[0] ?? {};
|
|
572
|
+
const fsHost = getFsHost(context.settings, context.cwd, options.repoRoot);
|
|
573
|
+
const fromFile = fsHost.toVirtual(context.filename);
|
|
574
|
+
if (fromFile === null) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
fromFile,
|
|
579
|
+
aliases: getAliases(context.settings, options.aliases, options.aliasesBaseUrl),
|
|
580
|
+
fsHost,
|
|
581
|
+
options
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/rules/no-barrel-bypass/core/barrier.ts
|
|
586
|
+
function isIndexFile(targetPath) {
|
|
587
|
+
return isEntryBasename(splitExtension(basename(targetPath)).name);
|
|
588
|
+
}
|
|
589
|
+
function findBarrier(fromDir, targetPath, hasEntryPoint) {
|
|
590
|
+
const targetDir = dirname(targetPath);
|
|
591
|
+
const targetDirSegments = segments(targetDir);
|
|
592
|
+
const depth = commonDepth(fromDir, targetDir);
|
|
593
|
+
let dir = joinSegments(targetDirSegments.slice(0, depth));
|
|
594
|
+
for (let i = depth; i < targetDirSegments.length; i += 1) {
|
|
595
|
+
dir = joinPath(dir, targetDirSegments[i]);
|
|
596
|
+
if (hasEntryPoint(dir)) {
|
|
597
|
+
const atTargetDir = i === targetDirSegments.length - 1;
|
|
598
|
+
return atTargetDir && isIndexFile(targetPath) ? null : dir;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/rules/no-barrel-bypass/pipeline.ts
|
|
605
|
+
var debug4 = createLogger("no-barrel-bypass");
|
|
606
|
+
function createChecker({ fromFile, aliases }, host) {
|
|
607
|
+
const fromDir = dirname(fromFile);
|
|
608
|
+
return function checkImport(specifier) {
|
|
609
|
+
const target = parseSpecifier(specifier, fromDir, aliases);
|
|
610
|
+
if (target === null) {
|
|
611
|
+
debug4("%s: %s external dependency", fromFile, specifier);
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const barrier = findBarrier(fromDir, target.path, host.hasEntryPoint);
|
|
615
|
+
if (barrier === null) {
|
|
616
|
+
debug4("%s: %s -> %s, no barrier on the way", fromFile, specifier, target.path);
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
return renderSpecifier(target.form, fromDir, barrier, aliases, target.extension);
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/rules/no-barrel-bypass/index.ts
|
|
624
|
+
var debug5 = createLogger("no-barrel-bypass");
|
|
625
|
+
var messages = {
|
|
626
|
+
bypass: "Import bypasses the module barrel. Use '{{suggestion}}' instead of '{{original}}'.",
|
|
627
|
+
useBarrel: "Import through '{{suggestion}}'."
|
|
628
|
+
};
|
|
629
|
+
function createRule(fsHost) {
|
|
630
|
+
return {
|
|
631
|
+
meta: {
|
|
632
|
+
type: "problem",
|
|
633
|
+
docs: {
|
|
634
|
+
description: "disallow imports that reach into a module past its barrel",
|
|
635
|
+
url: "https://github.com/jt4d-lab/weld/blob/master/docs/rules/no-barrel-bypass.md"
|
|
636
|
+
},
|
|
637
|
+
fixable: "code",
|
|
638
|
+
hasSuggestions: true,
|
|
639
|
+
schema: [
|
|
640
|
+
{
|
|
641
|
+
type: "object",
|
|
642
|
+
properties: {
|
|
643
|
+
fix: { type: "boolean" },
|
|
644
|
+
...WELD_OPTION_PROPERTIES
|
|
645
|
+
},
|
|
646
|
+
additionalProperties: false
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
messages
|
|
650
|
+
},
|
|
651
|
+
create(context) {
|
|
652
|
+
const weld = resolveWeldContext(context);
|
|
653
|
+
if (weld === null) {
|
|
654
|
+
return {};
|
|
655
|
+
}
|
|
656
|
+
const { fromFile } = weld;
|
|
657
|
+
const applyFix = weld.options.fix ?? true;
|
|
658
|
+
const checkImport = createChecker(weld, weld.fsHost);
|
|
659
|
+
return createSpecifierVisitor((sourceNode) => {
|
|
660
|
+
const original = sourceNode.value;
|
|
661
|
+
const suggestion = checkImport(original);
|
|
662
|
+
if (suggestion === null) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
debug5(
|
|
666
|
+
"%s: crosses barrier, %s -> %s (fix=%o)",
|
|
667
|
+
fromFile,
|
|
668
|
+
original,
|
|
669
|
+
suggestion,
|
|
670
|
+
applyFix
|
|
671
|
+
);
|
|
672
|
+
const fix = replaceSpecifier(sourceNode, suggestion);
|
|
673
|
+
context.report({
|
|
674
|
+
node: sourceNode,
|
|
675
|
+
messageId: "bypass",
|
|
676
|
+
data: { suggestion, original },
|
|
677
|
+
...applyFix ? { fix } : { suggest: [{ messageId: "useBarrel", data: { suggestion }, fix }] }
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
4
683
|
|
|
5
684
|
// src/rules/index.ts
|
|
6
|
-
var rules = {
|
|
685
|
+
var rules = {
|
|
686
|
+
"no-barrel-bypass": createRule()
|
|
687
|
+
};
|
|
7
688
|
|
|
8
689
|
// src/index.ts
|
|
9
|
-
var {
|
|
690
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
10
691
|
var recommended = {
|
|
11
692
|
name: "weld/recommended",
|
|
12
|
-
rules: {
|
|
693
|
+
rules: {
|
|
694
|
+
"weld/no-barrel-bypass": "error"
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
var strict = {
|
|
698
|
+
name: "weld/strict",
|
|
699
|
+
rules: {
|
|
700
|
+
"weld/no-barrel-bypass": "error"
|
|
701
|
+
}
|
|
13
702
|
};
|
|
14
703
|
var plugin = {
|
|
15
|
-
meta: {
|
|
704
|
+
meta: {
|
|
705
|
+
name: PACKAGE_NAME,
|
|
706
|
+
version
|
|
707
|
+
},
|
|
16
708
|
rules,
|
|
17
|
-
configs: {
|
|
709
|
+
configs: {
|
|
710
|
+
recommended,
|
|
711
|
+
strict
|
|
712
|
+
}
|
|
18
713
|
};
|
|
19
714
|
recommended.plugins = { weld: plugin };
|
|
715
|
+
strict.plugins = { weld: plugin };
|
|
20
716
|
var index_default = plugin;
|
|
21
717
|
|
|
22
718
|
export { index_default as default };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rules/index.ts","../src/index.ts"],"names":[],"mappings":";;;;;AAEO,IAAM,QAAQ,EAAC;;;ACItB,IAAM,EAAE,MAAM,OAAA,EAAQ,GAAI,cAAc,MAAA,CAAA,IAAA,CAAY,GAAG,EAAE,iBAAiB,CAAA;AAK1E,IAAM,WAAA,GAA6B;AAAA,EAC/B,IAAA,EAAM,kBAAA;AAAA,EACN,OAAO;AACX,CAAA;AAEA,IAAM,MAAA,GAAS;AAAA,EACX,IAAA,EAAM,EAAE,IAAA,EAAM,OAAA,EAAQ;AAAA,EACtB,KAAA;AAAA,EACA,OAAA,EAAS,EAAE,WAAA;AACf,CAAA;AAEA,WAAA,CAAY,OAAA,GAAU,EAAE,IAAA,EAAM,MAAA,EAAO;AAErC,IAAO,aAAA,GAAQ","file":"index.js","sourcesContent":["import type { Rule } from 'eslint';\n\nexport const rules = {} satisfies Record<string, Rule.RuleModule>;\n\nexport type RuleName = keyof typeof rules;\n","import { createRequire } from 'node:module';\n\nimport type { ESLint, Linter } from 'eslint';\n\nimport { rules } from './rules/index.js';\n\nconst { name, version } = createRequire(import.meta.url)('../package.json') as {\n name: string;\n version: string;\n};\n\nconst recommended: Linter.Config = {\n name: 'weld/recommended',\n rules: {},\n};\n\nconst plugin = {\n meta: { name, version },\n rules,\n configs: { recommended },\n} satisfies ESLint.Plugin;\n\nrecommended.plugins = { weld: plugin };\n\nexport default plugin;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/debug.ts","../src/imports/nodes.ts","../src/extensions.ts","../src/path/posix.ts","../src/imports/specifier.ts","../src/settings/aliases.ts","../src/settings/weld.ts","../src/host/real-path.ts","../src/host/root.ts","../src/host/fs.ts","../src/rules/context.ts","../src/rules/no-barrel-bypass/core/barrier.ts","../src/rules/no-barrel-bypass/pipeline.ts","../src/rules/no-barrel-bypass/index.ts","../src/rules/index.ts","../src/index.ts"],"names":["path","debug","TTL_MS"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug-логирование плагина. Namespace строится здесь и только здесь: README объявляет\n * `DEBUG=eslint-plugin-weld:*` контрактом, а три независимых литерала по модулям этот контракт\n * ничем не удерживали.\n */\n\nimport createDebug from 'debug';\nimport type { Debugger } from 'debug';\n\n/** Имя пакета — оно же корень debug-namespace и `plugin.meta.name`. */\nexport const PACKAGE_NAME = 'eslint-plugin-weld';\n\n/** Логгер модуля: `scope` — короткое имя вроде `fs`, `root`, `aliases`. */\nexport function createLogger(scope: string): Debugger {\n return createDebug(`${PACKAGE_NAME}:${scope}`);\n}\n","/**\n * Обход импортов в AST: где в дереве лежит строка-специфаер и как её заменить. Про баррели,\n * границы модулей и любое конкретное правило слой не знает — это общая инфраструктура импортов,\n * как и `specifier.ts`.\n *\n * Здесь же зафиксированы решения, которые иначе каждое правило принимало бы заново: репортим на\n * самом литерале пути, а не на всём операторе; кавычку для правки берём из исходника; `require`\n * считается импортом, а `export ... from` — нет.\n */\n\nimport type { Rule } from 'eslint';\nimport type { Literal } from 'estree';\n\n/**\n * Строковый литерал специфаера — он же узел отчёта: сообщение указывается на самом пути, а не на\n * всём операторе импорта. `Rule.Node` в пересечении — ради `context.report`, `range` обязателен.\n */\nexport type SpecifierNode = Rule.Node & {\n type: 'Literal';\n value: string;\n raw: string;\n range: [number, number];\n};\n\nfunction isSpecifierNode(node: { type: string } | null | undefined): node is SpecifierNode {\n return (\n node != null &&\n node.type === 'Literal' &&\n typeof (node as unknown as Literal).value === 'string'\n );\n}\n\n/**\n * Слушатели для `create()`, вызывающие `onSpecifier` на каждом строковом специфаере импорта:\n * `import ... from`, динамический `import()` и `require()`.\n *\n * `export ... from` намеренно не обходится: у реэкспорта своя семантика границ, и правило,\n * которому он нужен, должно включить его осознанно, а не получить молча.\n */\nexport function createSpecifierVisitor(\n onSpecifier: (node: SpecifierNode) => void,\n): Rule.RuleListener {\n return {\n 'ImportDeclaration, ImportExpression'(\n node: Rule.Node & { source?: { type: string } | null },\n ) {\n if (isSpecifierNode(node.source)) {\n onSpecifier(node.source);\n }\n },\n CallExpression(node) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'require') {\n return;\n }\n const arg = node.arguments[0];\n if (isSpecifierNode(arg)) {\n onSpecifier(arg);\n }\n },\n };\n}\n\n/**\n * Правка, подставляющая `specifier` вместо содержимого узла. Кавычка берётся из исходного текста —\n * правило не навязывает файлу свой стиль кавычек.\n */\nexport function replaceSpecifier(node: SpecifierNode, specifier: string): Rule.ReportFixer {\n const quote = node.raw[0] ?? \"'\";\n return (fixer) => fixer.replaceTextRange(node.range, `${quote}${specifier}${quote}`);\n}\n","/**\n * Словарь расширений файлов — общий для всех слоёв, как и `src/path/posix.ts`.\n *\n * Списка два, потому что понятия разные, хотя сегодня их состав совпадает. Точка входа — это файл,\n * который делает директорию модулем; модуль — это то, что вообще можно импортировать как код.\n * `.vue`/`.svelte` были бы модулями, но не обязательно точками входа; `.json` импортируется, но\n * баррелем не бывает. Одним списком на оба вопроса расширение любого из них молча меняло бы ответ\n * на второй.\n */\n\n/**\n * Имя файла точки входа без расширения. Ответ на тот же вопрос, что и {@link ENTRY_EXTENSIONS} —\n * какой файл делает директорию модулем, — поэтому живёт рядом с ним, а не литералом по слоям.\n */\nconst ENTRY_BASENAME = 'index';\n\n/** Расширения файла точки входа (`index.<ext>`) — по ним ищутся границы модулей. */\nexport const ENTRY_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'mts', 'cts', 'mjs', 'cjs'] as const;\n\n/**\n * Расширения, при которых специфаер считается импортом кода внутрь репозитория. Специфаер с чужим\n * расширением (`./styles.css`, `./logo.svg`) правила не проверяют.\n */\nconst MODULE_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'mts', 'cts', 'mjs', 'cjs'] as const;\n\nconst ENTRY_EXTENSION_SET: ReadonlySet<string> = new Set(ENTRY_EXTENSIONS);\nconst MODULE_EXTENSION_SET: ReadonlySet<string> = new Set(MODULE_EXTENSIONS);\n\n/** Входит ли расширение (без точки) в {@link ENTRY_EXTENSIONS}. */\nfunction isEntryExtension(ext: string): boolean {\n return ENTRY_EXTENSION_SET.has(ext);\n}\n\n/** Имя файла точки входа с расширением: `index.ts`, `index.mjs`. */\nexport function entryFileName(ext: string): string {\n return `${ENTRY_BASENAME}.${ext}`;\n}\n\n/** Все имена файлов точки входа — перебор при поиске границы модуля на диске. */\nexport const ENTRY_FILE_NAMES: readonly string[] = ENTRY_EXTENSIONS.map(entryFileName);\n\n/** Имя файла (без директории) — точка входа? Расширение обязано входить в {@link ENTRY_EXTENSIONS}. */\nexport function isEntryFileName(name: string, ext: string): boolean {\n return isEntryBasename(name) && isEntryExtension(ext);\n}\n\n/**\n * Имя без расширения — точка входа? В отличие от {@link isEntryFileName} расширение не\n * спрашивается: специфаер может быть записан и без него (`./other/index`), а вопрос «пробивает ли\n * импорт границу» от расширения не зависит. Ответ живёт здесь, а не у спрашивающего: иначе «что\n * делает директорию модулем» знали бы два слоя и разъехались бы при первой же правке.\n */\nexport function isEntryBasename(name: string): boolean {\n return name === ENTRY_BASENAME;\n}\n\n/** Входит ли расширение (без точки) в {@link MODULE_EXTENSIONS}. */\nexport function isModuleExtension(ext: string): boolean {\n return MODULE_EXTENSION_SET.has(ext);\n}\n","/**\n * Сегментные операции над виртуальными путями, без `node:path`.\n *\n * Виртуальный путь — posix-строка от корня репозитория (`/src/feature/x.ts`), корень — `/`.\n * `toPosix` — исключение: работает с реальными путями на границе (`createFsHost`), до перевода в\n * виртуальные; остальные функции здесь принимают только виртуальные пути.\n */\n\n/** Заменяет `\\` на `/`. Не резолвит `.`/`..` и ничего не знает о виртуальных путях. */\nexport function toPosix(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\n/** Непустые сегменты пути. Корень (`/`) и пустая строка дают пустой массив. */\nexport function segments(path: string): string[] {\n return path.split('/').filter(Boolean);\n}\n\n/** Собирает виртуальный путь из сегментов. Пустой список — корень (`/`). */\nexport function joinSegments(segs: string[]): string {\n return segs.length === 0 ? '/' : `/${segs.join('/')}`;\n}\n\n/**\n * Дописывает сегмент к виртуальной директории, не удваивая `/` у корня. Спуск по дереву наращивает\n * путь этой функцией вместо `joinSegments` от среза — знание «у корня разделителя не нужно» живёт\n * здесь, а не у каждого, кто идёт вниз.\n */\nexport function joinPath(dir: string, segment: string): string {\n return dir === '/' ? `/${segment}` : `${dir}/${segment}`;\n}\n\n/** Последний сегмент виртуального пути после последнего `/`. Корень даёт пустую строку. */\nexport function basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n\n/** Разбивает basename на имя и расширение (без точки). Расширения нет — `ext` пустой. */\nexport function splitExtension(base: string): { name: string; ext: string } {\n // Точка в начале — не расширение (`.storybook`), точка в конце — пустое расширение (`x.`).\n const dot = base.lastIndexOf('.');\n if (dot <= 0 || dot === base.length - 1) {\n return { name: base, ext: '' };\n }\n\n return { name: base.slice(0, dot), ext: base.slice(dot + 1) };\n}\n\n/** Директория выше `path`. У корня родителя нет — возвращает сам корень. */\nexport function dirname(path: string): string {\n const segs = segments(path);\n if (segs.length === 0) {\n return '/';\n }\n\n return joinSegments(segs.slice(0, -1));\n}\n\n/**\n * Резолвит `path` (относительный, с `.`/`..`, или абсолютный) от виртуальной директории `base`.\n * Выход `..` выше `/` — за пределами репозитория, невыразимо виртуальным путём — возвращает `null`.\n */\nexport function resolvePath(base: string, path: string): string | null {\n const segs = path.startsWith('/') ? [] : segments(base);\n\n for (const part of path.split('/')) {\n if (part === '' || part === '.') {\n continue;\n }\n if (part === '..') {\n if (segs.length === 0) {\n return null;\n }\n segs.pop();\n continue;\n }\n segs.push(part);\n }\n\n return joinSegments(segs);\n}\n\n/** Длина общего префикса двух списков сегментов. */\nfunction commonPrefixLength(segsA: string[], segsB: string[]): number {\n let i = 0;\n while (i < segsA.length && i < segsB.length && segsA[i] === segsB[i]) {\n i += 1;\n }\n\n return i;\n}\n\n/**\n * Глубина самой глубокой общей директории в сегментах. У виртуальных путей общий корень `/` есть\n * всегда, поэтому глубина — от `0`.\n *\n * Отдаётся числом, а не путём: спрашивают об общей директории только чтобы отсчитать от неё\n * сегменты, и собранная строка тут же разбиралась бы обратно.\n */\nexport function commonDepth(a: string, b: string): number {\n return commonPrefixLength(segments(a), segments(b));\n}\n\n/**\n * Путь от директории `from` до `to`, без префикса `./` (его добавляет `renderSpecifier`).\n * Совпадающие пути дают пустую строку.\n */\nexport function relativePath(from: string, to: string): string {\n const segsFrom = segments(from);\n const segsTo = segments(to);\n\n const i = commonPrefixLength(segsFrom, segsTo);\n\n const ups = Array<string>(segsFrom.length - i).fill('..');\n const downs = segsTo.slice(i);\n\n return [...ups, ...downs].join('/');\n}\n","/**\n * Соответствие «строка импорта ↔ виртуальный путь»: `parseSpecifier` разбирает специфаер в цель,\n * `renderSpecifier` собирает специфаер обратно в той же форме записи. Про барьеры, баррели и любое\n * другое правило слой не знает — это общая инфраструктура импортов.\n *\n * Строка импорта сама и есть путь: относительная резолвится от директории файла, алиасная —\n * подстановкой якоря. Резолвер не нужен — существование цели проверяет `import/no-unresolved`.\n */\n\nimport { entryFileName, isModuleExtension } from '@/extensions.js';\nimport { basename, relativePath, resolvePath, splitExtension } from '@/path/index.js';\nimport type { Alias } from '@/settings/index.js';\n\nexport type Form = { kind: 'relative' } | { kind: 'alias'; alias: Alias };\n\n/**\n * `extension` — расширение, явно записанное в специфаере (без точки), либо `null`. Это факт про\n * форму записи, а не про диск: в TS-проекте под Node-ESM пишут `./x.js`, хотя на диске `x.ts`.\n */\nexport type Target = { path: string; form: Form; extension: string | null };\n\n/**\n * `null`, если специфаер не выражает путь внутрь репозитория: голый пакет, `@scope/pkg`,\n * абсолютный специфаер, специфаер с `?`/`!`, последний сегмент с расширением вне\n * `MODULE_EXTENSIONS`, либо относительный подъём `..` выше виртуального корня.\n */\nexport function parseSpecifier(\n specifier: string,\n fromDir: string,\n aliases: Alias[],\n): Target | null {\n if (specifier.includes('?') || specifier.includes('!')) {\n return null;\n }\n\n if (isRelativeSpecifier(specifier)) {\n return finalize(resolvePath(fromDir, specifier), { kind: 'relative' });\n }\n\n const alias = matchAlias(specifier, aliases);\n if (alias !== null) {\n // `matchAlias` уже проверил границу по сегменту, поэтому хвост — либо пустой, либо `/…`;\n // ведущие слэши снимаются, чтобы `resolvePath` не принял хвост за абсолютный путь.\n const suffix = specifier.slice(alias.prefix.length).replace(/^\\/+/, '');\n return finalize(resolvePath(alias.anchor, suffix), { kind: 'alias', alias });\n }\n\n return null;\n}\n\nfunction isRelativeSpecifier(specifier: string): boolean {\n return (\n specifier === '.' ||\n specifier === '..' ||\n specifier.startsWith('./') ||\n specifier.startsWith('../')\n );\n}\n\n/**\n * Самый длинный алиас, подходящий по `key`; `key` возвращает `null`, если алиас не подходит. При\n * равной длине побеждает первая запись по порядку объявления.\n */\nfunction pickLongest(aliases: Alias[], key: (alias: Alias) => string | null): Alias | null {\n let best: Alias | null = null;\n let bestLength = -1;\n\n for (const alias of aliases) {\n const matched = key(alias);\n if (matched === null || matched.length <= bestLength) {\n continue;\n }\n best = alias;\n bestLength = matched.length;\n }\n\n return best;\n}\n\n/** Самый длинный подходящий префикс; при равных префиксах — первая запись по порядку. */\nfunction matchAlias(specifier: string, aliases: Alias[]): Alias | null {\n return pickLongest(aliases, (alias) => (covers(alias.prefix, specifier) ? alias.prefix : null));\n}\n\nfunction finalize(path: string | null, form: Form): Target | null {\n if (path === null) {\n return null;\n }\n\n const { ext } = splitExtension(basename(path));\n if (ext !== '' && !isModuleExtension(ext)) {\n return null;\n }\n\n return { path, form, extension: ext === '' ? null : ext };\n}\n\n/**\n * Обратный рендер: директория `barrier` (граница) в специфаер в форме исходного импорта.\n * Форма выигрывает у пути цели — путь про форму записи ничего не знает.\n *\n * `extension` (расширение исходного специфаера) продолжает ту же логику: импорт с явным\n * расширением рендерится в `<barrier>/index.<ext>`, потому что там, где расширения обязательны\n * (Node-ESM), импорт голой директории не резолвится. Расширение берётся из исходного импорта, а не\n * с диска: в TS под Node-ESM на диске лежит `index.ts`, а писать полагается `index.js`.\n */\nexport function renderSpecifier(\n form: Form,\n fromDir: string,\n barrier: string,\n aliases: Alias[],\n extension: string | null = null,\n): string {\n const dir = renderDirectory(form, fromDir, barrier, aliases);\n return extension === null ? dir : withEntry(dir, extension);\n}\n\nfunction renderDirectory(form: Form, fromDir: string, barrier: string, aliases: Alias[]): string {\n if (form.kind === 'relative') {\n return renderRelative(fromDir, barrier);\n }\n\n if (covers(form.alias.anchor, barrier)) {\n return renderAlias(form.alias, barrier);\n }\n\n const longest = longestCoveringAlias(aliases, barrier);\n return longest === null ? renderRelative(fromDir, barrier) : renderAlias(longest, barrier);\n}\n\n/** Дописывает точку входа к специфаеру директории, не удваивая `/`. */\nfunction withEntry(dir: string, extension: string): string {\n const separator = dir.endsWith('/') ? '' : '/';\n return `${dir}${separator}${entryFileName(extension)}`;\n}\n\n/**\n * `value` равен `prefix` или лежит под ним — граница проверяется по сегменту, а не по символам\n * (`@/features` не покрывает `@/features-old`). Один вопрос на обе стороны алиаса: слева от него\n * так сопоставляется префикс со специфаером, справа — якорь с директорией.\n */\nfunction covers(prefix: string, value: string): boolean {\n return value === prefix || value.startsWith(`${prefix}/`);\n}\n\n/** Самый длинный якорь, покрывающий `dir`; `null`, если ни один не покрывает. */\nfunction longestCoveringAlias(aliases: Alias[], dir: string): Alias | null {\n return pickLongest(aliases, (alias) => (covers(alias.anchor, dir) ? alias.anchor : null));\n}\n\nfunction renderAlias(alias: Alias, barrier: string): string {\n if (barrier === alias.anchor) {\n return alias.prefix;\n }\n return `${alias.prefix}/${relativePath(alias.anchor, barrier)}`;\n}\n\nfunction renderRelative(fromDir: string, barrier: string): string {\n const rel = relativePath(fromDir, barrier);\n return rel.startsWith('./') || rel.startsWith('../') ? rel : `./${rel}`;\n}\n","import { createLogger } from '@/debug.js';\nimport { isEntryFileName } from '@/extensions.js';\nimport { basename, resolvePath, splitExtension } from '@/path/index.js';\n\nconst debug = createLogger('aliases');\n\nexport type Alias = { prefix: string; anchor: string };\n\n/**\n * Разбирает значение `settings.weld.aliases` (формат `paths` из tsconfig) в список алиасов, готовых\n * для `parseSpecifier`/`renderSpecifier`. Обе настройки приходят уже добытыми — читает их\n * `src/settings/weld.ts`; он же решает, что делать с отсутствующей настройкой, поэтому `undefined`\n * сюда не доходит. `<base>` — виртуальный путь от корня репозитория, не от `cwd`: `aliasesBaseUrl`\n * отсчитывается от корня репозитория. `source` называет место значения в конфиге в сообщениях об\n * ошибках.\n */\nexport function parseAliases(rawAliases: unknown, aliasesBaseUrl: string, source: string): Alias[] {\n if (typeof rawAliases !== 'object' || rawAliases === null || Array.isArray(rawAliases)) {\n throw new Error(`${source} must be an object`);\n }\n\n const base = resolvePath('/', aliasesBaseUrl) ?? '/';\n\n const result: Alias[] = [];\n const seen = new Set<string>();\n\n for (const [key, rawValue] of Object.entries(rawAliases)) {\n if (!hasValidStarShape(key)) {\n debug('%s: dropped %s — key must have no \"*\" or end with \"/*\"', source, key);\n continue;\n }\n const anchorsRaw = toAnchorList(key, rawValue, source);\n const prefix = stripStarSuffix(key);\n\n for (const anchorRaw of anchorsRaw) {\n if (!hasValidStarShape(anchorRaw)) {\n debug(\n '%s: dropped %s -> %s — target must have no \"*\" or end with \"/*\"',\n source,\n key,\n anchorRaw,\n );\n continue;\n }\n const anchor = normalizeAnchor(anchorRaw, base);\n if (anchor === null) {\n debug(\n '%s: dropped %s -> %s — not a directory (only index.<ext> stands for one) or above the repository root',\n source,\n key,\n anchorRaw,\n );\n continue;\n }\n\n const dedupeKey = `${prefix}\\0${anchor}`;\n if (seen.has(dedupeKey)) {\n continue;\n }\n seen.add(dedupeKey);\n result.push({ prefix, anchor });\n }\n }\n\n if (debug.enabled) {\n debug(\n `${source} (aliasesBaseUrl ${aliasesBaseUrl}) resolved to:\\n${formatAliases(result)}`,\n );\n }\n return result;\n}\n\n/** Для debug: по строке на алиас, `<префикс> - <якорь>`. Пустой список — явным словом. */\nfunction formatAliases(aliases: Alias[]): string {\n if (aliases.length === 0) {\n return '(none)';\n }\n\n return aliases.map(({ prefix, anchor }) => `${prefix} - ${anchor}`).join('\\n');\n}\n\n/** Значение записи `paths`: строка (частая опечатка) или массив строк. */\nfunction toAnchorList(key: string, value: unknown, source: string): string[] {\n if (typeof value === 'string') {\n return [value];\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item !== 'string') {\n throw new Error(`${source}['${key}'] must contain only strings`);\n }\n }\n return value as string[];\n }\n\n throw new Error(`${source}['${key}'] must be a string or an array of strings`);\n}\n\n/**\n * Принимаются только записи без `*` или оканчивающиеся на `/*`; звёздочка в середине —\n * отбрасывается. `/*` тоже отбрасывается: `stripStarSuffix` дал бы пустой префикс/якорь, а пустая\n * строка — префикс любого абсолютного специфайера (`''` + `/` — начало любого `/foo`), из-за чего\n * запись стала бы вести себя как алиас на абсолютные пути, которые правило не обрабатывает.\n */\nfunction hasValidStarShape(value: string): boolean {\n const starCount = value.split('*').length - 1;\n if (starCount > 1 || (starCount === 1 && !value.endsWith('/*'))) {\n return false;\n }\n\n return stripStarSuffix(value) !== '';\n}\n\nfunction stripStarSuffix(value: string): string {\n return value.endsWith('/*') ? value.slice(0, -2) : value;\n}\n\n/**\n * Директория, которую обозначает запись `paths`. `null`, если запись — обычный файл\n * (не `index.<известное расширение>`) и директорией не выражается, либо якорь уходит `..`-подъёмом\n * выше виртуального корня.\n */\nfunction normalizeAnchor(anchorRaw: string, base: string): string | null {\n if (anchorRaw.endsWith('/*')) {\n return resolvePath(base, anchorRaw.slice(0, -2));\n }\n\n const lastSegment = basename(anchorRaw);\n const { name, ext } = splitExtension(lastSegment);\n\n if (ext === '') {\n return resolvePath(base, anchorRaw);\n }\n\n if (!isEntryFileName(name, ext)) {\n return null;\n }\n\n // Директория записи — сам `anchorRaw` без последнего сегмента; хвостовой `/` `resolvePath` съедает.\n return resolvePath(base, anchorRaw.slice(0, -lastSegment.length));\n}\n","/**\n * Единственная точка чтения `settings.weld`. Наружу слой отдаёт не сырую секцию, а геттер на каждую\n * настройку — `getRepoRoot` / `getAliasesBaseUrl` / `getAliases`; формат конфига за пределами\n * `src/settings/` не знает никто.\n *\n * Каждый геттер принимает необязательный `override` — значение этой настройки, подставляемое вместо\n * конфига. Оно тоже приходит из пользовательского конфига (опции правила), поэтому проверяется теми\n * же правилами, что и `settings.weld`; в сообщении об ошибке называется источник — `options.<имя>`\n * вместо `settings.weld.<имя>`.\n */\n\nimport type { Alias } from '@/settings/aliases.js';\nimport { parseAliases } from '@/settings/aliases.js';\n\n/** `aliasesBaseUrl` по умолчанию — сам корень репозитория. */\nconst DEFAULT_ALIASES_BASE_URL = '.';\n\n/** `settings.weld`, если задан. `undefined` без ошибки — секция необязательна. */\nfunction getWeldSettings(settings: unknown): Record<string, unknown> | undefined {\n if (typeof settings !== 'object' || settings === null) {\n return undefined;\n }\n\n const weld = (settings as Record<string, unknown>).weld;\n if (weld === undefined) {\n return undefined;\n }\n\n if (typeof weld !== 'object' || weld === null) {\n throw new Error('settings.weld must be an object');\n }\n\n return weld as Record<string, unknown>;\n}\n\nfunction requireString(value: unknown, source: string): string {\n if (typeof value !== 'string') {\n throw new Error(`${source} must be a string, got ${typeof value}`);\n }\n\n return value;\n}\n\n/**\n * `settings.weld.repoRoot` как он записан в конфиге — реальный путь, не виртуальный. Резолв\n * относительного значения и всё прочее знание о реальной ФС — за границей `src/host/`.\n * `undefined` — корень не задан, вызывающий ищет его сам.\n */\nexport function getRepoRoot(settings: unknown, override?: unknown): string | undefined {\n if (override !== undefined) {\n return requireString(override, 'options.repoRoot');\n }\n\n const repoRoot = getWeldSettings(settings)?.repoRoot;\n if (repoRoot === undefined) {\n return undefined;\n }\n\n return requireString(repoRoot, 'settings.weld.repoRoot');\n}\n\n/** `settings.weld.aliasesBaseUrl`; не задан — `'.'` (сам корень репозитория). */\nexport function getAliasesBaseUrl(settings: unknown, override?: unknown): string {\n if (override !== undefined) {\n return requireString(override, 'options.aliasesBaseUrl');\n }\n\n const aliasesBaseUrl = getWeldSettings(settings)?.aliasesBaseUrl;\n if (aliasesBaseUrl === undefined) {\n return DEFAULT_ALIASES_BASE_URL;\n }\n\n return requireString(aliasesBaseUrl, 'settings.weld.aliasesBaseUrl');\n}\n\nfunction readAliases(settings: unknown, override: unknown, baseUrlOverride: unknown): Alias[] {\n const fromOverride = override !== undefined;\n const rawAliases = fromOverride ? override : getWeldSettings(settings)?.aliases;\n if (rawAliases === undefined) {\n return [];\n }\n\n const source = fromOverride ? 'options.aliases' : 'settings.weld.aliases';\n return parseAliases(rawAliases, getAliasesBaseUrl(settings, baseUrlOverride), source);\n}\n\n/**\n * TTL записи кэша алиасов. Минута: сравнение по ссылке само по себе корректно (конфиг ESLint в\n * рамках прогона неизменен), поэтому TTL здесь не про свежесть ответа, а про то, чтобы записи не\n * жили вечно — иначе перечитанный конфиг языкового сервера навсегда оставлял бы в кэше и старый\n * объект `settings`, и разобранные по нему алиасы. Протухание посреди прогона ничего не стоит:\n * цена промаха — один разбор `paths`, десятки микросекунд.\n */\nconst TTL_MS = 60_000;\n\ntype AliasesCacheEntry = {\n settings: unknown;\n override: unknown;\n baseUrlOverride: unknown;\n result: Alias[];\n expiresAt: number;\n};\n\n/**\n * Кэш разобранных алиасов: правило спрашивает их на каждый линтуемый файл, а аргументы приходят те\n * же — в flat config объект `settings` у всех файлов одного конфиг-блока один и тот же, и опции\n * правила тоже. Попадание избавляет от повторного разбора `paths` и от повторного `debug`-лога о\n * нём.\n *\n * Список с линейным поиском, а не `Map`: ключ здесь — тройка ссылок, строкового ключа у неё нет, а\n * собирать его пришлось бы обходом тех же `paths`. Записей при этом единицы — по одной на\n * конфиг-блок ESLint.\n */\nlet aliasesCache: AliasesCacheEntry[] = [];\n\nfunction peekAliases(\n settings: unknown,\n override: unknown,\n baseUrlOverride: unknown,\n time: number,\n): Alias[] | undefined {\n for (const entry of aliasesCache) {\n if (\n entry.expiresAt > time &&\n entry.settings === settings &&\n entry.override === override &&\n entry.baseUrlOverride === baseUrlOverride\n ) {\n return entry.result;\n }\n }\n\n return undefined;\n}\n\n/**\n * Протухшие записи выбрасываются здесь, а не по таймеру: кэш растёт только на промахах, на промахе\n * же и чистится.\n */\nfunction rememberAliases(entry: AliasesCacheEntry, time: number): Alias[] {\n aliasesCache = aliasesCache.filter(\n (existing) =>\n existing.expiresAt > time &&\n !(\n existing.settings === entry.settings &&\n existing.override === entry.override &&\n existing.baseUrlOverride === entry.baseUrlOverride\n ),\n );\n aliasesCache.push(entry);\n return entry.result;\n}\n\n/**\n * Алиасы из `settings.weld`. Нет `aliases` — `[]`.\n *\n * `baseUrlOverride` — override той настройки, от которой отсчитываются якоря: алиасы без\n * `aliasesBaseUrl` переопределить нельзя, эти два значения имеют смысл только в паре.\n *\n * Результат берётся из кэша (см. {@link aliasesCache}), поэтому при попадании возвращается тот же\n * массив, что и в прошлый раз — считается он неизменяемым, потребители его только читают.\n */\nexport function getAliases(\n settings: unknown,\n override?: unknown,\n baseUrlOverride?: unknown,\n): Alias[] {\n const time = Date.now();\n\n const cached = peekAliases(settings, override, baseUrlOverride, time);\n if (cached !== undefined) {\n return cached;\n }\n\n // Запоминается только успешный разбор: на сломанном конфиге ESLint должен ругаться на каждом\n // файле, а не на первом.\n const result = readAliases(settings, override, baseUrlOverride);\n return rememberAliases(\n { settings, override, baseUrlOverride, result, expiresAt: time + TTL_MS },\n time,\n );\n}\n","/**\n * Арифметика реальных путей — внутренний словарь слоя `src/host/`, наружу не выходит.\n *\n * Здесь и только здесь живёт знание про формы реального пути: буквы дисков, корень ФС, склейка и\n * подъём вверх. `fs.ts` (перевод реального пути в виртуальный) и `root.ts` (поиск корня репозитория)\n * задают одни и те же вопросы; пока каждый отвечал на них у себя, «что считается диском» было\n * записано тремя литералами, и следующая правка кросс-платформенного поведения нашла бы не все.\n */\n\nimport path from 'node:path';\n\nimport { toPosix } from '@/path/index.js';\n\n/** Ведущий Windows-диск (`C:`) реального пути после `toPosix`. */\nconst DRIVE_PREFIX = /^[A-Za-z]:/;\n\n/** Путь — ровно диск без слэша (`C:`): для `win32` это не корень, а текущая директория диска. */\nfunction isDriveRoot(realPath: string): boolean {\n return realPath.length === 2 && DRIVE_PREFIX.test(realPath);\n}\n\n/**\n * Абсолютность реального пути: unix (`/…`) или Windows-диск (`C:…`) — уже после `toPosix`.\n *\n * `path.win32.isAbsolute` здесь не подходит: он считает неабсолютными drive-relative пути `C:` и\n * `C:foo`, а нам нужен признак «путь несёт диск или корень» — иначе `resolveRealPath` пытался бы\n * дорезолвить `C:` от `cwd`, и root `C:` (то, во что `normalizeRoot` превращает корень\n * Windows-ФС `C:/`) уехал бы в директорию запуска ESLint.\n */\nexport function isAbsoluteRealPath(realPath: string): boolean {\n return realPath.startsWith('/') || DRIVE_PREFIX.test(realPath);\n}\n\n/**\n * Нормализует root: `toPosix`, без завершающего слэша. Корень ФС (`/`, `C:/`) — как `''` / `C:`,\n * чтобы `root + vpath` не удваивал `/`.\n *\n * `node:path` тут не помощник: `normalize`/`resolve` сохраняют корень как `/` и `C:\\`, а нужна\n * именно строка-префикс для склейки `root + vpath`, для корня ФС пустая.\n */\nexport function normalizeRoot(root: string): string {\n const posix = toPosix(root);\n if (posix === '/') {\n return '';\n }\n\n return posix.endsWith('/') ? posix.slice(0, -1) : posix;\n}\n\n/** Соединяет директорию (в т.ч. корень `/` или `C:/`) с именем файла, не удваивая `/`. */\nexport function joinReal(dir: string, name: string): string {\n return dir.endsWith('/') ? `${dir}${name}` : `${dir}/${name}`;\n}\n\n/**\n * Директория выше `dir`. У корня ФС (`/`, `C:/`) родителя нет — возвращает сам корень, по чему\n * вызывающий код и определяет достижение верха.\n */\nexport function parentOf(dir: string): string {\n const idx = dir.lastIndexOf('/');\n if (idx === -1) {\n return dir;\n }\n\n const head = dir.slice(0, idx);\n if (head === '') {\n return '/';\n }\n if (isDriveRoot(head)) {\n return `${head}/`;\n }\n\n return head;\n}\n\n/**\n * Сравнение сегментов реального пути. Первый сегмент может быть Windows-диском — тогда буква\n * сравнивается без учёта регистра (`C:` и `c:` — один диск); остальные сегменты — строго:\n * регистрозависимость реальных ФС различается, и строгое сравнение — единственный ответ, не\n * зависящий от платформы запуска.\n */\nexport function sameSegment(left: string, right: string, index: number): boolean {\n if (index === 0 && DRIVE_PREFIX.test(left) && DRIVE_PREFIX.test(right)) {\n return left.toUpperCase() === right.toUpperCase();\n }\n\n return left === right;\n}\n\n/**\n * Резолвит `target` от `base` — оба реальные пути. `target` абсолютный (unix или Windows-диск) —\n * возвращается как есть; иначе схлопывается с `base` силами `node:path`.\n *\n * Ветка `node:path` выбирается по форме `base`, а не по текущей платформе: `win32.resolve` знает\n * про диски, `posix.resolve` — про unix-пути, и на абсолютном `base` обе чистые (к `process.cwd()`\n * они обращаются, только когда ни один аргумент не абсолютен). Один `win32.resolve` на оба случая\n * не годится: unix-путь без диска он достраивает диском из `process.cwd()`, и под Windows `/repo`\n * стал бы `C:/repo` — результат зависел бы от платформы, на которой запущен ESLint.\n *\n * Голый диск `C:` для `win32` — не корень, а «текущая директория диска C», поэтому перед резолвом\n * он дополняется до `C:/` (в таком виде root и приходит из `normalizeRoot`).\n */\nexport function resolveRealPath(base: string, target: string): string {\n const posixTarget = toPosix(target);\n if (isAbsoluteRealPath(posixTarget)) {\n return posixTarget;\n }\n\n const posixBase = toPosix(base);\n const resolveBase = isDriveRoot(posixBase) ? `${posixBase}/` : posixBase;\n const resolve = DRIVE_PREFIX.test(resolveBase) ? path.win32.resolve : path.posix.resolve;\n\n return toPosix(resolve(resolveBase, posixTarget));\n}\n","import { createLogger } from '@/debug.js';\nimport { toPosix } from '@/path/index.js';\n\nimport { joinReal, parentOf } from '@/host/real-path.js';\n\nconst debug = createLogger('root');\n\n/**\n * Подъём от `startDir` вверх, запоминает последнюю (самую верхнюю) директорию с `package.json`.\n * Останавливается на первой директории с `.git` (включительно — её `package.json` ещё учитывается)\n * или, если `.git` не встретился, на корне файловой системы. Ничего не нашли → `null`.\n *\n * Принимает реальный путь и реальный `exists` — работает до создания `FsHost`.\n */\nexport function findRepoRoot(startDir: string, exists: (path: string) => boolean): string | null {\n let current = toPosix(startDir);\n let found: string | null = null;\n\n debug('searching repo root from %s', current);\n\n for (;;) {\n if (exists(joinReal(current, 'package.json'))) {\n found = current;\n debug('found package.json at %s', current);\n }\n\n if (exists(joinReal(current, '.git'))) {\n debug('found .git at %s, stopping', current);\n break;\n }\n\n const parent = parentOf(current);\n if (parent === current) {\n debug('reached filesystem root at %s', current);\n break;\n }\n current = parent;\n }\n\n debug('resolved repo root: %o', found);\n return found;\n}\n","import { existsSync } from 'node:fs';\n\nimport { createLogger } from '@/debug.js';\nimport { ENTRY_FILE_NAMES } from '@/extensions.js';\nimport { dirname, joinSegments, segments, toPosix } from '@/path/index.js';\nimport { getRepoRoot } from '@/settings/index.js';\n\nimport {\n joinReal,\n normalizeRoot,\n resolveRealPath,\n sameSegment,\n isAbsoluteRealPath,\n} from '@/host/real-path.js';\nimport { findRepoRoot } from '@/host/root.js';\n\nconst debug = createLogger('fs');\n\n/** Граница ФС для правил: только виртуальные пути от корня репозитория (`/src/feature/x.ts`). */\nexport type FsHost = {\n /**\n * Есть ли в директории `dir` (виртуальный путь) точка входа — файл `index.<ext>` с одним из\n * `ENTRY_EXTENSIONS`. Именно этот вопрос, а не сырое «существует ли файл»: ответ на\n * директорию один, и кэшируется он одной записью вместо записи на каждое расширение.\n *\n * Границу намеренно держим узкой — примитив добавляется, когда его просит правило, а не\n * заранее: каждый метод здесь обязаны реализовать все фейки.\n */\n hasEntryPoint(dir: string): boolean;\n /** `null` — `realPath` вне root или не абсолютный. */\n toVirtual(realPath: string): string | null;\n};\n\ntype CacheEntry = { value: boolean; expiresAt: number };\ntype Cache = Map<string, CacheEntry>;\n\ntype CreateFsHostOptions = {\n now?: () => number;\n exists?: (realPath: string) => boolean;\n};\n\n/**\n * TTL положительного ответа. Десять минут: линт монорепы идёт минутами, а найденный `index.<ext>`\n * в рамках одного прогона никуда не девается — под работающим ESLint файлы не удаляют. Меньший TTL\n * просто заставлял бы перепроверять диск посреди прогона.\n */\nconst TTL_MS = 600_000;\n\n/**\n * TTL отрицательного ответа — заметно короче. Асимметрия отвечает асимметрии самих ответов:\n * «баррель есть» опровергается удалением файла, «барреля нет» — тем, что его дописали, а именно это\n * и делают в редакторе через секунды после того, как правило отрепортило нарушение. Долгий TTL\n * заставлял бы плагин в языковом сервере репортить уже созданный `index.<ext>` ещё десять минут.\n *\n * Кэш от этого не обесценивается: он нужен, чтобы отсутствующая ветка стоила одного обращения к ФС\n * вместо обращения на каждый сегмент, а весь спуск укладывается в линт одного файла — то есть в\n * миллисекунды. Пяти секунд хватает на пачку файлов, ссылающихся в одну и ту же мёртвую ветку.\n */\nconst NEGATIVE_TTL_MS = 5_000;\n\n/** Живое (непротухшее) значение кэша или `undefined`. */\nfunction peek(cache: Cache, key: string, time: number): boolean | undefined {\n const entry = cache.get(key);\n return entry !== undefined && entry.expiresAt > time ? entry.value : undefined;\n}\n\nfunction remember(cache: Cache, key: string, time: number, value: boolean): boolean {\n cache.set(key, { value, expiresAt: time + (value ? TTL_MS : NEGATIVE_TTL_MS) });\n return value;\n}\n\n/**\n * `createFsHost` — единственное место, знающее про реальные пути. Всё, что уходит вглубь правил, —\n * виртуальные пути; реальный root в публичный тип не входит, живёт в замыкании.\n */\nexport function createFsHost(root: string, options: CreateFsHostOptions = {}): FsHost {\n const normalizedRoot = normalizeRoot(root);\n const rootSegments = segments(normalizedRoot);\n const now = options.now ?? Date.now;\n const nativeExists = options.exists ?? existsSync;\n const entryPointCache: Cache = new Map();\n const directoryCache: Cache = new Map();\n\n /**\n * Реальный путь директории по виртуальному. Виртуальный путь всегда начинается с `/`, поэтому\n * для root-как-корня-ФС (`normalizedRoot === ''`) склейка сама даёт корректный путь от `/`.\n */\n function toRealDir(dir: string): string {\n return `${normalizedRoot}${dir}`;\n }\n\n function toVirtual(realPath: string): string | null {\n const posixPath = toPosix(realPath);\n if (!isAbsoluteRealPath(posixPath)) {\n return null;\n }\n\n const pathSegments = segments(posixPath);\n if (pathSegments.length < rootSegments.length) {\n return null;\n }\n for (let i = 0; i < rootSegments.length; i += 1) {\n if (!sameSegment(pathSegments[i] as string, rootSegments[i] as string, i)) {\n return null;\n }\n }\n\n return joinSegments(pathSegments.slice(rootSegments.length));\n }\n\n /**\n * Существует ли сама директория. Отдельный кэш и отдельный вопрос: несуществующие пути приходят\n * не поодиночке, а целыми ветками (промах алиаса, опечатка в специфаере, чужой корень) — и\n * каждая такая директория стоила бы перебора всех точек входа вместо одного обращения к ФС.\n */\n function directoryExists(dir: string): boolean {\n const time = now();\n const cached = peek(directoryCache, dir, time);\n if (cached !== undefined) {\n return cached;\n }\n\n if (missingParent(dir, time)) {\n // Родителя нет — значит нет и потомка, спрашивать диск не о чем.\n return remember(directoryCache, dir, time, false);\n }\n\n const found = nativeExists(toRealDir(dir));\n debug('check directory %s: %o', dir, found);\n return remember(directoryCache, dir, time, found);\n }\n\n /**\n * Известно ли уже, что родителя `dir` нет. Только по кэшу, без обращения к диску: спуск к цели\n * идёт сверху вниз, поэтому про родителя ответ к этому моменту есть — а несуществующие пути\n * приходят ветками, и без этого каждый сегмент отсутствующей ветки стоил бы своего обращения к\n * ФС. Отвечать на промах кэша рекурсивной проверкой предков не годится: она стоила бы обращения\n * на каждого предка там, где хватало одного.\n */\n function missingParent(dir: string, time: number): boolean {\n const parent = dirname(dir);\n if (parent === dir) {\n return false;\n }\n\n return peek(directoryCache, parent, time) === false;\n }\n\n /** Кэш по директории, с TTL: диск опрашивается на промахе или после истечения записи. */\n function hasEntryPoint(dir: string): boolean {\n if (!dir.startsWith('/')) {\n return false;\n }\n\n const time = now();\n const cached = peek(entryPointCache, dir, time);\n if (cached !== undefined) {\n return cached;\n }\n\n if (!directoryExists(dir)) {\n // Ответ запоминается и здесь: иначе каждый повторный вопрос про ту же отсутствующую\n // директорию заново проходил бы промах кэша точек входа, хотя ответ уже известен.\n return remember(entryPointCache, dir, time, false);\n }\n\n const realDir = toRealDir(dir);\n const found = ENTRY_FILE_NAMES.some((name) => nativeExists(joinReal(realDir, name)));\n debug('check entry point %s: %o', dir, found);\n return remember(entryPointCache, dir, time, found);\n }\n\n return { hasEntryPoint, toVirtual };\n}\n\nconst instanceCache = new Map<string, FsHost>();\nconst repoRootCache = new Map<string, string | null>();\n\nfunction findRepoRootCached(cwd: string): string | null {\n // `findRepoRoot` возвращает `string | null`, поэтому `undefined` однозначно значит «не кэшировано».\n const cached = repoRootCache.get(cwd);\n if (cached !== undefined) {\n return cached;\n }\n\n const found = findRepoRoot(cwd, existsSync);\n repoRootCache.set(cwd, found);\n return found;\n}\n\nfunction resolveRoot(settings: unknown, cwd: string, repoRootOverride?: unknown): string {\n const explicitRoot = getRepoRoot(settings, repoRootOverride);\n if (explicitRoot !== undefined) {\n return resolveRealPath(cwd, explicitRoot);\n }\n\n return findRepoRootCached(cwd) ?? cwd;\n}\n\n/**\n * `resolveRoot` + кэш инстансов по root — иначе TTL-кэш обращений к диску обнулялся бы на каждом\n * файле.\n *\n * `repoRootOverride` — значение repoRoot из опций правила; разбирает и проверяет его всё тот же\n * `getRepoRoot`, `src/host/` про формат конфига по-прежнему ничего не знает. Кэш инстансов ключуется\n * уже резолвнутым root, поэтому файлы с разным override не делят инстанс.\n */\nexport function getFsHost(settings: unknown, cwd: string, repoRootOverride?: unknown): FsHost {\n const root = resolveRoot(settings, cwd, repoRootOverride);\n\n const cached = instanceCache.get(root);\n if (cached) {\n return cached;\n }\n\n debug('getFsHost: creating instance for root %s', root);\n const created = createFsHost(root);\n instanceCache.set(root, created);\n return created;\n}\n\n/** Сбрасывает кэш инстансов `getFsHost` и мемоизацию `findRepoRoot` — для изоляции тестов. */\nexport function resetFsHostCaches(): void {\n instanceCache.clear();\n repoRootCache.clear();\n}\n","/**\n * Общий каркас правил: настройки WELD, объявленные один раз, и связка «ESLint-контекст → всё, что\n * правилу нужно до первого узла».\n *\n * Живёт выше `src/host/` и `src/settings/`: `getRepoRoot`/`getAliases` знают формат конфига,\n * `getFsHost` знает про диск, а решение «настройки правила перекрывают `settings.weld`» не\n * принадлежит ни тому, ни другому. Сами слои при этом не изолированы полностью: `host` берёт корень\n * репозитория через `getRepoRoot` — осознанная односторонняя зависимость `host → settings`\n * (см. `getFsHost` в `src/host/fs.ts`).\n *\n * Правило не перечисляет `repoRoot`/`aliasesBaseUrl`/`aliases` ни у себя в `meta.schema`, ни в типе\n * своих опций и не собирает `FsHost` руками — иначе четвёртая общая настройка потребовала бы правки\n * каждого правила по отдельности.\n */\n\nimport type { Rule } from 'eslint';\n\nimport type { FsHost } from '@/host/index.js';\nimport { getFsHost } from '@/host/index.js';\nimport type { Alias } from '@/settings/index.js';\nimport { getAliases } from '@/settings/index.js';\n\n/**\n * Свойства `meta.schema`, общие для всех правил: те же настройки, что и в `settings.weld`, но\n * заданные на самом правиле. Правило подмешивает их к своим спредом.\n */\nexport const WELD_OPTION_PROPERTIES = {\n repoRoot: { type: 'string' },\n aliasesBaseUrl: { type: 'string' },\n aliases: { type: 'object' },\n} as const;\n\n/**\n * Значения общих опций правила. Типы уже отсеяны `meta.schema` — в геттеры они идут как `unknown`,\n * а здесь нужны, чтобы подмешать общие опции к собственным опциям правила. Наружу тип не выходит:\n * правилу незачем его называть, пересечение делается здесь.\n */\ntype WeldOptions = {\n repoRoot?: string;\n aliasesBaseUrl?: string;\n aliases?: Record<string, unknown>;\n};\n\n/** Всё, что правило берёт из контекста до обхода AST. */\nexport type WeldContext<TOwnOptions = unknown> = {\n /** Файл, который сейчас линтуется, — виртуальный путь от корня репозитория. */\n fromFile: string;\n aliases: Alias[];\n fsHost: FsHost;\n /**\n * Опции правила, уже разобранные: собственные опции правила плюс общие. Правило читает их\n * отсюда, а не из `context.options[0]`: иначе каждое повторяло бы каст и дефолт, и форма опций\n * знала бы о себе в двух местах.\n */\n options: TOwnOptions & WeldOptions;\n};\n\n/**\n * `null` — линтуемый файл вне корня репозитория, и правилу нечего проверять: виртуального пути у\n * него нет.\n *\n * `fsHostOverride` — шов для тестов, общий на все правила: подменяет файловую систему целиком, и\n * тогда опция `repoRoot` ни на что не влияет. Шов именно параметром, а не модульным состоянием,\n * чтобы тесты не зависели от порядка запуска.\n *\n * `TOwnOptions` — только собственные опции правила: общие известны здесь и подмешиваются сами.\n */\nexport function resolveWeldContext<TOwnOptions = unknown>(\n context: Rule.RuleContext,\n fsHostOverride?: FsHost,\n): WeldContext<TOwnOptions> | null {\n const options = (context.options[0] ?? {}) as TOwnOptions & WeldOptions;\n const fsHost = fsHostOverride ?? getFsHost(context.settings, context.cwd, options.repoRoot);\n\n const fromFile = fsHost.toVirtual(context.filename);\n if (fromFile === null) {\n return null;\n }\n\n return {\n fromFile,\n aliases: getAliases(context.settings, options.aliases, options.aliasesBaseUrl),\n fsHost,\n options,\n };\n}\n","/**\n * Арифметика границ модулей: находит первую точку входа (`index`), которую пробивает импорт из\n * `fromDir` в `targetPath`, поднимаясь от их общей директории вниз к цели.\n */\n\nimport { isEntryBasename } from '@/extensions.js';\nimport {\n basename,\n commonDepth,\n dirname,\n joinPath,\n joinSegments,\n segments,\n splitExtension,\n} from '@/path/index.js';\n\nfunction isIndexFile(targetPath: string): boolean {\n return isEntryBasename(splitExtension(basename(targetPath)).name);\n}\n\n/**\n * Возвращает виртуальный путь границы, которую пробивает импорт, либо `null`, если нарушения нет.\n * `hasEntryPoint` вызывается только для директорий строго между общим предком `fromDir`/`targetPath`\n * и `targetPath` — границы, внутри которых уже находится `fromDir`, не проверяются.\n */\nexport function findBarrier(\n fromDir: string,\n targetPath: string,\n hasEntryPoint: (dir: string) => boolean,\n): string | null {\n const targetDir = dirname(targetPath);\n const targetDirSegments = segments(targetDir);\n\n // Глубина общей директории — она же индекс первого сегмента ниже неё.\n const depth = commonDepth(fromDir, targetDir);\n\n // Спуск от общей директории вниз к цели: путь наращивается посегментно, а не пересобирается.\n let dir = joinSegments(targetDirSegments.slice(0, depth));\n for (let i = depth; i < targetDirSegments.length; i += 1) {\n dir = joinPath(dir, targetDirSegments[i] as string);\n if (hasEntryPoint(dir)) {\n const atTargetDir = i === targetDirSegments.length - 1;\n return atTargetDir && isIndexFile(targetPath) ? null : dir;\n }\n }\n\n return null;\n}\n","/**\n * Вход в ядро: собирает `parseSpecifier` → `findBarrier` → `renderSpecifier` в одну проверку.\n */\n\nimport { createLogger } from '@/debug.js';\nimport type { FsHost } from '@/host/index.js';\nimport { parseSpecifier, renderSpecifier } from '@/imports/index.js';\nimport { dirname } from '@/path/index.js';\nimport type { Alias } from '@/settings/index.js';\n\nimport { findBarrier } from '@/rules/no-barrel-bypass/core/barrier.js';\n\nconst debug = createLogger('no-barrel-bypass');\n\ntype CheckerInput = { fromFile: string; aliases: Alias[] };\n\nexport type Host = Pick<FsHost, 'hasEntryPoint'>;\n\n/**\n * Проверка одного специфаера: возвращает исправленный специфаер при нарушении границы, иначе `null`\n * — нарушения нет. Найденная граница наружу не отдаётся: она нужна только для рендера пути.\n *\n * Проверка собирается на файл, а не на импорт: `fromDir` от линтуемого файла не зависит от того,\n * какой специфаер сейчас разбирается.\n */\nexport function createChecker(\n { fromFile, aliases }: CheckerInput,\n host: Host,\n): (specifier: string) => string | null {\n const fromDir = dirname(fromFile);\n\n return function checkImport(specifier: string): string | null {\n const target = parseSpecifier(specifier, fromDir, aliases);\n if (target === null) {\n debug('%s: %s external dependency', fromFile, specifier);\n return null;\n }\n\n const barrier = findBarrier(fromDir, target.path, host.hasEntryPoint);\n if (barrier === null) {\n debug('%s: %s -> %s, no barrier on the way', fromFile, specifier, target.path);\n return null;\n }\n\n return renderSpecifier(target.form, fromDir, barrier, aliases, target.extension);\n };\n}\n","/**\n * Правило `weld/no-barrel-bypass`: ловит импорты, входящие внутрь модуля мимо его точки входа\n * (`index.<ext>`), и предлагает исправленный путь через `checkImport`.\n *\n * Всё, что не про баррели, — общее: настройки и `FsHost` даёт `resolveWeldContext`, обход и правку\n * специфаеров — `src/imports/nodes.ts`. Здесь остаётся вердикт и текст сообщения.\n */\n\nimport type { Rule } from 'eslint';\n\nimport { createLogger } from '@/debug.js';\nimport type { FsHost } from '@/host/index.js';\nimport type { SpecifierNode } from '@/imports/index.js';\nimport { createSpecifierVisitor, replaceSpecifier } from '@/imports/index.js';\nimport { WELD_OPTION_PROPERTIES, resolveWeldContext } from '@/rules/context.js';\n\nimport { createChecker } from '@/rules/no-barrel-bypass/pipeline.js';\n\nconst debug = createLogger('no-barrel-bypass');\n\nconst messages = {\n bypass: \"Import bypasses the module barrel. Use '{{suggestion}}' instead of '{{original}}'.\",\n useBarrel: \"Import through '{{suggestion}}'.\",\n};\n\n/**\n * Своя опция у правила одна; общие к ней подмешивает `resolveWeldContext`, см.\n * {@link WELD_OPTION_PROPERTIES}.\n */\ntype RuleOptions = { fix?: boolean };\n\n/**\n * `fsHost` — для инъекции в тестах; без него файловую систему собирает `resolveWeldContext` из\n * `context.settings`/`context.cwd` и опции `repoRoot`. При инъекции опция `repoRoot` ни на что не\n * влияет.\n */\nexport function createRule(fsHost?: FsHost): Rule.RuleModule {\n return {\n meta: {\n type: 'problem',\n docs: {\n description: 'disallow imports that reach into a module past its barrel',\n url: 'https://github.com/jt4d-lab/weld/blob/master/docs/rules/no-barrel-bypass.md',\n },\n fixable: 'code',\n hasSuggestions: true,\n schema: [\n {\n type: 'object',\n properties: {\n fix: { type: 'boolean' },\n ...WELD_OPTION_PROPERTIES,\n },\n additionalProperties: false,\n },\n ],\n messages,\n },\n create(context) {\n const weld = resolveWeldContext<RuleOptions>(context, fsHost);\n if (weld === null) {\n return {};\n }\n\n const { fromFile } = weld;\n const applyFix = weld.options.fix ?? true;\n const checkImport = createChecker(weld, weld.fsHost);\n\n return createSpecifierVisitor((sourceNode: SpecifierNode) => {\n const original = sourceNode.value;\n const suggestion = checkImport(original);\n\n if (suggestion === null) {\n return;\n }\n\n debug(\n '%s: crosses barrier, %s -> %s (fix=%o)',\n fromFile,\n original,\n suggestion,\n applyFix,\n );\n const fix = replaceSpecifier(sourceNode, suggestion);\n\n context.report({\n node: sourceNode,\n messageId: 'bypass',\n data: { suggestion, original },\n ...(applyFix\n ? { fix }\n : { suggest: [{ messageId: 'useBarrel', data: { suggestion }, fix }] }),\n });\n });\n },\n };\n}\n","import type { Rule } from 'eslint';\n\nimport { createRule as createNoBarrelBypassRule } from '@/rules/no-barrel-bypass/index.js';\n\nexport const rules = {\n 'no-barrel-bypass': createNoBarrelBypassRule(),\n} satisfies Record<string, Rule.RuleModule>;\n","import { createRequire } from 'node:module';\n\nimport type { ESLint, Linter } from 'eslint';\n\nimport { PACKAGE_NAME } from '@/debug.js';\nimport { rules } from '@/rules/index.js';\n\n/** Версию проставляет release-workflow перед упаковкой, поэтому она читается из `package.json`. */\nconst { version } = createRequire(import.meta.url)('../package.json') as { version: string };\n\nconst recommended: Linter.Config = {\n name: 'weld/recommended',\n rules: {\n 'weld/no-barrel-bypass': 'error',\n },\n};\n\nconst strict: Linter.Config = {\n name: 'weld/strict',\n rules: {\n 'weld/no-barrel-bypass': 'error',\n },\n};\n\nconst plugin = {\n meta: {\n name: PACKAGE_NAME,\n version,\n },\n rules,\n configs: {\n recommended,\n strict,\n },\n} satisfies ESLint.Plugin;\n\nrecommended.plugins = { weld: plugin };\nstrict.plugins = { weld: plugin };\n\nexport default plugin;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-weld",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "ESLint-плагин для проверки правил WELD (Well-Encapsulated Layered Design)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -43,17 +43,29 @@
|
|
|
43
43
|
"format": "prettier --write .",
|
|
44
44
|
"format:check": "prettier --check .",
|
|
45
45
|
"lint": "eslint .",
|
|
46
|
+
"lint-staged": "lint-staged",
|
|
46
47
|
"prepack": "yarn build",
|
|
48
|
+
"prepare": "husky",
|
|
47
49
|
"smoke": "./scripts/smoke-test.sh",
|
|
48
50
|
"test": "vitest run",
|
|
49
51
|
"test:watch": "vitest",
|
|
50
52
|
"typecheck": "tsc --noEmit",
|
|
51
53
|
"verify": "yarn checks && yarn build"
|
|
52
54
|
},
|
|
55
|
+
"lint-staged": {
|
|
56
|
+
"*.{js,mjs,cjs,ts,tsx,json,md,mdx,yml,yaml}": "prettier --write",
|
|
57
|
+
"*.{js,mjs,cjs,ts,tsx}": "eslint --fix"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"debug": "^4.4.0"
|
|
61
|
+
},
|
|
53
62
|
"devDependencies": {
|
|
54
63
|
"@eslint/js": "^10.0.0",
|
|
64
|
+
"@types/debug": "^4.1.12",
|
|
55
65
|
"@types/node": "^26.0.0",
|
|
56
66
|
"eslint": "^10.0.0",
|
|
67
|
+
"husky": "^9.1.7",
|
|
68
|
+
"lint-staged": "^17.4.1",
|
|
57
69
|
"prettier": "^3.9.0",
|
|
58
70
|
"tsup": "^8.5.0",
|
|
59
71
|
"typescript": "^5.9.0",
|