@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,835 @@
1
+ /**
2
+ * The test runners a convention row names by its `testRunner`, and `node --test`, which no
3
+ * package enables: each reads the files its runner runs as tests from the runner's
4
+ * configuration and from the commands a manifest's scripts run it with, as the runner's own
5
+ * source reads them. A value a reader needs that the text does not write out names no file.
6
+ */
7
+
8
+ import type { CommandTests } from "../commands/read.ts";
9
+ import { readJSON, readJSONFile } from "../fs/manifest.ts";
10
+ import type { Host } from "../host.ts";
11
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
12
+ import { globExpression } from "../text/glob.ts";
13
+ import {
14
+ documentValue,
15
+ membersNamed,
16
+ textsOf,
17
+ type StaticModule,
18
+ type Value,
19
+ } from "./static-module.ts";
20
+
21
+ /** One pattern of the files a runner runs, over the files below `dir`. */
22
+ export interface TestGlob {
23
+ readonly dir: string;
24
+ /** What names the files, as written. */
25
+ readonly pattern: string;
26
+ /** Whether the file of one absolute name is one the pattern names. */
27
+ readonly matches: (fileName: string) => boolean;
28
+ }
29
+
30
+ /** One simple command of a manifest's scripts, a dynamic word `undefined`, in the directory it runs in. */
31
+ interface ScriptCommand {
32
+ readonly dir: string;
33
+ readonly words: readonly (string | undefined)[];
34
+ }
35
+
36
+ /** One configuration file a runner reads. */
37
+ interface Configuration {
38
+ /** Its directory, absolute. */
39
+ readonly dir: string;
40
+ readonly module: StaticModule;
41
+ }
42
+
43
+ /** What a runner reads for one manifest that declares it. */
44
+ export interface RunnerInput {
45
+ readonly host: Host;
46
+ /** The manifest's directory, absolute. */
47
+ readonly dir: string;
48
+ /** The manifest's document. */
49
+ readonly manifest: unknown;
50
+ /** The first of `names` in `dir` the host holds, read once per run; `undefined` where it holds none. */
51
+ readonly configuration: (dir: string, names: readonly string[]) => Configuration | undefined;
52
+ readonly scripts: readonly ScriptCommand[];
53
+ }
54
+
55
+ /** The files one runner runs as tests for one manifest that declares it. */
56
+ export type TestRunner = (input: RunnerInput) => readonly TestGlob[];
57
+
58
+ /** A member's last value: `null` where the object writes none, `undefined` where it may hide one or is no object. */
59
+ function lastOf(object: Value, key: string): Value | null | undefined {
60
+ if (object.kind !== "object") {
61
+ return undefined;
62
+ }
63
+ const values = membersNamed(object, key);
64
+ return values === undefined ? undefined : (values.at(-1) ?? null);
65
+ }
66
+
67
+ /** The one object a configuration's default export writes, `undefined` where it writes anything else. */
68
+ function configurationObject(module: StaticModule): Value | undefined {
69
+ const [only, ...more] = module.defaultExport;
70
+ return only?.kind === "object" && more.length === 0 ? only : undefined;
71
+ }
72
+
73
+ /** The strings a member holds, one or an array of them: `null` where absent, `undefined` where not written out. */
74
+ function stringsOf(object: Value, key: string): readonly string[] | null | undefined {
75
+ const value = lastOf(object, key);
76
+ return value === null || value === undefined ? value : textsOf(value, true);
77
+ }
78
+
79
+ /** A boolean member: `null` where absent, `undefined` where it is written as anything else. */
80
+ function booleanOf(object: Value, key: string): boolean | null | undefined {
81
+ const value = lastOf(object, key);
82
+ return value === null ? null : value?.kind === "boolean" ? value.value : undefined;
83
+ }
84
+
85
+ /** A manifest's member, `undefined` where it has none. */
86
+ function manifestMember(manifest: unknown, key: string): unknown {
87
+ return typeof manifest === "object" && manifest !== null && !Array.isArray(manifest)
88
+ ? (manifest as Readonly<Record<string, unknown>>)[key]
89
+ : undefined;
90
+ }
91
+
92
+ /** The configuration a path names, read where the host holds it. */
93
+ function configurationAt(input: RunnerInput, path: string): Configuration | undefined {
94
+ const dir = dirnamePath(path);
95
+ return input.configuration(dir, [path.slice(dir.length + 1)]);
96
+ }
97
+
98
+ /** Whether a pattern holds a character a glob reads specially. */
99
+ const MAGIC = /[*?[\]{}()!+@]/u;
100
+
101
+ /** A glob over the path below `dir`, case compared unless `caseless`. */
102
+ function belowGlob(dir: string, pattern: string, caseless = false): TestGlob {
103
+ const expression = globExpression(pattern.replace(/^(?:\.\/)+/u, ""));
104
+ const tested = caseless ? new RegExp(expression.source, "iu") : expression;
105
+ return {
106
+ dir,
107
+ pattern,
108
+ matches: (fileName) => {
109
+ const below = relativePath(dir, fileName);
110
+ return below !== undefined && below !== "" && tested.test(below);
111
+ },
112
+ };
113
+ }
114
+
115
+ /** The glob less every file one of `excludes` names. */
116
+ function less(glob: TestGlob, excludes: readonly ((fileName: string) => boolean)[]): TestGlob {
117
+ return excludes.length === 0
118
+ ? glob
119
+ : { ...glob, matches: (name) => glob.matches(name) && !excludes.some((one) => one(name)) };
120
+ }
121
+
122
+ /** The one file a path names. */
123
+ function exactly(cwd: string, path: string): TestGlob {
124
+ const file = resolvePath(cwd, path);
125
+ return { dir: dirnamePath(file), pattern: path, matches: (name) => name === file };
126
+ }
127
+
128
+ /** The words after the first one naming one of `programs`, by its name or a path ending in it. */
129
+ function afterProgram(
130
+ command: ScriptCommand,
131
+ programs: readonly string[],
132
+ ): readonly (string | undefined)[] | undefined {
133
+ const at = command.words.findIndex(
134
+ (word) => word !== undefined && programs.includes(word.slice(word.lastIndexOf("/") + 1)),
135
+ );
136
+ return at < 0 ? undefined : command.words.slice(at + 1);
137
+ }
138
+
139
+ /** The options a command line takes, each written with its dashes. */
140
+ interface OptionTable {
141
+ /** The options that take the next word, or the text after `=`, as their value. */
142
+ readonly valued: ReadonlySet<string>;
143
+ readonly flags: ReadonlySet<string>;
144
+ }
145
+
146
+ /** One command's options and operands. */
147
+ interface Parsed {
148
+ readonly values: ReadonlyMap<string, readonly string[]>;
149
+ readonly operands: readonly string[];
150
+ }
151
+
152
+ /** One command's options and operands, `undefined` where it holds a dynamic word or an option the table does not name. */
153
+ function parsed(words: readonly (string | undefined)[], table: OptionTable): Parsed | undefined {
154
+ const values = new Map<string, string[]>();
155
+ const operands: string[] = [];
156
+ const keep = (name: string, value: string): void => {
157
+ values.set(name, [...(values.get(name) ?? []), value]);
158
+ };
159
+ for (let at = 0; at < words.length; at += 1) {
160
+ const word = words[at];
161
+ if (word === undefined) {
162
+ return undefined;
163
+ }
164
+ if (word === "--") {
165
+ const rest = words.slice(at + 1);
166
+ return rest.every((one) => one !== undefined)
167
+ ? { values, operands: [...operands, ...rest] }
168
+ : undefined;
169
+ }
170
+ if (!word.startsWith("-") || word === "-") {
171
+ operands.push(word);
172
+ continue;
173
+ }
174
+ const equals = word.startsWith("--") ? word.indexOf("=") : -1;
175
+ const name = equals < 0 ? word : word.slice(0, equals);
176
+ if (table.valued.has(name)) {
177
+ const value = equals < 0 ? words[(at += 1)] : word.slice(equals + 1);
178
+ if (value === undefined) {
179
+ return undefined;
180
+ }
181
+ keep(name, value);
182
+ } else if (table.flags.has(name)) {
183
+ keep(name, equals < 0 ? "true" : word.slice(equals + 1));
184
+ } else {
185
+ return undefined;
186
+ }
187
+ }
188
+ return { values, operands };
189
+ }
190
+
191
+ /** Every value one command gives the options `names`, in order. */
192
+ function optionValues(command: Parsed, ...names: readonly string[]): readonly string[] {
193
+ return names.flatMap((name) => command.values.get(name) ?? []);
194
+ }
195
+
196
+ /** A table of options by their long names and one-letter aliases, each flag with its `--no-` form. */
197
+ function optionTable(
198
+ valued: readonly string[],
199
+ flags: readonly string[],
200
+ aliases: Readonly<Record<string, readonly string[]>>,
201
+ ): OptionTable {
202
+ const named = (name: string): readonly string[] =>
203
+ [name, ...(aliases[name] ?? [])].map((one) => (one.length === 1 ? `-${one}` : `--${one}`));
204
+ return {
205
+ valued: new Set(valued.flatMap(named)),
206
+ flags: new Set(flags.flatMap((flag) => [...named(flag), `--no-${flag}`])),
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Mocha's options, `lib/cli/run-option-metadata` of 10.8 to 12.0: its array, string and number
212
+ * options take a value and its boolean ones none; `--config` and `--package` also take a `--no-`
213
+ * form. A Node option mocha hands on is not named, so a command writing one is not read.
214
+ */
215
+ const MOCHA_OPTIONS = optionTable(
216
+ [
217
+ ...["extension", "file", "global", "ignore", "node-option", "reporter-option", "require"],
218
+ ...["spec", "watch-files", "watch-ignore", "retries", "jobs", "config", "fgrep", "grep"],
219
+ ...["package", "reporter", "ui", "slow", "timeout"],
220
+ ],
221
+ [
222
+ ...["allow-uncaught", "async-only", "bail", "check-leaks", "clear-screen", "color", "delay"],
223
+ ...["diff", "dry-run", "exit", "fail-hook-affected-tests", "pass-on-failing-test-suite"],
224
+ ...["fail-zero", "forbid-only", "forbid-pending", "full-trace", "inline-diffs", "invert"],
225
+ ...["list-interfaces", "list-reporters", "no-colors", "parallel", "posix-exit-codes"],
226
+ ...["recursive", "sort", "watch", "config", "package"],
227
+ ],
228
+ {
229
+ "async-only": ["A"],
230
+ bail: ["b"],
231
+ color: ["c", "colors"],
232
+ fgrep: ["f"],
233
+ global: ["globals"],
234
+ grep: ["g"],
235
+ ignore: ["exclude"],
236
+ invert: ["i"],
237
+ jobs: ["j"],
238
+ "no-colors": ["C"],
239
+ "node-option": ["n"],
240
+ parallel: ["p"],
241
+ reporter: ["R"],
242
+ "reporter-option": ["reporter-options", "O"],
243
+ require: ["r"],
244
+ slow: ["s"],
245
+ sort: ["S"],
246
+ timeout: ["t", "timeouts"],
247
+ ui: ["u"],
248
+ watch: ["w"],
249
+ },
250
+ );
251
+
252
+ /** The test-file options one mocha source sets. */
253
+ interface MochaSource {
254
+ readonly spec: readonly string[];
255
+ readonly file: readonly string[];
256
+ readonly ignore: readonly string[];
257
+ /** Absent where the source sets none. */
258
+ readonly extension?: readonly string[];
259
+ readonly recursive?: boolean;
260
+ }
261
+
262
+ /** The test-file options a mocha configuration object sets, `undefined` where one is not written out. */
263
+ function mochaObject(object: Value): MochaSource | undefined {
264
+ const spec = stringsOf(object, "spec");
265
+ const file = stringsOf(object, "file");
266
+ const ignore = stringsOf(object, "ignore");
267
+ const exclude = stringsOf(object, "exclude");
268
+ const extension = stringsOf(object, "extension");
269
+ const recursive = booleanOf(object, "recursive");
270
+ if ([spec, file, ignore, exclude, extension, recursive].includes(undefined)) {
271
+ return undefined;
272
+ }
273
+ return {
274
+ spec: spec ?? [],
275
+ file: file ?? [],
276
+ ignore: [...(ignore ?? []), ...(exclude ?? [])],
277
+ ...(extension === null || extension === undefined ? {} : { extension }),
278
+ ...(recursive === null || recursive === undefined ? {} : { recursive }),
279
+ };
280
+ }
281
+
282
+ /** The test-file options one mocha command line sets. */
283
+ function mochaCommand(command: Parsed): MochaSource {
284
+ const recursive = optionValues(command, "--recursive", "--no-recursive");
285
+ const extension = optionValues(command, "--extension");
286
+ return {
287
+ spec: [...command.operands, ...optionValues(command, "--spec")],
288
+ file: optionValues(command, "--file"),
289
+ ignore: optionValues(command, "--ignore", "--exclude"),
290
+ ...(extension.length > 0 ? { extension } : {}),
291
+ ...(command.values.has("--no-recursive")
292
+ ? { recursive: false }
293
+ : recursive.length > 0
294
+ ? { recursive: recursive.at(-1) !== "false" }
295
+ : {}),
296
+ };
297
+ }
298
+
299
+ /**
300
+ * The files one mocha spec names, `lib/cli/lookup-files`: an existing file itself, the files of
301
+ * an existing directory with one of `extensions` and a name not starting with `.`, at any depth
302
+ * where `recursive`, a glob as written, and any other path with an extension appended.
303
+ */
304
+ function mochaSpec(
305
+ host: Host,
306
+ cwd: string,
307
+ spec: string,
308
+ extensions: readonly string[],
309
+ recursive: boolean,
310
+ ): TestGlob {
311
+ const path = resolvePath(cwd, spec);
312
+ const braced = `{${extensions.map((one) => one.replace(/^\./u, "")).join(",")}}`;
313
+ const kind = host.kindOf(path);
314
+ if (kind === "file") {
315
+ return exactly(cwd, spec);
316
+ }
317
+ if (kind === "directory") {
318
+ const inner = belowGlob(path, `${recursive ? "**/" : ""}*.${braced}`);
319
+ return {
320
+ ...inner,
321
+ pattern: spec,
322
+ matches: (name) =>
323
+ inner.matches(name) && !name.slice(name.lastIndexOf("/") + 1).startsWith("."),
324
+ };
325
+ }
326
+ return belowGlob(cwd, MAGIC.test(spec) ? spec : `${spec}.${braced}`);
327
+ }
328
+
329
+ /**
330
+ * Mocha over the configuration files it looks for, in its order of preference, the nearest at
331
+ * or above the working directory (`lib/cli/config`): one run with no operand, and one per
332
+ * script command naming `mocha` or `_mocha`, its sources merged as `lib/cli/options` merges
333
+ * them. A list each source sets is joined, `extension` replacing the default where any sets
334
+ * it, and the command line, then the file, then the manifest's `mocha` member decides
335
+ * `recursive`; a run with no spec runs `./test`.
336
+ */
337
+ function mochaRunner(configNames: readonly string[]): TestRunner {
338
+ return (input) => {
339
+ const { host, dir, manifest, scripts } = input;
340
+ const nearest = (from: string): Configuration | undefined => {
341
+ for (const at of ancestors(from)) {
342
+ const found = input.configuration(at, configNames);
343
+ if (found !== undefined) {
344
+ return found;
345
+ }
346
+ }
347
+ return undefined;
348
+ };
349
+ /**
350
+ * The document `lib/cli/options` `loadPkgRc` reads the `mocha` member of, `undefined` where
351
+ * the run fails: the file the last non-empty `--package` names, failing it where it cannot be
352
+ * read, else the nearest `package.json` at or above `cwd`, none where that cannot be read;
353
+ * either failing it where it does not parse to a document `pkg.mocha` can be read on. `null`
354
+ * for none, or where the last of `--package` and `--no-package` is the latter.
355
+ */
356
+ const packageOf = (
357
+ cwd: string,
358
+ words: readonly (string | undefined)[],
359
+ command: Parsed,
360
+ ): unknown => {
361
+ const last = words.findLast(
362
+ (word) => word === "--no-package" || word === "--package" || word?.startsWith("--package="),
363
+ );
364
+ if (last === "--no-package") {
365
+ return null;
366
+ }
367
+ const named = optionValues(command, "--package").at(-1);
368
+ const explicit = named !== undefined && named !== "";
369
+ const path = explicit
370
+ ? resolvePath(cwd, named)
371
+ : ancestors(cwd)
372
+ .map((at) => joinPath(at, "package.json"))
373
+ .find((one) => host.kindOf(one) === "file");
374
+ if (path === undefined) {
375
+ return null;
376
+ }
377
+ if (path === joinPath(dir, "package.json")) {
378
+ return manifest;
379
+ }
380
+ const read = readJSONFile(host, path);
381
+ if (read.kind === "unread") {
382
+ return explicit ? undefined : null;
383
+ }
384
+ return read.kind === "malformed" || read.value === null ? undefined : read.value;
385
+ };
386
+ const run = (cwd: string, words: readonly (string | undefined)[]): readonly TestGlob[] => {
387
+ const command = parsed(words, MOCHA_OPTIONS);
388
+ if (command === undefined) {
389
+ return [];
390
+ }
391
+ const named = optionValues(command, "--config").at(-1);
392
+ const file = command.values.has("--no-config")
393
+ ? undefined
394
+ : named === undefined
395
+ ? nearest(cwd)
396
+ : configurationAt(input, resolvePath(cwd, named));
397
+ const object = file === undefined ? null : configurationObject(file.module);
398
+ const fromFile =
399
+ object === null ? null : object === undefined ? undefined : mochaObject(object);
400
+ const pkg = packageOf(cwd, words, command);
401
+ const held = pkg === null || pkg === undefined ? undefined : manifestMember(pkg, "mocha");
402
+ const fromManifest = held === undefined ? null : mochaObject(documentValue(held));
403
+ if (
404
+ pkg === undefined ||
405
+ (named !== undefined && file === undefined) ||
406
+ fromFile === undefined ||
407
+ fromManifest === undefined
408
+ ) {
409
+ return [];
410
+ }
411
+ const sources = [mochaCommand(command), fromFile ?? [], fromManifest ?? []].flat();
412
+ const spec = sources.flatMap((one) => one.spec);
413
+ const set = sources.flatMap((one) => one.extension ?? []);
414
+ const extensions = set.length > 0 ? [...new Set(set)] : ["js", "cjs", "mjs"];
415
+ const recursive = sources.find((one) => one.recursive !== undefined)?.recursive ?? false;
416
+ const ignored = sources
417
+ .flatMap((one) => one.ignore)
418
+ .map((one) => belowGlob(cwd, one).matches);
419
+ return [
420
+ ...(spec.length > 0 ? spec : ["./test"]).map((one) =>
421
+ less(mochaSpec(host, cwd, one, extensions, recursive), ignored),
422
+ ),
423
+ ...sources.flatMap((one) => one.file).map((one) => exactly(cwd, one)),
424
+ ];
425
+ };
426
+ return [
427
+ ...run(dir, []),
428
+ ...scripts.flatMap((command) => {
429
+ const words = afterProgram(command, ["mocha", "_mocha"]);
430
+ return words === undefined ? [] : run(command.dir, words);
431
+ }),
432
+ ];
433
+ };
434
+ }
435
+
436
+ const MOCHARC = [".mocharc.yaml", ".mocharc.yml", ".mocharc.jsonc", ".mocharc.json"];
437
+
438
+ /** Mocha 10 and 11, which read no `.mocharc.mjs`. */
439
+ export const MOCHA_10 = mochaRunner([".mocharc.cjs", ".mocharc.js", ...MOCHARC]);
440
+
441
+ /** Mocha 12. */
442
+ export const MOCHA_12 = mochaRunner([".mocharc.cjs", ".mocharc.js", ".mocharc.mjs", ...MOCHARC]);
443
+
444
+ /** A regular expression a runner writes as text, `undefined` where it is none. */
445
+ function expressionOf(source: string): RegExp | undefined {
446
+ try {
447
+ return new RegExp(source, "u");
448
+ } catch {
449
+ try {
450
+ return new RegExp(source);
451
+ } catch {
452
+ return undefined;
453
+ }
454
+ }
455
+ }
456
+
457
+ /** One Jest version's defaults (jest-config `Defaults`), its `testMatch` read over a file's whole path. */
458
+ interface JestDefaults {
459
+ readonly testMatch: readonly RegExp[];
460
+ readonly moduleFileExtensions: readonly string[];
461
+ }
462
+
463
+ /** The text `<rootDir>` stands for in one of Jest's paths, `rootDir` itself. */
464
+ function inRoot(text: string, rootDir: string): string {
465
+ return text.replaceAll("<rootDir>", rootDir);
466
+ }
467
+
468
+ /**
469
+ * Jest over the configuration files it looks for in a directory, in its order
470
+ * (jest-config `resolveConfigPath`): one run with no option, and one per script command naming
471
+ * `jest` with its `--config` or `-c` file. A configuration's own file, else its `package.json`
472
+ * member `jest`, else the defaults, configure a directory; two of them configure none.
473
+ */
474
+ function jestRunner(configNames: readonly string[], defaults: JestDefaults): TestRunner {
475
+ return (input) => {
476
+ const { host, dir, scripts } = input;
477
+ const directory = (at: string, depth: number): readonly TestGlob[] => {
478
+ const file = input.configuration(at, configNames);
479
+ const manifestPath = joinPath(at, "package.json");
480
+ const manifest =
481
+ host.kindOf(manifestPath) === "file" ? readJSON(host, manifestPath) : undefined;
482
+ const held = manifestMember(manifest, "jest");
483
+ if (file !== undefined && held !== undefined) {
484
+ return [];
485
+ }
486
+ if (file !== undefined) {
487
+ return configured(file, depth);
488
+ }
489
+ if (typeof held === "string") {
490
+ const named = configurationAt(input, resolvePath(at, held));
491
+ return named === undefined ? [] : configured(named, depth);
492
+ }
493
+ return manifest === undefined && held === undefined
494
+ ? []
495
+ : project(
496
+ held === undefined
497
+ ? { kind: "object", members: [], at: undefined }
498
+ : documentValue(held),
499
+ at,
500
+ depth,
501
+ );
502
+ };
503
+ const configured = (file: Configuration, depth: number): readonly TestGlob[] => {
504
+ const object = configurationObject(file.module);
505
+ return object === undefined ? [] : project(object, file.dir, depth);
506
+ };
507
+ const project = (object: Value, base: string, depth: number): readonly TestGlob[] => {
508
+ const rootValue = lastOf(object, "rootDir");
509
+ const rootDir =
510
+ rootValue === null
511
+ ? base
512
+ : rootValue?.kind === "text"
513
+ ? resolvePath(base, inRoot(rootValue.text, base))
514
+ : undefined;
515
+ if (rootDir === undefined) {
516
+ return [];
517
+ }
518
+ const projects = lastOf(object, "projects");
519
+ if (projects === undefined || (projects !== null && projects.kind !== "array")) {
520
+ return [];
521
+ }
522
+ if (projects !== null && projects.elements.length > 0) {
523
+ return depth > 4
524
+ ? []
525
+ : projects.elements.flatMap((one) =>
526
+ one.kind === "object"
527
+ ? project(one, rootDir, depth + 1)
528
+ : one.kind === "text"
529
+ ? projectDirectories(host, inRoot(one.text, rootDir), rootDir).flatMap((at) =>
530
+ host.kindOf(at) === "file"
531
+ ? [configurationAt(input, at) ?? []]
532
+ .flat()
533
+ .flatMap((named) => configured(named, depth + 1))
534
+ : directory(at, depth + 1),
535
+ )
536
+ : [],
537
+ );
538
+ }
539
+ return testsOf(object, rootDir, defaults);
540
+ };
541
+ const named = scripts.flatMap((command) => {
542
+ const words = afterProgram(command, ["jest"]);
543
+ const at = words?.findIndex((word) => word === "--config" || word === "-c") ?? -1;
544
+ const written =
545
+ words?.find((word) => word?.startsWith("--config=") === true)?.slice("--config=".length) ??
546
+ (at < 0 ? undefined : words?.[at + 1]);
547
+ return written === undefined ? [] : [resolvePath(command.dir, written)];
548
+ });
549
+ return [
550
+ ...directory(dir, 0),
551
+ ...named.flatMap((path) =>
552
+ host.kindOf(path) === "directory"
553
+ ? directory(path, 0)
554
+ : [configurationAt(input, path) ?? []].flat().flatMap((file) => configured(file, 0)),
555
+ ),
556
+ ];
557
+ };
558
+ }
559
+
560
+ /**
561
+ * The paths one Jest `projects` string names: itself, or, where it is a glob, every
562
+ * directory and file its segments match below the directories the earlier ones name.
563
+ */
564
+ function projectDirectories(host: Host, written: string, rootDir: string): readonly string[] {
565
+ const path = resolvePath(rootDir, written);
566
+ if (!MAGIC.test(path)) {
567
+ return [path];
568
+ }
569
+ let found = ["/"];
570
+ for (const segment of path.split("/").filter((one) => one !== "")) {
571
+ if (!MAGIC.test(segment)) {
572
+ found = found
573
+ .map((one) => joinPath(one, segment))
574
+ .filter((one) => host.kindOf(one) !== "absent");
575
+ continue;
576
+ }
577
+ const expression = globExpression(segment);
578
+ found = found.flatMap((one) =>
579
+ host.kindOf(one) === "directory"
580
+ ? host
581
+ .readDirectory(one)
582
+ .filter((entry) => expression.test(entry.name))
583
+ .map((entry) => joinPath(one, entry.name))
584
+ : [],
585
+ );
586
+ }
587
+ return found;
588
+ }
589
+
590
+ /**
591
+ * The tests one Jest project runs: files below each of its `roots` with one of its
592
+ * `moduleFileExtensions`, the only files jest-haste-map lists, that a `testMatch` glob, or a
593
+ * `testRegex` expression, matches over the whole path, and no `testPathIgnorePatterns`
594
+ * expression does. A project writing both `testMatch` and `testRegex` runs none.
595
+ */
596
+ function testsOf(object: Value, rootDir: string, defaults: JestDefaults): readonly TestGlob[] {
597
+ const roots = stringsOf(object, "roots");
598
+ const match = stringsOf(object, "testMatch");
599
+ const regex = stringsOf(object, "testRegex");
600
+ const ignore = stringsOf(object, "testPathIgnorePatterns");
601
+ const extensions = stringsOf(object, "moduleFileExtensions");
602
+ if (
603
+ roots === undefined ||
604
+ match === undefined ||
605
+ regex === undefined ||
606
+ ignore === undefined ||
607
+ extensions === undefined
608
+ ) {
609
+ return [];
610
+ }
611
+ const listed = (extensions ?? defaults.moduleFileExtensions).map((one) => `.${one}`);
612
+ const ignored = (ignore ?? ["/node_modules/"]).map((one) => expressionOf(inRoot(one, rootDir)));
613
+ if (ignored.includes(undefined) || ((match ?? []).length > 0 && (regex ?? []).length > 0)) {
614
+ return [];
615
+ }
616
+ const tests: readonly { pattern: string; test: (path: string) => boolean }[] =
617
+ (regex ?? []).length > 0
618
+ ? (regex ?? []).flatMap((one) => {
619
+ const expression = expressionOf(one);
620
+ return expression === undefined
621
+ ? []
622
+ : [{ pattern: one, test: (path: string) => expression.test(path) }];
623
+ })
624
+ : match !== null
625
+ ? jestGlobs(match, rootDir)
626
+ : defaults.testMatch.map((expression) => ({
627
+ pattern: expression.source,
628
+ test: (path: string) => expression.test(path),
629
+ }));
630
+ return (roots ?? ["<rootDir>"]).flatMap((root) => {
631
+ const at = resolvePath(rootDir, inRoot(root, rootDir));
632
+ return tests.map(({ pattern, test }) => ({
633
+ dir: at,
634
+ pattern,
635
+ matches: (name: string) =>
636
+ relativePath(at, name) !== undefined &&
637
+ listed.some((extension) => name.endsWith(extension)) &&
638
+ test(name) &&
639
+ !ignored.some((expression) => expression?.test(name) === true),
640
+ }));
641
+ });
642
+ }
643
+
644
+ /** Jest's `testMatch` globs over the whole path, a `!` glob leaving out what it matches. */
645
+ function jestGlobs(
646
+ globs: readonly string[],
647
+ rootDir: string,
648
+ ): readonly { pattern: string; test: (path: string) => boolean }[] {
649
+ const expression = (glob: string): RegExp =>
650
+ globExpression(inRoot(glob, rootDir).replace(/^\/+/u, ""));
651
+ const excluded = globs
652
+ .filter((one) => one.startsWith("!"))
653
+ .map((one) => expression(one.slice(1)));
654
+ return globs
655
+ .filter((one) => !one.startsWith("!"))
656
+ .map((glob) => {
657
+ const included = expression(glob);
658
+ return {
659
+ pattern: glob,
660
+ test: (path: string) => {
661
+ const bare = path.replace(/^\/+/u, "");
662
+ return included.test(bare) && !excluded.some((one) => one.test(bare));
663
+ },
664
+ };
665
+ });
666
+ }
667
+
668
+ /** Jest 29, which reads no `.mts` or `.cts` configuration and by default runs no `.mjs`, `.cjs`, `.mts` or `.cts` test. */
669
+ export const JEST_29 = jestRunner(
670
+ ["jest.config.js", "jest.config.ts", "jest.config.mjs", "jest.config.cjs", "jest.config.json"],
671
+ {
672
+ testMatch: [
673
+ /\/__tests__\/(?:[^/]+\/)*[^/]*\.[jt]sx?$/u,
674
+ /\/(?:[^/]*\.)?(?:spec|test)+\.[jt]sx?$/u,
675
+ ],
676
+ moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "json", "node"],
677
+ },
678
+ );
679
+
680
+ /** Jest 30. */
681
+ export const JEST_30 = jestRunner(
682
+ [
683
+ "jest.config.js",
684
+ "jest.config.ts",
685
+ "jest.config.mjs",
686
+ "jest.config.mts",
687
+ "jest.config.cjs",
688
+ "jest.config.cts",
689
+ "jest.config.json",
690
+ ],
691
+ {
692
+ testMatch: [
693
+ /\/__tests__\/(?:[^/]+\/)*[^/]*\.[mc]?[jt]sx?$/u,
694
+ /\/(?:[^/]*\.)?(?:spec|test)+\.[mc]?[jt]sx?$/u,
695
+ ],
696
+ moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx", "json", "node"],
697
+ },
698
+ );
699
+
700
+ /** The directories AVA leaves out, `ignore-by-default` 2.1 with `node_modules`. */
701
+ const AVA_IGNORED =
702
+ /(?:^|\/)(?:\.git|\.log|\.nyc_output|\.sass-cache|\.yarn|bower_components|coverage|node_modules)\//u;
703
+
704
+ /** AVA's default exclusions (`lib/globs`), over the path below the project directory. */
705
+ const AVA_HELPERS = [
706
+ /(?:^|\/)__tests__\/(?:.*\/)?__(?:helper|fixture)s?__\//iu,
707
+ /(?:^|\/)tests?\/(?:.*\/)?(?:helper|fixture)s?\//iu,
708
+ ];
709
+
710
+ /** The extensions an AVA configuration's `extensions` names, an array or an object's keys. */
711
+ function avaExtensions(object: Value): readonly string[] | null | undefined {
712
+ const value = lastOf(object, "extensions");
713
+ if (value === null || value === undefined) {
714
+ return value;
715
+ }
716
+ if (value.kind === "object") {
717
+ return value.members.every((member) => member.key !== undefined)
718
+ ? value.members.flatMap((member) => member.key ?? [])
719
+ : undefined;
720
+ }
721
+ return textsOf(value, true);
722
+ }
723
+
724
+ /**
725
+ * AVA over the first configuration file of the nearest directory holding one, up to the
726
+ * repository's, or the manifest's `ava` member; both, or a function, configure nothing
727
+ * (`lib/load-config`). Its tests match its `files` globs below the manifest's directory without
728
+ * regard to case, a list of exclusions only joining the defaults (`lib/globs`), with one of its
729
+ * extensions, in no directory AVA ignores and named without a leading `_`. Its command line
730
+ * only filters these, so none is read.
731
+ */
732
+ function avaRunner(configNames: readonly string[], defaults: readonly string[]): TestRunner {
733
+ return (input) => {
734
+ const { host, dir, manifest } = input;
735
+ let file: Configuration | undefined;
736
+ for (const at of ancestors(dir)) {
737
+ file = input.configuration(at, configNames);
738
+ if (file !== undefined || host.kindOf(joinPath(at, ".git")) !== "absent") {
739
+ break;
740
+ }
741
+ }
742
+ const held = manifestMember(manifest, "ava");
743
+ if (file !== undefined && held !== undefined) {
744
+ return [];
745
+ }
746
+ const object =
747
+ file === undefined
748
+ ? held === undefined
749
+ ? { kind: "object" as const, members: [], at: undefined }
750
+ : documentValue(held)
751
+ : configurationObject(file.module);
752
+ const files = object === undefined ? undefined : stringsOf(object, "files");
753
+ const extensions = object === undefined ? undefined : avaExtensions(object);
754
+ if (files === undefined || extensions === undefined || files?.length === 0) {
755
+ return [];
756
+ }
757
+ const pattern = `{${(extensions ?? defaults).join(",")}}`;
758
+ const written =
759
+ files === null || files.every((one) => one.startsWith("!"))
760
+ ? [
761
+ `test.${pattern}`,
762
+ `{src,source}/test.${pattern}`,
763
+ `**/__tests__/**/*.${pattern}`,
764
+ `**/*.spec.${pattern}`,
765
+ `**/*.test.${pattern}`,
766
+ `**/test-*.${pattern}`,
767
+ `**/test/**/*.${pattern}`,
768
+ `**/tests/**/*.${pattern}`,
769
+ ...(files ?? []),
770
+ ]
771
+ : files;
772
+ const excluded = [
773
+ ...written
774
+ .filter((one) => one.startsWith("!"))
775
+ .map((one) => belowGlob(dir, one.slice(1), true).matches),
776
+ ...(files === null || files.every((one) => one.startsWith("!"))
777
+ ? AVA_HELPERS.map(
778
+ (expression) => (name: string) => expression.test(relativePath(dir, name) ?? ""),
779
+ )
780
+ : []),
781
+ ];
782
+ const kept = new RegExp(
783
+ `\\.(?:${(extensions ?? defaults).map((one) => one.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("|")})$`,
784
+ "u",
785
+ );
786
+ return written
787
+ .filter((one) => !one.startsWith("!"))
788
+ .map((one) => {
789
+ const glob = less(belowGlob(dir, one, true), excluded);
790
+ return {
791
+ ...glob,
792
+ matches: (name: string) => {
793
+ const below = relativePath(dir, name) ?? "";
794
+ return (
795
+ glob.matches(name) &&
796
+ kept.test(name) &&
797
+ !AVA_IGNORED.test(below) &&
798
+ !below.split("/").some((segment) => segment.startsWith(".")) &&
799
+ !name.slice(name.lastIndexOf("/") + 1).startsWith("_")
800
+ );
801
+ },
802
+ };
803
+ });
804
+ };
805
+ }
806
+
807
+ const AVA_CONFIG = ["ava.config.js", "ava.config.cjs", "ava.config.mjs"];
808
+
809
+ /** AVA 6 and 7. */
810
+ export const AVA_6 = avaRunner(AVA_CONFIG, ["cjs", "mjs", "js"]);
811
+
812
+ /** AVA 8, which reads no `ava.config.cjs` and defaults to no `.cjs` test. */
813
+ export const AVA_8 = avaRunner(["ava.config.js", "ava.config.mjs"], ["mjs", "js"]);
814
+
815
+ /** The patterns `node --test` runs with no operand (`doc/api/test.md`), the TypeScript ones where types are stripped. */
816
+ function nodeTestDefaults(stripped: boolean): readonly string[] {
817
+ const forms = ["**/*.test", "**/*-test", "**/*_test", "**/test-*", "**/test", "**/test/**/*"];
818
+ return [
819
+ ...forms.map((one) => `${one}.{cjs,mjs,js}`),
820
+ ...(stripped ? forms.map((one) => `${one}.{cts,mts,ts}`) : []),
821
+ ];
822
+ }
823
+
824
+ /**
825
+ * The files each `node --test` run of a manifest's scripts runs, as the command reader reads
826
+ * it: its operands, each a glob read in the command's directory, or the default patterns where
827
+ * it writes none.
828
+ */
829
+ export function nodeTestGlobs(runs: readonly CommandTests[]): readonly TestGlob[] {
830
+ return runs.flatMap((run) =>
831
+ (run.patterns.length > 0 ? run.patterns : nodeTestDefaults(run.stripped)).map((one) =>
832
+ belowGlob(run.dir, one),
833
+ ),
834
+ );
835
+ }