@venizia/dev-configs 0.4.6 → 0.4.7-1

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 (199) hide show
  1. package/LICENSE.md +1 -3
  2. package/README.md +1 -1
  3. package/dist/checks/catalog.d.ts +4 -2
  4. package/dist/checks/catalog.d.ts.map +1 -1
  5. package/dist/checks/catalog.js +72 -68
  6. package/dist/checks/catalog.js.map +1 -1
  7. package/dist/checks/module-cycles.d.ts +11 -9
  8. package/dist/checks/module-cycles.d.ts.map +1 -1
  9. package/dist/checks/module-cycles.js +32 -30
  10. package/dist/checks/module-cycles.js.map +1 -1
  11. package/dist/checks/purity/manifest.d.ts +44 -38
  12. package/dist/checks/purity/manifest.d.ts.map +1 -1
  13. package/dist/checks/purity/manifest.js +121 -115
  14. package/dist/checks/purity/manifest.js.map +1 -1
  15. package/dist/checks/purity/probe.d.ts +22 -18
  16. package/dist/checks/purity/probe.d.ts.map +1 -1
  17. package/dist/checks/purity/probe.js +101 -99
  18. package/dist/checks/purity/probe.js.map +1 -1
  19. package/dist/checks/purity/run.d.ts +18 -16
  20. package/dist/checks/purity/run.d.ts.map +1 -1
  21. package/dist/checks/purity/run.js +90 -86
  22. package/dist/checks/purity/run.js.map +1 -1
  23. package/dist/checks/test-controls.d.ts +17 -15
  24. package/dist/checks/test-controls.d.ts.map +1 -1
  25. package/dist/checks/test-controls.js +71 -69
  26. package/dist/checks/test-controls.js.map +1 -1
  27. package/dist/checks/test-dead-code.d.ts +5 -3
  28. package/dist/checks/test-dead-code.d.ts.map +1 -1
  29. package/dist/checks/test-dead-code.js +23 -21
  30. package/dist/checks/test-dead-code.js.map +1 -1
  31. package/dist/checks/test-mutate.d.ts +12 -7
  32. package/dist/checks/test-mutate.d.ts.map +1 -1
  33. package/dist/checks/test-mutate.js +59 -56
  34. package/dist/checks/test-mutate.js.map +1 -1
  35. package/dist/checks/test-weight.d.ts +11 -8
  36. package/dist/checks/test-weight.d.ts.map +1 -1
  37. package/dist/checks/test-weight.js +98 -94
  38. package/dist/checks/test-weight.js.map +1 -1
  39. package/dist/cli/check.d.ts.map +1 -1
  40. package/dist/cli/check.js +11 -8
  41. package/dist/cli/check.js.map +1 -1
  42. package/dist/cli/codemod/async-style.d.ts +62 -0
  43. package/dist/cli/codemod/async-style.d.ts.map +1 -0
  44. package/dist/cli/codemod/async-style.js +248 -0
  45. package/dist/cli/codemod/async-style.js.map +1 -0
  46. package/dist/cli/codemod/const-classes.d.ts.map +1 -1
  47. package/dist/cli/codemod/const-classes.js +2 -1
  48. package/dist/cli/codemod/const-classes.js.map +1 -1
  49. package/dist/cli/codemod/drop-redundant-casts.d.ts.map +1 -1
  50. package/dist/cli/codemod/drop-redundant-casts.js +2 -1
  51. package/dist/cli/codemod/drop-redundant-casts.js.map +1 -1
  52. package/dist/cli/codemod/files.d.ts +7 -0
  53. package/dist/cli/codemod/files.d.ts.map +1 -1
  54. package/dist/cli/codemod/files.js +33 -0
  55. package/dist/cli/codemod/files.js.map +1 -1
  56. package/dist/cli/codemod/index.d.ts.map +1 -1
  57. package/dist/cli/codemod/index.js +12 -3
  58. package/dist/cli/codemod/index.js.map +1 -1
  59. package/dist/cli/codemod/model-settings.d.ts +7 -5
  60. package/dist/cli/codemod/model-settings.d.ts.map +1 -1
  61. package/dist/cli/codemod/model-settings.js +293 -291
  62. package/dist/cli/codemod/model-settings.js.map +1 -1
  63. package/dist/cli/codemod/namespace-functions/index.d.ts +20 -0
  64. package/dist/cli/codemod/namespace-functions/index.d.ts.map +1 -0
  65. package/dist/cli/codemod/namespace-functions/index.js +182 -0
  66. package/dist/cli/codemod/namespace-functions/index.js.map +1 -0
  67. package/dist/cli/codemod/namespace-functions/logger-scope.d.ts +32 -0
  68. package/dist/cli/codemod/namespace-functions/logger-scope.d.ts.map +1 -0
  69. package/dist/cli/codemod/namespace-functions/logger-scope.js +128 -0
  70. package/dist/cli/codemod/namespace-functions/logger-scope.js.map +1 -0
  71. package/dist/cli/codemod/namespace-functions/mapping.d.ts +66 -0
  72. package/dist/cli/codemod/namespace-functions/mapping.d.ts.map +1 -0
  73. package/dist/cli/codemod/namespace-functions/mapping.js +99 -0
  74. package/dist/cli/codemod/namespace-functions/mapping.js.map +1 -0
  75. package/dist/cli/codemod/namespace-functions/mappings/boot.d.ts +7 -0
  76. package/dist/cli/codemod/namespace-functions/mappings/boot.d.ts.map +1 -0
  77. package/dist/cli/codemod/namespace-functions/mappings/boot.js +28 -0
  78. package/dist/cli/codemod/namespace-functions/mappings/boot.js.map +1 -0
  79. package/dist/cli/codemod/namespace-functions/mappings/connectors.d.ts +4 -0
  80. package/dist/cli/codemod/namespace-functions/mappings/connectors.d.ts.map +1 -0
  81. package/dist/cli/codemod/namespace-functions/mappings/connectors.js +218 -0
  82. package/dist/cli/codemod/namespace-functions/mappings/connectors.js.map +1 -0
  83. package/dist/cli/codemod/namespace-functions/mappings/core-server.d.ts +7 -0
  84. package/dist/cli/codemod/namespace-functions/mappings/core-server.d.ts.map +1 -0
  85. package/dist/cli/codemod/namespace-functions/mappings/core-server.js +162 -0
  86. package/dist/cli/codemod/namespace-functions/mappings/core-server.js.map +1 -0
  87. package/dist/cli/codemod/namespace-functions/mappings/core-worker.d.ts +7 -0
  88. package/dist/cli/codemod/namespace-functions/mappings/core-worker.d.ts.map +1 -0
  89. package/dist/cli/codemod/namespace-functions/mappings/core-worker.js +18 -0
  90. package/dist/cli/codemod/namespace-functions/mappings/core-worker.js.map +1 -0
  91. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.d.ts +7 -0
  92. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.d.ts.map +1 -0
  93. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.js +192 -0
  94. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.js.map +1 -0
  95. package/dist/cli/codemod/namespace-functions/mappings/filter.d.ts +7 -0
  96. package/dist/cli/codemod/namespace-functions/mappings/filter.d.ts.map +1 -0
  97. package/dist/cli/codemod/namespace-functions/mappings/filter.js +30 -0
  98. package/dist/cli/codemod/namespace-functions/mappings/filter.js.map +1 -0
  99. package/dist/cli/codemod/namespace-functions/mappings/helpers.d.ts +16 -0
  100. package/dist/cli/codemod/namespace-functions/mappings/helpers.d.ts.map +1 -0
  101. package/dist/cli/codemod/namespace-functions/mappings/helpers.js +535 -0
  102. package/dist/cli/codemod/namespace-functions/mappings/helpers.js.map +1 -0
  103. package/dist/cli/codemod/namespace-functions/mappings/index.d.ts +4 -0
  104. package/dist/cli/codemod/namespace-functions/mappings/index.d.ts.map +1 -0
  105. package/dist/cli/codemod/namespace-functions/mappings/index.js +25 -0
  106. package/dist/cli/codemod/namespace-functions/mappings/index.js.map +1 -0
  107. package/dist/cli/codemod/namespace-functions/mappings/inversion.d.ts +10 -0
  108. package/dist/cli/codemod/namespace-functions/mappings/inversion.d.ts.map +1 -0
  109. package/dist/cli/codemod/namespace-functions/mappings/inversion.js +81 -0
  110. package/dist/cli/codemod/namespace-functions/mappings/inversion.js.map +1 -0
  111. package/dist/cli/codemod/namespace-functions/mappings/kernel.d.ts +17 -0
  112. package/dist/cli/codemod/namespace-functions/mappings/kernel.d.ts.map +1 -0
  113. package/dist/cli/codemod/namespace-functions/mappings/kernel.js +229 -0
  114. package/dist/cli/codemod/namespace-functions/mappings/kernel.js.map +1 -0
  115. package/dist/cli/codemod/namespace-functions/rewriter.d.ts +61 -0
  116. package/dist/cli/codemod/namespace-functions/rewriter.d.ts.map +1 -0
  117. package/dist/cli/codemod/namespace-functions/rewriter.js +564 -0
  118. package/dist/cli/codemod/namespace-functions/rewriter.js.map +1 -0
  119. package/dist/cli/index.js +2 -0
  120. package/dist/cli/index.js.map +1 -1
  121. package/dist/compilers/bun.d.ts +1 -1
  122. package/dist/compilers/bun.js +1 -1
  123. package/dist/compilers/bun.js.map +1 -1
  124. package/dist/linters/house/await-support.d.ts +74 -0
  125. package/dist/linters/house/await-support.d.ts.map +1 -0
  126. package/dist/linters/house/await-support.js +238 -0
  127. package/dist/linters/house/await-support.js.map +1 -0
  128. package/dist/linters/house/common.d.ts +15 -9
  129. package/dist/linters/house/common.d.ts.map +1 -1
  130. package/dist/linters/house/common.js +20 -15
  131. package/dist/linters/house/common.js.map +1 -1
  132. package/dist/linters/house/configs.d.ts +26 -0
  133. package/dist/linters/house/configs.d.ts.map +1 -1
  134. package/dist/linters/house/configs.js +55 -1
  135. package/dist/linters/house/configs.js.map +1 -1
  136. package/dist/linters/house/const-class-values.rule.d.ts.map +1 -1
  137. package/dist/linters/house/const-class-values.rule.js +27 -19
  138. package/dist/linters/house/const-class-values.rule.js.map +1 -1
  139. package/dist/linters/house/effect-cleanup-consistency.rule.d.ts +17 -0
  140. package/dist/linters/house/effect-cleanup-consistency.rule.d.ts.map +1 -0
  141. package/dist/linters/house/effect-cleanup-consistency.rule.js +87 -0
  142. package/dist/linters/house/effect-cleanup-consistency.rule.js.map +1 -0
  143. package/dist/linters/house/hook-support.d.ts +29 -0
  144. package/dist/linters/house/hook-support.d.ts.map +1 -0
  145. package/dist/linters/house/hook-support.js +72 -0
  146. package/dist/linters/house/hook-support.js.map +1 -0
  147. package/dist/linters/house/index.d.ts +9 -0
  148. package/dist/linters/house/index.d.ts.map +1 -1
  149. package/dist/linters/house/index.js +9 -0
  150. package/dist/linters/house/index.js.map +1 -1
  151. package/dist/linters/house/max-params-own.rule.d.ts.map +1 -1
  152. package/dist/linters/house/max-params-own.rule.js +33 -24
  153. package/dist/linters/house/max-params-own.rule.js.map +1 -1
  154. package/dist/linters/house/no-async-hook-callback.rule.d.ts +18 -0
  155. package/dist/linters/house/no-async-hook-callback.rule.d.ts.map +1 -0
  156. package/dist/linters/house/no-async-hook-callback.rule.js +50 -0
  157. package/dist/linters/house/no-async-hook-callback.rule.js.map +1 -0
  158. package/dist/linters/house/no-await-promise-chain.rule.d.ts +24 -0
  159. package/dist/linters/house/no-await-promise-chain.rule.d.ts.map +1 -0
  160. package/dist/linters/house/no-await-promise-chain.rule.js +298 -0
  161. package/dist/linters/house/no-await-promise-chain.rule.js.map +1 -0
  162. package/dist/linters/house/no-inline-await.rule.d.ts +20 -0
  163. package/dist/linters/house/no-inline-await.rule.d.ts.map +1 -0
  164. package/dist/linters/house/no-inline-await.rule.js +70 -0
  165. package/dist/linters/house/no-inline-await.rule.js.map +1 -0
  166. package/dist/linters/house/no-new-error.rule.js +4 -4
  167. package/dist/linters/house/no-new-error.rule.js.map +1 -1
  168. package/dist/linters/house/no-return-await.rule.d.ts +15 -0
  169. package/dist/linters/house/no-return-await.rule.d.ts.map +1 -0
  170. package/dist/linters/house/no-return-await.rule.js +92 -0
  171. package/dist/linters/house/no-return-await.rule.js.map +1 -0
  172. package/dist/linters/house/no-silent-catch.rule.d.ts +22 -0
  173. package/dist/linters/house/no-silent-catch.rule.d.ts.map +1 -0
  174. package/dist/linters/house/no-silent-catch.rule.js +88 -0
  175. package/dist/linters/house/no-silent-catch.rule.js.map +1 -0
  176. package/dist/linters/house/plugin.d.ts.map +1 -1
  177. package/dist/linters/house/plugin.js +12 -0
  178. package/dist/linters/house/plugin.js.map +1 -1
  179. package/dist/linters/house/promise-chain-support.d.ts +49 -0
  180. package/dist/linters/house/promise-chain-support.d.ts.map +1 -0
  181. package/dist/linters/house/promise-chain-support.js +188 -0
  182. package/dist/linters/house/promise-chain-support.js.map +1 -0
  183. package/dist/linters/rules/no-restricted-properties.rule.js +1 -1
  184. package/dist/linters/rules/no-restricted-properties.rule.js.map +1 -1
  185. package/dist/testing/rejection.helper.d.ts +25 -20
  186. package/dist/testing/rejection.helper.d.ts.map +1 -1
  187. package/dist/testing/rejection.helper.js +33 -31
  188. package/dist/testing/rejection.helper.js.map +1 -1
  189. package/dist/testing/repo-tmp.d.ts +19 -9
  190. package/dist/testing/repo-tmp.d.ts.map +1 -1
  191. package/dist/testing/repo-tmp.js +50 -48
  192. package/dist/testing/repo-tmp.js.map +1 -1
  193. package/dist/testing/spawn.helper.d.ts +12 -10
  194. package/dist/testing/spawn.helper.d.ts.map +1 -1
  195. package/dist/testing/spawn.helper.js +22 -16
  196. package/dist/testing/spawn.helper.js.map +1 -1
  197. package/dist/testing/tmpdir-preload.js +1 -1
  198. package/dist/testing/tmpdir-preload.js.map +1 -1
  199. package/package.json +2 -3
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { buildManifest } from './manifest';\nimport type { IPurityClaim, IPurityEntry, IPurityOptions } from './manifest';\nimport { probeEntry } from './probe';\nimport type { IPurityResult } from './probe';\n\n/** The outcome of one row, as the text report and the JSON verdict both name it. */\nexport class PurityVerdicts {\n static readonly PASS = 'pass';\n static readonly FAIL = 'fail';\n static readonly WAIVED = 'waived';\n static readonly MISSING = 'missing';\n static readonly STALE_WAIVER = 'stale-waiver';\n\n static readonly SCHEME_SET = new Set<string>([\n this.PASS,\n this.FAIL,\n this.WAIVED,\n this.MISSING,\n this.STALE_WAIVER,\n ]);\n\n static isValid(value: string): value is TPurityVerdict {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport type TPurityVerdict = Extract<(typeof PurityVerdicts)[keyof typeof PurityVerdicts], string>;\n\n/** One row of the `--json` verdict: nothing machine- or time-specific, so two runs on one commit diff empty. */\nexport interface IPurityRowVerdict {\n label: string;\n entry: string;\n verdict: TPurityVerdict;\n sizeBytes?: number;\n builtins?: string[];\n globals?: string[];\n guardedGlobals?: string[];\n external?: string[];\n buildError?: string;\n}\n\nexport interface IPurityReport {\n rows: IPurityRowVerdict[];\n failed: number;\n waived: number;\n checked: number;\n}\n\nconst formatList = (values: string[]): string => values.join(', ') || 'none';\n\n/**\n * Probes every row of the claims and returns the report. `print` receives the human report line by\n * line, in the exact shape the repository scripts printed before the engine moved here.\n */\nexport const checkPurity = async (opts: {\n root: string;\n claims: IPurityClaim[];\n options?: IPurityOptions;\n /** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */\n filter?: string;\n /** The claims file, repository-relative, named in the hint printed for a stale waiver. */\n claimsFile: string;\n print: (line: string) => void;\n /** Where the no-match message goes (stderr for the command): it is an error, not a report line. */\n printError: (line: string) => void;\n}): Promise<IPurityReport | undefined> => {\n const { root, claims, options, filter, claimsFile, print, printError } = opts;\n const manifest = buildManifest({ claims, root, options });\n const rows: IPurityEntry[] = filter ? manifest.filter((row) => row.package === filter) : manifest;\n\n // Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn\n // a release-blocking gate into a silent no-op that still reports success.\n if (rows.length === 0) {\n const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');\n printError(\n `[purity] No manifest entry matches \"${filter}\" | Known packages: ${known} - a filter that ` +\n `matches nothing is a broken gate, not an empty one.`,\n );\n return undefined;\n }\n\n const labelWidth = Math.max(...rows.map((row) => row.label.length));\n const verdicts: IPurityRowVerdict[] = [];\n let failed = 0;\n let waived = 0;\n\n for (const row of rows) {\n const entry = join(root, row.entry);\n\n // The `exports` map is the source of these rows, so a missing file is either an unbuilt package\n // or a manifest pointing at something the build never emits - both fatal, and neither is worth\n // reporting as a bundler error twenty lines long.\n if (!existsSync(entry)) {\n failed += 1;\n print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);\n print(\n ` The package's \\`exports\\` map points here and nothing is built - run \\`make ${row.package}\\``,\n );\n verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });\n continue;\n }\n\n const result: IPurityResult = await probeEntry({\n entry,\n cwd: root,\n external: row.external,\n });\n\n const detail = {\n label: row.label,\n entry: row.entry,\n sizeBytes: result.sizeBytes,\n builtins: result.builtins,\n globals: result.globals,\n guardedGlobals: result.guardedGlobals,\n external: result.external,\n // The bundler's message names absolute paths; the verdict must read the same on any machine.\n ...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),\n };\n\n // Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to\n // see that the entry is clean by construction and not merely by the shape of one expression.\n const guarded =\n result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';\n\n // Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency\n // measures less than one that does not, and that must stay visible next to the verdict.\n const external = result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';\n\n // Checked before the normal verdict, because for a waived row the two outcomes swap meaning.\n if (row.expectImpure) {\n // A waived entry that comes back pure fails: the waiver now describes something untrue, and\n // the reader who trusts it next will be trusting it about a different entry.\n if (result.ok) {\n failed += 1;\n print(` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`);\n print(` Drop it from \\`impure\\` in ${claimsFile} so the gate measures it again`);\n verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });\n continue;\n }\n\n waived += 1;\n print(\n ` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });\n continue;\n }\n\n if (result.ok) {\n print(\n ` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });\n continue;\n }\n\n failed += 1;\n print(\n ` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`,\n );\n if (result.buildError) {\n print(` ${result.buildError.split('\\n')[0]}`);\n }\n verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });\n }\n\n const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';\n\n if (failed > 0) {\n print(`\\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);\n } else {\n const checked = rows.length - waived;\n print(`\\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);\n }\n\n return { rows: verdicts, failed, waived, checked: rows.length - waived };\n};\n"]}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { PurityManifest } from './manifest';\nimport type { IPurityClaim, IPurityEntry, IPurityOptions } from './manifest';\nimport { PurityProbe } from './probe';\nimport type { IPurityResult } from './probe';\n\n/** The outcome of one row, as the text report and the JSON verdict both name it. */\nexport class PurityVerdicts {\n static readonly PASS = 'pass';\n static readonly FAIL = 'fail';\n static readonly WAIVED = 'waived';\n static readonly MISSING = 'missing';\n static readonly STALE_WAIVER = 'stale-waiver';\n\n static readonly SCHEME_SET = new Set<string>([\n this.PASS,\n this.FAIL,\n this.WAIVED,\n this.MISSING,\n this.STALE_WAIVER,\n ]);\n\n static isValid(value: string): value is TPurityVerdict {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport type TPurityVerdict = Extract<(typeof PurityVerdicts)[keyof typeof PurityVerdicts], string>;\n\n/** One row of the `--json` verdict: nothing machine- or time-specific, so two runs on one commit diff empty. */\nexport interface IPurityRowVerdict {\n label: string;\n entry: string;\n verdict: TPurityVerdict;\n sizeBytes?: number;\n builtins?: string[];\n globals?: string[];\n guardedGlobals?: string[];\n external?: string[];\n buildError?: string;\n}\n\nexport interface IPurityReport {\n rows: IPurityRowVerdict[];\n failed: number;\n waived: number;\n checked: number;\n}\n\nconst formatList = (values: string[]): string => values.join(', ') || 'none';\n\nexport class PurityCheck {\n /**\n * Probes every row of the claims and returns the report. `print` receives the human report line by\n * line, in the exact shape the repository scripts printed before the engine moved here.\n */\n static async run(opts: {\n root: string;\n claims: IPurityClaim[];\n options?: IPurityOptions;\n /** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */\n filter?: string;\n /** The claims file, repository-relative, named in the hint printed for a stale waiver. */\n claimsFile: string;\n print: (line: string) => void;\n /** Where the no-match message goes (stderr for the command): it is an error, not a report line. */\n printError: (line: string) => void;\n }): Promise<IPurityReport | undefined> {\n const { root, claims, options, filter, claimsFile, print, printError } = opts;\n const manifest = PurityManifest.build({ claims, root, options });\n const rows: IPurityEntry[] = filter\n ? manifest.filter((row) => row.package === filter)\n : manifest;\n\n // Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn\n // a release-blocking gate into a silent no-op that still reports success.\n if (rows.length === 0) {\n const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');\n printError(\n `[purity] No manifest entry matches \"${filter}\" | Known packages: ${known} - a filter that ` +\n `matches nothing is a broken gate, not an empty one.`,\n );\n return undefined;\n }\n\n const labelWidth = Math.max(...rows.map((row) => row.label.length));\n const verdicts: IPurityRowVerdict[] = [];\n let failed = 0;\n let waived = 0;\n\n for (const row of rows) {\n const entry = join(root, row.entry);\n\n // The `exports` map is the source of these rows, so a missing file is either an unbuilt package\n // or a manifest pointing at something the build never emits - both fatal, and neither is worth\n // reporting as a bundler error twenty lines long.\n if (!existsSync(entry)) {\n failed += 1;\n print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);\n print(\n ` The package's \\`exports\\` map points here and nothing is built - run \\`make ${row.package}\\``,\n );\n verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });\n continue;\n }\n\n const result: IPurityResult = await PurityProbe.probeEntry({\n entry,\n cwd: root,\n external: row.external,\n });\n\n const detail = {\n label: row.label,\n entry: row.entry,\n sizeBytes: result.sizeBytes,\n builtins: result.builtins,\n globals: result.globals,\n guardedGlobals: result.guardedGlobals,\n external: result.external,\n // The bundler's message names absolute paths; the verdict must read the same on any machine.\n ...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),\n };\n\n // Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to\n // see that the entry is clean by construction and not merely by the shape of one expression.\n const guarded =\n result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';\n\n // Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency\n // measures less than one that does not, and that must stay visible next to the verdict.\n const external =\n result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';\n\n // Checked before the normal verdict, because for a waived row the two outcomes swap meaning.\n if (row.expectImpure) {\n // A waived entry that comes back pure fails: the waiver now describes something untrue, and\n // the reader who trusts it next will be trusting it about a different entry.\n if (result.ok) {\n failed += 1;\n print(\n ` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`,\n );\n print(` Drop it from \\`impure\\` in ${claimsFile} so the gate measures it again`);\n verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });\n continue;\n }\n\n waived += 1;\n print(\n ` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });\n continue;\n }\n\n if (result.ok) {\n print(\n ` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`,\n );\n verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });\n continue;\n }\n\n failed += 1;\n print(\n ` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`,\n );\n if (result.buildError) {\n print(` ${result.buildError.split('\\n')[0]}`);\n }\n verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });\n }\n\n const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';\n\n if (failed > 0) {\n print(`\\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);\n } else {\n const checked = rows.length - waived;\n print(`\\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);\n }\n\n return { rows: verdicts, failed, waived, checked: rows.length - waived };\n }\n}\n"]}
@@ -28,19 +28,21 @@ export interface IControlsReport {
28
28
  checked: number;
29
29
  problems: IControlProblem[];
30
30
  }
31
- /**
32
- * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the
33
- * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.
34
- * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular
35
- * expressions, so the expansion does not matter.
36
- */
37
- export declare const listTestTitles: (opts: {
38
- source: string;
39
- fileName?: string;
40
- }) => Promise<string[]>;
41
- /** Checks every entry of the registry against the test files it names. */
42
- export declare const checkControls: (opts: {
43
- root: string;
44
- controls: IControlEntry[];
45
- }) => Promise<IControlsReport>;
31
+ export declare class TestControlsCheck {
32
+ /**
33
+ * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the
34
+ * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.
35
+ * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular
36
+ * expressions, so the expansion does not matter.
37
+ */
38
+ static listTitles(opts: {
39
+ source: string;
40
+ fileName?: string;
41
+ }): Promise<string[]>;
42
+ /** Checks every entry of the registry against the test files it names. */
43
+ static run(opts: {
44
+ root: string;
45
+ controls: IControlEntry[];
46
+ }): Promise<IControlsReport>;
47
+ }
46
48
  //# sourceMappingURL=test-controls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-controls.d.ts","sourceRoot":"","sources":["../../src/checks/test-controls.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,KAAG,OAAO,CAAC,MAAM,EAAE,CAgDnB,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,GAAU,MAAM;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,KAAG,OAAO,CAAC,eAAe,CAkC1B,CAAC"}
1
+ {"version":3,"file":"test-controls.d.ts","sourceRoot":"","sources":["../../src/checks/test-controls.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,qBAAa,iBAAiB;IAC5B;;;;;OAKG;WACU,UAAU,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmDvF,0EAA0E;WAC7D,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC;CAmC9F"}
@@ -1,78 +1,80 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkControls = exports.listTestTitles = void 0;
3
+ exports.TestControlsCheck = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const node_path_1 = require("node:path");
6
- /**
7
- * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the
8
- * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.
9
- * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular
10
- * expressions, so the expansion does not matter.
11
- */
12
- const listTestTitles = async (opts) => {
13
- const typescript = (await import('typescript')).default;
14
- const file = typescript.createSourceFile(opts.fileName ?? 'file.test.ts', opts.source, typescript.ScriptTarget.Latest, true);
15
- const titles = [];
16
- const RUNNERS = new Set(['test', 'it', 'describe']);
17
- const rootName = (expression) => {
18
- if (typescript.isIdentifier(expression)) {
19
- return expression.text;
20
- }
21
- if (typescript.isPropertyAccessExpression(expression)) {
22
- return rootName(expression.expression);
23
- }
24
- if (typescript.isCallExpression(expression)) {
25
- return rootName(expression.expression);
26
- }
27
- return undefined;
28
- };
29
- const visit = (node) => {
30
- if (typescript.isCallExpression(node)) {
31
- const first = node.arguments[0];
32
- const root = rootName(node.expression);
33
- if (root !== undefined &&
34
- RUNNERS.has(root) &&
35
- first &&
36
- (typescript.isStringLiteral(first) || typescript.isNoSubstitutionTemplateLiteral(first))) {
37
- titles.push(first.text);
6
+ class TestControlsCheck {
7
+ /**
8
+ * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the
9
+ * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.
10
+ * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular
11
+ * expressions, so the expansion does not matter.
12
+ */
13
+ static async listTitles(opts) {
14
+ const result = await import('typescript');
15
+ const typescript = result.default;
16
+ const file = typescript.createSourceFile(opts.fileName ?? 'file.test.ts', opts.source, typescript.ScriptTarget.Latest, true);
17
+ const titles = [];
18
+ const RUNNERS = new Set(['test', 'it', 'describe']);
19
+ const rootName = (expression) => {
20
+ if (typescript.isIdentifier(expression)) {
21
+ return expression.text;
38
22
  }
39
- }
40
- typescript.forEachChild(node, visit);
41
- };
42
- visit(file);
43
- return titles;
44
- };
45
- exports.listTestTitles = listTestTitles;
46
- /** Checks every entry of the registry against the test files it names. */
47
- const checkControls = async (opts) => {
48
- const problems = [];
49
- for (const entry of opts.controls) {
50
- const path = (0, node_path_1.join)(opts.root, entry.file);
51
- if (!(0, node_fs_1.existsSync)(path)) {
52
- problems.push({
53
- entry,
54
- message: `the ${entry.kind === 'file' ? 'negative fixture' : 'test file'} ${entry.file} does not exist`,
55
- });
56
- continue;
57
- }
58
- if (entry.kind === 'file') {
59
- continue;
60
- }
61
- const titles = await (0, exports.listTestTitles)({
62
- source: (0, node_fs_1.readFileSync)(path, 'utf8'),
63
- fileName: entry.file,
64
- });
65
- const pattern = new RegExp(entry.titlePattern ?? '');
66
- const matched = titles.filter((title) => pattern.test(title)).length;
67
- const min = entry.min ?? 1;
68
- if (matched < min) {
69
- problems.push({
70
- entry,
71
- message: `${matched} title(s) match /${entry.titlePattern ?? ''}/ in ${entry.file}, ${min} required | the control of gate '${entry.gate}' is gone or renamed`,
23
+ if (typescript.isPropertyAccessExpression(expression)) {
24
+ return rootName(expression.expression);
25
+ }
26
+ if (typescript.isCallExpression(expression)) {
27
+ return rootName(expression.expression);
28
+ }
29
+ return undefined;
30
+ };
31
+ const visit = (node) => {
32
+ if (typescript.isCallExpression(node)) {
33
+ const first = node.arguments[0];
34
+ const root = rootName(node.expression);
35
+ if (root !== undefined &&
36
+ RUNNERS.has(root) &&
37
+ first &&
38
+ (typescript.isStringLiteral(first) || typescript.isNoSubstitutionTemplateLiteral(first))) {
39
+ titles.push(first.text);
40
+ }
41
+ }
42
+ typescript.forEachChild(node, visit);
43
+ };
44
+ visit(file);
45
+ return titles;
46
+ }
47
+ /** Checks every entry of the registry against the test files it names. */
48
+ static async run(opts) {
49
+ const problems = [];
50
+ for (const entry of opts.controls) {
51
+ const path = (0, node_path_1.join)(opts.root, entry.file);
52
+ if (!(0, node_fs_1.existsSync)(path)) {
53
+ problems.push({
54
+ entry,
55
+ message: `the ${entry.kind === 'file' ? 'negative fixture' : 'test file'} ${entry.file} does not exist`,
56
+ });
57
+ continue;
58
+ }
59
+ if (entry.kind === 'file') {
60
+ continue;
61
+ }
62
+ const titles = await TestControlsCheck.listTitles({
63
+ source: (0, node_fs_1.readFileSync)(path, 'utf8'),
64
+ fileName: entry.file,
72
65
  });
66
+ const pattern = new RegExp(entry.titlePattern ?? '');
67
+ const matched = titles.filter((title) => pattern.test(title)).length;
68
+ const min = entry.min ?? 1;
69
+ if (matched < min) {
70
+ problems.push({
71
+ entry,
72
+ message: `${matched} title(s) match /${entry.titlePattern ?? ''}/ in ${entry.file}, ${min} required | the control of gate '${entry.gate}' is gone or renamed`,
73
+ });
74
+ }
73
75
  }
76
+ return { checked: opts.controls.length, problems };
74
77
  }
75
- return { checked: opts.controls.length, problems };
76
- };
77
- exports.checkControls = checkControls;
78
+ }
79
+ exports.TestControlsCheck = TestControlsCheck;
78
80
  //# sourceMappingURL=test-controls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-controls.js","sourceRoot":"","sources":["../../src/checks/test-controls.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type ts from 'typescript';\n\n/**\n * A positive control is a test that proves a gate can fail: a fixture that must be refused, a broken\n * subject the check must catch. Deleting one makes the gate pass for the wrong reason, so the trim-tests\n * method keeps a committed registry of them and fails when one disappears.\n */\nexport interface IControlEntry {\n /** The gate the control belongs to; reported with a failure. */\n gate: string;\n /** Repository-relative path of the test file. */\n file: string;\n /**\n * `test` (default): `file` is a test file and `titlePattern` is matched against its titles.\n * `file`: `file` is a NEGATIVE FIXTURE, a file a test needs to be absent from the result (a class the\n * scanner must ignore). It only has to exist: without it the test's \"not found\" assertion passes for the\n * wrong reason.\n */\n kind?: 'test' | 'file';\n /** A regular expression matched against the TITLES (suite and case) in that file. Not used by `file` entries. */\n titlePattern?: string;\n /** At least this many titles must match. Default 1. */\n min?: number;\n}\n\nexport interface IControlProblem {\n entry: IControlEntry;\n message: string;\n}\n\nexport interface IControlsReport {\n checked: number;\n problems: IControlProblem[];\n}\n\n/**\n * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the\n * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.\n * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular\n * expressions, so the expansion does not matter.\n */\nexport const listTestTitles = async (opts: {\n source: string;\n fileName?: string;\n}): Promise<string[]> => {\n const typescript: typeof ts = (await import('typescript')).default;\n const file = typescript.createSourceFile(\n opts.fileName ?? 'file.test.ts',\n opts.source,\n typescript.ScriptTarget.Latest,\n true,\n );\n const titles: string[] = [];\n const RUNNERS = new Set(['test', 'it', 'describe']);\n\n const rootName = (expression: ts.Expression): string | undefined => {\n if (typescript.isIdentifier(expression)) {\n return expression.text;\n }\n\n if (typescript.isPropertyAccessExpression(expression)) {\n return rootName(expression.expression);\n }\n\n if (typescript.isCallExpression(expression)) {\n return rootName(expression.expression);\n }\n\n return undefined;\n };\n\n const visit = (node: ts.Node): void => {\n if (typescript.isCallExpression(node)) {\n const first = node.arguments[0];\n const root = rootName(node.expression);\n\n if (\n root !== undefined &&\n RUNNERS.has(root) &&\n first &&\n (typescript.isStringLiteral(first) || typescript.isNoSubstitutionTemplateLiteral(first))\n ) {\n titles.push(first.text);\n }\n }\n\n typescript.forEachChild(node, visit);\n };\n\n visit(file);\n\n return titles;\n};\n\n/** Checks every entry of the registry against the test files it names. */\nexport const checkControls = async (opts: {\n root: string;\n controls: IControlEntry[];\n}): Promise<IControlsReport> => {\n const problems: IControlProblem[] = [];\n\n for (const entry of opts.controls) {\n const path = join(opts.root, entry.file);\n if (!existsSync(path)) {\n problems.push({\n entry,\n message: `the ${entry.kind === 'file' ? 'negative fixture' : 'test file'} ${entry.file} does not exist`,\n });\n continue;\n }\n\n if (entry.kind === 'file') {\n continue;\n }\n\n const titles = await listTestTitles({\n source: readFileSync(path, 'utf8'),\n fileName: entry.file,\n });\n const pattern = new RegExp(entry.titlePattern ?? '');\n const matched = titles.filter((title) => pattern.test(title)).length;\n const min = entry.min ?? 1;\n\n if (matched < min) {\n problems.push({\n entry,\n message: `${matched} title(s) match /${entry.titlePattern ?? ''}/ in ${entry.file}, ${min} required | the control of gate '${entry.gate}' is gone or renamed`,\n });\n }\n }\n\n return { checked: opts.controls.length, problems };\n};\n"]}
1
+ {"version":3,"file":"test-controls.js","sourceRoot":"","sources":["../../src/checks/test-controls.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type ts from 'typescript';\n\n/**\n * A positive control is a test that proves a gate can fail: a fixture that must be refused, a broken\n * subject the check must catch. Deleting one makes the gate pass for the wrong reason, so the trim-tests\n * method keeps a committed registry of them and fails when one disappears.\n */\nexport interface IControlEntry {\n /** The gate the control belongs to; reported with a failure. */\n gate: string;\n /** Repository-relative path of the test file. */\n file: string;\n /**\n * `test` (default): `file` is a test file and `titlePattern` is matched against its titles.\n * `file`: `file` is a NEGATIVE FIXTURE, a file a test needs to be absent from the result (a class the\n * scanner must ignore). It only has to exist: without it the test's \"not found\" assertion passes for the\n * wrong reason.\n */\n kind?: 'test' | 'file';\n /** A regular expression matched against the TITLES (suite and case) in that file. Not used by `file` entries. */\n titlePattern?: string;\n /** At least this many titles must match. Default 1. */\n min?: number;\n}\n\nexport interface IControlProblem {\n entry: IControlEntry;\n message: string;\n}\n\nexport interface IControlsReport {\n checked: number;\n problems: IControlProblem[];\n}\n\nexport class TestControlsCheck {\n /**\n * The titles in a test file: the first string argument of `test`, `it`, `describe` and of the\n * second call of `test.each(rows)('title', ...)`, with their `.only`/`.skip`/`.if(...)` forms.\n * A `test.each` title is returned as written (`%s` and all): the registry patterns are regular\n * expressions, so the expansion does not matter.\n */\n static async listTitles(opts: { source: string; fileName?: string }): Promise<string[]> {\n const result = await import('typescript');\n const typescript: typeof ts = result.default;\n const file = typescript.createSourceFile(\n opts.fileName ?? 'file.test.ts',\n opts.source,\n typescript.ScriptTarget.Latest,\n true,\n );\n const titles: string[] = [];\n const RUNNERS = new Set(['test', 'it', 'describe']);\n\n const rootName = (expression: ts.Expression): string | undefined => {\n if (typescript.isIdentifier(expression)) {\n return expression.text;\n }\n\n if (typescript.isPropertyAccessExpression(expression)) {\n return rootName(expression.expression);\n }\n\n if (typescript.isCallExpression(expression)) {\n return rootName(expression.expression);\n }\n\n return undefined;\n };\n\n const visit = (node: ts.Node): void => {\n if (typescript.isCallExpression(node)) {\n const first = node.arguments[0];\n const root = rootName(node.expression);\n\n if (\n root !== undefined &&\n RUNNERS.has(root) &&\n first &&\n (typescript.isStringLiteral(first) || typescript.isNoSubstitutionTemplateLiteral(first))\n ) {\n titles.push(first.text);\n }\n }\n\n typescript.forEachChild(node, visit);\n };\n\n visit(file);\n\n return titles;\n }\n\n /** Checks every entry of the registry against the test files it names. */\n static async run(opts: { root: string; controls: IControlEntry[] }): Promise<IControlsReport> {\n const problems: IControlProblem[] = [];\n\n for (const entry of opts.controls) {\n const path = join(opts.root, entry.file);\n if (!existsSync(path)) {\n problems.push({\n entry,\n message: `the ${entry.kind === 'file' ? 'negative fixture' : 'test file'} ${entry.file} does not exist`,\n });\n continue;\n }\n\n if (entry.kind === 'file') {\n continue;\n }\n\n const titles = await TestControlsCheck.listTitles({\n source: readFileSync(path, 'utf8'),\n fileName: entry.file,\n });\n const pattern = new RegExp(entry.titlePattern ?? '');\n const matched = titles.filter((title) => pattern.test(title)).length;\n const min = entry.min ?? 1;\n\n if (matched < min) {\n problems.push({\n entry,\n message: `${matched} title(s) match /${entry.titlePattern ?? ''}/ in ${entry.file}, ${min} required | the control of gate '${entry.gate}' is gone or renamed`,\n });\n }\n }\n\n return { checked: opts.controls.length, problems };\n }\n}\n"]}
@@ -12,7 +12,9 @@ export interface IDeadTestCodeCandidate {
12
12
  */
13
13
  kind: 'unreferenced' | 'folder-referenced';
14
14
  }
15
- export declare const findDeadTestCode: (opts: {
16
- root: string;
17
- }) => IDeadTestCodeCandidate[];
15
+ export declare class DeadTestCodeCheck {
16
+ static find(opts: {
17
+ root: string;
18
+ }): IDeadTestCodeCandidate[];
19
+ }
18
20
  //# sourceMappingURL=test-dead-code.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-dead-code.d.ts","sourceRoot":"","sources":["../../src/checks/test-dead-code.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,cAAc,GAAG,mBAAmB,CAAC;CAC5C;AA6BD,eAAO,MAAM,gBAAgB,GAAI,MAAM;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KAAG,sBAAsB,EA+B/E,CAAC"}
1
+ {"version":3,"file":"test-dead-code.d.ts","sourceRoot":"","sources":["../../src/checks/test-dead-code.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,cAAc,GAAG,mBAAmB,CAAC;CAC5C;AA6BD,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,sBAAsB,EAAE;CAgC9D"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findDeadTestCode = void 0;
3
+ exports.DeadTestCodeCheck = void 0;
4
4
  const node_child_process_1 = require("node:child_process");
5
5
  const node_path_1 = require("node:path");
6
6
  const SUPPORT_EXTENSION = /\.(?:[cm]?[jt]sx?|json|sql|txt)$/;
@@ -25,26 +25,28 @@ const nameReferencedElsewhere = (opts) => {
25
25
  const result = git({ root: opts.root, args: ['grep', '-l', '-I', '-E', '-e', pattern] });
26
26
  return result.stdout.split('\n').some((line) => line !== '' && line !== opts.self);
27
27
  };
28
- const findDeadTestCode = (opts) => {
29
- const tracked = git({ root: opts.root, args: ['ls-files'] }).stdout.split('\n');
30
- const support = tracked.filter((file) => file.includes('/__tests__/') &&
31
- SUPPORT_EXTENSION.test(file) &&
32
- !TEST_FILE.test(file) &&
33
- !file.endsWith('.d.ts'));
34
- const candidates = [];
35
- for (const file of support) {
36
- const underTests = file.slice(file.indexOf('/__tests__/') + '/__tests__/'.length);
37
- const stem = underTests.split('/').pop().replace(SUPPORT_EXTENSION, '');
38
- const withoutExtension = underTests.replace(SUPPORT_EXTENSION, '');
39
- if (nameReferencedElsewhere({ root: opts.root, stem, self: file }) ||
40
- referencedElsewhere({ root: opts.root, needle: withoutExtension, self: file })) {
41
- continue;
28
+ class DeadTestCodeCheck {
29
+ static find(opts) {
30
+ const tracked = git({ root: opts.root, args: ['ls-files'] }).stdout.split('\n');
31
+ const support = tracked.filter((file) => file.includes('/__tests__/') &&
32
+ SUPPORT_EXTENSION.test(file) &&
33
+ !TEST_FILE.test(file) &&
34
+ !file.endsWith('.d.ts'));
35
+ const candidates = [];
36
+ for (const file of support) {
37
+ const underTests = file.slice(file.indexOf('/__tests__/') + '/__tests__/'.length);
38
+ const stem = underTests.split('/').pop().replace(SUPPORT_EXTENSION, '');
39
+ const withoutExtension = underTests.replace(SUPPORT_EXTENSION, '');
40
+ if (nameReferencedElsewhere({ root: opts.root, stem, self: file }) ||
41
+ referencedElsewhere({ root: opts.root, needle: withoutExtension, self: file })) {
42
+ continue;
43
+ }
44
+ const folder = (0, node_path_1.dirname)(underTests);
45
+ const folderReferenced = folder !== '.' && referencedElsewhere({ root: opts.root, needle: folder, self: file });
46
+ candidates.push({ file, kind: folderReferenced ? 'folder-referenced' : 'unreferenced' });
42
47
  }
43
- const folder = (0, node_path_1.dirname)(underTests);
44
- const folderReferenced = folder !== '.' && referencedElsewhere({ root: opts.root, needle: folder, self: file });
45
- candidates.push({ file, kind: folderReferenced ? 'folder-referenced' : 'unreferenced' });
48
+ return candidates;
46
49
  }
47
- return candidates;
48
- };
49
- exports.findDeadTestCode = findDeadTestCode;
50
+ }
51
+ exports.DeadTestCodeCheck = DeadTestCodeCheck;
50
52
  //# sourceMappingURL=test-dead-code.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-dead-code.js","sourceRoot":"","sources":["../../src/checks/test-dead-code.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { dirname } from 'node:path';\n\n/**\n * Support files of the tests (fixtures, helpers, probes) that no other file refers to. The second\n * step of the trim-tests method. The output is CANDIDATES: a fixture can be loaded by a path built at\n * run time or by scanning a folder, so a candidate is deleted only after the package suite is green\n * without it.\n */\nexport interface IDeadTestCodeCandidate {\n file: string;\n /**\n * `unreferenced`: neither its name nor its path under `__tests__` appears in another file, nor does\n * its folder. `folder-referenced`: only its folder path does, so a scan of that folder may load it.\n */\n kind: 'unreferenced' | 'folder-referenced';\n}\n\nconst SUPPORT_EXTENSION = /\\.(?:[cm]?[jt]sx?|json|sql|txt)$/;\nconst TEST_FILE = /\\.(?:test|spec)\\.[cm]?[jt]sx?$/;\n\nconst git = (opts: { root: string; args: string[] }): { status: number; stdout: string } => {\n const result = spawnSync('git', opts.args, {\n cwd: opts.root,\n encoding: 'utf8',\n maxBuffer: 256 * 1024 * 1024,\n });\n return { status: result.status ?? 1, stdout: result.stdout ?? '' };\n};\n\n/** Files other than `self` whose text contains `needle` (a fixed string). */\nconst referencedElsewhere = (opts: { root: string; needle: string; self: string }): boolean => {\n const result = git({ root: opts.root, args: ['grep', '-l', '-I', '-F', '-e', opts.needle] });\n return result.stdout.split('\\n').some((line) => line !== '' && line !== opts.self);\n};\n\nconst escapeForRegex = (text: string): string => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Files other than `self` that mention the name of the file as a path segment or an import. */\nconst nameReferencedElsewhere = (opts: { root: string; stem: string; self: string }): boolean => {\n const pattern = `[/'\"\\`]${escapeForRegex(opts.stem)}(\\\\.|'|\"|\\`|$)`;\n const result = git({ root: opts.root, args: ['grep', '-l', '-I', '-E', '-e', pattern] });\n return result.stdout.split('\\n').some((line) => line !== '' && line !== opts.self);\n};\n\nexport const findDeadTestCode = (opts: { root: string }): IDeadTestCodeCandidate[] => {\n const tracked = git({ root: opts.root, args: ['ls-files'] }).stdout.split('\\n');\n const support = tracked.filter(\n (file) =>\n file.includes('/__tests__/') &&\n SUPPORT_EXTENSION.test(file) &&\n !TEST_FILE.test(file) &&\n !file.endsWith('.d.ts'),\n );\n const candidates: IDeadTestCodeCandidate[] = [];\n\n for (const file of support) {\n const underTests = file.slice(file.indexOf('/__tests__/') + '/__tests__/'.length);\n const stem = underTests.split('/').pop()!.replace(SUPPORT_EXTENSION, '');\n const withoutExtension = underTests.replace(SUPPORT_EXTENSION, '');\n\n if (\n nameReferencedElsewhere({ root: opts.root, stem, self: file }) ||\n referencedElsewhere({ root: opts.root, needle: withoutExtension, self: file })\n ) {\n continue;\n }\n\n const folder = dirname(underTests);\n const folderReferenced =\n folder !== '.' && referencedElsewhere({ root: opts.root, needle: folder, self: file });\n\n candidates.push({ file, kind: folderReferenced ? 'folder-referenced' : 'unreferenced' });\n }\n\n return candidates;\n};\n"]}
1
+ {"version":3,"file":"test-dead-code.js","sourceRoot":"","sources":["../../src/checks/test-dead-code.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { dirname } from 'node:path';\n\n/**\n * Support files of the tests (fixtures, helpers, probes) that no other file refers to. The second\n * step of the trim-tests method. The output is CANDIDATES: a fixture can be loaded by a path built at\n * run time or by scanning a folder, so a candidate is deleted only after the package suite is green\n * without it.\n */\nexport interface IDeadTestCodeCandidate {\n file: string;\n /**\n * `unreferenced`: neither its name nor its path under `__tests__` appears in another file, nor does\n * its folder. `folder-referenced`: only its folder path does, so a scan of that folder may load it.\n */\n kind: 'unreferenced' | 'folder-referenced';\n}\n\nconst SUPPORT_EXTENSION = /\\.(?:[cm]?[jt]sx?|json|sql|txt)$/;\nconst TEST_FILE = /\\.(?:test|spec)\\.[cm]?[jt]sx?$/;\n\nconst git = (opts: { root: string; args: string[] }): { status: number; stdout: string } => {\n const result = spawnSync('git', opts.args, {\n cwd: opts.root,\n encoding: 'utf8',\n maxBuffer: 256 * 1024 * 1024,\n });\n return { status: result.status ?? 1, stdout: result.stdout ?? '' };\n};\n\n/** Files other than `self` whose text contains `needle` (a fixed string). */\nconst referencedElsewhere = (opts: { root: string; needle: string; self: string }): boolean => {\n const result = git({ root: opts.root, args: ['grep', '-l', '-I', '-F', '-e', opts.needle] });\n return result.stdout.split('\\n').some((line) => line !== '' && line !== opts.self);\n};\n\nconst escapeForRegex = (text: string): string => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Files other than `self` that mention the name of the file as a path segment or an import. */\nconst nameReferencedElsewhere = (opts: { root: string; stem: string; self: string }): boolean => {\n const pattern = `[/'\"\\`]${escapeForRegex(opts.stem)}(\\\\.|'|\"|\\`|$)`;\n const result = git({ root: opts.root, args: ['grep', '-l', '-I', '-E', '-e', pattern] });\n return result.stdout.split('\\n').some((line) => line !== '' && line !== opts.self);\n};\n\nexport class DeadTestCodeCheck {\n static find(opts: { root: string }): IDeadTestCodeCandidate[] {\n const tracked = git({ root: opts.root, args: ['ls-files'] }).stdout.split('\\n');\n const support = tracked.filter(\n (file) =>\n file.includes('/__tests__/') &&\n SUPPORT_EXTENSION.test(file) &&\n !TEST_FILE.test(file) &&\n !file.endsWith('.d.ts'),\n );\n const candidates: IDeadTestCodeCandidate[] = [];\n\n for (const file of support) {\n const underTests = file.slice(file.indexOf('/__tests__/') + '/__tests__/'.length);\n const stem = underTests.split('/').pop()!.replace(SUPPORT_EXTENSION, '');\n const withoutExtension = underTests.replace(SUPPORT_EXTENSION, '');\n\n if (\n nameReferencedElsewhere({ root: opts.root, stem, self: file }) ||\n referencedElsewhere({ root: opts.root, needle: withoutExtension, self: file })\n ) {\n continue;\n }\n\n const folder = dirname(underTests);\n const folderReferenced =\n folder !== '.' && referencedElsewhere({ root: opts.root, needle: folder, self: file });\n\n candidates.push({ file, kind: folderReferenced ? 'folder-referenced' : 'unreferenced' });\n }\n\n return candidates;\n }\n}\n"]}
@@ -48,11 +48,16 @@ export interface ILedgerResult {
48
48
  ok: boolean;
49
49
  problem?: string;
50
50
  }
51
- /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */
52
- export declare const replayLedger: (opts: {
53
- root: string;
54
- ledger: ILedger;
55
- scratch?: string;
56
- }) => ILedgerResult[];
57
- export declare const formatLedgerResults: (results: ILedgerResult[]) => string[];
51
+ export declare class TestMutateCheck {
52
+ /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */
53
+ static replayLedger(opts: {
54
+ root: string;
55
+ ledger: ILedger;
56
+ scratch?: string;
57
+ }): ILedgerResult[];
58
+ /** The report lines of a replay, one per ledger line and a closing tally. */
59
+ static formatLedgerResults(opts: {
60
+ results: ILedgerResult[];
61
+ }): string[];
62
+ }
58
63
  //# sourceMappingURL=test-mutate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-mutate.d.ts","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAC5B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iHAAiH;IACjH,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kGAAkG;IAClG,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgJD,6GAA6G;AAC7G,eAAO,MAAM,YAAY,GAAI,MAAM;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KAAG,aAAa,EAuDhB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,SAAS,aAAa,EAAE,KAAG,MAAM,EAWpE,CAAC"}
1
+ {"version":3,"file":"test-mutate.d.ts","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAC5B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iHAAiH;IACjH,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kGAAkG;IAClG,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgJD,qBAAa,eAAe;IAC1B,6GAA6G;IAC7G,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,EAAE;IAyD/F,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,aAAa,EAAE,CAAA;KAAE,GAAG,MAAM,EAAE;CAazE"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatLedgerResults = exports.replayLedger = void 0;
3
+ exports.TestMutateCheck = void 0;
4
4
  const node_child_process_1 = require("node:child_process");
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const node_fs_1 = require("node:fs");
@@ -98,68 +98,71 @@ const withMutants = (opts) => {
98
98
  });
99
99
  return apply(0);
100
100
  };
101
- /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */
102
- const replayLedger = (opts) => {
103
- const root = (0, node_path_1.resolve)(opts.root);
104
- const scratch = opts.scratch ?? (0, node_path_1.join)(root, '.agents/local/tmp');
105
- const results = [];
106
- for (const line of opts.ledger.mutants) {
107
- const { survivorRed, candidateRed } = withMutants({
108
- root,
109
- mutant: line.mutant,
110
- scratch,
111
- id: line.id,
112
- action: () => ({
113
- survivorRed: runIsRed({ root, file: line.survivor, name: line.survivorName }),
114
- candidateRed: runIsRed({ root, file: line.candidate, name: line.candidateName }),
115
- }),
116
- });
117
- const result = { id: line.id, survivorRed, candidateRed, ok: true };
118
- const expectation = line.expectCandidate ?? 'red';
119
- if (!survivorRed) {
120
- result.ok = false;
121
- result.problem =
122
- 'the survivor stayed green under the mutant: it does not cover the behaviour';
123
- }
124
- else if (expectation === 'red' && !candidateRed) {
125
- result.ok = false;
126
- result.problem =
127
- 'the candidate stayed green under the mutant: record it as vacuous with a second mutant, or pick a mutant it checks';
128
- }
129
- else if (expectation === 'vacuous') {
130
- if (!line.secondMutant) {
101
+ class TestMutateCheck {
102
+ /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */
103
+ static replayLedger(opts) {
104
+ const root = (0, node_path_1.resolve)(opts.root);
105
+ const scratch = opts.scratch ?? (0, node_path_1.join)(root, '.agents/local/tmp');
106
+ const results = [];
107
+ for (const line of opts.ledger.mutants) {
108
+ const { survivorRed, candidateRed } = withMutants({
109
+ root,
110
+ mutant: line.mutant,
111
+ scratch,
112
+ id: line.id,
113
+ action: () => ({
114
+ survivorRed: runIsRed({ root, file: line.survivor, name: line.survivorName }),
115
+ candidateRed: runIsRed({ root, file: line.candidate, name: line.candidateName }),
116
+ }),
117
+ });
118
+ const result = { id: line.id, survivorRed, candidateRed, ok: true };
119
+ const expectation = line.expectCandidate ?? 'red';
120
+ if (!survivorRed) {
131
121
  result.ok = false;
132
- result.problem = 'a vacuous line needs a secondMutant';
122
+ result.problem =
123
+ 'the survivor stayed green under the mutant: it does not cover the behaviour';
133
124
  }
134
- else if (candidateRed) {
125
+ else if (expectation === 'red' && !candidateRed) {
135
126
  result.ok = false;
136
- result.problem = 'the candidate went red under the mutant: it is not vacuous';
127
+ result.problem =
128
+ 'the candidate stayed green under the mutant: record it as vacuous with a second mutant, or pick a mutant it checks';
137
129
  }
138
- else {
139
- const greenUnderSecond = !withMutants({
140
- root,
141
- mutant: line.secondMutant,
142
- scratch,
143
- id: `${line.id}-second`,
144
- action: () => runIsRed({ root, file: line.candidate, name: line.candidateName }),
145
- });
146
- result.candidateGreenUnderSecond = greenUnderSecond;
147
- if (!greenUnderSecond) {
130
+ else if (expectation === 'vacuous') {
131
+ if (!line.secondMutant) {
132
+ result.ok = false;
133
+ result.problem = 'a vacuous line needs a secondMutant';
134
+ }
135
+ else if (candidateRed) {
148
136
  result.ok = false;
149
- result.problem = 'the candidate went red under the second mutant: it is not vacuous';
137
+ result.problem = 'the candidate went red under the mutant: it is not vacuous';
138
+ }
139
+ else {
140
+ const greenUnderSecond = !withMutants({
141
+ root,
142
+ mutant: line.secondMutant,
143
+ scratch,
144
+ id: `${line.id}-second`,
145
+ action: () => runIsRed({ root, file: line.candidate, name: line.candidateName }),
146
+ });
147
+ result.candidateGreenUnderSecond = greenUnderSecond;
148
+ if (!greenUnderSecond) {
149
+ result.ok = false;
150
+ result.problem = 'the candidate went red under the second mutant: it is not vacuous';
151
+ }
150
152
  }
151
153
  }
154
+ results.push(result);
152
155
  }
153
- results.push(result);
156
+ return results;
154
157
  }
155
- return results;
156
- };
157
- exports.replayLedger = replayLedger;
158
- const formatLedgerResults = (results) => {
159
- const lines = results.map((result) => `${result.ok ? 'ok ' : 'FAIL'} ${result.id}: survivor ${result.survivorRed ? 'red' : 'GREEN'}, candidate ${result.candidateRed ? 'red' : 'green'}${result.problem ? ` | ${result.problem}` : ''}`);
160
- const failed = results.filter((result) => !result.ok).length;
161
- lines.push(`\n[test-mutate] ${results.length - failed}/${results.length} ledger lines hold${failed ? `, ${failed} do not` : ''}.`);
162
- return lines;
163
- };
164
- exports.formatLedgerResults = formatLedgerResults;
158
+ /** The report lines of a replay, one per ledger line and a closing tally. */
159
+ static formatLedgerResults(opts) {
160
+ const { results } = opts;
161
+ const lines = results.map((result) => `${result.ok ? 'ok ' : 'FAIL'} ${result.id}: survivor ${result.survivorRed ? 'red' : 'GREEN'}, candidate ${result.candidateRed ? 'red' : 'green'}${result.problem ? ` | ${result.problem}` : ''}`);
162
+ const failed = results.filter((result) => !result.ok).length;
163
+ lines.push(`\n[test-mutate] ${results.length - failed}/${results.length} ledger lines hold${failed ? `, ${failed} do not` : ''}.`);
164
+ return lines;
165
+ }
166
+ }
167
+ exports.TestMutateCheck = TestMutateCheck;
165
168
  //# sourceMappingURL=test-mutate.js.map