@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -5,8 +5,8 @@
5
5
  * write to a local that no read reaches, and each case an earlier case covers.
6
6
  */
7
7
 
8
+ import { CODE } from "./contract/contract.generated.ts";
8
9
  import {
9
- isArrayBindingPattern,
10
10
  isArrowFunction,
11
11
  isAwaitExpression,
12
12
  isBigIntLiteral,
@@ -29,6 +29,8 @@ import {
29
29
  isExpressionStatement,
30
30
  isFunctionDeclaration,
31
31
  isFunctionExpression,
32
+ isForInStatement,
33
+ isForOfStatement,
32
34
  isFunctionLikeDeclaration,
33
35
  isHeritageClause,
34
36
  isIdentifier,
@@ -46,6 +48,7 @@ import {
46
48
  isObjectLiteralExpression,
47
49
  isParenthesizedExpression,
48
50
  isPrefixUnaryExpression,
51
+ isPropertyAssignment,
49
52
  isPropertyAccessExpression,
50
53
  isQualifiedName,
51
54
  isReturnStatement,
@@ -56,6 +59,7 @@ import {
56
59
  isThrowStatement,
57
60
  isTypeAliasDeclaration,
58
61
  isTypeReferenceNode,
62
+ isVariableDeclarationList,
59
63
  isVariableStatement,
60
64
  isVoidExpression,
61
65
  NodeFlags,
@@ -66,20 +70,33 @@ import {
66
70
  type CallExpression,
67
71
  type CatchClause,
68
72
  type ConstructorDeclaration,
73
+ type ForInStatement,
74
+ type ForOfStatement,
75
+ type FunctionLikeDeclaration,
69
76
  type FunctionExpression,
70
77
  type Identifier,
71
78
  type Node,
72
79
  type SourceFile,
73
80
  type Statement,
81
+ type VariableDeclarationList,
74
82
  } from "@typescript/native/ast";
83
+ import { SignatureKind, type Type } from "@typescript/native/sync";
75
84
  import type { FindingPosition } from "./finding.ts";
85
+ import {
86
+ exportsLocally,
87
+ implicitReads,
88
+ importBindings,
89
+ type JsxOptions,
90
+ } from "./import-bindings.ts";
76
91
  import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
77
92
  import { positionKey, renderPosition } from "./position.ts";
93
+ import { importRef } from "./ref.ts";
78
94
  import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
79
95
  import type { Reference } from "./references.ts";
80
96
  import type { ProjectView } from "./session.ts";
81
97
  import { signatureOf, type SignatureNode } from "./signatures.ts";
82
98
  import { spellsNoBinding } from "./spelled-names.ts";
99
+ import { parenthesesRemoved } from "./unwrap.ts";
83
100
 
84
101
  /** The subject kind of each part this module reads. */
85
102
  type PartKind = "parameter" | "result" | "statement" | "store" | "case";
@@ -116,6 +133,20 @@ interface ParameterPlace {
116
133
  readonly beforeARead: boolean;
117
134
  /** Whether no declaration of the inventory declares the function, which is then a value. */
118
135
  readonly inValue: boolean;
136
+ /**
137
+ * Where the parameter carries an annotation and the compiler gives its function a
138
+ * contextual type: whether a type parameter of the callee reads the parameter's type,
139
+ * so the annotation may steer the call's inference.
140
+ */
141
+ readonly annotated?: "steers" | "free";
142
+ /**
143
+ * Whether the name is one an object binding pattern of a parameter binds, and the
144
+ * compiler gives its function a contextual type: the code handing the argument may act on
145
+ * the pattern's read of the property, which the program does not show.
146
+ */
147
+ readonly contextualPattern?: true;
148
+ /** Whether the compiler checks the parameter's file, so its unused-parameter rule may report it. */
149
+ readonly checked: boolean;
119
150
  }
120
151
 
121
152
  /** What one declaration's signature answers to beside its body. */
@@ -129,9 +160,18 @@ interface SignatureFact {
129
160
  readonly constructor: boolean;
130
161
  }
131
162
 
163
+ /** One name an import declaration binds, and whether its file reads it. */
164
+ interface ImportFact {
165
+ readonly ref: string;
166
+ readonly name: string;
167
+ readonly position: FindingPosition;
168
+ readonly used: boolean;
169
+ }
170
+
132
171
  /** What one project answers about the parts of its declarations. */
133
172
  export interface ProjectParts {
134
173
  readonly parts: readonly PartFact[];
174
+ readonly imports: readonly ImportFact[];
135
175
  readonly signatures: readonly SignatureFact[];
136
176
  /** Each name written as the callee of a call, by position, and what the call does with its value. */
137
177
  readonly calls: ReadonlyMap<string, CallUse>;
@@ -141,13 +181,6 @@ export interface ProjectParts {
141
181
  readonly constructions: ReadonlySet<string>;
142
182
  }
143
183
 
144
- /** The codes of the parts this module reads. */
145
- export const UNUSED_PARAMETER = "DS1801";
146
- export const UNUSED_RESULT = "DS1803";
147
- const UNREACHABLE_STATEMENT = "DS1805";
148
- const DEAD_STORE = "DS1807";
149
- const UNREACHABLE_CASE = "DS1809";
150
-
151
184
  /** The result types that carry no value, as the checker spells them. */
152
185
  const NO_VALUE: ReadonlySet<string> = new Set([
153
186
  "void",
@@ -165,11 +198,6 @@ function spanOf(file: SourceFile, root: string, node: Node): FindingPosition {
165
198
  return { ...start, endLine };
166
199
  }
167
200
 
168
- /** The parenthesized expression's content, through every pair of parentheses. */
169
- function unwrapped(node: Node): Node {
170
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
171
- }
172
-
173
201
  /** The node the parentheses around one expression sit in. */
174
202
  function outerParent(node: Node): Node {
175
203
  return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
@@ -177,7 +205,7 @@ function outerParent(node: Node): Node {
177
205
 
178
206
  /** The name a call is made through: the identifier, or the member a property access names. */
179
207
  function calleeName(call: CallExpression): Identifier | undefined {
180
- const callee = unwrapped(call.expression);
208
+ const callee = parenthesesRemoved(call.expression);
181
209
  if (isIdentifier(callee)) {
182
210
  return callee;
183
211
  }
@@ -244,7 +272,7 @@ function unreachableExempt(statement: Statement): boolean {
244
272
 
245
273
  /** The value a case expression is a literal of, as a key two equal literals share, or none. */
246
274
  function caseKey(expression: Node): string | undefined {
247
- const node = unwrapped(expression);
275
+ const node = parenthesesRemoved(expression);
248
276
  if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
249
277
  return `s:${node.text}`;
250
278
  }
@@ -370,7 +398,7 @@ function extendedNames(files: readonly SourceFile[]): ReadonlySet<string> {
370
398
  if (isTypeReferenceNode(type)) {
371
399
  named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
372
400
  } else {
373
- const expression = unwrapped(type.expression);
401
+ const expression = parenthesesRemoved(type.expression);
374
402
  named = isPropertyAccessExpression(expression) ? expression.name : expression;
375
403
  }
376
404
  if (isIdentifier(named)) {
@@ -423,7 +451,7 @@ function decoratorFixes(constructor: ConstructorDeclaration): boolean {
423
451
 
424
452
  /** The name an expression spells a class by: an identifier, or the member a property access names. */
425
453
  function spelledName(expression: Node): Identifier | undefined {
426
- const node = unwrapped(expression);
454
+ const node = parenthesesRemoved(expression);
427
455
  if (isIdentifier(node)) {
428
456
  return node;
429
457
  }
@@ -510,10 +538,49 @@ interface ParameterName {
510
538
  /** The parameter's index in its list. */
511
539
  readonly at: number;
512
540
  readonly pattern: boolean;
513
- /** A name beside a rest element of its object pattern: removing it changes what the rest holds. */
541
+ /**
542
+ * Whether the name is never reported: it is spelled declared unread, or it stands beside a
543
+ * rest element of its object pattern, whose removal changes what the rest holds.
544
+ */
514
545
  readonly exempt: boolean;
515
546
  }
516
547
 
548
+ /** Whether a name is spelled with a leading underscore, which declares it unread. */
549
+ function declaredUnread(name: Identifier): boolean {
550
+ return name.text.startsWith("_");
551
+ }
552
+
553
+ /** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
554
+ const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
555
+
556
+ /** A comment of the leading comments, a line comment's text after its slashes captured. */
557
+ const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
558
+
559
+ /**
560
+ * Whether a file's leading comments hold one pragma: as the compiler reads it, a line
561
+ * comment that starts with the pragma's name, in any case, followed by nothing or a space.
562
+ */
563
+ function hasPragma(leading: string, pragma: RegExp): boolean {
564
+ return [...leading.matchAll(LEADING_COMMENT)].some(
565
+ ([, text]) => text !== undefined && pragma.test(text),
566
+ );
567
+ }
568
+
569
+ /**
570
+ * Whether the compiler checks a file, so its rules report what they find there: a
571
+ * JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
572
+ * `@ts-nocheck` pragma.
573
+ */
574
+ function checkedByTheCompiler(file: SourceFile, checkJs: boolean): boolean {
575
+ const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
576
+ if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
577
+ return false;
578
+ }
579
+ return (
580
+ !/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu)
581
+ );
582
+ }
583
+
517
584
  /** What one file's walk collects before the batch that resolves its names. */
518
585
  interface FileWalk {
519
586
  readonly declared: Declared[];
@@ -524,6 +591,8 @@ interface FileWalk {
524
591
  readonly holder: string;
525
592
  }[];
526
593
  readonly blocks: Block[];
594
+ /** The loops whose `let` or `const` variables each iteration writes. */
595
+ readonly loops: { readonly loop: ForOfStatement | ForInStatement; readonly holder: string }[];
527
596
  readonly unreachable: { readonly statement: Statement; readonly holder: string }[];
528
597
  readonly cases: { readonly clause: Node; readonly text: string; readonly holder: string }[];
529
598
  }
@@ -548,8 +617,13 @@ export function projectParts<Brand>(
548
617
  const constructions = new Set<string>();
549
618
  const sites: { readonly file: SourceFile; readonly declared: Declared }[] = [];
550
619
  const contextual: ContextualParameter[] = [];
620
+ const imports: ImportFact[] = [];
621
+ const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
622
+ const options = project.program.getCompilerOptions();
623
+ const jsx = options as JsxOptions;
551
624
 
552
625
  for (const file of files) {
626
+ const checked = checkedByTheCompiler(file, options.checkJs === true);
553
627
  // A parameter property's own parts stay with the parameter when its member goes.
554
628
  const declarationAt = (node: Node): string | undefined =>
555
629
  isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
@@ -569,6 +643,7 @@ export function projectParts<Brand>(
569
643
  values: [],
570
644
  catches: [],
571
645
  blocks: [],
646
+ loops: [],
572
647
  unreachable: [],
573
648
  cases: [],
574
649
  };
@@ -645,8 +720,8 @@ export function projectParts<Brand>(
645
720
  if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
646
721
  specifiers.add(position(node.name));
647
722
  }
648
- if (isExportAssignment(node) && isIdentifier(unwrapped(node.expression))) {
649
- specifiers.add(position(unwrapped(node.expression)));
723
+ if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
724
+ specifiers.add(position(parenthesesRemoved(node.expression)));
650
725
  }
651
726
  if (isSwitchStatement(node)) {
652
727
  const holder = holderOf(node);
@@ -667,6 +742,18 @@ export function projectParts<Brand>(
667
742
  if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
668
743
  walk.blocks.push(node);
669
744
  }
745
+ const loopHolder =
746
+ (isForOfStatement(node) || isForInStatement(node)) && silenced === 0
747
+ ? holderOf(node)
748
+ : undefined;
749
+ if (
750
+ loopHolder !== undefined &&
751
+ (isForOfStatement(node) || isForInStatement(node)) &&
752
+ isVariableDeclarationList(node.initializer) &&
753
+ (node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0
754
+ ) {
755
+ walk.loops.push({ loop: node, holder: loopHolder });
756
+ }
670
757
  if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
671
758
  statementsOf(node.statements);
672
759
  return;
@@ -699,7 +786,44 @@ export function projectParts<Brand>(
699
786
  for (const store of stores) {
700
787
  asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
701
788
  }
789
+ const loopVariables = walk.loops.map(({ loop, holder }) => {
790
+ const list = loop.initializer as VariableDeclarationList;
791
+ // A loop must bind a name, so one spelled declared unread is no subject.
792
+ const names = list.declarations
793
+ .flatMap((declaration) => boundNames(declaration.name))
794
+ .filter((name) => !declaredUnread(name));
795
+ const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
796
+ asked.push(...names, ...uses);
797
+ return { names, uses, holder };
798
+ });
799
+ const bindings = fileId === undefined ? [] : importBindings(file);
800
+ const bound = new Set<Node>(bindings);
801
+ const bindingUses =
802
+ bindings.length === 0
803
+ ? []
804
+ : identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter(
805
+ (use) => !bound.has(use),
806
+ );
807
+ asked.push(...bindings, ...bindingUses);
702
808
  const ids = resolvedIds(project, [...new Set(asked)]);
809
+ const implicit = bindings.length === 0 ? new Set<string>() : implicitReads(file, jsx);
810
+ for (const binding of bindings) {
811
+ const fileRef = fileId === undefined ? undefined : refs.get(fileId);
812
+ if (fileId === undefined || fileRef === undefined) {
813
+ continue;
814
+ }
815
+ imports.push({
816
+ ref: importRef(fileRef, binding.text),
817
+ name: binding.text,
818
+ position: spanOf(file, targetRoot, binding),
819
+ used:
820
+ implicit.has(binding.text) ||
821
+ bindingUses.some(
822
+ (use) =>
823
+ use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids)),
824
+ ),
825
+ });
826
+ }
703
827
 
704
828
  for (const one of walk.declared) {
705
829
  const constructor = isConstructorDeclaration(one.node);
@@ -714,7 +838,10 @@ export function projectParts<Brand>(
714
838
  });
715
839
  const reader = readers.get(one.signature);
716
840
  if (reader !== undefined) {
717
- const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, false);
841
+ const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
842
+ inValue: false,
843
+ checked,
844
+ });
718
845
  parts.push(...found.parts);
719
846
  contextual.push(...found.contextual);
720
847
  }
@@ -722,22 +849,17 @@ export function projectParts<Brand>(
722
849
  for (const one of walk.values) {
723
850
  const reader = readers.get(one.signature);
724
851
  if (reader !== undefined) {
725
- const found = parameterParts(
726
- file,
727
- targetRoot,
728
- one.holder,
729
- one.signature,
730
- reader,
731
- ids,
732
- true,
733
- );
852
+ const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
853
+ inValue: true,
854
+ checked,
855
+ });
734
856
  parts.push(...found.parts);
735
857
  contextual.push(...found.contextual);
736
858
  }
737
859
  }
738
860
  walk.catches.forEach(({ name, holder }, at) => {
739
861
  parts.push({
740
- code: DEAD_STORE,
862
+ code: CODE.deadStore,
741
863
  declaration: holder,
742
864
  kind: "store",
743
865
  name: name.text,
@@ -746,6 +868,19 @@ export function projectParts<Brand>(
746
868
  used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
747
869
  });
748
870
  });
871
+ for (const { names, uses, holder } of loopVariables) {
872
+ for (const name of names) {
873
+ parts.push({
874
+ code: CODE.deadStore,
875
+ declaration: holder,
876
+ kind: "store",
877
+ name: name.text,
878
+ position: spanOf(file, targetRoot, name),
879
+ message: `value written to ${name.text} is never read`,
880
+ used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
881
+ });
882
+ }
883
+ }
749
884
  walk.blocks.forEach((block, at) => {
750
885
  const holder = holderOf(block);
751
886
  if (holder !== undefined) {
@@ -754,7 +889,7 @@ export function projectParts<Brand>(
754
889
  });
755
890
  for (const { statement, holder } of walk.unreachable) {
756
891
  parts.push({
757
- code: UNREACHABLE_STATEMENT,
892
+ code: CODE.unreachableStatement,
758
893
  declaration: holder,
759
894
  kind: "statement",
760
895
  name: "statement",
@@ -765,7 +900,7 @@ export function projectParts<Brand>(
765
900
  }
766
901
  for (const { clause, text, holder } of walk.cases) {
767
902
  parts.push({
768
- code: UNREACHABLE_CASE,
903
+ code: CODE.unreachableCase,
769
904
  declaration: holder,
770
905
  kind: "case",
771
906
  name: text,
@@ -779,6 +914,7 @@ export function projectParts<Brand>(
779
914
  parts.push(...resultParts(project, targetRoot, sites, references, calls));
780
915
  return {
781
916
  parts: withContextualRead(project, parts, contextual),
917
+ imports,
782
918
  signatures,
783
919
  calls,
784
920
  specifiers,
@@ -807,7 +943,9 @@ function valueSignature(node: Node): SignatureNode | undefined {
807
943
  /**
808
944
  * Every name one function's parameters bind, a binding pattern's names one by one. A
809
945
  * `this` parameter receives no argument, a decorated one is read by its decorator, and a
810
- * parameter property is read by the write to its member, so none binds a subject.
946
+ * parameter property is read by the write to its member, so none binds a subject. A name
947
+ * spelled with a leading underscore is declared unread, so it is listed for its reads and
948
+ * reported never.
811
949
  */
812
950
  function parameterNames(signature: SignatureNode): ParameterName[] {
813
951
  const found: ParameterName[] = [];
@@ -820,7 +958,8 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
820
958
  continue;
821
959
  }
822
960
  if (isIdentifier(element.name)) {
823
- const exempt = rest && element.dotDotDotToken === undefined;
961
+ const exempt =
962
+ (rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
824
963
  found.push({ name: element.name, at, pattern: true, exempt });
825
964
  } else {
826
965
  patternNames(element.name, at);
@@ -837,7 +976,7 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
837
976
  }
838
977
  if (isIdentifier(name)) {
839
978
  if (name.text !== "this") {
840
- found.push({ name, at, pattern: false, exempt: false });
979
+ found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
841
980
  }
842
981
  return;
843
982
  }
@@ -850,13 +989,109 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
850
989
  interface ContextualParameter {
851
990
  readonly part: PartFact;
852
991
  readonly signature: ArrowFunction | FunctionExpression;
992
+ /** The parameter's index in its list. */
993
+ readonly at: number;
994
+ /** Whether the parameter is an object binding pattern, rather than an annotated name. */
995
+ readonly pattern: boolean;
996
+ }
997
+
998
+ /** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
999
+ const TYPE_PARAMETER_DEPTH = 3;
1000
+
1001
+ /** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
1002
+ function namesTypeParameter<Brand>(project: ProjectView<Brand>, type: Type, depth = 0): boolean {
1003
+ if (type.isTypeParameter()) {
1004
+ return true;
1005
+ }
1006
+ if (depth >= TYPE_PARAMETER_DEPTH) {
1007
+ return false;
1008
+ }
1009
+ const { queries } = project;
1010
+ const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
1011
+ const args = queries.typeArguments(type);
1012
+ const signatures = queries.signaturesOf(type, SignatureKind.Call);
1013
+ if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
1014
+ return true;
1015
+ }
1016
+ const inner = [
1017
+ ...parts,
1018
+ ...args,
1019
+ ...signatures.flatMap((signature) =>
1020
+ signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at)),
1021
+ ),
1022
+ ];
1023
+ return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
853
1024
  }
854
1025
 
855
1026
  /**
856
- * The parts with each name of `contextual` read where the compiler gives its function a
857
- * contextual type: the inference of the call holding the function may read a parameter's
858
- * annotation, and the code handing the argument may act on what an object pattern reads.
859
- * A question the checker leaves unanswered keeps the name read.
1027
+ * Whether the call a function expression is written in may read the annotation of its
1028
+ * parameter at `at`: the callee is generic, and its parameter type at the function's place
1029
+ * reads a type parameter there. A function written inside an object literal a generic
1030
+ * call is passed is read so too, and a question the checker leaves unanswered reads it.
1031
+ */
1032
+ function calleeSteers<Brand>(
1033
+ project: ProjectView<Brand>,
1034
+ signature: ArrowFunction | FunctionExpression,
1035
+ at: number,
1036
+ ): boolean {
1037
+ const { queries } = project;
1038
+ let value: Node = signature;
1039
+ while (isParenthesizedExpression(value.parent)) {
1040
+ value = value.parent;
1041
+ }
1042
+ let nested = false;
1043
+ while (
1044
+ (isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
1045
+ isObjectLiteralExpression(value.parent)
1046
+ ) {
1047
+ value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
1048
+ nested = true;
1049
+ }
1050
+ const call = value.parent;
1051
+ if (!isCallExpression(call) && !isNewExpression(call)) {
1052
+ return false;
1053
+ }
1054
+ const argument = (call.arguments ?? []).findIndex((one) => one === value);
1055
+ const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
1056
+ const generic =
1057
+ resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
1058
+ if (argument < 0 || generic === undefined) {
1059
+ return false;
1060
+ }
1061
+ if (!isAnswered(generic) || nested) {
1062
+ return true;
1063
+ }
1064
+ const position = queries.typeAtPosition(generic, argument);
1065
+ if (!isAnswered(position) || position.isTypeParameter()) {
1066
+ return true;
1067
+ }
1068
+ const parts =
1069
+ position.isUnionType() || position.isIntersectionType()
1070
+ ? queries.constituents(position)
1071
+ : [position];
1072
+ if (!isAnswered(parts)) {
1073
+ return true;
1074
+ }
1075
+ return parts.some((part) => {
1076
+ if (part.isTypeParameter()) {
1077
+ return true;
1078
+ }
1079
+ const calls = queries.signaturesOf(part, SignatureKind.Call);
1080
+ if (!isAnswered(calls)) {
1081
+ return true;
1082
+ }
1083
+ return calls.some((one) => {
1084
+ const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
1085
+ return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
1086
+ });
1087
+ });
1088
+ }
1089
+
1090
+ /**
1091
+ * The parts with each name of `contextual` judged where the compiler gives its function a
1092
+ * contextual type: a name an object pattern binds is marked as one, and an annotated
1093
+ * parameter with whether the call's inference may read its annotation. A question the
1094
+ * checker leaves unanswered keeps the name read.
860
1095
  */
861
1096
  function withContextualRead<Brand>(
862
1097
  project: ProjectView<Brand>,
@@ -868,9 +1103,30 @@ function withContextualRead<Brand>(
868
1103
  }
869
1104
  const signatures = [...new Set(contextual.map((one) => one.signature))];
870
1105
  const answers = project.queries.contextualTypes(signatures);
871
- const typed = new Set(signatures.filter((_signature, at) => answers[at] !== undefined));
872
- const read = new Set(contextual.filter((one) => typed.has(one.signature)).map((one) => one.part));
873
- return parts.map((part) => (read.has(part) ? { ...part, used: true } : part));
1106
+ const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
1107
+ const judged = new Map<PartFact, PartFact>();
1108
+ for (const one of contextual) {
1109
+ const answer = typedBy.get(one.signature);
1110
+ if (answer === undefined) {
1111
+ continue;
1112
+ }
1113
+ const place = one.part.parameter;
1114
+ judged.set(
1115
+ one.part,
1116
+ answer === UNANSWERED || place === undefined
1117
+ ? { ...one.part, used: true }
1118
+ : one.pattern
1119
+ ? { ...one.part, parameter: { ...place, contextualPattern: true } }
1120
+ : {
1121
+ ...one.part,
1122
+ parameter: {
1123
+ ...place,
1124
+ annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
1125
+ },
1126
+ },
1127
+ );
1128
+ }
1129
+ return parts.map((part) => judged.get(part) ?? part);
874
1130
  }
875
1131
 
876
1132
  /**
@@ -886,7 +1142,7 @@ function parameterParts(
886
1142
  signature: SignatureNode,
887
1143
  reader: { readonly names: readonly ParameterName[]; readonly uses: readonly Identifier[] },
888
1144
  ids: ReadonlyMap<Node, Answer<number>>,
889
- inValue: boolean,
1145
+ place: Pick<ParameterPlace, "inValue" | "checked">,
890
1146
  ): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
891
1147
  // A function that reads its `arguments` reads every parameter by position. A nested
892
1148
  // function other than an arrow has an `arguments` object of its own.
@@ -910,14 +1166,14 @@ function parameterParts(
910
1166
  return;
911
1167
  }
912
1168
  const part: PartFact = {
913
- code: UNUSED_PARAMETER,
1169
+ code: CODE.unusedParameter,
914
1170
  declaration,
915
1171
  kind: "parameter",
916
1172
  name: name.text,
917
1173
  position: spanOf(file, root, name),
918
1174
  message: `parameter ${name.text} is never read in the body`,
919
1175
  used: read[index] === true,
920
- parameter: { beforeARead: !pattern && at < lastRead, inValue },
1176
+ parameter: { beforeARead: !pattern && at < lastRead, ...place },
921
1177
  };
922
1178
  found.push(part);
923
1179
  const parameter = signature.parameters[at];
@@ -927,7 +1183,12 @@ function parameterParts(
927
1183
  parameter !== undefined &&
928
1184
  (parameter.type !== undefined || isObjectBindingPattern(parameter.name))
929
1185
  ) {
930
- contextual.push({ part, signature: expression });
1186
+ contextual.push({
1187
+ part,
1188
+ signature: expression,
1189
+ at,
1190
+ pattern: isObjectBindingPattern(parameter.name),
1191
+ });
931
1192
  }
932
1193
  });
933
1194
  return { parts: found, contextual };
@@ -939,44 +1200,107 @@ interface Local {
939
1200
  readonly statement: number;
940
1201
  readonly initialized: boolean;
941
1202
  readonly uses: readonly Identifier[];
1203
+ /** A `var` of a nested block, whose function scope outlives the block's end. */
1204
+ readonly outlives: boolean;
1205
+ /** A function declaration, written at the start of the block whatever its place. */
1206
+ readonly hoisted: boolean;
942
1207
  }
943
1208
 
944
1209
  /**
945
1210
  * The names one declaration binds that a write of its initializer stores each on its own:
946
- * the declared name, or each element name of an array binding pattern at any depth.
1211
+ * the declared name, or each element name of a binding pattern at any depth. A name with a
1212
+ * default is left out, because removing it skips the default's evaluation, and so is a name
1213
+ * beside a rest element of its object pattern, whose removal changes what the rest holds.
947
1214
  */
948
- function arrayPatternNames(name: BindingName): readonly Identifier[] {
1215
+ function boundNames(name: BindingName): readonly Identifier[] {
949
1216
  if (isIdentifier(name)) {
950
1217
  return [name];
951
1218
  }
952
- if (!isArrayBindingPattern(name)) {
953
- return [];
1219
+ const rest =
1220
+ isObjectBindingPattern(name) &&
1221
+ name.elements.some((element) => element.dotDotDotToken !== undefined);
1222
+ return name.elements.flatMap((element) => {
1223
+ if (!isBindingElement(element) || element.name === undefined) {
1224
+ return [];
1225
+ }
1226
+ if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
1227
+ return [];
1228
+ }
1229
+ return boundNames(element.name);
1230
+ });
1231
+ }
1232
+
1233
+ /** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
1234
+ function functionOf(block: Block): FunctionLikeDeclaration | undefined {
1235
+ for (let at: Node = block.parent; ; at = at.parent) {
1236
+ if (isFunctionLikeDeclaration(at)) {
1237
+ return at;
1238
+ }
1239
+ if (isSourceFile(at)) {
1240
+ return undefined;
1241
+ }
954
1242
  }
955
- return name.elements.flatMap((element) =>
956
- isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : [],
957
- );
958
1243
  }
959
1244
 
960
- /** The locals one block declares with `let` or `const`, each with the names that spell it. */
1245
+ /**
1246
+ * The locals one block declares with `let`, `const`, `var` or a function declaration
1247
+ * inside a function, each with the names of the block that spell it. A `var` of a nested
1248
+ * block is one where no nested function of its function names it, since the function scope
1249
+ * holds it past the block's end.
1250
+ */
961
1251
  function localsOf(block: Block): readonly Local[] {
962
- const declared: { name: Identifier; statement: number; initialized: boolean }[] = [];
1252
+ const declared: {
1253
+ name: Identifier;
1254
+ statement: number;
1255
+ initialized: boolean;
1256
+ outlives: boolean;
1257
+ hoisted: boolean;
1258
+ }[] = [];
1259
+ const scope = functionOf(block);
1260
+ const outlives = scope?.body !== block;
963
1261
  block.statements.forEach((statement, at) => {
1262
+ if (isFunctionDeclaration(statement)) {
1263
+ if (scope !== undefined && statement.name !== undefined && statement.body !== undefined) {
1264
+ declared.push({
1265
+ name: statement.name,
1266
+ statement: at,
1267
+ initialized: true,
1268
+ outlives: false,
1269
+ hoisted: true,
1270
+ });
1271
+ }
1272
+ return;
1273
+ }
964
1274
  if (!isVariableStatement(statement)) {
965
1275
  return;
966
1276
  }
967
1277
  const list = statement.declarationList;
968
- if (
969
- (list.flags & (NodeFlags.Let | NodeFlags.Const)) === 0 ||
970
- (list.flags & NodeFlags.Using) !== 0
971
- ) {
1278
+ const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
1279
+ if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
972
1280
  return;
973
1281
  }
974
1282
  for (const declaration of list.declarations) {
975
- for (const name of arrayPatternNames(declaration.name)) {
1283
+ // A write to a local typed `never` is the compiler's exhaustiveness check, which
1284
+ // fails to compile once a case goes unhandled.
1285
+ if (declaration.type?.kind === SyntaxKind.NeverKeyword) {
1286
+ continue;
1287
+ }
1288
+ for (const name of boundNames(declaration.name)) {
1289
+ if (
1290
+ !blockScoped &&
1291
+ outlives &&
1292
+ identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) =>
1293
+ inNestedFunction(use, scope ?? block),
1294
+ )
1295
+ ) {
1296
+ continue;
1297
+ }
976
1298
  declared.push({
977
1299
  name,
978
1300
  statement: at,
979
1301
  initialized: declaration.initializer !== undefined,
1302
+ outlives: !blockScoped && outlives,
1303
+ hoisted: false,
980
1304
  });
981
1305
  }
982
1306
  }
@@ -991,14 +1315,23 @@ function localsOf(block: Block): readonly Local[] {
991
1315
  }));
992
1316
  }
993
1317
 
1318
+ /** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
1319
+ function isPlainWrite(use: Identifier): boolean {
1320
+ const parent = outerParent(use);
1321
+ return (
1322
+ isBinaryExpression(parent) &&
1323
+ parent.operatorToken.kind === SyntaxKind.EqualsToken &&
1324
+ parenthesesRemoved(parent.left) === use
1325
+ );
1326
+ }
1327
+
994
1328
  /**
995
1329
  * The writes to the locals of one block that no read reaches. A write is a declaration's
996
- * initializer or a plain assignment written as a statement of the block itself; it is
997
- * dead when the statements after it in the block read the local nowhere before the next
998
- * such write to it, or before the block ends, which ends the local's scope. A statement
999
- * that writes the local inside a nested construct ends the search with the write kept,
1000
- * and a local a nested function names is no subject, because the function may read it
1001
- * at any time.
1330
+ * initializer or a plain assignment written as a statement of the block; it is dead when
1331
+ * the block's later statements read the local nowhere before the next such write or the
1332
+ * block's end. A statement writing it inside a nested construct ends the search with the
1333
+ * write kept. A local a nested function reads is no subject, since the function may run at
1334
+ * any time, and every write to a local nothing reads is dead, a nested function's included.
1002
1335
  */
1003
1336
  function deadStores(
1004
1337
  file: SourceFile,
@@ -1009,8 +1342,32 @@ function deadStores(
1009
1342
  ids: ReadonlyMap<Node, Answer<number>>,
1010
1343
  ): PartFact[] {
1011
1344
  const found: PartFact[] = [];
1345
+ const deadAt = (local: Local, target: Identifier): PartFact => ({
1346
+ code: CODE.deadStore,
1347
+ declaration: holder,
1348
+ kind: "store",
1349
+ name: local.name.text,
1350
+ position: spanOf(file, root, target),
1351
+ message: `value written to ${local.name.text} is never read`,
1352
+ used: false,
1353
+ });
1012
1354
  for (const local of locals) {
1013
- const uses = local.uses.filter((use) => readsBinding(use, local.name, ids));
1355
+ const own = local.hoisted ? block.statements[local.statement] : undefined;
1356
+ // A function's reads of itself keep it no more than its absence would.
1357
+ const uses = local.uses.filter(
1358
+ (use) => readsBinding(use, local.name, ids) && (own === undefined || !within(use, own)),
1359
+ );
1360
+ if (!local.outlives && uses.every(isPlainWrite)) {
1361
+ // A declaration with no initializer writes `undefined`, the one store left to name.
1362
+ const writes = [...(local.initialized ? [local.name] : []), ...uses];
1363
+ const targets = writes.length === 0 ? [local.name] : writes;
1364
+ found.push(...targets.map((target) => deadAt(local, target)));
1365
+ continue;
1366
+ }
1367
+ // A read anywhere in the block reads a function declaration, which is written first.
1368
+ if (local.hoisted) {
1369
+ continue;
1370
+ }
1014
1371
  if (uses.some((use) => inNestedFunction(use, block))) {
1015
1372
  continue;
1016
1373
  }
@@ -1021,14 +1378,14 @@ function deadStores(
1021
1378
  if (!isExpressionStatement(statement)) {
1022
1379
  return undefined;
1023
1380
  }
1024
- const expression = unwrapped(statement.expression);
1381
+ const expression = parenthesesRemoved(statement.expression);
1025
1382
  if (
1026
1383
  !isBinaryExpression(expression) ||
1027
1384
  expression.operatorToken.kind !== SyntaxKind.EqualsToken
1028
1385
  ) {
1029
1386
  return undefined;
1030
1387
  }
1031
- const target = unwrapped(expression.left);
1388
+ const target = parenthesesRemoved(expression.left);
1032
1389
  return isIdentifier(target) && uses.includes(target)
1033
1390
  ? { target, value: expression.right }
1034
1391
  : undefined;
@@ -1050,7 +1407,7 @@ function deadStores(
1050
1407
  if (sameStatement.length > 0) {
1051
1408
  continue;
1052
1409
  }
1053
- let dead = true;
1410
+ let dead = !local.outlives;
1054
1411
  for (let at = write.at + 1; at < statements.length; at += 1) {
1055
1412
  const statement = statements[at];
1056
1413
  if (statement === undefined) {
@@ -1067,19 +1424,9 @@ function deadStores(
1067
1424
  break;
1068
1425
  }
1069
1426
  }
1070
- if (!dead) {
1071
- continue;
1427
+ if (dead) {
1428
+ found.push(deadAt(local, write.target));
1072
1429
  }
1073
- const position = spanOf(file, root, write.target);
1074
- found.push({
1075
- code: DEAD_STORE,
1076
- declaration: holder,
1077
- kind: "store",
1078
- name: local.name.text,
1079
- position,
1080
- message: `value written to ${local.name.text} is never read`,
1081
- used: false,
1082
- });
1083
1430
  }
1084
1431
  }
1085
1432
  return found;
@@ -1125,7 +1472,7 @@ function resultParts<Brand>(
1125
1472
  const valued = project.queries.nonNullable(result);
1126
1473
  const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
1127
1474
  found.push({
1128
- code: UNUSED_RESULT,
1475
+ code: CODE.unusedResult,
1129
1476
  declaration: id,
1130
1477
  kind: "result",
1131
1478
  name: "result",
@@ -1142,6 +1489,8 @@ function resultParts<Brand>(
1142
1489
  export interface IntraFunctionFacts {
1143
1490
  /** Every part of the run, each once, in use where any project found it in use. */
1144
1491
  readonly parts: readonly PartFact[];
1492
+ /** Every import binding of the run, each once, read where any project found it read. */
1493
+ readonly imports: readonly ImportFact[];
1145
1494
  /** The declarations whose signature is free to change. */
1146
1495
  readonly free: ReadonlySet<string>;
1147
1496
  /** The declarations whose signature is free but for a use as a value. */
@@ -1172,6 +1521,15 @@ export function intraFunctionFacts(
1172
1521
  const known = parts.get(key);
1173
1522
  parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
1174
1523
  }
1524
+ const imports = new Map<string, ImportFact>();
1525
+ for (const binding of projects.flatMap((one) => one.imports)) {
1526
+ const key = positionKey(binding.position);
1527
+ const known = imports.get(key);
1528
+ imports.set(
1529
+ key,
1530
+ known === undefined ? binding : { ...known, used: known.used || binding.used },
1531
+ );
1532
+ }
1175
1533
  const calls = new Map<string, CallUse>();
1176
1534
  const specifiers = new Set<string>();
1177
1535
  for (const one of projects) {
@@ -1231,5 +1589,11 @@ export function intraFunctionFacts(
1231
1589
  .filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
1232
1590
  .map(([id]) => id),
1233
1591
  );
1234
- return { parts: [...parts.values()], free, freeButValued, discardedEverywhere };
1592
+ return {
1593
+ parts: [...parts.values()],
1594
+ imports: [...imports.values()],
1595
+ free,
1596
+ freeButValued,
1597
+ discardedEverywhere,
1598
+ };
1235
1599
  }