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

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 (431) 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 +35 -9
  6. package/dist/src/amd.js +81 -0
  7. package/dist/src/analysis.js +274 -496
  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/commands/held.js +499 -0
  12. package/dist/src/commands/read.js +161 -0
  13. package/dist/src/commands/shell.js +169 -0
  14. package/dist/src/commands/tools.js +1847 -0
  15. package/dist/src/component-files.js +3 -2
  16. package/dist/src/component-layer.js +4 -2
  17. package/dist/src/config.js +6 -82
  18. package/dist/src/conformance-record.js +2 -2
  19. package/dist/src/consumers.js +10 -38
  20. package/dist/src/contextual-uses.js +57 -26
  21. package/dist/src/contract/config.generated.js +561 -0
  22. package/dist/src/contract/contract.generated.js +1370 -0
  23. package/dist/src/contract/report.generated.js +866 -0
  24. package/dist/src/dependencies.js +165 -122
  25. package/dist/src/destructuring.js +1 -19
  26. package/dist/src/edges.js +38 -122
  27. package/dist/src/element-access.js +181 -168
  28. package/dist/src/entries/build-scripts.js +222 -0
  29. package/dist/src/{configuration-files.js → entries/configuration-files.js} +52 -256
  30. package/dist/src/{convention-rows.js → entries/convention-rows.js} +170 -30
  31. package/dist/src/entries/conventions.js +740 -0
  32. package/dist/src/{html-entries.js → entries/html-entries.js} +12 -42
  33. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +5 -28
  34. package/dist/src/entries/run-directories.js +77 -0
  35. package/dist/src/entries/sources.js +144 -0
  36. package/dist/src/entries/static-module.js +670 -0
  37. package/dist/src/entries/test-runners.js +650 -0
  38. package/dist/src/entries/workflow-steps.js +345 -0
  39. package/dist/src/exemptions/argument-places.js +131 -0
  40. package/dist/src/exemptions/calls.js +338 -0
  41. package/dist/src/exemptions/classes.js +13 -0
  42. package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
  43. package/dist/src/exemptions/detectors.js +48 -0
  44. package/dist/src/{enum-group.js → exemptions/enum-group.js} +9 -16
  45. package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +7 -11
  46. package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +76 -54
  47. package/dist/src/exemptions/generated-file.js +241 -0
  48. package/dist/src/{injection-container.js → exemptions/injection-container.js} +20 -26
  49. package/dist/src/exemptions/installed-globals.js +173 -0
  50. package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
  51. package/dist/src/exemptions/lifecycle.js +150 -0
  52. package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
  53. package/dist/src/exemptions/serialization-contract.js +1276 -0
  54. package/dist/src/exemptions/sites.js +182 -0
  55. package/dist/src/{template-field.js → exemptions/template-field.js} +13 -23
  56. package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
  57. package/dist/src/exposure.js +400 -0
  58. package/dist/src/extensions.js +37 -0
  59. package/dist/src/facts.js +38 -0
  60. package/dist/src/failure.js +36 -0
  61. package/dist/src/file-facts.js +3 -26
  62. package/dist/src/findings/completion.js +109 -56
  63. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  64. package/dist/src/findings/emitters.js +27 -41
  65. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  66. package/dist/src/{in-operands.js → findings/in-operands.js} +3 -2
  67. package/dist/src/findings/interfaces.js +4 -5
  68. package/dist/src/findings/intra-function.js +16 -13
  69. package/dist/src/findings/non-code-artifacts.js +17 -12
  70. package/dist/src/findings/reads-and-writes.js +86 -27
  71. package/dist/src/findings/self-check.js +44 -30
  72. package/dist/src/findings/unused-declarations.js +29 -45
  73. package/dist/src/findings/visibility-narrowing.js +23 -31
  74. package/dist/src/findings-pass.js +13 -21
  75. package/dist/src/fs/document.js +52 -0
  76. package/dist/src/fs/manifest.js +67 -0
  77. package/dist/src/fs/tree.js +88 -0
  78. package/dist/src/git-ignore.js +39 -26
  79. package/dist/src/import-bindings.js +73 -28
  80. package/dist/src/index/sites.js +557 -0
  81. package/dist/src/index/values.js +124 -0
  82. package/dist/src/index.js +2 -2
  83. package/dist/src/indexed-access.js +523 -478
  84. package/dist/src/intra-function-parts.js +63 -43
  85. package/dist/src/inventory/commonjs.js +507 -0
  86. package/dist/src/inventory/enumerate.js +167 -0
  87. package/dist/src/inventory/syntax.js +236 -0
  88. package/dist/src/inventory.js +323 -724
  89. package/dist/src/javascript-file.js +48 -0
  90. package/dist/src/json-document.js +198 -67
  91. package/dist/src/keyof-comparisons.js +126 -107
  92. package/dist/src/kinds.js +3 -366
  93. package/dist/src/{components.js → liveness/components.js} +2 -3
  94. package/dist/src/{graph.js → liveness/graph.js} +52 -28
  95. package/dist/src/liveness/matrix.js +99 -0
  96. package/dist/src/liveness/sweep.js +558 -0
  97. package/dist/src/load/classify.js +289 -0
  98. package/dist/src/load/diagnostics.js +87 -0
  99. package/dist/src/load/discover.js +285 -0
  100. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  101. package/dist/src/load/expected-module.js +136 -0
  102. package/dist/src/load/load.js +27 -0
  103. package/dist/src/load/module-lookup.js +37 -0
  104. package/dist/src/load/package-scope.js +56 -0
  105. package/dist/src/load/plan.js +313 -0
  106. package/dist/src/load/specifiers.js +113 -0
  107. package/dist/src/load/synthesize.js +152 -0
  108. package/dist/src/load/test-globals.js +207 -0
  109. package/dist/src/load/test-joins.js +82 -0
  110. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  111. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  112. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +24 -66
  113. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  114. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  115. package/dist/src/module-declarations.js +70 -85
  116. package/dist/src/overrides.js +134 -112
  117. package/dist/src/partial.js +2 -1
  118. package/dist/src/paths.js +82 -16
  119. package/dist/src/platform-calls.js +78 -0
  120. package/dist/src/platform-symbols.js +134 -0
  121. package/dist/src/position.js +43 -1
  122. package/dist/src/print.js +11 -79
  123. package/dist/src/product.js +13 -0
  124. package/dist/src/query.js +95 -0
  125. package/dist/src/ref.js +12 -20
  126. package/dist/src/references/read-rule.js +1 -0
  127. package/dist/src/references/read-rules.js +48 -0
  128. package/dist/src/references/sink.js +237 -0
  129. package/dist/src/references/sources.js +450 -0
  130. package/dist/src/references.js +117 -1074
  131. package/dist/src/report-schema.js +51 -0
  132. package/dist/src/report.js +57 -41
  133. package/dist/src/reporters/annotations.js +15 -8
  134. package/dist/src/reporters/sarif.js +16 -10
  135. package/dist/src/reporters/template.js +4 -19
  136. package/dist/src/resolve.js +246 -730
  137. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +22 -24
  138. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +6 -15
  139. package/dist/src/roots/manifest.js +216 -0
  140. package/dist/src/roots/published-reach.js +412 -0
  141. package/dist/src/roots/root-set.js +487 -0
  142. package/dist/src/roots/unseen-loads.js +236 -0
  143. package/dist/src/run.js +66 -77
  144. package/dist/src/schema.js +320 -343
  145. package/dist/src/scope.js +38 -115
  146. package/dist/src/session.js +37 -29
  147. package/dist/src/source-files.js +6 -10
  148. package/dist/src/stores.js +4 -2
  149. package/dist/src/suppress-file.js +25 -28
  150. package/dist/src/suppress-inline.js +36 -83
  151. package/dist/src/suppress.js +50 -71
  152. package/dist/src/symbol-keys.js +92 -88
  153. package/dist/src/tests/file-class.js +76 -0
  154. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  155. package/dist/src/{test-support.js → tests/support.js} +5 -12
  156. package/dist/src/text/compare.js +26 -0
  157. package/dist/src/text/glob.js +194 -0
  158. package/dist/src/text/named-files.js +298 -0
  159. package/dist/src/text/yaml.js +684 -0
  160. package/dist/src/type-errors.js +11 -232
  161. package/dist/src/type-parameter-reads.js +178 -139
  162. package/dist/src/unimplemented-callees.js +262 -242
  163. package/dist/src/unwrap.js +23 -0
  164. package/dist/src/usage.js +10 -0
  165. package/dist/src/vendor/unbash/ansi-c.js +132 -0
  166. package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
  167. package/dist/src/vendor/unbash/arithmetic.js +591 -0
  168. package/dist/src/vendor/unbash/assignment.js +131 -0
  169. package/dist/src/vendor/unbash/chars.js +48 -0
  170. package/dist/src/vendor/unbash/command.js +41 -0
  171. package/dist/src/vendor/unbash/expansion.js +80 -0
  172. package/dist/src/vendor/unbash/glob.js +268 -0
  173. package/dist/src/vendor/unbash/heredoc.js +27 -0
  174. package/dist/src/vendor/unbash/internal-types.js +1 -0
  175. package/dist/src/vendor/unbash/lexer.js +4274 -0
  176. package/dist/src/vendor/unbash/limits.js +3 -0
  177. package/dist/src/vendor/unbash/parser.js +1366 -0
  178. package/dist/src/vendor/unbash/parts.js +57 -0
  179. package/dist/src/vendor/unbash/substitution-context.js +8 -0
  180. package/dist/src/vendor/unbash/traversal.js +212 -0
  181. package/dist/src/vendor/unbash/types.js +1 -0
  182. package/dist/src/vendor/unbash/word-value.js +61 -0
  183. package/dist/src/vendor/unbash/word.js +40 -0
  184. package/dist/src/verbs/analyze.js +194 -127
  185. package/dist/src/verbs/describe.js +13 -18
  186. package/dist/src/verbs/explain.js +85 -139
  187. package/dist/src/verbs/print-config.js +2 -1
  188. package/dist/src/verbs/print-projects.js +6 -4
  189. package/dist/src/verbs/print-retained.js +30 -6
  190. package/dist/src/verbs/print-roots.js +4 -2
  191. package/dist/src/verbs/verb.js +1 -5
  192. package/dist/src/verbs/version.js +14 -3
  193. package/jsr.json +2 -1
  194. package/package.json +14 -8
  195. package/src/alias-chain.ts +40 -11
  196. package/src/amd.ts +130 -0
  197. package/src/analysis.ts +426 -733
  198. package/src/augmentations.ts +93 -4
  199. package/src/automatic-types.ts +12 -30
  200. package/src/bindings.ts +460 -0
  201. package/src/commands/held.ts +632 -0
  202. package/src/commands/read.ts +242 -0
  203. package/src/commands/shell.ts +228 -0
  204. package/src/commands/tools.ts +2382 -0
  205. package/src/component-files.ts +4 -2
  206. package/src/component-layer.ts +4 -2
  207. package/src/config.ts +31 -302
  208. package/src/conformance-record.ts +2 -2
  209. package/src/consumers.ts +16 -66
  210. package/src/contextual-uses.ts +66 -34
  211. package/src/contract/config.generated.ts +821 -0
  212. package/src/contract/contract.generated.ts +1453 -0
  213. package/src/contract/report.generated.ts +1241 -0
  214. package/src/dependencies.ts +223 -128
  215. package/src/destructuring.ts +1 -31
  216. package/src/edges.ts +41 -140
  217. package/src/element-access.ts +190 -187
  218. package/src/entries/build-scripts.ts +292 -0
  219. package/src/{configuration-files.ts → entries/configuration-files.ts} +68 -278
  220. package/src/{convention-rows.ts → entries/convention-rows.ts} +227 -31
  221. package/src/{conventions.ts → entries/conventions.ts} +367 -583
  222. package/src/{html-entries.ts → entries/html-entries.ts} +22 -48
  223. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +7 -30
  224. package/src/entries/run-directories.ts +108 -0
  225. package/src/entries/sources.ts +266 -0
  226. package/src/entries/static-module.ts +858 -0
  227. package/src/entries/test-runners.ts +835 -0
  228. package/src/entries/workflow-steps.ts +432 -0
  229. package/src/exemptions/argument-places.ts +198 -0
  230. package/src/exemptions/calls.ts +483 -0
  231. package/src/exemptions/classes.ts +49 -0
  232. package/src/exemptions/decorator.ts +56 -0
  233. package/src/exemptions/detectors.ts +75 -0
  234. package/src/{enum-group.ts → exemptions/enum-group.ts} +10 -19
  235. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +7 -14
  236. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +85 -66
  237. package/src/exemptions/generated-file.ts +318 -0
  238. package/src/{injection-container.ts → exemptions/injection-container.ts} +29 -34
  239. package/src/exemptions/installed-globals.ts +255 -0
  240. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  241. package/src/exemptions/lifecycle.ts +286 -0
  242. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  243. package/src/exemptions/serialization-contract.ts +1729 -0
  244. package/src/exemptions/sites.ts +293 -0
  245. package/src/{template-field.ts → exemptions/template-field.ts} +22 -32
  246. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  247. package/src/exposure.ts +492 -0
  248. package/src/extensions.ts +45 -0
  249. package/src/facts.ts +46 -0
  250. package/src/failure.ts +63 -0
  251. package/src/file-facts.ts +15 -25
  252. package/src/finding.ts +10 -10
  253. package/src/findings/boundary.ts +11 -0
  254. package/src/findings/completion.ts +133 -64
  255. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  256. package/src/findings/emitter.ts +9 -10
  257. package/src/findings/emitters.ts +38 -52
  258. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  259. package/src/{in-operands.ts → findings/in-operands.ts} +4 -3
  260. package/src/findings/interfaces.ts +7 -9
  261. package/src/findings/intra-function.ts +16 -14
  262. package/src/findings/non-code-artifacts.ts +24 -15
  263. package/src/findings/reads-and-writes.ts +118 -41
  264. package/src/findings/self-check.ts +51 -38
  265. package/src/findings/unused-declarations.ts +31 -49
  266. package/src/findings/visibility-narrowing.ts +36 -40
  267. package/src/findings-pass.ts +15 -31
  268. package/src/fs/document.ts +70 -0
  269. package/src/fs/manifest.ts +96 -0
  270. package/src/fs/tree.ts +110 -0
  271. package/src/git-ignore.ts +50 -26
  272. package/src/host.ts +12 -5
  273. package/src/import-bindings.ts +98 -29
  274. package/src/index/sites.ts +781 -0
  275. package/src/index/values.ts +170 -0
  276. package/src/index.ts +2 -2
  277. package/src/indexed-access.ts +570 -527
  278. package/src/intra-function-parts.ts +73 -44
  279. package/src/inventory/commonjs.ts +696 -0
  280. package/src/inventory/enumerate.ts +203 -0
  281. package/src/inventory/syntax.ts +311 -0
  282. package/src/inventory.ts +442 -895
  283. package/src/javascript-file.ts +54 -0
  284. package/src/json-document.ts +241 -76
  285. package/src/keyof-comparisons.ts +133 -122
  286. package/src/kinds.ts +5 -413
  287. package/src/{components.ts → liveness/components.ts} +4 -6
  288. package/src/{graph.ts → liveness/graph.ts} +100 -35
  289. package/src/liveness/matrix.ts +162 -0
  290. package/src/liveness/sweep.ts +855 -0
  291. package/src/load/classify.ts +433 -0
  292. package/src/load/diagnostics.ts +154 -0
  293. package/src/load/discover.ts +471 -0
  294. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  295. package/src/load/expected-module.ts +194 -0
  296. package/src/load/load.ts +73 -0
  297. package/src/load/module-lookup.ts +50 -0
  298. package/src/load/package-scope.ts +74 -0
  299. package/src/load/plan.ts +513 -0
  300. package/src/load/specifiers.ts +171 -0
  301. package/src/load/synthesize.ts +230 -0
  302. package/src/load/test-globals.ts +251 -0
  303. package/src/load/test-joins.ts +98 -0
  304. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  305. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  306. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +41 -77
  307. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  308. package/src/{workspace.ts → load/workspace.ts} +90 -134
  309. package/src/module-declarations.ts +74 -98
  310. package/src/overrides.ts +162 -134
  311. package/src/partial.ts +3 -2
  312. package/src/paths.ts +92 -16
  313. package/src/platform-calls.ts +119 -0
  314. package/src/platform-symbols.ts +189 -0
  315. package/src/position.ts +44 -1
  316. package/src/print.ts +12 -92
  317. package/src/product.ts +18 -0
  318. package/src/query.ts +201 -10
  319. package/src/ref.ts +12 -20
  320. package/src/references/read-rule.ts +60 -0
  321. package/src/references/read-rules.ts +55 -0
  322. package/src/references/sink.ts +314 -0
  323. package/src/references/sources.ts +632 -0
  324. package/src/references.ts +187 -1450
  325. package/src/report-schema.ts +68 -0
  326. package/src/report.ts +103 -226
  327. package/src/reporters/annotations.ts +22 -11
  328. package/src/reporters/json.ts +1 -1
  329. package/src/reporters/reporter.ts +2 -4
  330. package/src/reporters/reporters.ts +1 -1
  331. package/src/reporters/sarif.ts +29 -16
  332. package/src/reporters/template.ts +4 -20
  333. package/src/reporters/text.ts +3 -3
  334. package/src/resolve.ts +284 -1115
  335. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +26 -29
  336. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +7 -16
  337. package/src/roots/manifest.ts +303 -0
  338. package/src/roots/published-reach.ts +486 -0
  339. package/src/{roots.ts → roots/root-set.ts} +286 -469
  340. package/src/roots/unseen-loads.ts +345 -0
  341. package/src/run.ts +75 -91
  342. package/src/schema.ts +391 -388
  343. package/src/scope.ts +46 -138
  344. package/src/session.ts +87 -55
  345. package/src/source-files.ts +6 -11
  346. package/src/stores.ts +15 -3
  347. package/src/suppress-file.ts +24 -33
  348. package/src/suppress-inline.ts +55 -91
  349. package/src/suppress.ts +78 -79
  350. package/src/symbol-keys.ts +104 -107
  351. package/src/tests/file-class.ts +135 -0
  352. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  353. package/src/{test-support.ts → tests/support.ts} +15 -21
  354. package/src/text/compare.ts +27 -0
  355. package/src/text/glob.ts +210 -0
  356. package/src/text/named-files.ts +388 -0
  357. package/src/text/yaml.ts +747 -0
  358. package/src/type-errors.ts +18 -288
  359. package/src/type-parameter-reads.ts +187 -158
  360. package/src/unimplemented-callees.ts +297 -280
  361. package/src/unwrap.ts +34 -0
  362. package/src/usage.ts +10 -0
  363. package/src/vendor/unbash/LICENSE +15 -0
  364. package/src/vendor/unbash/VENDORED.md +16 -0
  365. package/src/vendor/unbash/ansi-c.ts +132 -0
  366. package/src/vendor/unbash/arithmetic-parts.ts +53 -0
  367. package/src/vendor/unbash/arithmetic.ts +641 -0
  368. package/src/vendor/unbash/assignment.ts +163 -0
  369. package/src/vendor/unbash/chars.ts +48 -0
  370. package/src/vendor/unbash/command.ts +47 -0
  371. package/src/vendor/unbash/expansion.ts +144 -0
  372. package/src/vendor/unbash/glob.ts +284 -0
  373. package/src/vendor/unbash/heredoc.ts +34 -0
  374. package/src/vendor/unbash/internal-types.ts +94 -0
  375. package/src/vendor/unbash/lexer.ts +4352 -0
  376. package/src/vendor/unbash/limits.ts +3 -0
  377. package/src/vendor/unbash/parser.ts +1612 -0
  378. package/src/vendor/unbash/parts.ts +96 -0
  379. package/src/vendor/unbash/substitution-context.ts +20 -0
  380. package/src/vendor/unbash/traversal.ts +182 -0
  381. package/src/vendor/unbash/types.ts +660 -0
  382. package/src/vendor/unbash/word-value.ts +64 -0
  383. package/src/vendor/unbash/word.ts +50 -0
  384. package/src/verbs/analyze.ts +255 -164
  385. package/src/verbs/describe.ts +19 -17
  386. package/src/verbs/explain.ts +93 -169
  387. package/src/verbs/print-config.ts +2 -1
  388. package/src/verbs/print-projects.ts +6 -4
  389. package/src/verbs/print-retained.ts +36 -6
  390. package/src/verbs/print-roots.ts +4 -2
  391. package/src/verbs/verb.ts +2 -8
  392. package/src/verbs/version.ts +15 -3
  393. package/dist/src/base-url.js +0 -18
  394. package/dist/src/calls.js +0 -155
  395. package/dist/src/conventions.js +0 -937
  396. package/dist/src/discover.js +0 -468
  397. package/dist/src/entry-points.js +0 -529
  398. package/dist/src/exempt-classes.js +0 -75
  399. package/dist/src/exempt.js +0 -209
  400. package/dist/src/generated-file.js +0 -95
  401. package/dist/src/glob.js +0 -65
  402. package/dist/src/manifest.js +0 -165
  403. package/dist/src/matrix.js +0 -246
  404. package/dist/src/pnpm-workspace.js +0 -120
  405. package/dist/src/roots.js +0 -672
  406. package/dist/src/serialization-contract.js +0 -611
  407. package/dist/src/setup-failure.js +0 -23
  408. package/dist/src/sweep.js +0 -224
  409. package/dist/src/test-joins.js +0 -101
  410. package/dist/src/value-flow.js +0 -74
  411. package/dist/src/version.js +0 -7
  412. package/dist/src/workflow-steps.js +0 -185
  413. package/src/base-url.ts +0 -24
  414. package/src/calls.ts +0 -225
  415. package/src/decorator.ts +0 -78
  416. package/src/discover.ts +0 -653
  417. package/src/entry-points.ts +0 -687
  418. package/src/exempt-classes.ts +0 -123
  419. package/src/exempt.ts +0 -323
  420. package/src/generated-file.ts +0 -111
  421. package/src/glob.ts +0 -67
  422. package/src/manifest.ts +0 -234
  423. package/src/matrix.ts +0 -423
  424. package/src/pnpm-workspace.ts +0 -135
  425. package/src/serialization-contract.ts +0 -849
  426. package/src/setup-failure.ts +0 -39
  427. package/src/sweep.ts +0 -347
  428. package/src/test-joins.ts +0 -120
  429. package/src/value-flow.ts +0 -104
  430. package/src/version.ts +0 -7
  431. package/src/workflow-steps.ts +0 -216
@@ -6,46 +6,8 @@
6
6
  * No configuration file is run.
7
7
  */
8
8
 
9
- import {
10
- isArrayLiteralExpression,
11
- isAsExpression,
12
- isBinaryExpression,
13
- isCallExpression,
14
- isClassDeclaration,
15
- isComputedPropertyName,
16
- isExportAssignment,
17
- isExportDeclaration,
18
- isExpressionStatement,
19
- isFunctionDeclaration,
20
- isIdentifier,
21
- isImportDeclaration,
22
- isNamedExports,
23
- isNamespaceImport,
24
- isNoSubstitutionTemplateLiteral,
25
- isNonNullExpression,
26
- isNumericLiteral,
27
- isObjectBindingPattern,
28
- isObjectLiteralExpression,
29
- isParenthesizedExpression,
30
- isPropertyAccessExpression,
31
- isPropertyAssignment,
32
- isSatisfiesExpression,
33
- isShorthandPropertyAssignment,
34
- isSpreadAssignment,
35
- isStringLiteral,
36
- isVariableStatement,
37
- SyntaxKind,
38
- type AsExpression,
39
- type Identifier,
40
- type Node,
41
- type ObjectBindingPattern,
42
- type NonNullExpression,
43
- type ObjectLiteralExpression,
44
- type ParenthesizedExpression,
45
- type SatisfiesExpression,
46
- type SourceFile,
47
- } from "@typescript/native/ast";
48
- import { lineOf } from "./component-files.ts";
9
+ import type { SourceFile } from "@typescript/native/ast";
10
+ import { lineOf } from "../component-files.ts";
49
11
  import {
50
12
  CONVENTION_ROWS,
51
13
  type ConventionRow,
@@ -57,15 +19,41 @@ import {
57
19
  type PackageKey,
58
20
  type PrefixedNameReading,
59
21
  type ShortNameReading,
22
+ type TestDirectoryReading,
60
23
  type TestFileReading,
61
24
  } from "./convention-rows.ts";
62
25
  import { documentStrings } from "./configuration-files.ts";
63
- import { globExpression } from "./glob.ts";
64
- import type { Host } from "./host.ts";
65
- import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.ts";
66
- import type { SetupFailure } from "./setup-failure.ts";
67
- import type { SourceFiles } from "./source-files.ts";
68
- import { satisfies } from "./version-range.ts";
26
+ import { readCommands, type CommandReading } from "../commands/read.ts";
27
+ import { nodeTestGlobs, type RunnerInput, type TestGlob } from "./test-runners.ts";
28
+ import {
29
+ documentModule,
30
+ membersNamed,
31
+ staticModule,
32
+ textsOf,
33
+ valuesAt,
34
+ type StaticModule,
35
+ type Value,
36
+ } from "./static-module.ts";
37
+ import type { Host } from "../host.ts";
38
+ import {
39
+ ancestors,
40
+ cannotStartPackageName,
41
+ dirnamePath,
42
+ joinPath,
43
+ normalizePath,
44
+ relativePath,
45
+ resolvePath,
46
+ } from "../paths.ts";
47
+ import type { Failure } from "../failure.ts";
48
+ import type { SourceFiles } from "../source-files.ts";
49
+ import { satisfies } from "../version-range.ts";
50
+ import { bytewise } from "../text/compare.ts";
51
+ import { isRecord } from "../json-document.ts";
52
+ import { filesIn } from "../fs/tree.ts";
53
+ import { DEPENDENCY_SECTIONS, INSTALLED_PACKAGES, MANIFEST, readJSON } from "../fs/manifest.ts";
54
+ import { globExpression } from "../text/glob.ts";
55
+ import { yamlDocument } from "../text/yaml.ts";
56
+ import { APPENDED_EXTENSIONS, braced } from "../extensions.ts";
69
57
 
70
58
  /** One manifest the analysis reads. */
71
59
  export interface ConventionManifest {
@@ -110,7 +98,7 @@ interface GeneratedDirectory {
110
98
  /** A setup failure a row met, at the directory of the manifest that declared it. */
111
99
  interface ConventionFailure {
112
100
  readonly dir: string;
113
- readonly failure: SetupFailure;
101
+ readonly failure: Failure;
114
102
  }
115
103
 
116
104
  /** What the rows decided for one run. */
@@ -127,8 +115,10 @@ export interface Conventions {
127
115
  readonly uses: readonly string[];
128
116
  /** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
129
117
  readonly selected: ReadonlyMap<string, Selected>;
130
- /** The files, absolute, an applied row's test-file properties name. */
131
- readonly testFiles: ReadonlySet<string>;
118
+ /** The files, absolute, an applied row's test-file properties name, each with the string naming it. */
119
+ readonly testFiles: ReadonlyMap<string, string>;
120
+ /** The test-file patterns of the applied rows' test directory readings. */
121
+ readonly testGlobs: readonly TestGlob[];
132
122
  }
133
123
 
134
124
  /** The strings one configuration file holds at selection keys, which root no file. */
@@ -149,32 +139,15 @@ interface ConventionEntry {
149
139
  /** Parses one file's text with no program. */
150
140
  type ParseSource = (fileName: string, text: string) => SourceFile;
151
141
 
152
- const JSON_EXTENSION = ".json";
142
+ /** A configuration file its tool reads as a YAML document. */
143
+ const YAML_NAME = /\.ya?ml$/u;
153
144
 
154
- /** A configuration file with no extension that its tool reads as a JSON document. */
155
- const JSON_RC = /^\.[\w-]+rc$/u;
145
+ /** A configuration file its tool reads as a JSON document whose name does not end with `.json`. */
146
+ const JSON_NAME = /^\.[\w-]+rc$|\.jsonc$/u;
156
147
 
157
148
  /** The sections an installed package is required by, so its absence fails the run. */
158
149
  const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
159
150
 
160
- const DEPENDENCY_SECTIONS = [
161
- ...REQUIRED_SECTIONS,
162
- "peerDependencies",
163
- "optionalDependencies",
164
- ] as const;
165
-
166
- function isRecord(value: unknown): value is Record<string, unknown> {
167
- return typeof value === "object" && value !== null && !Array.isArray(value);
168
- }
169
-
170
- function readJSON(host: Host, path: string): unknown {
171
- try {
172
- return JSON.parse(host.readFile(path));
173
- } catch {
174
- return undefined;
175
- }
176
- }
177
-
178
151
  function declares(
179
152
  manifest: unknown,
180
153
  name: string,
@@ -195,353 +168,59 @@ function declares(
195
168
  * none, or `undefined` where no `node_modules` holds it.
196
169
  */
197
170
  function installedVersion(host: Host, dir: string, name: string): string | undefined {
198
- for (let at = normalizePath(dir); ; at = dirnamePath(at)) {
199
- const path = joinPath(at, "node_modules", name, "package.json");
171
+ for (const at of ancestors(dir)) {
172
+ const path = joinPath(at, INSTALLED_PACKAGES, name, MANIFEST);
200
173
  if (host.kindOf(path) === "file") {
201
174
  const manifest = readJSON(host, path);
202
175
  const version = isRecord(manifest) ? manifest["version"] : undefined;
203
176
  return typeof version === "string" ? version : "";
204
177
  }
205
- if (dirnamePath(at) === at) {
206
- return undefined;
207
- }
208
- }
209
- }
210
-
211
- /** Whether one node wraps an expression without changing its value. */
212
- function isWrapper(
213
- node: Node,
214
- ): node is ParenthesizedExpression | AsExpression | SatisfiesExpression | NonNullExpression {
215
- return (
216
- isParenthesizedExpression(node) ||
217
- isAsExpression(node) ||
218
- isSatisfiesExpression(node) ||
219
- isNonNullExpression(node)
220
- );
221
- }
222
-
223
- /** The expression a wrapper that changes no value holds. */
224
- function unwrapped(node: Node): Node {
225
- let at = node;
226
- while (isWrapper(at)) {
227
- at = at.expression;
228
- }
229
- return at;
230
- }
231
-
232
- /** The key one property is written with, where it is written as a name or as literal text. */
233
- function keyOf(property: Node): string | undefined {
234
- if (!isPropertyAssignment(property) && !isShorthandPropertyAssignment(property)) {
235
- return undefined;
236
- }
237
- const { name } = property;
238
- const key = isComputedPropertyName(name) ? name.expression : name;
239
- if (
240
- (isIdentifier(key) && key === name) ||
241
- isStringLiteral(key) ||
242
- isNoSubstitutionTemplateLiteral(key)
243
- ) {
244
- return key.text;
245
178
  }
246
179
  return undefined;
247
180
  }
248
181
 
249
- /** Whether one member's computed name is no literal, so it may set any key. */
250
- function computesKey(member: Node): boolean {
251
- const name = (member as { readonly name?: Node }).name;
252
- if (name === undefined || !isComputedPropertyName(name)) {
253
- return false;
254
- }
255
- const key = name.expression;
256
- return !(isStringLiteral(key) || isNumericLiteral(key) || isNoSubstitutionTemplateLiteral(key));
182
+ /** The objects a reading reads in one configuration file: each named call's options, or the default export. */
183
+ function readObjects(module: StaticModule, call: OptionsCall | undefined): readonly Value[] {
184
+ return call === undefined ? module.defaultExport : module.options(call.module, call.export);
257
185
  }
258
186
 
259
- /** The values one file's top-level variable declarations bind, by name. */
260
- function declaredValues(file: SourceFile): ReadonlyMap<string, Node> {
261
- const values = new Map<string, Node>();
262
- for (const statement of file.statements) {
263
- if (isVariableStatement(statement)) {
264
- for (const declaration of statement.declarationList.declarations) {
265
- if (isIdentifier(declaration.name) && declaration.initializer !== undefined) {
266
- values.set(declaration.name.text, declaration.initializer);
267
- }
268
- }
269
- }
270
- }
271
- return values;
272
- }
273
-
274
- function isModuleExports(node: Node): boolean {
275
- return (
276
- isPropertyAccessExpression(node) &&
277
- isIdentifier(node.expression) &&
278
- node.expression.text === "module" &&
279
- node.name.text === "exports"
280
- );
187
+ /** Where a value that a reading cannot take as a literal is written, undefined in a document with no positions. */
188
+ interface NotLiteral {
189
+ readonly at: number | undefined;
281
190
  }
282
191
 
283
192
  /**
284
- * Every expression or declaration one file exports as its default, in any module syntax.
285
- * A JSON module's default export is its document's value.
286
- */
287
- function defaultExports(file: SourceFile): readonly Node[] {
288
- if (file.fileName.endsWith(JSON_EXTENSION)) {
289
- return file.statements.flatMap((statement) =>
290
- isExpressionStatement(statement) ? [statement.expression] : [],
291
- );
292
- }
293
- const found: Node[] = [];
294
- for (const statement of file.statements) {
295
- if (isExportAssignment(statement)) {
296
- found.push(statement.expression);
297
- } else if (
298
- (isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
299
- statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true
300
- ) {
301
- found.push(statement);
302
- } else if (
303
- isExportDeclaration(statement) &&
304
- statement.moduleSpecifier === undefined &&
305
- statement.exportClause !== undefined &&
306
- isNamedExports(statement.exportClause)
307
- ) {
308
- for (const element of statement.exportClause.elements) {
309
- if (element.name.text === "default") {
310
- found.push(element.propertyName ?? element.name);
311
- }
312
- }
313
- } else if (
314
- isExpressionStatement(statement) &&
315
- isBinaryExpression(statement.expression) &&
316
- statement.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
317
- isModuleExports(statement.expression.left)
318
- ) {
319
- found.push(statement.expression.right);
320
- }
321
- }
322
- return found;
323
- }
324
-
325
- /** The declarations whose name is a binding of the scope they are written in. */
326
- const BINDING_KINDS: ReadonlySet<SyntaxKind> = new Set([
327
- SyntaxKind.VariableDeclaration,
328
- SyntaxKind.Parameter,
329
- SyntaxKind.BindingElement,
330
- SyntaxKind.FunctionDeclaration,
331
- SyntaxKind.ClassDeclaration,
332
- SyntaxKind.ImportClause,
333
- SyntaxKind.ImportSpecifier,
334
- SyntaxKind.NamespaceImport,
335
- SyntaxKind.ImportEqualsDeclaration,
336
- ]);
337
-
338
- /** Whether one file declares a binding named `require` at any depth, which shadows the global. */
339
- function declaresRequire(file: SourceFile): boolean {
340
- let found = false;
341
- const visit = (node: Node): void => {
342
- const name = (node as { readonly name?: Node }).name;
343
- if (
344
- name !== undefined &&
345
- isIdentifier(name) &&
346
- name.text === "require" &&
347
- BINDING_KINDS.has(node.kind)
348
- ) {
349
- found = true;
350
- }
351
- if (!found) {
352
- node.forEachChild(visit);
353
- }
354
- };
355
- file.forEachChild(visit);
356
- return found;
357
- }
358
-
359
- /**
360
- * The top-level `const`, `let` and `var` declarations that bind a `require` of one module,
361
- * written with the module's name as its one string-literal argument, in a file that
362
- * declares no binding named `require`: each binds as an import of the module does.
363
- */
364
- function requireBindings(
365
- file: SourceFile,
366
- module: string,
367
- ): readonly (Identifier | ObjectBindingPattern)[] {
368
- const found: (Identifier | ObjectBindingPattern)[] = [];
369
- for (const statement of file.statements) {
370
- if (!isVariableStatement(statement)) {
371
- continue;
372
- }
373
- for (const declaration of statement.declarationList.declarations) {
374
- const value =
375
- declaration.initializer === undefined ? undefined : unwrapped(declaration.initializer);
376
- const [argument, ...rest] =
377
- value !== undefined && isCallExpression(value) ? value.arguments : [];
378
- if (
379
- value === undefined ||
380
- !isCallExpression(value) ||
381
- !isIdentifier(value.expression) ||
382
- value.expression.text !== "require" ||
383
- argument === undefined ||
384
- rest.length > 0 ||
385
- !isStringLiteral(argument) ||
386
- argument.text !== module
387
- ) {
388
- continue;
389
- }
390
- if (isIdentifier(declaration.name) || isObjectBindingPattern(declaration.name)) {
391
- found.push(declaration.name);
392
- }
393
- }
394
- }
395
- return found.length > 0 && declaresRequire(file) ? [] : found;
396
- }
397
-
398
- /**
399
- * The first argument of every call one file makes to one module's export, through a
400
- * named or a namespace import; `undefined` for a call with no argument.
401
- */
402
- function optionsArguments(file: SourceFile, call: OptionsCall): readonly (Node | undefined)[] {
403
- const names = new Set<string>();
404
- const namespaces = new Set<string>();
405
- for (const name of requireBindings(file, call.module)) {
406
- if (isIdentifier(name)) {
407
- namespaces.add(name.text);
408
- continue;
409
- }
410
- for (const element of name.elements) {
411
- const local = element.name;
412
- const exported = element.propertyName ?? local;
413
- if (
414
- element.dotDotDotToken === undefined &&
415
- local !== undefined &&
416
- isIdentifier(local) &&
417
- exported !== undefined &&
418
- (isIdentifier(exported) || isStringLiteral(exported)) &&
419
- exported.text === call.export
420
- ) {
421
- names.add(local.text);
422
- }
423
- }
424
- }
425
- for (const statement of file.statements) {
426
- const bindings = isImportDeclaration(statement)
427
- ? statement.importClause?.namedBindings
428
- : undefined;
429
- if (
430
- bindings === undefined ||
431
- !isImportDeclaration(statement) ||
432
- !isStringLiteral(statement.moduleSpecifier) ||
433
- statement.moduleSpecifier.text !== call.module
434
- ) {
435
- continue;
436
- }
437
- if (isNamespaceImport(bindings)) {
438
- namespaces.add(bindings.name.text);
439
- continue;
440
- }
441
- for (const element of bindings.elements) {
442
- if ((element.propertyName ?? element.name).text === call.export) {
443
- names.add(element.name.text);
444
- }
445
- }
446
- }
447
- const found: (Node | undefined)[] = [];
448
- const visit = (node: Node): void => {
449
- if (isCallExpression(node)) {
450
- const callee = unwrapped(node.expression);
451
- const named = isIdentifier(callee) && names.has(callee.text);
452
- const throughNamespace =
453
- isPropertyAccessExpression(callee) &&
454
- isIdentifier(callee.expression) &&
455
- namespaces.has(callee.expression.text) &&
456
- callee.name.text === call.export;
457
- if (named || throughNamespace) {
458
- found.push(node.arguments[0]);
459
- }
460
- }
461
- node.forEachChild(visit);
462
- };
463
- file.forEachChild(visit);
464
- return found;
465
- }
466
-
467
- /**
468
- * The object literal one expression stands for, read through the file's top-level
469
- * declarations and, where `throughCalls`, through a call's first argument: `undefined`
470
- * for a call with no argument, and any other node where the expression is not written
471
- * out as an object literal.
472
- */
473
- function objectOf(
474
- node: Node,
475
- declared: ReadonlyMap<string, Node>,
476
- throughCalls: boolean,
477
- seen: ReadonlySet<string> = new Set(),
478
- ): Node | undefined {
479
- const at = unwrapped(node);
480
- if (isIdentifier(at)) {
481
- const value = declared.get(at.text);
482
- if (value !== undefined && !seen.has(at.text)) {
483
- return objectOf(value, declared, throughCalls, new Set([...seen, at.text]));
484
- }
485
- } else if (throughCalls && isCallExpression(at)) {
486
- const [first] = at.arguments;
487
- return first === undefined ? undefined : objectOf(first, declared, throughCalls, seen);
488
- }
489
- return at;
490
- }
491
-
492
- /** The text of a string literal or a template literal with no substitution, or undefined. */
493
- function literalText(node: Node): string | undefined {
494
- const at = unwrapped(node);
495
- return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
496
- }
497
-
498
- /**
499
- * The literal texts one value at a path's end writes: the literal itself, or, where
500
- * `elements`, each element of an array literal of literals. Undefined for any other value.
501
- */
502
- function literalTexts(held: Node, elements: boolean): readonly string[] | undefined {
503
- const one = literalText(held);
504
- if (one !== undefined) {
505
- return [one];
506
- }
507
- if (!elements || !isArrayLiteralExpression(held)) {
508
- return undefined;
509
- }
510
- const texts = held.elements.map(literalText);
511
- return texts.every((text) => text !== undefined) ? texts : undefined;
512
- }
513
-
514
- /**
515
- * Reads the values at one path of keys below one object literal into `values`, and
516
- * answers the first member that does not write its value out: a spread, a value that is
517
- * not a literal at the path's end, or one that is not an object literal before it.
193
+ * Reads the values at one path of keys below one object into `values`, and answers the
194
+ * first place that does not write its value out: an object that is no object literal, a
195
+ * spread or a computed key, a value at the path's end that is no literal, or one before
196
+ * it that is no object literal.
518
197
  */
519
198
  function readPath(
520
- object: ObjectLiteralExpression,
199
+ object: Value,
521
200
  keys: readonly string[],
522
201
  values: string[],
523
202
  elements: boolean,
524
- ): Node | undefined {
203
+ ): NotLiteral | undefined {
204
+ if (object.kind !== "object") {
205
+ return object;
206
+ }
525
207
  const [key, ...rest] = keys;
526
- for (const member of object.properties) {
527
- if (isSpreadAssignment(member) || computesKey(member)) {
208
+ for (const member of object.members) {
209
+ if (member.key === undefined) {
528
210
  return member;
529
211
  }
530
- if (keyOf(member) !== key) {
212
+ if (member.key !== key) {
531
213
  continue;
532
214
  }
533
- const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
534
215
  if (rest.length === 0) {
535
- const texts = held === undefined ? undefined : literalTexts(held, elements);
216
+ const texts = textsOf(member.value, elements);
536
217
  if (texts === undefined) {
537
218
  return member;
538
219
  }
539
220
  values.push(...texts);
540
221
  } else {
541
- if (held === undefined || !isObjectLiteralExpression(held)) {
542
- return member;
543
- }
544
- const failed = readPath(held, rest, values, elements);
222
+ const failed =
223
+ member.value.kind === "object" ? readPath(member.value, rest, values, elements) : member;
545
224
  if (failed !== undefined) {
546
225
  return failed;
547
226
  }
@@ -550,80 +229,45 @@ function readPath(
550
229
  return undefined;
551
230
  }
552
231
 
553
- /**
554
- * Every value one file writes at one property's path inside the objects it is read from,
555
- * as {@link readProperty} reads them, whatever else the path holds.
556
- */
557
- function valuesAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
558
- const declared = declaredValues(file);
559
- const objects =
560
- call === undefined
561
- ? defaultExports(file).map((one) => objectOf(one, declared, true))
562
- : optionsArguments(file, call).map((one) =>
563
- one === undefined ? undefined : objectOf(one, declared, false),
564
- );
565
- const found: Node[] = [];
566
- const visit = (object: Node, keys: readonly string[]): void => {
567
- if (!isObjectLiteralExpression(object)) {
568
- return;
569
- }
570
- const [key, ...rest] = keys;
571
- for (const member of object.properties) {
572
- if (!isPropertyAssignment(member) || keyOf(member) !== key) {
573
- continue;
574
- }
575
- const held = unwrapped(member.initializer);
576
- if (rest.length > 0) {
577
- visit(held, rest);
578
- } else {
579
- found.push(held);
580
- }
581
- }
582
- };
583
- for (const object of objects) {
584
- if (object !== undefined) {
585
- visit(object, property.split("."));
586
- }
587
- }
588
- return found;
589
- }
590
-
591
- /** The string literals among values: each value itself, or each element of an array value. */
592
- function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
593
- return valuesAt(file, property, call).flatMap((held) => {
594
- if (literalText(held) !== undefined) {
595
- return [held];
596
- }
597
- return isArrayLiteralExpression(held)
598
- ? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
599
- : [];
600
- });
232
+ /** The text values among the values at one path: each value itself, or each element of an array value. */
233
+ function stringsAt(
234
+ objects: readonly Value[],
235
+ property: string,
236
+ ): readonly { readonly text: string; readonly at: number | undefined }[] {
237
+ return valuesAt(objects, property.split(".")).flatMap((value) =>
238
+ (value.kind === "array" ? value.elements : [value]).flatMap((one) =>
239
+ one.kind === "text" ? [one] : [],
240
+ ),
241
+ );
601
242
  }
602
243
 
603
244
  /** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
604
- function holdsValue(held: Node, value: true | string): boolean {
245
+ function holdsValue(held: Value, value: true | string): boolean {
605
246
  if (value === true) {
606
- return held.kind === SyntaxKind.TrueKeyword;
247
+ return held.kind === "boolean" && held.value;
607
248
  }
608
- if (literalText(held) === value) {
609
- return true;
249
+ if (held.kind === "text") {
250
+ return held.text === value;
610
251
  }
611
252
  return (
612
- isArrayLiteralExpression(held) &&
253
+ held.kind === "array" &&
613
254
  held.elements.some((element) => {
614
- const one = unwrapped(element);
615
- const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
616
- return first !== undefined && literalText(first) === value;
255
+ const first = element.kind === "array" ? element.elements[0] : element;
256
+ return first?.kind === "text" && first.text === value;
617
257
  })
618
258
  );
619
259
  }
620
260
 
621
- /** The directory one file's test-file strings are read against, as {@link TestFileReading} states it. */
622
- function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading): string | undefined {
623
- for (const property of reading.roots) {
624
- const values = valuesAt(file, property, reading.call);
261
+ /** The directory one group of objects' test-file strings are read against, as {@link TestFileReading} states it. */
262
+ function testFileRoot(
263
+ objects: readonly Value[],
264
+ dir: string,
265
+ roots: readonly string[],
266
+ ): string | undefined {
267
+ for (const property of roots) {
268
+ const values = valuesAt(objects, property.split("."));
625
269
  if (values.length > 0) {
626
- const texts = new Set(values.map(literalText));
270
+ const texts = new Set(values.map((one) => (one.kind === "text" ? one.text : undefined)));
627
271
  const [text] = texts;
628
272
  return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
629
273
  }
@@ -631,45 +275,146 @@ function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading):
631
275
  return dir;
632
276
  }
633
277
 
634
- /** The files an applied row's test-file properties name. */
278
+ /**
279
+ * The files an applied row's test-file properties name, each string also added to
280
+ * `selected`, since a test file is rooted by no configuration string (conventions.11). A
281
+ * property's path up to its last `*` selects objects that each resolve their strings
282
+ * against their own roots.
283
+ */
635
284
  function testFilesNamed(
636
- host: Host,
637
- parse: ParseSource,
285
+ read: ReadFiles,
638
286
  dir: string,
639
287
  readings: readonly TestFileReading[],
640
- into: Set<string>,
288
+ into: Map<string, string>,
289
+ selected: Map<string, { starts: Set<number>; texts: string[] }>,
641
290
  ): void {
642
291
  for (const reading of readings) {
643
- for (const { file } of readingFiles(host, parse, dir, reading.files)) {
644
- const at = testFileRoot(file, dir, reading);
645
- if (at === undefined) {
646
- continue;
292
+ const star = reading.property.lastIndexOf("*.");
293
+ const each = star < 0 ? undefined : reading.property.slice(0, star + 1).split(".");
294
+ const property = star < 0 ? reading.property : reading.property.slice(star + 2);
295
+ for (const { name, module } of read(dir, reading.files)) {
296
+ const path = joinPath(dir, name);
297
+ const held = selected.get(path) ?? { starts: new Set<number>(), texts: [] };
298
+ selected.set(path, held);
299
+ const objects = readObjects(module, reading.call);
300
+ const groups = each === undefined ? [objects] : valuesAt(objects, each).map((one) => [one]);
301
+ for (const group of groups) {
302
+ const at = testFileRoot(group, dir, reading.roots);
303
+ if (at === undefined) {
304
+ continue;
305
+ }
306
+ for (const { text, at: start } of stringsAt(group, property)) {
307
+ if (start !== undefined) {
308
+ held.starts.add(start);
309
+ }
310
+ held.texts.push(text);
311
+ if (text !== "") {
312
+ into.set(resolvePath(at, text), text);
313
+ }
314
+ }
647
315
  }
648
- for (const node of stringsAt(file, reading.property, reading.call)) {
649
- const text = literalText(node);
650
- if (text !== undefined && text !== "") {
651
- into.add(resolvePath(at, text));
316
+ }
317
+ }
318
+ }
319
+
320
+ /** The directory and patterns one configuration object's tests are read from, undefined where its text does not state them. */
321
+ interface TestDirectory {
322
+ readonly directory: string;
323
+ readonly patterns: readonly string[];
324
+ }
325
+
326
+ /** The one value an object's last write of a key gives, `null` where it writes none, undefined where it may hide one. */
327
+ function lastMember(object: Value, key: string): Value | null | undefined {
328
+ const values = membersNamed(object, key);
329
+ return values === undefined ? undefined : (values.at(-1) ?? null);
330
+ }
331
+
332
+ /** The test directory one object states, each member it does not write taken from `inherited`. */
333
+ function testDirectoryOf(
334
+ object: Value,
335
+ reading: TestDirectoryReading,
336
+ inherited: TestDirectory,
337
+ ): TestDirectory | undefined {
338
+ if (object.kind !== "object") {
339
+ return undefined;
340
+ }
341
+ const directory = lastMember(object, reading.directory);
342
+ const match = lastMember(object, reading.match);
343
+ if (directory === undefined || match === undefined) {
344
+ return undefined;
345
+ }
346
+ const dirs = directory === null ? [inherited.directory] : textsOf(directory, false);
347
+ const patterns = match === null ? inherited.patterns : textsOf(match, true);
348
+ const [one] = dirs ?? [];
349
+ return one === undefined || patterns === undefined ? undefined : { directory: one, patterns };
350
+ }
351
+
352
+ /**
353
+ * The test-file patterns an applied row's test directory readings give: each configuration
354
+ * object's own, or, where it lists projects, each project's, a member a project does not
355
+ * write taken from the configuration. An object whose text does not state them gives none.
356
+ */
357
+ function testDirectoryGlobs(
358
+ read: ReadFiles,
359
+ dir: string,
360
+ readings: readonly TestDirectoryReading[],
361
+ into: TestGlob[],
362
+ ): void {
363
+ for (const reading of readings) {
364
+ const base = { directory: ".", patterns: [reading.defaultMatch] };
365
+ for (const { module } of read(dir, reading.files)) {
366
+ for (const config of module.defaultExport) {
367
+ const top = testDirectoryOf(config, reading, base);
368
+ const projects = lastMember(config, reading.projects);
369
+ if (top === undefined || projects === undefined) {
370
+ continue;
371
+ }
372
+ const listed =
373
+ projects === null ? [] : projects.kind === "array" ? projects.elements : [projects];
374
+ const each =
375
+ listed.length === 0 ? [top] : listed.map((one) => testDirectoryOf(one, reading, top));
376
+ for (const found of each) {
377
+ for (const pattern of found?.patterns ?? []) {
378
+ const expression = anywhereBelow(pattern);
379
+ into.push({
380
+ dir: resolvePath(dir, found?.directory ?? "."),
381
+ pattern,
382
+ matches: (name) => expression.test(name.replace(/^\/+/u, "")),
383
+ });
384
+ }
652
385
  }
653
386
  }
654
387
  }
655
388
  }
656
389
  }
657
390
 
391
+ /**
392
+ * The expression a test-file pattern denotes over a file's whole path, as Playwright's
393
+ * matcher reads it: a pattern gains a leading `**` segment unless it starts with one, and
394
+ * case is not compared (https://github.com/microsoft/playwright/blob/main/packages/playwright/src/util.ts,
395
+ * `createFileMatcher`).
396
+ */
397
+ function anywhereBelow(pattern: string): RegExp {
398
+ const expression = globExpression(pattern.startsWith("**/") ? pattern : `**/${pattern}`);
399
+ return new RegExp(expression.source, "iu");
400
+ }
401
+
658
402
  /** The strings an applied row's selection keys hold, added per configuration file to `into`. */
659
403
  function selectedStrings(
660
- host: Host,
661
- parse: ParseSource,
404
+ read: ReadFiles,
662
405
  dir: string,
663
406
  readings: readonly MoveReading[],
664
407
  into: Map<string, { starts: Set<number>; texts: string[] }>,
665
408
  ): void {
666
409
  for (const reading of readings) {
667
- for (const { name, file } of readingFiles(host, parse, dir, reading.files)) {
410
+ for (const { name, module } of read(dir, reading.files)) {
668
411
  const path = joinPath(dir, name);
669
412
  const held = into.get(path) ?? { starts: new Set<number>(), texts: [] };
670
- for (const node of stringsAt(file, reading.property, reading.call)) {
671
- held.starts.add(node.getStart());
672
- held.texts.push(literalText(node) ?? "");
413
+ for (const { text, at } of stringsAt(readObjects(module, reading.call), reading.property)) {
414
+ if (at !== undefined) {
415
+ held.starts.add(at);
416
+ }
417
+ held.texts.push(text);
673
418
  }
674
419
  into.set(path, held);
675
420
  }
@@ -695,7 +440,7 @@ function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly s
695
440
  }
696
441
 
697
442
  /** The extensions an entry a manifest entry key names without one may carry. */
698
- const ENTRY_EXTENSIONS = "{js,jsx,mjs,cjs,ts,tsx,mts,cts}";
443
+ const ENTRY_EXTENSIONS = braced(APPENDED_EXTENSIONS);
699
444
 
700
445
  /** The characters that make a manifest string a pattern rather than a directory's name. */
701
446
  const PATTERN = /[*?{}[\]]/u;
@@ -739,12 +484,12 @@ function manifestEntryGlobs(manifest: unknown, keys: readonly ManifestEntryKey[]
739
484
  interface PropertyReading {
740
485
  /** The literal values the file sets the property to. */
741
486
  readonly values: readonly string[];
742
- /** The position of the first value that is not a literal, where there is one. */
743
- readonly notLiteral?: Node;
487
+ /** Where the first value that is not a literal is written, where there is one. */
488
+ readonly notLiteral?: NotLiteral;
744
489
  }
745
490
 
746
491
  /**
747
- * Every value one file writes at one property's path inside the object it is read from:
492
+ * Every value one file writes at one property's path inside the objects it is read from:
748
493
  * the options argument of every call `call` names, or the file's default export. A string
749
494
  * literal or a template literal with no substitution is the value, and so is each element
750
495
  * of an array literal of them where `elements`. A read object, or an object along the
@@ -752,26 +497,14 @@ interface PropertyReading {
752
497
  * itself, is not a literal, and neither is any other value at the path.
753
498
  */
754
499
  export function readProperty(
755
- file: SourceFile,
500
+ module: StaticModule,
756
501
  property: string,
757
502
  call?: OptionsCall,
758
503
  elements = false,
759
504
  ): PropertyReading {
760
- const declared = declaredValues(file);
761
- const objects =
762
- call === undefined
763
- ? defaultExports(file).map((one) => objectOf(one, declared, true))
764
- : optionsArguments(file, call).map((one) =>
765
- one === undefined ? undefined : objectOf(one, declared, false),
766
- );
767
505
  const values: string[] = [];
768
- for (const object of objects) {
769
- if (object === undefined) {
770
- continue;
771
- }
772
- const notLiteral = isObjectLiteralExpression(object)
773
- ? readPath(object, property.split("."), values, elements)
774
- : object;
506
+ for (const object of readObjects(module, call)) {
507
+ const notLiteral = readPath(object, property.split("."), values, elements);
775
508
  if (notLiteral !== undefined) {
776
509
  return { values, notLiteral };
777
510
  }
@@ -815,24 +548,14 @@ function expand(
815
548
  /** The configuration files in one directory that one glob names, by name. */
816
549
  function configurationFiles(host: Host, dir: string, glob: string): readonly string[] {
817
550
  const expression = globExpression(glob);
818
- let entries;
819
- try {
820
- entries = host.readDirectory(dir);
821
- } catch {
822
- return [];
823
- }
824
- return entries
825
- .filter((entry) => !entry.directory && expression.test(entry.name))
826
- .map((entry) => entry.name)
827
- .sort();
551
+ return filesIn(host, dir, (name) => expression.test(name));
828
552
  }
829
553
 
830
554
  /** What one move resolved to: its directories, or the failure its property met. */
831
555
  type MoveResult = { readonly dirs: readonly (string | undefined)[] } | { readonly failure: string };
832
556
 
833
557
  function resolveMove(
834
- host: Host,
835
- parse: ParseSource,
558
+ read: ReadFiles,
836
559
  dir: string,
837
560
  row: ConventionRow,
838
561
  move: DirectoryMove,
@@ -840,13 +563,14 @@ function resolveMove(
840
563
  ): MoveResult {
841
564
  const set: string[] = [];
842
565
  for (const reading of move.readings) {
843
- for (const { name, text, file } of readingFiles(host, parse, dir, reading.files)) {
844
- const read = readProperty(file, reading.property, reading.call);
566
+ for (const { name, text, module } of read(dir, reading.files)) {
567
+ const read = readProperty(module, reading.property, reading.call);
845
568
  if (read.notLiteral !== undefined) {
846
- const line = lineOf(text, read.notLiteral.getStart());
569
+ const { at } = read.notLiteral;
570
+ const where = at === undefined ? name : `${name}:${String(lineOf(text, at))}`;
847
571
  return {
848
572
  failure:
849
- `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
573
+ `${where} sets ${reading.property}, which moves a directory ` +
850
574
  `the convention row ${row.name} reads, to a value that is not a string literal: ` +
851
575
  `write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
852
576
  `files in ts.entry_files`,
@@ -866,31 +590,44 @@ function resolveMove(
866
590
  return { dirs: [...new Set(dirs)] };
867
591
  }
868
592
 
869
- /** The texts each configuration file a reading names holds, parsed, in name order. */
870
- function readingFiles(
871
- host: Host,
872
- parse: ParseSource,
873
- dir: string,
874
- files: readonly string[],
875
- ): readonly { readonly name: string; readonly text: string; readonly file: SourceFile }[] {
876
- const found = [];
877
- for (const glob of files) {
878
- for (const name of configurationFiles(host, dir, glob)) {
879
- const path = joinPath(dir, name);
880
- let text: string;
881
- try {
882
- text = host.readFile(path);
883
- } catch {
884
- continue;
593
+ /** One configuration file a reading names, read as a static module. */
594
+ interface ReadFile {
595
+ readonly name: string;
596
+ readonly text: string;
597
+ readonly module: StaticModule;
598
+ }
599
+
600
+ /** The configuration files in one directory a reading's globs name, in name order. */
601
+ type ReadFiles = (dir: string, files: readonly string[]) => readonly ReadFile[];
602
+
603
+ /** A reader that reads and parses each configuration file once for every row and reading of a run. */
604
+ function readFilesOnce(host: Host, parse: ParseSource): ReadFiles {
605
+ const read = new Map<string, ReadFile | undefined>();
606
+ const readOne = (dir: string, name: string): ReadFile | undefined => {
607
+ const path = joinPath(dir, name);
608
+ if (read.has(path)) {
609
+ return read.get(path);
610
+ }
611
+ let found: ReadFile | undefined;
612
+ try {
613
+ const text = host.readFile(path);
614
+ if (YAML_NAME.test(name)) {
615
+ const value = yamlDocument(text);
616
+ found = value === undefined ? undefined : { name, text, module: documentModule(value) };
617
+ } else {
618
+ const file = parse(JSON_NAME.test(name) ? `${path}.json` : path, text);
619
+ found = { name, text, module: staticModule(file) };
885
620
  }
886
- found.push({
887
- name,
888
- text,
889
- file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
890
- });
621
+ } catch {
622
+ found = undefined;
891
623
  }
892
- }
893
- return found;
624
+ read.set(path, found);
625
+ return found;
626
+ };
627
+ return (dir, files) =>
628
+ files.flatMap((glob) =>
629
+ configurationFiles(host, dir, glob).flatMap((name) => readOne(dir, name) ?? []),
630
+ );
894
631
  }
895
632
 
896
633
  /**
@@ -899,8 +636,7 @@ function readingFiles(
899
636
  * declared dependency the row's package name stands for with some value.
900
637
  */
901
638
  function shortNameUses(
902
- host: Host,
903
- parse: ParseSource,
639
+ read: ReadFiles,
904
640
  dir: string,
905
641
  manifest: unknown,
906
642
  readings: readonly ShortNameReading[],
@@ -914,8 +650,8 @@ function shortNameUses(
914
650
  const used = new Set<string>();
915
651
  for (const reading of readings) {
916
652
  const [before = "", after = ""] = reading.package.split("{}");
917
- for (const { file } of readingFiles(host, parse, dir, reading.files)) {
918
- const read = readProperty(file, reading.property, reading.call, true);
653
+ for (const { module } of read(dir, reading.files)) {
654
+ const read = readProperty(module, reading.property, reading.call, true);
919
655
  for (const value of read.values) {
920
656
  used.add(`${before}${value}${after}`);
921
657
  }
@@ -932,7 +668,7 @@ function shortNameUses(
932
668
  }
933
669
  }
934
670
  }
935
- return [...used].filter((name) => declared.has(name)).sort(compare);
671
+ return [...used].filter((name) => declared.has(name)).sort(bytewise);
936
672
  }
937
673
 
938
674
  /**
@@ -943,7 +679,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
943
679
  const { prefix, marker } = reading;
944
680
  let name: string;
945
681
  if (marker === undefined) {
946
- if (value.includes(":") || value.startsWith(".") || value.startsWith("/")) {
682
+ if (value.includes(":") || cannotStartPackageName(value)) {
947
683
  return undefined;
948
684
  }
949
685
  const parts = value.split("/");
@@ -969,8 +705,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
969
705
 
970
706
  /** The declared dependencies an applied row's prefixed names use. */
971
707
  function prefixedNameUses(
972
- host: Host,
973
- parse: ParseSource,
708
+ read: ReadFiles,
974
709
  dir: string,
975
710
  manifest: unknown,
976
711
  readings: readonly PrefixedNameReading[],
@@ -983,8 +718,8 @@ function prefixedNameUses(
983
718
  );
984
719
  const used = new Set<string>();
985
720
  for (const reading of readings) {
986
- for (const { file } of readingFiles(host, parse, dir, reading.files)) {
987
- for (const value of readProperty(file, reading.property, reading.call, true).values) {
721
+ for (const { module } of read(dir, reading.files)) {
722
+ for (const value of readProperty(module, reading.property, reading.call, true).values) {
988
723
  const named = prefixedPackage(value, reading);
989
724
  if (named !== undefined && declared.has(named)) {
990
725
  used.add(named);
@@ -992,13 +727,12 @@ function prefixedNameUses(
992
727
  }
993
728
  }
994
729
  }
995
- return [...used].sort(compare);
730
+ return [...used].sort(bytewise);
996
731
  }
997
732
 
998
733
  /** The declared dependencies an applied row's helper settings use. */
999
734
  function helperUses(
1000
- host: Host,
1001
- parse: ParseSource,
735
+ read: ReadFiles,
1002
736
  dir: string,
1003
737
  manifest: unknown,
1004
738
  readings: readonly HelperReading[],
@@ -1013,8 +747,8 @@ function helperUses(
1013
747
  .filter(
1014
748
  (reading) =>
1015
749
  declared.has(reading.package) &&
1016
- readingFiles(host, parse, dir, reading.files).some(({ file }) =>
1017
- valuesAt(file, reading.property, reading.call).some((held) =>
750
+ read(dir, reading.files).some(({ module }) =>
751
+ valuesAt(readObjects(module, reading.call), reading.property.split(".")).some((held) =>
1018
752
  holdsValue(held, reading.value),
1019
753
  ),
1020
754
  ),
@@ -1028,8 +762,7 @@ function helperUses(
1028
762
  * by `:` and a name. A member's own name is no such string.
1029
763
  */
1030
764
  function packageKeyUses(
1031
- host: Host,
1032
- parse: ParseSource,
765
+ read: ReadFiles,
1033
766
  dir: string,
1034
767
  manifest: unknown,
1035
768
  keys: readonly PackageKey[],
@@ -1039,31 +772,31 @@ function packageKeyUses(
1039
772
  return isRecord(held) ? Object.keys(held) : [];
1040
773
  });
1041
774
  const strings: string[] = [];
1042
- const collect = (node: Node): void => {
1043
- const text = literalText(node);
1044
- if (text !== undefined) {
1045
- strings.push(text);
1046
- } else if (isPropertyAssignment(node)) {
1047
- collect(node.initializer);
1048
- } else {
1049
- node.forEachChild(collect);
775
+ const children = (value: Value): readonly Value[] =>
776
+ value.kind === "object"
777
+ ? value.members.map((member) => member.value)
778
+ : value.kind === "array"
779
+ ? value.elements
780
+ : [];
781
+ const collect = (value: Value): void => {
782
+ if (value.kind === "text") {
783
+ strings.push(value.text);
1050
784
  }
785
+ children(value).forEach(collect);
1051
786
  };
1052
787
  for (const { key, files } of keys) {
1053
- const visit = (node: Node): void => {
1054
- if (isPropertyAssignment(node) && keyOf(node) === key) {
1055
- collect(node.initializer);
788
+ const visit = (value: Value): void => {
789
+ if (value.kind === "object") {
790
+ value.members
791
+ .filter((member) => member.key === key)
792
+ .forEach((member) => {
793
+ collect(member.value);
794
+ });
1056
795
  }
1057
- node.forEachChild(visit);
796
+ children(value).forEach(visit);
1058
797
  };
1059
- for (const { file } of readingFiles(host, parse, dir, files)) {
1060
- const values = declaredValues(file);
1061
- for (const one of defaultExports(file)) {
1062
- const object = objectOf(one, values, true);
1063
- if (object !== undefined) {
1064
- visit(object);
1065
- }
1066
- }
798
+ for (const { module } of read(dir, files)) {
799
+ module.defaultExport.forEach(visit);
1067
800
  }
1068
801
  }
1069
802
  return declared.filter((name) =>
@@ -1076,12 +809,14 @@ function packageKeyUses(
1076
809
  );
1077
810
  }
1078
811
 
1079
- /** Two strings ordered bytewise. */
1080
- function compare(a: string, b: string): number {
1081
- if (a === b) {
1082
- return 0;
1083
- }
1084
- return a < b ? -1 : 1;
812
+ /** What the command reader reads each of a manifest's `scripts` as, in the manifest's directory. */
813
+ function scriptReadings(manifest: unknown, dir: string): readonly CommandReading[] {
814
+ const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
815
+ return isRecord(scripts)
816
+ ? Object.values(scripts).flatMap((text) =>
817
+ typeof text === "string" ? [readCommands(text, dir)] : [],
818
+ )
819
+ : [];
1085
820
  }
1086
821
 
1087
822
  /**
@@ -1101,9 +836,31 @@ export function readConventions(
1101
836
  const failures: ConventionFailure[] = [];
1102
837
  const uses = new Set<string>();
1103
838
  const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
1104
- const testFiles = new Set<string>();
839
+ const testFiles = new Map<string, string>();
840
+ const testGlobs: TestGlob[] = [];
841
+ const read = readFilesOnce(host, parse);
1105
842
  for (const manifest of manifests) {
1106
- const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
843
+ const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
844
+ const scripts = scriptReadings(declared, manifest.dir);
845
+ let input: RunnerInput | undefined;
846
+ const runnerInput = (): RunnerInput => {
847
+ input ??= {
848
+ host,
849
+ dir: manifest.dir,
850
+ manifest: declared,
851
+ configuration: (dir, names) => {
852
+ for (const name of names) {
853
+ const [found] = read(dir, [name]);
854
+ if (found !== undefined) {
855
+ return { dir, module: found.module };
856
+ }
857
+ }
858
+ return undefined;
859
+ },
860
+ scripts: scripts.flatMap((one) => one.commands),
861
+ };
862
+ return input;
863
+ };
1107
864
  const versions = new Map<string, string | undefined>();
1108
865
  const missing = new Set<string>();
1109
866
  for (const row of rows) {
@@ -1126,7 +883,7 @@ export function readConventions(
1126
883
  failures.push({
1127
884
  dir: manifest.dir,
1128
885
  failure: {
1129
- setupClass: "missing-module",
886
+ failureClass: "missing-module",
1130
887
  detail:
1131
888
  `${manifest.path} declares ${row.package}, which no node_modules at or ` +
1132
889
  `above its directory holds: run the package manager's install`,
@@ -1140,7 +897,7 @@ export function readConventions(
1140
897
  const resolved = new Map<string, readonly (string | undefined)[]>();
1141
898
  let failed: string | undefined;
1142
899
  for (const move of row.moves) {
1143
- const result = resolveMove(host, parse, manifest.dir, row, move, resolved);
900
+ const result = resolveMove(read, manifest.dir, row, move, resolved);
1144
901
  if ("failure" in result) {
1145
902
  failed = result.failure;
1146
903
  break;
@@ -1152,7 +909,7 @@ export function readConventions(
1152
909
  failures.push({
1153
910
  dir: manifest.dir,
1154
911
  failure: {
1155
- setupClass: "convention-not-literal",
912
+ failureClass: "convention-not-literal",
1156
913
  detail: where === "." ? failed : `${where}/${failed}`,
1157
914
  },
1158
915
  });
@@ -1164,25 +921,27 @@ export function readConventions(
1164
921
  version,
1165
922
  manifest: manifest.path,
1166
923
  });
1167
- for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
924
+ for (const name of shortNameUses(read, manifest.dir, declared, row.shortNames ?? [])) {
1168
925
  uses.add(name);
1169
926
  }
1170
927
  const prefixed = row.prefixedNames ?? [];
1171
- for (const name of prefixedNameUses(host, parse, manifest.dir, declared, prefixed)) {
928
+ for (const name of prefixedNameUses(read, manifest.dir, declared, prefixed)) {
1172
929
  uses.add(name);
1173
930
  }
1174
931
  const keys = row.packageKeys ?? [];
1175
- for (const name of packageKeyUses(host, parse, manifest.dir, declared, keys)) {
932
+ for (const name of packageKeyUses(read, manifest.dir, declared, keys)) {
1176
933
  uses.add(name);
1177
934
  }
1178
935
  for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
1179
936
  uses.add(name);
1180
937
  }
1181
- for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
938
+ for (const name of helperUses(read, manifest.dir, declared, row.helpers ?? [])) {
1182
939
  uses.add(name);
1183
940
  }
1184
- selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
1185
- testFilesNamed(host, parse, manifest.dir, row.testFiles ?? [], testFiles);
941
+ selectedStrings(read, manifest.dir, row.selections ?? [], selected);
942
+ testFilesNamed(read, manifest.dir, row.testFiles ?? [], testFiles, selected);
943
+ testDirectoryGlobs(read, manifest.dir, row.testDirectories ?? [], testGlobs);
944
+ testGlobs.push(...(row.testRunner?.(runnerInput()) ?? []));
1186
945
  const excludes = [
1187
946
  ...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
1188
947
  ].map(globExpression);
@@ -1202,25 +961,50 @@ export function readConventions(
1202
961
  });
1203
962
  }
1204
963
  }
964
+ testGlobs.push(...nodeTestGlobs(scripts.flatMap((one) => one.tests)));
1205
965
  }
1206
966
  const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
1207
967
  unique.sort(
1208
968
  (a, b) =>
1209
- compare(a.name, b.name) ||
1210
- compare(a.manifest, b.manifest) ||
1211
- compare(JSON.stringify(a), JSON.stringify(b)),
969
+ bytewise(a.name, b.name) ||
970
+ bytewise(a.manifest, b.manifest) ||
971
+ bytewise(JSON.stringify(a), JSON.stringify(b)),
1212
972
  );
1213
973
  return {
1214
974
  applied: unique,
1215
975
  globs,
1216
976
  generated,
1217
977
  failures,
1218
- uses: [...uses].sort(compare),
978
+ uses: [...uses].sort(bytewise),
1219
979
  selected,
1220
980
  testFiles,
981
+ testGlobs,
1221
982
  };
1222
983
  }
1223
984
 
985
+ /**
986
+ * The files, absolute, of one project an applied row names as test files, each with the
987
+ * string or the pattern that names it.
988
+ */
989
+ export function conventionTestFiles(
990
+ conventions: Conventions,
991
+ files: SourceFiles,
992
+ ): ReadonlyMap<string, string> {
993
+ const found = new Map(conventions.testFiles);
994
+ for (const { dir, pattern, matches } of conventions.testGlobs) {
995
+ for (const file of files.byName.values()) {
996
+ if (
997
+ relativePath(dir, file.fileName) !== undefined &&
998
+ matches(file.fileName) &&
999
+ !found.has(file.fileName)
1000
+ ) {
1001
+ found.set(file.fileName, pattern);
1002
+ }
1003
+ }
1004
+ }
1005
+ return found;
1006
+ }
1007
+
1224
1008
  /**
1225
1009
  * Every own file of one project below a directory an applied row names as generated, by
1226
1010
  * its path below the target root, with the name of the first such row.
@@ -1273,7 +1057,7 @@ export function conventionFailuresFor(
1273
1057
  conventions: Conventions,
1274
1058
  configFile: string,
1275
1059
  ownFiles: readonly string[],
1276
- ): readonly SetupFailure[] {
1060
+ ): readonly Failure[] {
1277
1061
  return conventions.failures
1278
1062
  .filter(
1279
1063
  ({ dir }) =>