@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) 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 +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  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/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
@@ -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) => {
@@ -182,7 +182,7 @@ function declarationNodes(held, byPath) {
182
182
  return {
183
183
  typeNamesOf: (symbol) => {
184
184
  const { byId, nodes } = nodesIn(symbol.position.path);
185
- return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
185
+ return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, (nested) => nodes.has(nested)));
186
186
  },
187
187
  nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
188
188
  };
@@ -369,12 +369,12 @@ function inferredTypeSymbols(project, declarations) {
369
369
  /**
370
370
  * The names of types one declaration's own syntax writes: in a type reference, a
371
371
  * heritage clause or a type query. A function body is not read, and neither is a
372
- * declaration nested in this one, which `nested` holds and which is read on its own.
372
+ * declaration nested in this one that `readAlone` says is read on its own.
373
373
  */
374
- function typeNamesOf(declaration, nested) {
374
+ export function typeNamesOf(declaration, readAlone) {
375
375
  const found = [];
376
376
  const visit = (node) => {
377
- if (node !== declaration && nested.has(node)) {
377
+ if (node !== declaration && readAlone(node)) {
378
378
  return;
379
379
  }
380
380
  if (isTypeReferenceNode(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.startsWith("./") || prefix.startsWith("../")) || !prefix.endsWith("/")) {
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.startsWith("/")) {
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.startsWith("/")) {
82
+ if (isAbsolutePath(pattern)) {
82
83
  path = against(pattern);
83
84
  }
84
- else if (pattern.startsWith("./") || pattern.startsWith("../")) {
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
- { call, from, require, files, resolved: target !== undefined, asset: false, eager: false },
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.startsWith(".") &&
206
- !prefix.startsWith("/") &&
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, fills the type that a type
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 &&
@@ -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.production || !reference.test)),
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.test && reference.use !== "write")
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)),
@@ -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 { nodeKey } from "./inventory.js";
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 references the iterations of one project's own files make to the iteration members
131
- * of the values they iterate: a `for…of` statement and a spread element read
132
- * `[Symbol.iterator]`, and a `for await…of` statement reads `[Symbol.asyncIterator]` and,
133
- * which it falls back to, `[Symbol.iterator]`. Each is made by the declaration holding it.
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 function iterationReferences(project, held, chains, root, testFiles) {
136
- const queries = project.queries;
137
- const ownPaths = project.ownPaths();
138
- const iterated = [];
139
- for (const file of project.ownSourceFiles()) {
140
- const fileId = held.declarations.get(nodeKey(file, file));
141
- if (fileId === undefined) {
142
- continue;
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
- for (const to of member.ids) {
200
- found.push({
201
- from: one.from,
202
- to,
203
- position,
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
- return found;
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 "./entries/configuration-files.js";
11
- import { emittedFrom } from "./load/emit-map.js";
12
- import { namesImported } from "./module-declarations.js";
13
- import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
14
- import { renderPosition } from "./position.js";
15
- import { nameComponent } from "./ref.js";
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 (!specifier.startsWith("./") && !specifier.startsWith("../")) {
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 "./entries/convention-rows.js";
9
- import { packageOfSpecifier } from "./dependencies.js";
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
- }