@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
@@ -0,0 +1,499 @@
1
+ /**
2
+ * The words layer 3 of the command reader reads: each word of a command string as the vendored
3
+ * shell parser reads it, and each token of a value of a command file that is no command, each
4
+ * with whether it may write the file a command runs. `text/named-files.ts` reads the files
5
+ * they name. Comments and keys are never words.
6
+ */
7
+ import { forEachChild, parse, } from "../vendor/unbash/parser.js";
8
+ import { expressionsAsParts, heldTextsOf, parseLine, PART, wordOf } from "./shell.js";
9
+ import { fileOperands, outputOf } from "./tools.js";
10
+ /** A `uses:` value that references a repository's action, never a local path (https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsuses). */
11
+ const ACTION_REFERENCE = /^[\w-][\w.-]*\/[\w.-]+(?:\/[\w.-]+)*@[\w./-]+$/u;
12
+ /** The word of a command whose runtime runs a file no word of it writes, as another program hands it the path. */
13
+ const UNWRITTEN = { text: PART, runs: true };
14
+ const NO_STDIN = { content: [], piped: [], unknown: false };
15
+ const UNKNOWN_STDIN = { ...NO_STDIN, unknown: true };
16
+ /** Whether `redirect` reads into descriptor 0, the default of `<`, `<>`, `<&`, `<<` and `<<<`. */
17
+ function readsInput(redirect) {
18
+ const { descriptor } = redirect;
19
+ return ((redirect.type !== "Redirect" || ["<", "<>", "<&"].includes(redirect.operator)) &&
20
+ (descriptor === undefined || (descriptor.type === "FileDescriptor" && descriptor.value === 0)));
21
+ }
22
+ /**
23
+ * The standard input `redirects` give a command, the last that reads descriptor 0 deciding as
24
+ * the shell applies them in order, or `inherited` where none does. A `<&` duplicates a
25
+ * descriptor the line does not show.
26
+ */
27
+ function stdinOf(redirects, inherited) {
28
+ const last = redirects.findLast(readsInput);
29
+ if (last === undefined) {
30
+ return inherited;
31
+ }
32
+ switch (last.type) {
33
+ case "HereDoc":
34
+ return { ...NO_STDIN, content: [last.body.text] };
35
+ case "HereString":
36
+ return { ...NO_STDIN, content: last.target === undefined ? [] : heldTextsOf(last.target) };
37
+ default:
38
+ return last.operator === "<&" || last.target === undefined
39
+ ? UNKNOWN_STDIN
40
+ : { ...NO_STDIN, file: wordOf(last.target) };
41
+ }
42
+ }
43
+ /** The words of a simple command, its program word first. */
44
+ function wordsOf(node) {
45
+ return node.type === "Command" && node.name !== undefined
46
+ ? [node.name, ...node.suffix.filter((part) => part.type === "Word")]
47
+ : [];
48
+ }
49
+ /** What the pipeline member `member`, whose own standard input `inputs` holds, writes to the next, as {@link outputOf} reads it. */
50
+ function pipedFrom(member, inputs) {
51
+ const words = wordsOf(member);
52
+ const output = words.length === 0 ? undefined : outputOf(words.map(wordOf));
53
+ if (output === undefined) {
54
+ return UNKNOWN_STDIN;
55
+ }
56
+ const own = output.input ? (inputs.get(member) ?? NO_STDIN) : NO_STDIN;
57
+ const textsAt = (at) => {
58
+ const word = words[at];
59
+ return word === undefined ? [] : heldTextsOf(word);
60
+ };
61
+ const lines = [
62
+ ...output.at.flatMap(textsAt),
63
+ ...output.joined.map(([from, to]) => words
64
+ .slice(from, to)
65
+ .flatMap((_, at) => textsAt(from + at))
66
+ .join(" ")),
67
+ ...output.texts,
68
+ ];
69
+ return { ...own, piped: [...own.piped, ...lines] };
70
+ }
71
+ /** How many times a quoted text, a here-document's body or a `-c` value is read as a command line in turn. */
72
+ const DEPTH = 4;
73
+ /** A `\\` a path may hold: none that ends a line, nor one before a backquote, which it escapes. */
74
+ const BACKSLASH_PATH = /\\(?![\r\n`])/gu;
75
+ /** Text a shell may read as more than one word: a blank or an operator. */
76
+ const LINE_SYNTAX = /[\s;&|<>()]/u;
77
+ /** A shell parameter's name, or a special parameter, after its `$`. */
78
+ const PARAMETER = /[A-Za-z_]\w*|[0-9@*#?!$-]/uy;
79
+ /** The place after the brackets that open at `at`, nested ones included, or the text's end. */
80
+ function closing(text, at, open, close) {
81
+ let depth = 0;
82
+ for (let next = at; next < text.length; next += 1) {
83
+ depth += text.charAt(next) === open ? 1 : text.charAt(next) === close ? -1 : 0;
84
+ if (depth === 0) {
85
+ return next + 1;
86
+ }
87
+ }
88
+ return text.length;
89
+ }
90
+ /** The text inside each outermost command substitution `text`, a line that does not parse, writes. */
91
+ function substitutions(text) {
92
+ const found = [];
93
+ for (let at = text.indexOf("$("); at >= 0; at = text.indexOf("$(", at + 1)) {
94
+ const end = closing(text, at + 1, "(", ")");
95
+ found.push(text.slice(at + 2, text.charAt(end - 1) === ")" ? end - 1 : end));
96
+ at = end - 1;
97
+ }
98
+ return found;
99
+ }
100
+ /**
101
+ * `text`, a line that does not parse, with each expansion it writes as a part: a parameter
102
+ * expansion, a command substitution or an arithmetic expansion to its matching bracket, and
103
+ * a backquoted command to its closing backquote, each to the text's end where none closes it.
104
+ */
105
+ function expansionsAsParts(text) {
106
+ let read = "";
107
+ for (let at = 0; at < text.length; at += 1) {
108
+ const char = text.charAt(at);
109
+ const next = text.charAt(at + 1);
110
+ let end = at;
111
+ if (char === "`") {
112
+ const close = text.indexOf("`", at + 1);
113
+ end = close < 0 ? text.length : close + 1;
114
+ }
115
+ else if (char === "$" && (next === "{" || next === "(")) {
116
+ end = closing(text, at + 1, next, next === "{" ? "}" : ")");
117
+ }
118
+ else if (char === "$") {
119
+ PARAMETER.lastIndex = at + 1;
120
+ end = at + 1 + (PARAMETER.exec(text)?.[0].length ?? -1);
121
+ }
122
+ if (end > at) {
123
+ read += PART;
124
+ at = end - 1;
125
+ }
126
+ else {
127
+ read += char;
128
+ }
129
+ }
130
+ return read;
131
+ }
132
+ /** A character a shell reads as syntax, a glob or a quote. */
133
+ const METACHARACTER = /[|&;<>()"'`*?[\]{}~\\]/gu;
134
+ /** An operator, which ends a shell word wherever it stands outside quotes. */
135
+ const OPERATOR = /[;&|<>()]+/u;
136
+ /**
137
+ * The texts one token stands for as one shell word, an assignment's value for an assignment,
138
+ * or `undefined` where it is not one.
139
+ */
140
+ function tokenTexts(token) {
141
+ const script = parse(token);
142
+ const [statement, ...more] = script.commands;
143
+ const command = statement?.command;
144
+ if ((script.errors ?? []).length > 0 || more.length > 0 || command?.type !== "Command") {
145
+ return undefined;
146
+ }
147
+ const [assignment, ...others] = command.prefix;
148
+ if (command.name === undefined) {
149
+ return others.length === 0 &&
150
+ command.suffix.length === 0 &&
151
+ assignment?.type === "Assignment" &&
152
+ assignment.value.type === "Word"
153
+ ? heldTextsOf(assignment.value)
154
+ : undefined;
155
+ }
156
+ return command.prefix.length === 0 && command.suffix.length === 0
157
+ ? heldTextsOf(command.name)
158
+ : undefined;
159
+ }
160
+ /**
161
+ * The texts one blank-separated token of a line that does not parse stands for: each
162
+ * expansion in it a part, then each piece of it between operators as the one word it is,
163
+ * else with its quotes removed, else with each other character a shell reads as syntax a part.
164
+ */
165
+ function tokenPieces(token) {
166
+ return expansionsAsParts(token)
167
+ .split(OPERATOR)
168
+ .filter((piece) => piece !== "")
169
+ .flatMap((piece) => tokenTexts(piece) ??
170
+ tokenTexts(piece.replaceAll(/["']/gu, "")) ?? [piece.replaceAll(METACHARACTER, PART)]);
171
+ }
172
+ /** A token that ends a simple command, as a `;`, `&`, `|` or parenthesis it ends with does, and one that starts the next. */
173
+ const ENDS_COMMAND = /[;&|()]$/u;
174
+ const STARTS_COMMAND = /^[;&|()]/u;
175
+ /** The reserved words that may stand before a simple command's program word in a line. */
176
+ const RESERVED = new Set(["if", "then", "else", "elif", "fi", "do", "done", "while", "until"].concat([
177
+ "case",
178
+ "esac",
179
+ "{",
180
+ "}",
181
+ "!",
182
+ "time",
183
+ ]));
184
+ /** A token that redirects: its descriptor, its operator, and its target where the token writes it. */
185
+ const REDIRECT = /^(\d*)(&>>|&>|<<<|<<-|<<|<>|<&|>&|>>|>\||<|>)(.*)$/su;
186
+ /**
187
+ * The standard input one redirect token of a line that does not parse gives: a file for `<`
188
+ * or `<>`, written as `target` writes it, and for a here-document, a here-string or a `<&`,
189
+ * text this reading does not take apart.
190
+ */
191
+ function tokenStdin(operator, target) {
192
+ if (operator !== "<" && operator !== "<>") {
193
+ return UNKNOWN_STDIN;
194
+ }
195
+ const text = target.replace(/[;&|)]+$/u, "").replaceAll(/["']/gu, "");
196
+ return {
197
+ ...NO_STDIN,
198
+ file: { value: text.includes(PART) ? undefined : text, text, written: text },
199
+ };
200
+ }
201
+ /**
202
+ * {@link fileOperands} of `words`, the tokens `tokens` of a line read without its structure,
203
+ * one simple command from each operator token to the next, past the reserved words it starts
204
+ * with, its redirect tokens read as {@link tokenStdin} reads them, each first word `program`'s.
205
+ * A command after a `|` reads what no row reads, and any other `stdin`, the line's own; a
206
+ * reader of what no word writes there runs a file no word writes.
207
+ */
208
+ function commandsOf(tokens, words, program, stdin) {
209
+ const places = new Set();
210
+ const lines = new Map();
211
+ const replaced = new Map();
212
+ const joined = [];
213
+ const texts = [];
214
+ let unwritten = false;
215
+ let input = false;
216
+ let stdinRead = false;
217
+ for (let from = 0; from < words.length;) {
218
+ let to = from;
219
+ while (to < words.length && !(to > from && STARTS_COMMAND.test(tokens[to] ?? ""))) {
220
+ to += 1;
221
+ if (ENDS_COMMAND.test(tokens[to - 1] ?? "")) {
222
+ break;
223
+ }
224
+ }
225
+ const piped = /(?<!\|)\|$/u.test(tokens[from - 1] ?? "") || /^\|(?!\|)/u.test(tokens[from] ?? "");
226
+ let fed = piped ? UNKNOWN_STDIN : stdin;
227
+ const kept = [];
228
+ let start = from;
229
+ while (start < to && RESERVED.has(tokens[start] ?? "")) {
230
+ start += 1;
231
+ }
232
+ for (let at = start; at < to; at += 1) {
233
+ const [, descriptor, operator = "", rest = ""] = REDIRECT.exec(tokens[at] ?? "") ?? [];
234
+ if (descriptor === undefined) {
235
+ kept.push(at);
236
+ continue;
237
+ }
238
+ const target = rest === "" && at + 1 < to ? (tokens[(at += 1)] ?? "") : rest;
239
+ if ((descriptor === "" || descriptor === "0") && operator.startsWith("<")) {
240
+ fed = tokenStdin(operator, target);
241
+ }
242
+ }
243
+ const one = fileOperands(kept.map((at) => words[at] ?? { value: undefined, text: "" }), program, fed.file);
244
+ const place = (at) => kept[at] ?? to;
245
+ for (const at of one.places) {
246
+ places.add(place(at));
247
+ }
248
+ for (const [at, prefix] of one.lines) {
249
+ lines.set(place(at), prefix);
250
+ }
251
+ for (const [at, pattern] of one.replaced) {
252
+ replaced.set(place(at), pattern);
253
+ }
254
+ joined.push(...one.joined.map(([start, end]) => [place(start), place(end)]));
255
+ texts.push(...one.texts);
256
+ unwritten ||=
257
+ one.unwritten ||
258
+ ((one.input || one.stdin) && (fed.unknown || fed.content.length > 0 || fed.piped.length > 0));
259
+ input ||= one.input;
260
+ stdinRead ||= one.stdin;
261
+ from = to;
262
+ }
263
+ return { places, unwritten, lines, input, stdin: stdinRead, replaced, joined, texts };
264
+ }
265
+ /**
266
+ * The words of one line read without its structure: the whole line, each blank-separated
267
+ * token as {@link tokenPieces} reads it, as written, so the words inside an expansion count,
268
+ * and with each expansion a part, so a blank inside one splits nothing, and the text of each
269
+ * command substitution as a command line. A token may write the file a command runs at the
270
+ * places {@link fileOperands} gives, `program` saying whether the first token is a program word.
271
+ */
272
+ function tokenWords(line, program, depth, into, stdin = NO_STDIN) {
273
+ const read = expressionsAsParts(line);
274
+ into.push({ text: expansionsAsParts(read), runs: false });
275
+ // Past the depth a command line a program runs is read no more.
276
+ const asLine = (text, quoted) => {
277
+ if (depth > 0) {
278
+ lineWords(text, depth - 1, quoted, into);
279
+ }
280
+ else {
281
+ into.push(UNWRITTEN);
282
+ }
283
+ };
284
+ for (const inner of substitutions(read)) {
285
+ asLine(inner, true);
286
+ }
287
+ const blanks = (text) => text.split(/\s+/u).filter((token) => token !== "");
288
+ for (const token of blanks(read)) {
289
+ for (const text of tokenPieces(token)) {
290
+ into.push({ text, runs: false });
291
+ }
292
+ }
293
+ const tokens = blanks(expansionsAsParts(read));
294
+ const texts = tokens.map((token) => tokenTexts(token) ?? tokenPieces(token));
295
+ const operands = commandsOf(tokens, texts.map(([only, ...more], at) => ({
296
+ value: more.length === 0 && only !== undefined && !only.includes(PART) ? only : undefined,
297
+ text: tokens[at] ?? "",
298
+ ...(more.length === 0 && only !== undefined ? { written: only } : {}),
299
+ })), program, stdin);
300
+ const replacedAt = (at) => (texts[at] ?? []).map((text) => {
301
+ const replaced = operands.replaced.get(at);
302
+ return replaced === undefined ? text : text.replace(replaced, PART);
303
+ });
304
+ texts.forEach((_, at) => {
305
+ for (const read of replacedAt(at)) {
306
+ const prefix = operands.lines.get(at);
307
+ into.push({ text: read, runs: operands.places.has(at) });
308
+ if (prefix !== undefined) {
309
+ asLine(read.slice(read.startsWith(prefix) ? prefix.length : 0), false);
310
+ }
311
+ }
312
+ });
313
+ for (const [from, to] of operands.joined) {
314
+ asLine(texts
315
+ .slice(from, to)
316
+ .flatMap((_, at) => replacedAt(from + at))
317
+ .join(" "), false);
318
+ }
319
+ for (const text of operands.texts) {
320
+ asLine(text, false);
321
+ }
322
+ if (operands.unwritten) {
323
+ into.push(UNWRITTEN);
324
+ }
325
+ }
326
+ /**
327
+ * The standard input of each simple command below `node`: its own, as {@link stdinOf} reads
328
+ * it, that of the nearest compound command around it, or `inherited`, what the command that
329
+ * hands the line on reads; each later member of a pipeline reads what the one before writes.
330
+ */
331
+ function inputsOf(node, inherited, into) {
332
+ const own = node.type === "Command" || node.type === "Redirected"
333
+ ? stdinOf(node.redirects, inherited)
334
+ : inherited;
335
+ if (node.type === "Command") {
336
+ into.set(node, own);
337
+ }
338
+ if (node.type === "Pipeline") {
339
+ let carried = own;
340
+ for (const member of node.commands) {
341
+ inputsOf(member, carried, into);
342
+ carried = pipedFrom(member, into);
343
+ }
344
+ return;
345
+ }
346
+ forEachChild(node, (child) => {
347
+ inputsOf(child, own, into);
348
+ });
349
+ }
350
+ /** The simple commands of `script` the command reader's first layer reads: each member of each pipeline of each and-or list. */
351
+ function topCommands(script) {
352
+ return new Set(script.commands.flatMap(({ command }) => (command.type === "AndOr" ? command.commands : [command]).flatMap((one) => one.type === "Pipeline" ? one.commands : [one])));
353
+ }
354
+ /**
355
+ * Each word one parsed command line writes, anywhere in its structure, into `into`. A quoted
356
+ * text or a here-document's body may be a command line a program it is handed to runs, so it
357
+ * is read as one in turn. Where {@link fileOperands} says a command hands a word, or words
358
+ * joined by blanks, to a shell, the shell reads them whole with the command's standard input,
359
+ * and where it reads its standard input, that input as {@link Stdin} reads it: a here-document
360
+ * is a command line only for a shell, and code for a runtime. `stdin` is what the line's own
361
+ * standard input carries, and `tests` whether the line is a manifest's script.
362
+ */
363
+ function scriptWords(script, depth, quoted, into, stdin, tests) {
364
+ const runs = new Set();
365
+ const lines = new Map();
366
+ const replaced = new Map();
367
+ const handed = new Map();
368
+ const inputs = new Map();
369
+ inputsOf(script, stdin, inputs);
370
+ const top = tests ? topCommands(script) : new Set();
371
+ const asLine = (text, how, fed = NO_STDIN) => {
372
+ if (depth > 0 && (how === "line" || (how === "text" && LINE_SYNTAX.test(text)))) {
373
+ lineWords(text, depth - 1, how === "text", into, fed);
374
+ }
375
+ else if (how === "line") {
376
+ // Past the depth a command line a program runs is read no more.
377
+ into.push(UNWRITTEN);
378
+ }
379
+ };
380
+ const visit = (node, how) => {
381
+ let inner = how;
382
+ switch (node.type) {
383
+ case "Command": {
384
+ const fed = inputs.get(node) ?? NO_STDIN;
385
+ const words = wordsOf(node);
386
+ const operands = words.length === 0
387
+ ? undefined
388
+ : fileOperands(words.map(wordOf), !quoted, fed.file, top.has(node));
389
+ words.forEach((word, at) => {
390
+ const pattern = operands?.replaced.get(at);
391
+ if (operands?.places.has(at) === true) {
392
+ runs.add(word);
393
+ }
394
+ const prefix = operands?.lines.get(at);
395
+ if (prefix !== undefined) {
396
+ lines.set(word, prefix);
397
+ handed.set(word, fed);
398
+ }
399
+ if (pattern !== undefined) {
400
+ replaced.set(word, pattern);
401
+ }
402
+ });
403
+ const reads = operands?.input === true || operands?.stdin === true;
404
+ if (operands?.unwritten === true || (reads && fed.unknown)) {
405
+ into.push(UNWRITTEN);
406
+ }
407
+ for (const text of reads ? fed.piped : []) {
408
+ asLine(text, "line");
409
+ }
410
+ for (const text of operands?.input === true ? fed.content : []) {
411
+ asLine(text, "line");
412
+ }
413
+ for (const text of operands?.texts ?? []) {
414
+ asLine(text, "line", fed);
415
+ }
416
+ for (const [from, to] of operands?.joined ?? []) {
417
+ const texts = words.slice(from, to).flatMap((word, at) => {
418
+ const pattern = operands?.replaced.get(from + at);
419
+ return heldTextsOf(word).map((one) => pattern === undefined ? one : one.replace(pattern, PART));
420
+ });
421
+ asLine(texts.join(" "), "line", fed);
422
+ }
423
+ break;
424
+ }
425
+ case "Word": {
426
+ const pattern = replaced.get(node);
427
+ const prefix = lines.get(node);
428
+ for (const one of heldTextsOf(node)) {
429
+ const text = pattern === undefined ? one : one.replace(pattern, PART);
430
+ into.push({ text, runs: runs.has(node) });
431
+ if (prefix !== undefined) {
432
+ asLine(text.slice(text.startsWith(prefix) ? prefix.length : 0), "line", handed.get(node));
433
+ }
434
+ }
435
+ inner = lines.has(node) ? "read" : how;
436
+ break;
437
+ }
438
+ case "SingleQuoted":
439
+ case "AnsiCQuoted":
440
+ asLine(node.value, how);
441
+ break;
442
+ case "DoubleQuoted":
443
+ asLine(node.text.slice(1, -1), how);
444
+ break;
445
+ case "HereDocBody":
446
+ asLine(node.text, how);
447
+ break;
448
+ default:
449
+ break;
450
+ }
451
+ forEachChild(node, (child) => {
452
+ visit(child, inner);
453
+ });
454
+ };
455
+ visit(script, "text");
456
+ }
457
+ /**
458
+ * Each word of one command line, read twice: as POSIX reads it, and with each
459
+ * {@link BACKSLASH_PATH} read as `/`, as PowerShell and cmd read a path. A line that does not
460
+ * parse is read line by line, and a line of it that does not parse either by {@link tokenWords};
461
+ * `stdin` and `tests` are what {@link scriptWords} takes.
462
+ */
463
+ function lineWords(source, depth, quoted, into, stdin = NO_STDIN, tests = false) {
464
+ for (const reading of new Set([source, source.replaceAll(BACKSLASH_PATH, "/")])) {
465
+ const script = parseLine(reading);
466
+ if ((script.errors ?? []).length === 0) {
467
+ scriptWords(script, depth, quoted, into, stdin, tests && reading === source);
468
+ continue;
469
+ }
470
+ for (const line of reading.split(/\r?\n/u)) {
471
+ const one = parseLine(line);
472
+ if ((one.errors ?? []).length === 0) {
473
+ scriptWords(one, depth, quoted, into, stdin, false);
474
+ }
475
+ else {
476
+ tokenWords(line, !quoted, depth, into, stdin);
477
+ }
478
+ }
479
+ }
480
+ }
481
+ /**
482
+ * The words of `texts`: each command string's, and each line of each other value's, read by
483
+ * {@link tokenWords} as a quoted text is, with no program word, but for a `uses:` value that
484
+ * references another repository's action.
485
+ */
486
+ export function heldWords(texts) {
487
+ const found = [];
488
+ for (const { text, shell, key, tested } of texts) {
489
+ if (shell) {
490
+ lineWords(text, DEPTH, false, found, NO_STDIN, tested === true);
491
+ }
492
+ else if (key !== "uses" || !ACTION_REFERENCE.test(text.trim())) {
493
+ for (const line of text.split(/\r?\n/u)) {
494
+ tokenWords(line, false, DEPTH, found);
495
+ }
496
+ }
497
+ }
498
+ return found;
499
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The one reader of every command string the analysis reads. Layer 3, the holding rule
3
+ * `roots/root-set.ts` applies through `commands/held.ts` and `text/named-files.ts`, reads every
4
+ * word of the string whatever the other two layers make of it; layer 1 parses the string and
5
+ * layer 2 reads each simple command a tool's reader understands, and they only add the files
6
+ * and modules it runs.
7
+ */
8
+ import { joinPath, resolvePath } from "../paths.js";
9
+ import { shellLine } from "./shell.js";
10
+ import { configurationsOf, preloadTarget, readBuiltin, handedOn, testRun, } from "./tools.js";
11
+ import { APPENDED_EXTENSIONS } from "../extensions.js";
12
+ /**
13
+ * The outcomes of one pipeline run in `dir`: those of the builtin that is its one member, as
14
+ * `readBuiltin` gives them, a directory the analysis does not know after a part layer 1 does
15
+ * not read, and either status in `dir` otherwise, a pipeline of several members included,
16
+ * whose members each run in a subshell.
17
+ */
18
+ function outcomesOf(pipeline, dir) {
19
+ const [only] = pipeline.alone ? pipeline.commands : [];
20
+ const outcomes = pipeline.opaque
21
+ ? [
22
+ { dir: undefined, ok: true },
23
+ { dir: undefined, ok: false },
24
+ ]
25
+ : ((only === undefined ? undefined : readBuiltin(only, dir)) ?? [
26
+ { dir, ok: true },
27
+ { dir, ok: false },
28
+ ]);
29
+ return outcomes.map((one) => ({ dir: one.dir, ok: one.ok !== pipeline.negated }));
30
+ }
31
+ /** The one directory every outcome is in, or `undefined` where they differ or none is known. */
32
+ function oneDirectory(outcomes) {
33
+ const dirs = new Set(outcomes.map((one) => one.dir));
34
+ return dirs.size === 1 ? [...dirs][0] : undefined;
35
+ }
36
+ /**
37
+ * What `text`, run in `dir`, `undefined` where not known, runs. A pipeline is read where every
38
+ * way its and-or list reaches it leaves the shell in one known directory, none after an `exit`;
39
+ * a background list moves and ends no later one. The words and configuration files of every
40
+ * simple command of the string's own line are read in `dir` as well, wherever they run.
41
+ */
42
+ export function readCommands(text, dir) {
43
+ const runs = [];
44
+ const commands = [];
45
+ const configurations = [];
46
+ const tests = [];
47
+ const readTests = (words, at) => {
48
+ const run = testRun(words);
49
+ if (run !== undefined) {
50
+ tests.push({ dir: at, ...run });
51
+ }
52
+ };
53
+ const readCommand = (words, at) => {
54
+ commands.push({ dir: at, words: words.map((word) => word.value) });
55
+ const [handed, reading] = handedOn(words);
56
+ readTests(handed, at);
57
+ if (reading?.kind === "line") {
58
+ readLine(reading.text, at);
59
+ return;
60
+ }
61
+ configurations.push(...configurationsOf(handed).map((written) => ({ dir: at, written })));
62
+ if (reading !== undefined && (reading.entry !== undefined || reading.preloads.length > 0)) {
63
+ runs.push({
64
+ dir: at,
65
+ entry: reading.entry,
66
+ preloads: reading.preloads,
67
+ runtime: reading.runtime,
68
+ });
69
+ }
70
+ };
71
+ const readLine = (source, start) => {
72
+ let at = start;
73
+ for (const list of shellLine(source)) {
74
+ let outcomes = [{ dir: at, ok: true }];
75
+ for (const pipeline of list.pipelines) {
76
+ const { operator } = pipeline;
77
+ const reached = outcomes.filter((one) => operator === undefined || one.ok === (operator === "&&"));
78
+ const where = reached.length === 0 ? undefined : oneDirectory(reached);
79
+ if (where !== undefined) {
80
+ pipeline.commands.forEach((words) => {
81
+ readCommand(words, where);
82
+ });
83
+ }
84
+ const next = reached.flatMap((one) => outcomesOf(pipeline, one.dir));
85
+ const passed = outcomes.filter((one) => !reached.includes(one));
86
+ outcomes = [
87
+ ...new Map([...passed, ...next].map((one) => [`${String(one.ok)}\u0000${one.dir ?? ""}`, one])).values(),
88
+ ];
89
+ }
90
+ if (!list.background) {
91
+ at = oneDirectory(outcomes);
92
+ }
93
+ }
94
+ };
95
+ readLine(text, dir);
96
+ if (dir !== undefined) {
97
+ for (const words of shellLine(text).flatMap((list) => list.pipelines.flatMap((pipeline) => pipeline.commands))) {
98
+ commands.push({ dir, words: words.map((word) => word.value) });
99
+ configurations.push(...configurationsOf(words).map((written) => ({ dir, written })));
100
+ readTests(handedOn(words)[0], dir);
101
+ }
102
+ }
103
+ const unique = (all) => [
104
+ ...new Map(all.map((one) => [JSON.stringify(one), one])).values(),
105
+ ];
106
+ return {
107
+ runs,
108
+ commands: unique(commands),
109
+ configurations: unique(configurations),
110
+ tests: unique(tests),
111
+ held: text,
112
+ };
113
+ }
114
+ /**
115
+ * The files `run` loads: its entry, and each preload written as a path. Each may be at its
116
+ * path, with each extension tried, and as a directory's `index` file.
117
+ */
118
+ export function loadsOf(run) {
119
+ const written = [
120
+ ...(run.entry === undefined
121
+ ? []
122
+ : [{ written: run.entry, path: resolvePath(run.dir, run.entry) }]),
123
+ ...run.preloads.flatMap((preload) => {
124
+ const target = preloadTarget(preload, run.dir);
125
+ return target !== undefined && "path" in target
126
+ ? [{ written: preload.specifier, path: target.path }]
127
+ : [];
128
+ }),
129
+ ];
130
+ return written.map(({ written: one, path }) => ({
131
+ written: one,
132
+ paths: [
133
+ path,
134
+ ...APPENDED_EXTENSIONS.map((extension) => `${path}${extension}`),
135
+ ...APPENDED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)),
136
+ ],
137
+ }));
138
+ }
139
+ /** The module specifiers `run` preloads that name a package rather than a path. */
140
+ export function preloadedPackages(run) {
141
+ return run.preloads.flatMap((preload) => {
142
+ const target = preloadTarget(preload, run.dir);
143
+ return target !== undefined && "package" in target ? [target.package] : [];
144
+ });
145
+ }
146
+ /**
147
+ * Every path `reading` names: each static word of its commands read against the directory the
148
+ * command runs in, a word holding `*` aside, which a tool reads as a pattern, and every path a
149
+ * file a read command runs or preloads may be at.
150
+ */
151
+ export function pathsNamed(reading) {
152
+ const named = [
153
+ ...reading.commands.flatMap(({ dir, words }) => words.flatMap((word) => word === undefined || word.includes("*")
154
+ ? []
155
+ : [{ path: resolvePath(dir, word), written: word }])),
156
+ ...reading.runs
157
+ .flatMap(loadsOf)
158
+ .flatMap(({ written, paths }) => paths.map((path) => ({ path, written }))),
159
+ ];
160
+ return [...new Map(named.map((one) => [`${one.path}\u0000${one.written}`, one])).values()];
161
+ }