@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
package/dist/src/roots.js CHANGED
@@ -9,11 +9,12 @@
9
9
  */
10
10
  import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/ast";
11
11
  import { aliasChains } from "./alias-chain.js";
12
+ import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
12
13
  import { conventionEntries } from "./conventions.js";
13
14
  import { entryPoints } from "./entry-points.js";
14
15
  import { globExpression } from "./glob.js";
15
16
  import { nodeKey } from "./inventory.js";
16
- import { relativePath, resolvePath } from "./paths.js";
17
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
17
18
  import { byPosition } from "./position.js";
18
19
  import { UNANSWERED } from "./query.js";
19
20
  import { sourceFilesOf } from "./source-files.js";
@@ -43,11 +44,22 @@ const READ_BACK_RULES = new Set([
43
44
  const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
44
45
  /** The extension a source file's name drops. */
45
46
  const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
47
+ /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
48
+ function ofPackage(host, dir, file) {
49
+ for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
50
+ if (relativePath(dir, at) === undefined ||
51
+ host.kindOf(joinPath(at, "package.json")) === "file") {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
46
57
  /**
47
58
  * The rooted files one manifest target reads back to by name: its path below the
48
59
  * manifest's directory, past its first directory where it names one, without its output
49
- * extension, is the name, and a rooted file is one whose path below the same directory,
50
- * without its source extension, ends with that name at a directory boundary.
60
+ * extension, is the name, and a rooted file of the manifest's own package is one whose
61
+ * path below the same directory, without its source extension, ends with that name at a
62
+ * directory boundary.
51
63
  */
52
64
  function readBackByName(host, entry, rooted) {
53
65
  const dir = resolvePath(host.workingDirectory(), entry.dir);
@@ -63,12 +75,41 @@ function readBackByName(host, entry, rooted) {
63
75
  const found = new Map();
64
76
  for (const file of rooted) {
65
77
  const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
66
- if (path !== undefined && (path === name || path.endsWith(`/${name}`))) {
78
+ if (path !== undefined &&
79
+ (path === name || path.endsWith(`/${name}`)) &&
80
+ ofPackage(host, dir, file.fileName)) {
67
81
  found.set(file.fileName, file);
68
82
  }
69
83
  }
70
84
  return [...found.values()];
71
85
  }
86
+ /**
87
+ * The program files a manifest target no program holds reaches: the target is a file
88
+ * read as a configuration file outside the program is, and each file of the chain it
89
+ * starts loads the program files its relative specifiers name, read through the emit
90
+ * mapping, as a command's shim loads the compiled entry it runs.
91
+ */
92
+ function throughOutside(options, targetRoot, files, entry) {
93
+ const cwd = options.host.workingDirectory();
94
+ const path = resolvePath(cwd, entry.path);
95
+ if (options.parse === undefined ||
96
+ options.runFiles?.has(path) === true ||
97
+ options.host.kindOf(path) !== "file") {
98
+ return [];
99
+ }
100
+ const chain = outsideChain(options.host, targetRoot, files, options.parse, [
101
+ { path, manifestDir: resolvePath(cwd, entry.dir) },
102
+ ]);
103
+ const found = new Map();
104
+ for (const { file } of chain) {
105
+ for (const specifier of relativeSpecifiers(file)) {
106
+ for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
107
+ found.set(named.fileName, named);
108
+ }
109
+ }
110
+ }
111
+ return [...found.values()];
112
+ }
72
113
  /** Whether a configured string carries either special character. */
73
114
  function isPattern(text) {
74
115
  return text.includes("*") || text.includes("?");
@@ -168,8 +209,8 @@ export function roots(project, held, targetRoot, options) {
168
209
  found.set([id, kind, source].join("\u0000"), { id, kind, source });
169
210
  };
170
211
  const entered = [];
171
- const enter = (file, kind, source, exports) => {
172
- entered.push({ file, kind, source, exports });
212
+ const enter = (file, kind, source, exports, names) => {
213
+ entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
173
214
  };
174
215
  const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse, options.conventions?.selected);
175
216
  const published = [];
@@ -191,11 +232,28 @@ export function roots(project, held, targetRoot, options) {
191
232
  ...scripted,
192
233
  ...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
193
234
  ];
235
+ const entryFilesOf = new Map();
194
236
  const entryFiles = (entry) => {
237
+ const known = entryFilesOf.get(entry);
238
+ if (known !== undefined) {
239
+ return known;
240
+ }
241
+ const found = readEntryFiles(entry);
242
+ entryFilesOf.set(entry, found);
243
+ return found;
244
+ };
245
+ const readEntryFiles = (entry) => {
195
246
  const named = files.named(entry.path);
196
- return named.length > 0 || entry.role === "script"
197
- ? named
198
- : readBackByName(options.host, entry, readBackTo);
247
+ if (named.length > 0 || entry.role === "script") {
248
+ return named;
249
+ }
250
+ const cwd = options.host.workingDirectory();
251
+ const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
252
+ if (shipped.length > 0) {
253
+ return shipped;
254
+ }
255
+ const byName = readBackByName(options.host, entry, readBackTo);
256
+ return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
199
257
  };
200
258
  for (const { entry, publishes } of entries) {
201
259
  if (publishes) {
@@ -203,27 +261,39 @@ export function roots(project, held, targetRoot, options) {
203
261
  }
204
262
  }
205
263
  const publishedNames = new Set(published.map((file) => file.fileName));
206
- for (const { entry } of entries) {
207
- for (const file of entryFiles(entry)) {
208
- const kind = ENTRY_KINDS[entry.role];
209
- enter(file, kind, entry.member, !publishedNames.has(file.fileName));
210
- }
211
- }
264
+ const patterned = [];
212
265
  for (const pattern of options.entryFiles) {
213
266
  const expression = globExpression(pattern);
214
267
  for (const [path, file] of files.byPath) {
215
268
  if (expression.test(path)) {
216
- enter(file, "entry-file", pattern, true);
269
+ patterned.push({ file, pattern });
217
270
  }
218
271
  }
219
272
  }
273
+ const unmapped = [];
274
+ const mapped = patterned.map(({ file }) => file);
275
+ for (const { entry } of entries) {
276
+ const named = entryFiles(entry);
277
+ if (named.length === 0 && entry.role !== "script") {
278
+ unmapped.push(entry);
279
+ }
280
+ mapped.push(...named);
281
+ for (const file of named) {
282
+ const kind = ENTRY_KINDS[entry.role];
283
+ enter(file, kind, entry.member, !publishedNames.has(file.fileName));
284
+ }
285
+ }
286
+ const unmappedEntries = unmapped.filter((entry) => readBackByName(options.host, entry, mapped).length === 0);
287
+ for (const { file, pattern } of patterned) {
288
+ enter(file, "entry-file", pattern, true);
289
+ }
220
290
  for (const { file, row } of options.conventions === undefined
221
291
  ? []
222
292
  : conventionEntries(options.conventions, files)) {
223
293
  enter(file, "convention", row, true);
224
294
  }
225
295
  for (const point of points) {
226
- enter(point.file, point.rule, point.source, point.exports);
296
+ enter(point.file, point.rule, point.source, point.exports, point.names);
227
297
  }
228
298
  for (const file of files.byPath.values()) {
229
299
  for (const alias of typeQueryAliases(file)) {
@@ -236,7 +306,7 @@ export function roots(project, held, targetRoot, options) {
236
306
  }
237
307
  }
238
308
  }
239
- const { tables: exportsOf, answered } = exportTables(project, held, [
309
+ const { tables: exportsOf, named, answered, } = exportTables(project, held, [
240
310
  ...entered.filter((entry) => entry.exports).map((entry) => entry.file),
241
311
  ...published,
242
312
  ]);
@@ -247,8 +317,12 @@ export function roots(project, held, targetRoot, options) {
247
317
  }
248
318
  add(id, entry.kind, entry.source);
249
319
  if (entry.exports) {
250
- for (const exported of exportsOf.get(entry.file.fileName) ?? []) {
251
- add(exported, entry.kind, entry.source);
320
+ const byName = named.get(entry.file.fileName);
321
+ const exported = entry.names === undefined || byName === undefined
322
+ ? (exportsOf.get(entry.file.fileName) ?? [])
323
+ : entry.names.flatMap((name) => byName.get(name) ?? []);
324
+ for (const one of exported) {
325
+ add(one, entry.kind, entry.source);
252
326
  }
253
327
  }
254
328
  }
@@ -271,6 +345,7 @@ export function roots(project, held, targetRoot, options) {
271
345
  liveUnderReachability: ordered,
272
346
  unmatched,
273
347
  unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
348
+ unmappedEntries,
274
349
  };
275
350
  }
276
351
  /**
@@ -284,9 +359,10 @@ export function roots(project, held, targetRoot, options) {
284
359
  function exportTables(project, held, wanted) {
285
360
  const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
286
361
  const tables = new Map();
362
+ const named = new Map();
287
363
  let answered = true;
288
364
  if (unique.length === 0) {
289
- return { tables, answered };
365
+ return { tables, named, answered };
290
366
  }
291
367
  const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
292
368
  const chains = aliasChains(project, held);
@@ -307,15 +383,18 @@ function exportTables(project, held, wanted) {
307
383
  return;
308
384
  }
309
385
  const ids = new Set();
386
+ const byName = new Map();
310
387
  for (const exported of table) {
311
388
  for (const target of chains.chainOf(exported)) {
312
389
  answered &&= !target.guessed;
313
390
  ids.add(target.id);
391
+ byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
314
392
  }
315
393
  }
316
394
  tables.set(file.fileName, [...ids]);
395
+ named.set(file.fileName, byName);
317
396
  });
318
- return { tables, answered };
397
+ return { tables, named, answered };
319
398
  }
320
399
  /** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
321
400
  const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
@@ -370,11 +449,13 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
370
449
  const nodes = declarationNodes(held, byPath);
371
450
  const chains = aliasChains(project, held);
372
451
  const reached = new Set();
452
+ const entered = new Set();
373
453
  const guessed = new Set();
374
454
  const pending = [];
375
455
  const reach = (id) => {
376
- if (!reached.has(id) && byId.get(id)?.kind !== "file") {
377
- reached.add(id);
456
+ const file = byId.get(id)?.kind === "file";
457
+ if (!(file ? entered : reached).has(id)) {
458
+ (file ? entered : reached).add(id);
378
459
  pending.push(id);
379
460
  }
380
461
  };
@@ -383,12 +464,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
383
464
  }
384
465
  for (const file of published) {
385
466
  for (const id of exportsOf.get(file.fileName) ?? []) {
386
- if (byId.get(id)?.kind === "file") {
387
- pending.push(id);
388
- }
389
- else {
390
- reach(id);
391
- }
467
+ reach(id);
392
468
  }
393
469
  }
394
470
  while (pending.length > 0) {
@@ -411,8 +487,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
411
487
  });
412
488
  for (const symbol of inferredTypeSymbols(project, round.flatMap((id) => {
413
489
  const symbol = byId.get(id);
414
- const node = symbol === undefined ? undefined : nodes.nodeOf(symbol);
415
- return node === undefined ? [] : [node];
490
+ return symbol === undefined ? [] : nodes.nodesOf(symbol);
416
491
  }))) {
417
492
  for (const target of chains.chainOf(symbol)) {
418
493
  reach(target.id);
@@ -449,9 +524,7 @@ function declarationNodes(held, byPath) {
449
524
  const id = held.declarations.get(nodeKey(file, node));
450
525
  if (id !== undefined) {
451
526
  found.nodes.add(node);
452
- if (!found.byId.has(id)) {
453
- found.byId.set(id, node);
454
- }
527
+ found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
455
528
  }
456
529
  node.forEachChild(visit);
457
530
  };
@@ -463,10 +536,9 @@ function declarationNodes(held, byPath) {
463
536
  return {
464
537
  typeNamesOf: (symbol) => {
465
538
  const { byId, nodes } = nodesIn(symbol.position.path);
466
- const declaration = byId.get(symbol.id);
467
- return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
539
+ return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
468
540
  },
469
- nodeOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id),
541
+ nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
470
542
  };
471
543
  }
472
544
  /** How deep the type arguments and constituents of an inferred type are followed. */
@@ -82,6 +82,7 @@ export const SCHEMA_ROOT = {
82
82
  members: { components: declarations, bases: declarations, members: leaf },
83
83
  },
84
84
  serializers: declarations,
85
+ roots_complete: leaf,
85
86
  },
86
87
  },
87
88
  provenance: { kind: "map" },
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The serialization-contract class: the data members and `toJSON` of every type a value's
3
3
  * static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
4
- * names, and `toString` too where it reaches an `unknown` or `any` parameter of a function
5
- * outside the analysis or an erased property of a type the program does not declare. A
6
- * function of the project handing such a parameter to a destination is one, to a fixpoint,
7
- * and a function invoked through `call`, `apply` or `bind` is the callee.
4
+ * names, and `toString` too where it reaches an erased parameter of a function outside the
5
+ * analysis, or an erased property of a type the program does not declare. A function of
6
+ * the project handing such a parameter to a destination is one, to a fixpoint, and a
7
+ * function invoked through `call`, `apply` or `bind` is the callee.
8
8
  */
9
9
  import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyDeclaration, isSatisfiesExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
@@ -16,7 +16,7 @@ import { nodeKey } from "./inventory.js";
16
16
  import { renderPosition } from "./position.js";
17
17
  import { must } from "./query.js";
18
18
  import { erasedStores } from "./erased-stores.js";
19
- import { checkedTypes, valueReach } from "./value-reach.js";
19
+ import { checkedTypes, CONVERSION_MEMBERS, valueReach } from "./value-reach.js";
20
20
  /** The serializer every run reads, whatever the configuration names. */
21
21
  const JSON_STRINGIFY = { shape: "global", global: "JSON.stringify" };
22
22
  /** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
@@ -25,6 +25,8 @@ const CONVERSIONS = new Set(["toJSON", "toString"]);
25
25
  const SERIALIZED_CONVERSION = "toJSON";
26
26
  /** The parameter types that keep nothing of the value they are given. */
27
27
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
28
+ /** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
29
+ const MENTION_DEPTH = 3;
28
30
  /** The wider of two reaches, where either may be absent. */
29
31
  function wider(a, b) {
30
32
  return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
@@ -313,13 +315,14 @@ function destinations(input, chains, walked) {
313
315
  const inAnalysis = (path) => own.has(path) || input.consumers.some((root) => path === root || path.startsWith(`${root}/`));
314
316
  const implemented = implementedOwners(project, inAnalysis);
315
317
  /**
316
- * Whether every declaration of a callee is outside the analysis or ambient. An ambient
317
- * declaration has no body, so the code that runs is outside the analysis, unless it is a
318
- * member of an interface or an object type a class or a typed object literal of the
319
- * analysis implements; `settled` false skips that question, which asks the checker.
318
+ * Whether every one of `declarations`, all of the callee's by default, is outside the
319
+ * analysis or ambient. An ambient declaration has no body, so the code that runs is
320
+ * outside the analysis, unless it is a member of an interface or an object type a class
321
+ * or a typed object literal of the analysis implements; `settled` false skips that
322
+ * question, which asks the checker.
320
323
  */
321
- const outside = (target, settled) => target.declarations.length > 0 &&
322
- target.declarations.every((handle) => {
324
+ const outside = (target, settled, declarations = target.declarations) => declarations.length > 0 &&
325
+ declarations.every((handle) => {
323
326
  if (!inAnalysis(handle.path)) {
324
327
  return true;
325
328
  }
@@ -333,6 +336,12 @@ function destinations(input, chains, walked) {
333
336
  const owner = must(queries.parentOf(target));
334
337
  return owner === undefined || !implemented().has(owner.id);
335
338
  });
339
+ /**
340
+ * Whether some one declaration of a callee is outside the analysis or ambient, without
341
+ * asking the checker. A project's declaration merged into a library's symbol leaves the
342
+ * library's beside it, so a call can still resolve to the library's.
343
+ */
344
+ const mayBeOutside = (target) => target.declarations.some((handle) => outside(target, false, [handle]));
336
345
  const invocation = (call) => {
337
346
  const form = forms.get(call);
338
347
  const method = form === undefined ? undefined : callees.get(form.method);
@@ -340,7 +349,7 @@ function destinations(input, chains, walked) {
340
349
  // program's own `call` and `Reflect.apply` invoke nothing here.
341
350
  if (form !== undefined &&
342
351
  method !== undefined &&
343
- outside(method, false) &&
352
+ mayBeOutside(method) &&
344
353
  FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")) {
345
354
  return form.invocation;
346
355
  }
@@ -350,6 +359,52 @@ function destinations(input, chains, walked) {
350
359
  const name = calleeName(invocation(call).callee);
351
360
  return name === undefined ? undefined : callees.get(name);
352
361
  };
362
+ const signatures = new Map();
363
+ const resolvedOf = (call) => {
364
+ if (!signatures.has(call)) {
365
+ signatures.set(call, must(queries.resolvedSignature(call)));
366
+ }
367
+ return signatures.get(call);
368
+ };
369
+ /**
370
+ * The declarations of a callee that the declaration of a direct call's resolved
371
+ * signature lies in, or all of them where it lies in none: a declaration merged into
372
+ * the callee's symbol that the call does not resolve to does not decide where it runs.
373
+ */
374
+ const calledDeclarations = (call, target) => {
375
+ const declaration = invocation(call).indirect ? undefined : resolvedOf(call)?.declaration;
376
+ const signature = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
377
+ if (declaration === undefined || signature === undefined) {
378
+ return target.declarations;
379
+ }
380
+ const containing = target.declarations.filter((handle) => {
381
+ const node = handle.path === declaration.path ? project.declarationAt(handle)?.node : undefined;
382
+ return node !== undefined && node.pos <= signature.pos && signature.end <= node.end;
383
+ });
384
+ return containing.length === 0 ? target.declarations : containing;
385
+ };
386
+ /** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
387
+ const mentions = (type, parameter, depth) => {
388
+ // A conditional type an alias names, `Awaited<T>` among them, carries its arguments
389
+ // only on the alias the client's type object reports.
390
+ const aliased = type
391
+ .aliasTypeArguments;
392
+ if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
393
+ return true;
394
+ }
395
+ if (depth >= MENTION_DEPTH) {
396
+ return false;
397
+ }
398
+ const inner = [
399
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
400
+ ...must(queries.typeArguments(type)),
401
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
402
+ ...signature.parameters.map((_parameter, at) => must(queries.typeAtPosition(signature, at))),
403
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
404
+ ]),
405
+ ];
406
+ return inner.some((one) => mentions(one, parameter, depth + 1));
407
+ };
353
408
  const erasedArguments = new Map();
354
409
  const erasedAt = (call, argument) => {
355
410
  let indexes = erasedArguments.get(call);
@@ -362,11 +417,33 @@ function destinations(input, chains, walked) {
362
417
  ? type === undefined
363
418
  ? []
364
419
  : must(queries.signaturesOf(type, SignatureKind.Call))
365
- : [must(queries.resolvedSignature(call))].filter((one) => one !== undefined);
366
- indexes = new Set(invoked.arguments.flatMap((_unused, index) => signatures.some((signature) => {
367
- const parameter = must(queries.parameterType(signature, index));
368
- return parameter !== undefined && (parameter.flags & ERASED) !== 0;
369
- })
420
+ : [resolvedOf(call)].filter((one) => one !== undefined);
421
+ // A type parameter of the callee that nothing constrains and its result does not name
422
+ // keeps nothing of the value either: the instantiated signature names the value's own
423
+ // type there instead, and a result that names it hands the value back.
424
+ const generic = invoked.indirect ? undefined : resolvedOf(call);
425
+ const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
426
+ const unconstrained = (index) => {
427
+ const parameter = declared === undefined ? undefined : must(queries.parameterType(declared, index));
428
+ const parts = parameter === undefined
429
+ ? []
430
+ : parameter.isUnionType()
431
+ ? must(queries.constituents(parameter))
432
+ : [parameter];
433
+ const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
434
+ return parts.some((part) => {
435
+ if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
436
+ return false;
437
+ }
438
+ const constraint = must(queries.constraintOf(part));
439
+ return constraint === undefined || (constraint.flags & ERASED) !== 0;
440
+ });
441
+ };
442
+ indexes = new Set(invoked.arguments.flatMap((_unused, index) => unconstrained(index) ||
443
+ signatures.some((signature) => {
444
+ const parameter = must(queries.parameterType(signature, index));
445
+ return parameter !== undefined && (parameter.flags & ERASED) !== 0;
446
+ })
370
447
  ? [index]
371
448
  : []));
372
449
  erasedArguments.set(call, indexes);
@@ -384,7 +461,7 @@ function destinations(input, chains, walked) {
384
461
  if (entry !== undefined) {
385
462
  return { reach: "properties", callee: spelledEntry(entry.entry, held) };
386
463
  }
387
- if (outside(target, true)) {
464
+ if (outside(target, true, calledDeclarations(call, target))) {
388
465
  return erasedAt(call, argument)
389
466
  ? {
390
467
  reach: "conversions",
@@ -419,7 +496,7 @@ function destinations(input, chains, walked) {
419
496
  if (target === undefined) {
420
497
  return false;
421
498
  }
422
- if (named.some((one) => names(one, target, chains)) || outside(target, false)) {
499
+ if (named.some((one) => names(one, target, chains)) || mayBeOutside(target)) {
423
500
  return true;
424
501
  }
425
502
  return chains
@@ -509,7 +586,7 @@ export function serializationContract(input) {
509
586
  // A destination holds back a data member and `toJSON`, and `toString` as well where
510
587
  // the value left the analysis.
511
588
  const holdsBack = (flags, name, to) => (flags & SymbolFlags.Property) !== 0 ||
512
- ((flags & SymbolFlags.Method) !== 0 &&
589
+ ((flags & CONVERSION_MEMBERS) !== 0 &&
513
590
  (name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
514
591
  const found = [];
515
592
  for (const flow of flows) {
@@ -31,6 +31,7 @@ export function openEngine(options) {
31
31
  ...(mapper === undefined ? {} : { runExternalCode: true }),
32
32
  });
33
33
  let closed = false;
34
+ const parsed = new Map();
34
35
  return {
35
36
  parseConfigFile: (file) => api.parseConfigFile(file),
36
37
  parseConfigJson: (json, configFile) => api.parseJsonConfigFileContent(json, { configFileName: configFile }),
@@ -54,8 +55,14 @@ export function openEngine(options) {
54
55
  moduleResolutions: { fallback: "resolve", entries: [...entries] },
55
56
  }),
56
57
  parseSourceFile: (fileName, text) => {
58
+ // Every project of a run reads the same files outside its program, so one parse serves all.
59
+ const known = parsed.get(fileName);
60
+ if (known?.text === text) {
61
+ return known.file;
62
+ }
57
63
  const retained = api.createSourceFile(fileName, text);
58
64
  try {
65
+ parsed.set(fileName, { text, file: retained.sourceFile });
59
66
  return retained.sourceFile;
60
67
  }
61
68
  finally {
@@ -189,12 +196,30 @@ export function ownedAs(project, own) {
189
196
  },
190
197
  };
191
198
  }
192
- /** The three diagnostic sets one project carries, read before any analysis. */
199
+ /**
200
+ * The program's diagnostics placed in no file. The per-file sets never carry one; the
201
+ * client answers its `fileName` as `null`, and the global set repeats the program set:
202
+ * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/api/session.go#L3726-L3752
203
+ */
204
+ function unplacedOf(program) {
205
+ const seen = new Set();
206
+ return [...program.getGlobalDiagnostics(), ...program.getProgramDiagnostics()]
207
+ .filter((diagnostic) => diagnostic.fileName == null)
208
+ .filter((diagnostic) => {
209
+ const key = `${String(diagnostic.code)}\u0000${diagnostic.text}`;
210
+ const fresh = !seen.has(key);
211
+ seen.add(key);
212
+ return fresh;
213
+ })
214
+ .map((diagnostic) => ({ ...diagnostic, fileName: undefined }));
215
+ }
216
+ /** The diagnostic sets one project carries, read before any analysis. */
193
217
  export function diagnosticsOf(project) {
194
218
  return {
195
219
  syntactic: project.program.getSyntacticDiagnostics(),
196
220
  semantic: project.program.getSemanticDiagnostics(),
197
221
  configParsing: project.program.getConfigFileParsingDiagnostics(),
222
+ unplaced: unplacedOf(project.program),
198
223
  locate: (fileName, offset) => {
199
224
  const file = project.program.getSourceFile(fileName);
200
225
  if (file === undefined || offset < 0 || offset > file.text.length) {
@@ -4,9 +4,13 @@
4
4
  * compiler emits from it.
5
5
  */
6
6
  import { commonDirectory, emittedFrom } from "./emit-map.js";
7
- import { dirnamePath, relativePath, resolvePath } from "./paths.js";
7
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
8
8
  /** The one character a subpath pattern of a manifest's `exports` holds. */
9
9
  const SUBPATH_WILDCARD = "*";
10
+ /** The extension a source file's name drops when the project writes it out. */
11
+ const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
12
+ /** The extensions the project may write a source file's output under. */
13
+ const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"];
10
14
  /**
11
15
  * Whether one path is what a subpath pattern stands for: the text before the
12
16
  * wildcard, any run of characters including the solidus, then the text after it.
@@ -17,6 +21,27 @@ function matchesSubpath(pattern, path) {
17
21
  const after = pattern.slice(at + SUBPATH_WILDCARD.length);
18
22
  return (path.length >= before.length + after.length && path.startsWith(before) && path.endsWith(after));
19
23
  }
24
+ /** The first own file of the paths given that the project holds, or none. */
25
+ function heldAt(byName, paths) {
26
+ for (const candidate of paths) {
27
+ const file = byName.get(candidate);
28
+ if (file !== undefined) {
29
+ return [file];
30
+ }
31
+ }
32
+ return [];
33
+ }
34
+ /** Where one project writes its output, and the directory it writes it from, absolute. */
35
+ function layoutOf(project, fileNames) {
36
+ const options = project.program.getCompilerOptions();
37
+ const configDir = dirnamePath(project.configFile);
38
+ const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
39
+ return {
40
+ outDir: at(options.outDir),
41
+ declarationDir: at(options.declarationDir),
42
+ sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
43
+ };
44
+ }
20
45
  /**
21
46
  * The lookups over one project's own files.
22
47
  *
@@ -36,12 +61,7 @@ export function sourceFilesOf(project, targetRoot) {
36
61
  byPath.set(path, file);
37
62
  }
38
63
  }
39
- const options = project.program.getCompilerOptions();
40
- const configDir = dirnamePath(project.configFile);
41
- const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
42
- const outDir = at(options.outDir);
43
- const declarationDir = at(options.declarationDir);
44
- const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
64
+ const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
45
65
  const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
46
66
  /** The source paths one written path may stand for, the path itself first. */
47
67
  const candidates = (path) => [path, ...emittedFrom(path, layout)];
@@ -49,15 +69,20 @@ export function sourceFilesOf(project, targetRoot) {
49
69
  byPath,
50
70
  byName,
51
71
  outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
72
+ writtenAs: (file) => {
73
+ const below = relativePath(sourceDir, file.fileName);
74
+ if (below === undefined) {
75
+ return [];
76
+ }
77
+ const written = [outDir, declarationDir].flatMap((dir) => dir === undefined
78
+ ? []
79
+ : OUTPUT_EXTENSIONS.map((extension) => joinPath(dir, below.replace(SOURCE_EXTENSION, extension))));
80
+ return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
81
+ },
82
+ namedAsOutput: (outputDir, path) => heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
52
83
  named: (path) => {
53
84
  if (!path.includes(SUBPATH_WILDCARD)) {
54
- for (const candidate of candidates(path)) {
55
- const held = byName.get(candidate);
56
- if (held !== undefined) {
57
- return [held];
58
- }
59
- }
60
- return [];
85
+ return heldAt(byName, candidates(path));
61
86
  }
62
87
  const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
63
88
  return [...byName.values()].filter((file) => patterns.some((pattern) => matchesSubpath(pattern, file.fileName)));