@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.
Files changed (131) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +58 -22
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/report.generated.js +6 -2
  14. package/dist/src/dependencies.js +25 -29
  15. package/dist/src/destructuring.js +1 -19
  16. package/dist/src/element-access.js +179 -164
  17. package/dist/src/entries/build-scripts.js +2 -2
  18. package/dist/src/entries/configuration-files.js +3 -7
  19. package/dist/src/entries/conventions.js +2 -2
  20. package/dist/src/entries/html-entries.js +7 -10
  21. package/dist/src/entries/mdx-entries.js +2 -2
  22. package/dist/src/entries/static-module.js +2 -1
  23. package/dist/src/facts.js +4 -4
  24. package/dist/src/findings/completion.js +40 -4
  25. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  26. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  27. package/dist/src/framework-lifecycle.js +41 -14
  28. package/dist/src/graph.js +12 -4
  29. package/dist/src/import-bindings.js +73 -28
  30. package/dist/src/index/sites.js +557 -0
  31. package/dist/src/indexed-access.js +523 -478
  32. package/dist/src/interface-satisfaction.js +1 -1
  33. package/dist/src/keyof-comparisons.js +124 -103
  34. package/dist/src/load/expected-module.js +2 -2
  35. package/dist/src/load/specifiers.js +2 -4
  36. package/dist/src/load/synthesize.js +5 -0
  37. package/dist/src/load/workspace-resolution.js +2 -2
  38. package/dist/src/matrix.js +6 -4
  39. package/dist/src/module-declarations.js +70 -85
  40. package/dist/src/overrides.js +134 -112
  41. package/dist/src/paths.js +46 -7
  42. package/dist/src/platform-calls.js +2 -2
  43. package/dist/src/platform-symbols.js +13 -19
  44. package/dist/src/references/read-rule.js +1 -0
  45. package/dist/src/references/read-rules.js +48 -0
  46. package/dist/src/references/sink.js +151 -0
  47. package/dist/src/references/sources.js +450 -0
  48. package/dist/src/references.js +110 -1285
  49. package/dist/src/roots/configured-declarations.js +3 -0
  50. package/dist/src/roots/root-set.js +1 -1
  51. package/dist/src/roots/unseen-loads.js +38 -10
  52. package/dist/src/serialization-contract.js +8 -3
  53. package/dist/src/stores.js +4 -2
  54. package/dist/src/sweep.js +25 -13
  55. package/dist/src/symbol-keys.js +89 -83
  56. package/dist/src/tests/file-class.js +76 -0
  57. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  58. package/dist/src/{test-support.js → tests/support.js} +2 -11
  59. package/dist/src/type-parameter-reads.js +178 -139
  60. package/dist/src/unimplemented-callees.js +262 -242
  61. package/dist/src/verbs/analyze.js +3 -3
  62. package/dist/src/verbs/explain.js +50 -28
  63. package/dist/src/verbs/print-retained.js +2 -2
  64. package/jsr.json +1 -1
  65. package/package.json +2 -2
  66. package/src/alias-chain.ts +0 -7
  67. package/src/amd.ts +130 -0
  68. package/src/analysis.ts +86 -23
  69. package/src/augmentations.ts +41 -27
  70. package/src/bindings.ts +460 -0
  71. package/src/calls.ts +25 -7
  72. package/src/commands/tools.ts +5 -11
  73. package/src/component-files.ts +4 -2
  74. package/src/conformance-record.ts +1 -1
  75. package/src/consumers.ts +8 -11
  76. package/src/contextual-uses.ts +2 -22
  77. package/src/contract/config.generated.ts +4 -1
  78. package/src/contract/report.generated.ts +6 -2
  79. package/src/dependencies.ts +27 -28
  80. package/src/destructuring.ts +1 -31
  81. package/src/element-access.ts +188 -182
  82. package/src/entries/build-scripts.ts +2 -2
  83. package/src/entries/configuration-files.ts +11 -8
  84. package/src/entries/conventions.ts +2 -1
  85. package/src/entries/html-entries.ts +15 -14
  86. package/src/entries/mdx-entries.ts +2 -2
  87. package/src/entries/static-module.ts +2 -1
  88. package/src/facts.ts +5 -3
  89. package/src/findings/boundary.ts +2 -0
  90. package/src/findings/completion.ts +43 -4
  91. package/src/findings/emitter.ts +1 -1
  92. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  93. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  94. package/src/framework-lifecycle.ts +42 -14
  95. package/src/graph.ts +24 -6
  96. package/src/import-bindings.ts +98 -29
  97. package/src/index/sites.ts +781 -0
  98. package/src/indexed-access.ts +570 -527
  99. package/src/interface-satisfaction.ts +1 -1
  100. package/src/keyof-comparisons.ts +131 -116
  101. package/src/load/expected-module.ts +8 -2
  102. package/src/load/specifiers.ts +8 -5
  103. package/src/load/synthesize.ts +6 -0
  104. package/src/load/workspace-resolution.ts +9 -2
  105. package/src/matrix.ts +6 -3
  106. package/src/module-declarations.ts +74 -98
  107. package/src/overrides.ts +162 -134
  108. package/src/paths.ts +54 -8
  109. package/src/platform-calls.ts +3 -3
  110. package/src/platform-symbols.ts +11 -27
  111. package/src/references/read-rule.ts +60 -0
  112. package/src/references/read-rules.ts +55 -0
  113. package/src/references/sink.ts +213 -0
  114. package/src/references/sources.ts +632 -0
  115. package/src/references.ts +168 -1733
  116. package/src/report.ts +1 -1
  117. package/src/roots/configured-declarations.ts +3 -0
  118. package/src/roots/root-set.ts +1 -1
  119. package/src/roots/unseen-loads.ts +49 -11
  120. package/src/serialization-contract.ts +8 -3
  121. package/src/stores.ts +13 -2
  122. package/src/sweep.ts +37 -14
  123. package/src/symbol-keys.ts +102 -102
  124. package/src/tests/file-class.ts +118 -0
  125. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  126. package/src/{test-support.ts → tests/support.ts} +9 -20
  127. package/src/type-parameter-reads.ts +187 -158
  128. package/src/unimplemented-callees.ts +297 -280
  129. package/src/verbs/analyze.ts +3 -2
  130. package/src/verbs/explain.ts +74 -31
  131. 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(":") || value.startsWith(".") || value.startsWith("/")) {
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((specifier) => FILE_PREFIXES.some((prefix) => specifier.startsWith(prefix))));
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
- if (ELSEWHERE.test(value)) {
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.startsWith("/") ? manifestDirectory(host, root, dir) : dir;
105
- const relative = value.startsWith("/") ? `.${value}` : 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.startsWith("/")
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?.startsWith("./") === true || specifier?.startsWith("../") === true) {
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?.startsWith("./") === true || specifier?.startsWith("../") === true) {
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. It carries no binding
4
- * record, so it states `evaluated: false`.
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, and one a function outside the
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 === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
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 "./alias-chain.js";
11
- import { nodeKey } from "./inventory.js";
12
- import { must, Unanswerable } from "./query.js";
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 "./source-files.js";
8
- import { bytewise } from "./text/compare.js";
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 `ts.lifecycle_contracts` entry lists is held
3
- * back on each class that is the entry's component, being a class a decorator or a call
4
- * the entry's `components` name is attached to or passes, or one extending, directly or
5
- * through a chain, a class its `bases` name. A component's members are its type's, so a
6
- * member a base class declares answers for every subclass.
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 was found to
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
- if (input.ts.lifecycle_contracts.length === 0) {
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 = input.ts.lifecycle_contracts.some((contract) => contract.members.some((name) => name.endsWith(":static")))
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
- if (spelled !== undefined && listed.has(spelled)) {
189
- found.push({ id: member.id, detail: component.detail, site });
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 (reference.test) {
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 = { to, test: reference.test, evaluation: reference.use === "evaluation" };
99
- if (reference.consumer !== undefined || reference.unheld === true) {
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, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isJsxElement, isJsxFragment, isJsxSelfClosingElement, isNamespaceImport, SyntaxKind, } from "@typescript/native/ast";
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
- * The names one file's import declarations bind: the default, named and namespace names of
18
- * an `import` declaration, and the name of an `import x = require()` declaration that is
19
- * not exported. None is judged in a declaration file or a component file.
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 importBindings(file) {
22
- if (file.isDeclarationFile || !JUDGED.test(file.fileName)) {
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
- for (const statement of file.statements) {
27
- if (isImportDeclaration(statement)) {
28
- const clause = statement.importClause;
29
- if (clause?.name !== undefined) {
30
- found.push(clause.name);
31
- }
32
- const named = clause?.namedBindings;
33
- if (named !== undefined && isNamespaceImport(named)) {
34
- found.push(named.name);
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);