@cplieger/deadset-ts 2.3.0 → 3.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 +7 -16
- package/dist/src/alias-chain.js +96 -0
- package/dist/src/analysis.js +50 -16
- package/dist/src/components.js +289 -0
- package/dist/src/discover.js +98 -24
- package/dist/src/graph.js +120 -0
- package/dist/src/index.js +0 -2
- package/dist/src/matrix.js +138 -0
- package/dist/src/print.js +3 -6
- package/dist/src/references.js +162 -79
- package/dist/src/resolve.js +82 -20
- package/dist/src/roots.js +14 -37
- package/dist/src/run.js +11 -7
- package/dist/src/schema.js +1 -1
- package/dist/src/session.js +16 -5
- package/dist/src/sweep.js +152 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alias-chain.ts +135 -0
- package/src/analysis.ts +85 -24
- package/src/components.ts +372 -0
- package/src/config.ts +23 -2
- package/src/discover.ts +144 -29
- package/src/graph.ts +187 -0
- package/src/index.ts +0 -20
- package/src/matrix.ts +212 -0
- package/src/print.ts +5 -6
- package/src/references.ts +235 -93
- package/src/resolve.ts +117 -24
- package/src/roots.ts +15 -46
- package/src/run.ts +11 -7
- package/src/schema.ts +5 -2
- package/src/session.ts +18 -5
- package/src/sweep.ts +224 -0
- package/src/version.ts +1 -1
package/dist/src/references.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isBinaryExpression, isDeleteExpression, isElementAccessExpression, isForInStatement, isForOfStatement, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
|
-
import {
|
|
1
|
+
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExternalModuleReference, isForInStatement, isForOfStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
|
+
import { aliasChains } from "./alias-chain.js";
|
|
3
3
|
import { globExpression } from "./glob.js";
|
|
4
4
|
import { nodeKey } from "./inventory.js";
|
|
5
5
|
import { byPosition, renderPosition } from "./position.js";
|
|
@@ -126,18 +126,87 @@ function markStores(node, writes) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
130
|
+
function isLiteralText(node) {
|
|
131
|
+
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The module specifier of the module one node evaluates, if it evaluates one: an import
|
|
135
|
+
* declaration, an import-equals declaration of an external module, an export
|
|
136
|
+
* declaration that names a module, and an `import()` call whose argument is literal
|
|
137
|
+
* text. A type-only form evaluates nothing. An `import()` of a computed argument names
|
|
138
|
+
* no module this pass can know.
|
|
139
|
+
*/
|
|
140
|
+
function evaluatedBy(node) {
|
|
141
|
+
if (isImportDeclaration(node)) {
|
|
142
|
+
return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
|
|
143
|
+
? undefined
|
|
144
|
+
: node.moduleSpecifier;
|
|
145
|
+
}
|
|
146
|
+
if (isExportDeclaration(node)) {
|
|
147
|
+
return node.isTypeOnly ? undefined : node.moduleSpecifier;
|
|
148
|
+
}
|
|
149
|
+
if (isImportEqualsDeclaration(node)) {
|
|
150
|
+
const reference = node.moduleReference;
|
|
151
|
+
return !node.isTypeOnly && isExternalModuleReference(reference)
|
|
152
|
+
? reference.expression
|
|
153
|
+
: undefined;
|
|
154
|
+
}
|
|
155
|
+
if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
|
|
156
|
+
const [argument] = node.arguments;
|
|
157
|
+
return isLiteralText(argument) ? argument : undefined;
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
163
|
+
* namespace export are declarations whose names are where they are written, so no use is
|
|
164
|
+
* read from them; each references what it carries forward instead. A bare star declares
|
|
165
|
+
* nothing, so the declaration enclosing it, at the top level the file, references the
|
|
166
|
+
* module it names.
|
|
167
|
+
*/
|
|
168
|
+
function linksOf(file, node, declarations, enclosing) {
|
|
169
|
+
if (!isExportDeclaration(node)) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const clause = node.exportClause;
|
|
173
|
+
const specifier = node.moduleSpecifier;
|
|
174
|
+
if (clause === undefined) {
|
|
175
|
+
return specifier === undefined
|
|
176
|
+
? []
|
|
177
|
+
: [{ node: specifier, at: specifier, from: enclosing, alias: false }];
|
|
178
|
+
}
|
|
179
|
+
if (isNamespaceExport(clause)) {
|
|
180
|
+
const from = declarations.get(nodeKey(file, clause));
|
|
181
|
+
return from === undefined
|
|
182
|
+
? []
|
|
183
|
+
: [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
|
|
184
|
+
}
|
|
185
|
+
const found = [];
|
|
186
|
+
for (const element of clause.elements) {
|
|
187
|
+
const from = declarations.get(nodeKey(file, element));
|
|
188
|
+
if (from !== undefined) {
|
|
189
|
+
found.push({
|
|
190
|
+
node: element.name,
|
|
191
|
+
at: element.propertyName ?? element.name,
|
|
192
|
+
from,
|
|
193
|
+
alias: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return found;
|
|
198
|
+
}
|
|
129
199
|
/**
|
|
130
200
|
* Every name node one file holds that is a use rather than a declaration, in source
|
|
131
|
-
* order, each with
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* declared name of the declaration it belongs to is left out: it is where that
|
|
136
|
-
* declaration is written rather than a use of it, which is what makes a declaration
|
|
137
|
-
* referenced only from its own site an unreferenced declaration.
|
|
201
|
+
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
202
|
+
* writes. One walk answers all of it, because a second would have to agree with this one
|
|
203
|
+
* about which nodes are names. A declaration's own declared name is left out, which is
|
|
204
|
+
* what makes a declaration referenced only from its own site an unreferenced one.
|
|
138
205
|
*/
|
|
139
206
|
function sitesOf(file, declarations, fileId) {
|
|
140
207
|
const found = [];
|
|
208
|
+
const links = [];
|
|
209
|
+
const evaluations = [];
|
|
141
210
|
const writes = new Set();
|
|
142
211
|
const declared = new Set();
|
|
143
212
|
const shorthands = new Map();
|
|
@@ -173,6 +242,11 @@ function sitesOf(file, declarations, fileId) {
|
|
|
173
242
|
}
|
|
174
243
|
markNames(node);
|
|
175
244
|
markStores(node, writes);
|
|
245
|
+
links.push(...linksOf(file, node, declarations, enclosing));
|
|
246
|
+
const evaluated = evaluatedBy(node);
|
|
247
|
+
if (evaluated !== undefined) {
|
|
248
|
+
evaluations.push({ node: evaluated, from: enclosing });
|
|
249
|
+
}
|
|
176
250
|
const held = declarations.get(nodeKey(file, node));
|
|
177
251
|
const outer = enclosing;
|
|
178
252
|
if (held !== undefined) {
|
|
@@ -182,7 +256,7 @@ function sitesOf(file, declarations, fileId) {
|
|
|
182
256
|
enclosing = outer;
|
|
183
257
|
};
|
|
184
258
|
file.forEachChild(visit);
|
|
185
|
-
return found;
|
|
259
|
+
return { uses: found, links, evaluations };
|
|
186
260
|
}
|
|
187
261
|
/**
|
|
188
262
|
* The name one test-file rule takes. A rule's name is a word list the report's own
|
|
@@ -210,7 +284,8 @@ function compare(a, b) {
|
|
|
210
284
|
* The order is by the referencing position, then by the declaration named, the
|
|
211
285
|
* declaration referencing it and the use. That is a total order because one name
|
|
212
286
|
* references one declaration once from one declaration, while a chain of aliases reaches
|
|
213
|
-
* several declarations at one position
|
|
287
|
+
* several declarations at one position and a bare star re-export's specifier both reads
|
|
288
|
+
* and evaluates the module it names.
|
|
214
289
|
*/
|
|
215
290
|
export function references(project, held, targetRoot, options) {
|
|
216
291
|
const cap = options.batchCap ?? DEFAULT_BATCH_CAP;
|
|
@@ -218,73 +293,31 @@ export function references(project, held, targetRoot, options) {
|
|
|
218
293
|
const patterns = options.testFiles.map(globExpression);
|
|
219
294
|
const matched = options.testFiles.map(() => 0);
|
|
220
295
|
const found = [];
|
|
221
|
-
const
|
|
296
|
+
const tests = [];
|
|
297
|
+
const chains = aliasChains(project, held);
|
|
222
298
|
let batched = 0;
|
|
223
299
|
let fileBatches = 0;
|
|
224
300
|
let residueFallbacks = 0;
|
|
225
301
|
let shorthandLookups = 0;
|
|
226
|
-
let aliasSteps = 0;
|
|
227
302
|
const files = project.ownSourceFiles();
|
|
228
|
-
const
|
|
303
|
+
const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
|
|
229
304
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
230
305
|
const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
|
|
231
306
|
/**
|
|
232
|
-
* The declarations one
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* A symbol is asked to step only where the binder flagged it an alias, which is the
|
|
238
|
-
* same condition the step itself asserts on. The declaration's syntax is not that
|
|
239
|
-
* condition and does not stand in for it: a default export of an expression is
|
|
240
|
-
* written as one of the alias forms and names nothing declared elsewhere, so it is
|
|
241
|
-
* recorded as the declaration it is and the chain ends there.
|
|
242
|
-
*
|
|
243
|
-
* A declaration handle carries the file it is in, so the question the walk asks of a
|
|
244
|
-
* declaration is answered before it is resolved: a declaration outside the project's
|
|
245
|
-
* own files belongs to another program and is never read, which is what keeps the
|
|
246
|
-
* pass from fetching a library file to discover that the inventory does not hold what
|
|
247
|
-
* is in it.
|
|
307
|
+
* The declarations one re-export carries forward: for an alias, the first link along
|
|
308
|
+
* its chain that the inventory declares, stepping over the links it does not, such as
|
|
309
|
+
* an import the re-export is written against; for a bare star, the module it names.
|
|
310
|
+
* Each later link is a declaration of its own that references the next, so one link
|
|
311
|
+
* per re-export is what lets a chain be followed one declaration at a time.
|
|
248
312
|
*/
|
|
249
|
-
const
|
|
250
|
-
const cached = reached.get(symbol.id);
|
|
251
|
-
if (cached !== undefined) {
|
|
252
|
-
return cached;
|
|
253
|
-
}
|
|
254
|
-
const targets = [];
|
|
255
|
-
const walked = new Set();
|
|
256
|
-
let at = symbol;
|
|
257
|
-
let stepped = false;
|
|
258
|
-
while (at !== undefined && !walked.has(at.id)) {
|
|
259
|
-
walked.add(at.id);
|
|
260
|
-
const alias = (at.flags & SymbolFlags.Alias) !== 0;
|
|
261
|
-
for (const handle of at.declarations) {
|
|
262
|
-
if (!ownFiles.has(handle.path)) {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
const node = project.declarationAt(handle)?.node;
|
|
266
|
-
const id = node === undefined ? undefined : declaredAt(node);
|
|
267
|
-
if (id !== undefined && !targets.some((target) => target.id === id)) {
|
|
268
|
-
targets.push({ id, stepped });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (!alias) {
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
at = project.aliasStepOf(at);
|
|
275
|
-
aliasSteps += 1;
|
|
276
|
-
stepped = true;
|
|
277
|
-
}
|
|
278
|
-
reached.set(symbol.id, targets);
|
|
279
|
-
return targets;
|
|
280
|
-
};
|
|
313
|
+
const carriedBy = (link, symbol) => link.alias ? chains.nextDeclared(symbol) : chains.declarationsOf(symbol);
|
|
281
314
|
/** The symbol one site resolves to, asking only the accessor the site's form names. */
|
|
282
315
|
const symbolFor = (site, answered) => {
|
|
283
316
|
if (site.shorthand !== undefined) {
|
|
284
317
|
shorthandLookups += 1;
|
|
285
318
|
return project.shorthandValueAt(project.handle(site.shorthand));
|
|
286
319
|
}
|
|
287
|
-
const batched = answered.get(site.node
|
|
320
|
+
const batched = answered.get(site.node);
|
|
288
321
|
if (batched !== undefined) {
|
|
289
322
|
return batched;
|
|
290
323
|
}
|
|
@@ -293,35 +326,82 @@ export function references(project, held, targetRoot, options) {
|
|
|
293
326
|
};
|
|
294
327
|
for (const file of files) {
|
|
295
328
|
const path = renderPosition(file, targetRoot, 0).path;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
329
|
+
const hits = patterns.map((pattern) => pattern.test(path));
|
|
330
|
+
hits.forEach((hit, index) => {
|
|
331
|
+
if (hit) {
|
|
299
332
|
matched[index] = (matched[index] ?? 0) + 1;
|
|
300
|
-
test = true;
|
|
301
333
|
}
|
|
302
334
|
});
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
335
|
+
const test = hits.includes(true);
|
|
336
|
+
if (test) {
|
|
337
|
+
tests.push(path);
|
|
338
|
+
}
|
|
339
|
+
const { uses, links, evaluations } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
340
|
+
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
341
|
+
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
342
|
+
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
343
|
+
// of the batch: its name resolves to the property the literal declares, so the
|
|
344
|
+
// batch's answer for it would name something this project's inventory never holds.
|
|
345
|
+
const batching = [
|
|
346
|
+
...new Set([
|
|
347
|
+
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
348
|
+
...links.map((link) => link.node),
|
|
349
|
+
...evaluations.map((evaluation) => evaluation.node),
|
|
350
|
+
]),
|
|
351
|
+
];
|
|
308
352
|
const answered = new Map();
|
|
309
353
|
batched += batching.length;
|
|
310
354
|
for (let from = 0; from < batching.length; from += cap) {
|
|
311
355
|
const run = batching.slice(from, from + cap);
|
|
312
|
-
const answers = project.symbolsAt(run.map((
|
|
356
|
+
const answers = project.symbolsAt(run.map((node) => project.handle(node)));
|
|
313
357
|
fileBatches += 1;
|
|
314
|
-
run.forEach((
|
|
358
|
+
run.forEach((node, index) => {
|
|
315
359
|
const symbol = answers[index];
|
|
316
360
|
if (symbol !== undefined) {
|
|
317
|
-
answered.set(
|
|
361
|
+
answered.set(node, symbol);
|
|
318
362
|
}
|
|
319
363
|
});
|
|
320
364
|
}
|
|
321
|
-
for (const
|
|
365
|
+
for (const link of links) {
|
|
366
|
+
const symbol = answered.get(link.node);
|
|
367
|
+
if (symbol === undefined) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const position = renderPosition(file, targetRoot, link.at.getStart());
|
|
371
|
+
for (const id of carriedBy(link, symbol)) {
|
|
372
|
+
found.push({
|
|
373
|
+
from: link.from,
|
|
374
|
+
to: id,
|
|
375
|
+
position,
|
|
376
|
+
use: "read",
|
|
377
|
+
resolution: link.alias ? "alias" : "batch",
|
|
378
|
+
test,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const evaluation of evaluations) {
|
|
383
|
+
const module = answered.get(evaluation.node);
|
|
384
|
+
if (module === undefined) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const position = renderPosition(file, targetRoot, evaluation.node.getStart());
|
|
388
|
+
for (const id of chains.declarationsOf(module)) {
|
|
389
|
+
if (fileIds.has(id)) {
|
|
390
|
+
found.push({
|
|
391
|
+
from: evaluation.from,
|
|
392
|
+
to: id,
|
|
393
|
+
position,
|
|
394
|
+
use: "evaluation",
|
|
395
|
+
resolution: "batch",
|
|
396
|
+
test,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const site of uses) {
|
|
322
402
|
const direct = site.shorthand !== undefined
|
|
323
403
|
? "shorthand"
|
|
324
|
-
: answered.has(site.node
|
|
404
|
+
: answered.has(site.node)
|
|
325
405
|
? "batch"
|
|
326
406
|
: "resolved-symbol";
|
|
327
407
|
const symbol = symbolFor(site, answered);
|
|
@@ -329,7 +409,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
329
409
|
continue;
|
|
330
410
|
}
|
|
331
411
|
const position = renderPosition(file, targetRoot, site.node.getStart());
|
|
332
|
-
|
|
412
|
+
// A re-export something imports is used and so is the declaration behind it, so
|
|
413
|
+
// the use names every link of the chain its name stands at the head of.
|
|
414
|
+
for (const target of chains.chainOf(symbol)) {
|
|
333
415
|
found.push({
|
|
334
416
|
from: site.from,
|
|
335
417
|
to: target.id,
|
|
@@ -351,6 +433,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
351
433
|
testFileRules: options.testFiles
|
|
352
434
|
.map((_pattern, index) => ({ rule: patternRule(index), matched: matched[index] ?? 0 }))
|
|
353
435
|
.sort((a, b) => compare(a.rule, b.rule)),
|
|
354
|
-
|
|
436
|
+
testFilePaths: tests.sort(compare),
|
|
437
|
+
cost: { batched, fileBatches, residueFallbacks, shorthandLookups, aliasSteps: chains.steps },
|
|
355
438
|
};
|
|
356
439
|
}
|
package/dist/src/resolve.js
CHANGED
|
@@ -8,6 +8,20 @@ const FAMILY_KEY_LENGTH = 4;
|
|
|
8
8
|
const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
|
|
9
9
|
const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
|
|
10
10
|
const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
|
|
11
|
+
/** The members only a platform build configuration carries. */
|
|
12
|
+
const PLATFORM_MEMBERS = ["os", "arch", "tags"];
|
|
13
|
+
/** The member only a project build configuration carries. */
|
|
14
|
+
const PROJECT_MEMBER = "project";
|
|
15
|
+
/**
|
|
16
|
+
* One segment of a project path: not empty, not `.` or `..`, and holding no
|
|
17
|
+
* solidus, backslash or line break.
|
|
18
|
+
*/
|
|
19
|
+
const PROJECT_SEGMENT = String.raw `(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+)`;
|
|
20
|
+
/**
|
|
21
|
+
* A project path: segments joined by a solidus, so the path is relative, below
|
|
22
|
+
* the root it is read against, and spelled one way on every machine.
|
|
23
|
+
*/
|
|
24
|
+
const PROJECT_PATH = new RegExp(`^${PROJECT_SEGMENT}(?:/${PROJECT_SEGMENT})*$`, "u");
|
|
11
25
|
const TARGET_KINDS = ["application", "library"];
|
|
12
26
|
const CONFIDENCES = ["certain", "probable", "possible"];
|
|
13
27
|
const GENERATED_FILES = ["exclude", "include"];
|
|
@@ -135,10 +149,52 @@ function requireMember(value, path, label) {
|
|
|
135
149
|
}
|
|
136
150
|
return value;
|
|
137
151
|
}
|
|
152
|
+
/** One build configuration entry as a platform: an identifier, an operating system and an architecture. */
|
|
153
|
+
function readPlatform(entry, at, label) {
|
|
154
|
+
return {
|
|
155
|
+
shape: "platform",
|
|
156
|
+
id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
|
|
157
|
+
os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
|
|
158
|
+
arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
|
|
159
|
+
tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** One build configuration entry as a project: an identifier and a configuration file below the target root. */
|
|
163
|
+
function readProject(entry, at, label) {
|
|
164
|
+
const id = requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label);
|
|
165
|
+
const path = `${at}.${PROJECT_MEMBER}`;
|
|
166
|
+
const project = requireMember(readString(entry, PROJECT_MEMBER, path, label), path, label);
|
|
167
|
+
if (!PROJECT_PATH.test(project)) {
|
|
168
|
+
throw malformed(label, path, `${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
|
|
169
|
+
"none empty, . or .., and no backslash or line break");
|
|
170
|
+
}
|
|
171
|
+
return { shape: "project", id, project };
|
|
172
|
+
}
|
|
138
173
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
174
|
+
* One build configuration entry, read as the one shape its members name: a member
|
|
175
|
+
* only a platform carries makes it a platform, the member only a project carries
|
|
176
|
+
* makes it a project, and an entry naming members of both, or of neither, is
|
|
177
|
+
* refused at the entry, because the two shapes are exclusive.
|
|
141
178
|
*/
|
|
179
|
+
function readConfiguration(entry, at, label) {
|
|
180
|
+
if (!isRecord(entry)) {
|
|
181
|
+
throw malformed(label, at, "is not an object");
|
|
182
|
+
}
|
|
183
|
+
const platform = PLATFORM_MEMBERS.filter((name) => Object.hasOwn(entry, name));
|
|
184
|
+
const project = Object.hasOwn(entry, PROJECT_MEMBER);
|
|
185
|
+
if (platform.length > 0 && project) {
|
|
186
|
+
throw malformed(label, at, `carries ${spell(platform)} of a platform and ${JSON.stringify(PROJECT_MEMBER)} of a ` +
|
|
187
|
+
"project, and an entry is one shape or the other");
|
|
188
|
+
}
|
|
189
|
+
if (project) {
|
|
190
|
+
return readProject(entry, at, label);
|
|
191
|
+
}
|
|
192
|
+
if (platform.length === 0) {
|
|
193
|
+
throw malformed(label, at, `names neither a platform's "os" and "arch" nor a project's ${JSON.stringify(PROJECT_MEMBER)}`);
|
|
194
|
+
}
|
|
195
|
+
return readPlatform(entry, at, label);
|
|
196
|
+
}
|
|
197
|
+
/** The build matrix, one entry in one shape at a time. */
|
|
142
198
|
function readConfigurations(analysis, label) {
|
|
143
199
|
const value = member(analysis, "configurations");
|
|
144
200
|
if (value === undefined) {
|
|
@@ -147,18 +203,7 @@ function readConfigurations(analysis, label) {
|
|
|
147
203
|
if (!Array.isArray(value)) {
|
|
148
204
|
throw malformed(label, "analysis.configurations", "is not an array");
|
|
149
205
|
}
|
|
150
|
-
return value.map((entry, index) => {
|
|
151
|
-
const at = `analysis.configurations[${String(index)}]`;
|
|
152
|
-
if (!isRecord(entry)) {
|
|
153
|
-
throw malformed(label, at, "is not an object");
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
|
|
157
|
-
os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
|
|
158
|
-
arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
|
|
159
|
-
tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
|
|
160
|
-
};
|
|
161
|
-
});
|
|
206
|
+
return value.map((entry, index) => readConfiguration(entry, `analysis.configurations[${String(index)}]`, label));
|
|
162
207
|
}
|
|
163
208
|
/**
|
|
164
209
|
* The action delimiter pair: once a document names the object both members are
|
|
@@ -439,6 +484,29 @@ function checkLabels(provenance) {
|
|
|
439
484
|
* the usage exit code.
|
|
440
485
|
*/
|
|
441
486
|
export function resolve(inputs) {
|
|
487
|
+
const { config, provenance } = settleAll(inputs);
|
|
488
|
+
if (config.targetKind === "") {
|
|
489
|
+
throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set, it has no default and is never inferred: searched " +
|
|
490
|
+
`${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
|
|
491
|
+
describeSource("the central configuration", inputs.central, inputs.centralLabel));
|
|
492
|
+
}
|
|
493
|
+
checkLabels(provenance);
|
|
494
|
+
return { config, provenance };
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The build matrix the configuration documents resolve to, every document read and
|
|
498
|
+
* refused as {@link resolve} refuses it, and nothing else required of them: which
|
|
499
|
+
* projects a run analyzes depends on the matrix alone, so a question about the
|
|
500
|
+
* projects asks no target kind.
|
|
501
|
+
*/
|
|
502
|
+
export function resolveMatrix(inputs) {
|
|
503
|
+
return settleAll(inputs).config.analysis.configurations;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Every setting the documents supply, each from the highest-ranked source carrying
|
|
507
|
+
* it, with the origin of each. The target kind is empty where no source names one.
|
|
508
|
+
*/
|
|
509
|
+
function settleAll(inputs) {
|
|
442
510
|
const sources = readSources(inputs);
|
|
443
511
|
const defaults = defaultConfig();
|
|
444
512
|
const provenance = new Map();
|
|
@@ -472,11 +540,5 @@ export function resolve(inputs) {
|
|
|
472
540
|
entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
|
|
473
541
|
},
|
|
474
542
|
};
|
|
475
|
-
if (config.targetKind === "") {
|
|
476
|
-
throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set, it has no default and is never inferred: searched " +
|
|
477
|
-
`${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
|
|
478
|
-
describeSource("the central configuration", inputs.central, inputs.centralLabel));
|
|
479
|
-
}
|
|
480
|
-
checkLabels(provenance);
|
|
481
543
|
return { config, provenance };
|
|
482
544
|
}
|
package/dist/src/roots.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* The root set of one project: every declaration the analysis keeps live without a
|
|
3
3
|
* reference, and why.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* something outside the program reaches is live under
|
|
9
|
-
* candidate under
|
|
5
|
+
* Every root seeds REACHABILITY, and no root counts as a reference. A root of a kind
|
|
6
|
+
* that names a caller is live under reference counting as well, which the sweep
|
|
7
|
+
* decides; a root that only supposes a consumer is not, so an exported declaration
|
|
8
|
+
* something outside the program reaches is live under reachability and still a
|
|
9
|
+
* candidate under reference counting.
|
|
10
10
|
*/
|
|
11
|
+
import { aliasChains } from "./alias-chain.js";
|
|
11
12
|
import { REPOSITORY_DOCUMENT } from "./config.js";
|
|
12
13
|
import { entryPoints } from "./entry-points.js";
|
|
13
14
|
import { globExpression } from "./glob.js";
|
|
@@ -143,7 +144,10 @@ export function roots(project, held, targetRoot, options) {
|
|
|
143
144
|
for (const point of entryPoints(project, files, options.testFiles)) {
|
|
144
145
|
enter(point.file, point.rule, point.source, point.exports);
|
|
145
146
|
}
|
|
146
|
-
const exportsOf = exportTables(project, held, [
|
|
147
|
+
const exportsOf = exportTables(project, held, [
|
|
148
|
+
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
149
|
+
...published,
|
|
150
|
+
]);
|
|
147
151
|
for (const entry of entered) {
|
|
148
152
|
const id = fileIds.get(entry.file.fileName);
|
|
149
153
|
if (id === undefined) {
|
|
@@ -181,41 +185,14 @@ export function roots(project, held, targetRoot, options) {
|
|
|
181
185
|
* its export table, star re-exports resolved into it. A file that is no module
|
|
182
186
|
* exports nothing.
|
|
183
187
|
*/
|
|
184
|
-
function exportTables(project, held, wanted
|
|
188
|
+
function exportTables(project, held, wanted) {
|
|
185
189
|
const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
|
|
186
190
|
const tables = new Map();
|
|
187
191
|
if (unique.length === 0) {
|
|
188
192
|
return tables;
|
|
189
193
|
}
|
|
190
194
|
const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
|
|
191
|
-
const chains =
|
|
192
|
-
const chainOf = (symbol) => {
|
|
193
|
-
const cached = chains.get(symbol.id);
|
|
194
|
-
if (cached !== undefined) {
|
|
195
|
-
return cached;
|
|
196
|
-
}
|
|
197
|
-
const ids = [];
|
|
198
|
-
const walked = new Set();
|
|
199
|
-
let at = symbol;
|
|
200
|
-
while (at !== undefined && !walked.has(at.id)) {
|
|
201
|
-
walked.add(at.id);
|
|
202
|
-
for (const handle of at.declarations) {
|
|
203
|
-
if (!ownFiles.has(handle.path)) {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
const node = project.declarationAt(handle)?.node;
|
|
207
|
-
const id = node === undefined
|
|
208
|
-
? undefined
|
|
209
|
-
: held.declarations.get(nodeKey(node.getSourceFile(), node));
|
|
210
|
-
if (id !== undefined && !ids.includes(id)) {
|
|
211
|
-
ids.push(id);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
at = project.aliasStepOf(at);
|
|
215
|
-
}
|
|
216
|
-
chains.set(symbol.id, ids);
|
|
217
|
-
return ids;
|
|
218
|
-
};
|
|
195
|
+
const chains = aliasChains(project, held);
|
|
219
196
|
unique.forEach((file, index) => {
|
|
220
197
|
const module = modules[index];
|
|
221
198
|
if (module === undefined) {
|
|
@@ -224,8 +201,8 @@ function exportTables(project, held, wanted, ownFiles) {
|
|
|
224
201
|
}
|
|
225
202
|
const ids = new Set();
|
|
226
203
|
for (const exported of project.checker.getExportsOfModule(module)) {
|
|
227
|
-
for (const
|
|
228
|
-
ids.add(id);
|
|
204
|
+
for (const target of chains.chainOf(exported)) {
|
|
205
|
+
ids.add(target.id);
|
|
229
206
|
}
|
|
230
207
|
}
|
|
231
208
|
tables.set(file.fileName, [...ids]);
|
package/dist/src/run.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ConfigError, REPOSITORY_DOCUMENT } from "./config.js";
|
|
|
3
3
|
import { diagnosticErrors, discoverProjects, DiscoveryError, renderDiagnostic, } from "./discover.js";
|
|
4
4
|
import { joinPath, resolvePath } from "./paths.js";
|
|
5
5
|
import { printConfig } from "./print.js";
|
|
6
|
-
import { resolve } from "./resolve.js";
|
|
6
|
+
import { resolve, resolveMatrix } from "./resolve.js";
|
|
7
7
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
8
8
|
import { diagnosticsOf, openEngine, runSession } from "./session.js";
|
|
9
9
|
import { CONTRACT_VERSION } from "./version.js";
|
|
@@ -197,21 +197,25 @@ function printConfigVerb(args, out, err, host) {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
/**
|
|
200
|
-
* Writes the
|
|
201
|
-
*
|
|
200
|
+
* Writes the name of every project one run analyzes, one per line, having read each
|
|
201
|
+
* project's diagnostics first: the identifier the build matrix declares for it, or,
|
|
202
|
+
* where discovery derived the project, its configuration file's path below the
|
|
203
|
+
* target root.
|
|
202
204
|
*/
|
|
203
205
|
function printProjectsVerb(args, out, err, host, openClient) {
|
|
204
206
|
let engine;
|
|
205
207
|
try {
|
|
206
208
|
const options = readOptions(args);
|
|
209
|
+
const matrix = resolveMatrix(inputsOf(host, options));
|
|
207
210
|
const scope = scopeOf(host, options);
|
|
208
211
|
engine = openClient(false);
|
|
209
|
-
const discovered = discoverProjects(engine, host, scope);
|
|
212
|
+
const discovered = discoverProjects(engine, host, scope, matrix);
|
|
213
|
+
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
210
214
|
const failures = [];
|
|
211
215
|
const { projects } = runSession(engine, discovered.configFiles, (project) => {
|
|
212
216
|
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
213
217
|
failures.push(...errors.map(renderDiagnostic));
|
|
214
|
-
return project.configFile;
|
|
218
|
+
return ids.get(project.configFile) ?? project.configFile;
|
|
215
219
|
});
|
|
216
220
|
if (failures.length > 0) {
|
|
217
221
|
for (const line of failures) {
|
|
@@ -220,8 +224,8 @@ function printProjectsVerb(args, out, err, host, openClient) {
|
|
|
220
224
|
err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
|
|
221
225
|
return EXIT_FAILURE;
|
|
222
226
|
}
|
|
223
|
-
for (const
|
|
224
|
-
out.write(`${
|
|
227
|
+
for (const id of projects) {
|
|
228
|
+
out.write(`${id}\n`);
|
|
225
229
|
}
|
|
226
230
|
return EXIT_CLEAN;
|
|
227
231
|
}
|
package/dist/src/schema.js
CHANGED
|
@@ -19,7 +19,7 @@ export const SCHEMA_ROOT = {
|
|
|
19
19
|
consumer_tests: leaf,
|
|
20
20
|
configurations: {
|
|
21
21
|
kind: "list",
|
|
22
|
-
members: { id: leaf, os: leaf, arch: leaf, tags: leaf },
|
|
22
|
+
members: { id: leaf, os: leaf, arch: leaf, tags: leaf, project: leaf },
|
|
23
23
|
},
|
|
24
24
|
matrix: { kind: "section", members: { complete: leaf } },
|
|
25
25
|
template_dirs: leaf,
|
package/dist/src/session.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { API, SymbolFlags } from "@typescript/native/unstable/sync";
|
|
2
2
|
import { isIdentifier } from "@typescript/native/unstable/ast";
|
|
3
|
+
import { DiscoveryError } from "./discover.js";
|
|
3
4
|
/**
|
|
4
5
|
* Opens the compiler client the analysis reads through.
|
|
5
6
|
*
|
|
@@ -81,19 +82,29 @@ export function diagnosticsOf(project) {
|
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
84
|
* Runs `visit` over every project the compiler configurations name, inside one
|
|
84
|
-
* snapshot of one client.
|
|
85
|
+
* snapshot of one client, in the order they are named, and answers in that order.
|
|
85
86
|
*
|
|
86
87
|
* The snapshot is opened once with every configuration, so each becomes a project
|
|
87
88
|
* with its own program and checker; it is never updated, because the analysis
|
|
88
|
-
* reads and the tree does not change under it.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
89
|
+
* reads and the tree does not change under it. The order the snapshot lists its
|
|
90
|
+
* projects in is the compiler's own, so each named configuration is looked up in it
|
|
91
|
+
* instead, and a configuration the snapshot opened no project for ends the run: the
|
|
92
|
+
* run would otherwise answer for fewer projects than it was asked to analyze. The
|
|
93
|
+
* snapshot is disposed once when the last project has been visited, and the client
|
|
94
|
+
* is released after it, whether the visit completed or threw.
|
|
91
95
|
*/
|
|
92
96
|
export function runSession(engine, configFiles, visit) {
|
|
93
97
|
try {
|
|
94
98
|
const snapshot = engine.updateSnapshot(configFiles);
|
|
95
99
|
try {
|
|
96
|
-
const
|
|
100
|
+
const opened = new Map(snapshot.getProjects().map((project) => [project.configFileName, project]));
|
|
101
|
+
const projects = configFiles.map((configFile) => {
|
|
102
|
+
const project = opened.get(configFile);
|
|
103
|
+
if (project === undefined) {
|
|
104
|
+
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
105
|
+
}
|
|
106
|
+
return visit(viewOf(project));
|
|
107
|
+
});
|
|
97
108
|
return { projects, timing: engine.getTimingInfo() };
|
|
98
109
|
}
|
|
99
110
|
finally {
|