@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.20

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 (402) hide show
  1. package/README.md +3 -1
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/dist/bin/mapper-connection.js +2 -1
  4. package/dist/bin/node-host.js +24 -5
  5. package/dist/src/alias-chain.js +2 -7
  6. package/dist/src/amd.js +81 -0
  7. package/dist/src/analysis.js +269 -432
  8. package/dist/src/augmentations.js +64 -3
  9. package/dist/src/automatic-types.js +10 -29
  10. package/dist/src/bindings.js +382 -0
  11. package/dist/src/calls.js +279 -43
  12. package/dist/src/commands/held.js +499 -0
  13. package/dist/src/commands/read.js +161 -0
  14. package/dist/src/commands/shell.js +169 -0
  15. package/dist/src/commands/tools.js +1847 -0
  16. package/dist/src/component-files.js +3 -2
  17. package/dist/src/component-layer.js +4 -2
  18. package/dist/src/config.js +6 -82
  19. package/dist/src/conformance-record.js +1 -1
  20. package/dist/src/consumers.js +10 -38
  21. package/dist/src/contextual-uses.js +57 -26
  22. package/dist/src/contract/config.generated.js +561 -0
  23. package/dist/src/contract/contract.generated.js +1369 -0
  24. package/dist/src/contract/report.generated.js +866 -0
  25. package/dist/src/dependencies.js +164 -121
  26. package/dist/src/destructuring.js +1 -19
  27. package/dist/src/edges.js +38 -122
  28. package/dist/src/element-access.js +181 -168
  29. package/dist/src/entries/build-scripts.js +222 -0
  30. package/dist/src/{configuration-files.js → entries/configuration-files.js} +52 -256
  31. package/dist/src/{convention-rows.js → entries/convention-rows.js} +170 -30
  32. package/dist/src/entries/conventions.js +740 -0
  33. package/dist/src/{html-entries.js → entries/html-entries.js} +12 -42
  34. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +5 -28
  35. package/dist/src/entries/run-directories.js +77 -0
  36. package/dist/src/entries/sources.js +144 -0
  37. package/dist/src/entries/static-module.js +670 -0
  38. package/dist/src/entries/test-runners.js +650 -0
  39. package/dist/src/entries/workflow-steps.js +345 -0
  40. package/dist/src/enum-group.js +3 -10
  41. package/dist/src/erased-stores.js +4 -6
  42. package/dist/src/exempt-classes.js +1 -63
  43. package/dist/src/exempt.js +21 -66
  44. package/dist/src/exposure.js +400 -0
  45. package/dist/src/extensions.js +37 -0
  46. package/dist/src/facts.js +38 -0
  47. package/dist/src/failure.js +36 -0
  48. package/dist/src/file-facts.js +3 -26
  49. package/dist/src/findings/completion.js +109 -56
  50. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  51. package/dist/src/findings/emitters.js +27 -41
  52. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  53. package/dist/src/{in-operands.js → findings/in-operands.js} +3 -2
  54. package/dist/src/findings/interfaces.js +4 -5
  55. package/dist/src/findings/intra-function.js +16 -13
  56. package/dist/src/findings/non-code-artifacts.js +17 -12
  57. package/dist/src/findings/reads-and-writes.js +86 -27
  58. package/dist/src/findings/self-check.js +44 -30
  59. package/dist/src/findings/unused-declarations.js +29 -45
  60. package/dist/src/findings/visibility-narrowing.js +23 -31
  61. package/dist/src/findings-pass.js +13 -21
  62. package/dist/src/framework-lifecycle.js +46 -19
  63. package/dist/src/fs/document.js +52 -0
  64. package/dist/src/fs/manifest.js +67 -0
  65. package/dist/src/fs/tree.js +88 -0
  66. package/dist/src/generated-file.js +174 -28
  67. package/dist/src/git-ignore.js +39 -26
  68. package/dist/src/import-bindings.js +73 -28
  69. package/dist/src/index/sites.js +557 -0
  70. package/dist/src/index.js +2 -2
  71. package/dist/src/indexed-access.js +523 -478
  72. package/dist/src/injection-container.js +2 -2
  73. package/dist/src/interface-satisfaction.js +1 -1
  74. package/dist/src/intra-function-parts.js +63 -43
  75. package/dist/src/inventory/commonjs.js +507 -0
  76. package/dist/src/inventory/enumerate.js +167 -0
  77. package/dist/src/inventory/syntax.js +236 -0
  78. package/dist/src/inventory.js +323 -724
  79. package/dist/src/javascript-file.js +48 -0
  80. package/dist/src/json-document.js +198 -67
  81. package/dist/src/keyof-comparisons.js +126 -107
  82. package/dist/src/kinds.js +3 -366
  83. package/dist/src/{components.js → liveness/components.js} +2 -3
  84. package/dist/src/{graph.js → liveness/graph.js} +52 -28
  85. package/dist/src/liveness/matrix.js +99 -0
  86. package/dist/src/liveness/sweep.js +558 -0
  87. package/dist/src/load/classify.js +289 -0
  88. package/dist/src/load/diagnostics.js +87 -0
  89. package/dist/src/load/discover.js +285 -0
  90. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  91. package/dist/src/load/expected-module.js +136 -0
  92. package/dist/src/load/load.js +27 -0
  93. package/dist/src/load/module-lookup.js +37 -0
  94. package/dist/src/load/package-scope.js +56 -0
  95. package/dist/src/load/plan.js +313 -0
  96. package/dist/src/load/specifiers.js +113 -0
  97. package/dist/src/load/synthesize.js +152 -0
  98. package/dist/src/load/test-globals.js +207 -0
  99. package/dist/src/load/test-joins.js +82 -0
  100. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  101. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  102. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +24 -66
  103. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  104. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  105. package/dist/src/module-declarations.js +70 -85
  106. package/dist/src/overrides.js +134 -112
  107. package/dist/src/partial.js +2 -1
  108. package/dist/src/paths.js +82 -16
  109. package/dist/src/platform-calls.js +78 -0
  110. package/dist/src/platform-symbols.js +134 -0
  111. package/dist/src/position.js +43 -1
  112. package/dist/src/print.js +11 -79
  113. package/dist/src/product.js +13 -0
  114. package/dist/src/query.js +47 -0
  115. package/dist/src/ref.js +12 -20
  116. package/dist/src/references/read-rule.js +1 -0
  117. package/dist/src/references/read-rules.js +48 -0
  118. package/dist/src/references/sink.js +237 -0
  119. package/dist/src/references/sources.js +450 -0
  120. package/dist/src/references.js +117 -1074
  121. package/dist/src/report-schema.js +51 -0
  122. package/dist/src/report.js +57 -41
  123. package/dist/src/reporters/annotations.js +15 -8
  124. package/dist/src/reporters/sarif.js +16 -10
  125. package/dist/src/reporters/template.js +4 -19
  126. package/dist/src/resolve.js +246 -730
  127. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +22 -24
  128. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +6 -15
  129. package/dist/src/roots/manifest.js +216 -0
  130. package/dist/src/roots/published-reach.js +412 -0
  131. package/dist/src/roots/root-set.js +487 -0
  132. package/dist/src/roots/unseen-loads.js +236 -0
  133. package/dist/src/run.js +66 -77
  134. package/dist/src/schema.js +320 -343
  135. package/dist/src/scope.js +38 -115
  136. package/dist/src/serialization-contract.js +601 -22
  137. package/dist/src/session.js +37 -29
  138. package/dist/src/source-files.js +6 -10
  139. package/dist/src/stores.js +4 -2
  140. package/dist/src/suppress-file.js +25 -28
  141. package/dist/src/suppress-inline.js +36 -83
  142. package/dist/src/suppress.js +50 -71
  143. package/dist/src/symbol-keys.js +92 -88
  144. package/dist/src/template-field.js +9 -19
  145. package/dist/src/tests/file-class.js +76 -0
  146. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  147. package/dist/src/{test-support.js → tests/support.js} +5 -12
  148. package/dist/src/text/compare.js +26 -0
  149. package/dist/src/text/glob.js +194 -0
  150. package/dist/src/text/named-files.js +298 -0
  151. package/dist/src/text/yaml.js +684 -0
  152. package/dist/src/type-errors.js +11 -232
  153. package/dist/src/type-parameter-reads.js +178 -139
  154. package/dist/src/unimplemented-callees.js +262 -242
  155. package/dist/src/unwrap.js +23 -0
  156. package/dist/src/usage.js +10 -0
  157. package/dist/src/vendor/unbash/ansi-c.js +132 -0
  158. package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
  159. package/dist/src/vendor/unbash/arithmetic.js +591 -0
  160. package/dist/src/vendor/unbash/assignment.js +131 -0
  161. package/dist/src/vendor/unbash/chars.js +48 -0
  162. package/dist/src/vendor/unbash/command.js +41 -0
  163. package/dist/src/vendor/unbash/expansion.js +80 -0
  164. package/dist/src/vendor/unbash/glob.js +268 -0
  165. package/dist/src/vendor/unbash/heredoc.js +27 -0
  166. package/dist/src/vendor/unbash/internal-types.js +1 -0
  167. package/dist/src/vendor/unbash/lexer.js +4274 -0
  168. package/dist/src/vendor/unbash/limits.js +3 -0
  169. package/dist/src/vendor/unbash/parser.js +1366 -0
  170. package/dist/src/vendor/unbash/parts.js +57 -0
  171. package/dist/src/vendor/unbash/substitution-context.js +8 -0
  172. package/dist/src/vendor/unbash/traversal.js +212 -0
  173. package/dist/src/vendor/unbash/types.js +1 -0
  174. package/dist/src/vendor/unbash/word-value.js +61 -0
  175. package/dist/src/vendor/unbash/word.js +40 -0
  176. package/dist/src/verbs/analyze.js +194 -127
  177. package/dist/src/verbs/describe.js +13 -18
  178. package/dist/src/verbs/explain.js +85 -139
  179. package/dist/src/verbs/print-config.js +2 -1
  180. package/dist/src/verbs/print-projects.js +6 -4
  181. package/dist/src/verbs/print-retained.js +7 -5
  182. package/dist/src/verbs/print-roots.js +4 -2
  183. package/dist/src/verbs/verb.js +1 -5
  184. package/dist/src/verbs/version.js +14 -3
  185. package/jsr.json +2 -1
  186. package/package.json +14 -8
  187. package/src/alias-chain.ts +2 -9
  188. package/src/amd.ts +130 -0
  189. package/src/analysis.ts +424 -650
  190. package/src/augmentations.ts +93 -4
  191. package/src/automatic-types.ts +12 -30
  192. package/src/bindings.ts +460 -0
  193. package/src/calls.ts +338 -46
  194. package/src/commands/held.ts +632 -0
  195. package/src/commands/read.ts +242 -0
  196. package/src/commands/shell.ts +228 -0
  197. package/src/commands/tools.ts +2382 -0
  198. package/src/component-files.ts +4 -2
  199. package/src/component-layer.ts +4 -2
  200. package/src/config.ts +31 -302
  201. package/src/conformance-record.ts +1 -1
  202. package/src/consumers.ts +16 -66
  203. package/src/contextual-uses.ts +66 -34
  204. package/src/contract/config.generated.ts +821 -0
  205. package/src/contract/contract.generated.ts +1452 -0
  206. package/src/contract/report.generated.ts +1241 -0
  207. package/src/dependencies.ts +222 -127
  208. package/src/destructuring.ts +1 -31
  209. package/src/edges.ts +41 -140
  210. package/src/element-access.ts +190 -187
  211. package/src/entries/build-scripts.ts +292 -0
  212. package/src/{configuration-files.ts → entries/configuration-files.ts} +68 -278
  213. package/src/{convention-rows.ts → entries/convention-rows.ts} +227 -31
  214. package/src/{conventions.ts → entries/conventions.ts} +367 -583
  215. package/src/{html-entries.ts → entries/html-entries.ts} +22 -48
  216. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +7 -30
  217. package/src/entries/run-directories.ts +108 -0
  218. package/src/entries/sources.ts +266 -0
  219. package/src/entries/static-module.ts +858 -0
  220. package/src/entries/test-runners.ts +835 -0
  221. package/src/entries/workflow-steps.ts +432 -0
  222. package/src/enum-group.ts +2 -11
  223. package/src/erased-stores.ts +3 -7
  224. package/src/exempt-classes.ts +1 -75
  225. package/src/exempt.ts +70 -98
  226. package/src/exposure.ts +492 -0
  227. package/src/extensions.ts +45 -0
  228. package/src/facts.ts +46 -0
  229. package/src/failure.ts +63 -0
  230. package/src/file-facts.ts +15 -25
  231. package/src/finding.ts +10 -10
  232. package/src/findings/boundary.ts +11 -0
  233. package/src/findings/completion.ts +133 -64
  234. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  235. package/src/findings/emitter.ts +9 -10
  236. package/src/findings/emitters.ts +38 -52
  237. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  238. package/src/{in-operands.ts → findings/in-operands.ts} +4 -3
  239. package/src/findings/interfaces.ts +7 -9
  240. package/src/findings/intra-function.ts +16 -14
  241. package/src/findings/non-code-artifacts.ts +24 -15
  242. package/src/findings/reads-and-writes.ts +118 -41
  243. package/src/findings/self-check.ts +51 -38
  244. package/src/findings/unused-declarations.ts +31 -49
  245. package/src/findings/visibility-narrowing.ts +36 -40
  246. package/src/findings-pass.ts +15 -31
  247. package/src/framework-lifecycle.ts +47 -19
  248. package/src/fs/document.ts +70 -0
  249. package/src/fs/manifest.ts +96 -0
  250. package/src/fs/tree.ts +110 -0
  251. package/src/generated-file.ts +242 -35
  252. package/src/git-ignore.ts +50 -26
  253. package/src/host.ts +12 -5
  254. package/src/import-bindings.ts +98 -29
  255. package/src/index/sites.ts +781 -0
  256. package/src/index.ts +2 -2
  257. package/src/indexed-access.ts +570 -527
  258. package/src/injection-container.ts +2 -2
  259. package/src/interface-satisfaction.ts +2 -2
  260. package/src/intra-function-parts.ts +73 -44
  261. package/src/inventory/commonjs.ts +696 -0
  262. package/src/inventory/enumerate.ts +203 -0
  263. package/src/inventory/syntax.ts +311 -0
  264. package/src/inventory.ts +442 -895
  265. package/src/javascript-file.ts +54 -0
  266. package/src/json-document.ts +241 -76
  267. package/src/keyof-comparisons.ts +133 -122
  268. package/src/kinds.ts +5 -413
  269. package/src/{components.ts → liveness/components.ts} +4 -6
  270. package/src/{graph.ts → liveness/graph.ts} +100 -35
  271. package/src/liveness/matrix.ts +162 -0
  272. package/src/liveness/sweep.ts +855 -0
  273. package/src/load/classify.ts +433 -0
  274. package/src/load/diagnostics.ts +154 -0
  275. package/src/load/discover.ts +471 -0
  276. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  277. package/src/load/expected-module.ts +194 -0
  278. package/src/load/load.ts +73 -0
  279. package/src/load/module-lookup.ts +50 -0
  280. package/src/load/package-scope.ts +74 -0
  281. package/src/load/plan.ts +513 -0
  282. package/src/load/specifiers.ts +171 -0
  283. package/src/load/synthesize.ts +230 -0
  284. package/src/load/test-globals.ts +251 -0
  285. package/src/load/test-joins.ts +98 -0
  286. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  287. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  288. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +41 -77
  289. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  290. package/src/{workspace.ts → load/workspace.ts} +90 -134
  291. package/src/module-declarations.ts +74 -98
  292. package/src/overrides.ts +162 -134
  293. package/src/partial.ts +3 -2
  294. package/src/paths.ts +92 -16
  295. package/src/platform-calls.ts +119 -0
  296. package/src/platform-symbols.ts +189 -0
  297. package/src/position.ts +44 -1
  298. package/src/print.ts +12 -92
  299. package/src/product.ts +18 -0
  300. package/src/query.ts +92 -0
  301. package/src/ref.ts +12 -20
  302. package/src/references/read-rule.ts +60 -0
  303. package/src/references/read-rules.ts +55 -0
  304. package/src/references/sink.ts +314 -0
  305. package/src/references/sources.ts +632 -0
  306. package/src/references.ts +187 -1450
  307. package/src/report-schema.ts +68 -0
  308. package/src/report.ts +103 -226
  309. package/src/reporters/annotations.ts +22 -11
  310. package/src/reporters/json.ts +1 -1
  311. package/src/reporters/reporter.ts +2 -4
  312. package/src/reporters/reporters.ts +1 -1
  313. package/src/reporters/sarif.ts +29 -16
  314. package/src/reporters/template.ts +4 -20
  315. package/src/reporters/text.ts +3 -3
  316. package/src/resolve.ts +284 -1115
  317. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +27 -29
  318. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +7 -16
  319. package/src/roots/manifest.ts +303 -0
  320. package/src/roots/published-reach.ts +486 -0
  321. package/src/{roots.ts → roots/root-set.ts} +286 -469
  322. package/src/roots/unseen-loads.ts +345 -0
  323. package/src/run.ts +75 -91
  324. package/src/schema.ts +391 -388
  325. package/src/scope.ts +46 -138
  326. package/src/serialization-contract.ts +769 -23
  327. package/src/session.ts +87 -55
  328. package/src/source-files.ts +6 -11
  329. package/src/stores.ts +15 -3
  330. package/src/suppress-file.ts +24 -33
  331. package/src/suppress-inline.ts +55 -91
  332. package/src/suppress.ts +78 -79
  333. package/src/symbol-keys.ts +104 -107
  334. package/src/template-field.ts +15 -25
  335. package/src/tests/file-class.ts +135 -0
  336. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  337. package/src/{test-support.ts → tests/support.ts} +15 -21
  338. package/src/text/compare.ts +27 -0
  339. package/src/text/glob.ts +210 -0
  340. package/src/text/named-files.ts +388 -0
  341. package/src/text/yaml.ts +747 -0
  342. package/src/type-errors.ts +18 -288
  343. package/src/type-parameter-reads.ts +187 -158
  344. package/src/unimplemented-callees.ts +297 -280
  345. package/src/unwrap.ts +34 -0
  346. package/src/usage.ts +10 -0
  347. package/src/vendor/unbash/LICENSE +15 -0
  348. package/src/vendor/unbash/VENDORED.md +16 -0
  349. package/src/vendor/unbash/ansi-c.ts +132 -0
  350. package/src/vendor/unbash/arithmetic-parts.ts +53 -0
  351. package/src/vendor/unbash/arithmetic.ts +641 -0
  352. package/src/vendor/unbash/assignment.ts +163 -0
  353. package/src/vendor/unbash/chars.ts +48 -0
  354. package/src/vendor/unbash/command.ts +47 -0
  355. package/src/vendor/unbash/expansion.ts +144 -0
  356. package/src/vendor/unbash/glob.ts +284 -0
  357. package/src/vendor/unbash/heredoc.ts +34 -0
  358. package/src/vendor/unbash/internal-types.ts +94 -0
  359. package/src/vendor/unbash/lexer.ts +4352 -0
  360. package/src/vendor/unbash/limits.ts +3 -0
  361. package/src/vendor/unbash/parser.ts +1612 -0
  362. package/src/vendor/unbash/parts.ts +96 -0
  363. package/src/vendor/unbash/substitution-context.ts +20 -0
  364. package/src/vendor/unbash/traversal.ts +182 -0
  365. package/src/vendor/unbash/types.ts +660 -0
  366. package/src/vendor/unbash/word-value.ts +64 -0
  367. package/src/vendor/unbash/word.ts +50 -0
  368. package/src/verbs/analyze.ts +255 -164
  369. package/src/verbs/describe.ts +19 -17
  370. package/src/verbs/explain.ts +93 -169
  371. package/src/verbs/print-config.ts +2 -1
  372. package/src/verbs/print-projects.ts +6 -4
  373. package/src/verbs/print-retained.ts +7 -5
  374. package/src/verbs/print-roots.ts +4 -2
  375. package/src/verbs/verb.ts +2 -8
  376. package/src/verbs/version.ts +15 -3
  377. package/dist/src/base-url.js +0 -18
  378. package/dist/src/conventions.js +0 -937
  379. package/dist/src/discover.js +0 -468
  380. package/dist/src/entry-points.js +0 -529
  381. package/dist/src/glob.js +0 -65
  382. package/dist/src/manifest.js +0 -165
  383. package/dist/src/matrix.js +0 -246
  384. package/dist/src/pnpm-workspace.js +0 -120
  385. package/dist/src/roots.js +0 -672
  386. package/dist/src/setup-failure.js +0 -23
  387. package/dist/src/sweep.js +0 -224
  388. package/dist/src/test-joins.js +0 -101
  389. package/dist/src/version.js +0 -7
  390. package/dist/src/workflow-steps.js +0 -185
  391. package/src/base-url.ts +0 -24
  392. package/src/discover.ts +0 -653
  393. package/src/entry-points.ts +0 -687
  394. package/src/glob.ts +0 -67
  395. package/src/manifest.ts +0 -234
  396. package/src/matrix.ts +0 -423
  397. package/src/pnpm-workspace.ts +0 -135
  398. package/src/setup-failure.ts +0 -39
  399. package/src/sweep.ts +0 -347
  400. package/src/test-joins.ts +0 -120
  401. package/src/version.ts +0 -7
  402. package/src/workflow-steps.ts +0 -216
@@ -1,17 +1,15 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Finding } from "../finding.ts";
2
- import { OUTSIDE, type Graph } from "../graph.ts";
3
- import type { InventorySymbol, SymbolKind } from "../inventory.ts";
4
- import type { MatrixCandidate } from "../matrix.ts";
3
+ import { OUTSIDE, type Graph } from "../liveness/graph.ts";
4
+ import { WORDS, type InventorySymbol } from "../inventory.ts";
5
+ import type { Candidate } from "../liveness/sweep.ts";
5
6
  import { declarationKey } from "../ref.ts";
6
- import type { RootKind } from "../roots.ts";
7
- import { runnerCalls } from "../sweep.ts";
7
+ import type { RootKind } from "../roots/root-set.ts";
8
+ import { runnerCalls } from "../liveness/sweep.ts";
9
+ import { exposedTypes, exposes } from "../exposure.ts";
8
10
  import type { Emitter, EmitterInput } from "./emitter.ts";
9
11
  import { writeOnlyDeclarations } from "./reads-and-writes.ts";
10
12
 
11
- const UNNECESSARY_EXPORT = "DS1101";
12
- const UNREACHABLE_EXPORT = "DS1103";
13
- const REDUNDANT_EXPORT_KEYWORD = "DS1104";
14
-
15
13
  /** How far the references to one declaration reach, from the narrowest. */
16
14
  type Reach = "file" | "package" | "outside";
17
15
 
@@ -22,18 +20,6 @@ type Reach = "file" | "package" | "outside";
22
20
  */
23
21
  const PUBLISHING: ReadonlySet<RootKind> = new Set<RootKind>(["manifest-entry", "published-api"]);
24
22
 
25
- /** The reader's word for each kind of declaration this family reports. */
26
- const WORDS: Partial<Readonly<Record<SymbolKind, string>>> = {
27
- function: "function",
28
- class: "class",
29
- interface: "interface",
30
- type: "type alias",
31
- enum: "enum",
32
- namespace: "namespace",
33
- variable: "variable",
34
- "export-alias": "re-export",
35
- };
36
-
37
23
  /** Per declaration of the run, the facts both rules read. */
38
24
  interface Facts {
39
25
  readonly graph: Graph;
@@ -145,14 +131,10 @@ function topLevelExport(facts: Facts, at: number, symbol: InventorySymbol): bool
145
131
  return symbol.exported && parent !== OUTSIDE && facts.graph.symbols[parent]?.kind === "file";
146
132
  }
147
133
 
148
- function wordOf(symbol: InventorySymbol): string {
149
- return WORDS[symbol.kind] ?? symbol.kind;
150
- }
151
-
152
134
  /** The visibility each narrowing code names, which only the narrowing codes carry. */
153
135
  const NARROWER: Readonly<Record<string, "file" | "package">> = {
154
- [REDUNDANT_EXPORT_KEYWORD]: "file",
155
- [UNNECESSARY_EXPORT]: "package",
136
+ [CODE.redundantExportKeyword]: "file",
137
+ [CODE.unnecessaryExport]: "package",
156
138
  };
157
139
 
158
140
  function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
@@ -184,7 +166,11 @@ function narrowing(
184
166
  facts: Facts,
185
167
  at: number,
186
168
  symbol: InventorySymbol,
187
- world: { readonly closed: boolean; readonly writeOnly: ReadonlySet<string> },
169
+ world: {
170
+ readonly closed: boolean;
171
+ readonly writeOnly: ReadonlySet<string>;
172
+ readonly exposed: ReadonlySet<string> | "every";
173
+ },
188
174
  ): Finding | undefined {
189
175
  const { closed } = world;
190
176
  const file = facts.fileOf[at] ?? OUTSIDE;
@@ -198,7 +184,7 @@ function narrowing(
198
184
  ) {
199
185
  return undefined;
200
186
  }
201
- const word = wordOf(symbol);
187
+ const word = WORDS[symbol.kind];
202
188
  switch (facts.reach[at]) {
203
189
  case "file":
204
190
  if (facts.published[file] === true && !closed) {
@@ -206,16 +192,16 @@ function narrowing(
206
192
  }
207
193
  return findingOf(
208
194
  symbol,
209
- REDUNDANT_EXPORT_KEYWORD,
195
+ CODE.redundantExportKeyword,
210
196
  `exported ${word} is referenced only inside the file that declares it`,
211
197
  );
212
198
  case "package":
213
- if (!closed || !publishes(facts.rootedBy[at])) {
199
+ if (!closed || !publishes(facts.rootedBy[at]) || exposes(world.exposed, symbol)) {
214
200
  return undefined;
215
201
  }
216
202
  return findingOf(
217
203
  symbol,
218
- UNNECESSARY_EXPORT,
204
+ CODE.unnecessaryExport,
219
205
  `exported ${word} is referenced only inside the package that declares it`,
220
206
  );
221
207
  default:
@@ -234,7 +220,7 @@ function unreachable(
234
220
  facts: Facts,
235
221
  at: number,
236
222
  symbol: InventorySymbol,
237
- candidate: MatrixCandidate,
223
+ candidate: Candidate,
238
224
  input: EmitterInput,
239
225
  ): Finding | undefined {
240
226
  if (
@@ -254,13 +240,13 @@ function unreachable(
254
240
  : "has no reference in the target";
255
241
  return findingOf(
256
242
  symbol,
257
- UNREACHABLE_EXPORT,
258
- `exported ${wordOf(symbol)} ${found}, and nothing outside can import the file that declares it`,
243
+ CODE.unreachableExport,
244
+ `exported ${WORDS[symbol.kind]} ${found}, and nothing outside can import the file that declares it`,
259
245
  );
260
246
  }
261
247
 
262
248
  /**
263
- * The visibility-narrowing family over one run: `DS1101`, `DS1103` and `DS1104`. A finding
249
+ * The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
264
250
  * about a symbol a declared cross-language edge names is reported here like any other, and
265
251
  * the edge's evaluation then holds it pending, because the edge stands for a reference from
266
252
  * outside the file and the package whose use is decided in the other language. A
@@ -269,13 +255,23 @@ function unreachable(
269
255
  export function narrowings(input: EmitterInput): readonly Finding[] {
270
256
  const facts = factsOf(input.swept.matrix.union);
271
257
  const { declared, loaded } = input.boundary.consumers;
272
- const closed = input.config.consumersComplete && declared.every((id) => loaded.includes(id));
273
- const world = { closed, writeOnly: writeOnlyDeclarations(input) };
258
+ const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
259
+ const world = {
260
+ closed,
261
+ writeOnly: writeOnlyDeclarations(input),
262
+ exposed: closed
263
+ ? exposedTypes(
264
+ facts.graph,
265
+ facts.reach.map((reach) => reach === "outside"),
266
+ input.boundary.carriers,
267
+ )
268
+ : new Set<string>(),
269
+ };
274
270
  const candidates = new Map(
275
271
  input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]),
276
272
  );
277
273
  const generated =
278
- input.config.analysis.generatedFiles === "include" ? undefined : input.files.generated;
274
+ input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
279
275
 
280
276
  const findings: Finding[] = [];
281
277
  facts.graph.symbols.forEach((symbol, at) => {
@@ -316,5 +312,5 @@ export function unreachableExports(input: EmitterInput): ReadonlySet<string> {
316
312
  return found;
317
313
  }
318
314
 
319
- /** The findings of the visibility-narrowing family, `DS1100` to `DS1199`. */
315
+ /** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
320
316
  export const visibilityNarrowing: Emitter = narrowings;
@@ -4,8 +4,9 @@
4
4
  * verdict a report carries beside them.
5
5
  */
6
6
 
7
+ import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.ts";
7
8
  import type { AnalysisInputs } from "./analysis.ts";
8
- import { fails, type Config } from "./config.ts";
9
+ import { fails, type ResolvedConfig } from "./config.ts";
9
10
  import type { EdgeEvaluation } from "./edges.ts";
10
11
  import type { CompletedFinding, Finding } from "./finding.ts";
11
12
  import { decidedFindings, type Withheld } from "./findings/emitters.ts";
@@ -15,15 +16,10 @@ import {
15
16
  type StaleRecord,
16
17
  type StaleSuppression,
17
18
  } from "./findings/self-check.ts";
18
- import {
19
- isDependencySubject,
20
- isRowSubject,
21
- pairs,
22
- suppressionCounts,
23
- type Ledger,
24
- } from "./suppress.ts";
19
+ import { namesKind, pairs, suppressionCounts, type Ledger } from "./suppress.ts";
25
20
  import type { Provenance, Recorded } from "./suppress-file.ts";
26
- import { EXIT_CLEAN, EXIT_FINDINGS } from "./verbs/verb.ts";
21
+ import { bytewise } from "./text/compare.ts";
22
+ import { PRODUCT } from "./product.ts";
27
23
 
28
24
  /** The counts a report's totals carry beside its findings. */
29
25
  interface PassTotals {
@@ -49,25 +45,14 @@ export interface PassResult {
49
45
  readonly ledger: Ledger;
50
46
  }
51
47
 
52
- /** The exit code of a report holding at least one pending finding. */
53
- export const EXIT_PENDING = 4;
54
-
55
- /** Two strings ordered bytewise. */
56
- function compare(a: string, b: string): number {
57
- if (a === b) {
58
- return 0;
59
- }
60
- return a < b ? -1 : 1;
61
- }
62
-
63
48
  /** The canonical order: path, line, column, code, then the symbol reference. */
64
49
  function canonical(a: Finding, b: Finding): number {
65
50
  return (
66
- compare(a.position.path, b.position.path) ||
51
+ bytewise(a.position.path, b.position.path) ||
67
52
  a.position.line - b.position.line ||
68
53
  a.position.column - b.position.column ||
69
- compare(a.code, b.code) ||
70
- compare(a.symbol.ref, b.symbol.ref)
54
+ bytewise(a.code, b.code) ||
55
+ bytewise(a.symbol.ref, b.symbol.ref)
71
56
  );
72
57
  }
73
58
 
@@ -118,7 +103,7 @@ export function findingsPass(inputs: AnalysisInputs): PassResult {
118
103
  * suppression is stale or a finding's severity reaches the configured failing severity;
119
104
  * clean otherwise.
120
105
  */
121
- export function verdictOf(result: PassResult, config: Config): number {
106
+ export function verdictOf(result: PassResult, config: ResolvedConfig): number {
122
107
  if (result.totals.pending > 0) {
123
108
  return EXIT_PENDING;
124
109
  }
@@ -126,7 +111,7 @@ export function verdictOf(result: PassResult, config: Config): number {
126
111
  return EXIT_FINDINGS;
127
112
  }
128
113
  const failing = result.findings.some((finding) =>
129
- fails(finding.severity, config.reporters.failOn),
114
+ fails(finding.severity, config.reporters.fail_on),
130
115
  );
131
116
  return failing ? EXIT_FINDINGS : EXIT_CLEAN;
132
117
  }
@@ -140,15 +125,14 @@ export interface RecordableFinding {
140
125
 
141
126
  /**
142
127
  * The rows one round of a baseline write records from its findings, in their order. A
143
- * finding about a row of a document other than a dependency row is not recorded: no record
144
- * withholds one, so a row recording it would be stale on the first read back. Two findings
145
- * that would record the same row, two parts of one declaration under one code, record it
146
- * once.
128
+ * finding no baseline row can name is not recorded: a row recording it would be stale on
129
+ * the first read back. Two findings that would record the same row, two parts of one
130
+ * declaration under one code, record it once.
147
131
  */
148
132
  export function recordedFindings(findings: readonly RecordableFinding[]): readonly Recorded[] {
149
133
  const rows = new Map<string, Recorded>();
150
134
  for (const finding of findings) {
151
- if (isRowSubject(finding.symbol.kind) && !isDependencySubject(finding.symbol.kind)) {
135
+ if (!namesKind(finding.symbol.kind, "baseline")) {
152
136
  continue;
153
137
  }
154
138
  const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
@@ -164,5 +148,5 @@ export function rowKey(row: Recorded): string {
164
148
 
165
149
  /** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
166
150
  export function analyzerProvenance(version: string): Provenance {
167
- return { analyzer: "deadset-ts", version };
151
+ return { analyzer: PRODUCT, version };
168
152
  }
@@ -1,9 +1,10 @@
1
1
  /**
2
- * The framework-lifecycle class: a member a `ts.lifecycle_contracts` entry lists is held
3
- * back on each class that is the entry's component, being a class a decorator or a call
4
- * the entry's `components` name is attached to or passes, or one extending, directly or
5
- * through a chain, a class its `bases` name. A component's members are its type's, so a
6
- * member a base class declares answers for every subclass.
2
+ * The framework-lifecycle class: a member a lifecycle contract lists is held back on each
3
+ * class that is the contract's component, being a class a decorator or a call the
4
+ * contract's `components` name is attached to or passes, or one extending, directly or
5
+ * through a chain, a class its `bases` name. The contracts are the platform's custom
6
+ * elements and each `ts.lifecycle_contracts` entry. A component's members are its type's,
7
+ * so a member a base class declares answers for every subclass.
7
8
  */
8
9
 
9
10
  import {
@@ -26,13 +27,37 @@ import {
26
27
  resolveEntries,
27
28
  spelledEntry,
28
29
  type ResolvedEntry,
29
- } from "./configured-declarations.ts";
30
+ } from "./roots/configured-declarations.ts";
30
31
  import type { DetectorInput, Evidence } from "./exempt.ts";
31
32
  import { nodeKey, type Inventory } from "./inventory.ts";
32
33
  import { renderPosition } from "./position.ts";
33
34
  import { must } from "./query.ts";
34
35
  import type { ProjectView } from "./session.ts";
35
36
 
37
+ /**
38
+ * The platform's own contract (references-ts.38): `CustomElementRegistry.define` reads
39
+ * these members off the class it registers, its constructor's and its prototype's
40
+ * (https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define),
41
+ * and calls each callback as a custom element reaction.
42
+ */
43
+ const CUSTOM_ELEMENTS: LifecycleContract = {
44
+ components: [{ global: "CustomElementRegistry.define" }],
45
+ members: [
46
+ "observedAttributes:static",
47
+ "disabledFeatures:static",
48
+ "formAssociated:static",
49
+ "connectedCallback",
50
+ "disconnectedCallback",
51
+ "connectedMoveCallback",
52
+ "adoptedCallback",
53
+ "attributeChangedCallback",
54
+ "formAssociatedCallback",
55
+ "formResetCallback",
56
+ "formDisabledCallback",
57
+ "formStateRestoreCallback",
58
+ ],
59
+ };
60
+
36
61
  /** One class that is a component of one contract, and what says it is. */
37
62
  interface Component {
38
63
  readonly contract: LifecycleContract;
@@ -55,8 +80,8 @@ function resolveContracts<Brand>(
55
80
  contracts: readonly LifecycleContract[],
56
81
  ): readonly ResolvedContract[] {
57
82
  const entries: DeclarationEntry[] = contracts.flatMap((contract) => [
58
- ...contract.components,
59
- ...contract.bases,
83
+ ...(contract.components ?? []),
84
+ ...(contract.bases ?? []),
60
85
  ]);
61
86
  const resolved = resolveEntries(project, held, entries);
62
87
  let next = 0;
@@ -67,8 +92,8 @@ function resolveContracts<Brand>(
67
92
  };
68
93
  return contracts.map((contract) => ({
69
94
  contract,
70
- components: take(contract.components.length),
71
- bases: take(contract.bases.length),
95
+ components: take(contract.components?.length ?? 0),
96
+ bases: take(contract.bases?.length ?? 0),
72
97
  }));
73
98
  }
74
99
 
@@ -238,16 +263,17 @@ function byBases<Brand>(
238
263
 
239
264
  /**
240
265
  * The framework-lifecycle detector: each member a contract lists, on each class that
241
- * is a component of that contract's framework, recorded where the class was found to
242
- * be one.
266
+ * is a component of that contract's framework, recorded once per place where the class
267
+ * was found to be one.
243
268
  */
244
269
  export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
245
270
  const { project, held, targetRoot } = input;
246
- if (input.ts.lifecycleContracts.length === 0) {
271
+ const declared = [CUSTOM_ELEMENTS, ...input.ts.lifecycle_contracts];
272
+ const contracts = resolveContracts(project, held, declared);
273
+ if (contracts.every((one) => one.components.length === 0 && one.bases.length === 0)) {
247
274
  return [];
248
275
  }
249
276
  const chains = aliasChains(project, held);
250
- const contracts = resolveContracts(project, held, input.ts.lifecycleContracts);
251
277
  const components = [
252
278
  ...byComponents(project, held, chains, contracts),
253
279
  ...byBases(project, held, chains, contracts),
@@ -256,7 +282,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
256
282
  return [];
257
283
  }
258
284
  const ids = new Set(components.map((one) => one.id));
259
- const sides: readonly ClassSide[] = input.ts.lifecycleContracts.some((contract) =>
285
+ const sides: readonly ClassSide[] = declared.some((contract) =>
260
286
  contract.members.some((name) => name.endsWith(":static")),
261
287
  )
262
288
  ? ["instance", "static"]
@@ -264,7 +290,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
264
290
  const members = sides.map((side) => classMembers(project, held, ids, side));
265
291
  const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
266
292
 
267
- const found: Evidence[] = [];
293
+ const found = new Map<string, Evidence>();
268
294
  for (const component of components) {
269
295
  const listed = new Set(component.contract.members);
270
296
  const site = renderPosition(
@@ -275,10 +301,12 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
275
301
  for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
276
302
  const symbol = byId.get(member.id);
277
303
  const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
278
- if (spelled !== undefined && listed.has(spelled)) {
279
- found.push({ id: member.id, detail: component.detail, site });
304
+ // A configured contract may name the platform's registry too; one record answers both.
305
+ const key = `${member.id}\0${component.detail}\0${site.path}:${String(site.line)}:${String(site.column)}`;
306
+ if (spelled !== undefined && listed.has(spelled) && !found.has(key)) {
307
+ found.set(key, { id: member.id, detail: component.detail, site });
280
308
  }
281
309
  }
282
310
  }
283
- return found;
311
+ return [...found.values()];
284
312
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The one read of a document the target holds at its root, which a checkout under analysis
3
+ * controls: the repository configuration, the ignore file, the baseline and the edges
4
+ * document. The read stays inside the target root, takes a regular file alone, and holds no
5
+ * more of it than the Contract's document bound.
6
+ */
7
+
8
+ import { CONFIG_MAX_BYTES } from "../contract/config.generated.ts";
9
+ import type { Host } from "../host.ts";
10
+ import { failureExit } from "../failure.ts";
11
+ import { joinPath, relativePath } from "../paths.ts";
12
+ import { message } from "../product.ts";
13
+ import { utf8Bytes } from "../reporters/sha256.ts";
14
+ import { UsageError } from "../usage.ts";
15
+
16
+ function unreadable(label: string, reason: string): Error {
17
+ return failureExit(
18
+ "unreadable-document",
19
+ `${label}: ${reason}: make it a regular file inside the target root that the run can read, or remove it`,
20
+ );
21
+ }
22
+
23
+ /** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
24
+ export function refuseOverBound(label: string, text: string): void {
25
+ if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
26
+ throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
32
+ * there. One whose name resolves outside the target root or to anything but a regular file,
33
+ * or whose read fails, is refused by `refuse` naming it `label`, nothing read from it. A
34
+ * document past the bound is read one byte past it and refused as {@link refuseOverBound} does.
35
+ */
36
+ export function readTargetDocument(
37
+ host: Host,
38
+ targetRoot: string,
39
+ name: string,
40
+ label = name,
41
+ refuse: (label: string, reason: string) => Error = unreadable,
42
+ ): string | undefined {
43
+ const attempt = <T>(read: () => T): T => {
44
+ try {
45
+ return read();
46
+ } catch (error: unknown) {
47
+ throw refuse(label, message(error));
48
+ }
49
+ };
50
+ const path = joinPath(targetRoot, name);
51
+ const kind = attempt(() => host.kindOf(path));
52
+ if (kind === "absent") {
53
+ return undefined;
54
+ }
55
+ const real = attempt(() => host.realPath(path));
56
+ if (
57
+ relativePath(
58
+ attempt(() => host.realPath(targetRoot)),
59
+ real,
60
+ ) === undefined
61
+ ) {
62
+ throw refuse(label, `resolves to ${real}, outside the target root`);
63
+ }
64
+ if (kind !== "file") {
65
+ throw refuse(label, "is not a regular file");
66
+ }
67
+ const text = attempt(() => host.readFile(real, CONFIG_MAX_BYTES + 1));
68
+ refuseOverBound(label, text);
69
+ return text;
70
+ }
@@ -0,0 +1,96 @@
1
+ import type { Host } from "../host.ts";
2
+ import { isRecord } from "../json-document.ts";
3
+ import { joinPath } from "../paths.ts";
4
+ import { message } from "../product.ts";
5
+
6
+ /** The file a package's manifest is read from. */
7
+ export const MANIFEST = "package.json";
8
+
9
+ /** The directory a package manager installs packages into, whose files are no project's own. */
10
+ export const INSTALLED_PACKAGES = "node_modules";
11
+
12
+ /** The manifest members a package declares the packages it depends on under. */
13
+ export const DEPENDENCY_SECTIONS = [
14
+ "dependencies",
15
+ "devDependencies",
16
+ "peerDependencies",
17
+ "optionalDependencies",
18
+ ] as const;
19
+
20
+ /** Whether one path passes through an installed-packages directory. */
21
+ export function inInstalledPackages(path: string): boolean {
22
+ return path.split("/").includes(INSTALLED_PACKAGES);
23
+ }
24
+
25
+ /** What one JSON file reads as: its text and decoded value, text that does not parse, or a file that cannot be read, with the read's error. */
26
+ type JSONRead =
27
+ | { readonly kind: "read"; readonly text: string; readonly value: unknown }
28
+ | { readonly kind: "malformed" }
29
+ | { readonly kind: "unread"; readonly reason: string };
30
+
31
+ /** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
32
+ export function readJSONFile(host: Host, path: string): JSONRead {
33
+ let text: string;
34
+ try {
35
+ text = host.readFile(path);
36
+ } catch (error: unknown) {
37
+ return { kind: "unread", reason: message(error) };
38
+ }
39
+ try {
40
+ return { kind: "read", text, value: JSON.parse(text) as unknown };
41
+ } catch {
42
+ return { kind: "malformed" };
43
+ }
44
+ }
45
+
46
+ /** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
47
+ export function readDocument(
48
+ host: Host,
49
+ path: string,
50
+ ): { text: string; value: unknown } | undefined {
51
+ const read = readJSONFile(host, path);
52
+ return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
53
+ }
54
+
55
+ /** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
56
+ export function readJSON(host: Host, path: string): unknown {
57
+ return readDocument(host, path)?.value;
58
+ }
59
+
60
+ /**
61
+ * The manifest in one directory, decoded, or undefined where none reads as an object. A
62
+ * manifest this program cannot read declares nothing, the same answer as an absent one:
63
+ * the package manager owns the refusal of a manifest it rejects.
64
+ */
65
+ export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
66
+ const value = readJSON(host, joinPath(dir, MANIFEST));
67
+ return isRecord(value) ? value : undefined;
68
+ }
69
+
70
+ /**
71
+ * What one directory's manifest reads as: none, an object, a file that does not parse as one,
72
+ * or a file that cannot be read, with the read's error. Either of the last two still bounds
73
+ * the package it would declare.
74
+ */
75
+ export type ManifestRead =
76
+ | { readonly kind: "absent" }
77
+ | { readonly kind: "read"; readonly manifest: Record<string, unknown> }
78
+ | { readonly kind: "malformed" }
79
+ | { readonly kind: "unread"; readonly reason: string };
80
+
81
+ /** The manifest in one directory, as {@link ManifestRead} tells its states apart. */
82
+ export function readManifestAt(host: Host, dir: string): ManifestRead {
83
+ if (!holdsManifest(host, dir)) {
84
+ return { kind: "absent" };
85
+ }
86
+ const read = readJSONFile(host, joinPath(dir, MANIFEST));
87
+ if (read.kind !== "read") {
88
+ return read;
89
+ }
90
+ return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
91
+ }
92
+
93
+ /** Whether one directory holds a manifest file. */
94
+ export function holdsManifest(host: Host, dir: string): boolean {
95
+ return host.kindOf(joinPath(dir, MANIFEST)) === "file";
96
+ }
package/src/fs/tree.ts ADDED
@@ -0,0 +1,110 @@
1
+ import type { DirectoryEntry, Host } from "../host.ts";
2
+ import { ancestors, joinPath, resolvePath } from "../paths.ts";
3
+ import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
4
+ import { LANGUAGE } from "../product.ts";
5
+ import { bytewise } from "../text/compare.ts";
6
+ import { globExpression } from "../text/glob.ts";
7
+
8
+ /** The directories no walk of the tree enters, by the patterns the language's row names. */
9
+ const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
10
+
11
+ function skipped(name: string): boolean {
12
+ return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
13
+ }
14
+
15
+ /** A git repository's working tree root, and the `info/exclude` file git reads for it. */
16
+ interface Repository {
17
+ readonly root: string;
18
+ /** Undefined where a `.git` file names no repository directory. */
19
+ readonly exclude: string | undefined;
20
+ }
21
+
22
+ /** One file's text without its line ending, or undefined where it cannot be read. */
23
+ function lineOf(host: Host, path: string): string | undefined {
24
+ try {
25
+ return host.readFile(path).replace(/\r?\n$/u, "");
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * The repository whose working tree holds `dir`: the nearest directory at or above it with a
33
+ * `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
34
+ * directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
35
+ * the directory shared with the main worktree, which holds `info/exclude`:
36
+ * https://git-scm.com/docs/gitrepository-layout
37
+ */
38
+ export function repositoryOf(host: Host, dir: string): Repository | undefined {
39
+ for (const root of ancestors(dir)) {
40
+ const dotGit = joinPath(root, ".git");
41
+ const kind = host.kindOf(dotGit);
42
+ if (kind === "directory") {
43
+ return { root, exclude: joinPath(dotGit, "info/exclude") };
44
+ }
45
+ if (kind === "file") {
46
+ const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
47
+ const gitDir = named === undefined ? undefined : resolvePath(root, named);
48
+ const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
49
+ const shared =
50
+ gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
51
+ return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ /** The entries of one directory, none where it cannot be read. */
58
+ export function entriesOf(host: Host, dir: string): readonly DirectoryEntry[] {
59
+ try {
60
+ return host.readDirectory(dir);
61
+ } catch {
62
+ return [];
63
+ }
64
+ }
65
+
66
+ /** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
67
+ export function filesIn(
68
+ host: Host,
69
+ dir: string,
70
+ accepts: (name: string) => boolean,
71
+ ): readonly string[] {
72
+ return entriesOf(host, dir)
73
+ .filter((entry) => !entry.directory && accepts(entry.name))
74
+ .map((entry) => entry.name)
75
+ .sort(bytewise);
76
+ }
77
+
78
+ /** The names of the directories directly in `dir` a walk enters, in bytewise order. */
79
+ export function directoriesIn(host: Host, dir: string): readonly string[] {
80
+ return entriesOf(host, dir)
81
+ .filter((entry) => entry.directory && !skipped(entry.name))
82
+ .map((entry) => entry.name)
83
+ .sort(bytewise);
84
+ }
85
+
86
+ /**
87
+ * Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
88
+ * order, read in no skipped directory.
89
+ */
90
+ export function filesBelow(
91
+ host: Host,
92
+ root: string,
93
+ accepts: (name: string) => boolean,
94
+ ): readonly string[] {
95
+ const found: string[] = [];
96
+ const pending = [""];
97
+ for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
98
+ for (const entry of entriesOf(host, joinPath(root, dir))) {
99
+ const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
100
+ if (!entry.directory) {
101
+ if (accepts(entry.name)) {
102
+ found.push(path);
103
+ }
104
+ } else if (!skipped(entry.name)) {
105
+ pending.push(path);
106
+ }
107
+ }
108
+ }
109
+ return found.sort(bytewise);
110
+ }