@cplieger/deadset-ts 7.2.0-dev.4 → 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 (122) hide show
  1. package/dist/src/analysis.js +106 -10
  2. package/dist/src/augmentations.js +21 -8
  3. package/dist/src/automatic-types.js +189 -0
  4. package/dist/src/base-url.js +18 -0
  5. package/dist/src/components.js +7 -6
  6. package/dist/src/config.js +1 -0
  7. package/dist/src/configuration-files.js +50 -12
  8. package/dist/src/conformance-record.js +2 -2
  9. package/dist/src/convention-rows.js +75 -0
  10. package/dist/src/conventions.js +101 -5
  11. package/dist/src/decorator.js +21 -18
  12. package/dist/src/dependencies.js +183 -28
  13. package/dist/src/discover.js +132 -11
  14. package/dist/src/entry-point-gaps.js +7 -10
  15. package/dist/src/entry-points.js +112 -11
  16. package/dist/src/findings/completion.js +116 -21
  17. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  18. package/dist/src/findings/emitters.js +1 -1
  19. package/dist/src/findings/intra-function.js +44 -13
  20. package/dist/src/findings/non-code-artifacts.js +66 -33
  21. package/dist/src/findings/unused-declarations.js +21 -1
  22. package/dist/src/findings/visibility-narrowing.js +22 -3
  23. package/dist/src/generated-file.js +11 -0
  24. package/dist/src/git-ignore.js +86 -0
  25. package/dist/src/graph.js +4 -1
  26. package/dist/src/import-bindings.js +97 -0
  27. package/dist/src/indexed-access.js +130 -28
  28. package/dist/src/interface-satisfaction.js +30 -12
  29. package/dist/src/intra-function-parts.js +297 -45
  30. package/dist/src/inventory.js +52 -8
  31. package/dist/src/kinds.js +13 -13
  32. package/dist/src/matrix.js +18 -9
  33. package/dist/src/mdx-entries.js +197 -0
  34. package/dist/src/module-declarations.js +9 -13
  35. package/dist/src/overrides.js +41 -10
  36. package/dist/src/print.js +1 -0
  37. package/dist/src/ref.js +10 -0
  38. package/dist/src/references.js +4 -1
  39. package/dist/src/report.js +4 -2
  40. package/dist/src/resolve.js +2 -0
  41. package/dist/src/roots.js +110 -38
  42. package/dist/src/schema.js +1 -0
  43. package/dist/src/serialization-contract.js +54 -8
  44. package/dist/src/session.js +7 -0
  45. package/dist/src/source-files.js +39 -14
  46. package/dist/src/suppress-file.js +10 -2
  47. package/dist/src/suppress-inline.js +18 -2
  48. package/dist/src/suppress.js +7 -5
  49. package/dist/src/sweep.js +27 -10
  50. package/dist/src/symbol-keys.js +103 -2
  51. package/dist/src/test-support.js +121 -17
  52. package/dist/src/type-errors.js +34 -7
  53. package/dist/src/type-parameter-reads.js +172 -0
  54. package/dist/src/verbs/analyze.js +16 -0
  55. package/dist/src/verbs/explain.js +4 -2
  56. package/dist/src/version.js +1 -1
  57. package/dist/src/workflow-steps.js +119 -22
  58. package/dist/src/workspace-programs.js +7 -3
  59. package/dist/src/workspace-resolution.js +53 -10
  60. package/jsr.json +1 -1
  61. package/package.json +2 -2
  62. package/src/analysis.ts +143 -10
  63. package/src/augmentations.ts +21 -10
  64. package/src/automatic-types.ts +246 -0
  65. package/src/base-url.ts +24 -0
  66. package/src/components.ts +7 -6
  67. package/src/config.ts +3 -0
  68. package/src/configuration-files.ts +66 -12
  69. package/src/conformance-record.ts +2 -2
  70. package/src/convention-rows.ts +103 -0
  71. package/src/conventions.ts +121 -7
  72. package/src/decorator.ts +28 -18
  73. package/src/dependencies.ts +247 -41
  74. package/src/discover.ts +156 -13
  75. package/src/entry-point-gaps.ts +8 -11
  76. package/src/entry-points.ts +133 -11
  77. package/src/finding.ts +10 -0
  78. package/src/findings/completion.ts +147 -24
  79. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  80. package/src/findings/emitters.ts +1 -1
  81. package/src/findings/intra-function.ts +56 -14
  82. package/src/findings/non-code-artifacts.ts +81 -41
  83. package/src/findings/unused-declarations.ts +25 -1
  84. package/src/findings/visibility-narrowing.ts +32 -3
  85. package/src/generated-file.ts +16 -0
  86. package/src/git-ignore.ts +98 -0
  87. package/src/graph.ts +6 -0
  88. package/src/host.ts +5 -9
  89. package/src/import-bindings.ts +128 -0
  90. package/src/indexed-access.ts +152 -30
  91. package/src/interface-satisfaction.ts +33 -13
  92. package/src/intra-function-parts.ts +394 -59
  93. package/src/inventory.ts +71 -7
  94. package/src/kinds.ts +13 -13
  95. package/src/matrix.ts +32 -14
  96. package/src/mdx-entries.ts +241 -0
  97. package/src/module-declarations.ts +11 -13
  98. package/src/overrides.ts +51 -12
  99. package/src/print.ts +1 -0
  100. package/src/ref.ts +12 -0
  101. package/src/references.ts +10 -1
  102. package/src/report.ts +16 -3
  103. package/src/resolve.ts +3 -0
  104. package/src/roots.ts +163 -45
  105. package/src/schema.ts +1 -0
  106. package/src/serialization-contract.ts +55 -4
  107. package/src/session.ts +7 -0
  108. package/src/source-files.ts +66 -16
  109. package/src/suppress-file.ts +12 -1
  110. package/src/suppress-inline.ts +18 -1
  111. package/src/suppress.ts +9 -5
  112. package/src/sweep.ts +32 -11
  113. package/src/symbol-keys.ts +129 -2
  114. package/src/test-support.ts +149 -16
  115. package/src/type-errors.ts +38 -6
  116. package/src/type-parameter-reads.ts +227 -0
  117. package/src/verbs/analyze.ts +22 -0
  118. package/src/verbs/explain.ts +5 -2
  119. package/src/version.ts +1 -1
  120. package/src/workflow-steps.ts +127 -22
  121. package/src/workspace-programs.ts +7 -3
  122. package/src/workspace-resolution.ts +62 -10
@@ -4,7 +4,7 @@
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
6
  /** The version of the report schema this analyzer writes a report to. */
7
- const SCHEMA_VERSION = "8.2.0";
7
+ const SCHEMA_VERSION = "9.0.0";
8
8
  /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
9
9
  export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
10
10
  /** The name this analyzer writes every document under and mints every identifier with. */
@@ -247,7 +247,9 @@ export function buildReport(input) {
247
247
  ]
248
248
  .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
249
249
  .map(([, skip]) => skip),
250
- notes: [],
250
+ notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
251
+ .sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
252
+ .map(([, note]) => note),
251
253
  unanswered_questions: [...input.unanswered]
252
254
  .sort((a, b) => compare(a.configuration, b.configuration) ||
253
255
  compare(JSON.stringify(a), JSON.stringify(b)))
@@ -567,6 +567,7 @@ function readDoc(value, label) {
567
567
  injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
568
568
  lifecycleContracts: readLifecycleContracts(ts, label),
569
569
  serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
570
+ rootsComplete: readBoolean(ts, "roots_complete", "ts.roots_complete", label),
570
571
  };
571
572
  }
572
573
  /** Parses one document's text and walks it against the closed key list. */
@@ -776,6 +777,7 @@ function settleAll(inputs) {
776
777
  injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
777
778
  lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
778
779
  serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
780
+ rootsComplete: at("ts.roots_complete", defaults.ts.rootsComplete, (doc) => doc.rootsComplete),
779
781
  },
780
782
  };
781
783
  return { config, provenance };
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";
@@ -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);
@@ -381,6 +383,28 @@ function destinations(input, chains, walked) {
381
383
  });
382
384
  return containing.length === 0 ? target.declarations : containing;
383
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
+ };
384
408
  const erasedArguments = new Map();
385
409
  const erasedAt = (call, argument) => {
386
410
  let indexes = erasedArguments.get(call);
@@ -394,10 +418,32 @@ function destinations(input, chains, walked) {
394
418
  ? []
395
419
  : must(queries.signaturesOf(type, SignatureKind.Call))
396
420
  : [resolvedOf(call)].filter((one) => one !== undefined);
397
- indexes = new Set(invoked.arguments.flatMap((_unused, index) => signatures.some((signature) => {
398
- const parameter = must(queries.parameterType(signature, index));
399
- return parameter !== undefined && (parameter.flags & ERASED) !== 0;
400
- })
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
+ })
401
447
  ? [index]
402
448
  : []));
403
449
  erasedArguments.set(call, indexes);
@@ -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 {
@@ -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)));
@@ -8,7 +8,7 @@ import { jsonChunks } from "./json-chunks.js";
8
8
  import { joinPath } from "./paths.js";
9
9
  import { positionAt, walkDocument } from "./json-document.js";
10
10
  import { declarationKey } from "./ref.js";
11
- import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isSymbolRef, siteText, } from "./suppress.js";
11
+ import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
12
12
  /** The name and the location the grammar fixes for the ignore file. */
13
13
  export const IGNORE_FILE = "deadset-ignore.json";
14
14
  /** The name and the location the grammar fixes for the baseline. */
@@ -107,12 +107,20 @@ function readRecord(value, site, byReference, shape) {
107
107
  if (refusals.length > 0) {
108
108
  return { records: [], refusals };
109
109
  }
110
- const named = byReference.get(declarationKey(symbol, written.path)) ?? [];
110
+ const named = byReference.get(declarationKey(symbol, written.path)) ??
111
+ (isPartKind(code)
112
+ ? byReference.get(declarationKey(parameterHolder(symbol), written.path))
113
+ : undefined) ??
114
+ [];
111
115
  if (named.length === 0) {
112
116
  return { records: [{ ...written, bound: "" }], refusals: [] };
113
117
  }
114
118
  return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
115
119
  }
120
+ /** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
121
+ function parameterHolder(ref) {
122
+ return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
123
+ }
116
124
  function byReferenceOf(symbols) {
117
125
  const byReference = new Map();
118
126
  for (const symbol of symbols) {
@@ -4,8 +4,10 @@
4
4
  * below it, or on its own line where it trails a token there.
5
5
  */
6
6
  import { isConstructorDeclaration, SyntaxKind, } from "@typescript/native/ast";
7
+ import { importBindings } from "./import-bindings.js";
7
8
  import { isParameterProperty, nodeKey } from "./inventory.js";
8
9
  import { byPosition, positionKey, renderPosition } from "./position.js";
10
+ import { importRef } from "./ref.js";
9
11
  import { signatureOf } from "./signatures.js";
10
12
  import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
11
13
  /**
@@ -144,7 +146,7 @@ function bindingKey(one) {
144
146
  * function also begins, for a directive, on the line of each of its parameters, and a
145
147
  * class, for a part kind, on the lines of its constructor and its constructor's parameters.
146
148
  */
147
- function declarationLines(file, held, symbols) {
149
+ function declarationLines(file, held, symbols, targetRoot) {
148
150
  const below = new Map();
149
151
  const add = (offset, one) => {
150
152
  const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
@@ -186,6 +188,20 @@ function declarationLines(file, held, symbols) {
186
188
  node.forEachChild(visit);
187
189
  };
188
190
  file.forEachChild(visit);
191
+ // An import binding is a subject of its own, bound on its line as a declaration is.
192
+ const fileId = held.declarations.get(nodeKey(file, file));
193
+ const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
194
+ for (const name of moduleRef === "" ? [] : importBindings(file)) {
195
+ const position = renderPosition(file, targetRoot, name.getStart());
196
+ const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
197
+ let statement = name;
198
+ while (statement.parent.kind !== SyntaxKind.SourceFile) {
199
+ statement = statement.parent;
200
+ }
201
+ for (const at of [statement.pos, name.getStart()]) {
202
+ add(at, { declared: one, part: one });
203
+ }
204
+ }
189
205
  return below;
190
206
  }
191
207
  /**
@@ -210,7 +226,7 @@ export function inlineDirectives(project, held, targetRoot) {
210
226
  if (comments.length === 0) {
211
227
  continue;
212
228
  }
213
- const below = declarationLines(file, held, symbols);
229
+ const below = declarationLines(file, held, symbols, targetRoot);
214
230
  for (const comment of comments) {
215
231
  const site = renderPosition(file, targetRoot, comment.offset);
216
232
  const target = trails(file, comment.offset) ? site.line : site.line + 1;
@@ -144,10 +144,11 @@ function claimKey(finding) {
144
144
  }
145
145
  /**
146
146
  * Whether one record names one finding. A record reaches a finding in two ways: it bound
147
- * the declaration at the finding's position, or it names the finding's reference and the
148
- * file the finding is reported in, each exactly. The second is how an entry copied from a
149
- * finding about a part of a declaration reaches the part, and the only way any record
150
- * reaches a dependency row.
147
+ * the declaration at the finding's position, or it names the finding's reference, or the
148
+ * reference of the declaration holding a part that has one of its own, and the file the
149
+ * finding is reported in, each exactly. The second is how an entry copied from a finding
150
+ * about a part of a declaration reaches the part, and the only way any record reaches a
151
+ * dependency row.
151
152
  */
152
153
  export function pairs(record, finding) {
153
154
  if (record.code !== finding.code) {
@@ -164,7 +165,8 @@ export function pairs(record, finding) {
164
165
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
165
166
  return true;
166
167
  }
167
- return record.symbol === finding.symbol.ref && record.path === finding.position.path;
168
+ return ((record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
169
+ record.path === finding.position.path);
168
170
  }
169
171
  /**
170
172
  * Decides every record against `would`, the findings the run would produce were no record