@cplieger/deadset-ts 5.0.0 → 5.1.1
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 +1 -1
- package/dist/src/analysis.js +145 -39
- package/dist/src/configuration-files.js +108 -4
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +247 -0
- package/dist/src/convention-rows.js +6 -0
- package/dist/src/conventions.js +123 -14
- package/dist/src/dependencies.js +8 -2
- package/dist/src/destructuring.js +6 -6
- package/dist/src/entry-points.js +8 -5
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +35 -16
- package/dist/src/file-facts.js +23 -20
- package/dist/src/findings/completion.js +27 -3
- 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 +40 -1
- package/dist/src/generated-file.js +24 -0
- package/dist/src/graph.js +1 -1
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +17 -4
- package/dist/src/kinds.js +4 -3
- package/dist/src/manifest.js +29 -4
- package/dist/src/outside-tests.js +139 -0
- package/dist/src/overrides.js +104 -0
- package/dist/src/paths.js +17 -0
- package/dist/src/query.js +15 -0
- package/dist/src/references.js +229 -11
- package/dist/src/resolve.js +12 -11
- package/dist/src/roots.js +121 -37
- package/dist/src/session.js +4 -3
- package/dist/src/stores.js +3 -0
- package/dist/src/template-field.js +60 -3
- package/dist/src/value-flow.js +74 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +194 -49
- package/src/configuration-files.ts +137 -2
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +358 -0
- package/src/convention-rows.ts +8 -0
- package/src/conventions.ts +161 -13
- package/src/dependencies.ts +14 -1
- package/src/destructuring.ts +16 -6
- package/src/entry-points.ts +16 -3
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +67 -16
- package/src/file-facts.ts +26 -20
- package/src/findings/completion.ts +31 -3
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +51 -1
- package/src/generated-file.ts +27 -0
- package/src/graph.ts +4 -4
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +19 -4
- package/src/kinds.ts +5 -3
- package/src/manifest.ts +40 -4
- package/src/outside-tests.ts +191 -0
- package/src/overrides.ts +136 -0
- package/src/paths.ts +18 -0
- package/src/query.ts +26 -0
- package/src/references.ts +294 -22
- package/src/resolve.ts +14 -13
- package/src/roots.ts +152 -44
- package/src/session.ts +4 -2
- package/src/stores.ts +9 -0
- package/src/template-field.ts +65 -3
- package/src/value-flow.ts +104 -0
- package/src/version.ts +1 -1
package/dist/src/references.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
1
|
+
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
2
2
|
import { aliasChains } from "./alias-chain.js";
|
|
3
|
+
import { contextualUses } from "./contextual-uses.js";
|
|
3
4
|
import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
4
5
|
import { globExpression } from "./glob.js";
|
|
5
6
|
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
6
|
-
import {
|
|
7
|
+
import { overrideReferences } from "./overrides.js";
|
|
8
|
+
import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.js";
|
|
7
9
|
import { byPosition, isComponentFile, renderPosition } from "./position.js";
|
|
8
|
-
import { DEFAULT_BATCH_CAP, UNANSWERED } from "./query.js";
|
|
10
|
+
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
|
|
9
11
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
12
|
+
import { handedOn, valuesOf } from "./value-flow.js";
|
|
10
13
|
/**
|
|
11
14
|
* The operators whose left side one assignment stores into. A compound assignment is
|
|
12
15
|
* among them: where its value is discarded it reads its target only to write the result
|
|
@@ -94,7 +97,11 @@ function markWrite(target, writes) {
|
|
|
94
97
|
return;
|
|
95
98
|
}
|
|
96
99
|
if (isElementAccessExpression(held)) {
|
|
97
|
-
|
|
100
|
+
// A store through a member's value lands in storage every holder of the value
|
|
101
|
+
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
102
|
+
if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
|
|
103
|
+
markWrite(held.expression, writes);
|
|
104
|
+
}
|
|
98
105
|
return;
|
|
99
106
|
}
|
|
100
107
|
if (isArrayLiteralExpression(held)) {
|
|
@@ -194,6 +201,19 @@ function evaluatedBy(node) {
|
|
|
194
201
|
}
|
|
195
202
|
return undefined;
|
|
196
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
|
|
206
|
+
* checker resolves the whole specifier to whatever module declaration matches it.
|
|
207
|
+
*/
|
|
208
|
+
function queriedFile(file, specifier, fileIdByName) {
|
|
209
|
+
if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const path = queriedModulePath(specifier.text);
|
|
213
|
+
return path === undefined
|
|
214
|
+
? undefined
|
|
215
|
+
: fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
|
|
216
|
+
}
|
|
197
217
|
/**
|
|
198
218
|
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
199
219
|
* namespace export are declarations whose names are where they are written, so no use is
|
|
@@ -232,6 +252,27 @@ function linksOf(file, node, declarations, enclosing) {
|
|
|
232
252
|
}
|
|
233
253
|
return found;
|
|
234
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Whether one object literal is a destructuring target, written on the left of an
|
|
257
|
+
* assignment or as a `for` loop's initializer, through the literals and spreads it nests in.
|
|
258
|
+
*/
|
|
259
|
+
function isAssignmentPattern(node) {
|
|
260
|
+
let child = node;
|
|
261
|
+
let parent = node.parent;
|
|
262
|
+
while (isParenthesizedExpression(parent) ||
|
|
263
|
+
isArrayLiteralExpression(parent) ||
|
|
264
|
+
isObjectLiteralExpression(parent) ||
|
|
265
|
+
isSpreadElement(parent) ||
|
|
266
|
+
isSpreadAssignment(parent) ||
|
|
267
|
+
(isPropertyAssignment(parent) && parent.initializer === child)) {
|
|
268
|
+
child = parent;
|
|
269
|
+
parent = parent.parent;
|
|
270
|
+
}
|
|
271
|
+
if (isBinaryExpression(parent)) {
|
|
272
|
+
return parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.left === child;
|
|
273
|
+
}
|
|
274
|
+
return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === child;
|
|
275
|
+
}
|
|
235
276
|
/**
|
|
236
277
|
* Every name node one file holds that is a use rather than a declaration, in source
|
|
237
278
|
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
@@ -246,6 +287,8 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
246
287
|
const links = [];
|
|
247
288
|
const evaluations = [];
|
|
248
289
|
const decorated = [];
|
|
290
|
+
const literals = [];
|
|
291
|
+
const flows = [];
|
|
249
292
|
const writes = new Set();
|
|
250
293
|
const alsoRead = new Set();
|
|
251
294
|
const declared = new Set();
|
|
@@ -286,6 +329,13 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
286
329
|
}
|
|
287
330
|
markNames(node);
|
|
288
331
|
markStores(node, writes, alsoRead);
|
|
332
|
+
if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
|
|
333
|
+
literals.push({ literal: node, from: enclosing });
|
|
334
|
+
}
|
|
335
|
+
// An assertion needs only comparability, so no member of its operand is read by it.
|
|
336
|
+
for (const written of isAsExpression(node) ? [] : handedOn(node)) {
|
|
337
|
+
flows.push(...valuesOf(written).map((value) => ({ value, from: enclosing })));
|
|
338
|
+
}
|
|
289
339
|
for (const read of propertyReadsOf(node)) {
|
|
290
340
|
reads.push({ ...read, from: enclosing });
|
|
291
341
|
}
|
|
@@ -308,8 +358,16 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
308
358
|
enclosing = outer;
|
|
309
359
|
};
|
|
310
360
|
file.forEachChild(visit);
|
|
311
|
-
return { uses: found, reads, links, evaluations, decorated };
|
|
361
|
+
return { uses: found, literals, flows, reads, links, evaluations, decorated };
|
|
312
362
|
}
|
|
363
|
+
/** The declaration kinds of a property an object literal declares. */
|
|
364
|
+
const LITERAL_PROPERTY_KINDS = new Set([
|
|
365
|
+
SyntaxKind.PropertyAssignment,
|
|
366
|
+
SyntaxKind.ShorthandPropertyAssignment,
|
|
367
|
+
SyntaxKind.MethodDeclaration,
|
|
368
|
+
SyntaxKind.GetAccessor,
|
|
369
|
+
SyntaxKind.SetAccessor,
|
|
370
|
+
]);
|
|
313
371
|
/** The rule a file the analysis classified as test-support code is counted under. */
|
|
314
372
|
const TEST_SUPPORT_RULE = "test-support";
|
|
315
373
|
/**
|
|
@@ -412,6 +470,59 @@ function importBindings(file) {
|
|
|
412
470
|
}
|
|
413
471
|
return found;
|
|
414
472
|
}
|
|
473
|
+
/** The value one assignment stores through the name it writes, where it is a plain assignment. */
|
|
474
|
+
function storedValue(name) {
|
|
475
|
+
let target = name;
|
|
476
|
+
if (isPropertyAccessExpression(name.parent) && name.parent.name === name) {
|
|
477
|
+
target = name.parent;
|
|
478
|
+
}
|
|
479
|
+
while (isParenthesizedExpression(target.parent)) {
|
|
480
|
+
target = target.parent;
|
|
481
|
+
}
|
|
482
|
+
const parent = target.parent;
|
|
483
|
+
return isBinaryExpression(parent) &&
|
|
484
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
485
|
+
parent.left === target
|
|
486
|
+
? parent.right
|
|
487
|
+
: undefined;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The import bindings of one file that only the values written into one declaration
|
|
491
|
+
* use, for each write into it. A binding is named by an identifier that spells its
|
|
492
|
+
* local name and is not the member a property access names.
|
|
493
|
+
*/
|
|
494
|
+
function aloneIn(file, uses, writes, found) {
|
|
495
|
+
const bindings = new Set(importBindings(file).map((binding) => binding.node.getText()));
|
|
496
|
+
if (bindings.size === 0) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
const occurrences = new Map();
|
|
500
|
+
for (const site of uses) {
|
|
501
|
+
const text = site.node.getText();
|
|
502
|
+
const parent = site.node.parent;
|
|
503
|
+
if (!bindings.has(text) || (isPropertyAccessExpression(parent) && parent.name === site.node)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (!occurrences.get(text)?.includes(site.node)) {
|
|
507
|
+
occurrences.set(text, [...(occurrences.get(text) ?? []), site.node]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const inside = (node, value) => node.pos >= value.pos && node.end <= value.end;
|
|
511
|
+
const result = [];
|
|
512
|
+
for (const write of writes) {
|
|
513
|
+
const to = found[write.index]?.to;
|
|
514
|
+
const sameTarget = writes.filter((one) => found[one.index]?.to === to);
|
|
515
|
+
const names = [...occurrences]
|
|
516
|
+
.filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
|
|
517
|
+
nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
|
|
518
|
+
.map(([name]) => name)
|
|
519
|
+
.sort(compare);
|
|
520
|
+
if (names.length > 0) {
|
|
521
|
+
result.push({ index: write.index, names });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
415
526
|
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
416
527
|
function compare(a, b) {
|
|
417
528
|
if (a === b) {
|
|
@@ -442,7 +553,8 @@ export function references(project, held, targetRoot, options) {
|
|
|
442
553
|
const found = [];
|
|
443
554
|
const tests = [];
|
|
444
555
|
const chains = aliasChains(project, held);
|
|
445
|
-
const
|
|
556
|
+
const tables = propertyTables(project.queries);
|
|
557
|
+
const destructured = destructuring(project, cap, tables);
|
|
446
558
|
let batched = 0;
|
|
447
559
|
let fileBatches = 0;
|
|
448
560
|
let residueFallbacks = 0;
|
|
@@ -455,6 +567,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
455
567
|
.ownSourceFiles()
|
|
456
568
|
.filter((file) => relativePath(consumer.root, file.fileName) !== undefined &&
|
|
457
569
|
relativePath(targetRoot, file.fileName) === undefined);
|
|
570
|
+
const ownPaths = project.ownPaths();
|
|
458
571
|
const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
|
|
459
572
|
let topLevel;
|
|
460
573
|
/** The declarations one file's top level holds. */
|
|
@@ -551,14 +664,90 @@ export function references(project, held, targetRoot, options) {
|
|
|
551
664
|
residueFallbacks += 1;
|
|
552
665
|
return project.resolvedSymbolAt(project.handle(site.node));
|
|
553
666
|
};
|
|
554
|
-
|
|
667
|
+
const walked = files.map((file) => {
|
|
668
|
+
const fileId = declaredAt(file) ?? "";
|
|
669
|
+
return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
|
|
670
|
+
});
|
|
671
|
+
const fileIdByName = new Map(walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]));
|
|
672
|
+
const contextual = contextualUses(project, held, chains, walked.flatMap((one) => one.literals), walked.flatMap((one) => one.flows), cap, tables);
|
|
673
|
+
const writtenBy = new Map();
|
|
674
|
+
for (const write of contextual.writes) {
|
|
675
|
+
const property = write.at.parent;
|
|
676
|
+
writtenBy.set(nodeKey(property.getSourceFile(), property), write.ids);
|
|
677
|
+
}
|
|
678
|
+
const forwardedCache = new Map();
|
|
679
|
+
/**
|
|
680
|
+
* The members a literal property one symbol declares writes: a use of the property
|
|
681
|
+
* reaches them, because the literal's type carries the data the member describes.
|
|
682
|
+
*/
|
|
683
|
+
const forwarded = (symbol) => {
|
|
684
|
+
const known = forwardedCache.get(symbol.id);
|
|
685
|
+
if (known !== undefined) {
|
|
686
|
+
return known;
|
|
687
|
+
}
|
|
688
|
+
const found = [];
|
|
689
|
+
if (writtenBy.size > 0) {
|
|
690
|
+
for (const handle of symbol.declarations) {
|
|
691
|
+
if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const node = project.declarationAt(handle)?.node;
|
|
695
|
+
const ids = node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
|
|
696
|
+
for (const id of ids ?? []) {
|
|
697
|
+
if (!found.some((target) => target.id === id)) {
|
|
698
|
+
found.push({ id, stepped: true, guessed: false });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
forwardedCache.set(symbol.id, found);
|
|
704
|
+
return found;
|
|
705
|
+
};
|
|
706
|
+
/** One resolved symbol's declarations along its chain, and the members its literal properties write. */
|
|
707
|
+
const targetsOf = (symbol) => {
|
|
708
|
+
const through = forwarded(symbol);
|
|
709
|
+
return through.length === 0 ? chains.chainOf(symbol) : [...chains.chainOf(symbol), ...through];
|
|
710
|
+
};
|
|
711
|
+
const testFile = new Map();
|
|
712
|
+
for (const { file } of walked) {
|
|
555
713
|
const path = renderPosition(file, root, 0).path;
|
|
556
714
|
const test = patterns.some((pattern) => pattern.test(path));
|
|
715
|
+
testFile.set(file, test);
|
|
557
716
|
if (test) {
|
|
558
717
|
tests.push(path);
|
|
559
718
|
}
|
|
560
|
-
|
|
561
|
-
|
|
719
|
+
}
|
|
720
|
+
/** Each write reference's index in `found`, by its file, with the value it stores. */
|
|
721
|
+
const valued = new Map();
|
|
722
|
+
const holdsValue = (file, value) => {
|
|
723
|
+
if (value !== undefined) {
|
|
724
|
+
valued.set(file, [...(valued.get(file) ?? []), { index: found.length - 1, value }]);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
for (const [use, sites] of [
|
|
728
|
+
["write", contextual.writes],
|
|
729
|
+
["read", contextual.reads],
|
|
730
|
+
]) {
|
|
731
|
+
for (const site of sites) {
|
|
732
|
+
const file = site.at.getSourceFile();
|
|
733
|
+
const position = renderPosition(file, root, site.at.getStart());
|
|
734
|
+
for (const id of site.ids) {
|
|
735
|
+
found.push({
|
|
736
|
+
from: site.from,
|
|
737
|
+
to: id,
|
|
738
|
+
position,
|
|
739
|
+
use,
|
|
740
|
+
resolution: "contextual",
|
|
741
|
+
test: testFile.get(file) === true,
|
|
742
|
+
});
|
|
743
|
+
if (use === "write") {
|
|
744
|
+
holdsValue(file, site.at.parent);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
|
|
750
|
+
const test = testFile.get(file) === true;
|
|
562
751
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
563
752
|
const markup = isComponentFile(file);
|
|
564
753
|
const bindings = markup ? importBindings(file) : [];
|
|
@@ -622,6 +811,17 @@ export function references(project, held, targetRoot, options) {
|
|
|
622
811
|
}
|
|
623
812
|
}
|
|
624
813
|
for (const evaluation of evaluations) {
|
|
814
|
+
const queried = queriedFile(file, evaluation.node, fileIdByName);
|
|
815
|
+
if (queried !== undefined) {
|
|
816
|
+
found.push({
|
|
817
|
+
from: evaluation.from,
|
|
818
|
+
to: queried,
|
|
819
|
+
position: renderPosition(file, root, evaluation.node.getStart()),
|
|
820
|
+
use: "evaluation",
|
|
821
|
+
resolution: "syntax",
|
|
822
|
+
test,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
625
825
|
const module = answered.get(evaluation.node);
|
|
626
826
|
if (module === undefined) {
|
|
627
827
|
continue;
|
|
@@ -697,7 +897,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
697
897
|
stepped: false,
|
|
698
898
|
guessed: true,
|
|
699
899
|
}))
|
|
700
|
-
:
|
|
900
|
+
: targetsOf(symbol);
|
|
701
901
|
for (const target of targets) {
|
|
702
902
|
found.push({
|
|
703
903
|
from: read.from,
|
|
@@ -724,7 +924,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
724
924
|
// the use names every link of the chain its name stands at the head of.
|
|
725
925
|
const targets = throughAliases(symbol === UNANSWERED
|
|
726
926
|
? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
727
|
-
:
|
|
927
|
+
: targetsOf(symbol));
|
|
728
928
|
for (const target of targets) {
|
|
729
929
|
found.push({
|
|
730
930
|
from: site.from,
|
|
@@ -734,9 +934,25 @@ export function references(project, held, targetRoot, options) {
|
|
|
734
934
|
resolution: target.guessed ? "by-name" : target.stepped ? "alias" : direct,
|
|
735
935
|
test,
|
|
736
936
|
});
|
|
937
|
+
if (site.use === "write") {
|
|
938
|
+
holdsValue(file, storedValue(site.node));
|
|
939
|
+
}
|
|
737
940
|
}
|
|
738
941
|
}
|
|
739
942
|
}
|
|
943
|
+
const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
|
|
944
|
+
for (const [file, writes] of valued) {
|
|
945
|
+
const uses = usesOf.get(file) ?? [];
|
|
946
|
+
for (const { index, names } of aloneIn(file, uses, writes, found)) {
|
|
947
|
+
const reference = found[index];
|
|
948
|
+
if (reference !== undefined) {
|
|
949
|
+
found[index] = { ...reference, alone: names };
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (consumer === undefined) {
|
|
954
|
+
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
955
|
+
}
|
|
740
956
|
found.sort((a, b) => byPosition(a.position, b.position) ||
|
|
741
957
|
compare(a.to, b.to) ||
|
|
742
958
|
compare(a.from, b.from) ||
|
|
@@ -755,6 +971,8 @@ export function references(project, held, targetRoot, options) {
|
|
|
755
971
|
aliasSteps: chains.steps,
|
|
756
972
|
patternBatches: destructured.cost.patternBatches,
|
|
757
973
|
patternLookups: destructured.cost.patternLookups,
|
|
974
|
+
contextualBatches: contextual.batches,
|
|
975
|
+
contextualLookups: contextual.lookups,
|
|
758
976
|
},
|
|
759
977
|
};
|
|
760
978
|
}
|
package/dist/src/resolve.js
CHANGED
|
@@ -133,18 +133,19 @@ function readStrings(parent, name, path, label, minItems, allowed) {
|
|
|
133
133
|
throw malformed(label, path, `holds ${String(value.length)} entries, want at least ${String(minItems)}`);
|
|
134
134
|
}
|
|
135
135
|
const seen = new Set();
|
|
136
|
-
for (const entry of value) {
|
|
136
|
+
for (const [index, entry] of value.entries()) {
|
|
137
|
+
const at = `${path}[${String(index)}]`;
|
|
137
138
|
if (typeof entry !== "string") {
|
|
138
|
-
throw malformed(label,
|
|
139
|
+
throw malformed(label, at, "is not a string");
|
|
139
140
|
}
|
|
140
141
|
if (entry === "") {
|
|
141
|
-
throw malformed(label,
|
|
142
|
+
throw malformed(label, at, "is empty");
|
|
142
143
|
}
|
|
143
144
|
if (seen.has(entry)) {
|
|
144
|
-
throw malformed(label,
|
|
145
|
+
throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
|
|
145
146
|
}
|
|
146
147
|
if (allowed !== undefined && !allowed.includes(entry)) {
|
|
147
|
-
throw malformed(label,
|
|
148
|
+
throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
|
|
148
149
|
}
|
|
149
150
|
seen.add(entry);
|
|
150
151
|
}
|
|
@@ -154,9 +155,9 @@ function readStrings(parent, name, path, label, minItems, allowed) {
|
|
|
154
155
|
const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
|
|
155
156
|
function readComponentExtensions(ts, label) {
|
|
156
157
|
const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
|
|
157
|
-
const wrong = extensions?.
|
|
158
|
-
if (wrong !==
|
|
159
|
-
throw malformed(label,
|
|
158
|
+
const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
|
|
159
|
+
if (extensions !== undefined && wrong !== -1) {
|
|
160
|
+
throw malformed(label, `ts.component_extensions[${String(wrong)}]`, `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`);
|
|
160
161
|
}
|
|
161
162
|
return extensions;
|
|
162
163
|
}
|
|
@@ -164,9 +165,9 @@ function readComponentExtensions(ts, label) {
|
|
|
164
165
|
const CONVENTION_NAMES = new Set(CONVENTION_ROWS.map((row) => row.name));
|
|
165
166
|
function readDisabledConventions(ts, label) {
|
|
166
167
|
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
167
|
-
const unknown = names?.
|
|
168
|
-
if (unknown !==
|
|
169
|
-
throw malformed(label,
|
|
168
|
+
const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
|
|
169
|
+
if (names !== undefined && unknown !== -1) {
|
|
170
|
+
throw malformed(label, `ts.disabled_conventions[${String(unknown)}]`, `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`);
|
|
170
171
|
}
|
|
171
172
|
return names;
|
|
172
173
|
}
|
package/dist/src/roots.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* decides; the published API only supposes a consumer, so a published export is live
|
|
8
8
|
* under reachability and still a candidate under reference counting.
|
|
9
9
|
*/
|
|
10
|
+
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, } from "@typescript/native/unstable/ast";
|
|
10
11
|
import { aliasChains } from "./alias-chain.js";
|
|
11
12
|
import { conventionEntries } from "./conventions.js";
|
|
12
13
|
import { entryPoints } from "./entry-points.js";
|
|
@@ -131,7 +132,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
131
132
|
publishes: entry.published &&
|
|
132
133
|
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
133
134
|
})),
|
|
134
|
-
...(options.
|
|
135
|
+
...(options.otherPackages ?? []).map((entry) => ({ entry, publishes: entry.published })),
|
|
135
136
|
];
|
|
136
137
|
for (const { entry, publishes } of entries) {
|
|
137
138
|
if (options.publishedAPI && publishes) {
|
|
@@ -158,7 +159,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
158
159
|
: conventionEntries(options.conventions, files)) {
|
|
159
160
|
enter(file, "convention", row, true);
|
|
160
161
|
}
|
|
161
|
-
for (const point of entryPoints(project, files, options.testFiles, options.host, targetRoot)) {
|
|
162
|
+
for (const point of entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse)) {
|
|
162
163
|
enter(point.file, point.rule, point.source, point.exports);
|
|
163
164
|
}
|
|
164
165
|
for (const file of files.byPath.values()) {
|
|
@@ -188,10 +189,11 @@ export function roots(project, held, targetRoot, options) {
|
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
const api = options.publishedAPI
|
|
193
|
+
? publishedAPI(project, published, exportsOf, held, files.byPath)
|
|
194
|
+
: { ids: [], guessed: [] };
|
|
195
|
+
for (const id of api.ids) {
|
|
196
|
+
add(id, "published-api", "");
|
|
195
197
|
}
|
|
196
198
|
const unmatched = configured(held, options.patterns, add);
|
|
197
199
|
const sites = new Map(held.symbols.map((symbol) => [symbol.id, symbol.position]));
|
|
@@ -205,7 +207,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
205
207
|
configFile: project.configFile,
|
|
206
208
|
liveUnderReachability: ordered,
|
|
207
209
|
unmatched,
|
|
208
|
-
unanswered: answered ?
|
|
210
|
+
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
|
209
211
|
};
|
|
210
212
|
}
|
|
211
213
|
/**
|
|
@@ -255,52 +257,134 @@ function exportTables(project, held, wanted) {
|
|
|
255
257
|
/** The kinds of container whose export table, not its members' visibility, says what it publishes. */
|
|
256
258
|
const EXPORTING_CONTAINERS = new Set(["file", "namespace"]);
|
|
257
259
|
/**
|
|
258
|
-
* Every declaration a consumer of the library can name: what the published files
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* name. Below a published module or namespace the walk follows what its export table
|
|
265
|
-
* names; below any other published declaration it follows every member a consumer
|
|
266
|
-
* can write: a public one, and a protected one, which a consumer's subclass names. A
|
|
267
|
-
* private member is not published, nor is anything below one, and a type parameter
|
|
268
|
-
* is not published either, because a consumer supplies a type argument by position.
|
|
269
|
-
*
|
|
270
|
-
* A published file is not itself published. A consumer imports a module and names
|
|
271
|
-
* what it exports; the file is a root because the manifest names it, which is a
|
|
272
|
-
* different reason.
|
|
260
|
+
* Every declaration a consumer of the library can name: what the published files export
|
|
261
|
+
* and, below each, the members a consumer writes: a namespace's exported ones, any other
|
|
262
|
+
* declaration's public and protected ones, never a private one or a type parameter. A
|
|
263
|
+
* declaration a published one's type names is published too, exported or not. A type name
|
|
264
|
+
* the checker leaves unanswered publishes every declaration spelled as it is, and those are
|
|
265
|
+
* `guessed`. A published file is not itself published.
|
|
273
266
|
*/
|
|
274
|
-
function publishedAPI(published, exportsOf, held) {
|
|
267
|
+
function publishedAPI(project, published, exportsOf, held, byPath) {
|
|
275
268
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
276
269
|
const children = childrenOf(held);
|
|
270
|
+
const nodes = declarationNodes(held, byPath);
|
|
271
|
+
const chains = aliasChains(project, held);
|
|
277
272
|
const reached = new Set();
|
|
273
|
+
const guessed = new Set();
|
|
278
274
|
const pending = [];
|
|
275
|
+
const reach = (id) => {
|
|
276
|
+
if (!reached.has(id) && byId.get(id)?.kind !== "file") {
|
|
277
|
+
reached.add(id);
|
|
278
|
+
pending.push(id);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
279
281
|
for (const file of published) {
|
|
280
282
|
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
281
283
|
if (byId.get(id)?.kind === "file") {
|
|
282
284
|
pending.push(id);
|
|
283
285
|
}
|
|
284
|
-
else
|
|
285
|
-
|
|
286
|
-
pending.push(id);
|
|
286
|
+
else {
|
|
287
|
+
reach(id);
|
|
287
288
|
}
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
for (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
while (pending.length > 0) {
|
|
292
|
+
const round = [];
|
|
293
|
+
for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
|
|
294
|
+
round.push(next);
|
|
295
|
+
const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
|
|
296
|
+
for (const member of children.get(next) ?? []) {
|
|
297
|
+
const reachable = exporting
|
|
298
|
+
? member.exported
|
|
299
|
+
: member.visibility !== "private" && member.visibility !== "private-name";
|
|
300
|
+
if (member.kind !== "type-parameter" && reachable) {
|
|
301
|
+
reach(member.id);
|
|
302
|
+
}
|
|
298
303
|
}
|
|
299
|
-
reached.add(member.id);
|
|
300
|
-
pending.push(member.id);
|
|
301
304
|
}
|
|
305
|
+
const named = round.flatMap((id) => {
|
|
306
|
+
const symbol = byId.get(id);
|
|
307
|
+
return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
|
|
308
|
+
});
|
|
309
|
+
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
310
|
+
if (symbol === UNANSWERED) {
|
|
311
|
+
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
312
|
+
guessed.add(id);
|
|
313
|
+
reach(id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (symbol !== undefined) {
|
|
317
|
+
for (const target of chains.chainOf(symbol)) {
|
|
318
|
+
reach(target.id);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
302
322
|
}
|
|
303
|
-
return [...reached];
|
|
323
|
+
return { ids: [...reached], guessed: [...guessed] };
|
|
324
|
+
}
|
|
325
|
+
/** The type names declarations of the inventory write, each file walked once, when first asked. */
|
|
326
|
+
function declarationNodes(held, byPath) {
|
|
327
|
+
const walked = new Map();
|
|
328
|
+
const nodesIn = (path) => {
|
|
329
|
+
const known = walked.get(path);
|
|
330
|
+
if (known !== undefined) {
|
|
331
|
+
return known;
|
|
332
|
+
}
|
|
333
|
+
const found = { byId: new Map(), nodes: new Set() };
|
|
334
|
+
const file = byPath.get(path);
|
|
335
|
+
if (file !== undefined) {
|
|
336
|
+
const visit = (node) => {
|
|
337
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
338
|
+
if (id !== undefined) {
|
|
339
|
+
found.nodes.add(node);
|
|
340
|
+
if (!found.byId.has(id)) {
|
|
341
|
+
found.byId.set(id, node);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
node.forEachChild(visit);
|
|
345
|
+
};
|
|
346
|
+
visit(file);
|
|
347
|
+
}
|
|
348
|
+
walked.set(path, found);
|
|
349
|
+
return found;
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
typeNamesOf: (symbol) => {
|
|
353
|
+
const { byId, nodes } = nodesIn(symbol.position.path);
|
|
354
|
+
const declaration = byId.get(symbol.id);
|
|
355
|
+
return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The names of types one declaration's own syntax writes: in a type reference, a
|
|
361
|
+
* heritage clause or a type query. A function body is not read, and neither is a
|
|
362
|
+
* declaration nested in this one, which `nested` holds and which is read on its own.
|
|
363
|
+
*/
|
|
364
|
+
function typeNamesOf(declaration, nested) {
|
|
365
|
+
const found = [];
|
|
366
|
+
const visit = (node) => {
|
|
367
|
+
if (node !== declaration && nested.has(node)) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (isTypeReferenceNode(node)) {
|
|
371
|
+
found.push(isQualifiedName(node.typeName) ? node.typeName.right : node.typeName);
|
|
372
|
+
}
|
|
373
|
+
else if (isExpressionWithTypeArguments(node)) {
|
|
374
|
+
found.push(isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression);
|
|
375
|
+
}
|
|
376
|
+
else if (isTypeQueryNode(node)) {
|
|
377
|
+
found.push(isQualifiedName(node.exprName) ? node.exprName.right : node.exprName);
|
|
378
|
+
}
|
|
379
|
+
const body = isFunctionLikeDeclaration(node) ? node.body : undefined;
|
|
380
|
+
node.forEachChild((child) => {
|
|
381
|
+
if (child !== body) {
|
|
382
|
+
visit(child);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
visit(declaration);
|
|
387
|
+
return found;
|
|
304
388
|
}
|
|
305
389
|
/**
|
|
306
390
|
* Keeps every declaration the configuration names and returns every string that
|
package/dist/src/session.js
CHANGED
|
@@ -225,10 +225,10 @@ function projectsOf(snapshot, built, written) {
|
|
|
225
225
|
* snapshot of one client, and answers in their order. A configuration `programs` describes is
|
|
226
226
|
* built as that program, or opened from the configuration it writes, instead of as a project.
|
|
227
227
|
* One the snapshot holds nothing for ends the run under `"refuse"` and is named in the result
|
|
228
|
-
* under `"record"`.
|
|
229
|
-
* after it, whether the visit completed or threw.
|
|
228
|
+
* under `"record"`. `last` runs after the last visit while the client still answers; the
|
|
229
|
+
* snapshot is disposed and the client released after it, whether the visit completed or threw.
|
|
230
230
|
*/
|
|
231
|
-
export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map()) {
|
|
231
|
+
export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map(), last = () => undefined) {
|
|
232
232
|
try {
|
|
233
233
|
const built = [];
|
|
234
234
|
const params = [];
|
|
@@ -262,6 +262,7 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
|
|
|
262
262
|
}
|
|
263
263
|
projects.push(visit(viewOf(project, configFile, engine, log)));
|
|
264
264
|
}
|
|
265
|
+
last();
|
|
265
266
|
return {
|
|
266
267
|
projects,
|
|
267
268
|
unopened: missing,
|
package/dist/src/stores.js
CHANGED
|
@@ -26,6 +26,9 @@ export function accessorsOf(project, held) {
|
|
|
26
26
|
export function storesOf(per, exempt, mode) {
|
|
27
27
|
return {
|
|
28
28
|
references: per.flatMap((one) => one.references.filter((reference) => !mode.production || !reference.test)),
|
|
29
|
+
readInTests: new Set(per.flatMap((one) => one.references
|
|
30
|
+
.filter((reference) => reference.test && reference.use !== "write")
|
|
31
|
+
.map((reference) => reference.to))),
|
|
29
32
|
accessors: new Set(per.flatMap((one) => one.accessors)),
|
|
30
33
|
exempt: new Set(exempt.map((record) => record.id)),
|
|
31
34
|
};
|