@cplieger/deadset-ts 2.3.0 → 3.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 +7 -16
- package/dist/src/analysis.js +49 -16
- package/dist/src/components.js +289 -0
- package/dist/src/discover.js +98 -24
- package/dist/src/graph.js +116 -0
- package/dist/src/index.js +0 -2
- package/dist/src/print.js +3 -6
- package/dist/src/references.js +154 -49
- package/dist/src/resolve.js +82 -20
- package/dist/src/run.js +11 -7
- package/dist/src/schema.js +1 -1
- package/dist/src/sweep.js +156 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +91 -24
- package/src/components.ts +372 -0
- package/src/config.ts +23 -2
- package/src/discover.ts +144 -29
- package/src/graph.ts +178 -0
- package/src/index.ts +0 -20
- package/src/print.ts +5 -6
- package/src/references.ts +199 -52
- package/src/resolve.ts +117 -24
- package/src/run.ts +11 -7
- package/src/schema.ts +5 -2
- package/src/sweep.ts +230 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One project's declarations, the references between them and the roots that seed
|
|
3
|
+
* reachability, indexed for the sweep. The graph reads no file and asks the compiler
|
|
4
|
+
* nothing, so a sweep over it is a function of the values it was built from, and the
|
|
5
|
+
* inventory's order is the order every set a sweep answers reads in.
|
|
6
|
+
*/
|
|
7
|
+
/** The position of a declaration the inventory does not hold. */
|
|
8
|
+
export const OUTSIDE = -1;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the sweep judges a declaration of each kind. A file is not judged by either
|
|
11
|
+
* relation: whether a file is built and whether anything imports it are the subjects of
|
|
12
|
+
* the file kinds, which read the program rather than ask whether anything references the
|
|
13
|
+
* file. The Contract's finding schema names the kinds that carry no liveness relation,
|
|
14
|
+
* and a test holds this table to that list.
|
|
15
|
+
*/
|
|
16
|
+
const SWEPT = {
|
|
17
|
+
file: false,
|
|
18
|
+
function: true,
|
|
19
|
+
class: true,
|
|
20
|
+
interface: true,
|
|
21
|
+
type: true,
|
|
22
|
+
enum: true,
|
|
23
|
+
namespace: true,
|
|
24
|
+
variable: true,
|
|
25
|
+
"export-alias": true,
|
|
26
|
+
method: true,
|
|
27
|
+
"class-member": true,
|
|
28
|
+
"interface-method": true,
|
|
29
|
+
"type-member": true,
|
|
30
|
+
"enum-member": true,
|
|
31
|
+
"type-parameter": true,
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Whether a root of each kind names a caller, which holds it live under both relations: a
|
|
35
|
+
* tool reads the configuration it loads, a runtime runs a test file, a setup file and a
|
|
36
|
+
* worker, and a configured root asserts a caller. The other kinds only suppose a consumer
|
|
37
|
+
* that imports what a manifest entry, a binary or an entry file exports, or names a
|
|
38
|
+
* library's published API, so such a root is live under reachability alone.
|
|
39
|
+
*/
|
|
40
|
+
const NAMES_A_CALLER = {
|
|
41
|
+
"entry-file": false,
|
|
42
|
+
"manifest-entry": false,
|
|
43
|
+
"manifest-binary": false,
|
|
44
|
+
"published-api": false,
|
|
45
|
+
configured: true,
|
|
46
|
+
pattern: true,
|
|
47
|
+
"test-runner": true,
|
|
48
|
+
"lint-configuration": true,
|
|
49
|
+
"mutation-testing": true,
|
|
50
|
+
"browser-tests": true,
|
|
51
|
+
worker: true,
|
|
52
|
+
};
|
|
53
|
+
/** Whether the sweep judges a declaration of one kind. */
|
|
54
|
+
export function swept(kind) {
|
|
55
|
+
return SWEPT[kind];
|
|
56
|
+
}
|
|
57
|
+
/** Whether a root of one kind names a caller rather than supposing one. */
|
|
58
|
+
export function namesACaller(kind) {
|
|
59
|
+
return NAMES_A_CALLER[kind];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Indexes one project's inventory, references and roots. `testFiles` are the paths the
|
|
63
|
+
* reference pass classified as test files, so a declaration one holds is a test
|
|
64
|
+
* declaration. A reference to a declaration the inventory does not hold is dropped; one
|
|
65
|
+
* from such a declaration still counts toward its target, because reference counting
|
|
66
|
+
* asks whether any declaration names the target. A root naming nothing is dropped.
|
|
67
|
+
*/
|
|
68
|
+
export function graphOf(symbols, references, roots, testFiles) {
|
|
69
|
+
const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
|
|
70
|
+
const at = (id) => index.get(id) ?? OUTSIDE;
|
|
71
|
+
const tests = new Set(testFiles);
|
|
72
|
+
const out = symbols.map(() => []);
|
|
73
|
+
const made = symbols.map(() => ({ production: 0, test: 0 }));
|
|
74
|
+
for (const reference of references) {
|
|
75
|
+
const to = at(reference.to);
|
|
76
|
+
if (to === OUTSIDE) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const counts = made[to];
|
|
80
|
+
if (counts !== undefined) {
|
|
81
|
+
if (reference.test) {
|
|
82
|
+
counts.test += 1;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
counts.production += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
out[at(reference.from)]?.push({ to, test: reference.test });
|
|
89
|
+
}
|
|
90
|
+
const parent = symbols.map((symbol) => at(symbol.parent));
|
|
91
|
+
const exportsOf = symbols.map(() => []);
|
|
92
|
+
symbols.forEach((symbol, position) => {
|
|
93
|
+
const container = parent[position] ?? OUTSIDE;
|
|
94
|
+
if (symbol.exported && symbols[container]?.kind === "file") {
|
|
95
|
+
exportsOf[container]?.push(position);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
const rooted = [];
|
|
99
|
+
for (const root of roots) {
|
|
100
|
+
const position = at(root.id);
|
|
101
|
+
if (position !== OUTSIDE) {
|
|
102
|
+
rooted.push({ at: position, kind: root.kind });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
symbols,
|
|
107
|
+
out,
|
|
108
|
+
made,
|
|
109
|
+
parent,
|
|
110
|
+
test: symbols.map((symbol) => tests.has(symbol.position.path)),
|
|
111
|
+
subject: symbols.map((symbol) => swept(symbol.kind)),
|
|
112
|
+
exportsOf,
|
|
113
|
+
rooted,
|
|
114
|
+
at,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { run, SETTING_OPTIONS } from "./run.js";
|
|
2
2
|
export { DECLINED_CONVENTIONS } from "./entry-point-gaps.js";
|
|
3
|
-
export { inventory, nodeKey, } from "./inventory.js";
|
|
4
|
-
export { DEFAULT_BATCH_CAP, references, } from "./references.js";
|
|
5
3
|
export { byPosition, positionKey, PositionError, renderPosition, } from "./position.js";
|
|
6
4
|
export { computedComponent, isRef, nameComponent, REF_EXPRESSIONS, renderRef, } from "./ref.js";
|
|
7
5
|
export { CONTRACT_VERSION } from "./version.js";
|
package/dist/src/print.js
CHANGED
|
@@ -19,12 +19,9 @@ export function printConfig(config, provenance) {
|
|
|
19
19
|
min_confidence: config.analysis.minConfidence,
|
|
20
20
|
generated_files: config.analysis.generatedFiles,
|
|
21
21
|
consumer_tests: config.analysis.consumerTests,
|
|
22
|
-
configurations: config.analysis.configurations.map((entry) =>
|
|
23
|
-
id: entry.id,
|
|
24
|
-
os: entry.os,
|
|
25
|
-
arch: entry.arch,
|
|
26
|
-
tags: entry.tags,
|
|
27
|
-
})),
|
|
22
|
+
configurations: config.analysis.configurations.map((entry) => entry.shape === "project"
|
|
23
|
+
? { id: entry.id, project: entry.project }
|
|
24
|
+
: { id: entry.id, os: entry.os, arch: entry.arch, tags: entry.tags }),
|
|
28
25
|
matrix: { complete: config.analysis.matrixComplete },
|
|
29
26
|
template_dirs: config.analysis.templateDirs,
|
|
30
27
|
template_delimiters: {
|
package/dist/src/references.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isBinaryExpression, isDeleteExpression, isElementAccessExpression, isForInStatement, isForOfStatement, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
1
|
+
import { isArrayLiteralExpression, isBinaryExpression, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isForInStatement, isForOfStatement, isNamespaceExport, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
2
|
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
3
3
|
import { globExpression } from "./glob.js";
|
|
4
4
|
import { nodeKey } from "./inventory.js";
|
|
@@ -126,18 +126,54 @@ function markStores(node, writes) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
131
|
+
* namespace export are declarations whose names are where they are written, so no use is
|
|
132
|
+
* read from them; each references what it carries forward instead. A bare star declares
|
|
133
|
+
* nothing, so the declaration enclosing it, at the top level the file, references the
|
|
134
|
+
* module it names.
|
|
135
|
+
*/
|
|
136
|
+
function linksOf(file, node, declarations, enclosing) {
|
|
137
|
+
if (!isExportDeclaration(node)) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const clause = node.exportClause;
|
|
141
|
+
const specifier = node.moduleSpecifier;
|
|
142
|
+
if (clause === undefined) {
|
|
143
|
+
return specifier === undefined
|
|
144
|
+
? []
|
|
145
|
+
: [{ node: specifier, at: specifier, from: enclosing, alias: false }];
|
|
146
|
+
}
|
|
147
|
+
if (isNamespaceExport(clause)) {
|
|
148
|
+
const from = declarations.get(nodeKey(file, clause));
|
|
149
|
+
return from === undefined
|
|
150
|
+
? []
|
|
151
|
+
: [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
|
|
152
|
+
}
|
|
153
|
+
const found = [];
|
|
154
|
+
for (const element of clause.elements) {
|
|
155
|
+
const from = declarations.get(nodeKey(file, element));
|
|
156
|
+
if (from !== undefined) {
|
|
157
|
+
found.push({
|
|
158
|
+
node: element.name,
|
|
159
|
+
at: element.propertyName ?? element.name,
|
|
160
|
+
from,
|
|
161
|
+
alias: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return found;
|
|
166
|
+
}
|
|
129
167
|
/**
|
|
130
168
|
* 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.
|
|
169
|
+
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
170
|
+
* writes. One walk answers all of it, because a second would have to agree with this one
|
|
171
|
+
* about which nodes are names. A declaration's own declared name is left out, which is
|
|
172
|
+
* what makes a declaration referenced only from its own site an unreferenced one.
|
|
138
173
|
*/
|
|
139
174
|
function sitesOf(file, declarations, fileId) {
|
|
140
175
|
const found = [];
|
|
176
|
+
const links = [];
|
|
141
177
|
const writes = new Set();
|
|
142
178
|
const declared = new Set();
|
|
143
179
|
const shorthands = new Map();
|
|
@@ -173,6 +209,7 @@ function sitesOf(file, declarations, fileId) {
|
|
|
173
209
|
}
|
|
174
210
|
markNames(node);
|
|
175
211
|
markStores(node, writes);
|
|
212
|
+
links.push(...linksOf(file, node, declarations, enclosing));
|
|
176
213
|
const held = declarations.get(nodeKey(file, node));
|
|
177
214
|
const outer = enclosing;
|
|
178
215
|
if (held !== undefined) {
|
|
@@ -182,7 +219,7 @@ function sitesOf(file, declarations, fileId) {
|
|
|
182
219
|
enclosing = outer;
|
|
183
220
|
};
|
|
184
221
|
file.forEachChild(visit);
|
|
185
|
-
return found;
|
|
222
|
+
return { uses: found, links };
|
|
186
223
|
}
|
|
187
224
|
/**
|
|
188
225
|
* The name one test-file rule takes. A rule's name is a word list the report's own
|
|
@@ -218,7 +255,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
218
255
|
const patterns = options.testFiles.map(globExpression);
|
|
219
256
|
const matched = options.testFiles.map(() => 0);
|
|
220
257
|
const found = [];
|
|
258
|
+
const tests = [];
|
|
221
259
|
const reached = new Map();
|
|
260
|
+
const steps = new Map();
|
|
222
261
|
let batched = 0;
|
|
223
262
|
let fileBatches = 0;
|
|
224
263
|
let residueFallbacks = 0;
|
|
@@ -228,23 +267,51 @@ export function references(project, held, targetRoot, options) {
|
|
|
228
267
|
const ownFiles = new Set(files.map((file) => file.fileName));
|
|
229
268
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
230
269
|
const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
|
|
270
|
+
/**
|
|
271
|
+
* The declarations of the inventory one symbol is.
|
|
272
|
+
*
|
|
273
|
+
* A declaration handle carries the file it is in, so the question is answered before
|
|
274
|
+
* the handle is resolved: a declaration outside the project's own files belongs to
|
|
275
|
+
* another program and is never read, which is what keeps the pass from fetching a
|
|
276
|
+
* library file to discover that the inventory does not hold what is in it.
|
|
277
|
+
*/
|
|
278
|
+
const declarationsOf = (symbol) => {
|
|
279
|
+
const ids = [];
|
|
280
|
+
for (const handle of symbol.declarations) {
|
|
281
|
+
if (!ownFiles.has(handle.path)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const node = project.declarationAt(handle)?.node;
|
|
285
|
+
const id = node === undefined ? undefined : declaredAt(node);
|
|
286
|
+
if (id !== undefined && !ids.includes(id)) {
|
|
287
|
+
ids.push(id);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return ids;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* What one alias names, one link along, asked once per alias whatever the number of
|
|
294
|
+
* chains it is a link of. Only a symbol the binder flagged an alias is asked, which is
|
|
295
|
+
* the condition the step asserts on: a default export of an expression is written in
|
|
296
|
+
* an alias form and names nothing declared elsewhere, so its chain ends at itself.
|
|
297
|
+
*/
|
|
298
|
+
const stepOf = (symbol) => {
|
|
299
|
+
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
if (steps.has(symbol.id)) {
|
|
303
|
+
return steps.get(symbol.id);
|
|
304
|
+
}
|
|
305
|
+
const next = project.aliasStepOf(symbol);
|
|
306
|
+
aliasSteps += 1;
|
|
307
|
+
steps.set(symbol.id, next);
|
|
308
|
+
return next;
|
|
309
|
+
};
|
|
231
310
|
/**
|
|
232
311
|
* The declarations one symbol names: the declaration it is, and every link of the
|
|
233
312
|
* alias chain it stands at the head of. A re-export something imports is used and so
|
|
234
313
|
* is the declaration behind it, so both are named; a link this project does not
|
|
235
314
|
* declare is stepped over rather than recorded.
|
|
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.
|
|
248
315
|
*/
|
|
249
316
|
const targetsOf = (symbol) => {
|
|
250
317
|
const cached = reached.get(symbol.id);
|
|
@@ -257,34 +324,47 @@ export function references(project, held, targetRoot, options) {
|
|
|
257
324
|
let stepped = false;
|
|
258
325
|
while (at !== undefined && !walked.has(at.id)) {
|
|
259
326
|
walked.add(at.id);
|
|
260
|
-
const
|
|
261
|
-
|
|
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)) {
|
|
327
|
+
for (const id of declarationsOf(at)) {
|
|
328
|
+
if (!targets.some((target) => target.id === id)) {
|
|
268
329
|
targets.push({ id, stepped });
|
|
269
330
|
}
|
|
270
331
|
}
|
|
271
|
-
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
at = project.aliasStepOf(at);
|
|
275
|
-
aliasSteps += 1;
|
|
332
|
+
at = stepOf(at);
|
|
276
333
|
stepped = true;
|
|
277
334
|
}
|
|
278
335
|
reached.set(symbol.id, targets);
|
|
279
336
|
return targets;
|
|
280
337
|
};
|
|
338
|
+
/**
|
|
339
|
+
* The declarations one re-export carries forward: for an alias, the first link along
|
|
340
|
+
* its chain that the inventory declares, stepping over the links it does not, such as
|
|
341
|
+
* an import the re-export is written against; for a bare star, the module it names.
|
|
342
|
+
* Each later link is a declaration of its own that references the next, so one link
|
|
343
|
+
* per re-export is what lets a chain be followed one declaration at a time.
|
|
344
|
+
*/
|
|
345
|
+
const carriedBy = (link, symbol) => {
|
|
346
|
+
if (!link.alias) {
|
|
347
|
+
return declarationsOf(symbol);
|
|
348
|
+
}
|
|
349
|
+
const walked = new Set([symbol.id]);
|
|
350
|
+
let at = stepOf(symbol);
|
|
351
|
+
while (at !== undefined && !walked.has(at.id)) {
|
|
352
|
+
walked.add(at.id);
|
|
353
|
+
const ids = declarationsOf(at);
|
|
354
|
+
if (ids.length > 0) {
|
|
355
|
+
return ids;
|
|
356
|
+
}
|
|
357
|
+
at = stepOf(at);
|
|
358
|
+
}
|
|
359
|
+
return [];
|
|
360
|
+
};
|
|
281
361
|
/** The symbol one site resolves to, asking only the accessor the site's form names. */
|
|
282
362
|
const symbolFor = (site, answered) => {
|
|
283
363
|
if (site.shorthand !== undefined) {
|
|
284
364
|
shorthandLookups += 1;
|
|
285
365
|
return project.shorthandValueAt(project.handle(site.shorthand));
|
|
286
366
|
}
|
|
287
|
-
const batched = answered.get(site.node
|
|
367
|
+
const batched = answered.get(site.node);
|
|
288
368
|
if (batched !== undefined) {
|
|
289
369
|
return batched;
|
|
290
370
|
}
|
|
@@ -293,35 +373,59 @@ export function references(project, held, targetRoot, options) {
|
|
|
293
373
|
};
|
|
294
374
|
for (const file of files) {
|
|
295
375
|
const path = renderPosition(file, targetRoot, 0).path;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
376
|
+
const hits = patterns.map((pattern) => pattern.test(path));
|
|
377
|
+
hits.forEach((hit, index) => {
|
|
378
|
+
if (hit) {
|
|
299
379
|
matched[index] = (matched[index] ?? 0) + 1;
|
|
300
|
-
test = true;
|
|
301
380
|
}
|
|
302
381
|
});
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
382
|
+
const test = hits.includes(true);
|
|
383
|
+
if (test) {
|
|
384
|
+
tests.push(path);
|
|
385
|
+
}
|
|
386
|
+
const { uses, links } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
387
|
+
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
388
|
+
// the uses. A shorthand is left out of the batch: its name resolves to the property
|
|
389
|
+
// the literal declares, so the batch's answer for it would name something this
|
|
390
|
+
// project's inventory never holds.
|
|
391
|
+
const batching = [
|
|
392
|
+
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
393
|
+
...links.map((link) => link.node),
|
|
394
|
+
];
|
|
308
395
|
const answered = new Map();
|
|
309
396
|
batched += batching.length;
|
|
310
397
|
for (let from = 0; from < batching.length; from += cap) {
|
|
311
398
|
const run = batching.slice(from, from + cap);
|
|
312
|
-
const answers = project.symbolsAt(run.map((
|
|
399
|
+
const answers = project.symbolsAt(run.map((node) => project.handle(node)));
|
|
313
400
|
fileBatches += 1;
|
|
314
|
-
run.forEach((
|
|
401
|
+
run.forEach((node, index) => {
|
|
315
402
|
const symbol = answers[index];
|
|
316
403
|
if (symbol !== undefined) {
|
|
317
|
-
answered.set(
|
|
404
|
+
answered.set(node, symbol);
|
|
318
405
|
}
|
|
319
406
|
});
|
|
320
407
|
}
|
|
321
|
-
for (const
|
|
408
|
+
for (const link of links) {
|
|
409
|
+
const symbol = answered.get(link.node);
|
|
410
|
+
if (symbol === undefined) {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const position = renderPosition(file, targetRoot, link.at.getStart());
|
|
414
|
+
for (const id of carriedBy(link, symbol)) {
|
|
415
|
+
found.push({
|
|
416
|
+
from: link.from,
|
|
417
|
+
to: id,
|
|
418
|
+
position,
|
|
419
|
+
use: "read",
|
|
420
|
+
resolution: link.alias ? "alias" : "batch",
|
|
421
|
+
test,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (const site of uses) {
|
|
322
426
|
const direct = site.shorthand !== undefined
|
|
323
427
|
? "shorthand"
|
|
324
|
-
: answered.has(site.node
|
|
428
|
+
: answered.has(site.node)
|
|
325
429
|
? "batch"
|
|
326
430
|
: "resolved-symbol";
|
|
327
431
|
const symbol = symbolFor(site, answered);
|
|
@@ -351,6 +455,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
351
455
|
testFileRules: options.testFiles
|
|
352
456
|
.map((_pattern, index) => ({ rule: patternRule(index), matched: matched[index] ?? 0 }))
|
|
353
457
|
.sort((a, b) => compare(a.rule, b.rule)),
|
|
458
|
+
testFilePaths: tests.sort(compare),
|
|
354
459
|
cost: { batched, fileBatches, residueFallbacks, shorthandLookups, aliasSteps },
|
|
355
460
|
};
|
|
356
461
|
}
|
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/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,
|