@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) 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 +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -27,7 +27,7 @@ import {
27
27
  resolveEntries,
28
28
  spelledEntry,
29
29
  type ResolvedEntry,
30
- } from "./configured-declarations.ts";
30
+ } from "./roots/configured-declarations.ts";
31
31
  import type { DetectorInput, Evidence } from "./exempt.ts";
32
32
  import { isParameterProperty, nodeKey } from "./inventory.ts";
33
33
  import { renderPosition } from "./position.ts";
@@ -29,7 +29,7 @@ import {
29
29
  } from "@typescript/native/ast";
30
30
  import { SymbolFlags, type Type } from "@typescript/native/sync";
31
31
  import { aliasChains } from "./alias-chain.ts";
32
- import { emptyImplementation } from "./implementations.ts";
32
+ import { emptyImplementation } from "./findings/implementations.ts";
33
33
  import type { DetectorInput, Evidence } from "./exempt.ts";
34
34
  import type { Reference } from "./references.ts";
35
35
  import type { Exemption } from "./sweep.ts";
@@ -59,6 +59,7 @@ import {
59
59
  type DeclarationSource,
60
60
  type SymbolKind,
61
61
  } from "../inventory.ts";
62
+ import { callsPlatformBy, lexicalBinding, writtenName, type BindingOf } from "../platform-calls.ts";
62
63
  import type { Component } from "../ref.ts";
63
64
  import { spellsNoBinding } from "../spelled-names.ts";
64
65
  import { unwrapped } from "../unwrap.ts";
@@ -67,13 +68,14 @@ import { unwrapped } from "../unwrap.ts";
67
68
  * The export table of a CommonJS file: the module value `module.exports` is assigned and
68
69
  * the properties written on it, read from the top-level unconditional statements in order,
69
70
  * as Node runs them (https://nodejs.org/api/modules.html#moduleexports). Each entry is
70
- * keyed by the node that writes it, which is the checker's declaration of the export where
71
- * it binds one, so the two name one subject. Any other write of the exports opens the
72
- * table: its entries are kept and the file is listed open.
71
+ * keyed by the node that writes it, the checker's declaration of the export where it binds
72
+ * one. Any other write of the exports opens the table: its entries are kept and the file is
73
+ * listed open. `declarationOf` reads a `require()` call through the file's own scopes.
73
74
  */
74
75
  export function commonJS(
75
76
  typeIsModule: (path: string) => boolean,
76
77
  resolved: Resolved,
78
+ declarationOf: (occurrence: Identifier) => Node | undefined,
77
79
  ): DeclarationSource {
78
80
  return {
79
81
  declare(file: SourceFile, owner: Building, into: Builder): void {
@@ -82,7 +84,12 @@ export function commonJS(
82
84
  }
83
85
  const bound = boundOccurrences(file, resolved);
84
86
  const free = (node: Node): boolean => !bound.has(node);
85
- const table = readTable(file, free, topLevelDeclarations(file));
87
+ const table = readTable(
88
+ file,
89
+ free,
90
+ topLevelDeclarations(file),
91
+ lexicalBinding(declarationOf),
92
+ );
86
93
  const open = table.open || opens(file, free, table.modelled);
87
94
  for (const entry of table.entries.values()) {
88
95
  const record = into.declare(entry.node, owner, entry.kind, entry.name, {
@@ -267,6 +274,8 @@ interface Entry {
267
274
  readonly component: Component;
268
275
  readonly localName: string | undefined;
269
276
  readonly kind: SymbolKind;
277
+ /** The value written under the key, undefined for an accessor. */
278
+ readonly value: Node | undefined;
270
279
  /** The declaration of the file the module value names, which the export then exports. */
271
280
  readonly target?: Node;
272
281
  /** The class whose members the entry holds. */
@@ -279,6 +288,10 @@ interface Table {
279
288
  readonly open: boolean;
280
289
  /** Every write the scan read, which the opening walk then passes over. */
281
290
  readonly modelled: ReadonlySet<Node>;
291
+ /** The value the last assignment of `module.exports` writes, undefined where none does. */
292
+ readonly whole: Node | undefined;
293
+ /** Whether a member write opens the table, apart from a module value the table cannot read. */
294
+ readonly membersOpen: boolean;
282
295
  }
283
296
 
284
297
  const DEFAULT: Component = { text: "default", computed: false };
@@ -288,23 +301,34 @@ function readTable(
288
301
  file: SourceFile,
289
302
  isFree: (node: Node) => boolean,
290
303
  declarations: ReadonlyMap<string, Node>,
304
+ bindingOf: BindingOf,
291
305
  ): Table {
292
306
  const entries = new Map<string, Entry>();
293
307
  const modelled = new Set<Node>();
294
- let open = false;
308
+ // Written inside the closures below, which the compiler's narrowing does not follow.
309
+ let open = false as boolean;
295
310
  let aliasing = true;
311
+ let whole: Node | undefined;
312
+ let wholeOpen = false;
296
313
  const free = (node: Node, name: string): boolean =>
297
314
  isIdentifier(node) && node.text === name && isFree(node);
298
315
  const isModuleExports = (node: Node): node is PropertyAccessExpression =>
299
316
  isPropertyAccessExpression(node) &&
300
317
  free(node.expression, "module") &&
301
318
  node.name.text === "exports";
302
- const isRequire = (node: Node): boolean =>
303
- isCallExpression(node) &&
304
- free(node.expression, "require") &&
305
- node.arguments.length === 1 &&
306
- node.arguments[0] !== undefined &&
307
- isLiteralText(node.arguments[0]);
319
+ const isRequire = (node: Node): boolean => {
320
+ if (!isCallExpression(node) || node.arguments.length !== 1) {
321
+ return false;
322
+ }
323
+ const [argument] = node.arguments;
324
+ const callee = writtenName(node.expression);
325
+ return (
326
+ argument !== undefined &&
327
+ isLiteralText(argument) &&
328
+ callee !== undefined &&
329
+ callsPlatformBy("require", callee, bindingOf)
330
+ );
331
+ };
308
332
 
309
333
  /** What a value written under one key declares. */
310
334
  const entryOf = (
@@ -314,7 +338,7 @@ function readTable(
314
338
  value: Node | undefined,
315
339
  ): Entry => {
316
340
  const held = value === undefined ? undefined : unwrapped(value);
317
- const base = { node, name, component, localName: undefined };
341
+ const base = { node, name, component, localName: undefined, value };
318
342
  if (held === undefined) {
319
343
  return { ...base, kind: "variable" };
320
344
  }
@@ -365,6 +389,7 @@ function readTable(
365
389
  /** The module value one assignment writes, placed at the `exports` name it assigns. */
366
390
  const moduleValue = (node: BinaryExpression, name: Node): void => {
367
391
  entries.clear();
392
+ whole = node.right;
368
393
  const value = unwrapped(node.right);
369
394
  if (isRequire(value)) {
370
395
  return;
@@ -373,7 +398,7 @@ function readTable(
373
398
  value.properties.forEach(literalProperty);
374
399
  return;
375
400
  }
376
- const base = { node, name, component: DEFAULT };
401
+ const base = { node, name, component: DEFAULT, value: node.right };
377
402
  const target = isIdentifier(value) ? declarations.get(value.text) : undefined;
378
403
  if (target !== undefined) {
379
404
  entries.set("default", { ...base, localName: undefined, kind: "export-alias", target });
@@ -389,7 +414,7 @@ function readTable(
389
414
  members: value,
390
415
  });
391
416
  } else {
392
- open = true;
417
+ wholeOpen = true;
393
418
  }
394
419
  };
395
420
 
@@ -422,8 +447,18 @@ function readTable(
422
447
  }
423
448
  } else if (isModuleExports(left)) {
424
449
  modelled.add(expression);
425
- moduleValue(expression, left.name);
426
- aliasing = false;
450
+ const inner = unwrapped(expression.right);
451
+ const chained =
452
+ isBinaryExpression(inner) &&
453
+ inner.operatorToken.kind === SyntaxKind.EqualsToken &&
454
+ free(inner.left, "exports")
455
+ ? inner
456
+ : undefined;
457
+ if (chained !== undefined) {
458
+ modelled.add(chained);
459
+ }
460
+ moduleValue(chained ?? expression, left.name);
461
+ aliasing = chained !== undefined;
427
462
  } else if (isPropertyAccessExpression(left) || isElementAccessExpression(left)) {
428
463
  const object = exportsObject(left.expression);
429
464
  const key = isPropertyAccessExpression(left) ? left.name : left.argumentExpression;
@@ -454,7 +489,57 @@ function readTable(
454
489
  }
455
490
  }
456
491
  }
457
- return { entries, open, modelled };
492
+ return { entries, open: open || wholeOpen, modelled, whole, membersOpen: open };
493
+ }
494
+
495
+ /** One member a CommonJS file's module value holds, as {@link commonJSModuleValue} reads it. */
496
+ interface WrittenMember {
497
+ readonly key: string;
498
+ /** The value written, undefined for an accessor. */
499
+ readonly value: Node | undefined;
500
+ /** The node that writes the member. */
501
+ readonly node: Node;
502
+ }
503
+
504
+ /** The module value a CommonJS file leaves once Node has run its top level. */
505
+ export interface ModuleValue {
506
+ /** The value the last assignment of `module.exports` writes, where that is no object literal. */
507
+ readonly whole: Node | undefined;
508
+ /** The members written on the module value, an object literal assigned to `module.exports` among them. */
509
+ readonly members: readonly WrittenMember[];
510
+ /** Whether a write the table does not model may set any member. */
511
+ readonly open: boolean;
512
+ }
513
+
514
+ /**
515
+ * The module value one file leaves where Node runs it as CommonJS, read by the one table
516
+ * the inventory's export table reads, or undefined where the file writes module syntax.
517
+ * `declarationOf` names the declaration each name resolves to, undefined for none, as a
518
+ * lexical reading of a file outside every program answers it.
519
+ */
520
+ export function commonJSModuleValue(
521
+ file: SourceFile,
522
+ declarationOf: (occurrence: Identifier) => Node | undefined,
523
+ ): ModuleValue | undefined {
524
+ if (writesModuleSyntax(file)) {
525
+ return undefined;
526
+ }
527
+ const free = (node: Node): boolean => {
528
+ const declaration = isIdentifier(node) ? declarationOf(node) : undefined;
529
+ return declaration === undefined || !bindsOwn(declaration, file);
530
+ };
531
+ const table = readTable(file, free, topLevelDeclarations(file), lexicalBinding(declarationOf));
532
+ const held = table.whole === undefined ? undefined : unwrapped(table.whole);
533
+ const members = [...table.entries.values()].flatMap((entry) =>
534
+ entry.component === DEFAULT && held !== undefined && !isObjectLiteralExpression(held)
535
+ ? []
536
+ : [{ key: entry.component.text, value: entry.value, node: entry.node }],
537
+ );
538
+ return {
539
+ whole: held === undefined || isObjectLiteralExpression(held) ? undefined : table.whole,
540
+ members,
541
+ open: table.membersOpen || opens(file, free, table.modelled),
542
+ };
458
543
  }
459
544
 
460
545
  /** Whether one node is `Object.defineProperty`. */
@@ -15,6 +15,7 @@ import { relativePath } from "../paths.ts";
15
15
  import { renderPosition } from "../position.ts";
16
16
  import { isAnswered, UNANSWERED } from "../query.ts";
17
17
  import type { Handle, ProjectView } from "../session.ts";
18
+ import { declarationOf } from "../entries/static-module.ts";
18
19
  import { commonJS, type Resolved } from "./commonjs.ts";
19
20
  import { SYNTAX } from "./syntax.ts";
20
21
 
@@ -34,7 +35,7 @@ export function inventory<Brand>(
34
35
  const moduleOf = packageScope(host, targetRoot);
35
36
  const sources: readonly DeclarationSource[] = [
36
37
  SYNTAX,
37
- commonJS(packageTypeIsModule(host, targetRoot), resolvedIn(project)),
38
+ commonJS(packageTypeIsModule(host, targetRoot), resolvedIn(project), declarationOf),
38
39
  ];
39
40
  const into = new Builder(targetRoot);
40
41
  for (const file of files) {
@@ -1,29 +1,46 @@
1
+ import { LANGUAGE_ROWS } from "./contract/contract.generated.ts";
2
+ import { globExpression } from "./text/glob.ts";
3
+
1
4
  export const JAVASCRIPT_FILE = /\.(?:js|jsx|mjs|cjs)$/u;
2
5
 
3
- /** A minified file by its name; any other is one by its size and its mean line length. */
4
- const MINIFIED_NAME = /\.min\.(?:js|mjs|cjs)$/u;
6
+ /** The patterns the TypeScript row of `languages.json` skips files by, each matched against a name alone. */
7
+ const MINIFIED_NAMES = LANGUAGE_ROWS.ts.skippedFiles.map((pattern) => ({
8
+ pattern,
9
+ expression: globExpression(pattern),
10
+ }));
5
11
  const MINIFIED_BYTES = 10_240;
6
12
  const MINIFIED_LINE = 500;
7
13
 
8
- /** Whether one name is a minified file's, which decides it without its text. */
9
- export function minifiedByName(fileName: string): boolean {
10
- return MINIFIED_NAME.test(fileName);
14
+ /** What a minified file is decided by where its text is not read. */
15
+ const LONG_LINES = "long lines";
16
+
17
+ /** The skipped-file pattern one file's name matches, which decides it minified without its text. */
18
+ export function minifiedByName(fileName: string): string | undefined {
19
+ const name = fileName.slice(fileName.lastIndexOf("/") + 1);
20
+ return MINIFIED_NAMES.find((one) => one.expression.test(name))?.pattern;
11
21
  }
12
22
 
13
- /** Whether one JavaScript file is minified: by its name, or by its text's size and mean line length. */
14
- export function minified(fileName: string, text: string): boolean {
23
+ /**
24
+ * What decides one JavaScript file minified: the skipped-file pattern its name matches, or
25
+ * {@link LONG_LINES} for a text of the size whose mean line is longer than the limit;
26
+ * undefined for a file that is not minified.
27
+ */
28
+ export function minifiedBy(fileName: string, text: string): string | undefined {
15
29
  if (!JAVASCRIPT_FILE.test(fileName)) {
16
- return false;
30
+ return undefined;
17
31
  }
18
- if (minifiedByName(fileName)) {
19
- return true;
32
+ const named = minifiedByName(fileName);
33
+ if (named !== undefined) {
34
+ return named;
20
35
  }
21
36
  // A UTF-16 unit encodes to at most three UTF-8 bytes, so a shorter text is below the size.
22
37
  if (text.length * 3 < MINIFIED_BYTES) {
23
- return false;
38
+ return undefined;
24
39
  }
25
40
  const lines = text.split(/\r?\n/u).length;
26
- return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE;
41
+ return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE
42
+ ? LONG_LINES
43
+ : undefined;
27
44
  }
28
45
 
29
46
  /** The length of a text in UTF-8, a lone surrogate counting as the three bytes of U+FFFD. */
@@ -10,7 +10,10 @@ import {
10
10
  isCaseClause,
11
11
  isElementAccessExpression,
12
12
  isIdentifier,
13
+ isNoSubstitutionTemplateLiteral,
14
+ isNumericLiteral,
13
15
  isPropertyAccessExpression,
16
+ isStringLiteral,
14
17
  isSwitchStatement,
15
18
  isTypeOperatorNode,
16
19
  SyntaxKind,
@@ -20,12 +23,8 @@ import {
20
23
  type TypeNode,
21
24
  } from "@typescript/native/ast";
22
25
  import { SymbolFlags, type Type } from "@typescript/native/sync";
23
- import type { AliasChains } from "./alias-chain.ts";
24
- import { nodeKey, type Inventory } from "./inventory.ts";
25
- import { renderPosition } from "./position.ts";
26
26
  import { UNANSWERED } from "./query.ts";
27
- import type { Reference } from "./references.ts";
28
- import type { ProjectView } from "./session.ts";
27
+ import type { Read, ReadRule } from "./references/read-rule.ts";
29
28
  import { parenthesesRemoved } from "./unwrap.ts";
30
29
 
31
30
  /** The operators that compare their operands for equality. */
@@ -83,125 +82,141 @@ function literalNames(parts: readonly Type[]): readonly string[] {
83
82
  }
84
83
 
85
84
  /**
86
- * The references the key comparisons of one project's own files make to the members their
87
- * literal side names, each made by the declaration holding the comparison.
85
+ * The reads the key comparisons of a side's files make of the members their literal side
86
+ * names, each made by the declaration holding the comparison. A question the checker leaves
87
+ * unanswered holds what its answer could have read.
88
88
  */
89
- export function keyofComparisonReferences<Brand>(
90
- project: ProjectView<Brand>,
91
- held: Inventory,
92
- chains: AliasChains,
93
- root: string,
94
- testFiles: ReadonlySet<string>,
95
- ): readonly Reference[] {
96
- const queries = project.queries;
97
- const comparisons: Comparison[] = [];
98
- for (const file of project.ownSourceFiles()) {
99
- const fileId = held.declarations.get(nodeKey(file, file));
100
- if (fileId === undefined) {
101
- continue;
102
- }
103
- const visit = (node: Node, from: string): void => {
104
- const holder = held.declarations.get(nodeKey(file, node)) ?? from;
105
- if (isBinaryExpression(node) && EQUALITIES.has(node.operatorToken.kind)) {
106
- comparisons.push({ file, from: holder, keyed: node.left, other: node.right });
107
- comparisons.push({ file, from: holder, keyed: node.right, other: node.left });
108
- }
109
- if (isSwitchStatement(node)) {
110
- for (const clause of node.caseBlock.clauses) {
111
- if (isCaseClause(clause)) {
112
- comparisons.push({
113
- file,
114
- from: holder,
115
- keyed: node.expression,
116
- other: clause.expression,
89
+ export const keyofComparisons: ReadRule = {
90
+ start() {
91
+ const comparisons: Comparison[] = [];
92
+ return {
93
+ visit(node, { file, from }) {
94
+ if (isBinaryExpression(node) && EQUALITIES.has(node.operatorToken.kind)) {
95
+ comparisons.push({ file, from, keyed: node.left, other: node.right });
96
+ comparisons.push({ file, from, keyed: node.right, other: node.left });
97
+ }
98
+ if (isSwitchStatement(node)) {
99
+ for (const clause of node.caseBlock.clauses) {
100
+ if (isCaseClause(clause)) {
101
+ comparisons.push({ file, from, keyed: node.expression, other: clause.expression });
102
+ }
103
+ }
104
+ }
105
+ },
106
+ reads({ project, chains }) {
107
+ const queries = project.queries;
108
+ const found: Read[] = [];
109
+ const named = comparisons.flatMap((one) => {
110
+ const name = nameOf(one.keyed);
111
+ return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
112
+ ? []
113
+ : [{ ...one, name }];
114
+ });
115
+ if (named.length === 0) {
116
+ return found;
117
+ }
118
+ // The operand's own declaration states the key type, which the checker resolves to
119
+ // the literals it admits and so no longer names the type the key indexes.
120
+ const symbols = queries.symbolsAt(named.map((one) => one.name));
121
+ const keyed = named.flatMap((one, at) => {
122
+ const asked = symbols[at];
123
+ const symbol =
124
+ asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
125
+ ? asked
126
+ : queries.aliased(asked);
127
+ if (symbol === UNANSWERED) {
128
+ const text = literalText(one.other);
129
+ found.push({
130
+ kind: "held",
131
+ from: one.from,
132
+ ...(text === undefined ? {} : { names: [text] }),
117
133
  });
134
+ return [];
135
+ }
136
+ if (symbol === undefined) {
137
+ return [];
118
138
  }
139
+ const operands = symbol.declarations.flatMap((handle) => {
140
+ const declared = (
141
+ project.declarationAt(handle)?.node as { readonly type?: TypeNode } | undefined
142
+ )?.type;
143
+ return declared === undefined ? [] : keyofOperands(declared);
144
+ });
145
+ return operands.length === 0 ? [] : [{ ...one, operands }];
146
+ });
147
+ if (keyed.length === 0) {
148
+ return found;
119
149
  }
120
- }
121
- node.forEachChild((child) => {
122
- visit(child, holder);
123
- });
124
- };
125
- file.forEachChild((child) => {
126
- visit(child, fileId);
127
- });
128
- }
129
- const named = comparisons.flatMap((one) => {
130
- const name = nameOf(one.keyed);
131
- return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
132
- ? []
133
- : [{ ...one, name }];
134
- });
135
- if (named.length === 0) {
136
- return [];
137
- }
138
-
139
- // The operand's own declaration states the key type, which the checker resolves to
140
- // the literals it admits and so no longer names the type the key indexes.
141
- const symbols = queries.symbolsAt(named.map((one) => one.name));
142
- const keyed = named.flatMap((one, at) => {
143
- const asked = symbols[at];
144
- const symbol =
145
- asked === UNANSWERED || asked === undefined || (asked.flags & SymbolFlags.Alias) === 0
146
- ? asked
147
- : queries.aliased(asked);
148
- if (symbol === UNANSWERED || symbol === undefined) {
149
- return [];
150
- }
151
- const operands = symbol.declarations.flatMap((handle) => {
152
- const declared = (
153
- project.declarationAt(handle)?.node as { readonly type?: TypeNode } | undefined
154
- )?.type;
155
- return declared === undefined ? [] : keyofOperands(declared);
156
- });
157
- return operands.length === 0 ? [] : [{ ...one, operands }];
158
- });
159
- if (keyed.length === 0) {
160
- return [];
161
- }
162
- const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
163
- const types = queries.typesAt(nodes);
164
- const typeOf = new Map<Node, Type>();
165
- nodes.forEach((node, at) => {
166
- const type = types[at];
167
- if (type !== UNANSWERED && type !== undefined) {
168
- typeOf.set(node, type);
169
- }
170
- });
171
- const partsOf = (type: Type): readonly Type[] => {
172
- const parts = queries.constituents(type);
173
- return parts === UNANSWERED ? [type] : parts;
174
- };
175
-
176
- const found: Reference[] = [];
177
- for (const one of keyed) {
178
- const literal = typeOf.get(one.other);
179
- const names = literal === undefined ? [] : literalNames(partsOf(literal));
180
- if (names.length === 0) {
181
- continue;
182
- }
183
- const position = renderPosition(one.file, root, one.other.getStart());
184
- for (const operand of one.operands) {
185
- const object = typeOf.get(operand);
186
- for (const part of object === undefined ? [] : partsOf(object)) {
187
- const properties = queries.propertiesOf(part);
188
- for (const property of properties === UNANSWERED ? [] : properties) {
189
- if (!names.includes(property.name)) {
190
- continue;
150
+ const nodes = keyed.flatMap((one) => [one.other, ...one.operands]);
151
+ const types = queries.typesAt(nodes);
152
+ const typeOf = new Map<Node, Type | typeof UNANSWERED>();
153
+ nodes.forEach((node, at) => {
154
+ const type = types[at];
155
+ if (type !== undefined) {
156
+ typeOf.set(node, type);
191
157
  }
192
- for (const to of chains.declarationsOf(property)) {
158
+ });
159
+ const partsOf = (type: Type): readonly Type[] => {
160
+ const parts = queries.constituents(type);
161
+ return parts === UNANSWERED ? [type] : parts;
162
+ };
163
+ for (const one of keyed) {
164
+ const literal = typeOf.get(one.other);
165
+ const objects = one.operands.map((operand) => typeOf.get(operand));
166
+ if (literal === UNANSWERED) {
167
+ const known = objects.filter((type) => type !== undefined && type !== UNANSWERED);
193
168
  found.push({
169
+ kind: "held",
194
170
  from: one.from,
195
- to,
196
- position,
197
- use: "read",
198
- resolution: "keyed",
199
- test: testFiles.has(position.path),
171
+ types: known.flatMap(partsOf),
172
+ ...(known.length < objects.length ? { members: true as const } : {}),
200
173
  });
174
+ continue;
175
+ }
176
+ const names = literal === undefined ? [] : literalNames(partsOf(literal));
177
+ if (names.length === 0) {
178
+ continue;
179
+ }
180
+ for (const object of objects) {
181
+ if (object === UNANSWERED) {
182
+ found.push({ kind: "held", from: one.from, names });
183
+ continue;
184
+ }
185
+ for (const part of object === undefined ? [] : partsOf(object)) {
186
+ const properties = queries.propertiesOf(part);
187
+ if (properties === UNANSWERED) {
188
+ found.push({ kind: "held", from: one.from, names });
189
+ continue;
190
+ }
191
+ for (const property of properties) {
192
+ if (!names.includes(property.name)) {
193
+ continue;
194
+ }
195
+ for (const to of chains.declarationsOf(property)) {
196
+ found.push({
197
+ kind: "named",
198
+ from: one.from,
199
+ to,
200
+ file: one.file,
201
+ at: one.other,
202
+ use: "read",
203
+ via: "keyed",
204
+ });
205
+ }
206
+ }
207
+ }
201
208
  }
202
209
  }
203
- }
204
- }
205
- }
206
- return found;
210
+ return found;
211
+ },
212
+ };
213
+ },
214
+ };
215
+
216
+ /** The text a string or number literal operand spells, where it is one. */
217
+ function literalText(node: Expression): string | undefined {
218
+ const at = parenthesesRemoved(node);
219
+ return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at)
220
+ ? at.text
221
+ : undefined;
207
222
  }