@jterrazz/test 16.1.1 → 18.0.0

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 (309) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +1 -2980
  3. package/dist/browser/services.js +1 -0
  4. package/dist/browser/stubs.js +1 -0
  5. package/dist/browser-shared-index.browser.js +3233 -0
  6. package/dist/checker.d.ts +6 -1
  7. package/dist/checker.js +3721 -2624
  8. package/dist/config.d.ts +40 -0
  9. package/dist/config.js +309 -0
  10. package/dist/in-place.global-setup.d.ts +12 -0
  11. package/dist/in-place.global-setup.js +21 -0
  12. package/dist/index.d.ts +1641 -3004
  13. package/dist/index.js +5095 -3835
  14. package/dist/mobile-session.global-setup.d.ts +15 -0
  15. package/dist/mobile-session.global-setup.js +31 -0
  16. package/dist/oxlint.cjs +6069 -5634
  17. package/dist/oxlint.d.cts +43 -7212
  18. package/dist/oxlint.d.ts +43 -7212
  19. package/dist/oxlint.js +3979 -5799
  20. package/dist/route-coverage.global-setup.d.ts +43 -0
  21. package/dist/route-coverage.global-setup.js +1 -0
  22. package/dist/services.d.ts +3 -0
  23. package/dist/services.global-setup.d.ts +43 -0
  24. package/dist/services.global-setup.js +1 -0
  25. package/dist/services.js +597 -0
  26. package/dist/shared-appium.adapter.js +428 -0
  27. package/dist/shared-artifacts.js +34 -0
  28. package/dist/shared-chapter.js +7 -0
  29. package/dist/shared-container.port.d.ts +31 -0
  30. package/dist/shared-derived-projects.js +73 -0
  31. package/dist/shared-device.port.d.ts +189 -0
  32. package/dist/shared-exchange.js +447 -0
  33. package/dist/shared-in-place.js +172 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-listened-port.js +19 -0
  40. package/dist/shared-literate-plugin.js +184 -0
  41. package/dist/shared-manifest.js +1321 -0
  42. package/dist/shared-pause.js +22 -0
  43. package/dist/shared-peer.js +88 -0
  44. package/dist/shared-playwright.adapter.js +328 -0
  45. package/dist/shared-process-alive.js +13 -0
  46. package/dist/shared-process.adapter.js +74 -0
  47. package/dist/shared-record-key.js +13 -0
  48. package/dist/shared-refusal.js +35 -0
  49. package/dist/shared-resolve.js +24 -0
  50. package/dist/shared-route-coverage.global-setup.js +187 -0
  51. package/dist/shared-scenario-failure.js +198 -0
  52. package/dist/shared-serve.js +407 -0
  53. package/dist/shared-service-images.js +10 -0
  54. package/dist/shared-services.global-setup.js +174 -0
  55. package/dist/shared-session-ledger.js +305 -0
  56. package/dist/shared-setup-entry.js +12 -0
  57. package/dist/shared-source-scan.js +133 -0
  58. package/dist/shared-spec-document.js +303 -0
  59. package/dist/shared-spec-grammar.js +66 -0
  60. package/dist/shared-specifications.js +26 -0
  61. package/dist/shared-specs-modules.js +1033 -0
  62. package/dist/shared-stub-reply.js +167 -0
  63. package/dist/shared-stub-server.js +142 -0
  64. package/dist/shared-temp-root.global-setup.js +73 -0
  65. package/dist/shared-test-baseline.js +26 -0
  66. package/dist/shared-test-section.js +58 -0
  67. package/dist/shared-testcontainers.adapter.js +306 -0
  68. package/dist/shared-tokens.js +55 -0
  69. package/dist/shared-yaml-document.js +119 -0
  70. package/dist/stubs.d.ts +2 -0
  71. package/dist/stubs.js +657 -0
  72. package/dist/temp-root.global-setup.d.ts +7 -0
  73. package/dist/temp-root.global-setup.js +1 -0
  74. package/dist/vitest.d.ts +93 -164
  75. package/dist/vitest.js +382 -550
  76. package/docs/README.md +82 -36
  77. package/docs/concepts/architecture.md +118 -0
  78. package/docs/concepts/conventions.md +333 -0
  79. package/docs/guides/api-specs.md +374 -0
  80. package/docs/guides/cli-specs.md +566 -0
  81. package/docs/guides/component-specs.md +252 -0
  82. package/docs/guides/develop.md +97 -0
  83. package/docs/guides/integration-specs.md +181 -0
  84. package/docs/guides/job-specs.md +180 -0
  85. package/docs/guides/lint-specs.md +174 -0
  86. package/docs/guides/mobile-specs.md +319 -0
  87. package/docs/guides/module-tests.md +149 -0
  88. package/docs/guides/release.md +44 -0
  89. package/docs/guides/spec-documents.md +189 -0
  90. package/docs/guides/website-specs.md +413 -0
  91. package/docs/reference/assertions.md +345 -0
  92. package/docs/reference/glossary.md +20 -0
  93. package/docs/reference/linting.md +483 -0
  94. package/docs/reference/services.md +236 -0
  95. package/docs/reference/stubs.md +500 -0
  96. package/docs/reference/time-and-doubles.md +137 -0
  97. package/docs/reference/tokens.md +258 -0
  98. package/docs/reference/visitor.md +221 -0
  99. package/docs/start/getting-started.md +187 -0
  100. package/docs/start/set-up-a-project.md +228 -0
  101. package/package.json +56 -36
  102. package/schema/spec.schema.json +43 -17
  103. package/dist/appium.adapter.js +0 -489
  104. package/dist/artifacts.js +0 -32
  105. package/dist/fixtures.js +0 -110
  106. package/dist/literate.js +0 -1747
  107. package/dist/match.js +0 -804
  108. package/dist/playwright.adapter.js +0 -438
  109. package/dist/server.js +0 -160
  110. package/dist/stream.js +0 -321
  111. package/dist/substring-warning.js +0 -282
  112. package/docs/01-architecture.md +0 -95
  113. package/docs/02-developing.md +0 -351
  114. package/docs/03-testing.md +0 -495
  115. package/docs/04-operating.md +0 -101
  116. package/docs/05-module-tests.md +0 -152
  117. package/docs/06-integration.md +0 -180
  118. package/docs/07-component.md +0 -246
  119. package/docs/08-website.md +0 -404
  120. package/docs/09-mobile.md +0 -270
  121. package/docs/10-api.md +0 -264
  122. package/docs/11-jobs.md +0 -157
  123. package/docs/12-cli.md +0 -677
  124. package/docs/13-elements.md +0 -248
  125. package/docs/14-assertions.md +0 -314
  126. package/docs/15-tokens.md +0 -253
  127. package/docs/16-contracts.md +0 -438
  128. package/docs/17-services.md +0 -188
  129. package/docs/18-conventions.md +0 -332
  130. package/docs/19-linting.md +0 -512
  131. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  132. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  133. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  134. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  135. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  136. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  137. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  138. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  139. package/docs/decisions/_template.md +0 -22
  140. package/docs/reference/classes/Matcher.md +0 -120
  141. package/docs/reference/classes/ProcessHandle.md +0 -368
  142. package/docs/reference/functions/defineContract.md +0 -39
  143. package/docs/reference/functions/defineContracts.md +0 -36
  144. package/docs/reference/functions/disabled.md +0 -28
  145. package/docs/reference/functions/enabled.md +0 -20
  146. package/docs/reference/functions/focused.md +0 -26
  147. package/docs/reference/functions/mockOf.md +0 -79
  148. package/docs/reference/functions/postgres.md +0 -31
  149. package/docs/reference/functions/process.md +0 -33
  150. package/docs/reference/functions/redis.md +0 -28
  151. package/docs/reference/functions/required.md +0 -41
  152. package/docs/reference/functions/selected.md +0 -26
  153. package/docs/reference/functions/sqlite.md +0 -33
  154. package/docs/reference/functions/testId.md +0 -20
  155. package/docs/reference/functions/text.md +0 -40
  156. package/docs/reference/functions/valued.md +0 -28
  157. package/docs/reference/functions/waitUntil.md +0 -46
  158. package/docs/reference/functions/within.md +0 -29
  159. package/docs/reference/index.md +0 -196
  160. package/docs/reference/interfaces/BaseResult.md +0 -87
  161. package/docs/reference/interfaces/CallResult.md +0 -160
  162. package/docs/reference/interfaces/CaptureScope.md +0 -82
  163. package/docs/reference/interfaces/CliResult.md +0 -253
  164. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  165. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  166. package/docs/reference/interfaces/FetchResult.md +0 -180
  167. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  168. package/docs/reference/interfaces/HttpResult.md +0 -126
  169. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  170. package/docs/reference/interfaces/PageResult.md +0 -356
  171. package/docs/reference/interfaces/RenderResult.md +0 -72
  172. package/docs/reference/interfaces/RenderedText.md +0 -110
  173. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  174. package/docs/reference/interfaces/ScreenResult.md +0 -128
  175. package/docs/reference/interfaces/TableAccessor.md +0 -26
  176. package/docs/reference/interfaces/TextAccessor.md +0 -96
  177. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  178. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  179. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  180. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  181. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  182. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  183. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  184. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  185. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  186. package/docs/reference/type-aliases/CliEnv.md +0 -11
  187. package/docs/reference/type-aliases/CliHandle.md +0 -80
  188. package/docs/reference/type-aliases/CliInput.md +0 -36
  189. package/docs/reference/type-aliases/CliOutput.md +0 -46
  190. package/docs/reference/type-aliases/CliPort.md +0 -58
  191. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  192. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  193. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  194. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  195. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  196. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  197. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  198. package/docs/reference/type-aliases/Contract.md +0 -68
  199. package/docs/reference/type-aliases/ContractInput.md +0 -13
  200. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  201. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  202. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  203. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  204. package/docs/reference/type-aliases/Contracts.md +0 -48
  205. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  206. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  207. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  208. package/docs/reference/type-aliases/DevicePort.md +0 -51
  209. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  210. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  211. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  212. package/docs/reference/type-aliases/DomMount.md +0 -21
  213. package/docs/reference/type-aliases/ElementKind.md +0 -27
  214. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  215. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  216. package/docs/reference/type-aliases/ElementRef.md +0 -128
  217. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  218. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  219. package/docs/reference/type-aliases/HonoApp.md +0 -31
  220. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  221. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  222. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  223. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  224. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  225. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  226. package/docs/reference/type-aliases/Intercept.md +0 -19
  227. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  228. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  229. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  230. package/docs/reference/type-aliases/JobHandle.md +0 -34
  231. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  232. package/docs/reference/type-aliases/JobsResult.md +0 -16
  233. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  234. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  235. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  236. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  237. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  238. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  239. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  240. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  241. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  242. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  243. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  244. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  245. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  246. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  247. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  248. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  249. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  250. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  251. package/docs/reference/type-aliases/MockPort.md +0 -10
  252. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  253. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  254. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  255. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  256. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  257. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  258. package/docs/reference/type-aliases/ServerPort.md +0 -36
  259. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  260. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  261. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  262. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  263. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  264. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  265. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  266. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  267. package/docs/reference/type-aliases/SpecKind.md +0 -8
  268. package/docs/reference/type-aliases/SpecRun.md +0 -108
  269. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  270. package/docs/reference/type-aliases/SpecStream.md +0 -32
  271. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  272. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  273. package/docs/reference/type-aliases/SseEvent.md +0 -58
  274. package/docs/reference/type-aliases/StreamBody.md +0 -61
  275. package/docs/reference/type-aliases/TextFilter.md +0 -22
  276. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  277. package/docs/reference/type-aliases/Visitor.md +0 -215
  278. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  279. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  280. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  281. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  282. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  283. package/docs/reference/variables/anthropic.md +0 -19
  284. package/docs/reference/variables/banner.md +0 -10
  285. package/docs/reference/variables/button.md +0 -10
  286. package/docs/reference/variables/clock.md +0 -16
  287. package/docs/reference/variables/complementary.md +0 -10
  288. package/docs/reference/variables/component.md +0 -10
  289. package/docs/reference/variables/content.md +0 -10
  290. package/docs/reference/variables/contentinfo.md +0 -10
  291. package/docs/reference/variables/dialog.md +0 -10
  292. package/docs/reference/variables/field.md +0 -10
  293. package/docs/reference/variables/form.md +0 -10
  294. package/docs/reference/variables/heading.md +0 -10
  295. package/docs/reference/variables/http.md +0 -40
  296. package/docs/reference/variables/intercept.md +0 -11
  297. package/docs/reference/variables/link.md +0 -10
  298. package/docs/reference/variables/listitem.md +0 -10
  299. package/docs/reference/variables/main.md +0 -10
  300. package/docs/reference/variables/match.md +0 -8
  301. package/docs/reference/variables/navigation.md +0 -10
  302. package/docs/reference/variables/openai.md +0 -21
  303. package/docs/reference/variables/option.md +0 -17
  304. package/docs/reference/variables/region.md +0 -10
  305. package/docs/reference/variables/row.md +0 -10
  306. package/docs/reference/variables/search.md +0 -10
  307. package/docs/reference/variables/specification.md +0 -8
  308. package/docs/reference/variables/status.md +0 -10
  309. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,1033 @@
1
+ import { n as parsedJson, t as isRecord } from "./shared-json-shape.js";
2
+ import { c as isRelativePath } from "./shared-spec-grammar.js";
3
+ import { a as literalOf, n as argumentsOf$1, r as closingOf } from "./shared-source-scan.js";
4
+ import { createRequire } from "node:module";
5
+ import { GROUND_FOLDERS, PRUNED, RENAMED_FOLDERS, RETIRED_SHARED_FOLDERS, SETUP_FOLDER, TESTS_FOLDER, blankComments, folderChanged, holdsManifest, isFile, isGroundFolder, isProjectRoot, isSpecialFolder, nearestRoot, readFileCached, treeEntries, treeKind } from "@jterrazz/typescript/rules";
6
+ import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
7
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
8
+ //#region lint/kit/project/settings-file.ts
9
+ const SETTINGS_FILE = "jterrazz.config.ts";
10
+ //#endregion
11
+ //#region lint/kit/project/specs-anchor.ts
12
+ function segments(path) {
13
+ return path.split(/[/\\]/u).filter(Boolean);
14
+ }
15
+ /** The NEAREST `specs` ancestor, never above the project root: a checkout living under `~/specs/` must not read as one whole specs tree. */
16
+ function specsAnchor(filename) {
17
+ const relative = [basename(filename)];
18
+ let directory = dirname(filename);
19
+ for (;;) {
20
+ if (basename(directory) === "specs") return {
21
+ directory,
22
+ relative
23
+ };
24
+ if (isProjectRoot(directory)) return;
25
+ const parent = dirname(directory);
26
+ if (parent === directory) return;
27
+ relative.unshift(basename(directory));
28
+ directory = parent;
29
+ }
30
+ }
31
+ function isUnderSpecs(filename) {
32
+ return specsAnchor(filename) !== void 0;
33
+ }
34
+ //#endregion
35
+ //#region lint/kit/project/role.ts
36
+ const GROUND = new Set(GROUND_FOLDERS);
37
+ function strayTestRootOf(filename) {
38
+ const parts = projectSegments(filename);
39
+ const first = parts.length > 1 ? parts[0] : void 0;
40
+ if (projectRootOf(dirname(filename)) !== void 0 && (first === "tests" || first === "test")) return first;
41
+ return null;
42
+ }
43
+ /** The order matters: a `*.specification.ts` is one wherever it sits, and `.tsx` versus `.ts` tells a rendered unit apart as the two project helpers do. Ground swallows only what carries no suffix of its own. */
44
+ function roleOf(filename) {
45
+ const parts = projectSegments(filename);
46
+ const base = parts.at(-1) ?? "";
47
+ const identity = {
48
+ inSpecs: isUnderSpecs(filename),
49
+ inTests: parts.slice(0, -1).includes(TESTS_FOLDER),
50
+ strayTestRoot: strayTestRootOf(filename)
51
+ };
52
+ if (base.endsWith(".specification.ts") || base.endsWith(".specification.tsx")) return {
53
+ ...identity,
54
+ role: "specification"
55
+ };
56
+ if (base.endsWith(".spec.yaml")) return {
57
+ ...identity,
58
+ role: "document"
59
+ };
60
+ if (base.endsWith(".spec.ts")) return {
61
+ ...identity,
62
+ role: "spec"
63
+ };
64
+ if (base.endsWith(".test.tsx")) return {
65
+ ...identity,
66
+ role: "component"
67
+ };
68
+ if (base.endsWith(".test.ts")) return {
69
+ ...identity,
70
+ role: "module"
71
+ };
72
+ if (base === "jterrazz.config.ts") return {
73
+ ...identity,
74
+ role: "config"
75
+ };
76
+ if (parts.slice(0, -1).some((segment) => GROUND.has(segment))) return {
77
+ ...identity,
78
+ role: "ground"
79
+ };
80
+ if (parts.slice(0, -1).includes("stubs")) return {
81
+ ...identity,
82
+ role: "stub"
83
+ };
84
+ return {
85
+ ...identity,
86
+ role: "other"
87
+ };
88
+ }
89
+ const TEST_ROLES = /* @__PURE__ */ new Set([
90
+ "component",
91
+ "module",
92
+ "spec"
93
+ ]);
94
+ /** For the passes that walk a tree and hold only a name: one vocabulary with `roleOf`. */
95
+ function isTestFileName(name) {
96
+ return name.endsWith(".test.ts") || name.endsWith(".test.tsx") || name.endsWith(".spec.ts");
97
+ }
98
+ /** A `.test.tsx` renders and is not one. */
99
+ function isNodeTestFileName(name) {
100
+ return name.endsWith(".test.ts") || name.endsWith(".spec.ts");
101
+ }
102
+ function isTestRole(role) {
103
+ return TEST_ROLES.has(role);
104
+ }
105
+ function packageRootOf(directory) {
106
+ return nearestRoot(directory, holdsManifest);
107
+ }
108
+ const projectRoots = /* @__PURE__ */ new Map();
109
+ /** Remembered per folder, and forgotten for one directory once its own tree listing changes: a `package.json` written or removed there is what the memo must see. */
110
+ function projectRootOf(directory) {
111
+ if (!folderChanged(directory) && projectRoots.has(directory)) return projectRoots.get(directory);
112
+ const root = nearestRoot(directory, isProjectRoot);
113
+ projectRoots.set(directory, root);
114
+ return root;
115
+ }
116
+ /** A path no package and no repository holds (a rule's test case, `/repo/...`) is read whole. A workspace member named `specs` is the specs tree itself, so its folder name stays the first segment of what it holds. */
117
+ function projectSegments(filename) {
118
+ const root = projectRootOf(dirname(filename));
119
+ if (root === void 0) return segments(filename);
120
+ return [...basename(root) === "specs" ? ["specs"] : [], ...segments(relative(root, filename))];
121
+ }
122
+ //#endregion
123
+ //#region lint/kit/project/ast.ts
124
+ function fieldOf(value, key) {
125
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0;
126
+ }
127
+ function stringValue(node) {
128
+ if (node === void 0) return;
129
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
130
+ if (node.type !== "TemplateLiteral" || childList(node, "expressions").length > 0) return;
131
+ const [text] = childList(node, "quasis");
132
+ return cookedText(text);
133
+ }
134
+ function cookedText(quasi) {
135
+ const cooked = fieldOf(quasi?.value, "cooked");
136
+ return typeof cooked === "string" ? cooked : void 0;
137
+ }
138
+ function memberPropertyName(node) {
139
+ if (node?.type !== "MemberExpression" || node.computed === true) return;
140
+ const property = child(node, "property");
141
+ return property?.type === "Identifier" ? stringField(property, "name") ?? "" : void 0;
142
+ }
143
+ const CHAIN_LINKS = {
144
+ AwaitExpression: "argument",
145
+ CallExpression: "callee",
146
+ ChainExpression: "expression",
147
+ MemberExpression: "object"
148
+ };
149
+ function chainRootName(node) {
150
+ let current = node;
151
+ while (current !== void 0) {
152
+ if (current.type === "Identifier") return stringField(current, "name") ?? "";
153
+ const link = CHAIN_LINKS[current.type];
154
+ if (link === void 0) return;
155
+ current = child(current, link);
156
+ }
157
+ }
158
+ function specificationMember(callNode) {
159
+ const callee = child(callNode, "callee");
160
+ if (callee?.type !== "MemberExpression") return;
161
+ const object = child(callee, "object");
162
+ if (object?.type !== "Identifier" || object.name !== "specification") return;
163
+ return memberPropertyName(callee);
164
+ }
165
+ /** Only an object, among the nodes read here, lists properties. */
166
+ function findProperty(objectNode, name) {
167
+ return childList(objectNode, "properties").find((property) => propertyKeyName(property) === name);
168
+ }
169
+ function propertyKeyName(property) {
170
+ if (property.type !== "Property" || property.computed === true) return;
171
+ const key = child(property, "key");
172
+ if (key?.type === "Identifier") return stringField(key, "name") ?? "";
173
+ if (key?.type === "Literal") return String(key.value);
174
+ }
175
+ function walk(root, visit) {
176
+ visit(root);
177
+ for (const key of Object.keys(root)) {
178
+ if (key === "parent") continue;
179
+ const value = root[key];
180
+ if (Array.isArray(value)) {
181
+ for (const item of value) if (isNode(item)) walk(item, visit);
182
+ } else if (isNode(value)) walk(value, visit);
183
+ }
184
+ }
185
+ function nodeStart(node) {
186
+ return typeof node?.start === "number" ? node.start : -1;
187
+ }
188
+ function isNode(value) {
189
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
190
+ }
191
+ function child(node, key) {
192
+ const value = node?.[key];
193
+ return isNode(value) ? value : void 0;
194
+ }
195
+ function childList(node, key) {
196
+ const value = node?.[key];
197
+ return Array.isArray(value) ? value.filter((item) => isNode(item)) : [];
198
+ }
199
+ function stringField(node, key) {
200
+ const value = node?.[key];
201
+ return typeof value === "string" ? value : void 0;
202
+ }
203
+ function identifierName(node) {
204
+ if (node?.type !== "Identifier") return;
205
+ const { name } = node;
206
+ return typeof name === "string" ? name : void 0;
207
+ }
208
+ function nodeEnd(node) {
209
+ return typeof node?.end === "number" ? node.end : -1;
210
+ }
211
+ /** `result.stdout`, `result.meta()`; `undefined` for a computed access: two subjects a rule cannot spell must never be equated. */
212
+ function memberPath(node) {
213
+ if (node === void 0) return;
214
+ if (node.type === "Identifier") return identifierName(node);
215
+ if (node.type === "CallExpression") {
216
+ const callee = memberPath(child(node, "callee"));
217
+ return callee === void 0 || childList(node, "arguments").length > 0 ? void 0 : `${callee}()`;
218
+ }
219
+ if (node.type !== "MemberExpression") return;
220
+ const object = memberPath(child(node, "object"));
221
+ const property = memberPropertyName(node);
222
+ return object === void 0 || property === void 0 ? void 0 : `${object}.${property}`;
223
+ }
224
+ /** `new Date()` with an argument is pinned; its zero-argument form, `Date.now()`, `performance.now()`, `Math.random()` and `randomUUID()` answer differently on the next run. */
225
+ function isSampledValue(node) {
226
+ if (node.type === "NewExpression") return identifierName(child(node, "callee")) === "Date" && childList(node, "arguments").length === 0;
227
+ if (node.type !== "CallExpression") return false;
228
+ const path = memberPath(child(node, "callee"));
229
+ if (path === void 0) return false;
230
+ return path === "Date.now" || path === "performance.now" || path === "Math.random" || path === "randomUUID" || path.endsWith(".randomUUID");
231
+ }
232
+ //#endregion
233
+ //#region lint/kit/project/command-words.ts
234
+ const COMMAND_WORD = /^[a-z][a-z0-9-]*$/u;
235
+ /** Without the runner's declared list, the first word alone: a positional argument is never read as a subcommand. */
236
+ function commandWords(line, commands) {
237
+ const words = line.trim().split(/\s+/u);
238
+ if (commands === void 0) {
239
+ const [first = ""] = words;
240
+ return COMMAND_WORD.test(first) ? [first] : [];
241
+ }
242
+ const named = [];
243
+ for (const word of words) {
244
+ if (!commands.includes(word)) break;
245
+ named.push(word);
246
+ }
247
+ return named;
248
+ }
249
+ const CLI_CONSTRUCTOR = /\bspecification\s*\.\s*cli\s*\(/u;
250
+ function wordsOf(text, open) {
251
+ return argumentsOf$1(text.slice(open + 1, closingOf(text, open))).flatMap((entry) => {
252
+ const word = literalOf(entry);
253
+ return word === void 0 ? [] : [word];
254
+ });
255
+ }
256
+ function constantWords(text, name) {
257
+ const escaped = name.replaceAll("$", String.raw`\$`);
258
+ const pattern = String.raw`\bconst\s+${escaped}\s*(?::[^=]+)?=\s*\[`;
259
+ const binding = new RegExp(pattern, "u").exec(text);
260
+ return binding === null ? void 0 : wordsOf(text, binding.index + binding[0].length - 1);
261
+ }
262
+ /** Written as an array or bound to a `const` of the same file (`commands: COMMANDS`, `{ commands }`). */
263
+ function declaredCommands(door) {
264
+ const text = blankComments(door);
265
+ const call = CLI_CONSTRUCTOR.exec(text);
266
+ if (call === null) return;
267
+ const open = call.index + call[0].length - 1;
268
+ const [, options = ""] = argumentsOf$1(text.slice(open + 1, closingOf(text, open)));
269
+ const list = /[{,\s]commands\s*:\s*\[/u.exec(options);
270
+ if (list !== null) return wordsOf(options, list.index + list[0].length - 1);
271
+ const named = /[{,\s]commands\s*:\s*(?<name>[A-Za-z_$][\w$]*)/u.exec(options)?.groups?.name ?? (/[{,\s]commands\s*(?=[,}])/u.test(options) ? "commands" : void 0);
272
+ return named === void 0 ? void 0 : constantWords(text, named);
273
+ }
274
+ //#endregion
275
+ //#region lint/kit/project/spec-chains.ts
276
+ const SUBJECT_SPECIFICATIONS = [
277
+ "api",
278
+ "cli",
279
+ "job",
280
+ "mobile",
281
+ "website"
282
+ ];
283
+ function chainAt(text, handle, start) {
284
+ const calls = [];
285
+ let index = start + handle.length;
286
+ for (;;) {
287
+ const member = /^\s*\.\s*(?<name>[A-Za-z_$][\w$]*)\s*\(/u.exec(text.slice(index));
288
+ if (member === null) return {
289
+ calls,
290
+ end: index,
291
+ handle,
292
+ index: start
293
+ };
294
+ const { name = "" } = member.groups ?? {};
295
+ const open = index + member[0].length - 1;
296
+ const close = closingOf(text, open);
297
+ calls.push({
298
+ args: text.slice(open + 1, close),
299
+ name
300
+ });
301
+ index = close + 1;
302
+ }
303
+ }
304
+ const SPECIFICATION_DOOR = /import\s*\{(?<names>[^}]*)\}\s*from\s*['"](?<from>[^'"]*\.specification(?:\.[jt]s)?)['"]/gu;
305
+ const VIEW = /const\s+(?<view>[A-Za-z_$][\w$]*)\s*=\s*(?<from>[A-Za-z_$][\w$]*)\s*\.\s*with\s*\(/gu;
306
+ function importedDoors(text, wanted) {
307
+ const doors = /* @__PURE__ */ new Map();
308
+ for (const found of text.matchAll(SPECIFICATION_DOOR)) {
309
+ const { from = "", names = "" } = found.groups ?? {};
310
+ for (const name of names.split(",")) {
311
+ const [imported = "", local = imported] = name.trim().split(/\s+as\s+/u);
312
+ if (wanted(imported)) doors.set(local, from);
313
+ }
314
+ }
315
+ return doors;
316
+ }
317
+ function doorsWanted(text, wanted) {
318
+ const doors = importedDoors(text, wanted);
319
+ const views = [...text.matchAll(VIEW)];
320
+ for (let size = -1; size !== doors.size;) {
321
+ ({size} = doors);
322
+ for (const found of views) {
323
+ const { from = "", view = "" } = found.groups ?? {};
324
+ const door = doors.get(from);
325
+ if (door !== void 0) doors.set(view, door);
326
+ }
327
+ }
328
+ return doors;
329
+ }
330
+ function handlesOf(text) {
331
+ return [...doorsWanted(text, (imported) => SUBJECT_SPECIFICATIONS.includes(imported)).keys()];
332
+ }
333
+ function chainsOf(text, handles) {
334
+ if (handles.length === 0) return [];
335
+ const pattern = new RegExp(String.raw`(?<![.\w$])(?:${handles.join("|")})(?=\s*\.)`, "gu");
336
+ return [...text.matchAll(pattern)].map((found) => chainAt(text, found[0], found.index));
337
+ }
338
+ function doorsOf(text, specification) {
339
+ return Object.fromEntries(doorsWanted(text, (imported) => imported === specification));
340
+ }
341
+ //#endregion
342
+ //#region lint/kit/project/subject.ts
343
+ const NO_READING = Object.freeze({
344
+ collections: [],
345
+ methodless: []
346
+ });
347
+ const LITERAL_SEGMENT = /^[a-z][a-z._-]*$/u;
348
+ function segmentsOf(path) {
349
+ return (path.split(/[?#]/u)[0] ?? "").split("/").filter((segment) => segment !== "");
350
+ }
351
+ function isLiteral(segment) {
352
+ return LITERAL_SEGMENT.test(segment) && !segment.includes("\0");
353
+ }
354
+ /** Every segment that is a value (a hole, a number, an id, a symbol) is written `:id`. */
355
+ function pathTemplate(path) {
356
+ return `/${segmentsOf(path).map((segment) => isLiteral(segment) ? segment : ":id").join("/")}`;
357
+ }
358
+ function isCollection(word, collections) {
359
+ return collections.includes(word) || word.endsWith("s") && !/(?:ss|us|is)$/u.test(word);
360
+ }
361
+ function actionOf(method, resource, item, collections) {
362
+ if (method === "GET") return !item && isCollection(resource, collections) ? "list" : "get";
363
+ if (method === "POST") return "create";
364
+ if (method === "PUT" || method === "PATCH") return "update";
365
+ return method.toLowerCase();
366
+ }
367
+ function followsACollection(segments, index, collections) {
368
+ return isCollection(segments[index - 1] ?? "", collections);
369
+ }
370
+ /** `btc` in `/indicators/btc/chart`. The last segment is read as the route's: after a collection it names a sub-route far more often than an item. */
371
+ function ambiguousSegment(subject, reading = NO_READING) {
372
+ if (subject.specification !== "api" || subject.template === true) return;
373
+ const segments = segmentsOf(subject.path);
374
+ const { collections } = reading;
375
+ return segments.find((segment, index) => index < segments.length - 1 && isLiteral(segment) && followsACollection(segments, index, collections));
376
+ }
377
+ function routeFolder(method, path, collections) {
378
+ const segments = segmentsOf(pathTemplate(path));
379
+ const literals = segments.filter((segment) => segment !== ":id");
380
+ if (literals.length === 0) return;
381
+ if (method === "ALL" || literals.length > 1) return literals.join("/");
382
+ const [resource = ""] = literals;
383
+ return `${resource}/${actionOf(method, resource, segments.at(-1) === ":id", collections)}`;
384
+ }
385
+ function underBase(path, basePath) {
386
+ if (basePath === void 0) return path;
387
+ if (path === basePath) return "/";
388
+ return path.startsWith(`${basePath}/`) ? path.slice(basePath.length) : path;
389
+ }
390
+ /** Nothing for a subject with no standard name: the root route, a cli run with no command word, a page, a journey. */
391
+ function subjectFolder(subject, reading = NO_READING) {
392
+ if (subject.specification === "api") {
393
+ const mounted = {
394
+ ...subject,
395
+ path: underBase(subject.path, reading.basePath)
396
+ };
397
+ return ambiguousSegment(mounted, reading) === void 0 ? routeFolder(subject.method, mounted.path, reading.collections) : void 0;
398
+ }
399
+ if (subject.specification === "cli") return subject.command.length === 0 ? void 0 : subject.command.join("/");
400
+ return subject.specification === "job" ? subject.job : void 0;
401
+ }
402
+ /** `GET /events/:id`, `trigger digest`, ... */
403
+ function subjectName(subject) {
404
+ if (subject.specification === "api") return `${subject.method} ${pathTemplate(subject.path)}`;
405
+ if (subject.specification === "cli") return subject.command.length === 0 ? "exec (no command)" : `exec ${subject.command.join(" ")}`;
406
+ if (subject.specification === "job") return `trigger ${subject.job}`;
407
+ return subject.specification === "mobile" ? `open ${subject.journey}` : `visit ${subject.page}`;
408
+ }
409
+ const HTTP_VERBS = {
410
+ delete: "DELETE",
411
+ get: "GET",
412
+ patch: "PATCH",
413
+ post: "POST",
414
+ put: "PUT"
415
+ };
416
+ const REQUEST_LINE = /^\s*(?<method>DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT)\s+(?<path>\S+)/u;
417
+ const REQUEST_FILE = /^file\(\s*(?<quote>['"])(?<path>[^'"]*)\k<quote>\s*\)$/u;
418
+ function requestLineOf(text) {
419
+ const { method = "", path = "/" } = REQUEST_LINE.exec(text)?.groups ?? {};
420
+ const verb = HTTP_VERBS[method.toLowerCase()];
421
+ return verb === void 0 ? [] : [{
422
+ specification: "api",
423
+ method: verb,
424
+ path,
425
+ template: true
426
+ }];
427
+ }
428
+ const pageOf = (first) => {
429
+ const literal = literalOf(first);
430
+ return literal?.startsWith("/") === true ? [{
431
+ specification: "website",
432
+ page: pathTemplate(literal)
433
+ }] : [];
434
+ };
435
+ const TERMINALS = {
436
+ exec: (first, _readRequest, commands) => {
437
+ const line = first?.startsWith("[") === true ? literalOf(argumentsOf$1(first.slice(1, -1)).at(-1)) : literalOf(first);
438
+ return line === void 0 ? [] : [{
439
+ command: commandWords(line, commands),
440
+ specification: "cli"
441
+ }];
442
+ },
443
+ fetch: pageOf,
444
+ open: (first) => [{
445
+ specification: "mobile",
446
+ journey: literalOf(first) ?? "(launch)"
447
+ }],
448
+ request: (first, readRequest) => {
449
+ const path = REQUEST_FILE.exec(first ?? "")?.groups?.path;
450
+ return path === void 0 ? [] : requestLineOf(readRequest(path) ?? "");
451
+ },
452
+ trigger: (first) => {
453
+ const job = literalOf(first);
454
+ return job === void 0 ? [] : [{
455
+ specification: "job",
456
+ job
457
+ }];
458
+ },
459
+ visit: pageOf
460
+ };
461
+ function routeOf(method, args) {
462
+ const path = literalOf(args[0]);
463
+ if (path?.startsWith("/") !== true) return [];
464
+ const options = args[method === "POST" || method === "PUT" ? 2 : 1] ?? "";
465
+ return [{
466
+ specification: "api",
467
+ method,
468
+ path,
469
+ template: /\/:[A-Za-z_$]/u.test(path) || /(?<![\w$])params(?![\w$])/u.test(options)
470
+ }];
471
+ }
472
+ function declaredMethod(subject, methodless) {
473
+ if (subject.specification !== "api") return subject;
474
+ const template = pathTemplate(subject.path);
475
+ return methodless.some((path) => pathTemplate(path) === template) ? {
476
+ ...subject,
477
+ method: "ALL"
478
+ } : subject;
479
+ }
480
+ function subjectsOfChain(chain, readRequest = () => "", reading = NO_READING) {
481
+ const terminal = chain.calls.at(-1);
482
+ if (terminal === void 0) return [];
483
+ const args = argumentsOf$1(terminal.args);
484
+ const [first] = args;
485
+ const method = HTTP_VERBS[terminal.name];
486
+ return (method === void 0 ? TERMINALS[terminal.name]?.(first, readRequest, reading.commands?.[chain.handle]) ?? [] : routeOf(method, args)).map((subject) => declaredMethod(subject, reading.methodless));
487
+ }
488
+ function subjectsOf(source, readRequest, reading = NO_READING) {
489
+ const text = blankComments(source);
490
+ return chainsOf(text, handlesOf(text)).flatMap((chain) => subjectsOfChain(chain, readRequest, reading).map((subject) => ({
491
+ index: chain.index,
492
+ subject
493
+ })));
494
+ }
495
+ //#endregion
496
+ //#region lint/kit/project/import-sources.ts
497
+ function attributeText(node) {
498
+ return stringValue(node) ?? (node?.type === "Identifier" ? String(node.name) : void 0);
499
+ }
500
+ function isJsonImport(declaration) {
501
+ return childList(declaration, "attributes").some((attribute) => attributeText(child(attribute, "key")) === "type" && stringValue(child(attribute, "value")) === "json");
502
+ }
503
+ function importSourceVisitor(onSource) {
504
+ const fromSourceField = (node) => {
505
+ const source = child(node, "source");
506
+ const value = stringValue(source);
507
+ if (value !== void 0) onSource({
508
+ json: isJsonImport(node),
509
+ node: source ?? node,
510
+ source: value
511
+ });
512
+ };
513
+ return {
514
+ ExportAllDeclaration: fromSourceField,
515
+ ExportNamedDeclaration: fromSourceField,
516
+ ImportDeclaration: fromSourceField,
517
+ ImportExpression: fromSourceField
518
+ };
519
+ }
520
+ //#endregion
521
+ //#region lint/kit/project/path-references.ts
522
+ const FS_READ = /^(?:createReadStream|opendir(?:Sync)?|read\w*)$/u;
523
+ const RELATIVE = /^\.\.?\//u;
524
+ const PATH_BUILDERS = /* @__PURE__ */ new Set(["join", "resolve"]);
525
+ const COMMONJS_ANCHORS = ["__dirname", "__filename"];
526
+ function isMetaMember(node, name) {
527
+ return child(node, "object")?.type === "MetaProperty" && memberPropertyName(node) === name;
528
+ }
529
+ function isOwnDirectory(node) {
530
+ return identifierName(node) === "__dirname" || isMetaMember(node, "dirname");
531
+ }
532
+ function calleeName(node) {
533
+ const callee = child(node, "callee");
534
+ return identifierName(callee) ?? memberPropertyName(callee);
535
+ }
536
+ function argumentsOf(node) {
537
+ return childList(node, "arguments");
538
+ }
539
+ function literalArguments(node) {
540
+ return argumentsOf(node).map((target) => {
541
+ const literal = stringValue(target);
542
+ return literal === void 0 ? void 0 : {
543
+ literal,
544
+ target
545
+ };
546
+ });
547
+ }
548
+ function isAnchor(node, anchors) {
549
+ if (node === void 0) return false;
550
+ let found = false;
551
+ walk(node, (inner) => {
552
+ found ||= inner.type === "MetaProperty" || anchors.has(identifierName(inner));
553
+ });
554
+ return found;
555
+ }
556
+ function anchorsOf(program) {
557
+ const anchors = new Set(COMMONJS_ANCHORS);
558
+ for (const statement of childList(program, "body")) {
559
+ const declaration = statement.type === "ExportNamedDeclaration" ? child(statement, "declaration") : statement;
560
+ for (const declarator of childList(declaration, "declarations")) {
561
+ const name = identifierName(child(declarator, "id"));
562
+ if (name !== void 0 && isAnchor(child(declarator, "init"), anchors)) anchors.add(name);
563
+ }
564
+ }
565
+ return anchors;
566
+ }
567
+ function literalTail(pieces) {
568
+ const tail = [];
569
+ for (const piece of pieces) {
570
+ const value = stringValue(piece);
571
+ if (value === void 0) break;
572
+ tail.push(value);
573
+ }
574
+ return tail;
575
+ }
576
+ function builtPath(node, here, anchors) {
577
+ const [first, ...rest] = argumentsOf(node);
578
+ const tail = literalTail(rest);
579
+ if (!PATH_BUILDERS.has(calleeName(node)) || !isAnchor(first, anchors) || tail.length === 0) return;
580
+ return resolve(here, ...tail);
581
+ }
582
+ function templatePath(node, here, anchors) {
583
+ const [hole] = childList(node, "expressions");
584
+ const [head, tail] = childList(node, "quasis").map(cookedText);
585
+ if (!isAnchor(hole, anchors) || head !== "" || tail?.startsWith("/") !== true) return;
586
+ return resolve(here, `.${tail}`);
587
+ }
588
+ function urlLiteral(node) {
589
+ const [first] = literalArguments(node);
590
+ const [, base] = argumentsOf(node);
591
+ return identifierName(child(node, "callee")) === "URL" && isMetaMember(base, "url") ? first : void 0;
592
+ }
593
+ /** Its `Program` handler reads the module's anchors first, so it must see the program before any node inside it. */
594
+ function pathReferenceVisitor(file, onPath) {
595
+ const here = () => dirname(file());
596
+ let anchors = /* @__PURE__ */ new Set();
597
+ const report = (node, path) => {
598
+ if (path !== void 0) onPath({
599
+ node,
600
+ path
601
+ });
602
+ };
603
+ return {
604
+ CallExpression(node) {
605
+ const place = here();
606
+ report(node, builtPath(node, place, anchors));
607
+ const [first] = literalArguments(node);
608
+ if (first !== void 0 && FS_READ.test(calleeName(node) ?? "") && RELATIVE.test(first.literal)) onPath({
609
+ node: first.target,
610
+ path: resolve(projectRootOf(place) ?? place, first.literal)
611
+ });
612
+ },
613
+ NewExpression(node) {
614
+ const url = urlLiteral(node);
615
+ if (url !== void 0) onPath({
616
+ node: url.target,
617
+ path: resolve(here(), url.literal)
618
+ });
619
+ },
620
+ Program(node) {
621
+ anchors = anchorsOf(node);
622
+ },
623
+ TemplateLiteral(node) {
624
+ report(node, templatePath(node, here(), anchors));
625
+ }
626
+ };
627
+ }
628
+ /** A piece that is not a literal leaves the call out. */
629
+ function literalPathVisitor(onPath) {
630
+ return {
631
+ CallExpression(node) {
632
+ if (!PATH_BUILDERS.has(calleeName(node))) return;
633
+ const [anchor, ...rest] = argumentsOf(node);
634
+ const pieces = literalTail(rest);
635
+ const [head] = rest;
636
+ if (isOwnDirectory(anchor) && head !== void 0 && pieces.length === rest.length) onPath({
637
+ node: head,
638
+ pieces
639
+ });
640
+ },
641
+ NewExpression(node) {
642
+ const url = urlLiteral(node);
643
+ if (url !== void 0) onPath({
644
+ node: url.target,
645
+ pieces: [url.literal]
646
+ });
647
+ }
648
+ };
649
+ }
650
+ function mergeVisitors(...fragments) {
651
+ const merged = {};
652
+ for (const fragment of fragments) for (const [type, handler] of Object.entries(fragment)) {
653
+ const before = merged[type];
654
+ merged[type] = before === void 0 ? handler : (node) => {
655
+ before(node);
656
+ handler(node);
657
+ };
658
+ }
659
+ return merged;
660
+ }
661
+ function visitProgram(program, visitor) {
662
+ walk(program, (node) => {
663
+ visitor[node.type]?.(node);
664
+ });
665
+ }
666
+ //#endregion
667
+ //#region lint/kit/project/module-parser.ts
668
+ let parse;
669
+ function parsedModule(filename, source) {
670
+ if (parse === void 0) parse = createRequire(import.meta.url)("oxc-parser").parseSync;
671
+ return parse(filename, source);
672
+ }
673
+ function parseProgram(filename, source) {
674
+ const { errors, program } = parsedModule(filename, source);
675
+ return errors.length === 0 ? program : void 0;
676
+ }
677
+ //#endregion
678
+ //#region lint/kit/project/specifiers.ts
679
+ const EXTENSIONS = [
680
+ ".ts",
681
+ ".tsx",
682
+ ".mts",
683
+ ".cts",
684
+ ".js",
685
+ ".jsx",
686
+ ".mjs",
687
+ ".cjs",
688
+ ".json"
689
+ ];
690
+ const COMPILED = {
691
+ ".cjs": [".cts"],
692
+ ".js": [".ts", ".tsx"],
693
+ ".jsx": [".tsx"],
694
+ ".mjs": [".mts"]
695
+ };
696
+ function onDisk(path) {
697
+ if (isFile(path)) return path;
698
+ const extension = /\.[cm]?jsx?$/u.exec(path)?.[0] ?? "";
699
+ return [
700
+ ...(COMPILED[extension] ?? []).map((swap) => path.slice(0, -extension.length) + swap),
701
+ ...EXTENSIONS.map((added) => path + added),
702
+ ...EXTENSIONS.map((added) => join(path, `index${added}`))
703
+ ].find((candidate) => isFile(candidate)) ?? path;
704
+ }
705
+ function readJsonc(path) {
706
+ const text = readFileCached(path);
707
+ if (text === null) return;
708
+ return parsedJson(blankComments(text).replaceAll(/,(?=\s*[\]}])/gu, ""), null);
709
+ }
710
+ function recordOf(value) {
711
+ return isRecord(value) ? value : void 0;
712
+ }
713
+ function targetsOf(value) {
714
+ if (typeof value === "string") return [value];
715
+ if (Array.isArray(value)) return value.flatMap((entry) => targetsOf(entry));
716
+ return Object.values(recordOf(value) ?? {}).flatMap((entry) => targetsOf(entry));
717
+ }
718
+ function substitution(key, specifier) {
719
+ const star = key.indexOf("*");
720
+ if (star === -1) return key === specifier ? "" : void 0;
721
+ const head = key.slice(0, star);
722
+ const tail = key.slice(star + 1);
723
+ return specifier.startsWith(head) && specifier.endsWith(tail) && specifier.length >= head.length + tail.length ? specifier.slice(head.length, specifier.length - tail.length) : void 0;
724
+ }
725
+ function mapped(map, specifier, base) {
726
+ for (const [key, value] of Object.entries(map)) {
727
+ const star = substitution(key, specifier);
728
+ if (star !== void 0) return targetsOf(value).map((target) => resolve(base, target.replace("*", star)));
729
+ }
730
+ return [];
731
+ }
732
+ function tsconfigOf(directory) {
733
+ const root = projectRootOf(directory);
734
+ let current = directory;
735
+ for (;;) {
736
+ const candidate = join(current, "tsconfig.json");
737
+ if (isFile(candidate)) return candidate;
738
+ const parent = dirname(current);
739
+ if (current === root || parent === current) return;
740
+ current = parent;
741
+ }
742
+ }
743
+ function configCandidates(base) {
744
+ return [
745
+ base,
746
+ `${base}.json`,
747
+ join(base, "tsconfig.json")
748
+ ];
749
+ }
750
+ function extendedConfig(from, entry) {
751
+ const directory = dirname(from);
752
+ if (entry.startsWith(".") || entry.startsWith("/")) return configCandidates(resolve(directory, entry)).find((path) => isFile(path));
753
+ for (let current = directory;; current = dirname(current)) {
754
+ const found = configCandidates(join(current, "node_modules", entry)).find((path) => isFile(path));
755
+ if (found !== void 0 || dirname(current) === current) return found;
756
+ }
757
+ }
758
+ function resolutionOf(tsconfig, seen) {
759
+ if (seen.has(tsconfig)) return {};
760
+ seen.add(tsconfig);
761
+ const config = recordOf(readJsonc(tsconfig));
762
+ const bases = targetsOf(config?.extends);
763
+ const resolution = {};
764
+ for (const entry of bases) {
765
+ const base = extendedConfig(tsconfig, entry);
766
+ if (base !== void 0) Object.assign(resolution, resolutionOf(base, seen));
767
+ }
768
+ const options = recordOf(config?.compilerOptions);
769
+ const directory = dirname(tsconfig);
770
+ if (typeof options?.baseUrl === "string") resolution.baseUrl = resolve(directory, options.baseUrl);
771
+ const paths = recordOf(options?.paths);
772
+ if (paths !== void 0) resolution.paths = {
773
+ base: directory,
774
+ paths
775
+ };
776
+ return resolution;
777
+ }
778
+ function throughPaths(directory, specifier) {
779
+ const tsconfig = tsconfigOf(directory);
780
+ if (tsconfig === void 0) return [];
781
+ const { baseUrl, paths } = resolutionOf(tsconfig, /* @__PURE__ */ new Set());
782
+ if (paths === void 0) return [];
783
+ return mapped(paths.paths, specifier, baseUrl ?? paths.base);
784
+ }
785
+ function throughImports(directory, specifier) {
786
+ const root = packageRootOf(directory);
787
+ if (root === void 0) return [];
788
+ const imports = recordOf(recordOf(readJsonc(join(root, "package.json")))?.imports);
789
+ return imports === void 0 ? [] : mapped(imports, specifier, root);
790
+ }
791
+ function resolveSpecifier(from, specifier) {
792
+ const directory = dirname(from);
793
+ if (specifier === "." || specifier === ".." || /^\.\.?\//u.test(specifier)) return [onDisk(resolve(directory, specifier))];
794
+ if (specifier.startsWith("/") || specifier.startsWith("node:")) return [];
795
+ return (specifier.startsWith("#") ? throughImports(directory, specifier) : throughPaths(directory, specifier)).map((target) => onDisk(target));
796
+ }
797
+ //#endregion
798
+ //#region lint/kit/project/tree.ts
799
+ function isPool(dir) {
800
+ return isGroundFolder(basename(dir)) && basename(dirname(dir)) === "specs";
801
+ }
802
+ function specsRootOfPool(pool) {
803
+ return dirname(pool);
804
+ }
805
+ function isPruned(dir) {
806
+ return PRUNED.has(basename(dir)) || isGroundFolder(basename(dir));
807
+ }
808
+ function listFiles(dir, predicate) {
809
+ const out = [];
810
+ const visit = (current) => {
811
+ let entries;
812
+ try {
813
+ entries = treeEntries(current);
814
+ } catch {
815
+ return;
816
+ }
817
+ for (const entry of entries) {
818
+ const path = join(current, entry.name);
819
+ if (entry.isDirectory()) {
820
+ if (!isPruned(path)) visit(path);
821
+ } else if (predicate(path)) out.push(path);
822
+ }
823
+ };
824
+ visit(dir);
825
+ return out;
826
+ }
827
+ function listDirs(dir) {
828
+ const out = [];
829
+ const visit = (current) => {
830
+ out.push(current);
831
+ let entries;
832
+ try {
833
+ entries = treeEntries(current);
834
+ } catch {
835
+ return;
836
+ }
837
+ for (const entry of entries) {
838
+ const path = join(current, entry.name);
839
+ if (entry.isDirectory() && !isPruned(path)) visit(path);
840
+ }
841
+ };
842
+ visit(dir);
843
+ return out;
844
+ }
845
+ function findSpecsRoots(dir) {
846
+ const roots = [];
847
+ const visit = (current) => {
848
+ if (basename(current) === "specs") {
849
+ roots.push(current);
850
+ return;
851
+ }
852
+ let entries;
853
+ try {
854
+ entries = treeEntries(current);
855
+ } catch {
856
+ return;
857
+ }
858
+ for (const entry of entries) if (entry.isDirectory() && !PRUNED.has(entry.name)) visit(join(current, entry.name));
859
+ };
860
+ visit(dir);
861
+ return roots;
862
+ }
863
+ function findPools(dir) {
864
+ const pools = [];
865
+ const visit = (current) => {
866
+ let entries;
867
+ try {
868
+ entries = treeEntries(current);
869
+ } catch {
870
+ return;
871
+ }
872
+ for (const entry of entries) {
873
+ if (!entry.isDirectory() || PRUNED.has(entry.name)) continue;
874
+ const path = join(current, entry.name);
875
+ if (isPool(path)) pools.push(path);
876
+ else if (!isGroundFolder(entry.name)) visit(path);
877
+ }
878
+ };
879
+ visit(dir);
880
+ return pools;
881
+ }
882
+ function readText(path) {
883
+ try {
884
+ return readFileSync(path, "utf8");
885
+ } catch {
886
+ return "";
887
+ }
888
+ }
889
+ function readSource(path) {
890
+ return blankComments(readText(path));
891
+ }
892
+ /** A document counts: a rendered unit's ARIA golden sits in the same `__expected__/` as a spec's. */
893
+ function isReferrer(name) {
894
+ return isTestFileName(name) || name.endsWith(".spec.yaml");
895
+ }
896
+ function isDir(path) {
897
+ return treeKind(path) === "directory";
898
+ }
899
+ function columnAt(text, index) {
900
+ return index - text.lastIndexOf("\n", index - 1);
901
+ }
902
+ function lineAt(text, index) {
903
+ return text.slice(0, index).split("\n").length;
904
+ }
905
+ const DISK_CALL = /(?<![.\w])(?<kind>file|directory)\(\s*(?<quote>['"`])(?<path>(?:[^'"`\\]|\\.)*)\k<quote>/gu;
906
+ /** A path that is no folder holds no tests and lists no children: it is none. */
907
+ function isConventionalSubdir(path) {
908
+ if (listFiles(path, isTestFileName).length > 0) return false;
909
+ let children;
910
+ try {
911
+ children = treeEntries(path);
912
+ } catch {
913
+ return false;
914
+ }
915
+ return !children.some((child) => child.isDirectory() && isGroundFolder(child.name));
916
+ }
917
+ function listReaders(specsRoot) {
918
+ return listFiles(specsRoot, (path) => path.endsWith(".ts") || path.endsWith(".spec.yaml"));
919
+ }
920
+ function removeIfEmpty(folder) {
921
+ if (existsSync(folder) && readdirSync(folder).length === 0) rmSync(folder, { recursive: true });
922
+ }
923
+ function isWithin(directory, path) {
924
+ const rest = relative(directory, path);
925
+ return rest !== "" && !rest.startsWith("..") && !isAbsolute(rest);
926
+ }
927
+ function lowestCommonFolder(folders) {
928
+ const [first, ...others] = folders;
929
+ if (first === void 0) return;
930
+ let common = first;
931
+ while (!others.every((folder) => folder === common || isWithin(common, folder))) {
932
+ const parent = dirname(common);
933
+ if (parent === common) return;
934
+ common = parent;
935
+ }
936
+ return common;
937
+ }
938
+ function reservedFolderOf(owner, reserved) {
939
+ return join(owner, reserved);
940
+ }
941
+ function decodeText(path) {
942
+ let text;
943
+ try {
944
+ text = readFileSync(path, "utf8");
945
+ } catch {
946
+ return null;
947
+ }
948
+ return text.includes("\0") || text.includes("�") ? null : text;
949
+ }
950
+ //#endregion
951
+ //#region lint/kit/project/specs-modules.ts
952
+ const MODULE = /\.(?:[cm]?[jt]sx?)$/u;
953
+ const TEST_OR_SPEC = /\.(?:test|test-d|spec)\.[cm]?[jt]sx?$/u;
954
+ const RUNNER = /\.specification\.[cm]?[jt]sx?$/u;
955
+ function isModuleName(name) {
956
+ return MODULE.test(name) && !name.endsWith(".d.ts");
957
+ }
958
+ function isTestOrSpecName(name) {
959
+ return TEST_OR_SPEC.test(name);
960
+ }
961
+ function isRunnerName(name) {
962
+ return RUNNER.test(name);
963
+ }
964
+ function modulesUnder(specsRoot) {
965
+ return listFiles(specsRoot, (path) => isModuleName(basename(path)));
966
+ }
967
+ function setupFolderOf(specsRoot, file) {
968
+ for (let folder = dirname(file); isWithin(specsRoot, folder); folder = dirname(folder)) if (basename(folder) === SETUP_FOLDER) return folder;
969
+ }
970
+ function placeOf(specsRoot, reader) {
971
+ const setup = setupFolderOf(specsRoot, reader);
972
+ return setup === void 0 ? dirname(reader) : dirname(setup);
973
+ }
974
+ function importsOf(file) {
975
+ const program = parseProgram(file, readText(file));
976
+ if (!isNode(program)) return [];
977
+ const imported = [];
978
+ visitProgram(program, importSourceVisitor(({ source }) => {
979
+ imported.push({
980
+ relative: isRelativePath(source),
981
+ targets: resolveSpecifier(file, source)
982
+ });
983
+ }));
984
+ return imported;
985
+ }
986
+ function importedModulesOf(file) {
987
+ return importsOf(file).flatMap(({ targets }) => targets);
988
+ }
989
+ function importsThroughAlias(file, module) {
990
+ return importsOf(file).some(({ relative, targets }) => !relative && targets.includes(module));
991
+ }
992
+ function readersOf(modules) {
993
+ const known = new Set(modules);
994
+ const readers = /* @__PURE__ */ new Map();
995
+ for (const file of modules) for (const target of new Set(importedModulesOf(file))) if (target !== file && known.has(target)) readers.set(target, [...readers.get(target) ?? [], file]);
996
+ return readers;
997
+ }
998
+ function lowestPlaceOf(specsRoot, readers) {
999
+ return lowestCommonFolder(readers.map((reader) => placeOf(specsRoot, reader)));
1000
+ }
1001
+ function declaresTests(name) {
1002
+ return isTestOrSpecName(name) || name.endsWith(".spec.yaml");
1003
+ }
1004
+ function holdsASpec(folder) {
1005
+ let entries;
1006
+ try {
1007
+ entries = treeEntries(folder);
1008
+ } catch {
1009
+ return false;
1010
+ }
1011
+ return entries.some((entry) => entry.isDirectory() ? !PRUNED.has(entry.name) && !isSpecialFolder(entry.name) && holdsASpec(join(folder, entry.name)) : declaresTests(entry.name));
1012
+ }
1013
+ /** The special folder a bare earlier spelling names (a `setup/` at the specs root or holding no spec), or `undefined`; a `_` name is refused before it is asked. */
1014
+ function renamedSpecialOf(specsRoot, folder) {
1015
+ const renamed = RENAMED_FOLDERS[basename(folder)];
1016
+ if (renamed === void 0) return;
1017
+ return dirname(folder) === specsRoot || !holdsASpec(folder) ? renamed : void 0;
1018
+ }
1019
+ /** A bare `shared/` at the specs root or holding no spec: what specifications share sits at the specs root now; a `_` name is refused before it is asked. */
1020
+ function isRetiredShared(specsRoot, folder) {
1021
+ return RETIRED_SHARED_FOLDERS.includes(basename(folder)) && (dirname(folder) === specsRoot || !holdsASpec(folder));
1022
+ }
1023
+ function isRefusedReserved(specsRoot, folder) {
1024
+ const name = basename(folder);
1025
+ return name.startsWith("_") && !isSpecialFolder(name) || renamedSpecialOf(specsRoot, folder) !== void 0 || isRetiredShared(specsRoot, folder);
1026
+ }
1027
+ function refusedReservedAbove(specsRoot, file) {
1028
+ for (let folder = dirname(file); isWithin(specsRoot, folder); folder = dirname(folder)) if (isRefusedReserved(specsRoot, folder)) return folder;
1029
+ }
1030
+ //#endregion
1031
+ export { identifierName as $, removeIfEmpty as A, ambiguousSegment as B, lineAt as C, lowestCommonFolder as D, listReaders as E, literalPathVisitor as F, SUBJECT_SPECIFICATIONS as G, subjectFolder as H, mergeVisitors as I, chainRootName as J, doorsOf as K, pathReferenceVisitor as L, specsRootOfPool as M, resolveSpecifier as N, readSource as O, parseProgram as P, findProperty as Q, visitProgram as R, isWithin as S, listFiles as T, subjectName as U, pathTemplate as V, subjectsOf as W, childList as X, child as Y, cookedText as Z, findPools as _, roleOf as _t, isRunnerName as a, nodeStart as at, isDir as b, modulesUnder as c, stringField as ct, refusedReservedAbove as d, isNodeTestFileName as dt, isNode as et, renamedSpecialOf as f, isTestFileName as ft, decodeText as g, projectSegments as gt, columnAt as h, projectRootOf as ht, isRetiredShared as i, nodeEnd as it, reservedFolderOf as j, readText as k, placeOf as l, stringValue as lt, DISK_CALL as m, packageRootOf as mt, importsThroughAlias as n, memberPath as nt, isTestOrSpecName as o, propertyKeyName as ot, setupFolderOf as p, isTestRole as pt, declaredCommands as q, isModuleName as r, memberPropertyName as rt, lowestPlaceOf as s, specificationMember as st, declaresTests as t, isSampledValue as tt, readersOf as u, walk as ut, findSpecsRoots as v, specsAnchor as vt, listDirs as w, isReferrer as x, isConventionalSubdir as y, SETTINGS_FILE as yt, importSourceVisitor as z };
1032
+
1033
+ //# sourceMappingURL=shared-specs-modules.js.map