@crutchcrew/oxlint-plugin-import-access 0.0.0-stage → 1.0.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/LICENSE +22 -0
- package/README.md +156 -2
- package/dist/core/ambientModules.d.ts +22 -0
- package/dist/core/checkImportability.d.ts +60 -0
- package/dist/core/exportTable.d.ts +102 -0
- package/dist/core/lookupPackageJson.d.ts +8 -0
- package/dist/core/resolveModule.d.ts +28 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1077 -0
- package/dist/rules/jsdoc.d.ts +9 -0
- package/dist/utils/getAccessOfJsDocs.d.ts +8 -0
- package/dist/utils/isInPackage.d.ts +19 -0
- package/dist/utils/jsdoc.d.ts +46 -0
- package/dist/utils/options.d.ts +55 -0
- package/package.json +101 -5
package/dist/index.js
ADDED
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
// src/rules/jsdoc.ts
|
|
2
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
3
|
+
|
|
4
|
+
// src/core/checkImportability.ts
|
|
5
|
+
import path5 from "node:path";
|
|
6
|
+
import { Minimatch as Minimatch2 } from "minimatch";
|
|
7
|
+
|
|
8
|
+
// src/utils/getAccessOfJsDocs.ts
|
|
9
|
+
function getAccessOfJsDocs(tags, defaultImportability) {
|
|
10
|
+
for (const { name, text } of tags) {
|
|
11
|
+
if (name === "package" || name === "private" || name === "public") {
|
|
12
|
+
return name;
|
|
13
|
+
}
|
|
14
|
+
if (name === "access" && (text === "package" || text === "private" || text === "public")) {
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return defaultImportability;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/utils/isInPackage.ts
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { Minimatch } from "minimatch";
|
|
24
|
+
var indexFileRegExp = /[/\\]index\.[cm]?[jt]sx?$/;
|
|
25
|
+
|
|
26
|
+
class PackageBoundaries {
|
|
27
|
+
#options;
|
|
28
|
+
#patterns;
|
|
29
|
+
#packageDirCache = new Map;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#options = options;
|
|
32
|
+
const patterns = options.packageDirectory;
|
|
33
|
+
this.#patterns = patterns && patterns.length > 0 ? patterns.map((pattern) => {
|
|
34
|
+
const negated = pattern.startsWith("!");
|
|
35
|
+
return { negated, matcher: new Minimatch(negated ? pattern.slice(1) : pattern) };
|
|
36
|
+
}) : undefined;
|
|
37
|
+
}
|
|
38
|
+
#isPackageDirectory(dir, patterns) {
|
|
39
|
+
const dirName = path.basename(dir);
|
|
40
|
+
const relativePath = path.relative(this.#options.projectDirectory, dir);
|
|
41
|
+
let matched = false;
|
|
42
|
+
for (const { negated, matcher } of patterns) {
|
|
43
|
+
if (matcher.match(dirName) || matcher.match(relativePath)) {
|
|
44
|
+
if (negated) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
matched = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return matched;
|
|
51
|
+
}
|
|
52
|
+
packageDirectoryOf(filePath) {
|
|
53
|
+
const fileDir = path.dirname(filePath);
|
|
54
|
+
const patterns = this.#patterns;
|
|
55
|
+
if (patterns === undefined) {
|
|
56
|
+
return fileDir;
|
|
57
|
+
}
|
|
58
|
+
const cached = this.#packageDirCache.get(fileDir);
|
|
59
|
+
if (cached !== undefined) {
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
let result = fileDir;
|
|
63
|
+
const { root } = path.parse(fileDir);
|
|
64
|
+
for (let dir = fileDir;dir !== root; dir = path.dirname(dir)) {
|
|
65
|
+
if (this.#isPackageDirectory(dir, patterns)) {
|
|
66
|
+
result = dir;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.#packageDirCache.set(fileDir, result);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
isInPackage(importer, exporter) {
|
|
74
|
+
if (this.#options.indexLoophole) {
|
|
75
|
+
const match = indexFileRegExp.exec(exporter);
|
|
76
|
+
if (match) {
|
|
77
|
+
exporter = exporter.slice(0, -match[0].length);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const importerPackageDir = this.packageDirectoryOf(importer);
|
|
81
|
+
const exporterPackageDir = this.packageDirectoryOf(exporter);
|
|
82
|
+
if (importerPackageDir === exporterPackageDir) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (this.#options.filenameLoophole) {
|
|
86
|
+
const rel = path.relative(path.dirname(importer), path.dirname(exporter));
|
|
87
|
+
if (rel === path.basename(importer, path.extname(importer))) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const rel = path.relative(exporterPackageDir, importerPackageDir);
|
|
92
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/core/ambientModules.ts
|
|
97
|
+
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
98
|
+
import path2 from "node:path";
|
|
99
|
+
import { parseSync as parseSync2 } from "oxc-parser";
|
|
100
|
+
|
|
101
|
+
// src/core/exportTable.ts
|
|
102
|
+
import { readFileSync, statSync } from "node:fs";
|
|
103
|
+
import { parseSync } from "oxc-parser";
|
|
104
|
+
|
|
105
|
+
// src/utils/jsdoc.ts
|
|
106
|
+
var LF = 10;
|
|
107
|
+
var CR = 13;
|
|
108
|
+
function isWhitespace(code) {
|
|
109
|
+
return code === 32 || code === 9 || code === 11 || code === 12 || code === LF || code === CR || code === 160 || code === 65279 || code === 8232 || code === 8233;
|
|
110
|
+
}
|
|
111
|
+
function isLineBreak(code) {
|
|
112
|
+
return code === LF || code === CR || code === 8232 || code === 8233;
|
|
113
|
+
}
|
|
114
|
+
function lastCommentEndingBefore(comments, pos) {
|
|
115
|
+
let lo = 0;
|
|
116
|
+
let hi = comments.length - 1;
|
|
117
|
+
let found = -1;
|
|
118
|
+
while (lo <= hi) {
|
|
119
|
+
const mid = lo + hi >> 1;
|
|
120
|
+
if (comments[mid].end <= pos) {
|
|
121
|
+
found = mid;
|
|
122
|
+
lo = mid + 1;
|
|
123
|
+
} else {
|
|
124
|
+
hi = mid - 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
function findJsDocComments(sourceText, comments, nodeStart, includeSameLine = false) {
|
|
130
|
+
const attached = [];
|
|
131
|
+
let pos = nodeStart;
|
|
132
|
+
let index = lastCommentEndingBefore(comments, pos);
|
|
133
|
+
for (;; ) {
|
|
134
|
+
while (pos > 0 && isWhitespace(sourceText.charCodeAt(pos - 1))) {
|
|
135
|
+
pos--;
|
|
136
|
+
}
|
|
137
|
+
const comment = index >= 0 ? comments[index] : undefined;
|
|
138
|
+
if (comment === undefined || comment.end !== pos) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
attached.push(comment);
|
|
142
|
+
pos = comment.start;
|
|
143
|
+
index--;
|
|
144
|
+
}
|
|
145
|
+
attached.reverse();
|
|
146
|
+
let firstCollectable = 0;
|
|
147
|
+
if (pos > 0 && !includeSameLine) {
|
|
148
|
+
firstCollectable = pos;
|
|
149
|
+
while (firstCollectable < nodeStart && !isLineBreak(sourceText.charCodeAt(firstCollectable))) {
|
|
150
|
+
firstCollectable++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return attached.filter((comment) => comment.start >= firstCollectable && comment.type === "Block" && comment.value.startsWith("*") && comment.value !== "*");
|
|
154
|
+
}
|
|
155
|
+
var tagNameStart = /[\p{ID_Start}$_]/u;
|
|
156
|
+
var tagNamePart = /[-\p{ID_Continue}$\u200c\u200d]/u;
|
|
157
|
+
function parseJsDocTags(comment) {
|
|
158
|
+
const text = comment.value;
|
|
159
|
+
const tags = [];
|
|
160
|
+
let current;
|
|
161
|
+
let lineStart = true;
|
|
162
|
+
let afterWhitespace = true;
|
|
163
|
+
let inBackticks = false;
|
|
164
|
+
let pos = 1;
|
|
165
|
+
while (pos < text.length) {
|
|
166
|
+
const char = text[pos];
|
|
167
|
+
if (isLineBreak(char.charCodeAt(0))) {
|
|
168
|
+
if (current) {
|
|
169
|
+
current.text += `
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
lineStart = true;
|
|
173
|
+
afterWhitespace = true;
|
|
174
|
+
inBackticks = false;
|
|
175
|
+
pos++;
|
|
176
|
+
while (pos < text.length && (text[pos] === " " || text[pos] === "\t")) {
|
|
177
|
+
pos++;
|
|
178
|
+
}
|
|
179
|
+
if (text[pos] === "*") {
|
|
180
|
+
pos++;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (char === "@" && !inBackticks && (lineStart || afterWhitespace) && tagNameStart.test(text[pos + 1] ?? "")) {
|
|
185
|
+
let end = pos + 2;
|
|
186
|
+
while (end < text.length && tagNamePart.test(text[end])) {
|
|
187
|
+
end++;
|
|
188
|
+
}
|
|
189
|
+
if (current) {
|
|
190
|
+
tags.push({ name: current.name, text: current.text.trim() });
|
|
191
|
+
}
|
|
192
|
+
current = { name: text.slice(pos + 1, end), text: "" };
|
|
193
|
+
pos = end;
|
|
194
|
+
lineStart = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (char === "`") {
|
|
198
|
+
inBackticks = !inBackticks;
|
|
199
|
+
}
|
|
200
|
+
afterWhitespace = char === " " || char === "\t";
|
|
201
|
+
lineStart = false;
|
|
202
|
+
if (current) {
|
|
203
|
+
current.text += char;
|
|
204
|
+
}
|
|
205
|
+
pos++;
|
|
206
|
+
}
|
|
207
|
+
if (current) {
|
|
208
|
+
tags.push({ name: current.name, text: current.text.trim() });
|
|
209
|
+
}
|
|
210
|
+
return tags;
|
|
211
|
+
}
|
|
212
|
+
function jsDocTagsAt(sourceText, comments, nodeStart, includeSameLine = false) {
|
|
213
|
+
const blocks = findJsDocComments(sourceText, comments, nodeStart, includeSameLine);
|
|
214
|
+
const last = blocks.at(-1);
|
|
215
|
+
return {
|
|
216
|
+
own: last === undefined ? [] : parseJsDocTags(last),
|
|
217
|
+
all: blocks.flatMap(parseJsDocTags)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/core/exportTable.ts
|
|
222
|
+
var EXPORT_EQUALS = "export=";
|
|
223
|
+
var accessTagPattern = /@(?:package|private|access)(?![\p{ID_Continue}$-])/u;
|
|
224
|
+
var starExportPattern = /\bexport(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\n]*)*(?:type(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\n]*)*)?\*/;
|
|
225
|
+
var exportEqualsHint = /\bexport\s*=(?!=)/;
|
|
226
|
+
function skipTrivia(text, pos) {
|
|
227
|
+
for (;; ) {
|
|
228
|
+
const code = text.charCodeAt(pos);
|
|
229
|
+
if (code === 32 || code === 9 || code === 10 || code === 13 || code === 11 || code === 12) {
|
|
230
|
+
pos++;
|
|
231
|
+
} else if (code === 47 && text.charCodeAt(pos + 1) === 47) {
|
|
232
|
+
const end = text.indexOf(`
|
|
233
|
+
`, pos);
|
|
234
|
+
pos = end === -1 ? text.length : end;
|
|
235
|
+
} else if (code === 47 && text.charCodeAt(pos + 1) === 42) {
|
|
236
|
+
const end = text.indexOf("*/", pos + 2);
|
|
237
|
+
pos = end === -1 ? text.length : end + 2;
|
|
238
|
+
} else {
|
|
239
|
+
return pos;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
var identifierPattern = /[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*/uy;
|
|
244
|
+
function wordAt(text, pos) {
|
|
245
|
+
identifierPattern.lastIndex = pos;
|
|
246
|
+
return identifierPattern.exec(text)?.[0] ?? "";
|
|
247
|
+
}
|
|
248
|
+
function declarationKind(text, pos) {
|
|
249
|
+
if (text.charCodeAt(pos) === 64) {
|
|
250
|
+
return "checked";
|
|
251
|
+
}
|
|
252
|
+
let word = wordAt(text, pos);
|
|
253
|
+
if (word === "declare") {
|
|
254
|
+
pos = skipTrivia(text, pos + word.length);
|
|
255
|
+
word = wordAt(text, pos);
|
|
256
|
+
}
|
|
257
|
+
switch (word) {
|
|
258
|
+
case "const":
|
|
259
|
+
return wordAt(text, skipTrivia(text, pos + word.length)) === "enum" ? "unchecked" : "variable";
|
|
260
|
+
case "let":
|
|
261
|
+
case "var":
|
|
262
|
+
return "variable";
|
|
263
|
+
case "function":
|
|
264
|
+
case "async":
|
|
265
|
+
case "class":
|
|
266
|
+
case "abstract":
|
|
267
|
+
case "type":
|
|
268
|
+
case "interface":
|
|
269
|
+
return "checked";
|
|
270
|
+
default:
|
|
271
|
+
return "unchecked";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function isSpecifierList(text, start) {
|
|
275
|
+
let pos = skipTrivia(text, start + "export".length);
|
|
276
|
+
if (text.startsWith("type", pos)) {
|
|
277
|
+
pos = skipTrivia(text, pos + "type".length);
|
|
278
|
+
}
|
|
279
|
+
return text.charCodeAt(pos) === 123;
|
|
280
|
+
}
|
|
281
|
+
function isInComment(comments, pos) {
|
|
282
|
+
let lo = 0;
|
|
283
|
+
let hi = comments.length - 1;
|
|
284
|
+
while (lo <= hi) {
|
|
285
|
+
const mid = lo + hi >> 1;
|
|
286
|
+
const comment = comments[mid];
|
|
287
|
+
if (pos < comment.start) {
|
|
288
|
+
hi = mid - 1;
|
|
289
|
+
} else if (pos >= comment.end) {
|
|
290
|
+
lo = mid + 1;
|
|
291
|
+
} else {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
function mayHaveDecorators(text, comments) {
|
|
298
|
+
for (let pos = text.indexOf("@");pos !== -1; pos = text.indexOf("@", pos + 1)) {
|
|
299
|
+
if (!isInComment(comments, pos)) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
var jsDocInitializers = new Set([
|
|
306
|
+
"ArrowFunctionExpression",
|
|
307
|
+
"FunctionExpression",
|
|
308
|
+
"ParenthesizedExpression"
|
|
309
|
+
]);
|
|
310
|
+
function syntaxDetails(program) {
|
|
311
|
+
const decoratorStarts = new Map;
|
|
312
|
+
const exportLists = [];
|
|
313
|
+
const exportEquals = [];
|
|
314
|
+
const variables = new Map;
|
|
315
|
+
for (const statement of program.body) {
|
|
316
|
+
switch (statement.type) {
|
|
317
|
+
case "ExportNamedDeclaration":
|
|
318
|
+
case "ExportDefaultDeclaration": {
|
|
319
|
+
const { declaration } = statement;
|
|
320
|
+
if (declaration?.type === "ClassDeclaration") {
|
|
321
|
+
const first = declaration.decorators[0];
|
|
322
|
+
if (first !== undefined && first.start < statement.start) {
|
|
323
|
+
decoratorStarts.set(statement.start, first.start);
|
|
324
|
+
}
|
|
325
|
+
} else if (declaration?.type === "VariableDeclaration") {
|
|
326
|
+
variables.set(statement.start, declaration.declarations.map(({ start, end, init }) => ({
|
|
327
|
+
start,
|
|
328
|
+
end,
|
|
329
|
+
initializer: init && jsDocInitializers.has(init.type) ? init.start : undefined
|
|
330
|
+
})));
|
|
331
|
+
} else if (declaration === null) {
|
|
332
|
+
exportLists.push([statement.start, statement.end]);
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case "TSExportAssignment":
|
|
337
|
+
exportEquals.push(statement.start);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return { decoratorStarts, exportLists, exportEquals, variables };
|
|
342
|
+
}
|
|
343
|
+
function createModuleExports(fileName, text, comments, names, stars, details) {
|
|
344
|
+
const tagCache = new Map;
|
|
345
|
+
const tagsAt = (start, includeSameLine) => {
|
|
346
|
+
const key = `${start}:${includeSameLine}`;
|
|
347
|
+
let tags = tagCache.get(key);
|
|
348
|
+
if (tags === undefined) {
|
|
349
|
+
tags = jsDocTagsAt(text, comments, start, includeSameLine);
|
|
350
|
+
tagCache.set(key, tags);
|
|
351
|
+
}
|
|
352
|
+
return tags;
|
|
353
|
+
};
|
|
354
|
+
const ownTags = (entry) => {
|
|
355
|
+
const { own } = entry;
|
|
356
|
+
switch (own.kind) {
|
|
357
|
+
case "statement":
|
|
358
|
+
return tagsAt(entry.statementStart, false).own;
|
|
359
|
+
case "specifier":
|
|
360
|
+
return tagsAt(own.start, true).own;
|
|
361
|
+
case "none":
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
const statementTags = tagsAt(entry.statementStart, false);
|
|
365
|
+
const inner = comments.some((comment) => comment.start > entry.statementStart && comment.end <= own.statementEnd && comment.value.startsWith("*"));
|
|
366
|
+
if (!inner && statementTags.all.length === statementTags.own.length) {
|
|
367
|
+
return statementTags.own;
|
|
368
|
+
}
|
|
369
|
+
const declarators = details().variables.get(entry.statementStart);
|
|
370
|
+
const index = declarators.findIndex(({ start, end }) => start <= own.nameStart && own.nameStart < end);
|
|
371
|
+
const declarator = declarators[index];
|
|
372
|
+
return [
|
|
373
|
+
...declarator.initializer === undefined ? [] : tagsAt(declarator.initializer, true).own,
|
|
374
|
+
...tagsAt(declarator.start, true).own,
|
|
375
|
+
...index === 0 ? statementTags.own : []
|
|
376
|
+
];
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
fileName,
|
|
380
|
+
names,
|
|
381
|
+
stars,
|
|
382
|
+
tagsOf(entries) {
|
|
383
|
+
const tags = entries.flatMap(ownTags);
|
|
384
|
+
tags.push(...tagsAt(entries[0].statementStart, false).all);
|
|
385
|
+
return tags;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function parseModuleExports(fileName, text, assumeAllPublic) {
|
|
390
|
+
if (assumeAllPublic && !accessTagPattern.test(text) && !starExportPattern.test(text)) {
|
|
391
|
+
return createModuleExports(fileName, text, [], undefined, []);
|
|
392
|
+
}
|
|
393
|
+
const result = parseSync(fileName, text);
|
|
394
|
+
const { comments } = result;
|
|
395
|
+
let cachedDetails;
|
|
396
|
+
const details = () => cachedDetails ??= syntaxDetails(result.program);
|
|
397
|
+
const decorated = mayHaveDecorators(text, comments);
|
|
398
|
+
const names = new Map;
|
|
399
|
+
const stars = [];
|
|
400
|
+
const add = (name, entry) => {
|
|
401
|
+
const list = names.get(name);
|
|
402
|
+
if (list) {
|
|
403
|
+
list.push(entry);
|
|
404
|
+
} else {
|
|
405
|
+
names.set(name, [entry]);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
for (const statement of result.module.staticExports) {
|
|
409
|
+
for (const entry of statement.entries) {
|
|
410
|
+
const { exportName, importName, localName, moduleRequest } = entry;
|
|
411
|
+
if (importName.kind === "AllButDefault") {
|
|
412
|
+
stars.push(moduleRequest.value);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
let statementStart = statement.start;
|
|
416
|
+
if (!text.startsWith("export", statementStart)) {
|
|
417
|
+
statementStart = details().exportLists.find(([start, end]) => start <= entry.start && entry.start < end)[0];
|
|
418
|
+
}
|
|
419
|
+
let checked = true;
|
|
420
|
+
let own = { kind: "statement" };
|
|
421
|
+
if (isSpecifierList(text, statementStart)) {
|
|
422
|
+
own = { kind: "specifier", start: entry.start };
|
|
423
|
+
} else if (importName.kind === "All") {
|
|
424
|
+
own = { kind: "none" };
|
|
425
|
+
} else if (exportName.kind === "Name" && localName.kind === "Name") {
|
|
426
|
+
const kind = declarationKind(text, entry.start);
|
|
427
|
+
checked = kind !== "unchecked";
|
|
428
|
+
if (kind === "variable") {
|
|
429
|
+
own = { kind: "variable", nameStart: exportName.start, statementEnd: statement.end };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
add(exportName.kind === "Default" ? "default" : exportName.name, {
|
|
433
|
+
checked,
|
|
434
|
+
statementStart: decorated ? details().decoratorStarts.get(statementStart) ?? statementStart : statementStart,
|
|
435
|
+
own
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (exportEqualsHint.test(text)) {
|
|
440
|
+
for (const start of details().exportEquals) {
|
|
441
|
+
add(EXPORT_EQUALS, { checked: true, statementStart: start, own: { kind: "statement" } });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return createModuleExports(fileName, text, comments, names, stars, details);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
class ExportTableCache {
|
|
448
|
+
#entries = new Map;
|
|
449
|
+
#generation = 0;
|
|
450
|
+
nextGeneration() {
|
|
451
|
+
this.#generation++;
|
|
452
|
+
}
|
|
453
|
+
get(fileName, assumeAllPublic) {
|
|
454
|
+
const cached = this.#entries.get(fileName);
|
|
455
|
+
const usable = cached !== undefined && (cached.exports.names !== undefined || assumeAllPublic) ? cached : undefined;
|
|
456
|
+
if (usable?.generation === this.#generation) {
|
|
457
|
+
return usable.exports;
|
|
458
|
+
}
|
|
459
|
+
let stats;
|
|
460
|
+
try {
|
|
461
|
+
stats = statSync(fileName);
|
|
462
|
+
} catch {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (usable !== undefined && usable.mtimeMs === stats.mtimeMs && usable.size === stats.size) {
|
|
466
|
+
usable.generation = this.#generation;
|
|
467
|
+
return usable.exports;
|
|
468
|
+
}
|
|
469
|
+
let text;
|
|
470
|
+
try {
|
|
471
|
+
text = readFileSync(fileName, "utf8");
|
|
472
|
+
} catch {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const exports = parseModuleExports(fileName, text, assumeAllPublic);
|
|
476
|
+
this.#entries.set(fileName, {
|
|
477
|
+
mtimeMs: stats.mtimeMs,
|
|
478
|
+
size: stats.size,
|
|
479
|
+
generation: this.#generation,
|
|
480
|
+
exports
|
|
481
|
+
});
|
|
482
|
+
return exports;
|
|
483
|
+
}
|
|
484
|
+
clear() {
|
|
485
|
+
this.#entries.clear();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/core/ambientModules.ts
|
|
490
|
+
var declarationFilePattern = /\.d\.[cm]?ts$/;
|
|
491
|
+
var ambientModulePattern = /\bdeclare\s+module\s+["']/;
|
|
492
|
+
function exportNameOf(name) {
|
|
493
|
+
return name.type === "Literal" ? name.value : name.name;
|
|
494
|
+
}
|
|
495
|
+
function isCheckedDeclaration(declaration) {
|
|
496
|
+
switch (declaration.type) {
|
|
497
|
+
case "VariableDeclaration":
|
|
498
|
+
case "FunctionDeclaration":
|
|
499
|
+
case "TSDeclareFunction":
|
|
500
|
+
case "ClassDeclaration":
|
|
501
|
+
case "TSTypeAliasDeclaration":
|
|
502
|
+
case "TSInterfaceDeclaration":
|
|
503
|
+
return true;
|
|
504
|
+
default:
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function declaredNames(declaration) {
|
|
509
|
+
if (declaration.type === "VariableDeclaration") {
|
|
510
|
+
const names = [];
|
|
511
|
+
const visit = (pattern) => {
|
|
512
|
+
const node = pattern;
|
|
513
|
+
switch (node.type) {
|
|
514
|
+
case "Identifier":
|
|
515
|
+
names.push(node["name"]);
|
|
516
|
+
break;
|
|
517
|
+
case "ObjectPattern":
|
|
518
|
+
for (const property of node["properties"]) {
|
|
519
|
+
visit(property);
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
case "Property":
|
|
523
|
+
visit(node["value"]);
|
|
524
|
+
break;
|
|
525
|
+
case "ArrayPattern":
|
|
526
|
+
for (const element of node["elements"]) {
|
|
527
|
+
if (element !== null) {
|
|
528
|
+
visit(element);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
case "AssignmentPattern":
|
|
533
|
+
visit(node["left"]);
|
|
534
|
+
break;
|
|
535
|
+
case "RestElement":
|
|
536
|
+
visit(node["argument"]);
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
for (const declarator of declaration.declarations) {
|
|
541
|
+
visit(declarator.id);
|
|
542
|
+
}
|
|
543
|
+
return names;
|
|
544
|
+
}
|
|
545
|
+
const id = declaration.id;
|
|
546
|
+
return id?.type === "Identifier" && id.name !== undefined ? [id.name] : [];
|
|
547
|
+
}
|
|
548
|
+
function moduleBlockExports(fileName, text, comments, body) {
|
|
549
|
+
const names = new Map;
|
|
550
|
+
const stars = [];
|
|
551
|
+
const add = (name, entry) => {
|
|
552
|
+
const list = names.get(name);
|
|
553
|
+
if (list) {
|
|
554
|
+
list.push(entry);
|
|
555
|
+
} else {
|
|
556
|
+
names.set(name, [entry]);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
for (const statement of body) {
|
|
560
|
+
switch (statement.type) {
|
|
561
|
+
case "ExportNamedDeclaration": {
|
|
562
|
+
const { declaration } = statement;
|
|
563
|
+
if (declaration) {
|
|
564
|
+
const checked = isCheckedDeclaration(declaration);
|
|
565
|
+
for (const name of declaredNames(declaration)) {
|
|
566
|
+
add(name, { checked, statementStart: statement.start, own: { kind: "statement" } });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
for (const specifier of statement.specifiers) {
|
|
570
|
+
add(exportNameOf(specifier.exported), {
|
|
571
|
+
checked: true,
|
|
572
|
+
statementStart: statement.start,
|
|
573
|
+
own: { kind: "specifier", start: specifier.start }
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
case "ExportDefaultDeclaration":
|
|
579
|
+
add("default", {
|
|
580
|
+
checked: true,
|
|
581
|
+
statementStart: statement.start,
|
|
582
|
+
own: { kind: "statement" }
|
|
583
|
+
});
|
|
584
|
+
break;
|
|
585
|
+
case "TSExportAssignment":
|
|
586
|
+
add(EXPORT_EQUALS, {
|
|
587
|
+
checked: true,
|
|
588
|
+
statementStart: statement.start,
|
|
589
|
+
own: { kind: "statement" }
|
|
590
|
+
});
|
|
591
|
+
break;
|
|
592
|
+
case "ExportAllDeclaration":
|
|
593
|
+
if (statement.exported) {
|
|
594
|
+
add(exportNameOf(statement.exported), {
|
|
595
|
+
checked: true,
|
|
596
|
+
statementStart: statement.start,
|
|
597
|
+
own: { kind: "none" }
|
|
598
|
+
});
|
|
599
|
+
} else {
|
|
600
|
+
stars.push(statement.source.value);
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return createModuleExports(fileName, text, comments, names, stars);
|
|
606
|
+
}
|
|
607
|
+
function parseAmbientModules(fileName, text) {
|
|
608
|
+
const modules = new Map;
|
|
609
|
+
const result = parseSync2(fileName, text);
|
|
610
|
+
if (result.module.hasModuleSyntax) {
|
|
611
|
+
return modules;
|
|
612
|
+
}
|
|
613
|
+
for (const statement of result.program.body) {
|
|
614
|
+
if (statement.type === "TSModuleDeclaration" && statement.id.type === "Literal" && statement.body?.type === "TSModuleBlock") {
|
|
615
|
+
const name = statement.id.value;
|
|
616
|
+
if (!modules.has(name)) {
|
|
617
|
+
modules.set(name, moduleBlockExports(fileName, text, result.comments, statement.body.body));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return modules;
|
|
622
|
+
}
|
|
623
|
+
function* declarationFiles(dir) {
|
|
624
|
+
let entries;
|
|
625
|
+
try {
|
|
626
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
627
|
+
} catch {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
for (const entry of entries) {
|
|
631
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const fullPath = path2.join(dir, entry.name);
|
|
635
|
+
if (entry.isDirectory()) {
|
|
636
|
+
yield* declarationFiles(fullPath);
|
|
637
|
+
} else if (declarationFilePattern.test(entry.name)) {
|
|
638
|
+
yield fullPath;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
class AmbientModuleIndex {
|
|
644
|
+
#roots = new Map;
|
|
645
|
+
#indexOf(rootDir) {
|
|
646
|
+
let index = this.#roots.get(rootDir);
|
|
647
|
+
if (index === undefined) {
|
|
648
|
+
index = { exact: new Map, patterns: [] };
|
|
649
|
+
for (const fileName of declarationFiles(rootDir)) {
|
|
650
|
+
let text;
|
|
651
|
+
try {
|
|
652
|
+
text = readFileSync2(fileName, "utf8");
|
|
653
|
+
} catch {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (!ambientModulePattern.test(text)) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
for (const [name, exports] of parseAmbientModules(fileName, text)) {
|
|
660
|
+
const star = name.indexOf("*");
|
|
661
|
+
if (star === -1) {
|
|
662
|
+
if (!index.exact.has(name)) {
|
|
663
|
+
index.exact.set(name, exports);
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
index.patterns.push({
|
|
667
|
+
prefix: name.slice(0, star),
|
|
668
|
+
suffix: name.slice(star + 1),
|
|
669
|
+
exports
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
this.#roots.set(rootDir, index);
|
|
675
|
+
}
|
|
676
|
+
return index;
|
|
677
|
+
}
|
|
678
|
+
exact(rootDir, specifier) {
|
|
679
|
+
return this.#indexOf(rootDir).exact.get(specifier);
|
|
680
|
+
}
|
|
681
|
+
pattern(rootDir, specifier) {
|
|
682
|
+
let best;
|
|
683
|
+
for (const pattern of this.#indexOf(rootDir).patterns) {
|
|
684
|
+
if (specifier.length >= pattern.prefix.length + pattern.suffix.length && specifier.startsWith(pattern.prefix) && specifier.endsWith(pattern.suffix) && (best === undefined || pattern.prefix.length > best.prefix.length)) {
|
|
685
|
+
best = pattern;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return best?.exports;
|
|
689
|
+
}
|
|
690
|
+
clear() {
|
|
691
|
+
this.#roots.clear();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/core/lookupPackageJson.ts
|
|
696
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
697
|
+
import path3 from "node:path";
|
|
698
|
+
var packageNameCache = new Map;
|
|
699
|
+
function readPackageName(packageJsonPath) {
|
|
700
|
+
let text;
|
|
701
|
+
try {
|
|
702
|
+
text = readFileSync3(packageJsonPath, "utf8");
|
|
703
|
+
} catch (error) {
|
|
704
|
+
if (error.code === "ENOENT") {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
let packageJson;
|
|
710
|
+
try {
|
|
711
|
+
packageJson = JSON.parse(text);
|
|
712
|
+
} catch {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const name = packageJson?.name;
|
|
716
|
+
return typeof name === "string" ? name : null;
|
|
717
|
+
}
|
|
718
|
+
function lookupPackageName(file) {
|
|
719
|
+
const visited = [];
|
|
720
|
+
let result = null;
|
|
721
|
+
const { root } = path3.parse(path3.resolve(file));
|
|
722
|
+
for (let dir = path3.dirname(path3.resolve(file));dir !== root; dir = path3.dirname(dir)) {
|
|
723
|
+
const cached = packageNameCache.get(dir);
|
|
724
|
+
if (cached !== undefined) {
|
|
725
|
+
result = cached;
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
visited.push(dir);
|
|
729
|
+
const name = readPackageName(path3.join(dir, "package.json"));
|
|
730
|
+
if (name !== undefined) {
|
|
731
|
+
result = name;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
for (const dir of visited) {
|
|
736
|
+
packageNameCache.set(dir, result);
|
|
737
|
+
}
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/core/resolveModule.ts
|
|
742
|
+
import { realpathSync } from "node:fs";
|
|
743
|
+
import path4 from "node:path";
|
|
744
|
+
import { ResolverFactory } from "oxc-resolver";
|
|
745
|
+
var EXTERNAL = { kind: "external" };
|
|
746
|
+
var UNRESOLVED = { kind: "unresolved" };
|
|
747
|
+
var sourceFilePattern = /\.(?:[cm]?[jt]sx?|d\.[cm]?ts)$/;
|
|
748
|
+
var jsonFilePattern = /\.json$/;
|
|
749
|
+
var nodeModulesPattern = /[/\\]node_modules[/\\]/;
|
|
750
|
+
var relativeSpecifier = /^\.{1,2}(?:[/\\]|$)/;
|
|
751
|
+
var directorySpecifier = /(?:^|[/\\])\.{0,2}$/;
|
|
752
|
+
|
|
753
|
+
class ModuleResolver {
|
|
754
|
+
#resolver = new ResolverFactory({
|
|
755
|
+
tsconfig: "auto",
|
|
756
|
+
builtinModules: true,
|
|
757
|
+
symlinks: false,
|
|
758
|
+
conditionNames: ["types", "import", "require", "node", "default"],
|
|
759
|
+
mainFields: ["types", "typings", "module", "main"],
|
|
760
|
+
extensions: [
|
|
761
|
+
".ts",
|
|
762
|
+
".tsx",
|
|
763
|
+
".d.ts",
|
|
764
|
+
".mts",
|
|
765
|
+
".d.mts",
|
|
766
|
+
".cts",
|
|
767
|
+
".d.cts",
|
|
768
|
+
".js",
|
|
769
|
+
".jsx",
|
|
770
|
+
".mjs",
|
|
771
|
+
".cjs",
|
|
772
|
+
".json"
|
|
773
|
+
],
|
|
774
|
+
extensionAlias: {
|
|
775
|
+
".js": [".ts", ".tsx", ".d.ts", ".js", ".jsx"],
|
|
776
|
+
".jsx": [".tsx", ".d.ts", ".jsx"],
|
|
777
|
+
".mjs": [".mts", ".d.mts", ".mjs"],
|
|
778
|
+
".cjs": [".cts", ".d.cts", ".cjs"]
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
#byDirectory = new Map;
|
|
782
|
+
#byTarget = new Map;
|
|
783
|
+
resolve(importerFileName, specifier) {
|
|
784
|
+
const importerDir = path4.dirname(importerFileName);
|
|
785
|
+
const directoryKey = `${importerDir}\x00${specifier}`;
|
|
786
|
+
let resolution = this.#byDirectory.get(directoryKey);
|
|
787
|
+
if (resolution === undefined) {
|
|
788
|
+
if (relativeSpecifier.test(specifier)) {
|
|
789
|
+
const target = path4.resolve(importerDir, specifier) + (directorySpecifier.test(specifier) ? path4.sep : "");
|
|
790
|
+
resolution = this.#byTarget.get(target);
|
|
791
|
+
if (resolution === undefined) {
|
|
792
|
+
resolution = this.#resolveUncached(importerFileName, specifier);
|
|
793
|
+
this.#byTarget.set(target, resolution);
|
|
794
|
+
}
|
|
795
|
+
} else {
|
|
796
|
+
resolution = this.#resolveUncached(importerFileName, specifier);
|
|
797
|
+
}
|
|
798
|
+
this.#byDirectory.set(directoryKey, resolution);
|
|
799
|
+
}
|
|
800
|
+
return resolution;
|
|
801
|
+
}
|
|
802
|
+
#resolveUncached(importerFileName, specifier) {
|
|
803
|
+
const result = this.#resolver.resolveFileSync(importerFileName, specifier);
|
|
804
|
+
if (result.builtin !== undefined) {
|
|
805
|
+
return EXTERNAL;
|
|
806
|
+
}
|
|
807
|
+
const resolved = result.path;
|
|
808
|
+
if (resolved === undefined) {
|
|
809
|
+
return UNRESOLVED;
|
|
810
|
+
}
|
|
811
|
+
if (jsonFilePattern.test(resolved)) {
|
|
812
|
+
return EXTERNAL;
|
|
813
|
+
}
|
|
814
|
+
if (!sourceFilePattern.test(resolved)) {
|
|
815
|
+
return UNRESOLVED;
|
|
816
|
+
}
|
|
817
|
+
if (nodeModulesPattern.test(resolved)) {
|
|
818
|
+
return EXTERNAL;
|
|
819
|
+
}
|
|
820
|
+
return { kind: "internal", fileName: realpathSync.native(resolved) };
|
|
821
|
+
}
|
|
822
|
+
clear() {
|
|
823
|
+
this.#byDirectory.clear();
|
|
824
|
+
this.#byTarget.clear();
|
|
825
|
+
this.#resolver.clearCache();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/core/checkImportability.ts
|
|
830
|
+
var possibleSubpathImportFromPackage = /^(?![./\\])([^/\\]*)(?:$|[/\\][^/\\])/;
|
|
831
|
+
var relativeOrAbsolute = /^(?:\.{1,2}(?:[/\\]|$)|[/\\]|[a-zA-Z]:[/\\])/;
|
|
832
|
+
|
|
833
|
+
class ImportabilityChecker {
|
|
834
|
+
resolver = new ModuleResolver;
|
|
835
|
+
exportTables = new ExportTableCache;
|
|
836
|
+
ambientModules = new AmbientModuleIndex;
|
|
837
|
+
#found = new WeakMap;
|
|
838
|
+
findExport(module, name) {
|
|
839
|
+
let byName = this.#found.get(module);
|
|
840
|
+
if (byName === undefined) {
|
|
841
|
+
byName = new Map;
|
|
842
|
+
this.#found.set(module, byName);
|
|
843
|
+
}
|
|
844
|
+
let found = byName.get(name);
|
|
845
|
+
if (found === undefined) {
|
|
846
|
+
found = this.#findExport(module, name, new Set) ?? null;
|
|
847
|
+
byName.set(name, found);
|
|
848
|
+
}
|
|
849
|
+
return found ?? undefined;
|
|
850
|
+
}
|
|
851
|
+
#findExport(module, name, visited) {
|
|
852
|
+
if (module.names === undefined) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const entries = module.names.get(name);
|
|
856
|
+
if (entries !== undefined) {
|
|
857
|
+
return { module, name, entries };
|
|
858
|
+
}
|
|
859
|
+
if (name === "default") {
|
|
860
|
+
const exportEquals = module.names.get(EXPORT_EQUALS);
|
|
861
|
+
return exportEquals && { module, name: EXPORT_EQUALS, entries: exportEquals };
|
|
862
|
+
}
|
|
863
|
+
visited.add(module.fileName);
|
|
864
|
+
for (const request of module.stars) {
|
|
865
|
+
const target = this.resolver.resolve(module.fileName, request);
|
|
866
|
+
if (target.kind !== "internal" || visited.has(target.fileName)) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
const targetModule = this.exportTables.get(target.fileName, false);
|
|
870
|
+
const found = targetModule && this.#findExport(targetModule, name, visited);
|
|
871
|
+
if (found !== undefined) {
|
|
872
|
+
return found;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
targetModule(importerFileName, specifier, projectDirectory, assumeAllPublic) {
|
|
878
|
+
if (!relativeOrAbsolute.test(specifier)) {
|
|
879
|
+
const ambient = this.ambientModules.exact(projectDirectory, specifier);
|
|
880
|
+
if (ambient !== undefined) {
|
|
881
|
+
return ambient;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const resolution = this.resolver.resolve(importerFileName, specifier);
|
|
885
|
+
if (resolution.kind === "internal") {
|
|
886
|
+
return this.exportTables.get(resolution.fileName, assumeAllPublic);
|
|
887
|
+
}
|
|
888
|
+
return resolution.kind === "unresolved" ? this.ambientModules.pattern(projectDirectory, specifier) : undefined;
|
|
889
|
+
}
|
|
890
|
+
#watching;
|
|
891
|
+
#lintedFiles = new Set;
|
|
892
|
+
constructor(watching = process.argv.includes("--lsp")) {
|
|
893
|
+
this.#watching = watching;
|
|
894
|
+
}
|
|
895
|
+
nextFile(fileName) {
|
|
896
|
+
if (!this.#watching) {
|
|
897
|
+
if (!this.#lintedFiles.has(fileName)) {
|
|
898
|
+
this.#lintedFiles.add(fileName);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
this.#watching = true;
|
|
902
|
+
this.#lintedFiles.clear();
|
|
903
|
+
}
|
|
904
|
+
this.exportTables.nextGeneration();
|
|
905
|
+
this.resolver.clear();
|
|
906
|
+
this.#found = new WeakMap;
|
|
907
|
+
}
|
|
908
|
+
clear() {
|
|
909
|
+
this.#found = new WeakMap;
|
|
910
|
+
this.resolver.clear();
|
|
911
|
+
this.exportTables.clear();
|
|
912
|
+
this.ambientModules.clear();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
class ImportabilityPolicy {
|
|
917
|
+
options;
|
|
918
|
+
#exclude;
|
|
919
|
+
#boundaries;
|
|
920
|
+
constructor(options) {
|
|
921
|
+
this.options = options;
|
|
922
|
+
this.#exclude = options.excludeSourcePatterns.map((pattern) => new Minimatch2(pattern, { dot: true }));
|
|
923
|
+
this.#boundaries = new PackageBoundaries(options);
|
|
924
|
+
}
|
|
925
|
+
#isExcluded(exporterFileName) {
|
|
926
|
+
if (this.#exclude.length === 0) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
const relativePath = path5.relative(this.options.projectDirectory, exporterFileName);
|
|
930
|
+
return this.#exclude.some((matcher) => matcher.match(relativePath));
|
|
931
|
+
}
|
|
932
|
+
#isSelfReference(importerFileName, specifier) {
|
|
933
|
+
if (this.options.treatSelfReferenceAs !== "external" || !possibleSubpathImportFromPackage.test(specifier)) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
const packageName = lookupPackageName(importerFileName);
|
|
937
|
+
return packageName !== null && (specifier === packageName || specifier.startsWith(`${packageName}/`));
|
|
938
|
+
}
|
|
939
|
+
check(checker, importerFileName, specifier, name) {
|
|
940
|
+
const { defaultImportability, projectDirectory } = this.options;
|
|
941
|
+
const assumeAllPublic = defaultImportability === "public";
|
|
942
|
+
const module = checker.targetModule(importerFileName, specifier, projectDirectory, assumeAllPublic);
|
|
943
|
+
const found = module && checker.findExport(module, name);
|
|
944
|
+
if (found === undefined || !found.entries[0].checked) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const exporterFileName = found.module.fileName;
|
|
948
|
+
if (this.#isExcluded(exporterFileName) || this.#isSelfReference(importerFileName, specifier)) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const access = getAccessOfJsDocs(found.module.tagsOf(found.entries), defaultImportability);
|
|
952
|
+
if (access === "public" || access === "package" && this.#boundaries.isInPackage(importerFileName, exporterFileName)) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
return { access, identifier: found.name };
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/utils/options.ts
|
|
960
|
+
var defaultOptions = {
|
|
961
|
+
indexLoophole: true,
|
|
962
|
+
filenameLoophole: false,
|
|
963
|
+
defaultImportability: "package",
|
|
964
|
+
treatSelfReferenceAs: "external",
|
|
965
|
+
excludeSourcePatterns: []
|
|
966
|
+
};
|
|
967
|
+
function normalizeOptions(options, projectDirectory) {
|
|
968
|
+
return {
|
|
969
|
+
indexLoophole: options?.indexLoophole ?? defaultOptions.indexLoophole,
|
|
970
|
+
filenameLoophole: options?.filenameLoophole ?? defaultOptions.filenameLoophole,
|
|
971
|
+
defaultImportability: options?.defaultImportability ?? defaultOptions.defaultImportability,
|
|
972
|
+
treatSelfReferenceAs: options?.treatSelfReferenceAs ?? defaultOptions.treatSelfReferenceAs,
|
|
973
|
+
excludeSourcePatterns: options?.excludeSourcePatterns ?? defaultOptions.excludeSourcePatterns,
|
|
974
|
+
packageDirectory: options?.packageDirectory,
|
|
975
|
+
projectDirectory
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/rules/jsdoc.ts
|
|
980
|
+
var checker = new ImportabilityChecker;
|
|
981
|
+
var policies = new Map;
|
|
982
|
+
function policyFor(options, cwd) {
|
|
983
|
+
const key = `${cwd}\x00${JSON.stringify(options ?? {})}`;
|
|
984
|
+
let policy = policies.get(key);
|
|
985
|
+
if (policy === undefined) {
|
|
986
|
+
policy = new ImportabilityPolicy(normalizeOptions(options, cwd));
|
|
987
|
+
policies.set(key, policy);
|
|
988
|
+
}
|
|
989
|
+
return policy;
|
|
990
|
+
}
|
|
991
|
+
function realFileName(fileName) {
|
|
992
|
+
try {
|
|
993
|
+
return realpathSync2.native(fileName);
|
|
994
|
+
} catch {
|
|
995
|
+
return fileName;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function nameOf(node) {
|
|
999
|
+
return node.type === "Literal" ? node.value : node.name;
|
|
1000
|
+
}
|
|
1001
|
+
var rule = {
|
|
1002
|
+
meta: {
|
|
1003
|
+
type: "problem",
|
|
1004
|
+
docs: {
|
|
1005
|
+
description: "Prohibit importing package-private and private exports.",
|
|
1006
|
+
url: "https://github.com/crutchcrew/oxlint-plugin-import-access/blob/main/docs/rule-jsdoc.md"
|
|
1007
|
+
},
|
|
1008
|
+
messages: {
|
|
1009
|
+
package: "Cannot import a package-private export '{{ identifier }}'",
|
|
1010
|
+
"package:reexport": "Cannot re-export a package-private export '{{ identifier }}'",
|
|
1011
|
+
private: "Cannot import a private export '{{ identifier }}'",
|
|
1012
|
+
"private:reexport": "Cannot re-export a private export '{{ identifier }}'"
|
|
1013
|
+
},
|
|
1014
|
+
schema: [
|
|
1015
|
+
{
|
|
1016
|
+
type: "object",
|
|
1017
|
+
properties: {
|
|
1018
|
+
indexLoophole: { type: "boolean" },
|
|
1019
|
+
filenameLoophole: { type: "boolean" },
|
|
1020
|
+
defaultImportability: { type: "string", enum: ["public", "package", "private"] },
|
|
1021
|
+
treatSelfReferenceAs: { type: "string", enum: ["external", "internal"] },
|
|
1022
|
+
excludeSourcePatterns: { type: "array", items: { type: "string" } },
|
|
1023
|
+
packageDirectory: { type: "array", items: { type: "string" } }
|
|
1024
|
+
},
|
|
1025
|
+
additionalProperties: false
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
},
|
|
1029
|
+
createOnce(context) {
|
|
1030
|
+
let policy;
|
|
1031
|
+
let importer;
|
|
1032
|
+
const check = (node, specifier, name, reexport) => {
|
|
1033
|
+
const violation = policy.check(checker, importer, specifier, name);
|
|
1034
|
+
if (violation !== undefined) {
|
|
1035
|
+
context.report({
|
|
1036
|
+
node,
|
|
1037
|
+
messageId: reexport ? `${violation.access}:reexport` : violation.access,
|
|
1038
|
+
data: { identifier: violation.identifier }
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
before() {
|
|
1044
|
+
if (!context.sourceCode.text.includes("from")) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
policy = policyFor(context.options[0], context.cwd);
|
|
1048
|
+
importer = realFileName(context.filename);
|
|
1049
|
+
checker.nextFile(importer);
|
|
1050
|
+
return true;
|
|
1051
|
+
},
|
|
1052
|
+
ImportSpecifier(node) {
|
|
1053
|
+
check(node, node.parent.source.value, nameOf(node.imported), false);
|
|
1054
|
+
},
|
|
1055
|
+
ImportDefaultSpecifier(node) {
|
|
1056
|
+
check(node, node.parent.source.value, "default", false);
|
|
1057
|
+
},
|
|
1058
|
+
ExportSpecifier(node) {
|
|
1059
|
+
const { source } = node.parent;
|
|
1060
|
+
if (source !== null) {
|
|
1061
|
+
check(node, source.value, nameOf(node.local), true);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
var jsdoc_default = rule;
|
|
1068
|
+
|
|
1069
|
+
// src/index.ts
|
|
1070
|
+
var plugin = {
|
|
1071
|
+
meta: { name: "import-access" },
|
|
1072
|
+
rules: { jsdoc: jsdoc_default }
|
|
1073
|
+
};
|
|
1074
|
+
var src_default = plugin;
|
|
1075
|
+
export {
|
|
1076
|
+
src_default as default
|
|
1077
|
+
};
|