@cplieger/deadset-ts 4.0.1 → 5.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 +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 +301 -41
- 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 +290 -0
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +240 -0
- package/dist/src/convention-rows.js +389 -0
- package/dist/src/conventions.js +565 -0
- package/dist/src/dependencies.js +36 -3
- package/dist/src/destructuring.js +6 -6
- 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 +50 -32
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +32 -20
- package/dist/src/findings/completion.js +46 -32
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/interfaces.js +6 -2
- package/dist/src/findings/reads-and-writes.js +53 -9
- package/dist/src/findings/unused-declarations.js +47 -3
- package/dist/src/generated-file.js +24 -0
- package/dist/src/graph.js +13 -5
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +8 -3
- package/dist/src/kinds.js +6 -5
- 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/query.js +15 -0
- package/dist/src/references.js +404 -36
- 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 +37 -11
- 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/stores.js +3 -0
- 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/value-flow.js +74 -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 +3 -2
- package/src/analysis.ts +475 -72
- 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 +370 -0
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +354 -0
- package/src/convention-rows.ts +448 -0
- package/src/conventions.ts +788 -0
- package/src/dependencies.ts +49 -2
- package/src/destructuring.ts +16 -6
- 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 +79 -36
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +69 -20
- package/src/file-facts.ts +2 -0
- package/src/findings/completion.ts +55 -36
- package/src/findings/emitters.ts +32 -1
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +60 -3
- package/src/generated-file.ts +27 -0
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +17 -3
- package/src/kinds.ts +7 -5
- 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/query.ts +26 -0
- package/src/references.ts +472 -40
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +65 -11
- package/src/roots.ts +70 -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/stores.ts +9 -0
- 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/value-flow.ts +104 -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,565 @@
|
|
|
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, isNumericLiteral, isObjectBindingPattern, 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
|
+
/** The sections an installed package is required by, so its absence fails the run. */
|
|
15
|
+
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"];
|
|
16
|
+
const DEPENDENCY_SECTIONS = [
|
|
17
|
+
...REQUIRED_SECTIONS,
|
|
18
|
+
"peerDependencies",
|
|
19
|
+
"optionalDependencies",
|
|
20
|
+
];
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function readJSON(host, path) {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(host.readFile(path));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function declares(manifest, name, sections = DEPENDENCY_SECTIONS) {
|
|
33
|
+
if (!isRecord(manifest)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return sections.some((section) => {
|
|
37
|
+
const held = manifest[section];
|
|
38
|
+
return isRecord(held) && Object.hasOwn(held, name);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The version the installed package's manifest names, read from the nearest
|
|
43
|
+
* `node_modules` at or above `dir` that holds it, `""` where that manifest names
|
|
44
|
+
* none, or `undefined` where no `node_modules` holds it.
|
|
45
|
+
*/
|
|
46
|
+
function installedVersion(host, dir, name) {
|
|
47
|
+
for (let at = normalizePath(dir);; at = dirnamePath(at)) {
|
|
48
|
+
const path = joinPath(at, "node_modules", name, "package.json");
|
|
49
|
+
if (host.kindOf(path) === "file") {
|
|
50
|
+
const manifest = readJSON(host, path);
|
|
51
|
+
const version = isRecord(manifest) ? manifest["version"] : undefined;
|
|
52
|
+
return typeof version === "string" ? version : "";
|
|
53
|
+
}
|
|
54
|
+
if (dirnamePath(at) === at) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Whether one node wraps an expression without changing its value. */
|
|
60
|
+
function isWrapper(node) {
|
|
61
|
+
return (isParenthesizedExpression(node) ||
|
|
62
|
+
isAsExpression(node) ||
|
|
63
|
+
isSatisfiesExpression(node) ||
|
|
64
|
+
isNonNullExpression(node));
|
|
65
|
+
}
|
|
66
|
+
/** The expression a wrapper that changes no value holds. */
|
|
67
|
+
function unwrapped(node) {
|
|
68
|
+
let at = node;
|
|
69
|
+
while (isWrapper(at)) {
|
|
70
|
+
at = at.expression;
|
|
71
|
+
}
|
|
72
|
+
return at;
|
|
73
|
+
}
|
|
74
|
+
/** The key one property is written with, where it is written as a name or as literal text. */
|
|
75
|
+
function keyOf(property) {
|
|
76
|
+
if (!isPropertyAssignment(property) && !isShorthandPropertyAssignment(property)) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const { name } = property;
|
|
80
|
+
const key = isComputedPropertyName(name) ? name.expression : name;
|
|
81
|
+
if ((isIdentifier(key) && key === name) ||
|
|
82
|
+
isStringLiteral(key) ||
|
|
83
|
+
isNoSubstitutionTemplateLiteral(key)) {
|
|
84
|
+
return key.text;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/** Whether one member's computed name is no literal, so it may set any key. */
|
|
89
|
+
function computesKey(member) {
|
|
90
|
+
const name = member.name;
|
|
91
|
+
if (name === undefined || !isComputedPropertyName(name)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const key = name.expression;
|
|
95
|
+
return !(isStringLiteral(key) || isNumericLiteral(key) || isNoSubstitutionTemplateLiteral(key));
|
|
96
|
+
}
|
|
97
|
+
/** The values one file's top-level variable declarations bind, by name. */
|
|
98
|
+
function declaredValues(file) {
|
|
99
|
+
const values = new Map();
|
|
100
|
+
for (const statement of file.statements) {
|
|
101
|
+
if (isVariableStatement(statement)) {
|
|
102
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
103
|
+
if (isIdentifier(declaration.name) && declaration.initializer !== undefined) {
|
|
104
|
+
values.set(declaration.name.text, declaration.initializer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return values;
|
|
110
|
+
}
|
|
111
|
+
function isModuleExports(node) {
|
|
112
|
+
return (isPropertyAccessExpression(node) &&
|
|
113
|
+
isIdentifier(node.expression) &&
|
|
114
|
+
node.expression.text === "module" &&
|
|
115
|
+
node.name.text === "exports");
|
|
116
|
+
}
|
|
117
|
+
/** Every expression or declaration one file exports as its default, in any module syntax. */
|
|
118
|
+
function defaultExports(file) {
|
|
119
|
+
const found = [];
|
|
120
|
+
for (const statement of file.statements) {
|
|
121
|
+
if (isExportAssignment(statement)) {
|
|
122
|
+
found.push(statement.expression);
|
|
123
|
+
}
|
|
124
|
+
else if ((isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
|
|
125
|
+
statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true) {
|
|
126
|
+
found.push(statement);
|
|
127
|
+
}
|
|
128
|
+
else if (isExportDeclaration(statement) &&
|
|
129
|
+
statement.moduleSpecifier === undefined &&
|
|
130
|
+
statement.exportClause !== undefined &&
|
|
131
|
+
isNamedExports(statement.exportClause)) {
|
|
132
|
+
for (const element of statement.exportClause.elements) {
|
|
133
|
+
if (element.name.text === "default") {
|
|
134
|
+
found.push(element.propertyName ?? element.name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (isExpressionStatement(statement) &&
|
|
139
|
+
isBinaryExpression(statement.expression) &&
|
|
140
|
+
statement.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
141
|
+
isModuleExports(statement.expression.left)) {
|
|
142
|
+
found.push(statement.expression.right);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return found;
|
|
146
|
+
}
|
|
147
|
+
/** The declarations whose name is a binding of the scope they are written in. */
|
|
148
|
+
const BINDING_KINDS = new Set([
|
|
149
|
+
SyntaxKind.VariableDeclaration,
|
|
150
|
+
SyntaxKind.Parameter,
|
|
151
|
+
SyntaxKind.BindingElement,
|
|
152
|
+
SyntaxKind.FunctionDeclaration,
|
|
153
|
+
SyntaxKind.ClassDeclaration,
|
|
154
|
+
SyntaxKind.ImportClause,
|
|
155
|
+
SyntaxKind.ImportSpecifier,
|
|
156
|
+
SyntaxKind.NamespaceImport,
|
|
157
|
+
SyntaxKind.ImportEqualsDeclaration,
|
|
158
|
+
]);
|
|
159
|
+
/** Whether one file declares a binding named `require` at any depth, which shadows the global. */
|
|
160
|
+
function declaresRequire(file) {
|
|
161
|
+
let found = false;
|
|
162
|
+
const visit = (node) => {
|
|
163
|
+
const name = node.name;
|
|
164
|
+
if (name !== undefined &&
|
|
165
|
+
isIdentifier(name) &&
|
|
166
|
+
name.text === "require" &&
|
|
167
|
+
BINDING_KINDS.has(node.kind)) {
|
|
168
|
+
found = true;
|
|
169
|
+
}
|
|
170
|
+
if (!found) {
|
|
171
|
+
node.forEachChild(visit);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
file.forEachChild(visit);
|
|
175
|
+
return found;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The top-level `const`, `let` and `var` declarations that bind a `require` of one module,
|
|
179
|
+
* written with the module's name as its one string-literal argument, in a file that
|
|
180
|
+
* declares no binding named `require`: each binds as an import of the module does.
|
|
181
|
+
*/
|
|
182
|
+
function requireBindings(file, module) {
|
|
183
|
+
const found = [];
|
|
184
|
+
for (const statement of file.statements) {
|
|
185
|
+
if (!isVariableStatement(statement)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
189
|
+
const value = declaration.initializer === undefined ? undefined : unwrapped(declaration.initializer);
|
|
190
|
+
const [argument, ...rest] = value !== undefined && isCallExpression(value) ? value.arguments : [];
|
|
191
|
+
if (value === undefined ||
|
|
192
|
+
!isCallExpression(value) ||
|
|
193
|
+
!isIdentifier(value.expression) ||
|
|
194
|
+
value.expression.text !== "require" ||
|
|
195
|
+
argument === undefined ||
|
|
196
|
+
rest.length > 0 ||
|
|
197
|
+
!isStringLiteral(argument) ||
|
|
198
|
+
argument.text !== module) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (isIdentifier(declaration.name) || isObjectBindingPattern(declaration.name)) {
|
|
202
|
+
found.push(declaration.name);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return found.length > 0 && declaresRequire(file) ? [] : found;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The first argument of every call one file makes to one module's export, through a
|
|
210
|
+
* named or a namespace import; `undefined` for a call with no argument.
|
|
211
|
+
*/
|
|
212
|
+
function optionsArguments(file, call) {
|
|
213
|
+
const names = new Set();
|
|
214
|
+
const namespaces = new Set();
|
|
215
|
+
for (const name of requireBindings(file, call.module)) {
|
|
216
|
+
if (isIdentifier(name)) {
|
|
217
|
+
namespaces.add(name.text);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
for (const element of name.elements) {
|
|
221
|
+
const local = element.name;
|
|
222
|
+
const exported = element.propertyName ?? local;
|
|
223
|
+
if (element.dotDotDotToken === undefined &&
|
|
224
|
+
local !== undefined &&
|
|
225
|
+
isIdentifier(local) &&
|
|
226
|
+
exported !== undefined &&
|
|
227
|
+
(isIdentifier(exported) || isStringLiteral(exported)) &&
|
|
228
|
+
exported.text === call.export) {
|
|
229
|
+
names.add(local.text);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const statement of file.statements) {
|
|
234
|
+
const bindings = isImportDeclaration(statement)
|
|
235
|
+
? statement.importClause?.namedBindings
|
|
236
|
+
: undefined;
|
|
237
|
+
if (bindings === undefined ||
|
|
238
|
+
!isImportDeclaration(statement) ||
|
|
239
|
+
!isStringLiteral(statement.moduleSpecifier) ||
|
|
240
|
+
statement.moduleSpecifier.text !== call.module) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (isNamespaceImport(bindings)) {
|
|
244
|
+
namespaces.add(bindings.name.text);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
for (const element of bindings.elements) {
|
|
248
|
+
if ((element.propertyName ?? element.name).text === call.export) {
|
|
249
|
+
names.add(element.name.text);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const found = [];
|
|
254
|
+
const visit = (node) => {
|
|
255
|
+
if (isCallExpression(node)) {
|
|
256
|
+
const callee = unwrapped(node.expression);
|
|
257
|
+
const named = isIdentifier(callee) && names.has(callee.text);
|
|
258
|
+
const throughNamespace = isPropertyAccessExpression(callee) &&
|
|
259
|
+
isIdentifier(callee.expression) &&
|
|
260
|
+
namespaces.has(callee.expression.text) &&
|
|
261
|
+
callee.name.text === call.export;
|
|
262
|
+
if (named || throughNamespace) {
|
|
263
|
+
found.push(node.arguments[0]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
node.forEachChild(visit);
|
|
267
|
+
};
|
|
268
|
+
file.forEachChild(visit);
|
|
269
|
+
return found;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The object literal one expression stands for, read through the file's top-level
|
|
273
|
+
* declarations and, where `throughCalls`, through a call's first argument: `undefined`
|
|
274
|
+
* for a call with no argument, and any other node where the expression is not written
|
|
275
|
+
* out as an object literal.
|
|
276
|
+
*/
|
|
277
|
+
function objectOf(node, declared, throughCalls, seen = new Set()) {
|
|
278
|
+
const at = unwrapped(node);
|
|
279
|
+
if (isIdentifier(at)) {
|
|
280
|
+
const value = declared.get(at.text);
|
|
281
|
+
if (value !== undefined && !seen.has(at.text)) {
|
|
282
|
+
return objectOf(value, declared, throughCalls, new Set([...seen, at.text]));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else if (throughCalls && isCallExpression(at)) {
|
|
286
|
+
const [first] = at.arguments;
|
|
287
|
+
return first === undefined ? undefined : objectOf(first, declared, throughCalls, seen);
|
|
288
|
+
}
|
|
289
|
+
return at;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Reads the values at one path of keys below one object literal into `values`, and
|
|
293
|
+
* answers the first member that does not write its value out: a spread, a value that is
|
|
294
|
+
* not a literal at the path's end, or one that is not an object literal before it.
|
|
295
|
+
*/
|
|
296
|
+
function readPath(object, keys, values) {
|
|
297
|
+
const [key, ...rest] = keys;
|
|
298
|
+
for (const member of object.properties) {
|
|
299
|
+
if (isSpreadAssignment(member) || computesKey(member)) {
|
|
300
|
+
return member;
|
|
301
|
+
}
|
|
302
|
+
if (keyOf(member) !== key) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
306
|
+
if (rest.length === 0) {
|
|
307
|
+
if (held === undefined || !(isStringLiteral(held) || isNoSubstitutionTemplateLiteral(held))) {
|
|
308
|
+
return member;
|
|
309
|
+
}
|
|
310
|
+
values.push(held.text);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
if (held === undefined || !isObjectLiteralExpression(held)) {
|
|
314
|
+
return member;
|
|
315
|
+
}
|
|
316
|
+
const failed = readPath(held, rest, values);
|
|
317
|
+
if (failed !== undefined) {
|
|
318
|
+
return failed;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Every value one file writes at one property's path inside the object it is read from:
|
|
326
|
+
* the options argument of every call `call` names, or the file's default export. A string
|
|
327
|
+
* literal or a template literal with no substitution is the value. A read object, or an
|
|
328
|
+
* object along the path, that is not written out as an object literal, or that spreads
|
|
329
|
+
* another object into itself, is not a literal, and neither is any other value at the path.
|
|
330
|
+
*/
|
|
331
|
+
export function readProperty(file, property, call) {
|
|
332
|
+
const declared = declaredValues(file);
|
|
333
|
+
const objects = call === undefined
|
|
334
|
+
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
335
|
+
: optionsArguments(file, call).map((one) => one === undefined ? undefined : objectOf(one, declared, false));
|
|
336
|
+
const values = [];
|
|
337
|
+
for (const object of objects) {
|
|
338
|
+
if (object === undefined) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const notLiteral = isObjectLiteralExpression(object)
|
|
342
|
+
? readPath(object, property.split("."), values)
|
|
343
|
+
: object;
|
|
344
|
+
if (notLiteral !== undefined) {
|
|
345
|
+
return { values, notLiteral };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return { values };
|
|
349
|
+
}
|
|
350
|
+
/** A directory below a manifest's, `.` for the directory itself, `undefined` outside it. */
|
|
351
|
+
function below(dir, path) {
|
|
352
|
+
return normalizePath(path) === normalizePath(dir) ? "." : relativePath(dir, path);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Every text a template stands for, each placeholder replaced by every directory its move
|
|
356
|
+
* resolved to. A placeholder whose move resolved outside the manifest's directory leaves
|
|
357
|
+
* the text standing for nothing.
|
|
358
|
+
*/
|
|
359
|
+
function expand(template, resolved) {
|
|
360
|
+
const match = /<([A-Za-z]+)>/u.exec(template);
|
|
361
|
+
if (match === null) {
|
|
362
|
+
return [template];
|
|
363
|
+
}
|
|
364
|
+
const [placeholder, id = ""] = match;
|
|
365
|
+
const out = [];
|
|
366
|
+
for (const value of resolved.get(id) ?? []) {
|
|
367
|
+
if (value === undefined) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const replaced = value === "." && template.startsWith(`${placeholder}/`, match.index)
|
|
371
|
+
? template.replace(`${placeholder}/`, "")
|
|
372
|
+
: template.replace(placeholder, value);
|
|
373
|
+
out.push(...expand(replaced, resolved));
|
|
374
|
+
}
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
/** The configuration files in one directory that one glob names, by name. */
|
|
378
|
+
function configurationFiles(host, dir, glob) {
|
|
379
|
+
const expression = globExpression(glob);
|
|
380
|
+
let entries;
|
|
381
|
+
try {
|
|
382
|
+
entries = host.readDirectory(dir);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
return entries
|
|
388
|
+
.filter((entry) => !entry.directory && expression.test(entry.name))
|
|
389
|
+
.map((entry) => entry.name)
|
|
390
|
+
.sort();
|
|
391
|
+
}
|
|
392
|
+
function resolveMove(host, parse, dir, row, move, resolved) {
|
|
393
|
+
const set = [];
|
|
394
|
+
for (const reading of move.readings) {
|
|
395
|
+
for (const glob of reading.files) {
|
|
396
|
+
for (const name of configurationFiles(host, dir, glob)) {
|
|
397
|
+
const path = joinPath(dir, name);
|
|
398
|
+
let text;
|
|
399
|
+
try {
|
|
400
|
+
text = host.readFile(path);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const file = parse(path, text);
|
|
406
|
+
const read = readProperty(file, reading.property, reading.call);
|
|
407
|
+
if (read.notLiteral !== undefined) {
|
|
408
|
+
const line = lineOf(text, read.notLiteral.getStart());
|
|
409
|
+
return {
|
|
410
|
+
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
411
|
+
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
412
|
+
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
413
|
+
`files in ts.entry_files`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
set.push(...read.values);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const dirs = [];
|
|
421
|
+
for (const base of expand(move.base ?? ".", resolved)) {
|
|
422
|
+
const against = joinPath(dir, base);
|
|
423
|
+
const texts = set.length > 0 ? set : move.defaults.flatMap((one) => expand(one, resolved));
|
|
424
|
+
for (const text of texts) {
|
|
425
|
+
dirs.push(below(dir, resolvePath(against, text)));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return { dirs: [...new Set(dirs)] };
|
|
429
|
+
}
|
|
430
|
+
/** Two strings ordered bytewise. */
|
|
431
|
+
function compare(a, b) {
|
|
432
|
+
if (a === b) {
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
return a < b ? -1 : 1;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* What the rows decide over the manifests the analysis reads. A row `disabled` names
|
|
439
|
+
* is skipped before anything is read for it.
|
|
440
|
+
*/
|
|
441
|
+
export function readConventions(host, parse, manifests, disabled, rows = CONVENTION_ROWS) {
|
|
442
|
+
const applied = [];
|
|
443
|
+
const globs = [];
|
|
444
|
+
const generated = [];
|
|
445
|
+
const failures = [];
|
|
446
|
+
for (const manifest of manifests) {
|
|
447
|
+
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
448
|
+
const versions = new Map();
|
|
449
|
+
const missing = new Set();
|
|
450
|
+
for (const row of rows) {
|
|
451
|
+
if (disabled.includes(row.name) || !declares(declared, row.package)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (!versions.has(row.package)) {
|
|
455
|
+
versions.set(row.package, installedVersion(host, manifest.dir, row.package));
|
|
456
|
+
}
|
|
457
|
+
const version = versions.get(row.package);
|
|
458
|
+
if (version === undefined) {
|
|
459
|
+
if (missing.has(row.package) || !declares(declared, row.package, REQUIRED_SECTIONS)) {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
missing.add(row.package);
|
|
463
|
+
failures.push({
|
|
464
|
+
dir: manifest.dir,
|
|
465
|
+
failure: {
|
|
466
|
+
setupClass: "missing-module",
|
|
467
|
+
detail: `${manifest.path} declares ${row.package}, which no node_modules at or ` +
|
|
468
|
+
`above its directory holds: run the package manager's install`,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (!satisfies(version, row.range)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const resolved = new Map();
|
|
477
|
+
let failed;
|
|
478
|
+
for (const move of row.moves) {
|
|
479
|
+
const result = resolveMove(host, parse, manifest.dir, row, move, resolved);
|
|
480
|
+
if ("failure" in result) {
|
|
481
|
+
failed = result.failure;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
resolved.set(move.id, result.dirs);
|
|
485
|
+
}
|
|
486
|
+
if (failed !== undefined) {
|
|
487
|
+
const where = dirnamePath(manifest.path);
|
|
488
|
+
failures.push({
|
|
489
|
+
dir: manifest.dir,
|
|
490
|
+
failure: {
|
|
491
|
+
setupClass: "convention-not-literal",
|
|
492
|
+
detail: where === "." ? failed : `${where}/${failed}`,
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
applied.push({
|
|
498
|
+
name: row.name,
|
|
499
|
+
package: row.package,
|
|
500
|
+
version,
|
|
501
|
+
manifest: manifest.path,
|
|
502
|
+
});
|
|
503
|
+
const excludes = [
|
|
504
|
+
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
505
|
+
].map(globExpression);
|
|
506
|
+
for (const below of new Set((row.generated ?? []).flatMap((one) => expand(one, resolved)))) {
|
|
507
|
+
generated.push({ dir: manifest.dir, row: row.name, below });
|
|
508
|
+
}
|
|
509
|
+
for (const glob of new Set(row.entries.flatMap((entry) => expand(entry, resolved)))) {
|
|
510
|
+
globs.push({
|
|
511
|
+
dir: manifest.dir,
|
|
512
|
+
row: row.name,
|
|
513
|
+
expression: globExpression(glob),
|
|
514
|
+
excludes,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
|
|
520
|
+
unique.sort((a, b) => compare(a.name, b.name) ||
|
|
521
|
+
compare(a.manifest, b.manifest) ||
|
|
522
|
+
compare(JSON.stringify(a), JSON.stringify(b)));
|
|
523
|
+
return { applied: unique, globs, generated, failures };
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Every own file of one project below a directory an applied row names as generated, by
|
|
527
|
+
* its path below the target root, with the name of the first such row.
|
|
528
|
+
*/
|
|
529
|
+
export function generatedFiles(conventions, files) {
|
|
530
|
+
const found = new Map();
|
|
531
|
+
for (const { dir, row, below } of conventions.generated) {
|
|
532
|
+
const root = joinPath(dir, below);
|
|
533
|
+
for (const [path, file] of files.byPath) {
|
|
534
|
+
if (!found.has(path) && relativePath(root, file.fileName) !== undefined) {
|
|
535
|
+
found.set(path, row);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return found;
|
|
540
|
+
}
|
|
541
|
+
/** Every own file of one project an applied row's globs match, once per row. */
|
|
542
|
+
export function conventionEntries(conventions, files) {
|
|
543
|
+
const found = new Map();
|
|
544
|
+
for (const glob of conventions.globs) {
|
|
545
|
+
for (const file of files.byName.values()) {
|
|
546
|
+
const path = relativePath(glob.dir, file.fileName);
|
|
547
|
+
if (path !== undefined &&
|
|
548
|
+
glob.expression.test(path) &&
|
|
549
|
+
!glob.excludes.some((one) => one.test(path))) {
|
|
550
|
+
found.set(`${file.fileName}\u0000${glob.row}`, { file, row: glob.row });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return [...found.values()];
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The failures the rows met that hold for one configuration: those of a manifest whose
|
|
558
|
+
* directory holds the configuration file or one of the files the project owns.
|
|
559
|
+
*/
|
|
560
|
+
export function conventionFailuresFor(conventions, configFile, ownFiles) {
|
|
561
|
+
return conventions.failures
|
|
562
|
+
.filter(({ dir }) => below(dir, dirnamePath(configFile)) !== undefined ||
|
|
563
|
+
ownFiles.some((file) => relativePath(dir, file) !== undefined))
|
|
564
|
+
.map(({ failure }) => failure);
|
|
565
|
+
}
|
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, parse) {
|
|
187
191
|
const programPackages = new Set();
|
|
188
192
|
for (const name of project.program.getSourceFileNames()) {
|
|
189
193
|
const named = packageOfPath(name);
|
|
@@ -211,6 +215,35 @@ 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), parse);
|
|
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
|
+
}
|
|
241
|
+
for (const { file, manifestDir } of configuration.outside) {
|
|
242
|
+
const names = declaredAt(joinPath(manifestDir, "package.json"));
|
|
243
|
+
for (const { text } of moduleStrings(file)) {
|
|
244
|
+
use([PROJECT_OWNER], dependenciesNamed(text, names));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
214
247
|
return { packages, uses, unanswered };
|
|
215
248
|
}
|
|
216
249
|
/** The declarations of the inventory a use written at one node belongs to. */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
9
|
import { TypeFlags } from "@typescript/native/unstable/sync";
|
|
10
|
-
import { isAnswered, UNANSWERED } from "./query.js";
|
|
10
|
+
import { isAnswered, propertyTables, UNANSWERED, } from "./query.js";
|
|
11
11
|
/** The text a property name spells, or `undefined` for a computed name. */
|
|
12
12
|
function nameText(node) {
|
|
13
13
|
if (node === undefined) {
|
|
@@ -151,7 +151,7 @@ function sourceOf(target) {
|
|
|
151
151
|
* read once per type for the whole project, since one type is destructured in many
|
|
152
152
|
* places and a table answers every name of it.
|
|
153
153
|
*/
|
|
154
|
-
export function destructuring(project, cap) {
|
|
154
|
+
export function destructuring(project, cap, properties = propertyTables(project.queries)) {
|
|
155
155
|
const queries = project.queries;
|
|
156
156
|
const tables = new Map();
|
|
157
157
|
let patternBatches = 0;
|
|
@@ -163,10 +163,10 @@ export function destructuring(project, cap) {
|
|
|
163
163
|
}
|
|
164
164
|
let table = new Map();
|
|
165
165
|
if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
table = isAnswered(properties)
|
|
169
|
-
? new Map(properties.map((property) => [property.name, property]))
|
|
166
|
+
const read = properties.of(type);
|
|
167
|
+
patternLookups += read.asked ? 1 : 0;
|
|
168
|
+
table = isAnswered(read.properties)
|
|
169
|
+
? new Map(read.properties.map((property) => [property.name, property]))
|
|
170
170
|
: UNANSWERED;
|
|
171
171
|
}
|
|
172
172
|
tables.set(type.id, table);
|
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) {
|