@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
|
@@ -11,7 +11,7 @@ import { documentStrings } from "./configuration-files.js";
|
|
|
11
11
|
import { readCommands } from "../commands/read.js";
|
|
12
12
|
import { nodeTestGlobs, } from "./test-runners.js";
|
|
13
13
|
import { documentModule, membersNamed, staticModule, textsOf, valuesAt, } from "./static-module.js";
|
|
14
|
-
import { ancestors, dirnamePath, joinPath, normalizePath, relativePath, resolvePath, } from "../paths.js";
|
|
14
|
+
import { ancestors, cannotStartPackageName, dirnamePath, joinPath, normalizePath, relativePath, resolvePath, } from "../paths.js";
|
|
15
15
|
import { satisfies } from "../version-range.js";
|
|
16
16
|
import { bytewise } from "../text/compare.js";
|
|
17
17
|
import { isRecord } from "../json-document.js";
|
|
@@ -425,7 +425,7 @@ function prefixedPackage(value, reading) {
|
|
|
425
425
|
const { prefix, marker } = reading;
|
|
426
426
|
let name;
|
|
427
427
|
if (marker === undefined) {
|
|
428
|
-
if (value.includes(":") ||
|
|
428
|
+
if (value.includes(":") || cannotStartPackageName(value)) {
|
|
429
429
|
return undefined;
|
|
430
430
|
}
|
|
431
431
|
const parts = value.split("/");
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isExportDeclaration, isImportDeclaration, isStringLiteral, } from "@typescript/native/ast";
|
|
7
7
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
8
|
-
import { ancestors, dirnamePath, joinPath, resolvePath } from "../paths.js";
|
|
8
|
+
import { ancestors, dirnamePath, hasURLScheme, isAbsolutePath, isExplicitPath, joinPath, resolvePath, } from "../paths.js";
|
|
9
9
|
import { filesBelow } from "../fs/tree.js";
|
|
10
10
|
import { holdsManifest } from "../fs/manifest.js";
|
|
11
11
|
/** A `<script` start tag, its attribute text captured. */
|
|
@@ -14,12 +14,8 @@ const SCRIPT_TAG = /<script(?=[\s>/])([^>]*)>/giu;
|
|
|
14
14
|
const SCRIPT_END = /<\/script\s*>/iu;
|
|
15
15
|
/** One attribute: a name, then a double-quoted, single-quoted or unquoted value. */
|
|
16
16
|
const ATTRIBUTE = /([^\s"'=<>/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu;
|
|
17
|
-
/** A value that names a resource on another origin, or by another scheme. */
|
|
18
|
-
const ELSEWHERE = /^(?:\/\/|[A-Za-z][A-Za-z\d+.-]*:)/u;
|
|
19
17
|
/** The `type` values a classic script carries, compared in lowercase. */
|
|
20
18
|
const CLASSIC_TYPES = new Set(["text/javascript", "application/javascript"]);
|
|
21
|
-
/** The prefixes an inline script's specifier names a file of the target with. */
|
|
22
|
-
const FILE_PREFIXES = ["/", "./", "../"];
|
|
23
19
|
/** Each attribute of one start tag's attribute text, by its lowercase name. */
|
|
24
20
|
function attributes(text) {
|
|
25
21
|
const found = new Map();
|
|
@@ -69,7 +65,7 @@ function scriptValues(parse, path, text) {
|
|
|
69
65
|
const rest = text.slice(start);
|
|
70
66
|
const end = rest.search(SCRIPT_END);
|
|
71
67
|
const content = end === -1 ? rest : rest.slice(0, end);
|
|
72
|
-
values.push(...specifiersOf(parse, `${path}.${String(start)}.ts`, content).filter(
|
|
68
|
+
values.push(...specifiersOf(parse, `${path}.${String(start)}.ts`, content).filter(isExplicitPath));
|
|
73
69
|
}
|
|
74
70
|
return values;
|
|
75
71
|
}
|
|
@@ -98,18 +94,19 @@ export function htmlEntries(host, targetRoot, files, parse) {
|
|
|
98
94
|
}
|
|
99
95
|
const dir = dirnamePath(path);
|
|
100
96
|
for (const value of scriptValues(parse, path, text)) {
|
|
101
|
-
|
|
97
|
+
// A value with a scheme, or one starting `//`, names a resource on another origin.
|
|
98
|
+
if (value.startsWith("//") || hasURLScheme(value)) {
|
|
102
99
|
continue;
|
|
103
100
|
}
|
|
104
|
-
const base = value
|
|
105
|
-
const relative = value
|
|
101
|
+
const base = isAbsolutePath(value) ? manifestDirectory(host, root, dir) : dir;
|
|
102
|
+
const relative = isAbsolutePath(value) ? `.${value}` : value;
|
|
106
103
|
const reached = resolveRelativeSpecifier(host, base, relative);
|
|
107
104
|
const file = reached === undefined ? undefined : files.byName.get(reached);
|
|
108
105
|
if (file !== undefined) {
|
|
109
106
|
found.push({ file, source: value });
|
|
110
107
|
continue;
|
|
111
108
|
}
|
|
112
|
-
const served = value
|
|
109
|
+
const served = isAbsolutePath(value)
|
|
113
110
|
? files.outputDirs.filter((outDir) => outDir === dir)
|
|
114
111
|
: [];
|
|
115
112
|
const emitting = [base, ...served].flatMap((against) => files.named(resolvePath(against, relative)));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* is an import declaration, which an MDX compiler hoists into the module it builds.
|
|
5
5
|
*/
|
|
6
6
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
7
|
-
import { dirnamePath, joinPath, resolvePath } from "../paths.js";
|
|
7
|
+
import { dirnamePath, joinPath, resolvePath, startsRelativePath } from "../paths.js";
|
|
8
8
|
import { filesBelow } from "../fs/tree.js";
|
|
9
9
|
/** The extensions of a document whose imports an MDX compiler reads. */
|
|
10
10
|
const DOCUMENT = /\.mdx?$/u;
|
|
@@ -122,7 +122,7 @@ function importedBy(text) {
|
|
|
122
122
|
}
|
|
123
123
|
const matched = open === undefined ? IMPORT_LINE.exec(line) : null;
|
|
124
124
|
const specifier = matched?.[2];
|
|
125
|
-
if (specifier
|
|
125
|
+
if (specifier !== undefined && startsRelativePath(specifier)) {
|
|
126
126
|
found.push({ specifier, names: clauseNames(matched?.[1] ?? "") });
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { isArrayLiteralExpression, isBindingElement, isExternalModuleReference, isImportClause, isImportEqualsDeclaration, isImportSpecifier, isStringLiteralLikeNode, isCallExpression, isClassDeclaration, isClassExpression, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableDeclaration, isVariableStatement, isSourceFile, isBlock, isModuleBlock, isCaseBlock, isCatchClause, isForStatement, isForInStatement, isForOfStatement, isVariableDeclarationList, isMetaProperty, isAssignmentOperator, isBinaryExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isShorthandPropertyAssignment, isSpreadElement, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { commonJSModuleValue } from "../inventory/commonjs.js";
|
|
9
9
|
import { callsPlatformBy, lexicalBinding, writtenName } from "../platform-calls.js";
|
|
10
|
+
import { startsRelativePath } from "../paths.js";
|
|
10
11
|
import { unwrapped } from "../unwrap.js";
|
|
11
12
|
/** A JSON or YAML document's value, which carries no positions. */
|
|
12
13
|
export function documentValue(value) {
|
|
@@ -443,7 +444,7 @@ export function relativeSpecifiers(file) {
|
|
|
443
444
|
}
|
|
444
445
|
const found = [];
|
|
445
446
|
const named = (specifier) => {
|
|
446
|
-
if (specifier
|
|
447
|
+
if (specifier !== undefined && startsRelativePath(specifier)) {
|
|
447
448
|
found.push(specifier);
|
|
448
449
|
}
|
|
449
450
|
};
|
package/dist/src/facts.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The facts document `--facts` writes, `bindings.1` and `bindings.2`: the run's generated
|
|
3
|
-
* files under the one decision the generated-file class reads
|
|
4
|
-
*
|
|
3
|
+
* files under the one decision the generated-file class reads, and the binding identities
|
|
4
|
+
* they publish. No binding state is computed, so it states `evaluated: false` (`bindings.10`).
|
|
5
5
|
*/
|
|
6
6
|
import { FACTS_VERSION } from "./contract/contract.generated.js";
|
|
7
7
|
import { LANGUAGE, PRODUCT } from "./product.js";
|
|
8
8
|
import { bytewise } from "./text/compare.js";
|
|
9
9
|
/** The facts document of one run, as the text written to the path `--facts` names. */
|
|
10
|
-
export function factsDocument(version, generated) {
|
|
10
|
+
export function factsDocument(version, generated, bindings) {
|
|
11
11
|
const document = {
|
|
12
12
|
facts_version: FACTS_VERSION,
|
|
13
13
|
analyzer: { name: PRODUCT, version },
|
|
@@ -32,7 +32,7 @@ export function factsDocument(version, generated) {
|
|
|
32
32
|
schema_files: fact.generator.schemaFiles,
|
|
33
33
|
}),
|
|
34
34
|
})),
|
|
35
|
-
bindings
|
|
35
|
+
bindings,
|
|
36
36
|
};
|
|
37
37
|
return `${JSON.stringify(document, null, 2)}\n`;
|
|
38
38
|
}
|
|
@@ -79,12 +79,13 @@ function testedByIn(input) {
|
|
|
79
79
|
* published API, a public member of a published type included, is `certain` where the
|
|
80
80
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
81
81
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
82
|
-
* consumer set. A member an `in` expression tests by name,
|
|
83
|
-
* analysis fills, is `possible`.
|
|
82
|
+
* consumer set. A member an `in` expression tests by name, one a function outside the
|
|
83
|
+
* analysis fills, and one {@link possiblyLoaded} names, is `possible`.
|
|
84
84
|
*/
|
|
85
85
|
function reachabilityClasses(input) {
|
|
86
86
|
const tested = testedByIn(input);
|
|
87
87
|
const filled = input.boundary.filledOutside ?? new Set();
|
|
88
|
+
const possible = possiblyLoaded(input);
|
|
88
89
|
const published = publishedIds(input.swept.matrix.union);
|
|
89
90
|
const { declared } = input.boundary.consumers;
|
|
90
91
|
const outside = declared.length === 0
|
|
@@ -93,7 +94,7 @@ function reachabilityClasses(input) {
|
|
|
93
94
|
? "certain"
|
|
94
95
|
: "probable";
|
|
95
96
|
return (id) => {
|
|
96
|
-
if (tested.has(id) || filled.has(id)) {
|
|
97
|
+
if (tested.has(id) || filled.has(id) || possible.has(id)) {
|
|
97
98
|
return "possible";
|
|
98
99
|
}
|
|
99
100
|
return published.has(id) ? outside : "certain";
|
|
@@ -137,6 +138,35 @@ function testCeilings(input, classOf) {
|
|
|
137
138
|
? testTargets(union, id).reduce((lowest, target) => capped(lowest, classOf(target)), "certain")
|
|
138
139
|
: "certain";
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* What only a bundler's retry of an import Node leaves unresolved names (program.38), and each
|
|
143
|
+
* dead declaration that reaches through dead declarations, as confidence.20 reads what only
|
|
144
|
+
* tests reach: each is dead only while no loader the analysis does not see loads the file.
|
|
145
|
+
*/
|
|
146
|
+
function possiblyLoaded(input) {
|
|
147
|
+
const seeds = input.boundary.possiblyLoaded ?? new Set();
|
|
148
|
+
if (seeds.size === 0) {
|
|
149
|
+
return seeds;
|
|
150
|
+
}
|
|
151
|
+
const union = input.swept.matrix.union;
|
|
152
|
+
const dead = new Set(input.swept.sweep.candidates.map((one) => one.id));
|
|
153
|
+
const reached = new Set();
|
|
154
|
+
const pending = union.symbols.flatMap((symbol, at) => (seeds.has(symbol.id) ? [at] : []));
|
|
155
|
+
for (let at = pending.pop(); at !== undefined; at = pending.pop()) {
|
|
156
|
+
const symbol = union.symbols[at];
|
|
157
|
+
if (symbol === undefined || reached.has(symbol.id)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
reached.add(symbol.id);
|
|
161
|
+
for (const edge of union.out[at] ?? []) {
|
|
162
|
+
const to = union.symbols[edge.to];
|
|
163
|
+
if (to !== undefined && dead.has(to.id)) {
|
|
164
|
+
pending.push(edge.to);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return reached;
|
|
169
|
+
}
|
|
140
170
|
/**
|
|
141
171
|
* The subjects dead only because production code only tests use is: each declaration the
|
|
142
172
|
* sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
|
|
@@ -281,6 +311,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
281
311
|
const union = swept.matrix.union;
|
|
282
312
|
const positions = new Map(union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]));
|
|
283
313
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
314
|
+
const fileIds = new Map(union.symbols.flatMap((one) => (one.kind === "file" ? [[one.position.path, one.id]] : [])));
|
|
284
315
|
const classOf = reachabilityClasses(input);
|
|
285
316
|
const ceilingOf = testCeilings(input, classOf);
|
|
286
317
|
const testUsed = testUsedSubjects(input);
|
|
@@ -365,7 +396,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
365
396
|
const counts = candidate === undefined
|
|
366
397
|
? union.made[at]
|
|
367
398
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
368
|
-
const ownClass = judged
|
|
399
|
+
const ownClass = judged !== undefined
|
|
400
|
+
? classOf(judged.id)
|
|
401
|
+
: (finding.reachabilityClass ??
|
|
402
|
+
(finding.code === CODE.fileNeverImported
|
|
403
|
+
? classOf(fileIds.get(finding.position.path) ?? "")
|
|
404
|
+
: "certain"));
|
|
369
405
|
const used = judged !== undefined
|
|
370
406
|
? candidate !== undefined && testUsed.declarations.has(judged.id)
|
|
371
407
|
: finding.code === CODE.fileNeverImported && testUsed.files.has(finding.position.path);
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isClassDeclaration, isHeritageClause, isInterfaceDeclaration, isMethodDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
9
9
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
10
|
-
import { aliasChains } from "
|
|
11
|
-
import { nodeKey } from "
|
|
12
|
-
import { must, Unanswerable } from "
|
|
10
|
+
import { aliasChains } from "../alias-chain.js";
|
|
11
|
+
import { nodeKey } from "../inventory.js";
|
|
12
|
+
import { must, Unanswerable } from "../query.js";
|
|
13
13
|
/** The interfaces, the classes and the `implements` entries of one project's own files. */
|
|
14
14
|
function walk(project, held) {
|
|
15
15
|
const interfaces = [];
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* links the read to a declaration, so only a text match can tell which member it reads.
|
|
5
5
|
*/
|
|
6
6
|
import { isBinaryExpression, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
-
import { sourceFilesOf } from "
|
|
8
|
-
import { bytewise } from "
|
|
7
|
+
import { sourceFilesOf } from "../source-files.js";
|
|
8
|
+
import { bytewise } from "../text/compare.js";
|
|
9
9
|
/**
|
|
10
10
|
* The text of each string literal, or template literal with no substitution, written as
|
|
11
11
|
* the left operand of an `in` expression in a file of the project that is no test file.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The framework-lifecycle class: a member a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* through a chain, a class its `bases` name.
|
|
6
|
-
*
|
|
2
|
+
* The framework-lifecycle class: a member a lifecycle contract lists is held back on each
|
|
3
|
+
* class that is the contract's component, being a class a decorator or a call the
|
|
4
|
+
* contract's `components` name is attached to or passes, or one extending, directly or
|
|
5
|
+
* through a chain, a class its `bases` name. The contracts are the platform's custom
|
|
6
|
+
* elements and each `ts.lifecycle_contracts` entry. A component's members are its type's,
|
|
7
|
+
* so a member a base class declares answers for every subclass.
|
|
7
8
|
*/
|
|
8
9
|
import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, } from "@typescript/native/ast";
|
|
9
10
|
import { aliasChains } from "./alias-chain.js";
|
|
@@ -13,6 +14,29 @@ import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } fr
|
|
|
13
14
|
import { nodeKey } from "./inventory.js";
|
|
14
15
|
import { renderPosition } from "./position.js";
|
|
15
16
|
import { must } from "./query.js";
|
|
17
|
+
/**
|
|
18
|
+
* The platform's own contract (references-ts.38): `CustomElementRegistry.define` reads
|
|
19
|
+
* these members off the class it registers, its constructor's and its prototype's
|
|
20
|
+
* (https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define),
|
|
21
|
+
* and calls each callback as a custom element reaction.
|
|
22
|
+
*/
|
|
23
|
+
const CUSTOM_ELEMENTS = {
|
|
24
|
+
components: [{ global: "CustomElementRegistry.define" }],
|
|
25
|
+
members: [
|
|
26
|
+
"observedAttributes:static",
|
|
27
|
+
"disabledFeatures:static",
|
|
28
|
+
"formAssociated:static",
|
|
29
|
+
"connectedCallback",
|
|
30
|
+
"disconnectedCallback",
|
|
31
|
+
"connectedMoveCallback",
|
|
32
|
+
"adoptedCallback",
|
|
33
|
+
"attributeChangedCallback",
|
|
34
|
+
"formAssociatedCallback",
|
|
35
|
+
"formResetCallback",
|
|
36
|
+
"formDisabledCallback",
|
|
37
|
+
"formStateRestoreCallback",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
16
40
|
/** Every contract's components and bases, resolved in one pass over the entries. */
|
|
17
41
|
function resolveContracts(project, held, contracts) {
|
|
18
42
|
const entries = contracts.flatMap((contract) => [
|
|
@@ -155,16 +179,17 @@ function byBases(project, held, chains, contracts) {
|
|
|
155
179
|
}
|
|
156
180
|
/**
|
|
157
181
|
* The framework-lifecycle detector: each member a contract lists, on each class that
|
|
158
|
-
* is a component of that contract's framework, recorded where the class
|
|
159
|
-
* be one.
|
|
182
|
+
* is a component of that contract's framework, recorded once per place where the class
|
|
183
|
+
* was found to be one.
|
|
160
184
|
*/
|
|
161
185
|
export function frameworkLifecycle(input) {
|
|
162
186
|
const { project, held, targetRoot } = input;
|
|
163
|
-
|
|
187
|
+
const declared = [CUSTOM_ELEMENTS, ...input.ts.lifecycle_contracts];
|
|
188
|
+
const contracts = resolveContracts(project, held, declared);
|
|
189
|
+
if (contracts.every((one) => one.components.length === 0 && one.bases.length === 0)) {
|
|
164
190
|
return [];
|
|
165
191
|
}
|
|
166
192
|
const chains = aliasChains(project, held);
|
|
167
|
-
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
168
193
|
const components = [
|
|
169
194
|
...byComponents(project, held, chains, contracts),
|
|
170
195
|
...byBases(project, held, chains, contracts),
|
|
@@ -173,22 +198,24 @@ export function frameworkLifecycle(input) {
|
|
|
173
198
|
return [];
|
|
174
199
|
}
|
|
175
200
|
const ids = new Set(components.map((one) => one.id));
|
|
176
|
-
const sides =
|
|
201
|
+
const sides = declared.some((contract) => contract.members.some((name) => name.endsWith(":static")))
|
|
177
202
|
? ["instance", "static"]
|
|
178
203
|
: ["instance"];
|
|
179
204
|
const members = sides.map((side) => classMembers(project, held, ids, side));
|
|
180
205
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
181
|
-
const found =
|
|
206
|
+
const found = new Map();
|
|
182
207
|
for (const component of components) {
|
|
183
208
|
const listed = new Set(component.contract.members);
|
|
184
209
|
const site = renderPosition(component.site.getSourceFile(), targetRoot, component.site.getStart());
|
|
185
210
|
for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
|
|
186
211
|
const symbol = byId.get(member.id);
|
|
187
212
|
const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
// A configured contract may name the platform's registry too; one record answers both.
|
|
214
|
+
const key = `${member.id}\0${component.detail}\0${site.path}:${String(site.line)}:${String(site.column)}`;
|
|
215
|
+
if (spelled !== undefined && listed.has(spelled) && !found.has(key)) {
|
|
216
|
+
found.set(key, { id: member.id, detail: component.detail, site });
|
|
190
217
|
}
|
|
191
218
|
}
|
|
192
219
|
}
|
|
193
|
-
return found;
|
|
220
|
+
return [...found.values()];
|
|
194
221
|
}
|
package/dist/src/graph.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* nothing, so a sweep over it is a function of the values it was built from, and the
|
|
5
5
|
* inventory's order is the order every set a sweep answers reads in.
|
|
6
6
|
*/
|
|
7
|
+
import { madeByTestCode } from "./tests/file-class.js";
|
|
7
8
|
/** The position of a declaration the inventory does not hold. */
|
|
8
9
|
export const OUTSIDE = -1;
|
|
9
10
|
/**
|
|
@@ -79,24 +80,31 @@ export function graphOf(symbols, references, roots, testFiles, supportFiles = []
|
|
|
79
80
|
const at = (id) => index.get(id) ?? OUTSIDE;
|
|
80
81
|
const tests = new Set(testFiles);
|
|
81
82
|
const out = symbols.map(() => []);
|
|
82
|
-
const made = symbols.map(() => ({ production: 0, test: 0 }));
|
|
83
|
+
const made = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
|
|
83
84
|
const consumed = [];
|
|
84
85
|
for (const reference of references) {
|
|
85
86
|
const to = at(reference.to);
|
|
86
87
|
if (to === OUTSIDE) {
|
|
87
88
|
continue;
|
|
88
89
|
}
|
|
90
|
+
const test = madeByTestCode(reference, supports);
|
|
89
91
|
const counts = made[to];
|
|
90
92
|
if (counts !== undefined) {
|
|
91
|
-
if (
|
|
93
|
+
if (test) {
|
|
92
94
|
counts.test += 1;
|
|
95
|
+
counts.consumerTest += reference.consumer === undefined ? 0 : 1;
|
|
93
96
|
}
|
|
94
97
|
else {
|
|
95
98
|
counts.production += 1;
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
const edge = {
|
|
99
|
-
|
|
101
|
+
const edge = {
|
|
102
|
+
to,
|
|
103
|
+
test,
|
|
104
|
+
evaluation: reference.use === "evaluation",
|
|
105
|
+
consumer: reference.consumer !== undefined,
|
|
106
|
+
};
|
|
107
|
+
if (edge.consumer || reference.unheld === true) {
|
|
100
108
|
consumed.push(edge);
|
|
101
109
|
continue;
|
|
102
110
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The import bindings of one file: the names its import declarations bind, which only
|
|
3
3
|
* the file itself can read.
|
|
4
4
|
*/
|
|
5
|
-
import { isExportSpecifier, isExternalModuleReference,
|
|
5
|
+
import { isExportDeclaration, isExportSpecifier, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isJsxElement, isJsxFragment, isJsxSelfClosingElement, isNamedExports, isNamespaceImport, SyntaxKind, } from "@typescript/native/ast";
|
|
6
6
|
/** The extensions of the files whose import bindings are judged: no markup can read them. */
|
|
7
7
|
const JUDGED = /\.(?:[mc]?ts|tsx|[mc]?js|jsx)$/u;
|
|
8
8
|
/** The compiler's `jsx` settings whose emit calls no factory the file must hold. */
|
|
@@ -13,41 +13,86 @@ const DOC_COMMENT = /\/\*\*[\s\S]*?\*\//gu;
|
|
|
13
13
|
const DOC_REFERENCE = /\{[^{}]*\}|@see\s+[A-Za-z_$][\w$.]*/gu;
|
|
14
14
|
/** The factory a pragma comment names: `@jsx h` or `@jsxFrag Fragment`. */
|
|
15
15
|
const PRAGMA = /@jsx(?:Frag)?\s+([A-Za-z_$][\w$]*)/gu;
|
|
16
|
+
const bindingsCache = new WeakMap();
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
* an `import` declaration
|
|
19
|
-
*
|
|
18
|
+
* Every name one import declaration binds, in source order: the default, named and
|
|
19
|
+
* namespace names of an `import` declaration and the name of an `import x = …` declaration.
|
|
20
|
+
* A namespace import and an import assignment bind a whole module.
|
|
20
21
|
*/
|
|
21
|
-
export function
|
|
22
|
-
if (
|
|
23
|
-
return [
|
|
22
|
+
export function declarationBindings(declaration) {
|
|
23
|
+
if (isImportEqualsDeclaration(declaration)) {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
name: declaration.name,
|
|
27
|
+
equals: {
|
|
28
|
+
exported: (declaration.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword),
|
|
29
|
+
external: isExternalModuleReference(declaration.moduleReference),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
24
33
|
}
|
|
34
|
+
const clause = declaration.importClause;
|
|
25
35
|
const found = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
else if (named !== undefined) {
|
|
37
|
-
for (const element of named.elements) {
|
|
38
|
-
found.push(element.name);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
else if (isImportEqualsDeclaration(statement) &&
|
|
43
|
-
isExternalModuleReference(statement.moduleReference) &&
|
|
44
|
-
isIdentifier(statement.name) &&
|
|
45
|
-
!(statement.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword)) {
|
|
46
|
-
found.push(statement.name);
|
|
36
|
+
if (clause?.name !== undefined) {
|
|
37
|
+
found.push({ name: clause.name, imported: "default" });
|
|
38
|
+
}
|
|
39
|
+
const named = clause?.namedBindings;
|
|
40
|
+
if (named !== undefined && isNamespaceImport(named)) {
|
|
41
|
+
found.push({ name: named.name });
|
|
42
|
+
}
|
|
43
|
+
else if (named !== undefined) {
|
|
44
|
+
for (const element of named.elements) {
|
|
45
|
+
found.push({ name: element.name, imported: element.propertyName?.text ?? element.name.text });
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
return found;
|
|
50
49
|
}
|
|
50
|
+
/** Every name one file's top-level import declarations bind, in source order. */
|
|
51
|
+
export function bindingsOf(file) {
|
|
52
|
+
const known = bindingsCache.get(file);
|
|
53
|
+
if (known !== undefined) {
|
|
54
|
+
return known;
|
|
55
|
+
}
|
|
56
|
+
const found = file.statements.flatMap((statement) => isImportDeclaration(statement) || isImportEqualsDeclaration(statement)
|
|
57
|
+
? declarationBindings(statement)
|
|
58
|
+
: []);
|
|
59
|
+
bindingsCache.set(file, found);
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The export names an import or a re-export declaration reads from the module it names,
|
|
64
|
+
* `undefined` standing for the whole module where a namespace or a star reads it; none for
|
|
65
|
+
* any other node.
|
|
66
|
+
*/
|
|
67
|
+
export function importedNames(node) {
|
|
68
|
+
if (isImportDeclaration(node)) {
|
|
69
|
+
return declarationBindings(node).map((binding) => binding.imported);
|
|
70
|
+
}
|
|
71
|
+
if (!isExportDeclaration(node)) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const clause = node.exportClause;
|
|
75
|
+
return clause === undefined || !isNamedExports(clause)
|
|
76
|
+
? [undefined]
|
|
77
|
+
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
78
|
+
}
|
|
79
|
+
/** The export names {@link importedNames} gives one node, without the whole module. */
|
|
80
|
+
export function namesImported(node) {
|
|
81
|
+
return importedNames(node).flatMap((name) => name ?? []);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The bindings of one file that are judged as imports: those of {@link bindingsOf} but an
|
|
85
|
+
* `import x = …` that is exported or names no external module. None is judged in a
|
|
86
|
+
* declaration file or a component file.
|
|
87
|
+
*/
|
|
88
|
+
export function importBindings(file) {
|
|
89
|
+
if (file.isDeclarationFile || !JUDGED.test(file.fileName)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return bindingsOf(file).flatMap((binding) => binding.equals !== undefined && (binding.equals.exported || !binding.equals.external)
|
|
93
|
+
? []
|
|
94
|
+
: [binding.name]);
|
|
95
|
+
}
|
|
51
96
|
/** Whether one identifier names a binding in a local export, which reads it whatever it resolves to. */
|
|
52
97
|
export function exportsLocally(use) {
|
|
53
98
|
return isExportSpecifier(use.parent);
|