@cplieger/deadset-ts 4.0.1 → 5.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/README.md +4 -4
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +229 -36
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +186 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +383 -0
- package/dist/src/conventions.js +456 -0
- package/dist/src/dependencies.js +30 -3
- package/dist/src/discover.js +10 -3
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +47 -32
- package/dist/src/exempt.js +11 -3
- package/dist/src/findings/completion.js +19 -29
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/unused-declarations.js +7 -2
- package/dist/src/graph.js +13 -5
- package/dist/src/inventory.js +4 -2
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/references.js +209 -30
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +3 -0
- package/dist/src/resolve.js +31 -6
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +2 -0
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +394 -70
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +235 -0
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +440 -0
- package/src/conventions.ts +640 -0
- package/src/dependencies.ts +36 -2
- package/src/discover.ts +10 -6
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +66 -36
- package/src/exempt.ts +21 -3
- package/src/findings/completion.ts +24 -33
- package/src/findings/emitters.ts +32 -1
- package/src/findings/unused-declarations.ts +9 -2
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/inventory.ts +13 -2
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/references.ts +227 -28
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +59 -6
- package/src/roots.ts +60 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +2 -0
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- package/src/workspace.ts +463 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The convention rows a run applies, and the files they root. A row applies to a
|
|
3
|
+
* manifest that declares its enabling package when the installed package's version is
|
|
4
|
+
* in the row's range; its globs are read against that manifest's directory, with every
|
|
5
|
+
* directory a configuration property moves read from the property's literal value.
|
|
6
|
+
* No configuration file is run.
|
|
7
|
+
*/
|
|
8
|
+
import { isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
|
+
import { lineOf } from "./component-files.js";
|
|
10
|
+
import { CONVENTION_ROWS, } from "./convention-rows.js";
|
|
11
|
+
import { globExpression } from "./glob.js";
|
|
12
|
+
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
13
|
+
import { satisfies } from "./version-range.js";
|
|
14
|
+
const DEPENDENCY_SECTIONS = [
|
|
15
|
+
"dependencies",
|
|
16
|
+
"devDependencies",
|
|
17
|
+
"peerDependencies",
|
|
18
|
+
"optionalDependencies",
|
|
19
|
+
];
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function readJSON(host, path) {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(host.readFile(path));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function declares(manifest, name) {
|
|
32
|
+
if (!isRecord(manifest)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return DEPENDENCY_SECTIONS.some((section) => {
|
|
36
|
+
const held = manifest[section];
|
|
37
|
+
return isRecord(held) && Object.hasOwn(held, name);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The version the installed package's manifest names, read from the nearest
|
|
42
|
+
* `node_modules` at or above `dir` that holds it, `""` where that manifest names
|
|
43
|
+
* none, or `undefined` where no `node_modules` holds it.
|
|
44
|
+
*/
|
|
45
|
+
function installedVersion(host, dir, name) {
|
|
46
|
+
for (let at = normalizePath(dir);; at = dirnamePath(at)) {
|
|
47
|
+
const path = joinPath(at, "node_modules", name, "package.json");
|
|
48
|
+
if (host.kindOf(path) === "file") {
|
|
49
|
+
const manifest = readJSON(host, path);
|
|
50
|
+
const version = isRecord(manifest) ? manifest["version"] : undefined;
|
|
51
|
+
return typeof version === "string" ? version : "";
|
|
52
|
+
}
|
|
53
|
+
if (dirnamePath(at) === at) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Whether one node wraps an expression without changing its value. */
|
|
59
|
+
function isWrapper(node) {
|
|
60
|
+
return (isParenthesizedExpression(node) ||
|
|
61
|
+
isAsExpression(node) ||
|
|
62
|
+
isSatisfiesExpression(node) ||
|
|
63
|
+
isNonNullExpression(node));
|
|
64
|
+
}
|
|
65
|
+
/** The expression a wrapper that changes no value holds. */
|
|
66
|
+
function unwrapped(node) {
|
|
67
|
+
let at = node;
|
|
68
|
+
while (isWrapper(at)) {
|
|
69
|
+
at = at.expression;
|
|
70
|
+
}
|
|
71
|
+
return at;
|
|
72
|
+
}
|
|
73
|
+
/** The key one property is written with, where it is written as a name or a literal. */
|
|
74
|
+
function keyOf(property) {
|
|
75
|
+
if (!isPropertyAssignment(property) && !isShorthandPropertyAssignment(property)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const { name } = property;
|
|
79
|
+
if (isComputedPropertyName(name)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (isIdentifier(name) || isStringLiteral(name) || isNoSubstitutionTemplateLiteral(name)) {
|
|
83
|
+
return name.text;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/** The values one file's top-level variable declarations bind, by name. */
|
|
88
|
+
function declaredValues(file) {
|
|
89
|
+
const values = new Map();
|
|
90
|
+
for (const statement of file.statements) {
|
|
91
|
+
if (isVariableStatement(statement)) {
|
|
92
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
93
|
+
if (isIdentifier(declaration.name) && declaration.initializer !== undefined) {
|
|
94
|
+
values.set(declaration.name.text, declaration.initializer);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return values;
|
|
100
|
+
}
|
|
101
|
+
function isModuleExports(node) {
|
|
102
|
+
return (isPropertyAccessExpression(node) &&
|
|
103
|
+
isIdentifier(node.expression) &&
|
|
104
|
+
node.expression.text === "module" &&
|
|
105
|
+
node.name.text === "exports");
|
|
106
|
+
}
|
|
107
|
+
/** Every expression or declaration one file exports as its default, in any module syntax. */
|
|
108
|
+
function defaultExports(file) {
|
|
109
|
+
const found = [];
|
|
110
|
+
for (const statement of file.statements) {
|
|
111
|
+
if (isExportAssignment(statement)) {
|
|
112
|
+
found.push(statement.expression);
|
|
113
|
+
}
|
|
114
|
+
else if ((isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
|
|
115
|
+
statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true) {
|
|
116
|
+
found.push(statement);
|
|
117
|
+
}
|
|
118
|
+
else if (isExportDeclaration(statement) &&
|
|
119
|
+
statement.moduleSpecifier === undefined &&
|
|
120
|
+
statement.exportClause !== undefined &&
|
|
121
|
+
isNamedExports(statement.exportClause)) {
|
|
122
|
+
for (const element of statement.exportClause.elements) {
|
|
123
|
+
if (element.name.text === "default") {
|
|
124
|
+
found.push(element.propertyName ?? element.name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (isExpressionStatement(statement) &&
|
|
129
|
+
isBinaryExpression(statement.expression) &&
|
|
130
|
+
statement.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
131
|
+
isModuleExports(statement.expression.left)) {
|
|
132
|
+
found.push(statement.expression.right);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The first argument of every call one file makes to one module's export, through a
|
|
139
|
+
* named or a namespace import; `undefined` for a call with no argument.
|
|
140
|
+
*/
|
|
141
|
+
function optionsArguments(file, call) {
|
|
142
|
+
const names = new Set();
|
|
143
|
+
const namespaces = new Set();
|
|
144
|
+
for (const statement of file.statements) {
|
|
145
|
+
const bindings = isImportDeclaration(statement)
|
|
146
|
+
? statement.importClause?.namedBindings
|
|
147
|
+
: undefined;
|
|
148
|
+
if (bindings === undefined ||
|
|
149
|
+
!isImportDeclaration(statement) ||
|
|
150
|
+
!isStringLiteral(statement.moduleSpecifier) ||
|
|
151
|
+
statement.moduleSpecifier.text !== call.module) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (isNamespaceImport(bindings)) {
|
|
155
|
+
namespaces.add(bindings.name.text);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
for (const element of bindings.elements) {
|
|
159
|
+
if ((element.propertyName ?? element.name).text === call.export) {
|
|
160
|
+
names.add(element.name.text);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const found = [];
|
|
165
|
+
const visit = (node) => {
|
|
166
|
+
if (isCallExpression(node)) {
|
|
167
|
+
const callee = unwrapped(node.expression);
|
|
168
|
+
const named = isIdentifier(callee) && names.has(callee.text);
|
|
169
|
+
const throughNamespace = isPropertyAccessExpression(callee) &&
|
|
170
|
+
isIdentifier(callee.expression) &&
|
|
171
|
+
namespaces.has(callee.expression.text) &&
|
|
172
|
+
callee.name.text === call.export;
|
|
173
|
+
if (named || throughNamespace) {
|
|
174
|
+
found.push(node.arguments[0]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
node.forEachChild(visit);
|
|
178
|
+
};
|
|
179
|
+
file.forEachChild(visit);
|
|
180
|
+
return found;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The object literal one expression stands for, read through the file's top-level
|
|
184
|
+
* declarations and, where `throughCalls`, through a call's first argument: `undefined`
|
|
185
|
+
* for a call with no argument, and any other node where the expression is not written
|
|
186
|
+
* out as an object literal.
|
|
187
|
+
*/
|
|
188
|
+
function objectOf(node, declared, throughCalls, seen = new Set()) {
|
|
189
|
+
const at = unwrapped(node);
|
|
190
|
+
if (isIdentifier(at)) {
|
|
191
|
+
const value = declared.get(at.text);
|
|
192
|
+
if (value !== undefined && !seen.has(at.text)) {
|
|
193
|
+
return objectOf(value, declared, throughCalls, new Set([...seen, at.text]));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (throughCalls && isCallExpression(at)) {
|
|
197
|
+
const [first] = at.arguments;
|
|
198
|
+
return first === undefined ? undefined : objectOf(first, declared, throughCalls, seen);
|
|
199
|
+
}
|
|
200
|
+
return at;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Reads the values at one path of keys below one object literal into `values`, and
|
|
204
|
+
* answers the first member that does not write its value out: a spread, a value that is
|
|
205
|
+
* not a literal at the path's end, or one that is not an object literal before it.
|
|
206
|
+
*/
|
|
207
|
+
function readPath(object, keys, values) {
|
|
208
|
+
const [key, ...rest] = keys;
|
|
209
|
+
for (const member of object.properties) {
|
|
210
|
+
if (isSpreadAssignment(member)) {
|
|
211
|
+
return member;
|
|
212
|
+
}
|
|
213
|
+
if (keyOf(member) !== key) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
217
|
+
if (rest.length === 0) {
|
|
218
|
+
if (held === undefined || !(isStringLiteral(held) || isNoSubstitutionTemplateLiteral(held))) {
|
|
219
|
+
return member;
|
|
220
|
+
}
|
|
221
|
+
values.push(held.text);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
if (held === undefined || !isObjectLiteralExpression(held)) {
|
|
225
|
+
return member;
|
|
226
|
+
}
|
|
227
|
+
const failed = readPath(held, rest, values);
|
|
228
|
+
if (failed !== undefined) {
|
|
229
|
+
return failed;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Every value one file writes at one property's path inside the object it is read from:
|
|
237
|
+
* the options argument of every call `call` names, or the file's default export. A string
|
|
238
|
+
* literal or a template literal with no substitution is the value. A read object, or an
|
|
239
|
+
* object along the path, that is not written out as an object literal, or that spreads
|
|
240
|
+
* another object into itself, is not a literal, and neither is any other value at the path.
|
|
241
|
+
*/
|
|
242
|
+
export function readProperty(file, property, call) {
|
|
243
|
+
const declared = declaredValues(file);
|
|
244
|
+
const objects = call === undefined
|
|
245
|
+
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
246
|
+
: optionsArguments(file, call).map((one) => one === undefined ? undefined : objectOf(one, declared, false));
|
|
247
|
+
const values = [];
|
|
248
|
+
for (const object of objects) {
|
|
249
|
+
if (object === undefined) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const notLiteral = isObjectLiteralExpression(object)
|
|
253
|
+
? readPath(object, property.split("."), values)
|
|
254
|
+
: object;
|
|
255
|
+
if (notLiteral !== undefined) {
|
|
256
|
+
return { values, notLiteral };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return { values };
|
|
260
|
+
}
|
|
261
|
+
/** A directory below a manifest's, `.` for the directory itself, `undefined` outside it. */
|
|
262
|
+
function below(dir, path) {
|
|
263
|
+
return normalizePath(path) === normalizePath(dir) ? "." : relativePath(dir, path);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Every text a template stands for, each placeholder replaced by every directory its move
|
|
267
|
+
* resolved to. A placeholder whose move resolved outside the manifest's directory leaves
|
|
268
|
+
* the text standing for nothing.
|
|
269
|
+
*/
|
|
270
|
+
function expand(template, resolved) {
|
|
271
|
+
const match = /<([A-Za-z]+)>/u.exec(template);
|
|
272
|
+
if (match === null) {
|
|
273
|
+
return [template];
|
|
274
|
+
}
|
|
275
|
+
const [placeholder, id = ""] = match;
|
|
276
|
+
const out = [];
|
|
277
|
+
for (const value of resolved.get(id) ?? []) {
|
|
278
|
+
if (value === undefined) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const replaced = value === "." && template.startsWith(`${placeholder}/`, match.index)
|
|
282
|
+
? template.replace(`${placeholder}/`, "")
|
|
283
|
+
: template.replace(placeholder, value);
|
|
284
|
+
out.push(...expand(replaced, resolved));
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
/** The configuration files in one directory that one glob names, by name. */
|
|
289
|
+
function configurationFiles(host, dir, glob) {
|
|
290
|
+
const expression = globExpression(glob);
|
|
291
|
+
let entries;
|
|
292
|
+
try {
|
|
293
|
+
entries = host.readDirectory(dir);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
return entries
|
|
299
|
+
.filter((entry) => !entry.directory && expression.test(entry.name))
|
|
300
|
+
.map((entry) => entry.name)
|
|
301
|
+
.sort();
|
|
302
|
+
}
|
|
303
|
+
function resolveMove(host, parse, dir, row, move, resolved) {
|
|
304
|
+
const set = [];
|
|
305
|
+
for (const reading of move.readings) {
|
|
306
|
+
for (const glob of reading.files) {
|
|
307
|
+
for (const name of configurationFiles(host, dir, glob)) {
|
|
308
|
+
const path = joinPath(dir, name);
|
|
309
|
+
let text;
|
|
310
|
+
try {
|
|
311
|
+
text = host.readFile(path);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const file = parse(path, text);
|
|
317
|
+
const read = readProperty(file, reading.property, reading.call);
|
|
318
|
+
if (read.notLiteral !== undefined) {
|
|
319
|
+
const line = lineOf(text, read.notLiteral.getStart());
|
|
320
|
+
return {
|
|
321
|
+
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
322
|
+
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
323
|
+
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
324
|
+
`files in ts.entry_files`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
set.push(...read.values);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const dirs = [];
|
|
332
|
+
for (const base of expand(move.base ?? ".", resolved)) {
|
|
333
|
+
const against = joinPath(dir, base);
|
|
334
|
+
const texts = set.length > 0 ? set : move.defaults.flatMap((one) => expand(one, resolved));
|
|
335
|
+
for (const text of texts) {
|
|
336
|
+
dirs.push(below(dir, resolvePath(against, text)));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { dirs: [...new Set(dirs)] };
|
|
340
|
+
}
|
|
341
|
+
/** Two strings ordered bytewise. */
|
|
342
|
+
function compare(a, b) {
|
|
343
|
+
if (a === b) {
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
return a < b ? -1 : 1;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
350
|
+
* is skipped before anything is read for it.
|
|
351
|
+
*/
|
|
352
|
+
export function readConventions(host, parse, manifests, disabled, rows = CONVENTION_ROWS) {
|
|
353
|
+
const applied = [];
|
|
354
|
+
const globs = [];
|
|
355
|
+
const failures = [];
|
|
356
|
+
for (const manifest of manifests) {
|
|
357
|
+
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
358
|
+
const versions = new Map();
|
|
359
|
+
const missing = new Set();
|
|
360
|
+
for (const row of rows) {
|
|
361
|
+
if (disabled.includes(row.name) || !declares(declared, row.package)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (!versions.has(row.package)) {
|
|
365
|
+
versions.set(row.package, installedVersion(host, manifest.dir, row.package));
|
|
366
|
+
}
|
|
367
|
+
const version = versions.get(row.package);
|
|
368
|
+
if (version === undefined) {
|
|
369
|
+
if (missing.has(row.package)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
missing.add(row.package);
|
|
373
|
+
failures.push({
|
|
374
|
+
dir: manifest.dir,
|
|
375
|
+
failure: {
|
|
376
|
+
setupClass: "missing-module",
|
|
377
|
+
detail: `${manifest.path} declares ${row.package}, which no node_modules at or ` +
|
|
378
|
+
`above its directory holds: run the package manager's install`,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (!satisfies(version, row.range)) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const resolved = new Map();
|
|
387
|
+
let failed;
|
|
388
|
+
for (const move of row.moves) {
|
|
389
|
+
const result = resolveMove(host, parse, manifest.dir, row, move, resolved);
|
|
390
|
+
if ("failure" in result) {
|
|
391
|
+
failed = result.failure;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
resolved.set(move.id, result.dirs);
|
|
395
|
+
}
|
|
396
|
+
if (failed !== undefined) {
|
|
397
|
+
const where = dirnamePath(manifest.path);
|
|
398
|
+
failures.push({
|
|
399
|
+
dir: manifest.dir,
|
|
400
|
+
failure: {
|
|
401
|
+
setupClass: "convention-not-literal",
|
|
402
|
+
detail: where === "." ? failed : `${where}/${failed}`,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
applied.push({
|
|
408
|
+
name: row.name,
|
|
409
|
+
package: row.package,
|
|
410
|
+
version,
|
|
411
|
+
manifest: manifest.path,
|
|
412
|
+
});
|
|
413
|
+
const excludes = [
|
|
414
|
+
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
415
|
+
].map(globExpression);
|
|
416
|
+
for (const glob of new Set(row.entries.flatMap((entry) => expand(entry, resolved)))) {
|
|
417
|
+
globs.push({
|
|
418
|
+
dir: manifest.dir,
|
|
419
|
+
row: row.name,
|
|
420
|
+
expression: globExpression(glob),
|
|
421
|
+
excludes,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
427
|
+
unique.sort((a, b) => compare(a.name, b.name) ||
|
|
428
|
+
compare(a.manifest, b.manifest) ||
|
|
429
|
+
compare(JSON.stringify(a), JSON.stringify(b)));
|
|
430
|
+
return { applied: unique, globs, failures };
|
|
431
|
+
}
|
|
432
|
+
/** Every own file of one project an applied row's globs match, once per row. */
|
|
433
|
+
export function conventionEntries(conventions, files) {
|
|
434
|
+
const found = new Map();
|
|
435
|
+
for (const glob of conventions.globs) {
|
|
436
|
+
for (const file of files.byName.values()) {
|
|
437
|
+
const path = relativePath(glob.dir, file.fileName);
|
|
438
|
+
if (path !== undefined &&
|
|
439
|
+
glob.expression.test(path) &&
|
|
440
|
+
!glob.excludes.some((one) => one.test(path))) {
|
|
441
|
+
found.set(`${file.fileName}\u0000${glob.row}`, { file, row: glob.row });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return [...found.values()];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The failures the rows met that hold for one configuration: those of a manifest whose
|
|
449
|
+
* directory holds the configuration file or one of the files the project owns.
|
|
450
|
+
*/
|
|
451
|
+
export function conventionFailuresFor(conventions, configFile, ownFiles) {
|
|
452
|
+
return conventions.failures
|
|
453
|
+
.filter(({ dir }) => below(dir, dirnamePath(configFile)) !== undefined ||
|
|
454
|
+
ownFiles.some((file) => relativePath(dir, file) !== undefined))
|
|
455
|
+
.map(({ failure }) => failure);
|
|
456
|
+
}
|
package/dist/src/dependencies.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* The dependencies the target's manifest declares, and what the projects' own files
|
|
3
3
|
* need from outside the target. A file needs a package it imports, re-exports from,
|
|
4
4
|
* augments or references the types of, by name or through the file the specifier
|
|
5
|
-
* resolves to; a configuration needs one it names
|
|
5
|
+
* resolves to; a compiler configuration needs one it names, and a configuration file a
|
|
6
|
+
* tool loads needs each dependency its strings spell. A package one dependency pulls in
|
|
6
7
|
* for itself is that dependency's need, not the target's.
|
|
7
8
|
*/
|
|
8
9
|
import { isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
10
|
+
import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, } from "./configuration-files.js";
|
|
9
11
|
import { nodeKey, packageScope } from "./inventory.js";
|
|
10
12
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
11
13
|
import { UNANSWERED } from "./query.js";
|
|
14
|
+
import { sourceFilesOf } from "./source-files.js";
|
|
12
15
|
const MANIFEST = "package.json";
|
|
13
16
|
const MODULES_DIR = "node_modules";
|
|
14
17
|
const MODULES_SEGMENT = `/${MODULES_DIR}/`;
|
|
@@ -181,9 +184,10 @@ function bindingsOf(node) {
|
|
|
181
184
|
* Every package one project needs, and which declaration each use is written in.
|
|
182
185
|
* One local walk and one batched resolution per file: a use of an import binding is
|
|
183
186
|
* an identifier resolving to the binding's own symbol, so a local declaration that
|
|
184
|
-
* shadows the name is not one.
|
|
187
|
+
* shadows the name is not one. A string of a configuration file uses each dependency
|
|
188
|
+
* the manifest beside the file declares that it spells.
|
|
185
189
|
*/
|
|
186
|
-
export function projectNeeds(project, held, host) {
|
|
190
|
+
export function projectNeeds(project, held, host, targetRoot) {
|
|
187
191
|
const programPackages = new Set();
|
|
188
192
|
for (const name of project.program.getSourceFileNames()) {
|
|
189
193
|
const named = packageOfPath(name);
|
|
@@ -211,6 +215,29 @@ export function projectNeeds(project, held, host) {
|
|
|
211
215
|
for (const file of project.ownSourceFiles()) {
|
|
212
216
|
unanswered = fileNeeds(project, held, file, programPackages, use) || unanswered;
|
|
213
217
|
}
|
|
218
|
+
const beside = new Map();
|
|
219
|
+
const declaredAt = (path) => {
|
|
220
|
+
const dir = dirnamePath(path);
|
|
221
|
+
let names = beside.get(dir);
|
|
222
|
+
if (names === undefined) {
|
|
223
|
+
names = declaredBeside(host, dir);
|
|
224
|
+
beside.set(dir, names);
|
|
225
|
+
}
|
|
226
|
+
return names;
|
|
227
|
+
};
|
|
228
|
+
const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot));
|
|
229
|
+
for (const { file } of configuration.modules) {
|
|
230
|
+
const names = declaredAt(file.fileName);
|
|
231
|
+
for (const { node, text } of moduleStrings(file)) {
|
|
232
|
+
use(ownersOf(file, held, node), dependenciesNamed(text, names));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (const document of configuration.documents) {
|
|
236
|
+
const names = declaredAt(document.path);
|
|
237
|
+
for (const text of document.strings) {
|
|
238
|
+
use([PROJECT_OWNER], dependenciesNamed(text, names));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
214
241
|
return { packages, uses, unanswered };
|
|
215
242
|
}
|
|
216
243
|
/** The declarations of the inventory a use written at one node belongs to. */
|
package/dist/src/discover.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DiagnosticCategory } from "@typescript/native/unstable/sync";
|
|
2
2
|
import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
3
|
+
import { SetupError } from "./setup-failure.js";
|
|
3
4
|
/** Directory names discovery does not descend into. */
|
|
4
5
|
export const IGNORED_DIRECTORIES = new Set(["node_modules", ".git"]);
|
|
5
6
|
/** A compiler configuration file discovery recognises under a root. */
|
|
@@ -217,10 +218,16 @@ function derivedFrom(engine, host, pending, unreadable) {
|
|
|
217
218
|
*/
|
|
218
219
|
export function consumerProjects(engine, host, root) {
|
|
219
220
|
const kind = host.kindOf(root);
|
|
221
|
+
if (kind === "absent") {
|
|
222
|
+
throw new SetupError([
|
|
223
|
+
{
|
|
224
|
+
setupClass: "missing-consumer",
|
|
225
|
+
detail: `the scope declares the consumer ${root}, and nothing is at that path: check the consumer out at ${root} and install its dependencies`,
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
220
229
|
if (kind !== "directory") {
|
|
221
|
-
throw new DiscoveryError(
|
|
222
|
-
? `the consumer ${root} does not exist`
|
|
223
|
-
: `the consumer ${root} is not a directory`, []);
|
|
230
|
+
throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
|
|
224
231
|
}
|
|
225
232
|
const found = derivedFrom(engine, host, configFilesUnder(host, root));
|
|
226
233
|
if (found.length === 0) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the compiler writes what it compiles: the output extension each source
|
|
3
|
+
* extension becomes, and the output path one source path is written to. A path a
|
|
4
|
+
* manifest or a document names is read back to its source through this one rule,
|
|
5
|
+
* whether the path is the target's own or a workspace package's.
|
|
6
|
+
*/
|
|
7
|
+
import { dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
8
|
+
/** The source extensions the compiler emits each output extension from. */
|
|
9
|
+
const EMITTED = [
|
|
10
|
+
{ emitted: ".d.ts", sources: [".ts", ".tsx"] },
|
|
11
|
+
{ emitted: ".d.mts", sources: [".mts"] },
|
|
12
|
+
{ emitted: ".d.cts", sources: [".cts"] },
|
|
13
|
+
{ emitted: ".js", sources: [".ts", ".tsx"] },
|
|
14
|
+
{ emitted: ".jsx", sources: [".tsx"] },
|
|
15
|
+
{ emitted: ".mjs", sources: [".mts"] },
|
|
16
|
+
{ emitted: ".cjs", sources: [".cts"] },
|
|
17
|
+
];
|
|
18
|
+
/** One path with its extension replaced, where it carries the one named. */
|
|
19
|
+
function reExtended(path, emitted, source) {
|
|
20
|
+
return path.endsWith(emitted) ? path.slice(0, path.length - emitted.length) + source : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The longest directory every path shares, which is where the compiler roots its
|
|
24
|
+
* output when the configuration declares no root directory and its directory is not
|
|
25
|
+
* the root.
|
|
26
|
+
*/
|
|
27
|
+
export function commonDirectory(paths) {
|
|
28
|
+
const first = paths[0];
|
|
29
|
+
if (first === undefined) {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
let common = dirnamePath(first).split("/");
|
|
33
|
+
for (const path of paths.slice(1)) {
|
|
34
|
+
const segments = dirnamePath(path).split("/");
|
|
35
|
+
let shared = 0;
|
|
36
|
+
while (shared < common.length &&
|
|
37
|
+
shared < segments.length &&
|
|
38
|
+
common[shared] === segments[shared]) {
|
|
39
|
+
shared += 1;
|
|
40
|
+
}
|
|
41
|
+
common = common.slice(0, shared);
|
|
42
|
+
}
|
|
43
|
+
return common.join("/");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The source paths one written path may be compiled from, in the order they are
|
|
47
|
+
* tried: for each output extension the path carries, the path re-extended where it
|
|
48
|
+
* stands, then read below each source directory where it is below the directory that
|
|
49
|
+
* output is written to.
|
|
50
|
+
*/
|
|
51
|
+
export function emittedFrom(path, layout) {
|
|
52
|
+
const found = [];
|
|
53
|
+
for (const { emitted, sources } of EMITTED) {
|
|
54
|
+
if (!path.endsWith(emitted)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const emitDir = emitted.startsWith(".d.")
|
|
58
|
+
? (layout.declarationDir ?? layout.outDir)
|
|
59
|
+
: layout.outDir;
|
|
60
|
+
const below = emitDir === undefined ? undefined : relativePath(emitDir, path);
|
|
61
|
+
const mapped = below === undefined ? [] : layout.sourceDirs.map((dir) => joinPath(dir, below));
|
|
62
|
+
for (const source of sources) {
|
|
63
|
+
for (const from of [path, ...mapped]) {
|
|
64
|
+
const candidate = reExtended(from, emitted, source);
|
|
65
|
+
if (candidate !== undefined) {
|
|
66
|
+
found.push(candidate);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|