@hominis/fireforge 0.44.4 → 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.
- package/CHANGELOG.md +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -19,7 +19,9 @@ import { discoverPatches } from './patch-files.js';
|
|
|
19
19
|
import { lintPatchQueueBinaryBodies } from './patch-lint-binary.js';
|
|
20
20
|
import { collectNewFileCreatorsByPath } from './patch-lint-creators.js';
|
|
21
21
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
22
|
+
import { lintPatchQueueForwardRegistrations } from './patch-lint-forward-registration.js';
|
|
22
23
|
import { lintPatchQueueModuleRegistrations } from './patch-lint-module-registration.js';
|
|
24
|
+
import { describeRegistrationFailure, findStagedRegistrationFailure, isLaterOwner, quoteRegistrationLine, } from './patch-lint-staged-registration.js';
|
|
23
25
|
import { loadPatchesManifest } from './patch-manifest-io.js';
|
|
24
26
|
import { categoryRangeForOrder, categoryRangeLabel } from './patch-policy.js';
|
|
25
27
|
import { extractNewFileContent } from './patch-transform.js';
|
|
@@ -77,6 +79,9 @@ export async function buildPatchQueueContext(patchesDir, config) {
|
|
|
77
79
|
metadata: metadataByFilename.get(patch.filename) ?? null,
|
|
78
80
|
diff,
|
|
79
81
|
newFiles,
|
|
82
|
+
// Straight from the detector, before the text-extraction filter above
|
|
83
|
+
// drops the bodies it cannot decode.
|
|
84
|
+
createdFiles: newFilePaths,
|
|
80
85
|
modifiedFileAdditions,
|
|
81
86
|
});
|
|
82
87
|
}
|
|
@@ -125,24 +130,6 @@ const FORWARD_IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
|
125
130
|
export function isForwardImportableFile(path) {
|
|
126
131
|
return FORWARD_IMPORTABLE_EXTENSIONS.some((ext) => path.endsWith(ext));
|
|
127
132
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Regex-level extractor for module specifiers in ES module / subscript files.
|
|
130
|
-
*
|
|
131
|
-
* Intentionally conservative. Catches:
|
|
132
|
-
* - `import ... from "specifier"` (ES module static imports)
|
|
133
|
-
* - `import "specifier"` (side-effect imports — the `from`
|
|
134
|
-
* clause is optional in the regex)
|
|
135
|
-
* - `import("specifier")` (dynamic imports)
|
|
136
|
-
* - ChromeUtils.importESModule("specifier")
|
|
137
|
-
* - ChromeUtils.defineESModuleGetters(obj, { Name: "specifier", ... })
|
|
138
|
-
*
|
|
139
|
-
* Returns the raw specifier strings — callers should take the leaf basename
|
|
140
|
-
* to match against the newFileIndex, because we do not resolve `resource://`
|
|
141
|
-
* URLs to engine file paths.
|
|
142
|
-
*/
|
|
143
|
-
export function extractImportSpecifiers(source) {
|
|
144
|
-
return extractImportSpecifiersWithLines(source).map((item) => item.specifier);
|
|
145
|
-
}
|
|
146
133
|
function buildLineOffsets(source) {
|
|
147
134
|
const offsets = [0];
|
|
148
135
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -236,7 +223,7 @@ function collectGetterSpecifiers(stripped, results, offsetToLine) {
|
|
|
236
223
|
}
|
|
237
224
|
/**
|
|
238
225
|
* Returns import specifiers plus 0-indexed line numbers, preserving the
|
|
239
|
-
* same matching behavior as {@link
|
|
226
|
+
* same matching behavior as {@link extractImportSpecifiersWithLines}.
|
|
240
227
|
*/
|
|
241
228
|
export function extractImportSpecifiersWithLines(source) {
|
|
242
229
|
// stripJsComments replaces comment bodies with space runs of equal
|
|
@@ -290,8 +277,8 @@ function extractBareGetterSpecifiers(source) {
|
|
|
290
277
|
const stripped = stripJsComments(source);
|
|
291
278
|
const results = [];
|
|
292
279
|
const lines = stripped.split('\n');
|
|
293
|
-
for (
|
|
294
|
-
const match = BARE_GETTER_PROPERTY_LINE.exec(
|
|
280
|
+
for (const [i, line] of lines.entries()) {
|
|
281
|
+
const match = BARE_GETTER_PROPERTY_LINE.exec(line);
|
|
295
282
|
if (match?.[1])
|
|
296
283
|
results.push({ specifier: match[1], line: i });
|
|
297
284
|
}
|
|
@@ -323,9 +310,8 @@ export const FORWARD_IMPORT_IGNORE_MARKER = 'fireforge-ignore: forward-import';
|
|
|
323
310
|
export function findForwardImportIgnoreLines(source) {
|
|
324
311
|
const lines = source.split('\n');
|
|
325
312
|
const ignored = new Set();
|
|
326
|
-
for (
|
|
327
|
-
|
|
328
|
-
if (line && line.includes(FORWARD_IMPORT_IGNORE_MARKER)) {
|
|
313
|
+
for (const [i, line] of lines.entries()) {
|
|
314
|
+
if (line.includes(FORWARD_IMPORT_IGNORE_MARKER)) {
|
|
329
315
|
ignored.add(i);
|
|
330
316
|
ignored.add(i + 1);
|
|
331
317
|
}
|
|
@@ -348,29 +334,44 @@ function findMatchingStagedDependency(entry, sitePath, specifier, laterOwners) {
|
|
|
348
334
|
laterOwners.some((owner) => owner.fullPath === dependency.creates &&
|
|
349
335
|
(dependency.owner === undefined || dependency.owner === owner.filename)));
|
|
350
336
|
}
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
* lexicographically later filename (the apply loop's tiebreak).
|
|
355
|
-
*/
|
|
356
|
-
function isLaterOwner(owner, entry) {
|
|
357
|
-
return (owner.order > entry.order || (owner.order === entry.order && owner.filename > entry.filename));
|
|
358
|
-
}
|
|
359
|
-
/** Builds the basename → later-creators index used by the forward-import scan. */
|
|
360
|
-
function buildNewFileIndex(ctx) {
|
|
361
|
-
const newFileIndex = new Map();
|
|
337
|
+
/** Builds a basename → creators index from one path set per entry. */
|
|
338
|
+
function buildCreatorIndex(ctx, pathsOf) {
|
|
339
|
+
const index = new Map();
|
|
362
340
|
for (const entry of ctx.entries) {
|
|
363
|
-
for (const fullPath of entry
|
|
341
|
+
for (const fullPath of pathsOf(entry)) {
|
|
364
342
|
const leaf = basename(fullPath);
|
|
365
|
-
let owners =
|
|
343
|
+
let owners = index.get(leaf);
|
|
366
344
|
if (!owners) {
|
|
367
345
|
owners = [];
|
|
368
|
-
|
|
346
|
+
index.set(leaf, owners);
|
|
369
347
|
}
|
|
370
348
|
owners.push({ filename: entry.filename, order: entry.order, fullPath });
|
|
371
349
|
}
|
|
372
350
|
}
|
|
373
|
-
return
|
|
351
|
+
return index;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Basename → later-creators index for the forward-IMPORT scan.
|
|
355
|
+
*
|
|
356
|
+
* Keyed on `newFiles` deliberately: this index answers "which patch creates
|
|
357
|
+
* the module this specifier resolves to", and a binary is not a module.
|
|
358
|
+
*/
|
|
359
|
+
function buildNewFileIndex(ctx) {
|
|
360
|
+
return buildCreatorIndex(ctx, (entry) => entry.newFiles.keys());
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Basename → creators index for the staged-REGISTRATION rules.
|
|
364
|
+
*
|
|
365
|
+
* Keyed on `createdFiles`, which includes binary creations. A registration
|
|
366
|
+
* (`support-files`, a jar.mn line, an actor entry) points at a file that
|
|
367
|
+
* must EXIST after apply; whether its body was text or a `GIT binary patch`
|
|
368
|
+
* has nothing to do with it. Resolving this from `newFiles` made a declared
|
|
369
|
+
* dependency on a binary fixture report `creates-unresolved`, so the
|
|
370
|
+
* declaration read as stale on every lint run and removing it changed
|
|
371
|
+
* nothing either — the failure was symmetric and therefore invisible.
|
|
372
|
+
*/
|
|
373
|
+
function buildCreatedFileIndex(ctx) {
|
|
374
|
+
return buildCreatorIndex(ctx, (entry) => entry.createdFiles);
|
|
374
375
|
}
|
|
375
376
|
/**
|
|
376
377
|
* Visits every forward-import site (an import whose leaf is created by a
|
|
@@ -389,9 +390,9 @@ function eachForwardImportSite(ctx, newFileIndex, visit) {
|
|
|
389
390
|
// Dedupe by (specifier, line): a patch that adds the whole getter
|
|
390
391
|
// call carries both the opener (found by the balanced walk) and the
|
|
391
392
|
// property line (found here) — report each site once.
|
|
392
|
-
const seen = new Set(sites.map((site) => `${site.specifier}|${
|
|
393
|
+
const seen = new Set(sites.map((site) => `${site.specifier}|${site.line}`));
|
|
393
394
|
for (const extra of extractBareGetterSpecifiers(content)) {
|
|
394
|
-
if (!seen.has(`${extra.specifier}|${
|
|
395
|
+
if (!seen.has(`${extra.specifier}|${extra.line}`))
|
|
395
396
|
sites.push(extra);
|
|
396
397
|
}
|
|
397
398
|
}
|
|
@@ -453,6 +454,7 @@ export function collectForwardImportEdges(ctx) {
|
|
|
453
454
|
*/
|
|
454
455
|
export function lintPatchQueueForwardImports(ctx) {
|
|
455
456
|
const newFileIndex = buildNewFileIndex(ctx);
|
|
457
|
+
const createdFileIndex = buildCreatedFileIndex(ctx);
|
|
456
458
|
const issues = [];
|
|
457
459
|
const usedStagedDeclarations = new Set();
|
|
458
460
|
eachForwardImportSite(ctx, newFileIndex, (entry, sitePath, specifier, cleaned, laterOwners) => {
|
|
@@ -531,7 +533,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
531
533
|
// (with `owner`, when set, naming that patch) — mirroring
|
|
532
534
|
// findMatchingStagedDependency for the import kind.
|
|
533
535
|
for (const registration of entry.metadata?.stagedDependencies?.registrations ?? []) {
|
|
534
|
-
const failure = findStagedRegistrationFailure(entry, registration,
|
|
536
|
+
const failure = findStagedRegistrationFailure(entry, registration, createdFileIndex);
|
|
535
537
|
if (failure === undefined)
|
|
536
538
|
continue;
|
|
537
539
|
issues.push({
|
|
@@ -541,62 +543,16 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
541
543
|
fingerprint: `staged-dependency-unused|${entry.filename}|${registration.file}|reg:${registration.line}|${registration.creates}|${registration.owner ?? ''}`,
|
|
542
544
|
message: `${entry.filename} declares a staged registration in ${registration.file} ` +
|
|
543
545
|
`("${registration.line}") for ${registration.creates}, but ${describeRegistrationFailure(failure)} ` +
|
|
544
|
-
'Remove the stale declaration with
|
|
546
|
+
'Remove the stale declaration with: ' +
|
|
547
|
+
`fireforge patch staged-dependency ${entry.filename} --remove --kind registration ` +
|
|
548
|
+
`--file ${registration.file} --line "${quoteRegistrationLine(registration.line)}" ` +
|
|
549
|
+
`--creates ${registration.creates}; or update it to match the patch and queue.`,
|
|
545
550
|
severity: 'warning',
|
|
546
551
|
});
|
|
547
552
|
}
|
|
548
553
|
}
|
|
549
554
|
return issues;
|
|
550
555
|
}
|
|
551
|
-
/**
|
|
552
|
-
* Validates one registration-kind declaration against the patch content
|
|
553
|
-
* and the queue, mirroring {@link findMatchingStagedDependency}: the
|
|
554
|
-
* declared line must be added by this patch, `creates` must exactly match
|
|
555
|
-
* a file a LATER-ordered patch creates (an earlier-only creator means the
|
|
556
|
-
* dependency is already satisfied and the declaration is stale), and
|
|
557
|
-
* `owner`, when set, must name one of those creating patches. Returns
|
|
558
|
-
* undefined when valid.
|
|
559
|
-
*/
|
|
560
|
-
function findStagedRegistrationFailure(entry, registration, newFileIndex) {
|
|
561
|
-
if (!isRegistrationLinePresent(entry, registration))
|
|
562
|
-
return { kind: 'line-absent' };
|
|
563
|
-
const creators = (newFileIndex.get(basename(registration.creates)) ?? []).filter((owner) => owner.fullPath === registration.creates && isLaterOwner(owner, entry));
|
|
564
|
-
if (creators.length === 0)
|
|
565
|
-
return { kind: 'creates-unresolved' };
|
|
566
|
-
if (registration.owner !== undefined &&
|
|
567
|
-
!creators.some((owner) => owner.filename === registration.owner)) {
|
|
568
|
-
return { kind: 'owner-mismatch', creators };
|
|
569
|
-
}
|
|
570
|
-
return undefined;
|
|
571
|
-
}
|
|
572
|
-
/** Message tail for each {@link StagedRegistrationFailure} shape. */
|
|
573
|
-
function describeRegistrationFailure(failure) {
|
|
574
|
-
switch (failure.kind) {
|
|
575
|
-
case 'line-absent':
|
|
576
|
-
return 'the patch does not add that line.';
|
|
577
|
-
case 'creates-unresolved':
|
|
578
|
-
return ('no later-ordered patch creates that file — the declaration is stale, ' +
|
|
579
|
-
'or the queue order no longer stages it.');
|
|
580
|
-
case 'owner-mismatch':
|
|
581
|
-
return `that file is created by ${failure.creators.map((o) => o.filename).join(', ')}, not the declared owner.`;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* True when the declared registration/packaging line appears (trimmed)
|
|
586
|
-
* among the lines the patch introduces in the declaring file — either as a
|
|
587
|
-
* newly-created file's content or as added lines to an existing file
|
|
588
|
-
* (where jar.mn / customElements.js / actor-registration edits land).
|
|
589
|
-
*/
|
|
590
|
-
function isRegistrationLinePresent(entry, registration) {
|
|
591
|
-
const declared = registration.line.trim();
|
|
592
|
-
if (declared.length === 0)
|
|
593
|
-
return false;
|
|
594
|
-
const introduced = [
|
|
595
|
-
entry.newFiles.get(registration.file),
|
|
596
|
-
entry.modifiedFileAdditions.get(registration.file),
|
|
597
|
-
];
|
|
598
|
-
return introduced.some((content) => content !== undefined && content.split('\n').some((line) => line.trim() === declared));
|
|
599
|
-
}
|
|
600
556
|
/**
|
|
601
557
|
* Cross-patch lint orchestrator. Runs every cross-patch rule against the
|
|
602
558
|
* provided context and returns combined issues.
|
|
@@ -614,6 +570,7 @@ export function lintPatchQueue(ctx) {
|
|
|
614
570
|
...lintPatchQueueDuplicateCreations(ctx),
|
|
615
571
|
...lintPatchQueueForwardImports(ctx),
|
|
616
572
|
...lintPatchQueueModuleRegistrations(ctx),
|
|
573
|
+
...lintPatchQueueForwardRegistrations(ctx),
|
|
617
574
|
...lintPatchQueueBinaryBodies(ctx),
|
|
618
575
|
];
|
|
619
576
|
}
|
|
@@ -73,11 +73,8 @@ function buildAddedLinesScanSource(rawCss, addedNumbers) {
|
|
|
73
73
|
.map((n) => maskedLines[n - 1] ?? '')
|
|
74
74
|
.join('\n');
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* when diff context is available. Pushes onto `issues`.
|
|
79
|
-
*/
|
|
80
|
-
function checkRawColorValues(file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues) {
|
|
76
|
+
function checkRawColorValues(input) {
|
|
77
|
+
const { file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues } = input;
|
|
81
78
|
// Check only introduced raw color values when diff context is available.
|
|
82
79
|
// Skip files on the raw-color allowlist (exact path or basename match) and
|
|
83
80
|
// auto-exempt files under `browser/branding/` — those are the fork's
|
|
@@ -207,7 +204,14 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
|
|
|
207
204
|
const rawCss = await readText(filePath);
|
|
208
205
|
// Strip block comments before scanning
|
|
209
206
|
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
210
|
-
checkRawColorValues(
|
|
207
|
+
checkRawColorValues({
|
|
208
|
+
file,
|
|
209
|
+
rawCss,
|
|
210
|
+
addedLinesByFile,
|
|
211
|
+
addedLineNumbersByFile,
|
|
212
|
+
config,
|
|
213
|
+
issues,
|
|
214
|
+
});
|
|
211
215
|
checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues);
|
|
212
216
|
}
|
|
213
217
|
return issues;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `file-too-large` thresholds and their rendered message.
|
|
3
|
+
*
|
|
4
|
+
* Its own module for two reasons: the config validator needs the DEFAULTS
|
|
5
|
+
* to validate an override triple against the merged result, and importing
|
|
6
|
+
* `patch-lint.ts` from `config-validate.ts` for a constant would draw an
|
|
7
|
+
* import edge (and a cycle) between the rule bodies and config parsing.
|
|
8
|
+
*
|
|
9
|
+
* On the vocabulary: the bands were `notice` / `warning` / `error` with the
|
|
10
|
+
* middle one described as a "soft limit". That description is false under
|
|
11
|
+
* the posture FireForge itself recommends for a release gate,
|
|
12
|
+
* `--max-warnings 0`, where a warning is a hard failure — so a file at 751
|
|
13
|
+
* lines failed a gate with a message calling 750 soft. The message below
|
|
14
|
+
* says what the band IS (a warning) and what a zero-warning gate does with
|
|
15
|
+
* it, and the thresholds are now tunable, so a project whose file-size
|
|
16
|
+
* policy differs has a dial instead of an argument.
|
|
17
|
+
*/
|
|
18
|
+
import type { PatchLintFileSizeThresholds } from '../types/config.js';
|
|
19
|
+
/** A fully resolved threshold triple. */
|
|
20
|
+
export interface ResolvedFileSizeTier {
|
|
21
|
+
notice: number;
|
|
22
|
+
warning: number;
|
|
23
|
+
error: number;
|
|
24
|
+
}
|
|
25
|
+
/** Built-in defaults, per file class. */
|
|
26
|
+
export declare const DEFAULT_FILE_SIZE_THRESHOLDS: {
|
|
27
|
+
general: ResolvedFileSizeTier;
|
|
28
|
+
test: ResolvedFileSizeTier;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Merges a configured override over the defaults for one file class.
|
|
32
|
+
*
|
|
33
|
+
* @param overrides - The `patchLint.fileSizeThresholds` block, if any
|
|
34
|
+
* @param isTest - Whether the file counts as a test file
|
|
35
|
+
* @returns The effective triple for this file
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveFileSizeThresholds(overrides: PatchLintFileSizeThresholds | undefined, isTest: boolean): ResolvedFileSizeTier;
|
|
38
|
+
/**
|
|
39
|
+
* Renders the `file-too-large` message for a given band.
|
|
40
|
+
*
|
|
41
|
+
* The `warning` and `notice` bands name the gate consequence explicitly:
|
|
42
|
+
* the number is advisory to patch-lint and blocking to a `--max-warnings 0`
|
|
43
|
+
* gate, and an operator deciding whether to decompose a file needs to know
|
|
44
|
+
* which of those they are looking at.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatFileTooLargeMessage(args: {
|
|
47
|
+
label: string;
|
|
48
|
+
lineCount: number;
|
|
49
|
+
thresholds: ResolvedFileSizeTier;
|
|
50
|
+
band: 'notice' | 'warning' | 'error';
|
|
51
|
+
verb: string;
|
|
52
|
+
}): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The `file-too-large` thresholds and their rendered message.
|
|
4
|
+
*
|
|
5
|
+
* Its own module for two reasons: the config validator needs the DEFAULTS
|
|
6
|
+
* to validate an override triple against the merged result, and importing
|
|
7
|
+
* `patch-lint.ts` from `config-validate.ts` for a constant would draw an
|
|
8
|
+
* import edge (and a cycle) between the rule bodies and config parsing.
|
|
9
|
+
*
|
|
10
|
+
* On the vocabulary: the bands were `notice` / `warning` / `error` with the
|
|
11
|
+
* middle one described as a "soft limit". That description is false under
|
|
12
|
+
* the posture FireForge itself recommends for a release gate,
|
|
13
|
+
* `--max-warnings 0`, where a warning is a hard failure — so a file at 751
|
|
14
|
+
* lines failed a gate with a message calling 750 soft. The message below
|
|
15
|
+
* says what the band IS (a warning) and what a zero-warning gate does with
|
|
16
|
+
* it, and the thresholds are now tunable, so a project whose file-size
|
|
17
|
+
* policy differs has a dial instead of an argument.
|
|
18
|
+
*/
|
|
19
|
+
/** Built-in defaults, per file class. */
|
|
20
|
+
export const DEFAULT_FILE_SIZE_THRESHOLDS = {
|
|
21
|
+
general: { notice: 500, warning: 750, error: 900 },
|
|
22
|
+
test: { notice: 1200, warning: 1400, error: 1600 },
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Merges a configured override over the defaults for one file class.
|
|
26
|
+
*
|
|
27
|
+
* @param overrides - The `patchLint.fileSizeThresholds` block, if any
|
|
28
|
+
* @param isTest - Whether the file counts as a test file
|
|
29
|
+
* @returns The effective triple for this file
|
|
30
|
+
*/
|
|
31
|
+
export function resolveFileSizeThresholds(overrides, isTest) {
|
|
32
|
+
const key = isTest ? 'test' : 'general';
|
|
33
|
+
const configured = overrides?.[key] ?? {};
|
|
34
|
+
return { ...DEFAULT_FILE_SIZE_THRESHOLDS[key], ...configured };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Renders the `file-too-large` message for a given band.
|
|
38
|
+
*
|
|
39
|
+
* The `warning` and `notice` bands name the gate consequence explicitly:
|
|
40
|
+
* the number is advisory to patch-lint and blocking to a `--max-warnings 0`
|
|
41
|
+
* gate, and an operator deciding whether to decompose a file needs to know
|
|
42
|
+
* which of those they are looking at.
|
|
43
|
+
*/
|
|
44
|
+
export function formatFileTooLargeMessage(args) {
|
|
45
|
+
const { label, lineCount, thresholds, band, verb } = args;
|
|
46
|
+
if (band === 'error') {
|
|
47
|
+
return `${label} has ${lineCount} lines (error threshold: ${thresholds.error}). Consider ${verb}.`;
|
|
48
|
+
}
|
|
49
|
+
return (`${label} has ${lineCount} lines (warning threshold: ${thresholds.warning}, ` +
|
|
50
|
+
`error threshold: ${thresholds.error}). Consider ${verb}. ` +
|
|
51
|
+
`Reported as a ${band}, which a gate running --max-warnings 0 treats as a failure; ` +
|
|
52
|
+
`tune "patchLint.fileSizeThresholds" if these limits are wrong for this project.`);
|
|
53
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
2
|
+
import type { PatchQueueForwardRegistrationEntry, PatchQueueView } from './patch-lint-queue-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One `support-files` entry, with the line the patch actually adds it on.
|
|
5
|
+
*
|
|
6
|
+
* The entry alone is not enough for the discharge command the rule prints:
|
|
7
|
+
* `--line` is compared, whitespace-trimmed, against the lines the patch
|
|
8
|
+
* ADDS, and a synthesised `support-files = ["<entry>"]` only ever matches a
|
|
9
|
+
* manifest whose array is single-line and single-entry. Carrying the source
|
|
10
|
+
* line keeps the remedy and the validation talking about the same text.
|
|
11
|
+
*/
|
|
12
|
+
export interface SupportFileEntry {
|
|
13
|
+
/** The entry as written inside the manifest (e.g. `fixtures/*.sqlite`). */
|
|
14
|
+
entry: string;
|
|
15
|
+
/** The first added line, whitespace-trimmed, that spells the entry out. */
|
|
16
|
+
line: string;
|
|
17
|
+
/**
|
|
18
|
+
* How many distinct added lines spell it out. Above one, the printed
|
|
19
|
+
* command quotes the first and says so: one declaration covers the file
|
|
20
|
+
* either way, because the declared check keys on file + creates.
|
|
21
|
+
*/
|
|
22
|
+
occurrences: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the `support-files` entries declared in `content`, each paired
|
|
26
|
+
* with the manifest line it is written on.
|
|
27
|
+
*
|
|
28
|
+
* Handles the TOML array on one line and spread over several, and the
|
|
29
|
+
* whitespace-separated `.ini` spelling. Anything else — a computed value, a
|
|
30
|
+
* shape this does not recognise — yields nothing, because a rule that
|
|
31
|
+
* guesses at a manifest it cannot parse refuses correct queues.
|
|
32
|
+
*
|
|
33
|
+
* Quoted tokens are matched per physical line rather than over the whole
|
|
34
|
+
* accumulated array body, so a multi-line array attributes each element to
|
|
35
|
+
* its own line. Entries are deduplicated: a manifest that lists the same
|
|
36
|
+
* file under two per-test sections needs one declaration, not two.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractSupportFileEntries(content: string): SupportFileEntry[];
|
|
39
|
+
/**
|
|
40
|
+
* Resolves one `support-files` entry, relative to its manifest's directory,
|
|
41
|
+
* into a matcher over engine-relative paths.
|
|
42
|
+
*
|
|
43
|
+
* Only a `*` inside a single path segment is expanded — the shape the
|
|
44
|
+
* downstream report uses (`fixtures/*.sqlite`). `**`, `!` exclusions and
|
|
45
|
+
* absolute `/`-rooted entries return undefined: they either cross directory
|
|
46
|
+
* boundaries or subtract, and a matcher that guessed at them would attribute
|
|
47
|
+
* a creation to the wrong manifest.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildSupportFileMatcher(manifestPath: string, entry: string): ((candidate: string) => boolean) | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Flags every `support-files` registration a patch introduces that names a
|
|
52
|
+
* file only a later-ordered patch creates, unless the staged dependency is
|
|
53
|
+
* declared.
|
|
54
|
+
*
|
|
55
|
+
* @param ctx - Projected queue in application order
|
|
56
|
+
* @returns One issue per undeclared forward registration
|
|
57
|
+
*/
|
|
58
|
+
export declare function lintPatchQueueForwardRegistrations(ctx: PatchQueueView<PatchQueueForwardRegistrationEntry>): PatchLintIssue[];
|
|
@@ -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
|
+
}
|