@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.17
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/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +58 -22
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/graph.js +12 -4
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +6 -4
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +86 -23
- package/src/augmentations.ts +41 -27
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/facts.ts +5 -3
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/framework-lifecycle.ts +42 -14
- package/src/graph.ts +24 -6
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +1 -1
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/matrix.ts +6 -3
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +168 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +13 -2
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +74 -31
- package/src/verbs/print-retained.ts +2 -2
|
@@ -140,6 +140,9 @@ function specifierOf(node) {
|
|
|
140
140
|
* import resolves. A specifier no file writes resolves to no module.
|
|
141
141
|
*/
|
|
142
142
|
function modulesNamed(project, specifiers) {
|
|
143
|
+
if (specifiers.size === 0) {
|
|
144
|
+
return new Map();
|
|
145
|
+
}
|
|
143
146
|
const written = new Map();
|
|
144
147
|
for (const file of project.ownSourceFiles()) {
|
|
145
148
|
const visit = (node) => {
|
|
@@ -389,7 +389,7 @@ export function rootSet(project, held, targetRoot, options) {
|
|
|
389
389
|
for (const id of skippedRoots(project, held, options.skips)) {
|
|
390
390
|
add(id, "type-error", "");
|
|
391
391
|
}
|
|
392
|
-
for (const id of mergedGlobalInterfaces(project, held, files)) {
|
|
392
|
+
for (const id of mergedGlobalInterfaces(project, held, files).kept) {
|
|
393
393
|
add(id, "global-augmentation", "");
|
|
394
394
|
}
|
|
395
395
|
return {
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* are never complete.
|
|
5
5
|
*/
|
|
6
6
|
import { isBinaryExpression, isCallExpression, isIdentifier, isMetaProperty, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { amdLoadOf, amdUnresolved } from "../amd.js";
|
|
7
8
|
import { membersNamed, textsOf, writtenValue } from "../entries/static-module.js";
|
|
8
9
|
import { writtenName } from "../platform-calls.js";
|
|
9
|
-
import { dirnamePath, relativePath, resolvePath } from "../paths.js";
|
|
10
|
+
import { cannotStartPackageName, dirnamePath, hasURLScheme, isAbsolutePath, relativePath, resolvePath, startsRelativePath, } from "../paths.js";
|
|
10
11
|
import { unwrapped } from "../unwrap.js";
|
|
11
12
|
import { globExpression } from "../text/glob.js";
|
|
12
13
|
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
@@ -29,7 +30,7 @@ function leadingLiteral(node) {
|
|
|
29
30
|
* it, each of which would name every sibling.
|
|
30
31
|
*/
|
|
31
32
|
function computedLoadDirectory(dir, prefix) {
|
|
32
|
-
if (!(prefix
|
|
33
|
+
if (!startsRelativePath(prefix) || !prefix.endsWith("/")) {
|
|
33
34
|
return undefined;
|
|
34
35
|
}
|
|
35
36
|
const target = resolvePath(dir, prefix);
|
|
@@ -65,7 +66,7 @@ function globbed(file, first, options, others, rootOf) {
|
|
|
65
66
|
const dir = dirnamePath(file.fileName);
|
|
66
67
|
const root = () => rootOf(file.fileName);
|
|
67
68
|
const against = (at) => {
|
|
68
|
-
if (!at
|
|
69
|
+
if (!isAbsolutePath(at)) {
|
|
69
70
|
return resolvePath(dir, at);
|
|
70
71
|
}
|
|
71
72
|
const top = root();
|
|
@@ -78,10 +79,10 @@ function globbed(file, first, options, others, rootOf) {
|
|
|
78
79
|
const negated = written.startsWith("!");
|
|
79
80
|
const pattern = negated ? written.slice(1) : written;
|
|
80
81
|
let path;
|
|
81
|
-
if (pattern
|
|
82
|
+
if (isAbsolutePath(pattern)) {
|
|
82
83
|
path = against(pattern);
|
|
83
84
|
}
|
|
84
|
-
else if (
|
|
85
|
+
else if (startsRelativePath(pattern)) {
|
|
85
86
|
path = from === undefined ? undefined : resolvePath(from, pattern);
|
|
86
87
|
}
|
|
87
88
|
else if (pattern.startsWith("**")) {
|
|
@@ -100,7 +101,8 @@ function globbed(file, first, options, others, rootOf) {
|
|
|
100
101
|
* file itself: an `import()` or `require()` whose path starts with a relative literal naming
|
|
101
102
|
* a directory below the file's loads every candidate below it, as a bundler's context module
|
|
102
103
|
* reads it, and an `import.meta.glob` every candidate its globs match ({@link globbed}). A
|
|
103
|
-
* call of a computed path that names no such directory and no package is an unresolved load
|
|
104
|
+
* call of a computed path that names no such directory and no package is an unresolved load,
|
|
105
|
+
* and so is an AMD call one of whose dependencies names no path ({@link amdUnresolved}).
|
|
104
106
|
*/
|
|
105
107
|
export function computedLoadsOf(file, calls, candidates, rootOf) {
|
|
106
108
|
const dir = dirnamePath(file.fileName);
|
|
@@ -118,6 +120,7 @@ export function computedLoadsOf(file, calls, candidates, rootOf) {
|
|
|
118
120
|
call,
|
|
119
121
|
from,
|
|
120
122
|
require: undefined,
|
|
123
|
+
amd: false,
|
|
121
124
|
files: files ?? [],
|
|
122
125
|
resolved: files !== undefined,
|
|
123
126
|
asset,
|
|
@@ -125,6 +128,23 @@ export function computedLoadsOf(file, calls, candidates, rootOf) {
|
|
|
125
128
|
},
|
|
126
129
|
];
|
|
127
130
|
}
|
|
131
|
+
const amd = amdLoadOf(call);
|
|
132
|
+
if (amd !== undefined) {
|
|
133
|
+
return amdUnresolved(amd)
|
|
134
|
+
? [
|
|
135
|
+
{
|
|
136
|
+
call,
|
|
137
|
+
from,
|
|
138
|
+
require: amd.callee,
|
|
139
|
+
amd: true,
|
|
140
|
+
files: [],
|
|
141
|
+
resolved: false,
|
|
142
|
+
asset: false,
|
|
143
|
+
eager: false,
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
: [];
|
|
147
|
+
}
|
|
128
148
|
const argument = unwrapped(first);
|
|
129
149
|
const require = call.expression.kind === SyntaxKind.ImportKeyword ? undefined : writtenName(call.expression);
|
|
130
150
|
if (!mayNamePath(argument)) {
|
|
@@ -139,7 +159,16 @@ export function computedLoadsOf(file, calls, candidates, rootOf) {
|
|
|
139
159
|
? []
|
|
140
160
|
: others.filter((candidate) => relativePath(target, candidate.file.fileName) !== undefined);
|
|
141
161
|
return [
|
|
142
|
-
{
|
|
162
|
+
{
|
|
163
|
+
call,
|
|
164
|
+
from,
|
|
165
|
+
require,
|
|
166
|
+
amd: false,
|
|
167
|
+
files,
|
|
168
|
+
resolved: target !== undefined,
|
|
169
|
+
asset: false,
|
|
170
|
+
eager: false,
|
|
171
|
+
},
|
|
143
172
|
];
|
|
144
173
|
});
|
|
145
174
|
}
|
|
@@ -202,7 +231,6 @@ function readsAsAsset(options) {
|
|
|
202
231
|
function namesPackage(prefix) {
|
|
203
232
|
return (prefix !== undefined &&
|
|
204
233
|
prefix !== "" &&
|
|
205
|
-
!prefix
|
|
206
|
-
!prefix
|
|
207
|
-
!/^[A-Za-z][A-Za-z\d+.-]*:/u.test(prefix));
|
|
234
|
+
!cannotStartPackageName(prefix) &&
|
|
235
|
+
!hasURLScheme(prefix));
|
|
208
236
|
}
|
|
@@ -898,7 +898,8 @@ export function serializationContract(input) {
|
|
|
898
898
|
}
|
|
899
899
|
/**
|
|
900
900
|
* The data members the decodes and copies of `files` fill, as {@link Decoded} names them.
|
|
901
|
-
* A generic declared outside the analysis, or only ambiently,
|
|
901
|
+
* A generic declared outside the analysis, or only ambiently, or one that reads a value
|
|
902
|
+
* {@link readTypes} finds untyped as its own type parameter, fills the type that a type
|
|
902
903
|
* parameter of its own no parameter names binds, where the awaited result carries that
|
|
903
904
|
* type through its data members rather than only naming it, as a store's methods do: no
|
|
904
905
|
* argument hands the value over, so it fills it where the analysis cannot see, and may
|
|
@@ -951,6 +952,10 @@ function decodedOf(project, held, files, consumers) {
|
|
|
951
952
|
for (const file of files) {
|
|
952
953
|
file.forEachChild(visit);
|
|
953
954
|
}
|
|
955
|
+
const read = readTypes(project, untyped);
|
|
956
|
+
// A generic of the analysis that reads an untyped value as its own type parameter is a
|
|
957
|
+
// client of what made the value, and fills what that parameter binds as an outside one does.
|
|
958
|
+
const clients = new Set(read.made.flatMap((type) => (type.isTypeParameter() ? [type.id] : [])));
|
|
954
959
|
const filled = [];
|
|
955
960
|
const resolved = queries.resolvedSignatures(calls).map(must);
|
|
956
961
|
for (const signature of resolved) {
|
|
@@ -963,7 +968,8 @@ function decodedOf(project, held, files, consumers) {
|
|
|
963
968
|
const node = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
964
969
|
if (declaration === undefined ||
|
|
965
970
|
(inAnalysis(declaration.path) &&
|
|
966
|
-
(node === undefined || (node.flags & NodeFlags.Ambient) === 0)
|
|
971
|
+
(node === undefined || (node.flags & NodeFlags.Ambient) === 0) &&
|
|
972
|
+
!generic.some((id) => clients.has(id)))) {
|
|
967
973
|
continue;
|
|
968
974
|
}
|
|
969
975
|
const result = must(queries.returnTypeOf(declared));
|
|
@@ -982,7 +988,6 @@ function decodedOf(project, held, files, consumers) {
|
|
|
982
988
|
filled.push({ carried: awaited(project, given), types });
|
|
983
989
|
}
|
|
984
990
|
}
|
|
985
|
-
const read = readTypes(project, untyped);
|
|
986
991
|
const copies = copiedNames(project, spreads, calls);
|
|
987
992
|
if (filled.length === 0 &&
|
|
988
993
|
read.parsed.length === 0 &&
|
package/dist/src/stores.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isGetAccessorDeclaration, isSetAccessorDeclaration, } from "@typescript/native/ast";
|
|
7
7
|
import { nodeKey } from "./inventory.js";
|
|
8
|
+
import { classedAsTest, madeByTestCode, withheldBy } from "./tests/file-class.js";
|
|
8
9
|
/** The class members one project's own files declare by a getter or a setter. */
|
|
9
10
|
export function accessorsOf(project, held) {
|
|
10
11
|
const found = [];
|
|
@@ -25,9 +26,10 @@ export function accessorsOf(project, held) {
|
|
|
25
26
|
/** The run's stores, from what each configuration contributed, the records and the mode. */
|
|
26
27
|
export function storesOf(per, exempt, mode) {
|
|
27
28
|
return {
|
|
28
|
-
references: per.flatMap((one) => one.references.filter((reference) => !mode
|
|
29
|
+
references: per.flatMap((one) => one.references.filter((reference) => !withheldBy(mode, reference, one.support))),
|
|
29
30
|
readInTests: new Set(per.flatMap((one) => one.references
|
|
30
|
-
.filter((reference) => reference.
|
|
31
|
+
.filter((reference) => reference.use !== "write" &&
|
|
32
|
+
classedAsTest(madeByTestCode(reference, one.support), reference.consumer !== undefined, mode))
|
|
31
33
|
.map((reference) => reference.to))),
|
|
32
34
|
accessors: new Set(per.flatMap((one) => one.accessors)),
|
|
33
35
|
exempt: new Set(exempt.map((record) => record.id)),
|
package/dist/src/sweep.js
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
* declaration only unreachable ones reference is live under the first, dead under the second.
|
|
7
7
|
*/
|
|
8
8
|
import { namesACaller, OUTSIDE } from "./graph.js";
|
|
9
|
+
import { edgeWithheldBy } from "./tests/file-class.js";
|
|
10
|
+
/** The references to one declaration the mode counts: under `production` none test code made but those it classes as production. */
|
|
11
|
+
function countedRefs(made, mode) {
|
|
12
|
+
if (made === undefined) {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
if (!mode.production) {
|
|
16
|
+
return made.production + made.test;
|
|
17
|
+
}
|
|
18
|
+
return made.production + (mode.consumerTestsProduction === true ? made.consumerTest : 0);
|
|
19
|
+
}
|
|
20
|
+
/** The references to one declaration, split as the mode classes them, whatever it counts. */
|
|
21
|
+
export function splitRefs(made, mode) {
|
|
22
|
+
const moved = mode.consumerTestsProduction === true ? (made?.consumerTest ?? 0) : 0;
|
|
23
|
+
return { production: (made?.production ?? 0) + moved, test: (made?.test ?? 0) - moved };
|
|
24
|
+
}
|
|
9
25
|
/**
|
|
10
26
|
* Answers which declarations of one graph are dead under the input's mode.
|
|
11
27
|
*
|
|
@@ -30,13 +46,7 @@ export function sweep(graph, input) {
|
|
|
30
46
|
called[root.at] = true;
|
|
31
47
|
}
|
|
32
48
|
}
|
|
33
|
-
const counted = (at) =>
|
|
34
|
-
const made = graph.made[at];
|
|
35
|
-
if (made === undefined) {
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
return input.mode.production ? made.production : made.production + made.test;
|
|
39
|
-
};
|
|
49
|
+
const counted = (at) => countedRefs(graph.made[at], input.mode);
|
|
40
50
|
const reached = reachable(graph, held, input.mode);
|
|
41
51
|
const liveUnder = graph.symbols.map((_symbol, at) => {
|
|
42
52
|
const relations = [];
|
|
@@ -60,10 +70,11 @@ export function sweep(graph, input) {
|
|
|
60
70
|
if (dead[at] !== true) {
|
|
61
71
|
return;
|
|
62
72
|
}
|
|
73
|
+
const split = splitRefs(graph.made[at], input.mode);
|
|
63
74
|
candidates.push({
|
|
64
75
|
id: symbol.id,
|
|
65
|
-
productionRefs:
|
|
66
|
-
testRefs:
|
|
76
|
+
productionRefs: split.production,
|
|
77
|
+
testRefs: split.test,
|
|
67
78
|
relation: counted(at) === 0 ? "reference-counting" : "reachability",
|
|
68
79
|
testOfDeadCode: testOfDeadCode[at] === true,
|
|
69
80
|
});
|
|
@@ -100,11 +111,12 @@ export function reachedWithTheTests(graph, held, namespaces = true) {
|
|
|
100
111
|
return reachable({ ...graph, rooted: [...graph.rooted, ...tests] }, flagged(graph, held), { production: false }, namespaces);
|
|
101
112
|
}
|
|
102
113
|
/**
|
|
103
|
-
* The references a loaded consumer makes that the mode counts. A
|
|
104
|
-
*
|
|
114
|
+
* The references a loaded consumer or an unheld file makes that the mode counts. A
|
|
115
|
+
* production mode counts no test reference, but a loaded consumer's where the mode classes
|
|
116
|
+
* a consumer's tests as production.
|
|
105
117
|
*/
|
|
106
118
|
function callsOf(graph, mode) {
|
|
107
|
-
return graph.consumed.filter((edge) => !mode
|
|
119
|
+
return graph.consumed.filter((edge) => !edgeWithheldBy(mode, edge));
|
|
108
120
|
}
|
|
109
121
|
/** Per declaration of the graph, whether one of the identifiers names it. */
|
|
110
122
|
function flagged(graph, ids) {
|
|
@@ -149,7 +161,7 @@ function reachable(graph, held, mode, namespaces = true) {
|
|
|
149
161
|
const walk = (holding) => {
|
|
150
162
|
for (let at = queue.pop(); at !== undefined; at = queue.pop()) {
|
|
151
163
|
for (const edge of graph.out[at] ?? []) {
|
|
152
|
-
if (!holding && mode
|
|
164
|
+
if (!holding && edgeWithheldBy(mode, edge)) {
|
|
153
165
|
continue;
|
|
154
166
|
}
|
|
155
167
|
enter(edge.to, namespaces && !edge.evaluation);
|
package/dist/src/symbol-keys.js
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
|
|
9
9
|
import { parenthesesRemoved } from "./unwrap.js";
|
|
10
|
-
import {
|
|
11
|
-
import { renderPosition } from "./position.js";
|
|
12
|
-
import { isAnswered } from "./query.js";
|
|
10
|
+
import { isAnswered, UNANSWERED } from "./query.js";
|
|
13
11
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
14
12
|
export function keyNameOf(argument) {
|
|
15
13
|
const at = parenthesesRemoved(argument);
|
|
@@ -127,87 +125,95 @@ function wellKnownName(name) {
|
|
|
127
125
|
: undefined;
|
|
128
126
|
}
|
|
129
127
|
/**
|
|
130
|
-
* The
|
|
131
|
-
*
|
|
132
|
-
* `
|
|
133
|
-
*
|
|
128
|
+
* The reads the iterations of a side's files make of the iteration members of the values they
|
|
129
|
+
* iterate: a `for…of` statement and a spread element read `[Symbol.iterator]`, and a
|
|
130
|
+
* `for await…of` statement reads `[Symbol.asyncIterator]` and, which it falls back to,
|
|
131
|
+
* `[Symbol.iterator]`. Each is made by the declaration holding it. A value whose type or
|
|
132
|
+
* members the checker leaves unanswered holds the members those names could spell.
|
|
134
133
|
*/
|
|
135
|
-
export
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const visit = (node, from) => {
|
|
145
|
-
const holder = held.declarations.get(nodeKey(file, node)) ?? from;
|
|
146
|
-
if (isForOfStatement(node)) {
|
|
147
|
-
const protocols = node.awaitModifier === undefined ? ["iterator"] : ["asyncIterator", "iterator"];
|
|
148
|
-
iterated.push({ file, from: holder, operand: node.expression, protocols });
|
|
149
|
-
}
|
|
150
|
-
else if (isSpreadElement(node)) {
|
|
151
|
-
iterated.push({ file, from: holder, operand: node.expression, protocols: ["iterator"] });
|
|
152
|
-
}
|
|
153
|
-
node.forEachChild((child) => {
|
|
154
|
-
visit(child, holder);
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
file.forEachChild((child) => {
|
|
158
|
-
visit(child, fileId);
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
if (iterated.length === 0) {
|
|
162
|
-
return [];
|
|
163
|
-
}
|
|
164
|
-
const types = queries.typesAt(iterated.map((one) => one.operand));
|
|
165
|
-
const members = new Map();
|
|
166
|
-
const membersOf = (type) => {
|
|
167
|
-
const known = members.get(type.id);
|
|
168
|
-
if (known !== undefined) {
|
|
169
|
-
return known;
|
|
170
|
-
}
|
|
171
|
-
const keyed = [];
|
|
172
|
-
const properties = queries.propertiesOf(type);
|
|
173
|
-
for (const property of isAnswered(properties) ? properties : []) {
|
|
174
|
-
for (const handle of property.declarations) {
|
|
175
|
-
const node = ownPaths.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
176
|
-
const name = wellKnownName(node?.name);
|
|
177
|
-
if (name !== undefined) {
|
|
178
|
-
keyed.push({ name, ids: chains.declarationsOf(property) });
|
|
179
|
-
break;
|
|
134
|
+
export const iteration = {
|
|
135
|
+
start() {
|
|
136
|
+
const iterated = [];
|
|
137
|
+
return {
|
|
138
|
+
visit(node, { file, from }) {
|
|
139
|
+
if (isForOfStatement(node)) {
|
|
140
|
+
const protocols = node.awaitModifier === undefined ? ["iterator"] : ["asyncIterator", "iterator"];
|
|
141
|
+
iterated.push({ file, from, operand: node.expression, protocols });
|
|
180
142
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
members.set(type.id, keyed);
|
|
184
|
-
return keyed;
|
|
185
|
-
};
|
|
186
|
-
const found = [];
|
|
187
|
-
iterated.forEach((one, index) => {
|
|
188
|
-
const type = types[index];
|
|
189
|
-
if (type === undefined || !isAnswered(type)) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [type];
|
|
193
|
-
const position = renderPosition(one.file, root, one.operand.getStart());
|
|
194
|
-
for (const part of isAnswered(parts) ? parts : []) {
|
|
195
|
-
for (const member of membersOf(part)) {
|
|
196
|
-
if (!one.protocols.includes(member.name)) {
|
|
197
|
-
continue;
|
|
143
|
+
else if (isSpreadElement(node)) {
|
|
144
|
+
iterated.push({ file, from, operand: node.expression, protocols: ["iterator"] });
|
|
198
145
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
use: "read",
|
|
205
|
-
resolution: "keyed",
|
|
206
|
-
test: testFiles.has(position.path),
|
|
207
|
-
});
|
|
146
|
+
},
|
|
147
|
+
reads({ project, chains }) {
|
|
148
|
+
const found = [];
|
|
149
|
+
if (iterated.length === 0) {
|
|
150
|
+
return found;
|
|
208
151
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
152
|
+
const queries = project.queries;
|
|
153
|
+
const ownPaths = project.ownPaths();
|
|
154
|
+
const types = queries.typesAt(iterated.map((one) => one.operand));
|
|
155
|
+
const members = new Map();
|
|
156
|
+
const membersOf = (type) => {
|
|
157
|
+
const known = members.get(type.id);
|
|
158
|
+
if (known !== undefined) {
|
|
159
|
+
return known;
|
|
160
|
+
}
|
|
161
|
+
const properties = queries.propertiesOf(type);
|
|
162
|
+
const keyed = [];
|
|
163
|
+
for (const property of isAnswered(properties) ? properties : []) {
|
|
164
|
+
for (const handle of property.declarations) {
|
|
165
|
+
const node = ownPaths.has(handle.path)
|
|
166
|
+
? project.declarationAt(handle)?.node
|
|
167
|
+
: undefined;
|
|
168
|
+
const name = wellKnownName(node?.name);
|
|
169
|
+
if (name !== undefined) {
|
|
170
|
+
keyed.push({ name, ids: chains.declarationsOf(property) });
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const result = isAnswered(properties) ? keyed : UNANSWERED;
|
|
176
|
+
members.set(type.id, result);
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
// A member a well-known symbol keys is named by the symbol's computed name.
|
|
180
|
+
const spelled = (protocols) => protocols.map((protocol) => `Symbol.${protocol}`);
|
|
181
|
+
iterated.forEach((one, index) => {
|
|
182
|
+
const type = types[index];
|
|
183
|
+
if (type === UNANSWERED) {
|
|
184
|
+
found.push({ kind: "held", from: one.from, names: spelled(one.protocols) });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (type === undefined) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [type];
|
|
191
|
+
for (const part of isAnswered(parts) ? parts : [type]) {
|
|
192
|
+
const keyed = membersOf(part);
|
|
193
|
+
if (keyed === UNANSWERED) {
|
|
194
|
+
found.push({ kind: "held", from: one.from, names: spelled(one.protocols) });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
for (const member of keyed) {
|
|
198
|
+
if (!one.protocols.includes(member.name)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
for (const to of member.ids) {
|
|
202
|
+
found.push({
|
|
203
|
+
kind: "named",
|
|
204
|
+
from: one.from,
|
|
205
|
+
to,
|
|
206
|
+
file: one.file,
|
|
207
|
+
at: one.operand,
|
|
208
|
+
use: "read",
|
|
209
|
+
via: "keyed",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
return found;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file classes the run counts references by. A test file is one a `ts.test_files`
|
|
3
|
+
* pattern or a test runner's row names (the test-runner entry source decides which), a test
|
|
4
|
+
* file no project holds, or a consumer's file a pattern names below the consumer's root.
|
|
5
|
+
* A test-support file is test code as well, decided once the references are known
|
|
6
|
+
* (`support.ts`), so a reference carries only whether a test file made it and the counters
|
|
7
|
+
* read the support class beside it.
|
|
8
|
+
*/
|
|
9
|
+
import { renderPosition } from "../position.js";
|
|
10
|
+
import { bytewise } from "../text/compare.js";
|
|
11
|
+
import { globExpression } from "../text/glob.js";
|
|
12
|
+
/** The test files of the target: each file the test-runner source names, by its file name. */
|
|
13
|
+
export function targetTestFile(named) {
|
|
14
|
+
return (file) => named.has(file.fileName);
|
|
15
|
+
}
|
|
16
|
+
/** The test files a pattern names, each by its path below `root`: a consumer's, below the consumer's root. */
|
|
17
|
+
export function patternTestFile(patterns, root) {
|
|
18
|
+
const expressions = patterns.map(globExpression);
|
|
19
|
+
return (file) => {
|
|
20
|
+
const path = renderPosition(file, root, 0).path;
|
|
21
|
+
return expressions.some((expression) => expression.test(path));
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The own files of a project that are test files, by their path below `root`, ascending. */
|
|
25
|
+
export function testFilePaths(project, root, testFile) {
|
|
26
|
+
return project
|
|
27
|
+
.ownSourceFiles()
|
|
28
|
+
.filter(testFile)
|
|
29
|
+
.map((file) => renderPosition(file, root, 0).path)
|
|
30
|
+
.sort(bytewise);
|
|
31
|
+
}
|
|
32
|
+
/** Whether test code made one reference: a test file, or a test-support file of the target. */
|
|
33
|
+
export function madeByTestCode(reference, support) {
|
|
34
|
+
return (reference.test || (reference.consumer === undefined && support.has(reference.position.path)));
|
|
35
|
+
}
|
|
36
|
+
/** Whether the mode classes a reference test code made as a test one: any but a loaded consumer's it classes as production. */
|
|
37
|
+
export function classedAsTest(test, consumer, mode) {
|
|
38
|
+
return test && !(consumer && mode.consumerTestsProduction === true);
|
|
39
|
+
}
|
|
40
|
+
/** Whether a production mode counts no reference of this one: test code made it, as the mode classes it. */
|
|
41
|
+
export function withheldBy(mode, reference, support) {
|
|
42
|
+
return (mode.production &&
|
|
43
|
+
classedAsTest(madeByTestCode(reference, support), reference.consumer !== undefined, mode));
|
|
44
|
+
}
|
|
45
|
+
/** Whether a production mode follows no edge like this one: {@link withheldBy} for a graph edge. */
|
|
46
|
+
export function edgeWithheldBy(mode, edge) {
|
|
47
|
+
return mode.production && classedAsTest(edge.test, edge.consumer, mode);
|
|
48
|
+
}
|
|
49
|
+
/** The rule a file the analysis classified as test-support code is counted under. */
|
|
50
|
+
const TEST_SUPPORT_RULE = "test-support";
|
|
51
|
+
/**
|
|
52
|
+
* The name one test-file rule takes. A rule's name is a word list the report's own
|
|
53
|
+
* vocabulary spells, which cannot carry a pattern, so a rule is named by the place of
|
|
54
|
+
* its pattern in the configured list.
|
|
55
|
+
*/
|
|
56
|
+
function patternRule(index) {
|
|
57
|
+
return `test-file-pattern-${String(index + 1)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The rules over the test files of several projects at once, ordered by rule: a file two
|
|
61
|
+
* projects hold is one file, so each rule counts the distinct paths it matches.
|
|
62
|
+
*/
|
|
63
|
+
export function testFileRulesOf(testFiles, paths, supportFiles = []) {
|
|
64
|
+
const distinct = [...new Set(paths)];
|
|
65
|
+
const support = new Set(supportFiles).size;
|
|
66
|
+
return [
|
|
67
|
+
...testFiles.map((pattern, index) => {
|
|
68
|
+
const expression = globExpression(pattern);
|
|
69
|
+
return {
|
|
70
|
+
rule: patternRule(index),
|
|
71
|
+
matched: distinct.filter((path) => expression.test(path)).length,
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
74
|
+
...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
|
|
75
|
+
].sort((a, b) => bytewise(a.rule, b.rule));
|
|
76
|
+
}
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
* it names is not known.
|
|
8
8
|
*/
|
|
9
9
|
import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
-
import { RESOLVED_EXTENSIONS } from "
|
|
11
|
-
import { emittedFrom } from "
|
|
12
|
-
import { namesImported } from "
|
|
13
|
-
import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "
|
|
14
|
-
import { renderPosition } from "
|
|
15
|
-
import { nameComponent } from "
|
|
10
|
+
import { RESOLVED_EXTENSIONS } from "../entries/configuration-files.js";
|
|
11
|
+
import { emittedFrom } from "../load/emit-map.js";
|
|
12
|
+
import { namesImported } from "../import-bindings.js";
|
|
13
|
+
import { dirnamePath, joinPath, normalizePath, queriedModulePath, startsRelativePath, } from "../paths.js";
|
|
14
|
+
import { renderPosition } from "../position.js";
|
|
15
|
+
import { nameComponent } from "../ref.js";
|
|
16
16
|
/** The paths below the target root one relative specifier may name, written path first. */
|
|
17
17
|
function candidates(dir, specifier) {
|
|
18
|
-
if (!
|
|
18
|
+
if (!startsRelativePath(specifier)) {
|
|
19
19
|
return [];
|
|
20
20
|
}
|
|
21
21
|
const path = normalizePath(dir === "" ? specifier : `${dir}/${specifier}`);
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* another such file, so it is judged with the tests: what it references is referenced by
|
|
6
6
|
* test code.
|
|
7
7
|
*/
|
|
8
|
-
import { CONVENTION_ROWS, TEST_LIBRARIES, TEST_LIBRARY_SCOPES } from "
|
|
9
|
-
import { packageOfSpecifier } from "
|
|
8
|
+
import { CONVENTION_ROWS, TEST_LIBRARIES, TEST_LIBRARY_SCOPES, } from "../entries/convention-rows.js";
|
|
9
|
+
import { packageOfSpecifier } from "../dependencies.js";
|
|
10
10
|
/** The root kind that never takes a file out of test-support code: the skip only withholds. */
|
|
11
11
|
const SKIP_ROOT = "type-error";
|
|
12
12
|
/**
|
|
@@ -163,12 +163,3 @@ export function testSupportFiles(symbols, references, roots, testFiles, evidence
|
|
|
163
163
|
const support = withoutOutsideUsers(new Set([...onlyTests].filter((path) => carries(onlyTests, path))));
|
|
164
164
|
return { support, testUsed: new Set([...onlyTests].filter((path) => !support.has(path))) };
|
|
165
165
|
}
|
|
166
|
-
/** The references with every one a test-support file makes counted as a test reference. */
|
|
167
|
-
export function supportReferences(references, support) {
|
|
168
|
-
if (support.size === 0) {
|
|
169
|
-
return references;
|
|
170
|
-
}
|
|
171
|
-
return references.map((reference) => !reference.test && support.has(reference.position.path)
|
|
172
|
-
? { ...reference, test: true }
|
|
173
|
-
: reference);
|
|
174
|
-
}
|