@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
@@ -4,18 +4,17 @@
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, isFunctionLikeDeclaration, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
7
+ import { CODE } from "./contract/contract.generated.js";
8
+ 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";
9
+ import { SignatureKind } from "@typescript/native/sync";
10
+ import { exportsLocally, implicitReads, importBindings, } from "./import-bindings.js";
8
11
  import { isParameterProperty, nodeKey } from "./inventory.js";
9
12
  import { positionKey, renderPosition } from "./position.js";
13
+ import { importRef } from "./ref.js";
10
14
  import { isAnswered, UNANSWERED } from "./query.js";
11
15
  import { signatureOf } from "./signatures.js";
12
16
  import { spellsNoBinding } from "./spelled-names.js";
13
- /** The codes of the parts this module reads. */
14
- export const UNUSED_PARAMETER = "DS1801";
15
- export const UNUSED_RESULT = "DS1803";
16
- const UNREACHABLE_STATEMENT = "DS1805";
17
- const DEAD_STORE = "DS1807";
18
- const UNREACHABLE_CASE = "DS1809";
17
+ import { parenthesesRemoved } from "./unwrap.js";
19
18
  /** The result types that carry no value, as the checker spells them. */
20
19
  const NO_VALUE = new Set([
21
20
  "void",
@@ -31,17 +30,13 @@ function spanOf(file, root, node) {
31
30
  const endLine = file.getLineAndCharacterOfPosition(node.end).line + 1;
32
31
  return { ...start, endLine };
33
32
  }
34
- /** The parenthesized expression's content, through every pair of parentheses. */
35
- function unwrapped(node) {
36
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
37
- }
38
33
  /** The node the parentheses around one expression sit in. */
39
34
  function outerParent(node) {
40
35
  return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
41
36
  }
42
37
  /** The name a call is made through: the identifier, or the member a property access names. */
43
38
  function calleeName(call) {
44
- const callee = unwrapped(call.expression);
39
+ const callee = parenthesesRemoved(call.expression);
45
40
  if (isIdentifier(callee)) {
46
41
  return callee;
47
42
  }
@@ -96,7 +91,7 @@ function unreachableExempt(statement) {
96
91
  }
97
92
  /** The value a case expression is a literal of, as a key two equal literals share, or none. */
98
93
  function caseKey(expression) {
99
- const node = unwrapped(expression);
94
+ const node = parenthesesRemoved(expression);
100
95
  if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
101
96
  return `s:${node.text}`;
102
97
  }
@@ -207,7 +202,7 @@ function extendedNames(files) {
207
202
  named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
208
203
  }
209
204
  else {
210
- const expression = unwrapped(type.expression);
205
+ const expression = parenthesesRemoved(type.expression);
211
206
  named = isPropertyAccessExpression(expression) ? expression.name : expression;
212
207
  }
213
208
  if (isIdentifier(named)) {
@@ -251,7 +246,7 @@ function decoratorFixes(constructor) {
251
246
  }
252
247
  /** The name an expression spells a class by: an identifier, or the member a property access names. */
253
248
  function spelledName(expression) {
254
- const node = unwrapped(expression);
249
+ const node = parenthesesRemoved(expression);
255
250
  if (isIdentifier(node)) {
256
251
  return node;
257
252
  }
@@ -304,6 +299,33 @@ function readsBinding(use, declared, ids) {
304
299
  const own = ids.get(declared);
305
300
  return at === undefined || at === UNANSWERED || own === UNANSWERED || at === own;
306
301
  }
302
+ /** Whether a name is spelled with a leading underscore, which declares it unread. */
303
+ function declaredUnread(name) {
304
+ return name.text.startsWith("_");
305
+ }
306
+ /** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
307
+ const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
308
+ /** A comment of the leading comments, a line comment's text after its slashes captured. */
309
+ const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
310
+ /**
311
+ * Whether a file's leading comments hold one pragma: as the compiler reads it, a line
312
+ * comment that starts with the pragma's name, in any case, followed by nothing or a space.
313
+ */
314
+ function hasPragma(leading, pragma) {
315
+ return [...leading.matchAll(LEADING_COMMENT)].some(([, text]) => text !== undefined && pragma.test(text));
316
+ }
317
+ /**
318
+ * Whether the compiler checks a file, so its rules report what they find there: a
319
+ * JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
320
+ * `@ts-nocheck` pragma.
321
+ */
322
+ function checkedByTheCompiler(file, checkJs) {
323
+ const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
324
+ if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
325
+ return false;
326
+ }
327
+ return (!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu));
328
+ }
307
329
  /**
308
330
  * The parts of one project's own declarations. `references` are the project's own
309
331
  * references, which say which declarations a discarded call reaches, so the checker is
@@ -319,7 +341,12 @@ export function projectParts(project, held, references, targetRoot) {
319
341
  const constructions = new Set();
320
342
  const sites = [];
321
343
  const contextual = [];
344
+ const imports = [];
345
+ const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
346
+ const options = project.program.getCompilerOptions();
347
+ const jsx = options;
322
348
  for (const file of files) {
349
+ const checked = checkedByTheCompiler(file, options.checkJs === true);
323
350
  // A parameter property's own parts stay with the parameter when its member goes.
324
351
  const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
325
352
  // A part no declaration holds belongs to the file, which is judged while the file is live.
@@ -338,6 +365,7 @@ export function projectParts(project, held, references, targetRoot) {
338
365
  values: [],
339
366
  catches: [],
340
367
  blocks: [],
368
+ loops: [],
341
369
  unreachable: [],
342
370
  cases: [],
343
371
  };
@@ -411,8 +439,8 @@ export function projectParts(project, held, references, targetRoot) {
411
439
  if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
412
440
  specifiers.add(position(node.name));
413
441
  }
414
- if (isExportAssignment(node) && isIdentifier(unwrapped(node.expression))) {
415
- specifiers.add(position(unwrapped(node.expression)));
442
+ if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
443
+ specifiers.add(position(parenthesesRemoved(node.expression)));
416
444
  }
417
445
  if (isSwitchStatement(node)) {
418
446
  const holder = holderOf(node);
@@ -433,6 +461,15 @@ export function projectParts(project, held, references, targetRoot) {
433
461
  if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
434
462
  walk.blocks.push(node);
435
463
  }
464
+ const loopHolder = (isForOfStatement(node) || isForInStatement(node)) && silenced === 0
465
+ ? holderOf(node)
466
+ : undefined;
467
+ if (loopHolder !== undefined &&
468
+ (isForOfStatement(node) || isForInStatement(node)) &&
469
+ isVariableDeclarationList(node.initializer) &&
470
+ (node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0) {
471
+ walk.loops.push({ loop: node, holder: loopHolder });
472
+ }
436
473
  if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
437
474
  statementsOf(node.statements);
438
475
  return;
@@ -462,7 +499,37 @@ export function projectParts(project, held, references, targetRoot) {
462
499
  for (const store of stores) {
463
500
  asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
464
501
  }
502
+ const loopVariables = walk.loops.map(({ loop, holder }) => {
503
+ const list = loop.initializer;
504
+ // A loop must bind a name, so one spelled declared unread is no subject.
505
+ const names = list.declarations
506
+ .flatMap((declaration) => boundNames(declaration.name))
507
+ .filter((name) => !declaredUnread(name));
508
+ const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
509
+ asked.push(...names, ...uses);
510
+ return { names, uses, holder };
511
+ });
512
+ const bindings = fileId === undefined ? [] : importBindings(file);
513
+ const bound = new Set(bindings);
514
+ const bindingUses = bindings.length === 0
515
+ ? []
516
+ : identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter((use) => !bound.has(use));
517
+ asked.push(...bindings, ...bindingUses);
465
518
  const ids = resolvedIds(project, [...new Set(asked)]);
519
+ const implicit = bindings.length === 0 ? new Set() : implicitReads(file, jsx);
520
+ for (const binding of bindings) {
521
+ const fileRef = fileId === undefined ? undefined : refs.get(fileId);
522
+ if (fileId === undefined || fileRef === undefined) {
523
+ continue;
524
+ }
525
+ imports.push({
526
+ ref: importRef(fileRef, binding.text),
527
+ name: binding.text,
528
+ position: spanOf(file, targetRoot, binding),
529
+ used: implicit.has(binding.text) ||
530
+ bindingUses.some((use) => use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids))),
531
+ });
532
+ }
466
533
  for (const one of walk.declared) {
467
534
  const constructor = isConstructorDeclaration(one.node);
468
535
  if (!constructor) {
@@ -476,7 +543,10 @@ export function projectParts(project, held, references, targetRoot) {
476
543
  });
477
544
  const reader = readers.get(one.signature);
478
545
  if (reader !== undefined) {
479
- const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, false);
546
+ const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
547
+ inValue: false,
548
+ checked,
549
+ });
480
550
  parts.push(...found.parts);
481
551
  contextual.push(...found.contextual);
482
552
  }
@@ -484,14 +554,17 @@ export function projectParts(project, held, references, targetRoot) {
484
554
  for (const one of walk.values) {
485
555
  const reader = readers.get(one.signature);
486
556
  if (reader !== undefined) {
487
- const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, true);
557
+ const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
558
+ inValue: true,
559
+ checked,
560
+ });
488
561
  parts.push(...found.parts);
489
562
  contextual.push(...found.contextual);
490
563
  }
491
564
  }
492
565
  walk.catches.forEach(({ name, holder }, at) => {
493
566
  parts.push({
494
- code: DEAD_STORE,
567
+ code: CODE.deadStore,
495
568
  declaration: holder,
496
569
  kind: "store",
497
570
  name: name.text,
@@ -500,6 +573,19 @@ export function projectParts(project, held, references, targetRoot) {
500
573
  used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
501
574
  });
502
575
  });
576
+ for (const { names, uses, holder } of loopVariables) {
577
+ for (const name of names) {
578
+ parts.push({
579
+ code: CODE.deadStore,
580
+ declaration: holder,
581
+ kind: "store",
582
+ name: name.text,
583
+ position: spanOf(file, targetRoot, name),
584
+ message: `value written to ${name.text} is never read`,
585
+ used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
586
+ });
587
+ }
588
+ }
503
589
  walk.blocks.forEach((block, at) => {
504
590
  const holder = holderOf(block);
505
591
  if (holder !== undefined) {
@@ -508,7 +594,7 @@ export function projectParts(project, held, references, targetRoot) {
508
594
  });
509
595
  for (const { statement, holder } of walk.unreachable) {
510
596
  parts.push({
511
- code: UNREACHABLE_STATEMENT,
597
+ code: CODE.unreachableStatement,
512
598
  declaration: holder,
513
599
  kind: "statement",
514
600
  name: "statement",
@@ -519,7 +605,7 @@ export function projectParts(project, held, references, targetRoot) {
519
605
  }
520
606
  for (const { clause, text, holder } of walk.cases) {
521
607
  parts.push({
522
- code: UNREACHABLE_CASE,
608
+ code: CODE.unreachableCase,
523
609
  declaration: holder,
524
610
  kind: "case",
525
611
  name: text,
@@ -532,6 +618,7 @@ export function projectParts(project, held, references, targetRoot) {
532
618
  parts.push(...resultParts(project, targetRoot, sites, references, calls));
533
619
  return {
534
620
  parts: withContextualRead(project, parts, contextual),
621
+ imports,
535
622
  signatures,
536
623
  calls,
537
624
  specifiers,
@@ -558,7 +645,9 @@ function valueSignature(node) {
558
645
  /**
559
646
  * Every name one function's parameters bind, a binding pattern's names one by one. A
560
647
  * `this` parameter receives no argument, a decorated one is read by its decorator, and a
561
- * parameter property is read by the write to its member, so none binds a subject.
648
+ * parameter property is read by the write to its member, so none binds a subject. A name
649
+ * spelled with a leading underscore is declared unread, so it is listed for its reads and
650
+ * reported never.
562
651
  */
563
652
  function parameterNames(signature) {
564
653
  const found = [];
@@ -570,7 +659,7 @@ function parameterNames(signature) {
570
659
  continue;
571
660
  }
572
661
  if (isIdentifier(element.name)) {
573
- const exempt = rest && element.dotDotDotToken === undefined;
662
+ const exempt = (rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
574
663
  found.push({ name: element.name, at, pattern: true, exempt });
575
664
  }
576
665
  else {
@@ -586,7 +675,7 @@ function parameterNames(signature) {
586
675
  }
587
676
  if (isIdentifier(name)) {
588
677
  if (name.text !== "this") {
589
- found.push({ name, at, pattern: false, exempt: false });
678
+ found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
590
679
  }
591
680
  return;
592
681
  }
@@ -594,11 +683,90 @@ function parameterNames(signature) {
594
683
  });
595
684
  return found;
596
685
  }
686
+ /** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
687
+ const TYPE_PARAMETER_DEPTH = 3;
688
+ /** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
689
+ function namesTypeParameter(project, type, depth = 0) {
690
+ if (type.isTypeParameter()) {
691
+ return true;
692
+ }
693
+ if (depth >= TYPE_PARAMETER_DEPTH) {
694
+ return false;
695
+ }
696
+ const { queries } = project;
697
+ const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
698
+ const args = queries.typeArguments(type);
699
+ const signatures = queries.signaturesOf(type, SignatureKind.Call);
700
+ if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
701
+ return true;
702
+ }
703
+ const inner = [
704
+ ...parts,
705
+ ...args,
706
+ ...signatures.flatMap((signature) => signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at))),
707
+ ];
708
+ return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
709
+ }
597
710
  /**
598
- * The parts with each name of `contextual` read where the compiler gives its function a
599
- * contextual type: the inference of the call holding the function may read a parameter's
600
- * annotation, and the code handing the argument may act on what an object pattern reads.
601
- * A question the checker leaves unanswered keeps the name read.
711
+ * Whether the call a function expression is written in may read the annotation of its
712
+ * parameter at `at`: the callee is generic, and its parameter type at the function's place
713
+ * reads a type parameter there. A function written inside an object literal a generic
714
+ * call is passed is read so too, and a question the checker leaves unanswered reads it.
715
+ */
716
+ function calleeSteers(project, signature, at) {
717
+ const { queries } = project;
718
+ let value = signature;
719
+ while (isParenthesizedExpression(value.parent)) {
720
+ value = value.parent;
721
+ }
722
+ let nested = false;
723
+ while ((isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
724
+ isObjectLiteralExpression(value.parent)) {
725
+ value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
726
+ nested = true;
727
+ }
728
+ const call = value.parent;
729
+ if (!isCallExpression(call) && !isNewExpression(call)) {
730
+ return false;
731
+ }
732
+ const argument = (call.arguments ?? []).findIndex((one) => one === value);
733
+ const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
734
+ const generic = resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
735
+ if (argument < 0 || generic === undefined) {
736
+ return false;
737
+ }
738
+ if (!isAnswered(generic) || nested) {
739
+ return true;
740
+ }
741
+ const position = queries.typeAtPosition(generic, argument);
742
+ if (!isAnswered(position) || position.isTypeParameter()) {
743
+ return true;
744
+ }
745
+ const parts = position.isUnionType() || position.isIntersectionType()
746
+ ? queries.constituents(position)
747
+ : [position];
748
+ if (!isAnswered(parts)) {
749
+ return true;
750
+ }
751
+ return parts.some((part) => {
752
+ if (part.isTypeParameter()) {
753
+ return true;
754
+ }
755
+ const calls = queries.signaturesOf(part, SignatureKind.Call);
756
+ if (!isAnswered(calls)) {
757
+ return true;
758
+ }
759
+ return calls.some((one) => {
760
+ const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
761
+ return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
762
+ });
763
+ });
764
+ }
765
+ /**
766
+ * The parts with each name of `contextual` judged where the compiler gives its function a
767
+ * contextual type: a name an object pattern binds is marked as one, and an annotated
768
+ * parameter with whether the call's inference may read its annotation. A question the
769
+ * checker leaves unanswered keeps the name read.
602
770
  */
603
771
  function withContextualRead(project, parts, contextual) {
604
772
  if (contextual.length === 0) {
@@ -606,9 +774,27 @@ function withContextualRead(project, parts, contextual) {
606
774
  }
607
775
  const signatures = [...new Set(contextual.map((one) => one.signature))];
608
776
  const answers = project.queries.contextualTypes(signatures);
609
- const typed = new Set(signatures.filter((_signature, at) => answers[at] !== undefined));
610
- const read = new Set(contextual.filter((one) => typed.has(one.signature)).map((one) => one.part));
611
- return parts.map((part) => (read.has(part) ? { ...part, used: true } : part));
777
+ const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
778
+ const judged = new Map();
779
+ for (const one of contextual) {
780
+ const answer = typedBy.get(one.signature);
781
+ if (answer === undefined) {
782
+ continue;
783
+ }
784
+ const place = one.part.parameter;
785
+ judged.set(one.part, answer === UNANSWERED || place === undefined
786
+ ? { ...one.part, used: true }
787
+ : one.pattern
788
+ ? { ...one.part, parameter: { ...place, contextualPattern: true } }
789
+ : {
790
+ ...one.part,
791
+ parameter: {
792
+ ...place,
793
+ annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
794
+ },
795
+ });
796
+ }
797
+ return parts.map((part) => judged.get(part) ?? part);
612
798
  }
613
799
  /**
614
800
  * Each name one function's parameters bind and whether the function reads it, and the
@@ -616,7 +802,7 @@ function withContextualRead(project, parts, contextual) {
616
802
  * that carries a type annotation or is an object binding pattern. A binding pattern counts
617
803
  * as read where its function reads any name it binds.
618
804
  */
619
- function parameterParts(file, root, declaration, signature, reader, ids, inValue) {
805
+ function parameterParts(file, root, declaration, signature, reader, ids, place) {
620
806
  // A function that reads its `arguments` reads every parameter by position. A nested
621
807
  // function other than an arrow has an `arguments` object of its own.
622
808
  const readsArguments = !isArrowFunction(signature) &&
@@ -632,14 +818,14 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
632
818
  return;
633
819
  }
634
820
  const part = {
635
- code: UNUSED_PARAMETER,
821
+ code: CODE.unusedParameter,
636
822
  declaration,
637
823
  kind: "parameter",
638
824
  name: name.text,
639
825
  position: spanOf(file, root, name),
640
826
  message: `parameter ${name.text} is never read in the body`,
641
827
  used: read[index] === true,
642
- parameter: { beforeARead: !pattern && at < lastRead, inValue },
828
+ parameter: { beforeARead: !pattern && at < lastRead, ...place },
643
829
  };
644
830
  found.push(part);
645
831
  const parameter = signature.parameters[at];
@@ -647,42 +833,98 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
647
833
  !part.used &&
648
834
  parameter !== undefined &&
649
835
  (parameter.type !== undefined || isObjectBindingPattern(parameter.name))) {
650
- contextual.push({ part, signature: expression });
836
+ contextual.push({
837
+ part,
838
+ signature: expression,
839
+ at,
840
+ pattern: isObjectBindingPattern(parameter.name),
841
+ });
651
842
  }
652
843
  });
653
844
  return { parts: found, contextual };
654
845
  }
655
846
  /**
656
847
  * The names one declaration binds that a write of its initializer stores each on its own:
657
- * the declared name, or each element name of an array binding pattern at any depth.
848
+ * the declared name, or each element name of a binding pattern at any depth. A name with a
849
+ * default is left out, because removing it skips the default's evaluation, and so is a name
850
+ * beside a rest element of its object pattern, whose removal changes what the rest holds.
658
851
  */
659
- function arrayPatternNames(name) {
852
+ function boundNames(name) {
660
853
  if (isIdentifier(name)) {
661
854
  return [name];
662
855
  }
663
- if (!isArrayBindingPattern(name)) {
664
- return [];
856
+ const rest = isObjectBindingPattern(name) &&
857
+ name.elements.some((element) => element.dotDotDotToken !== undefined);
858
+ return name.elements.flatMap((element) => {
859
+ if (!isBindingElement(element) || element.name === undefined) {
860
+ return [];
861
+ }
862
+ if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
863
+ return [];
864
+ }
865
+ return boundNames(element.name);
866
+ });
867
+ }
868
+ /** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
869
+ function functionOf(block) {
870
+ for (let at = block.parent;; at = at.parent) {
871
+ if (isFunctionLikeDeclaration(at)) {
872
+ return at;
873
+ }
874
+ if (isSourceFile(at)) {
875
+ return undefined;
876
+ }
665
877
  }
666
- return name.elements.flatMap((element) => isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : []);
667
878
  }
668
- /** The locals one block declares with `let` or `const`, each with the names that spell it. */
879
+ /**
880
+ * The locals one block declares with `let`, `const`, `var` or a function declaration
881
+ * inside a function, each with the names of the block that spell it. A `var` of a nested
882
+ * block is one where no nested function of its function names it, since the function scope
883
+ * holds it past the block's end.
884
+ */
669
885
  function localsOf(block) {
670
886
  const declared = [];
887
+ const scope = functionOf(block);
888
+ const outlives = scope?.body !== block;
671
889
  block.statements.forEach((statement, at) => {
890
+ if (isFunctionDeclaration(statement)) {
891
+ if (scope !== undefined && statement.name !== undefined && statement.body !== undefined) {
892
+ declared.push({
893
+ name: statement.name,
894
+ statement: at,
895
+ initialized: true,
896
+ outlives: false,
897
+ hoisted: true,
898
+ });
899
+ }
900
+ return;
901
+ }
672
902
  if (!isVariableStatement(statement)) {
673
903
  return;
674
904
  }
675
905
  const list = statement.declarationList;
676
- if ((list.flags & (NodeFlags.Let | NodeFlags.Const)) === 0 ||
677
- (list.flags & NodeFlags.Using) !== 0) {
906
+ const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
907
+ if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
678
908
  return;
679
909
  }
680
910
  for (const declaration of list.declarations) {
681
- for (const name of arrayPatternNames(declaration.name)) {
911
+ // A write to a local typed `never` is the compiler's exhaustiveness check, which
912
+ // fails to compile once a case goes unhandled.
913
+ if (declaration.type?.kind === SyntaxKind.NeverKeyword) {
914
+ continue;
915
+ }
916
+ for (const name of boundNames(declaration.name)) {
917
+ if (!blockScoped &&
918
+ outlives &&
919
+ identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) => inNestedFunction(use, scope ?? block))) {
920
+ continue;
921
+ }
682
922
  declared.push({
683
923
  name,
684
924
  statement: at,
685
925
  initialized: declaration.initializer !== undefined,
926
+ outlives: !blockScoped && outlives,
927
+ hoisted: false,
686
928
  });
687
929
  }
688
930
  }
@@ -696,19 +938,47 @@ function localsOf(block) {
696
938
  uses: spelled.filter((use) => use !== one.name && use.text === one.name.text),
697
939
  }));
698
940
  }
941
+ /** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
942
+ function isPlainWrite(use) {
943
+ const parent = outerParent(use);
944
+ return (isBinaryExpression(parent) &&
945
+ parent.operatorToken.kind === SyntaxKind.EqualsToken &&
946
+ parenthesesRemoved(parent.left) === use);
947
+ }
699
948
  /**
700
949
  * The writes to the locals of one block that no read reaches. A write is a declaration's
701
- * initializer or a plain assignment written as a statement of the block itself; it is
702
- * dead when the statements after it in the block read the local nowhere before the next
703
- * such write to it, or before the block ends, which ends the local's scope. A statement
704
- * that writes the local inside a nested construct ends the search with the write kept,
705
- * and a local a nested function names is no subject, because the function may read it
706
- * at any time.
950
+ * initializer or a plain assignment written as a statement of the block; it is dead when
951
+ * the block's later statements read the local nowhere before the next such write or the
952
+ * block's end. A statement writing it inside a nested construct ends the search with the
953
+ * write kept. A local a nested function reads is no subject, since the function may run at
954
+ * any time, and every write to a local nothing reads is dead, a nested function's included.
707
955
  */
708
956
  function deadStores(file, root, block, holder, locals, ids) {
709
957
  const found = [];
958
+ const deadAt = (local, target) => ({
959
+ code: CODE.deadStore,
960
+ declaration: holder,
961
+ kind: "store",
962
+ name: local.name.text,
963
+ position: spanOf(file, root, target),
964
+ message: `value written to ${local.name.text} is never read`,
965
+ used: false,
966
+ });
710
967
  for (const local of locals) {
711
- const uses = local.uses.filter((use) => readsBinding(use, local.name, ids));
968
+ const own = local.hoisted ? block.statements[local.statement] : undefined;
969
+ // A function's reads of itself keep it no more than its absence would.
970
+ const uses = local.uses.filter((use) => readsBinding(use, local.name, ids) && (own === undefined || !within(use, own)));
971
+ if (!local.outlives && uses.every(isPlainWrite)) {
972
+ // A declaration with no initializer writes `undefined`, the one store left to name.
973
+ const writes = [...(local.initialized ? [local.name] : []), ...uses];
974
+ const targets = writes.length === 0 ? [local.name] : writes;
975
+ found.push(...targets.map((target) => deadAt(local, target)));
976
+ continue;
977
+ }
978
+ // A read anywhere in the block reads a function declaration, which is written first.
979
+ if (local.hoisted) {
980
+ continue;
981
+ }
712
982
  if (uses.some((use) => inNestedFunction(use, block))) {
713
983
  continue;
714
984
  }
@@ -719,12 +989,12 @@ function deadStores(file, root, block, holder, locals, ids) {
719
989
  if (!isExpressionStatement(statement)) {
720
990
  return undefined;
721
991
  }
722
- const expression = unwrapped(statement.expression);
992
+ const expression = parenthesesRemoved(statement.expression);
723
993
  if (!isBinaryExpression(expression) ||
724
994
  expression.operatorToken.kind !== SyntaxKind.EqualsToken) {
725
995
  return undefined;
726
996
  }
727
- const target = unwrapped(expression.left);
997
+ const target = parenthesesRemoved(expression.left);
728
998
  return isIdentifier(target) && uses.includes(target)
729
999
  ? { target, value: expression.right }
730
1000
  : undefined;
@@ -746,7 +1016,7 @@ function deadStores(file, root, block, holder, locals, ids) {
746
1016
  if (sameStatement.length > 0) {
747
1017
  continue;
748
1018
  }
749
- let dead = true;
1019
+ let dead = !local.outlives;
750
1020
  for (let at = write.at + 1; at < statements.length; at += 1) {
751
1021
  const statement = statements[at];
752
1022
  if (statement === undefined) {
@@ -763,19 +1033,9 @@ function deadStores(file, root, block, holder, locals, ids) {
763
1033
  break;
764
1034
  }
765
1035
  }
766
- if (!dead) {
767
- continue;
1036
+ if (dead) {
1037
+ found.push(deadAt(local, write.target));
768
1038
  }
769
- const position = spanOf(file, root, write.target);
770
- found.push({
771
- code: DEAD_STORE,
772
- declaration: holder,
773
- kind: "store",
774
- name: local.name.text,
775
- position,
776
- message: `value written to ${local.name.text} is never read`,
777
- used: false,
778
- });
779
1039
  }
780
1040
  }
781
1041
  return found;
@@ -812,7 +1072,7 @@ function resultParts(project, root, sites, references, calls) {
812
1072
  const valued = project.queries.nonNullable(result);
813
1073
  const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
814
1074
  found.push({
815
- code: UNUSED_RESULT,
1075
+ code: CODE.unusedResult,
816
1076
  declaration: id,
817
1077
  kind: "result",
818
1078
  name: "result",
@@ -839,6 +1099,12 @@ export function intraFunctionFacts(projects, references, exempted) {
839
1099
  const known = parts.get(key);
840
1100
  parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
841
1101
  }
1102
+ const imports = new Map();
1103
+ for (const binding of projects.flatMap((one) => one.imports)) {
1104
+ const key = positionKey(binding.position);
1105
+ const known = imports.get(key);
1106
+ imports.set(key, known === undefined ? binding : { ...known, used: known.used || binding.used });
1107
+ }
842
1108
  const calls = new Map();
843
1109
  const specifiers = new Set();
844
1110
  for (const one of projects) {
@@ -885,5 +1151,11 @@ export function intraFunctionFacts(projects, references, exempted) {
885
1151
  const discardedEverywhere = new Set([...called]
886
1152
  .filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
887
1153
  .map(([id]) => id));
888
- return { parts: [...parts.values()], free, freeButValued, discardedEverywhere };
1154
+ return {
1155
+ parts: [...parts.values()],
1156
+ imports: [...imports.values()],
1157
+ free,
1158
+ freeButValued,
1159
+ discardedEverywhere,
1160
+ };
889
1161
  }