@hominis/fireforge 0.45.0 → 0.46.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 (359) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor.d.ts +2 -0
  18. package/dist/src/commands/doctor.js +7 -8
  19. package/dist/src/commands/download.js +35 -24
  20. package/dist/src/commands/export-all.js +21 -4
  21. package/dist/src/commands/export-flow.js +15 -45
  22. package/dist/src/commands/export-placement-conflicts.js +3 -3
  23. package/dist/src/commands/export-placement-gate.js +2 -8
  24. package/dist/src/commands/export-placement-policy.js +3 -5
  25. package/dist/src/commands/export-shared.d.ts +42 -23
  26. package/dist/src/commands/export-shared.js +4 -19
  27. package/dist/src/commands/export.js +10 -1
  28. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  29. package/dist/src/commands/furnace/authoring-config.js +19 -0
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  31. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  32. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  33. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  34. package/dist/src/commands/furnace/create-templates.js +2 -1
  35. package/dist/src/commands/furnace/create.js +25 -19
  36. package/dist/src/commands/furnace/deploy.js +11 -2
  37. package/dist/src/commands/furnace/init.js +2 -1
  38. package/dist/src/commands/furnace/override.js +28 -19
  39. package/dist/src/commands/furnace/refresh.js +9 -1
  40. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  41. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  42. package/dist/src/commands/furnace/remove.js +2 -6
  43. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  44. package/dist/src/commands/furnace/rename.js +29 -29
  45. package/dist/src/commands/furnace/status.js +12 -9
  46. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  47. package/dist/src/commands/furnace/test-file-name.js +39 -0
  48. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  49. package/dist/src/commands/furnace/validation-output.js +3 -8
  50. package/dist/src/commands/import.js +2 -9
  51. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  52. package/dist/src/commands/lint-per-patch.js +19 -4
  53. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  54. package/dist/src/commands/lint.d.ts +2 -1
  55. package/dist/src/commands/lint.js +1 -1
  56. package/dist/src/commands/manifest.js +1 -1
  57. package/dist/src/commands/package.js +2 -4
  58. package/dist/src/commands/patch/compact.js +14 -15
  59. package/dist/src/commands/patch/delete.js +15 -25
  60. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  61. package/dist/src/commands/patch/entry-projection.js +24 -0
  62. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  63. package/dist/src/commands/patch/lint-ignore.js +19 -29
  64. package/dist/src/commands/patch/move-files-create.js +24 -10
  65. package/dist/src/commands/patch/move-files-into.js +38 -34
  66. package/dist/src/commands/patch/move-files.js +4 -13
  67. package/dist/src/commands/patch/rename.js +27 -41
  68. package/dist/src/commands/patch/reorder.js +25 -38
  69. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  70. package/dist/src/commands/patch/split-plan.js +4 -8
  71. package/dist/src/commands/patch/split.js +45 -43
  72. package/dist/src/commands/patch/staged-dependency.js +16 -22
  73. package/dist/src/commands/patch/tier.js +15 -25
  74. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  75. package/dist/src/commands/patch-rename-map.js +36 -0
  76. package/dist/src/commands/pending-resolution.d.ts +15 -0
  77. package/dist/src/commands/pending-resolution.js +25 -0
  78. package/dist/src/commands/re-export-adjacent.d.ts +24 -2
  79. package/dist/src/commands/re-export-adjacent.js +32 -16
  80. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  81. package/dist/src/commands/re-export-drift.js +4 -4
  82. package/dist/src/commands/re-export-files.d.ts +2 -3
  83. package/dist/src/commands/re-export-files.js +38 -29
  84. package/dist/src/commands/re-export-lint.d.ts +3 -4
  85. package/dist/src/commands/re-export-lint.js +9 -2
  86. package/dist/src/commands/re-export-scan.js +5 -2
  87. package/dist/src/commands/re-export-single.d.ts +22 -3
  88. package/dist/src/commands/re-export-single.js +19 -11
  89. package/dist/src/commands/re-export.js +28 -11
  90. package/dist/src/commands/rebase/abort.js +3 -8
  91. package/dist/src/commands/rebase/continue.js +12 -12
  92. package/dist/src/commands/rebase/index.js +2 -2
  93. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  94. package/dist/src/commands/rebase/patch-loop.js +2 -7
  95. package/dist/src/commands/resolve.js +18 -10
  96. package/dist/src/commands/run.d.ts +0 -13
  97. package/dist/src/commands/run.js +12 -25
  98. package/dist/src/commands/setup-support.d.ts +16 -4
  99. package/dist/src/commands/setup-support.js +16 -16
  100. package/dist/src/commands/setup.js +3 -9
  101. package/dist/src/commands/source.js +8 -1
  102. package/dist/src/commands/status-check.js +2 -2
  103. package/dist/src/commands/status-output.js +2 -2
  104. package/dist/src/commands/status.js +3 -4
  105. package/dist/src/commands/test-build-phase.d.ts +2 -2
  106. package/dist/src/commands/test-build-phase.js +31 -7
  107. package/dist/src/commands/test-modes.d.ts +2 -2
  108. package/dist/src/commands/test-modes.js +0 -1
  109. package/dist/src/commands/test-register.js +5 -3
  110. package/dist/src/commands/test-run.js +25 -18
  111. package/dist/src/commands/test-stale-gate.js +4 -4
  112. package/dist/src/commands/test-verdict.d.ts +16 -2
  113. package/dist/src/commands/test-verdict.js +18 -3
  114. package/dist/src/commands/test.js +42 -5
  115. package/dist/src/commands/token-list.d.ts +26 -0
  116. package/dist/src/commands/token-list.js +153 -0
  117. package/dist/src/commands/token.js +17 -1
  118. package/dist/src/commands/tree.js +7 -2
  119. package/dist/src/commands/typecheck.js +9 -9
  120. package/dist/src/commands/wire.js +4 -3
  121. package/dist/src/core/branding.d.ts +0 -7
  122. package/dist/src/core/branding.js +3 -2
  123. package/dist/src/core/browser-wire.js +6 -3
  124. package/dist/src/core/build-audit-platform.d.ts +29 -0
  125. package/dist/src/core/build-audit-platform.js +140 -0
  126. package/dist/src/core/build-audit-registration.js +3 -2
  127. package/dist/src/core/build-audit-skip.d.ts +57 -0
  128. package/dist/src/core/build-audit-skip.js +112 -0
  129. package/dist/src/core/build-audit.d.ts +15 -2
  130. package/dist/src/core/build-audit.js +110 -10
  131. package/dist/src/core/build-baseline.d.ts +45 -28
  132. package/dist/src/core/build-baseline.js +6 -29
  133. package/dist/src/core/build-jar-escalation.js +3 -2
  134. package/dist/src/core/build-overwrite-guard.js +1 -1
  135. package/dist/src/core/build-prepare.d.ts +0 -8
  136. package/dist/src/core/build-prepare.js +7 -12
  137. package/dist/src/core/config-paths.d.ts +2 -6
  138. package/dist/src/core/config-paths.js +4 -2
  139. package/dist/src/core/config-state.d.ts +0 -6
  140. package/dist/src/core/config-state.js +1 -19
  141. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  142. package/dist/src/core/config-validate-build-audit.js +80 -0
  143. package/dist/src/core/config-validate-file-size.js +1 -1
  144. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  145. package/dist/src/core/config-validate-patch-policy.js +3 -3
  146. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  147. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  148. package/dist/src/core/config-validate.js +14 -3
  149. package/dist/src/core/config.d.ts +2 -2
  150. package/dist/src/core/config.js +11 -15
  151. package/dist/src/core/coverage-extend.js +4 -4
  152. package/dist/src/core/discard-baseline.js +7 -7
  153. package/dist/src/core/engine-precondition.d.ts +1 -9
  154. package/dist/src/core/engine-precondition.js +3 -8
  155. package/dist/src/core/engine-session-lock.js +13 -17
  156. package/dist/src/core/export-moved-code-shape.js +2 -2
  157. package/dist/src/core/file-lock-owner.d.ts +103 -0
  158. package/dist/src/core/file-lock-owner.js +259 -0
  159. package/dist/src/core/file-lock.d.ts +2 -7
  160. package/dist/src/core/file-lock.js +40 -91
  161. package/dist/src/core/firefox-cache.d.ts +13 -1
  162. package/dist/src/core/firefox-cache.js +59 -34
  163. package/dist/src/core/firefox-download.d.ts +15 -0
  164. package/dist/src/core/firefox-download.js +37 -21
  165. package/dist/src/core/firefox-extract.d.ts +6 -5
  166. package/dist/src/core/firefox-extract.js +36 -18
  167. package/dist/src/core/firefox.d.ts +30 -32
  168. package/dist/src/core/firefox.js +5 -36
  169. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  170. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  171. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  172. package/dist/src/core/furnace-apply-helpers.js +8 -19
  173. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  174. package/dist/src/core/furnace-apply.d.ts +1 -1
  175. package/dist/src/core/furnace-apply.js +30 -10
  176. package/dist/src/core/furnace-config.d.ts +1 -1
  177. package/dist/src/core/furnace-config.js +23 -36
  178. package/dist/src/core/furnace-constants.js +3 -2
  179. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  180. package/dist/src/core/furnace-dir-entry.js +21 -0
  181. package/dist/src/core/furnace-operation.d.ts +2 -0
  182. package/dist/src/core/furnace-operation.js +3 -1
  183. package/dist/src/core/furnace-refresh.d.ts +18 -6
  184. package/dist/src/core/furnace-refresh.js +3 -6
  185. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  186. package/dist/src/core/furnace-registration-ast.js +0 -3
  187. package/dist/src/core/furnace-registration.d.ts +2 -1
  188. package/dist/src/core/furnace-registration.js +22 -29
  189. package/dist/src/core/furnace-scanner.js +3 -6
  190. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  191. package/dist/src/core/furnace-validate-registration.js +5 -7
  192. package/dist/src/core/git-base.d.ts +3 -1
  193. package/dist/src/core/git-base.js +25 -3
  194. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  195. package/dist/src/core/git-diff-new-file.js +119 -0
  196. package/dist/src/core/git-diff.d.ts +9 -9
  197. package/dist/src/core/git-diff.js +88 -157
  198. package/dist/src/core/git-file-ops.d.ts +0 -7
  199. package/dist/src/core/git-file-ops.js +4 -28
  200. package/dist/src/core/git-readonly-index.d.ts +0 -2
  201. package/dist/src/core/git-readonly-index.js +0 -4
  202. package/dist/src/core/git-status.d.ts +7 -0
  203. package/dist/src/core/git-status.js +21 -9
  204. package/dist/src/core/git.d.ts +0 -6
  205. package/dist/src/core/git.js +14 -43
  206. package/dist/src/core/harness-orphans.js +8 -7
  207. package/dist/src/core/history-log.d.ts +19 -0
  208. package/dist/src/core/history-log.js +29 -0
  209. package/dist/src/core/mach-error-hints.d.ts +0 -5
  210. package/dist/src/core/mach-error-hints.js +1 -1
  211. package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
  212. package/dist/src/core/mach-known-noise-filter.js +7 -9
  213. package/dist/src/core/mach-mozconfig.js +2 -1
  214. package/dist/src/core/mach-python.d.ts +2 -0
  215. package/dist/src/core/mach-python.js +12 -7
  216. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  217. package/dist/src/core/mach-resource-shim.js +112 -9
  218. package/dist/src/core/mach.d.ts +39 -53
  219. package/dist/src/core/mach.js +13 -81
  220. package/dist/src/core/marionette-port.d.ts +0 -17
  221. package/dist/src/core/marionette-port.js +12 -58
  222. package/dist/src/core/marionette-preflight.js +9 -27
  223. package/dist/src/core/mochitest-server-port.js +3 -3
  224. package/dist/src/core/moz-manifest-rules.js +37 -33
  225. package/dist/src/core/patch-apply-fuzz.js +4 -4
  226. package/dist/src/core/patch-apply.d.ts +4 -5
  227. package/dist/src/core/patch-apply.js +6 -12
  228. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  229. package/dist/src/core/patch-export-coverage.js +1 -30
  230. package/dist/src/core/patch-export-update.d.ts +18 -1
  231. package/dist/src/core/patch-export-update.js +4 -3
  232. package/dist/src/core/patch-export.d.ts +11 -21
  233. package/dist/src/core/patch-export.js +44 -66
  234. package/dist/src/core/patch-files.d.ts +0 -4
  235. package/dist/src/core/patch-files.js +0 -11
  236. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  237. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  238. package/dist/src/core/patch-lint-cache.js +3 -6
  239. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  240. package/dist/src/core/patch-lint-checkjs.js +37 -24
  241. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  242. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  243. package/dist/src/core/patch-lint-cross.js +49 -92
  244. package/dist/src/core/patch-lint-css.js +10 -6
  245. package/dist/src/core/patch-lint-file-size.js +3 -3
  246. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  247. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  248. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  249. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  250. package/dist/src/core/patch-lint-prettier.js +1 -1
  251. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  252. package/dist/src/core/patch-lint-queue-types.js +12 -0
  253. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  254. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  255. package/dist/src/core/patch-lint.d.ts +14 -9
  256. package/dist/src/core/patch-lint.js +4 -3
  257. package/dist/src/core/patch-lock.js +3 -3
  258. package/dist/src/core/patch-manifest-io.js +8 -7
  259. package/dist/src/core/patch-parse.d.ts +19 -8
  260. package/dist/src/core/patch-parse.js +25 -0
  261. package/dist/src/core/patch-policy.js +15 -27
  262. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  263. package/dist/src/core/patch-source-metadata.js +0 -4
  264. package/dist/src/core/patch-transform.d.ts +0 -8
  265. package/dist/src/core/patch-transform.js +0 -10
  266. package/dist/src/core/process-cpu-time.js +1 -1
  267. package/dist/src/core/rebase-session.d.ts +2 -8
  268. package/dist/src/core/rebase-session.js +5 -17
  269. package/dist/src/core/register-browser-content.js +3 -2
  270. package/dist/src/core/register-browser-test.js +2 -1
  271. package/dist/src/core/register-module.js +2 -1
  272. package/dist/src/core/register-scaffold.js +5 -4
  273. package/dist/src/core/register-shared-css.js +2 -1
  274. package/dist/src/core/run-log-redact.d.ts +38 -0
  275. package/dist/src/core/run-log-redact.js +69 -0
  276. package/dist/src/core/run-log.d.ts +13 -2
  277. package/dist/src/core/run-log.js +67 -18
  278. package/dist/src/core/signal-critical.js +2 -4
  279. package/dist/src/core/smoke-patterns.d.ts +0 -1
  280. package/dist/src/core/smoke-patterns.js +6 -11
  281. package/dist/src/core/state-file.d.ts +12 -1
  282. package/dist/src/core/state-file.js +14 -1
  283. package/dist/src/core/status-classify.js +2 -1
  284. package/dist/src/core/test-changed-prefs.js +26 -11
  285. package/dist/src/core/test-harness-crash.js +2 -2
  286. package/dist/src/core/test-stale-check.d.ts +0 -2
  287. package/dist/src/core/test-stale-check.js +2 -2
  288. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  289. package/dist/src/core/test-xpcshell-retry.js +14 -11
  290. package/dist/src/core/token-category.d.ts +33 -0
  291. package/dist/src/core/token-category.js +56 -48
  292. package/dist/src/core/token-dark-mode.js +2 -14
  293. package/dist/src/core/token-inventory.d.ts +56 -0
  294. package/dist/src/core/token-inventory.js +109 -0
  295. package/dist/src/core/token-manager.d.ts +1 -1
  296. package/dist/src/core/token-manager.js +1 -1
  297. package/dist/src/core/token-variant.js +21 -22
  298. package/dist/src/core/toolchain-preflight.js +1 -1
  299. package/dist/src/core/tree-cow.d.ts +0 -2
  300. package/dist/src/core/tree-cow.js +2 -2
  301. package/dist/src/core/tree-store.js +4 -5
  302. package/dist/src/core/typecheck.js +2 -2
  303. package/dist/src/core/wire-dom-fragment.js +6 -17
  304. package/dist/src/core/wire-utils.js +5 -17
  305. package/dist/src/core/xpcshell-appdir.js +2 -4
  306. package/dist/src/errors/base.d.ts +53 -1
  307. package/dist/src/errors/base.js +79 -11
  308. package/dist/src/errors/build.js +20 -28
  309. package/dist/src/errors/config.d.ts +0 -14
  310. package/dist/src/errors/config.js +13 -34
  311. package/dist/src/errors/download.d.ts +13 -0
  312. package/dist/src/errors/download.js +53 -24
  313. package/dist/src/errors/furnace.js +8 -9
  314. package/dist/src/errors/git.d.ts +8 -8
  315. package/dist/src/errors/git.js +20 -30
  316. package/dist/src/errors/patch.js +8 -10
  317. package/dist/src/errors/rebase.js +7 -7
  318. package/dist/src/types/commands/options.d.ts +77 -123
  319. package/dist/src/types/config.d.ts +34 -2
  320. package/dist/src/types/index.d.ts +0 -1
  321. package/dist/src/utils/assert.d.ts +3 -15
  322. package/dist/src/utils/assert.js +1 -18
  323. package/dist/src/utils/build-info.d.ts +1 -1
  324. package/dist/src/utils/child-env.d.ts +2 -1
  325. package/dist/src/utils/elapsed.js +10 -1
  326. package/dist/src/utils/fs.js +8 -13
  327. package/dist/src/utils/hash.d.ts +12 -0
  328. package/dist/src/utils/hash.js +16 -0
  329. package/dist/src/utils/line-dispatch.d.ts +15 -0
  330. package/dist/src/utils/line-dispatch.js +48 -0
  331. package/dist/src/utils/options.d.ts +1 -7
  332. package/dist/src/utils/options.js +6 -7
  333. package/dist/src/utils/package-root.js +3 -12
  334. package/dist/src/utils/platform.d.ts +0 -4
  335. package/dist/src/utils/process-group.d.ts +2 -1
  336. package/dist/src/utils/process-group.js +20 -19
  337. package/dist/src/utils/process.d.ts +40 -26
  338. package/dist/src/utils/process.js +115 -166
  339. package/dist/src/utils/ps-duration.js +3 -3
  340. package/dist/src/utils/sleep.d.ts +26 -0
  341. package/dist/src/utils/sleep.js +31 -0
  342. package/dist/src/utils/validation.d.ts +2 -38
  343. package/dist/src/utils/validation.js +7 -16
  344. package/docs/configuration.md +89 -1
  345. package/docs/exit-codes.md +31 -19
  346. package/docs/lifecycle-invariants.md +18 -12
  347. package/docs/machine-output.md +31 -8
  348. package/docs/patch-workflow.md +2 -1
  349. package/docs/run-logs.md +44 -4
  350. package/docs/testing.md +8 -1
  351. package/package.json +8 -7
  352. package/dist/src/commands/rebase.d.ts +0 -4
  353. package/dist/src/commands/rebase.js +0 -5
  354. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  355. package/dist/src/core/patch-artifact-normalize.js +0 -24
  356. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  357. package/dist/src/core/patch-lint-reexports.js +0 -8
  358. package/dist/src/core/wire-targets.d.ts +0 -7
  359. package/dist/src/core/wire-targets.js +0 -8
@@ -0,0 +1,210 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Cross-patch lint rule: a test manifest that registers a file a
4
+ * LATER-ordered patch creates, without declaring the staged dependency.
5
+ *
6
+ * The import kind has had this arm since forward-import shipped; the
7
+ * registration kind never did. `--kind registration` metadata was only ever
8
+ * checked for STALENESS once declared, so a patch whose `xpcshell.toml`
9
+ * carried `support-files = ["fixtures/*.sqlite"]` for a fixture the next
10
+ * patch creates passed lint in both arms — declaring the dependency and
11
+ * removing the declaration produced identical silence, which is what made
12
+ * the gap invisible rather than merely absent.
13
+ *
14
+ * Scope is deliberately narrow: test-manifest `support-files` only. A
15
+ * jar.mn packaging line or an actor/customElements registration names its
16
+ * target through indirection the linter cannot resolve without the build
17
+ * system, and a rule that guesses there would refuse correct queues on a
18
+ * Firefox-sized tree. The staged-registration declaration remains the
19
+ * documented escape hatch for a deliberate stage.
20
+ */
21
+ import { basename, dirname, posix } from 'node:path';
22
+ import { normalizePathSlashes } from '../utils/paths.js';
23
+ import { escapeRegex } from '../utils/regex.js';
24
+ import { isLaterOwner, quoteRegistrationLine } from './patch-lint-staged-registration.js';
25
+ /**
26
+ * Manifest basenames whose `support-files` key registers auxiliary files
27
+ * for a test suite. Closed and documented on purpose — a pattern like
28
+ * "anything .toml" would sweep in Cargo and taskcluster manifests, whose
29
+ * `support-files`-shaped keys mean something else entirely.
30
+ */
31
+ const TEST_MANIFEST_BASENAMES = new Set([
32
+ 'xpcshell.toml',
33
+ 'xpcshell.ini',
34
+ 'mochitest.toml',
35
+ 'mochitest.ini',
36
+ 'browser.toml',
37
+ 'browser.ini',
38
+ 'chrome.toml',
39
+ 'chrome.ini',
40
+ 'a11y.toml',
41
+ 'a11y.ini',
42
+ ]);
43
+ /** True for a path whose basename is a recognised test manifest. */
44
+ function isTestManifestPath(path) {
45
+ return TEST_MANIFEST_BASENAMES.has(basename(normalizePathSlashes(path)));
46
+ }
47
+ /** Matches the start of a `support-files` assignment. */
48
+ const SUPPORT_FILES_PATTERN = /^\s*support-files\s*=\s*(.*)$/;
49
+ /**
50
+ * Extracts the `support-files` entries declared in `content`, each paired
51
+ * with the manifest line it is written on.
52
+ *
53
+ * Handles the TOML array on one line and spread over several, and the
54
+ * whitespace-separated `.ini` spelling. Anything else — a computed value, a
55
+ * shape this does not recognise — yields nothing, because a rule that
56
+ * guesses at a manifest it cannot parse refuses correct queues.
57
+ *
58
+ * Quoted tokens are matched per physical line rather than over the whole
59
+ * accumulated array body, so a multi-line array attributes each element to
60
+ * its own line. Entries are deduplicated: a manifest that lists the same
61
+ * file under two per-test sections needs one declaration, not two.
62
+ */
63
+ export function extractSupportFileEntries(content) {
64
+ const lines = content.split('\n');
65
+ const order = [];
66
+ const sourceLines = new Map();
67
+ const record = (entry, source) => {
68
+ let seen = sourceLines.get(entry);
69
+ if (seen === undefined) {
70
+ seen = [];
71
+ sourceLines.set(entry, seen);
72
+ order.push(entry);
73
+ }
74
+ const trimmed = source.trim();
75
+ if (!seen.includes(trimmed))
76
+ seen.push(trimmed);
77
+ };
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const raw = lines[i] ?? '';
80
+ const match = SUPPORT_FILES_PATTERN.exec(raw);
81
+ if (!match)
82
+ continue;
83
+ const head = match[1] ?? '';
84
+ if (!head.trimStart().startsWith('[')) {
85
+ // `.ini` spelling: whitespace-separated bare paths on one line.
86
+ for (const token of head.split(/\s+/).filter((t) => t.length > 0))
87
+ record(token, raw);
88
+ continue;
89
+ }
90
+ // TOML array: walk the physical lines until the closing bracket,
91
+ // bounded so an unterminated array in a malformed manifest cannot walk
92
+ // the file.
93
+ let body = head;
94
+ let scanned = 0;
95
+ let current = raw;
96
+ for (;;) {
97
+ for (const quoted of current.matchAll(/["']([^"']+)["']/g)) {
98
+ if (quoted[1] !== undefined)
99
+ record(quoted[1], current);
100
+ }
101
+ if (body.includes(']') || i + 1 >= lines.length || scanned >= 200)
102
+ break;
103
+ i += 1;
104
+ scanned += 1;
105
+ current = lines[i] ?? '';
106
+ body += `\n${current}`;
107
+ }
108
+ }
109
+ return order.map((entry) => {
110
+ const seen = sourceLines.get(entry) ?? [];
111
+ return { entry, line: seen[0] ?? '', occurrences: seen.length };
112
+ });
113
+ }
114
+ /**
115
+ * Resolves one `support-files` entry, relative to its manifest's directory,
116
+ * into a matcher over engine-relative paths.
117
+ *
118
+ * Only a `*` inside a single path segment is expanded — the shape the
119
+ * downstream report uses (`fixtures/*.sqlite`). `**`, `!` exclusions and
120
+ * absolute `/`-rooted entries return undefined: they either cross directory
121
+ * boundaries or subtract, and a matcher that guessed at them would attribute
122
+ * a creation to the wrong manifest.
123
+ */
124
+ export function buildSupportFileMatcher(manifestPath, entry) {
125
+ if (entry.startsWith('!') || entry.includes('**') || entry.startsWith('/'))
126
+ return undefined;
127
+ const resolved = posix.normalize(posix.join(normalizePathSlashes(dirname(manifestPath)), normalizePathSlashes(entry)));
128
+ if (resolved.startsWith('..'))
129
+ return undefined;
130
+ if (!resolved.includes('*')) {
131
+ return (candidate) => normalizePathSlashes(candidate) === resolved;
132
+ }
133
+ const pattern = new RegExp(`^${resolved
134
+ .split('*')
135
+ .map((part) => escapeRegex(part))
136
+ .join('[^/]*')}$`);
137
+ return (candidate) => pattern.test(normalizePathSlashes(candidate));
138
+ }
139
+ /** True when this patch already declares the staged registration. */
140
+ function isDeclared(entry, manifestPath, createdPath) {
141
+ return (entry.metadata?.stagedDependencies?.registrations ?? []).some((registration) => normalizePathSlashes(registration.file) === normalizePathSlashes(manifestPath) &&
142
+ normalizePathSlashes(registration.creates) === normalizePathSlashes(createdPath));
143
+ }
144
+ /**
145
+ * Flags every `support-files` registration a patch introduces that names a
146
+ * file only a later-ordered patch creates, unless the staged dependency is
147
+ * declared.
148
+ *
149
+ * @param ctx - Projected queue in application order
150
+ * @returns One issue per undeclared forward registration
151
+ */
152
+ export function lintPatchQueueForwardRegistrations(ctx) {
153
+ const issues = [];
154
+ for (const entry of ctx.entries) {
155
+ // Both arms: a manifest this patch CREATES and one it MODIFIES can each
156
+ // introduce a registration line.
157
+ const manifests = [
158
+ ...entry.newFiles,
159
+ ...entry.modifiedFileAdditions,
160
+ ].filter(([path]) => isTestManifestPath(path));
161
+ for (const [manifestPath, content] of manifests) {
162
+ for (const { entry: raw, line: addedLine, occurrences } of extractSupportFileEntries(content)) {
163
+ const matches = buildSupportFileMatcher(manifestPath, raw);
164
+ if (matches === undefined)
165
+ continue;
166
+ // `--line` is the line as the patch adds it, which is what the
167
+ // declared arm compares and what `--remove` matches on. Synthesising
168
+ // `support-files = ["<entry>"]` instead only ever matched a
169
+ // single-line single-entry array, so the pasted command left the
170
+ // declaring patch red with staged-dependency-unused.
171
+ const declaredLine = quoteRegistrationLine(addedLine);
172
+ const ambiguity = occurrences > 1
173
+ ? ` (the entry is spelled out on ${occurrences} added lines here; ` +
174
+ 'the first is quoted, and one declaration covers the file either way ' +
175
+ 'because the declared check keys on --file and --creates.)'
176
+ : '';
177
+ for (const later of ctx.entries) {
178
+ // Same predicate the declared-registration arm validates against,
179
+ // tiebreak included: two spellings of "later" would let one rule
180
+ // flag a pair the other considers already satisfied.
181
+ if (!isLaterOwner(later, entry))
182
+ continue;
183
+ for (const created of later.createdFiles) {
184
+ if (!matches(created))
185
+ continue;
186
+ if (isDeclared(entry, manifestPath, created))
187
+ continue;
188
+ issues.push({
189
+ file: manifestPath,
190
+ check: 'forward-registration',
191
+ patches: [entry.filename],
192
+ fingerprint: `forward-registration|${entry.filename}|${manifestPath}|${raw}|${created}|${later.filename}`,
193
+ message: `${manifestPath} in ${entry.filename} registers "${raw}" as a support file, ` +
194
+ `but ${created} is created by the later patch ${later.filename}. ` +
195
+ 'Applying the queue up to this patch leaves the registration dangling. ' +
196
+ 'Reorder the patches so the file is created first, move the registration into ' +
197
+ 'the later patch, or declare the intentional staged dependency with: ' +
198
+ `fireforge patch staged-dependency ${entry.filename} --add --kind registration ` +
199
+ `--file ${manifestPath} --line "${declaredLine}" ` +
200
+ `--creates ${created} --owner ${later.filename}` +
201
+ ambiguity,
202
+ severity: 'error',
203
+ });
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ return issues;
210
+ }
@@ -46,8 +46,8 @@ function findAttachedJsDoc(comments, declStart, source) {
46
46
  * loses the param name. String literal types may contain braces too, so
47
47
  * quoted runs are skipped verbatim.
48
48
  *
49
- * @returns Index just past the matching close brace, or -1 when the type
50
- * expression never closes (malformed doc — caller skips the tag).
49
+ * @returns Index just past the matching close brace, or undefined when the
50
+ * type expression never closes (malformed doc — caller skips the tag).
51
51
  */
52
52
  function skipBalancedTypeBraces(jsDoc, start) {
53
53
  let depth = 0;
@@ -73,7 +73,7 @@ function skipBalancedTypeBraces(jsDoc, start) {
73
73
  return i + 1;
74
74
  }
75
75
  }
76
- return -1;
76
+ return undefined;
77
77
  }
78
78
  function extractParamNames(jsDoc) {
79
79
  const names = [];
@@ -87,7 +87,7 @@ function extractParamNames(jsDoc) {
87
87
  // inline object types or Record<…> generics don't truncate the scan.
88
88
  if (jsDoc[i] === '{') {
89
89
  const afterType = skipBalancedTypeBraces(jsDoc, i);
90
- if (afterType === -1)
90
+ if (afterType === undefined)
91
91
  continue;
92
92
  i = afterType;
93
93
  while (i < jsDoc.length && /\s/.test(jsDoc[i] ?? ''))
@@ -124,6 +124,17 @@ function hasPrivateOrInternalTag(jsDoc) {
124
124
  function functionReturnsValue(node) {
125
125
  return walkForReturn(node.body);
126
126
  }
127
+ /**
128
+ * Narrows an arbitrary child value reached by walking a node's own
129
+ * properties to an ESTree node. Acorn's AST holds nodes, arrays of nodes,
130
+ * and plain scalars in the same property positions, so the discriminating
131
+ * `type` string is the only structural signal available.
132
+ */
133
+ function isEstreeNode(value) {
134
+ if (typeof value !== 'object' || value === null || !('type' in value))
135
+ return false;
136
+ return typeof value.type === 'string';
137
+ }
127
138
  function walkForReturn(node) {
128
139
  if (node.type === 'ReturnStatement') {
129
140
  return node.argument != null;
@@ -139,20 +150,16 @@ function walkForReturn(node) {
139
150
  for (const [key, val] of entries) {
140
151
  if (key === 'type')
141
152
  continue;
142
- if (val && typeof val === 'object') {
143
- if (Array.isArray(val)) {
144
- for (const child of val) {
145
- if (child && typeof child === 'object' && 'type' in child) {
146
- if (walkForReturn(child))
147
- return true;
148
- }
149
- }
150
- }
151
- else if ('type' in val) {
152
- if (walkForReturn(val))
153
+ if (Array.isArray(val)) {
154
+ const children = val;
155
+ for (const child of children) {
156
+ if (isEstreeNode(child) && walkForReturn(child))
153
157
  return true;
154
158
  }
155
159
  }
160
+ else if (isEstreeNode(val) && walkForReturn(val)) {
161
+ return true;
162
+ }
156
163
  }
157
164
  return false;
158
165
  }
@@ -183,12 +190,7 @@ function findLocalDeclaration(body, name) {
183
190
  // Line number helpers
184
191
  // ---------------------------------------------------------------------------
185
192
  function lineAt(source, offset) {
186
- let line = 1;
187
- for (let i = 0; i < offset && i < source.length; i++) {
188
- if (source[i] === '\n')
189
- line++;
190
- }
191
- return line;
193
+ return source.slice(0, offset).split('\n').length;
192
194
  }
193
195
  /**
194
196
  * Validates @param name matching and @returns presence on a function-like
@@ -198,21 +200,19 @@ function lineAt(source, offset) {
198
200
  */
199
201
  function validateParamsAndReturns(fnNode, jsDoc, issues, ctx) {
200
202
  const docText = jsDoc.value;
201
- if (!ctx.skipParams) {
202
- const actualParams = fnNode.params
203
- .map((p) => (p.type === 'Identifier' ? p.name : null))
204
- .filter((n) => n !== null);
205
- if (actualParams.length > 0) {
206
- const docParams = extractParamNames(docText);
207
- for (const param of actualParams) {
208
- if (!docParams.includes(param)) {
209
- issues.push({
210
- line: ctx.line,
211
- check: ctx.paramCheck,
212
- message: `Exported ${ctx.label} at line ${ctx.line}: @param "${param}" is missing or misnamed in JSDoc.`,
213
- ...(ctx.severity ? { severity: ctx.severity } : {}),
214
- });
215
- }
203
+ const actualParams = fnNode.params
204
+ .map((p) => (p.type === 'Identifier' ? p.name : null))
205
+ .filter((n) => n !== null);
206
+ if (actualParams.length > 0) {
207
+ const docParams = extractParamNames(docText);
208
+ for (const param of actualParams) {
209
+ if (!docParams.includes(param)) {
210
+ issues.push({
211
+ line: ctx.line,
212
+ check: ctx.paramCheck,
213
+ message: `Exported ${ctx.label} at line ${ctx.line}: @param "${param}" is missing or misnamed in JSDoc.`,
214
+ ...(ctx.severity ? { severity: ctx.severity } : {}),
215
+ });
216
216
  }
217
217
  }
218
218
  }
@@ -1,12 +1,6 @@
1
1
  import type { PatchLintIssue } from '../types/commands/index.js';
2
- interface ModuleRegistrationQueueEntry {
3
- filename: string;
4
- newFiles: ReadonlyMap<string, string>;
5
- modifiedFileAdditions: ReadonlyMap<string, string>;
6
- }
7
- interface ModuleRegistrationQueueContext {
8
- entries: readonly ModuleRegistrationQueueEntry[];
9
- }
2
+ import type { PatchQueueRegistrationEntry, PatchQueueView } from './patch-lint-queue-types.js';
3
+ type ModuleRegistrationQueueContext = PatchQueueView<PatchQueueRegistrationEntry>;
10
4
  /**
11
5
  * Maps Firefox's common system-module resource URLs to their source
12
6
  * suffix. Exported alongside {@link extractResourceModuleSpecifiers} so
@@ -94,7 +94,7 @@ export async function invokePatchLintPrettier(engineDir, projectRoot, files, gat
94
94
  {
95
95
  file: '(prettier)',
96
96
  check: 'prettier-format',
97
- message: `prettier exited ${String(result.exitCode)} without completing the check: ` +
97
+ message: `prettier exited ${result.exitCode} without completing the check: ` +
98
98
  output.trim().split('\n').slice(-5).join(' ').slice(0, 400),
99
99
  severity,
100
100
  },
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Structural views of the cross-patch lint queue context.
3
+ *
4
+ * A leaf module by design: it imports nothing, so the individual rule
5
+ * modules can describe the slice of `PatchQueueContext` they read without
6
+ * importing `patch-lint-cross.ts` (which imports them back). Previously each
7
+ * rule re-declared its own private `{ entries }` interface; sharing them here
8
+ * keeps the dependency edge one-way and `dpdm` clean while stating the shape
9
+ * once.
10
+ */
11
+ /**
12
+ * The slice of a queue entry consumed by rules that read raw patch bodies
13
+ * (the new-file creators map, the binary-body check).
14
+ */
15
+ export interface PatchQueueBodyEntry {
16
+ /** Filename on disk and in the manifest. */
17
+ filename: string;
18
+ /** Raw unified-diff content of the patch body. */
19
+ diff: string;
20
+ }
21
+ /**
22
+ * The slice of a queue entry consumed by the module-registration rule: the
23
+ * per-path content maps rather than the raw diff.
24
+ */
25
+ export interface PatchQueueRegistrationEntry {
26
+ /** Filename on disk and in the manifest. */
27
+ filename: string;
28
+ /** Newly-created file path → the content the patch would produce. */
29
+ newFiles: ReadonlyMap<string, string>;
30
+ /** Existing file path → the lines the patch adds to it. */
31
+ modifiedFileAdditions: ReadonlyMap<string, string>;
32
+ }
33
+ /**
34
+ * Structural queue view: the `entries` field of a `PatchQueueContext`,
35
+ * narrowed to whichever entry slice a rule needs.
36
+ */
37
+ export interface PatchQueueView<TEntry> {
38
+ /** Entries in application order (lowest `order` first). */
39
+ entries: readonly TEntry[];
40
+ }
41
+ /**
42
+ * The slice consumed by the forward-registration rule: the same content maps
43
+ * the module-registration rule reads, plus the body-kind-agnostic creation
44
+ * set and the ordering/metadata needed to decide "later" and "declared".
45
+ */
46
+ export interface PatchQueueForwardRegistrationEntry extends PatchQueueRegistrationEntry {
47
+ /** Order number from the manifest (or filename prefix fallback). */
48
+ order: number;
49
+ /** Every path this patch creates, whatever the body kind. */
50
+ createdFiles: ReadonlySet<string>;
51
+ /** Manifest metadata. Null when the patch file exists but has no entry. */
52
+ metadata: {
53
+ stagedDependencies?: {
54
+ registrations?: readonly {
55
+ file: string;
56
+ creates: string;
57
+ }[];
58
+ };
59
+ } | null;
60
+ }
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Structural views of the cross-patch lint queue context.
4
+ *
5
+ * A leaf module by design: it imports nothing, so the individual rule
6
+ * modules can describe the slice of `PatchQueueContext` they read without
7
+ * importing `patch-lint-cross.ts` (which imports them back). Previously each
8
+ * rule re-declared its own private `{ entries }` interface; sharing them here
9
+ * keeps the dependency edge one-way and `dpdm` clean while stating the shape
10
+ * once.
11
+ */
12
+ export {};
@@ -0,0 +1,61 @@
1
+ import type { PatchStagedRegistration } from '../types/commands/index.js';
2
+ /** A patch that creates a given path, with its position in the queue. */
3
+ export interface NewFileOwner {
4
+ filename: string;
5
+ order: number;
6
+ fullPath: string;
7
+ }
8
+ /** The ordering fields `isLaterOwner` compares an owner against. */
9
+ export interface QueuePosition {
10
+ filename: string;
11
+ order: number;
12
+ }
13
+ /**
14
+ * Later-in-apply-order predicate shared by the forward-import scan, the
15
+ * forward-registration rule and the registration validation: strictly
16
+ * higher ordinal, or same ordinal with a lexicographically later filename
17
+ * (the apply loop's tiebreak).
18
+ *
19
+ * @param owner - The creating patch
20
+ * @param entry - The referencing patch
21
+ * @returns True when `owner` applies after `entry`
22
+ */
23
+ export declare function isLaterOwner(owner: QueuePosition, entry: QueuePosition): boolean;
24
+ /** The slice of a queue entry the registration validation reads. */
25
+ export interface StagedRegistrationEntry extends QueuePosition {
26
+ newFiles: ReadonlyMap<string, string>;
27
+ modifiedFileAdditions: ReadonlyMap<string, string>;
28
+ }
29
+ /** Why a staged registration declaration failed validation. */
30
+ export type StagedRegistrationFailure = {
31
+ kind: 'line-absent';
32
+ } | {
33
+ kind: 'creates-unresolved';
34
+ } | {
35
+ kind: 'owner-mismatch';
36
+ creators: readonly NewFileOwner[];
37
+ };
38
+ /**
39
+ * Validates one registration-kind declaration against the patch content
40
+ * and the queue, mirroring {@link findMatchingStagedDependency}: the
41
+ * declared line must be added by this patch, `creates` must exactly match
42
+ * a file a LATER-ordered patch creates (an earlier-only creator means the
43
+ * dependency is already satisfied and the declaration is stale), and
44
+ * `owner`, when set, must name one of those creating patches. Returns
45
+ * undefined when valid.
46
+ */
47
+ export declare function findStagedRegistrationFailure(entry: StagedRegistrationEntry, registration: PatchStagedRegistration, createdFileIndex: Map<string, NewFileOwner[]>): StagedRegistrationFailure | undefined;
48
+ /** Message tail for each {@link StagedRegistrationFailure} shape. */
49
+ export declare function describeRegistrationFailure(failure: StagedRegistrationFailure): string;
50
+ /**
51
+ * Renders `line` for the `--line "…"` argument of a paste-and-run
52
+ * `patch staged-dependency` command.
53
+ *
54
+ * A registration line is quoted for the shell in the messages that print
55
+ * the command, and a `support-files` element carries its own quotes
56
+ * (`"file_tiles_find.html",` is what a multi-line TOML array adds). Without
57
+ * escaping, the shell would eat them and the declaration would be written
58
+ * with a line the patch never adds — the exact mismatch that made the
59
+ * forward-registration remedy leave the declaring patch red.
60
+ */
61
+ export declare function quoteRegistrationLine(line: string): string;
@@ -0,0 +1,90 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Validation of `--kind registration` staged-dependency declarations.
4
+ *
5
+ * Split out of `patch-lint-cross.ts` to keep that file inside the per-file
6
+ * line budget, and because the forward-registration rule needs the same
7
+ * apply-order predicate: two spellings of "later in the queue" would let the
8
+ * rule that FLAGS an undeclared registration and the rule that validates a
9
+ * DECLARED one disagree about the very same pair of patches.
10
+ *
11
+ * Exports helpers consumed by `patch-lint-cross.ts` and
12
+ * `patch-lint-forward-registration.ts`; no registrar, no rule of its own.
13
+ */
14
+ import { basename } from 'node:path';
15
+ /**
16
+ * Later-in-apply-order predicate shared by the forward-import scan, the
17
+ * forward-registration rule and the registration validation: strictly
18
+ * higher ordinal, or same ordinal with a lexicographically later filename
19
+ * (the apply loop's tiebreak).
20
+ *
21
+ * @param owner - The creating patch
22
+ * @param entry - The referencing patch
23
+ * @returns True when `owner` applies after `entry`
24
+ */
25
+ export function isLaterOwner(owner, entry) {
26
+ return (owner.order > entry.order || (owner.order === entry.order && owner.filename > entry.filename));
27
+ }
28
+ /**
29
+ * Validates one registration-kind declaration against the patch content
30
+ * and the queue, mirroring {@link findMatchingStagedDependency}: the
31
+ * declared line must be added by this patch, `creates` must exactly match
32
+ * a file a LATER-ordered patch creates (an earlier-only creator means the
33
+ * dependency is already satisfied and the declaration is stale), and
34
+ * `owner`, when set, must name one of those creating patches. Returns
35
+ * undefined when valid.
36
+ */
37
+ export function findStagedRegistrationFailure(entry, registration, createdFileIndex) {
38
+ if (!isRegistrationLinePresent(entry, registration))
39
+ return { kind: 'line-absent' };
40
+ const creators = (createdFileIndex.get(basename(registration.creates)) ?? []).filter((owner) => owner.fullPath === registration.creates && isLaterOwner(owner, entry));
41
+ if (creators.length === 0)
42
+ return { kind: 'creates-unresolved' };
43
+ if (registration.owner !== undefined &&
44
+ !creators.some((owner) => owner.filename === registration.owner)) {
45
+ return { kind: 'owner-mismatch', creators };
46
+ }
47
+ return undefined;
48
+ }
49
+ /** Message tail for each {@link StagedRegistrationFailure} shape. */
50
+ export function describeRegistrationFailure(failure) {
51
+ switch (failure.kind) {
52
+ case 'line-absent':
53
+ return 'the patch does not add that line.';
54
+ case 'creates-unresolved':
55
+ return ('no later-ordered patch creates that file — the declaration is stale, ' +
56
+ 'or the queue order no longer stages it.');
57
+ case 'owner-mismatch':
58
+ return `that file is created by ${failure.creators.map((o) => o.filename).join(', ')}, not the declared owner.`;
59
+ }
60
+ }
61
+ /**
62
+ * True when the declared registration/packaging line appears (trimmed)
63
+ * among the lines the patch introduces in the declaring file — either as a
64
+ * newly-created file's content or as added lines to an existing file
65
+ * (where jar.mn / customElements.js / actor-registration edits land).
66
+ */
67
+ function isRegistrationLinePresent(entry, registration) {
68
+ const declared = registration.line.trim();
69
+ if (declared.length === 0)
70
+ return false;
71
+ const introduced = [
72
+ entry.newFiles.get(registration.file),
73
+ entry.modifiedFileAdditions.get(registration.file),
74
+ ];
75
+ return introduced.some((content) => content !== undefined && content.split('\n').some((line) => line.trim() === declared));
76
+ }
77
+ /**
78
+ * Renders `line` for the `--line "…"` argument of a paste-and-run
79
+ * `patch staged-dependency` command.
80
+ *
81
+ * A registration line is quoted for the shell in the messages that print
82
+ * the command, and a `support-files` element carries its own quotes
83
+ * (`"file_tiles_find.html",` is what a multi-line TOML array adds). Without
84
+ * escaping, the shell would eat them and the declaration would be written
85
+ * with a line the patch never adds — the exact mismatch that made the
86
+ * forward-registration remedy leave the declaring patch red.
87
+ */
88
+ export function quoteRegistrationLine(line) {
89
+ return line.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
90
+ }
@@ -2,7 +2,8 @@ import type { PatchLintIssue } from '../types/commands/index.js';
2
2
  import type { FireForgeConfig, ProjectLicense } from '../types/config.js';
3
3
  import { type CommentStyle } from './license-headers.js';
4
4
  import { lintPatchedCss } from './patch-lint-css.js';
5
- export * from './patch-lint-reexports.js';
5
+ export * from './patch-lint-cross.js';
6
+ export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
6
7
  export { lintPatchedCss };
7
8
  /**
8
9
  * Canonical `[check] file: message` rendering of a patch-lint issue — the
@@ -132,6 +133,17 @@ export type PatchSizeTierDecision = {
132
133
  * defensive-for-tests choice.
133
134
  */
134
135
  export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
136
+ /** Line and file-count thresholds governing one patch-size tier. */
137
+ export interface PatchSizeThresholds {
138
+ /** Added-line counts at which notice/warning/error issues fire. */
139
+ lines: {
140
+ notice: number;
141
+ warning: number;
142
+ error: number;
143
+ };
144
+ /** Maximum number of files a patch may touch before an issue fires. */
145
+ maxFiles: number;
146
+ }
135
147
  /**
136
148
  * Read-only view of the size thresholds a tier enforces.
137
149
  * Public-API companion to {@link resolvePatchSizeTier} /
@@ -139,14 +151,7 @@ export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string
139
151
  * checks against the SAME numbers `large-patch-lines` /
140
152
  * `large-patch-files` fire on instead of mirroring them.
141
153
  */
142
- export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): {
143
- lines: {
144
- notice: number;
145
- warning: number;
146
- error: number;
147
- };
148
- maxFiles: number;
149
- };
154
+ export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): PatchSizeThresholds;
150
155
  /**
151
156
  * Checks patch size and emits advisory warnings.
152
157
  *
@@ -22,9 +22,10 @@ import { invokePatchLintPrettier } from './patch-lint-prettier.js';
22
22
  // The cross-patch lint infrastructure (queue context builder,
23
23
  // duplicate-creation and forward-import rules, ignore marker) lives in
24
24
  // `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies each
25
- // stay within the per-file line budget. The public surface is re-exported
26
- // from `patch-lint-reexports.ts` so callers keep importing from one module.
27
- export * from './patch-lint-reexports.js';
25
+ // stay within the per-file line budget. Its surface is re-exported here so
26
+ // callers keep importing from one module.
27
+ export * from './patch-lint-cross.js';
28
+ export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
28
29
  // The CSS rule bodies live in `patch-lint-css.ts` (same per-file-budget
29
30
  // split as the other rule families); re-export the imported binding so
30
31
  // callers and tests keep importing `lintPatchedCss` from this module.