@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
@@ -4,11 +4,15 @@
4
4
  * call sites that use or discard it, each statement control flow cannot reach, each
5
5
  * write to a local that no read reaches, and each case an earlier case covers.
6
6
  */
7
- import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
7
+ import { isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isForInStatement, isForOfStatement, isFunctionLikeDeclaration, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAssignment, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableDeclarationList, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
+ import { SignatureKind } from "@typescript/native/sync";
9
+ import { exportsLocally, implicitReads, importBindings, } from "./import-bindings.js";
8
10
  import { isParameterProperty, nodeKey } from "./inventory.js";
9
11
  import { positionKey, renderPosition } from "./position.js";
12
+ import { importRef } from "./ref.js";
10
13
  import { isAnswered, UNANSWERED } from "./query.js";
11
14
  import { signatureOf } from "./signatures.js";
15
+ import { spellsNoBinding } from "./spelled-names.js";
12
16
  /** The codes of the parts this module reads. */
13
17
  export const UNUSED_PARAMETER = "DS1801";
14
18
  export const UNUSED_RESULT = "DS1803";
@@ -117,13 +121,12 @@ function caseKey(expression) {
117
121
  }
118
122
  return undefined;
119
123
  }
120
- /** Every identifier below one node whose text one of the names spells, a member's name aside. */
124
+ /** Every identifier below one node whose text one of the names spells as a reference. */
121
125
  function identifiersNaming(node, names) {
122
126
  const found = [];
123
127
  const visit = (at) => {
124
128
  if (isIdentifier(at)) {
125
- const parent = at.parent;
126
- if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
129
+ if (names.has(at.text) && !spellsNoBinding(at)) {
127
130
  found.push(at);
128
131
  }
129
132
  return;
@@ -154,6 +157,15 @@ function inNestedFunction(node, outer) {
154
157
  }
155
158
  return false;
156
159
  }
160
+ /** The nearest function around one node whose `arguments` object it names: an arrow has none. */
161
+ function ownArgumentsOf(node) {
162
+ for (let at = node.parent; !isSourceFile(at); at = at.parent) {
163
+ if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
164
+ return at;
165
+ }
166
+ }
167
+ return undefined;
168
+ }
157
169
  /** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
158
170
  function isStub(signature) {
159
171
  const body = signature.body;
@@ -295,6 +307,33 @@ function readsBinding(use, declared, ids) {
295
307
  const own = ids.get(declared);
296
308
  return at === undefined || at === UNANSWERED || own === UNANSWERED || at === own;
297
309
  }
310
+ /** Whether a name is spelled with a leading underscore, which declares it unread. */
311
+ function declaredUnread(name) {
312
+ return name.text.startsWith("_");
313
+ }
314
+ /** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
315
+ const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
316
+ /** A comment of the leading comments, a line comment's text after its slashes captured. */
317
+ const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
318
+ /**
319
+ * Whether a file's leading comments hold one pragma: as the compiler reads it, a line
320
+ * comment that starts with the pragma's name, in any case, followed by nothing or a space.
321
+ */
322
+ function hasPragma(leading, pragma) {
323
+ return [...leading.matchAll(LEADING_COMMENT)].some(([, text]) => text !== undefined && pragma.test(text));
324
+ }
325
+ /**
326
+ * Whether the compiler checks a file, so its rules report what they find there: a
327
+ * JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
328
+ * `@ts-nocheck` pragma.
329
+ */
330
+ function checkedByTheCompiler(file, checkJs) {
331
+ const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
332
+ if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
333
+ return false;
334
+ }
335
+ return (!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu));
336
+ }
298
337
  /**
299
338
  * The parts of one project's own declarations. `references` are the project's own
300
339
  * references, which say which declarations a discarded call reaches, so the checker is
@@ -310,7 +349,12 @@ export function projectParts(project, held, references, targetRoot) {
310
349
  const constructions = new Set();
311
350
  const sites = [];
312
351
  const contextual = [];
352
+ const imports = [];
353
+ const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
354
+ const options = project.program.getCompilerOptions();
355
+ const jsx = options;
313
356
  for (const file of files) {
357
+ const checked = checkedByTheCompiler(file, options.checkJs === true);
314
358
  // A parameter property's own parts stay with the parameter when its member goes.
315
359
  const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
316
360
  // A part no declaration holds belongs to the file, which is judged while the file is live.
@@ -329,6 +373,7 @@ export function projectParts(project, held, references, targetRoot) {
329
373
  values: [],
330
374
  catches: [],
331
375
  blocks: [],
376
+ loops: [],
332
377
  unreachable: [],
333
378
  cases: [],
334
379
  };
@@ -424,6 +469,15 @@ export function projectParts(project, held, references, targetRoot) {
424
469
  if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
425
470
  walk.blocks.push(node);
426
471
  }
472
+ const loopHolder = (isForOfStatement(node) || isForInStatement(node)) && silenced === 0
473
+ ? holderOf(node)
474
+ : undefined;
475
+ if (loopHolder !== undefined &&
476
+ (isForOfStatement(node) || isForInStatement(node)) &&
477
+ isVariableDeclarationList(node.initializer) &&
478
+ (node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0) {
479
+ walk.loops.push({ loop: node, holder: loopHolder });
480
+ }
427
481
  if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
428
482
  statementsOf(node.statements);
429
483
  return;
@@ -453,7 +507,37 @@ export function projectParts(project, held, references, targetRoot) {
453
507
  for (const store of stores) {
454
508
  asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
455
509
  }
510
+ const loopVariables = walk.loops.map(({ loop, holder }) => {
511
+ const list = loop.initializer;
512
+ // A loop must bind a name, so one spelled declared unread is no subject.
513
+ const names = list.declarations
514
+ .flatMap((declaration) => boundNames(declaration.name))
515
+ .filter((name) => !declaredUnread(name));
516
+ const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
517
+ asked.push(...names, ...uses);
518
+ return { names, uses, holder };
519
+ });
520
+ const bindings = fileId === undefined ? [] : importBindings(file);
521
+ const bound = new Set(bindings);
522
+ const bindingUses = bindings.length === 0
523
+ ? []
524
+ : identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter((use) => !bound.has(use));
525
+ asked.push(...bindings, ...bindingUses);
456
526
  const ids = resolvedIds(project, [...new Set(asked)]);
527
+ const implicit = bindings.length === 0 ? new Set() : implicitReads(file, jsx);
528
+ for (const binding of bindings) {
529
+ const fileRef = fileId === undefined ? undefined : refs.get(fileId);
530
+ if (fileId === undefined || fileRef === undefined) {
531
+ continue;
532
+ }
533
+ imports.push({
534
+ ref: importRef(fileRef, binding.text),
535
+ name: binding.text,
536
+ position: spanOf(file, targetRoot, binding),
537
+ used: implicit.has(binding.text) ||
538
+ bindingUses.some((use) => use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids))),
539
+ });
540
+ }
457
541
  for (const one of walk.declared) {
458
542
  const constructor = isConstructorDeclaration(one.node);
459
543
  if (!constructor) {
@@ -467,7 +551,10 @@ export function projectParts(project, held, references, targetRoot) {
467
551
  });
468
552
  const reader = readers.get(one.signature);
469
553
  if (reader !== undefined) {
470
- const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, false);
554
+ const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
555
+ inValue: false,
556
+ checked,
557
+ });
471
558
  parts.push(...found.parts);
472
559
  contextual.push(...found.contextual);
473
560
  }
@@ -475,7 +562,10 @@ export function projectParts(project, held, references, targetRoot) {
475
562
  for (const one of walk.values) {
476
563
  const reader = readers.get(one.signature);
477
564
  if (reader !== undefined) {
478
- const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, true);
565
+ const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
566
+ inValue: true,
567
+ checked,
568
+ });
479
569
  parts.push(...found.parts);
480
570
  contextual.push(...found.contextual);
481
571
  }
@@ -491,6 +581,19 @@ export function projectParts(project, held, references, targetRoot) {
491
581
  used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
492
582
  });
493
583
  });
584
+ for (const { names, uses, holder } of loopVariables) {
585
+ for (const name of names) {
586
+ parts.push({
587
+ code: DEAD_STORE,
588
+ declaration: holder,
589
+ kind: "store",
590
+ name: name.text,
591
+ position: spanOf(file, targetRoot, name),
592
+ message: `value written to ${name.text} is never read`,
593
+ used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
594
+ });
595
+ }
596
+ }
494
597
  walk.blocks.forEach((block, at) => {
495
598
  const holder = holderOf(block);
496
599
  if (holder !== undefined) {
@@ -523,6 +626,7 @@ export function projectParts(project, held, references, targetRoot) {
523
626
  parts.push(...resultParts(project, targetRoot, sites, references, calls));
524
627
  return {
525
628
  parts: withContextualRead(project, parts, contextual),
629
+ imports,
526
630
  signatures,
527
631
  calls,
528
632
  specifiers,
@@ -549,7 +653,9 @@ function valueSignature(node) {
549
653
  /**
550
654
  * Every name one function's parameters bind, a binding pattern's names one by one. A
551
655
  * `this` parameter receives no argument, a decorated one is read by its decorator, and a
552
- * parameter property is read by the write to its member, so none binds a subject.
656
+ * parameter property is read by the write to its member, so none binds a subject. A name
657
+ * spelled with a leading underscore is declared unread, so it is listed for its reads and
658
+ * reported never.
553
659
  */
554
660
  function parameterNames(signature) {
555
661
  const found = [];
@@ -561,7 +667,7 @@ function parameterNames(signature) {
561
667
  continue;
562
668
  }
563
669
  if (isIdentifier(element.name)) {
564
- const exempt = rest && element.dotDotDotToken === undefined;
670
+ const exempt = (rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
565
671
  found.push({ name: element.name, at, pattern: true, exempt });
566
672
  }
567
673
  else {
@@ -577,7 +683,7 @@ function parameterNames(signature) {
577
683
  }
578
684
  if (isIdentifier(name)) {
579
685
  if (name.text !== "this") {
580
- found.push({ name, at, pattern: false, exempt: false });
686
+ found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
581
687
  }
582
688
  return;
583
689
  }
@@ -585,11 +691,91 @@ function parameterNames(signature) {
585
691
  });
586
692
  return found;
587
693
  }
694
+ /** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
695
+ const TYPE_PARAMETER_DEPTH = 3;
696
+ /** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
697
+ function namesTypeParameter(project, type, depth = 0) {
698
+ if (type.isTypeParameter()) {
699
+ return true;
700
+ }
701
+ if (depth >= TYPE_PARAMETER_DEPTH) {
702
+ return false;
703
+ }
704
+ const { queries } = project;
705
+ const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
706
+ const args = queries.typeArguments(type);
707
+ const signatures = queries.signaturesOf(type, SignatureKind.Call);
708
+ if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
709
+ return true;
710
+ }
711
+ const inner = [
712
+ ...parts,
713
+ ...args,
714
+ ...signatures.flatMap((signature) => signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at))),
715
+ ];
716
+ return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
717
+ }
718
+ /**
719
+ * Whether the call a function expression is written in may read the annotation of its
720
+ * parameter at `at`: the callee is generic, and its parameter type at the function's place
721
+ * reads a type parameter there. A function written inside an object literal a generic
722
+ * call is passed is read so too, and a question the checker leaves unanswered reads it.
723
+ */
724
+ function calleeSteers(project, signature, at) {
725
+ const { queries } = project;
726
+ let value = signature;
727
+ while (isParenthesizedExpression(value.parent)) {
728
+ value = value.parent;
729
+ }
730
+ let nested = false;
731
+ while ((isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
732
+ isObjectLiteralExpression(value.parent)) {
733
+ value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
734
+ nested = true;
735
+ }
736
+ const call = value.parent;
737
+ if (!isCallExpression(call) && !isNewExpression(call)) {
738
+ return false;
739
+ }
740
+ const argument = (call.arguments ?? []).findIndex((one) => one === value);
741
+ const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
742
+ const generic = resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
743
+ if (argument < 0 || generic === undefined) {
744
+ return false;
745
+ }
746
+ if (!isAnswered(generic) || nested) {
747
+ return true;
748
+ }
749
+ const position = queries.typeAtPosition(generic, argument);
750
+ if (!isAnswered(position) || position.isTypeParameter()) {
751
+ return true;
752
+ }
753
+ const parts = position.isUnionType() || position.isIntersectionType()
754
+ ? queries.constituents(position)
755
+ : [position];
756
+ if (!isAnswered(parts)) {
757
+ return true;
758
+ }
759
+ return parts.some((part) => {
760
+ if (part.isTypeParameter()) {
761
+ return true;
762
+ }
763
+ const calls = queries.signaturesOf(part, SignatureKind.Call);
764
+ if (!isAnswered(calls)) {
765
+ return true;
766
+ }
767
+ return calls.some((one) => {
768
+ const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
769
+ return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
770
+ });
771
+ });
772
+ }
588
773
  /**
589
- * The parts with each name of `contextual` read where the compiler gives its function a
590
- * contextual type: the inference of the call holding the function may read a parameter's
591
- * annotation, and the code handing the argument may act on what an object pattern reads.
592
- * A question the checker leaves unanswered keeps the name read.
774
+ * The parts with each name of `contextual` judged where the compiler gives its function a
775
+ * contextual type: the code handing the argument may act on what an object pattern reads,
776
+ * so each such name is read, and an annotated parameter is marked with whether the call's
777
+ * inference may read its annotation. A question the checker leaves unanswered keeps the
778
+ * name read.
593
779
  */
594
780
  function withContextualRead(project, parts, contextual) {
595
781
  if (contextual.length === 0) {
@@ -597,9 +783,25 @@ function withContextualRead(project, parts, contextual) {
597
783
  }
598
784
  const signatures = [...new Set(contextual.map((one) => one.signature))];
599
785
  const answers = project.queries.contextualTypes(signatures);
600
- const typed = new Set(signatures.filter((_signature, at) => answers[at] !== undefined));
601
- const read = new Set(contextual.filter((one) => typed.has(one.signature)).map((one) => one.part));
602
- return parts.map((part) => (read.has(part) ? { ...part, used: true } : part));
786
+ const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
787
+ const judged = new Map();
788
+ for (const one of contextual) {
789
+ const answer = typedBy.get(one.signature);
790
+ if (answer === undefined) {
791
+ continue;
792
+ }
793
+ const place = one.part.parameter;
794
+ judged.set(one.part, one.pattern || answer === UNANSWERED || place === undefined
795
+ ? { ...one.part, used: true }
796
+ : {
797
+ ...one.part,
798
+ parameter: {
799
+ ...place,
800
+ annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
801
+ },
802
+ });
803
+ }
804
+ return parts.map((part) => judged.get(part) ?? part);
603
805
  }
604
806
  /**
605
807
  * Each name one function's parameters bind and whether the function reads it, and the
@@ -607,9 +809,11 @@ function withContextualRead(project, parts, contextual) {
607
809
  * that carries a type annotation or is an object binding pattern. A binding pattern counts
608
810
  * as read where its function reads any name it binds.
609
811
  */
610
- function parameterParts(file, root, declaration, signature, reader, ids, inValue) {
611
- // A function that reads `arguments` reads every parameter by position.
612
- const readsArguments = !isArrowFunction(signature) && identifiersNaming(signature, new Set(["arguments"])).length > 0;
812
+ function parameterParts(file, root, declaration, signature, reader, ids, place) {
813
+ // A function that reads its `arguments` reads every parameter by position. A nested
814
+ // function other than an arrow has an `arguments` object of its own.
815
+ const readsArguments = !isArrowFunction(signature) &&
816
+ identifiersNaming(signature, new Set(["arguments"])).some((use) => ownArgumentsOf(use) === signature);
613
817
  const read = reader.names.map(({ name }) => readsArguments ||
614
818
  reader.uses.some((use) => use.text === name.text && readsBinding(use, name, ids)));
615
819
  const lastRead = Math.max(-1, ...reader.names.filter((_, at) => read[at]).map((one) => one.at));
@@ -628,7 +832,7 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
628
832
  position: spanOf(file, root, name),
629
833
  message: `parameter ${name.text} is never read in the body`,
630
834
  used: read[index] === true,
631
- parameter: { beforeARead: !pattern && at < lastRead, inValue },
835
+ parameter: { beforeARead: !pattern && at < lastRead, ...place },
632
836
  };
633
837
  found.push(part);
634
838
  const parameter = signature.parameters[at];
@@ -636,42 +840,79 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
636
840
  !part.used &&
637
841
  parameter !== undefined &&
638
842
  (parameter.type !== undefined || isObjectBindingPattern(parameter.name))) {
639
- contextual.push({ part, signature: expression });
843
+ contextual.push({
844
+ part,
845
+ signature: expression,
846
+ at,
847
+ pattern: isObjectBindingPattern(parameter.name),
848
+ });
640
849
  }
641
850
  });
642
851
  return { parts: found, contextual };
643
852
  }
644
853
  /**
645
854
  * The names one declaration binds that a write of its initializer stores each on its own:
646
- * the declared name, or each element name of an array binding pattern at any depth.
855
+ * the declared name, or each element name of a binding pattern at any depth. A name with a
856
+ * default is left out, because removing it skips the default's evaluation, and so is a name
857
+ * beside a rest element of its object pattern, whose removal changes what the rest holds.
647
858
  */
648
- function arrayPatternNames(name) {
859
+ function boundNames(name) {
649
860
  if (isIdentifier(name)) {
650
861
  return [name];
651
862
  }
652
- if (!isArrayBindingPattern(name)) {
653
- return [];
863
+ const rest = isObjectBindingPattern(name) &&
864
+ name.elements.some((element) => element.dotDotDotToken !== undefined);
865
+ return name.elements.flatMap((element) => {
866
+ if (!isBindingElement(element) || element.name === undefined) {
867
+ return [];
868
+ }
869
+ if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
870
+ return [];
871
+ }
872
+ return boundNames(element.name);
873
+ });
874
+ }
875
+ /** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
876
+ function functionOf(block) {
877
+ for (let at = block.parent;; at = at.parent) {
878
+ if (isFunctionLikeDeclaration(at)) {
879
+ return at;
880
+ }
881
+ if (isSourceFile(at)) {
882
+ return undefined;
883
+ }
654
884
  }
655
- return name.elements.flatMap((element) => isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : []);
656
885
  }
657
- /** The locals one block declares with `let` or `const`, each with the names that spell it. */
886
+ /**
887
+ * The locals one block declares with `let`, `const` or `var`, each with the names of the
888
+ * block that spell it. A `var` of a nested block is one where no nested function of its
889
+ * function names it, since the function scope holds it past the block's end.
890
+ */
658
891
  function localsOf(block) {
659
892
  const declared = [];
893
+ const scope = functionOf(block);
894
+ const outlives = scope?.body !== block;
660
895
  block.statements.forEach((statement, at) => {
661
896
  if (!isVariableStatement(statement)) {
662
897
  return;
663
898
  }
664
899
  const list = statement.declarationList;
665
- if ((list.flags & (NodeFlags.Let | NodeFlags.Const)) === 0 ||
666
- (list.flags & NodeFlags.Using) !== 0) {
900
+ const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
901
+ if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
667
902
  return;
668
903
  }
669
904
  for (const declaration of list.declarations) {
670
- for (const name of arrayPatternNames(declaration.name)) {
905
+ for (const name of boundNames(declaration.name)) {
906
+ if (!blockScoped &&
907
+ outlives &&
908
+ identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) => inNestedFunction(use, scope ?? block))) {
909
+ continue;
910
+ }
671
911
  declared.push({
672
912
  name,
673
913
  statement: at,
674
914
  initialized: declaration.initializer !== undefined,
915
+ outlives: !blockScoped && outlives,
675
916
  });
676
917
  }
677
918
  }
@@ -685,19 +926,39 @@ function localsOf(block) {
685
926
  uses: spelled.filter((use) => use !== one.name && use.text === one.name.text),
686
927
  }));
687
928
  }
929
+ /** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
930
+ function isPlainWrite(use) {
931
+ const parent = outerParent(use);
932
+ return (isBinaryExpression(parent) &&
933
+ parent.operatorToken.kind === SyntaxKind.EqualsToken &&
934
+ unwrapped(parent.left) === use);
935
+ }
688
936
  /**
689
937
  * The writes to the locals of one block that no read reaches. A write is a declaration's
690
- * initializer or a plain assignment written as a statement of the block itself; it is
691
- * dead when the statements after it in the block read the local nowhere before the next
692
- * such write to it, or before the block ends, which ends the local's scope. A statement
693
- * that writes the local inside a nested construct ends the search with the write kept,
694
- * and a local a nested function names is no subject, because the function may read it
695
- * at any time.
938
+ * initializer or a plain assignment written as a statement of the block; it is dead when
939
+ * the block's later statements read the local nowhere before the next such write or the
940
+ * block's end. A statement writing it inside a nested construct ends the search with the
941
+ * write kept. A local a nested function reads is no subject, since the function may run at
942
+ * any time, and every write to a local nothing reads is dead, a nested function's included.
696
943
  */
697
944
  function deadStores(file, root, block, holder, locals, ids) {
698
945
  const found = [];
946
+ const deadAt = (local, target) => ({
947
+ code: DEAD_STORE,
948
+ declaration: holder,
949
+ kind: "store",
950
+ name: local.name.text,
951
+ position: spanOf(file, root, target),
952
+ message: `value written to ${local.name.text} is never read`,
953
+ used: false,
954
+ });
699
955
  for (const local of locals) {
700
956
  const uses = local.uses.filter((use) => readsBinding(use, local.name, ids));
957
+ if (!local.outlives && uses.every(isPlainWrite)) {
958
+ const targets = [...(local.initialized ? [local.name] : []), ...uses];
959
+ found.push(...targets.map((target) => deadAt(local, target)));
960
+ continue;
961
+ }
701
962
  if (uses.some((use) => inNestedFunction(use, block))) {
702
963
  continue;
703
964
  }
@@ -735,7 +996,7 @@ function deadStores(file, root, block, holder, locals, ids) {
735
996
  if (sameStatement.length > 0) {
736
997
  continue;
737
998
  }
738
- let dead = true;
999
+ let dead = !local.outlives;
739
1000
  for (let at = write.at + 1; at < statements.length; at += 1) {
740
1001
  const statement = statements[at];
741
1002
  if (statement === undefined) {
@@ -752,19 +1013,9 @@ function deadStores(file, root, block, holder, locals, ids) {
752
1013
  break;
753
1014
  }
754
1015
  }
755
- if (!dead) {
756
- continue;
1016
+ if (dead) {
1017
+ found.push(deadAt(local, write.target));
757
1018
  }
758
- const position = spanOf(file, root, write.target);
759
- found.push({
760
- code: DEAD_STORE,
761
- declaration: holder,
762
- kind: "store",
763
- name: local.name.text,
764
- position,
765
- message: `value written to ${local.name.text} is never read`,
766
- used: false,
767
- });
768
1019
  }
769
1020
  }
770
1021
  return found;
@@ -828,6 +1079,12 @@ export function intraFunctionFacts(projects, references, exempted) {
828
1079
  const known = parts.get(key);
829
1080
  parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
830
1081
  }
1082
+ const imports = new Map();
1083
+ for (const binding of projects.flatMap((one) => one.imports)) {
1084
+ const key = positionKey(binding.position);
1085
+ const known = imports.get(key);
1086
+ imports.set(key, known === undefined ? binding : { ...known, used: known.used || binding.used });
1087
+ }
831
1088
  const calls = new Map();
832
1089
  const specifiers = new Set();
833
1090
  for (const one of projects) {
@@ -874,5 +1131,11 @@ export function intraFunctionFacts(projects, references, exempted) {
874
1131
  const discardedEverywhere = new Set([...called]
875
1132
  .filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
876
1133
  .map(([id]) => id));
877
- return { parts: [...parts.values()], free, freeButValued, discardedEverywhere };
1134
+ return {
1135
+ parts: [...parts.values()],
1136
+ imports: [...imports.values()],
1137
+ free,
1138
+ freeButValued,
1139
+ discardedEverywhere,
1140
+ };
878
1141
  }