@hominis/fireforge 0.39.0 → 0.41.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 +100 -0
- package/README.md +35 -5
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +27 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +27 -9
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +30 -10
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +73 -51
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint-report.d.ts +21 -0
- package/dist/src/commands/lint-report.js +62 -0
- package/dist/src/commands/lint.js +26 -22
- package/dist/src/commands/manifest.js +3 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +152 -51
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +112 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +105 -0
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +11 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +123 -289
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +29 -23
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +48 -0
- package/dist/src/commands/status-check.js +106 -0
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +190 -170
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +59 -7
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +19 -0
- package/dist/src/commands/tree.js +320 -0
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +45 -30
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +49 -14
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.d.ts +2 -2
- package/dist/src/core/patch-export.js +4 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +42 -2
- package/dist/src/core/patch-lint-checkjs.js +132 -8
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-css.js +64 -16
- package/dist/src/core/patch-lint-diff.d.ts +8 -0
- package/dist/src/core/patch-lint-diff.js +31 -0
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +2 -1
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +31 -0
- package/dist/src/core/patch-lint.js +114 -8
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +24 -16
- package/dist/src/core/smoke-patterns.js +26 -14
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +22 -1
- package/dist/src/core/status-classify.js +112 -70
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/token-category.d.ts +40 -0
- package/dist/src/core/token-category.js +264 -0
- package/dist/src/core/token-manager.d.ts +7 -0
- package/dist/src/core/token-manager.js +48 -196
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-cow.d.ts +18 -0
- package/dist/src/core/tree-cow.js +71 -0
- package/dist/src/core/tree-guard.d.ts +35 -0
- package/dist/src/core/tree-guard.js +179 -0
- package/dist/src/core/tree-store.d.ts +129 -0
- package/dist/src/core/tree-store.js +452 -0
- package/dist/src/core/typecheck-shim.d.ts +14 -0
- package/dist/src/core/typecheck-shim.js +41 -2
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
- package/dist/src/core/xpcshell-profile-dir.js +45 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/git.d.ts +8 -0
- package/dist/src/errors/git.js +17 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types/commands/options.d.ts +119 -3
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/config.d.ts +19 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +33 -2
- package/dist/src/utils/validation.js +57 -2
- package/package.json +13 -13
|
@@ -61,9 +61,17 @@ export interface GroupedCheckJsResult {
|
|
|
61
61
|
* the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
|
|
62
62
|
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
63
63
|
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
64
|
+
* @param builtinShimSuffix - Optional shim text appended AFTER the consumer shim
|
|
65
|
+
* @param rootScope - When set, only these repo-relative files become program
|
|
66
|
+
* ROOTS; resolution (and the host allowlist) still spans all of
|
|
67
|
+
* `resolutionOwned`, so a subset root's cross-patch imports type-check
|
|
68
|
+
* against the real owning sources while unrelated owned files are never
|
|
69
|
+
* parsed. `.mjs` files are module-scoped, so a root's diagnostics are
|
|
70
|
+
* identical whether other owned files are roots or mere resolution
|
|
71
|
+
* targets (pinned by the rootScope parity test — FORGE J1c).
|
|
64
72
|
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
65
73
|
*/
|
|
66
|
-
export declare function runCheckJsGrouped(repoDir: string, resolutionOwned: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode): Promise<GroupedCheckJsResult>;
|
|
74
|
+
export declare function runCheckJsGrouped(repoDir: string, resolutionOwned: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode, builtinShimSuffix?: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
|
|
67
75
|
/**
|
|
68
76
|
* Flattens a {@link runCheckJsGrouped} run into a single issue list. When
|
|
69
77
|
* `reportScope` is supplied, only diagnostics from files in that set are
|
|
@@ -93,6 +101,24 @@ export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>
|
|
|
93
101
|
* not surface other patches' diagnostics)
|
|
94
102
|
*/
|
|
95
103
|
export declare function invokePatchLintCheckJs(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, reportScope?: ReadonlySet<string>): Promise<PatchLintIssue[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Runs the checkJs pass over patch-owned test `.js` files (FORGE G5,
|
|
106
|
+
* `patchLint.checkJsTestFiles`). Each test file gets its OWN small
|
|
107
|
+
* program — mochitest scripts share top-level scope only with their
|
|
108
|
+
* directory's `head*.js` helpers, so one multi-script program would emit
|
|
109
|
+
* false cross-file redeclaration errors — with roots = the test file plus
|
|
110
|
+
* any same-directory patch-owned `head*.js`, checked against the built-in
|
|
111
|
+
* Firefox shim + {@link TEST_HARNESS_SHIM} + the optional consumer
|
|
112
|
+
* `checkJsTestShim`. Only the target file's diagnostics are reported per
|
|
113
|
+
* program (head helpers report from their own program), and run-level
|
|
114
|
+
* errors are de-duplicated across programs.
|
|
115
|
+
*
|
|
116
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
117
|
+
* @param testFiles - Patch-owned test-script paths (repo-relative)
|
|
118
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
119
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
120
|
+
*/
|
|
121
|
+
export declare function runCheckJsTestFilesGrouped(repoDir: string, testFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
|
|
96
122
|
/**
|
|
97
123
|
* Grouped variant of {@link invokePatchLintCheckJs}: builds one queue-wide
|
|
98
124
|
* checkJs program over `patchOwnedFiles` and returns its findings grouped by
|
|
@@ -104,5 +130,19 @@ export declare function invokePatchLintCheckJs(repoDir: string, patchOwnedFiles:
|
|
|
104
130
|
* @param patchOwnedFiles - Every patch-owned `.sys.mjs` in the queue
|
|
105
131
|
* @param patchLint - The resolved `patchLint` config block
|
|
106
132
|
* @param projectRoot - FireForge project root for shim resolution
|
|
133
|
+
* @param rootScope - Optional subset of files to use as program roots
|
|
134
|
+
* (`--patches`); resolution still spans the whole queue (FORGE J1c)
|
|
135
|
+
*/
|
|
136
|
+
export declare function invokePatchLintCheckJsGrouped(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
|
|
137
|
+
/**
|
|
138
|
+
* Cheap probe reproducing the only run-level ("global") checkJs findings
|
|
139
|
+
* the build path can produce — a missing `typescript` package and an
|
|
140
|
+
* unreadable consumer shim (`checkJsExtraShim`, and `checkJsTestShim`
|
|
141
|
+
* when `checkJsTestFiles` is on). The built program itself never
|
|
142
|
+
* contributes globals (see {@link runCheckJsGrouped}), so a warm
|
|
143
|
+
* all-cache-hit run can satisfy the "warm never reports less than cold"
|
|
144
|
+
* invariant (FORGE F5) with this probe instead of building the whole
|
|
145
|
+
* TypeScript program. Issue objects are byte-identical to the build
|
|
146
|
+
* path's, deduplicated by message like {@link runCheckJsTestFilesGrouped}.
|
|
107
147
|
*/
|
|
108
|
-
export declare function
|
|
148
|
+
export declare function probeCheckJsGlobalIssues(patchLint: PatchLintConfig, projectRoot: string): Promise<PatchLintIssue[]>;
|
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
* while reporting only the patch under export.
|
|
27
27
|
*/
|
|
28
28
|
import { basename, resolve } from 'node:path';
|
|
29
|
+
import { toError } from '../utils/errors.js';
|
|
29
30
|
import { pathExists } from '../utils/fs.js';
|
|
30
31
|
import { verbose } from '../utils/logger.js';
|
|
31
|
-
import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
|
|
32
|
+
import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, TEST_HARNESS_SHIM, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
// Public API
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
@@ -146,9 +147,17 @@ function createPathsResolver(ts, paths, baseDir, fileExists, onResolved) {
|
|
|
146
147
|
* the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
|
|
147
148
|
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
148
149
|
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
150
|
+
* @param builtinShimSuffix - Optional shim text appended AFTER the consumer shim
|
|
151
|
+
* @param rootScope - When set, only these repo-relative files become program
|
|
152
|
+
* ROOTS; resolution (and the host allowlist) still spans all of
|
|
153
|
+
* `resolutionOwned`, so a subset root's cross-patch imports type-check
|
|
154
|
+
* against the real owning sources while unrelated owned files are never
|
|
155
|
+
* parsed. `.mjs` files are module-scoped, so a root's diagnostics are
|
|
156
|
+
* identical whether other owned files are roots or mere resolution
|
|
157
|
+
* targets (pinned by the rootScope parity test — FORGE J1c).
|
|
149
158
|
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
150
159
|
*/
|
|
151
|
-
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode) {
|
|
160
|
+
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode, builtinShimSuffix, rootScope) {
|
|
152
161
|
const empty = { byFile: new Map(), global: [] };
|
|
153
162
|
if (resolutionOwned.size === 0)
|
|
154
163
|
return empty;
|
|
@@ -171,14 +180,18 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
171
180
|
],
|
|
172
181
|
};
|
|
173
182
|
}
|
|
174
|
-
// Resolve absolute paths for root files, filtering to files that exist
|
|
183
|
+
// Resolve absolute paths for root files, filtering to files that exist.
|
|
184
|
+
// Under a rootScope, non-scoped owned files stay resolvable (host
|
|
185
|
+
// allowlist + relByAbsolute) but are not program roots — they are only
|
|
186
|
+
// parsed if a scoped root's import closure reaches them.
|
|
175
187
|
const rootFiles = [];
|
|
176
188
|
const ownedAbsolute = new Set();
|
|
177
189
|
const relByAbsolute = new Map();
|
|
178
190
|
for (const rel of resolutionOwned) {
|
|
179
191
|
const abs = resolve(repoDir, rel);
|
|
180
192
|
if (await pathExists(abs)) {
|
|
181
|
-
|
|
193
|
+
if (rootScope === undefined || rootScope.has(rel))
|
|
194
|
+
rootFiles.push(abs);
|
|
182
195
|
ownedAbsolute.add(abs);
|
|
183
196
|
relByAbsolute.set(abs, rel);
|
|
184
197
|
}
|
|
@@ -193,7 +206,11 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
193
206
|
let shimSource;
|
|
194
207
|
try {
|
|
195
208
|
const composed = await composeShimSource(projectRoot ?? repoDir, extraShimPath);
|
|
196
|
-
|
|
209
|
+
// The suffix (e.g. the loose test-harness baseline, FORGE G5) goes
|
|
210
|
+
// AFTER the consumer's extra shim: TypeScript resolves conflicting
|
|
211
|
+
// `declare var` redeclarations to the FIRST declaration, so a typed
|
|
212
|
+
// consumer declaration must precede the loose fallback to win.
|
|
213
|
+
shimSource = builtinShimSuffix ? `${composed.source}\n${builtinShimSuffix}` : composed.source;
|
|
197
214
|
if (composed.extraShimAppended) {
|
|
198
215
|
verbose(`checkJs: extra shim ${extraShimPath ?? ''} appended to Firefox globals shim`);
|
|
199
216
|
}
|
|
@@ -205,7 +222,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
205
222
|
{
|
|
206
223
|
file: extraShimPath ?? '(checkJs)',
|
|
207
224
|
check: 'checkjs-type-error',
|
|
208
|
-
message: err
|
|
225
|
+
message: toError(err).message,
|
|
209
226
|
severity: 'error',
|
|
210
227
|
},
|
|
211
228
|
],
|
|
@@ -402,6 +419,60 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
402
419
|
export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint, projectRoot, reportScope) {
|
|
403
420
|
return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), reportScope);
|
|
404
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Runs the checkJs pass over patch-owned test `.js` files (FORGE G5,
|
|
424
|
+
* `patchLint.checkJsTestFiles`). Each test file gets its OWN small
|
|
425
|
+
* program — mochitest scripts share top-level scope only with their
|
|
426
|
+
* directory's `head*.js` helpers, so one multi-script program would emit
|
|
427
|
+
* false cross-file redeclaration errors — with roots = the test file plus
|
|
428
|
+
* any same-directory patch-owned `head*.js`, checked against the built-in
|
|
429
|
+
* Firefox shim + {@link TEST_HARNESS_SHIM} + the optional consumer
|
|
430
|
+
* `checkJsTestShim`. Only the target file's diagnostics are reported per
|
|
431
|
+
* program (head helpers report from their own program), and run-level
|
|
432
|
+
* errors are de-duplicated across programs.
|
|
433
|
+
*
|
|
434
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
435
|
+
* @param testFiles - Patch-owned test-script paths (repo-relative)
|
|
436
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
437
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
438
|
+
*/
|
|
439
|
+
export async function runCheckJsTestFilesGrouped(repoDir, testFiles, patchLint, projectRoot, rootScope) {
|
|
440
|
+
const merged = { byFile: new Map(), global: [] };
|
|
441
|
+
if (testFiles.size === 0)
|
|
442
|
+
return merged;
|
|
443
|
+
const mode = modeFromPatchLintConfig(patchLint);
|
|
444
|
+
const seenGlobal = new Set();
|
|
445
|
+
const files = [...testFiles].sort((a, b) => a.localeCompare(b));
|
|
446
|
+
for (const file of files) {
|
|
447
|
+
// Under a rootScope only the scoped files get their own program, but
|
|
448
|
+
// head.js helper discovery still spans the full owned set so a scoped
|
|
449
|
+
// test keeps its cross-patch harness globals (FORGE J1c).
|
|
450
|
+
if (rootScope !== undefined && !rootScope.has(file))
|
|
451
|
+
continue;
|
|
452
|
+
const dir = file.slice(0, file.lastIndexOf('/') + 1);
|
|
453
|
+
const roots = new Set([file]);
|
|
454
|
+
for (const candidate of files) {
|
|
455
|
+
if (candidate === file)
|
|
456
|
+
continue;
|
|
457
|
+
const base = candidate.split('/').pop() ?? '';
|
|
458
|
+
if (candidate.startsWith(dir) && /^head(?:_.*)?\.js$/.test(base)) {
|
|
459
|
+
roots.add(candidate);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const result = await runCheckJsGrouped(repoDir, roots, patchLint.checkJsTestShim, projectRoot, mode, TEST_HARNESS_SHIM);
|
|
463
|
+
const own = result.byFile.get(file);
|
|
464
|
+
if (own && own.length > 0) {
|
|
465
|
+
merged.byFile.set(file, [...(merged.byFile.get(file) ?? []), ...own]);
|
|
466
|
+
}
|
|
467
|
+
for (const globalIssue of result.global) {
|
|
468
|
+
if (seenGlobal.has(globalIssue.message))
|
|
469
|
+
continue;
|
|
470
|
+
seenGlobal.add(globalIssue.message);
|
|
471
|
+
merged.global.push(globalIssue);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return merged;
|
|
475
|
+
}
|
|
405
476
|
/** Derives the {@link CheckJsMode} from a `patchLint` config block. */
|
|
406
477
|
function modeFromPatchLintConfig(patchLint) {
|
|
407
478
|
const strict = patchLint.checkJsStrict === true;
|
|
@@ -426,7 +497,60 @@ function modeFromPatchLintConfig(patchLint) {
|
|
|
426
497
|
* @param patchOwnedFiles - Every patch-owned `.sys.mjs` in the queue
|
|
427
498
|
* @param patchLint - The resolved `patchLint` config block
|
|
428
499
|
* @param projectRoot - FireForge project root for shim resolution
|
|
500
|
+
* @param rootScope - Optional subset of files to use as program roots
|
|
501
|
+
* (`--patches`); resolution still spans the whole queue (FORGE J1c)
|
|
502
|
+
*/
|
|
503
|
+
export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, patchLint, projectRoot, rootScope) {
|
|
504
|
+
return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), undefined, rootScope);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Cheap probe reproducing the only run-level ("global") checkJs findings
|
|
508
|
+
* the build path can produce — a missing `typescript` package and an
|
|
509
|
+
* unreadable consumer shim (`checkJsExtraShim`, and `checkJsTestShim`
|
|
510
|
+
* when `checkJsTestFiles` is on). The built program itself never
|
|
511
|
+
* contributes globals (see {@link runCheckJsGrouped}), so a warm
|
|
512
|
+
* all-cache-hit run can satisfy the "warm never reports less than cold"
|
|
513
|
+
* invariant (FORGE F5) with this probe instead of building the whole
|
|
514
|
+
* TypeScript program. Issue objects are byte-identical to the build
|
|
515
|
+
* path's, deduplicated by message like {@link runCheckJsTestFilesGrouped}.
|
|
429
516
|
*/
|
|
430
|
-
export async function
|
|
431
|
-
|
|
517
|
+
export async function probeCheckJsGlobalIssues(patchLint, projectRoot) {
|
|
518
|
+
try {
|
|
519
|
+
await import('typescript');
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
return [
|
|
523
|
+
{
|
|
524
|
+
file: '(checkJs)',
|
|
525
|
+
check: 'checkjs-type-error',
|
|
526
|
+
message: 'patchLint.checkJs is enabled but the "typescript" package is not installed. ' +
|
|
527
|
+
'Run "npm install typescript" to enable type checking.',
|
|
528
|
+
severity: 'error',
|
|
529
|
+
},
|
|
530
|
+
];
|
|
531
|
+
}
|
|
532
|
+
const issues = [];
|
|
533
|
+
const seen = new Set();
|
|
534
|
+
const probeShim = async (shimPath) => {
|
|
535
|
+
try {
|
|
536
|
+
await composeShimSource(projectRoot, shimPath);
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
const message = toError(err).message;
|
|
540
|
+
if (seen.has(message))
|
|
541
|
+
return;
|
|
542
|
+
seen.add(message);
|
|
543
|
+
issues.push({
|
|
544
|
+
file: shimPath ?? '(checkJs)',
|
|
545
|
+
check: 'checkjs-type-error',
|
|
546
|
+
message,
|
|
547
|
+
severity: 'error',
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
await probeShim(patchLint.checkJsExtraShim);
|
|
552
|
+
if (patchLint.checkJsTestFiles === true) {
|
|
553
|
+
await probeShim(patchLint.checkJsTestShim);
|
|
554
|
+
}
|
|
555
|
+
return issues;
|
|
432
556
|
}
|
|
@@ -40,6 +40,15 @@ export function validateChromeScriptJsDoc(source, options) {
|
|
|
40
40
|
ast = parseScript(source, comments);
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
+
// Deliberate carve-out, unlike the `.sys.mjs` walker in
|
|
44
|
+
// `patch-lint-jsdoc.ts` (which reports a parse failure as an issue).
|
|
45
|
+
// `parseScript` rejects `import`/`export`, so a chrome subscript that was
|
|
46
|
+
// misclassified as a `.js` file — or that mistakenly uses module syntax —
|
|
47
|
+
// would emit a pseudo-issue for every rule here. The orchestrator already
|
|
48
|
+
// runs the export walker on `.sys.mjs` separately, so silently declining
|
|
49
|
+
// to lint an unparseable script is the correct degradation for this file
|
|
50
|
+
// and only this file. Pinned by "returns no issues when the source uses
|
|
51
|
+
// module-only syntax" in patch-lint-chrome-jsdoc.test.ts.
|
|
43
52
|
return [];
|
|
44
53
|
}
|
|
45
54
|
const issues = [];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New-file creators map for the patch queue, split out of
|
|
3
|
+
* `patch-lint-cross.ts` (at the per-file line budget). Re-exported from
|
|
4
|
+
* there so callers keep importing from `patch-lint.ts` unchanged.
|
|
5
|
+
*
|
|
6
|
+
* Memoised per context object (FORGE J1): ownership resolution and
|
|
7
|
+
* cache-key fingerprints call this 2-3× per linted patch, and each call
|
|
8
|
+
* used to re-run `detectNewFilesInDiff` over EVERY queue entry's diff.
|
|
9
|
+
* Keyed weakly so a discarded context frees its map; a caller that
|
|
10
|
+
* mutates a context entry in place must call
|
|
11
|
+
* {@link invalidateNewFileCreatorsCache}.
|
|
12
|
+
*/
|
|
13
|
+
/** The slice of a queue context the creators map is derived from. */
|
|
14
|
+
interface CreatorsContext {
|
|
15
|
+
entries: {
|
|
16
|
+
filename: string;
|
|
17
|
+
diff: string;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
/** Drops the memoised creators map after an in-place context entry update. */
|
|
21
|
+
export declare function invalidateNewFileCreatorsCache(ctx: CreatorsContext): void;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the raw `path → patches[]` map of files created in `new file
|
|
24
|
+
* mode` by at least one patch in the queue. Paths created by only one
|
|
25
|
+
* patch are also included so callers can distinguish "no creator" from
|
|
26
|
+
* "exactly one creator" without re-scanning the diffs. The returned map
|
|
27
|
+
* is memoised and shared — treat it as read-only.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectNewFileCreatorsByPath(ctx: CreatorsContext): Map<string, string[]>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* New-file creators map for the patch queue, split out of
|
|
4
|
+
* `patch-lint-cross.ts` (at the per-file line budget). Re-exported from
|
|
5
|
+
* there so callers keep importing from `patch-lint.ts` unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Memoised per context object (FORGE J1): ownership resolution and
|
|
8
|
+
* cache-key fingerprints call this 2-3× per linted patch, and each call
|
|
9
|
+
* used to re-run `detectNewFilesInDiff` over EVERY queue entry's diff.
|
|
10
|
+
* Keyed weakly so a discarded context frees its map; a caller that
|
|
11
|
+
* mutates a context entry in place must call
|
|
12
|
+
* {@link invalidateNewFileCreatorsCache}.
|
|
13
|
+
*/
|
|
14
|
+
import { detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
15
|
+
const newFileCreatorsMemo = new WeakMap();
|
|
16
|
+
/** Drops the memoised creators map after an in-place context entry update. */
|
|
17
|
+
export function invalidateNewFileCreatorsCache(ctx) {
|
|
18
|
+
newFileCreatorsMemo.delete(ctx);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the raw `path → patches[]` map of files created in `new file
|
|
22
|
+
* mode` by at least one patch in the queue. Paths created by only one
|
|
23
|
+
* patch are also included so callers can distinguish "no creator" from
|
|
24
|
+
* "exactly one creator" without re-scanning the diffs. The returned map
|
|
25
|
+
* is memoised and shared — treat it as read-only.
|
|
26
|
+
*/
|
|
27
|
+
export function collectNewFileCreatorsByPath(ctx) {
|
|
28
|
+
const memoised = newFileCreatorsMemo.get(ctx);
|
|
29
|
+
if (memoised)
|
|
30
|
+
return memoised;
|
|
31
|
+
const creators = new Map();
|
|
32
|
+
for (const entry of ctx.entries) {
|
|
33
|
+
const newFiles = detectNewFilesInDiff(entry.diff);
|
|
34
|
+
for (const file of newFiles) {
|
|
35
|
+
let owners = creators.get(file);
|
|
36
|
+
if (!owners) {
|
|
37
|
+
owners = [];
|
|
38
|
+
creators.set(file, owners);
|
|
39
|
+
}
|
|
40
|
+
owners.push(entry.filename);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
newFileCreatorsMemo.set(ctx, creators);
|
|
44
|
+
return creators;
|
|
45
|
+
}
|
|
@@ -79,23 +79,7 @@ export interface PatchQueueContext {
|
|
|
79
79
|
* @param patchesDir - Path to the patches directory
|
|
80
80
|
*/
|
|
81
81
|
export declare function buildPatchQueueContext(patchesDir: string, config?: FireForgeConfig): Promise<PatchQueueContext>;
|
|
82
|
-
|
|
83
|
-
* Returns the raw `path → patches[]` map of files created in `new file
|
|
84
|
-
* mode` by at least one patch in the queue. Paths created by only one
|
|
85
|
-
* patch are also included so callers can distinguish "no creator" from
|
|
86
|
-
* "exactly one creator" without re-scanning the diffs.
|
|
87
|
-
*
|
|
88
|
-
* Split out from {@link lintPatchQueueDuplicateCreations} so
|
|
89
|
-
* `status --ownership` (and any future caller that wants ownership
|
|
90
|
-
* without a rendered PatchLintIssue) can consume the same structured
|
|
91
|
-
* data the rule itself relies on. Previously status had to parse the
|
|
92
|
-
* rule's human-readable message to recover the patch list, which was
|
|
93
|
-
* both fragile and made the lint message format part of an implicit
|
|
94
|
-
* contract.
|
|
95
|
-
*
|
|
96
|
-
* @param ctx - Pre-built queue context
|
|
97
|
-
*/
|
|
98
|
-
export declare function collectNewFileCreatorsByPath(ctx: PatchQueueContext): Map<string, string[]>;
|
|
82
|
+
export { collectNewFileCreatorsByPath, invalidateNewFileCreatorsCache, } from './patch-lint-creators.js';
|
|
99
83
|
/**
|
|
100
84
|
* Cross-patch lint rule: the same path is newly created (`--- /dev/null →
|
|
101
85
|
* +++ b/path`) by more than one patch. This is the failure mode that
|
|
@@ -16,7 +16,9 @@ import { readText } from '../utils/fs.js';
|
|
|
16
16
|
import { verbose } from '../utils/logger.js';
|
|
17
17
|
import { stripJsComments } from '../utils/regex.js';
|
|
18
18
|
import { discoverPatches } from './patch-files.js';
|
|
19
|
+
import { collectNewFileCreatorsByPath } from './patch-lint-creators.js';
|
|
19
20
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
21
|
+
import { lintPatchQueueModuleRegistrations } from './patch-lint-module-registration.js';
|
|
20
22
|
import { loadPatchesManifest } from './patch-manifest-io.js';
|
|
21
23
|
import { categoryRangeForOrder, categoryRangeLabel } from './patch-policy.js';
|
|
22
24
|
import { extractNewFileContent } from './patch-transform.js';
|
|
@@ -81,37 +83,11 @@ export async function buildPatchQueueContext(patchesDir, config) {
|
|
|
81
83
|
entries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
82
84
|
return { entries, ...(config?.patchPolicy ? { patchPolicy: config.patchPolicy } : {}) };
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
* Split out from {@link lintPatchQueueDuplicateCreations} so
|
|
91
|
-
* `status --ownership` (and any future caller that wants ownership
|
|
92
|
-
* without a rendered PatchLintIssue) can consume the same structured
|
|
93
|
-
* data the rule itself relies on. Previously status had to parse the
|
|
94
|
-
* rule's human-readable message to recover the patch list, which was
|
|
95
|
-
* both fragile and made the lint message format part of an implicit
|
|
96
|
-
* contract.
|
|
97
|
-
*
|
|
98
|
-
* @param ctx - Pre-built queue context
|
|
99
|
-
*/
|
|
100
|
-
export function collectNewFileCreatorsByPath(ctx) {
|
|
101
|
-
const creators = new Map();
|
|
102
|
-
for (const entry of ctx.entries) {
|
|
103
|
-
const newFiles = detectNewFilesInDiff(entry.diff);
|
|
104
|
-
for (const file of newFiles) {
|
|
105
|
-
let owners = creators.get(file);
|
|
106
|
-
if (!owners) {
|
|
107
|
-
owners = [];
|
|
108
|
-
creators.set(file, owners);
|
|
109
|
-
}
|
|
110
|
-
owners.push(entry.filename);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return creators;
|
|
114
|
-
}
|
|
86
|
+
// The creators map (split out of `lintPatchQueueDuplicateCreations` so
|
|
87
|
+
// `status --ownership` can consume the structured data) lives in
|
|
88
|
+
// `patch-lint-creators.ts` with its per-context memoisation (FORGE J1);
|
|
89
|
+
// re-exported here so callers keep importing from `patch-lint.ts`.
|
|
90
|
+
export { collectNewFileCreatorsByPath, invalidateNewFileCreatorsCache, } from './patch-lint-creators.js';
|
|
115
91
|
/**
|
|
116
92
|
* Cross-patch lint rule: the same path is newly created (`--- /dev/null →
|
|
117
93
|
* +++ b/path`) by more than one patch. This is the failure mode that
|
|
@@ -131,6 +107,7 @@ export function lintPatchQueueDuplicateCreations(ctx) {
|
|
|
131
107
|
issues.push({
|
|
132
108
|
file,
|
|
133
109
|
check: 'duplicate-new-file-creation',
|
|
110
|
+
patches: [...owners].sort((a, b) => a.localeCompare(b)),
|
|
134
111
|
fingerprint: `duplicate-new-file-creation|${file}|${[...owners].sort((a, b) => a.localeCompare(b)).join(',')}`,
|
|
135
112
|
message: `File "${file}" is created (new file mode) by multiple patches: ${owners.join(', ')}. ` +
|
|
136
113
|
'Only one patch may create a given path. Use "patch delete" or ' +
|
|
@@ -519,6 +496,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
519
496
|
issues.push({
|
|
520
497
|
file: sitePath,
|
|
521
498
|
check: 'forward-import',
|
|
499
|
+
patches: [entry.filename],
|
|
522
500
|
fingerprint: `forward-import|${sitePath}|${cleaned}|${fingerprintOwners}`,
|
|
523
501
|
message: `${sitePath} in ${entry.filename} imports "${specifier}", ` +
|
|
524
502
|
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
@@ -537,6 +515,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
537
515
|
issues.push({
|
|
538
516
|
file: dependency.file,
|
|
539
517
|
check: 'staged-dependency-unused',
|
|
518
|
+
patches: [entry.filename],
|
|
540
519
|
fingerprint: `staged-dependency-unused|${entry.filename}|${dependency.file}|${dependency.specifier}|${dependency.creates}|${dependency.owner ?? ''}`,
|
|
541
520
|
message: `${entry.filename} declares a staged forward import from ${dependency.file} ` +
|
|
542
521
|
`to "${dependency.specifier}" / ${dependency.creates}, but that exact forward dependency was not found. ` +
|
|
@@ -559,6 +538,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
559
538
|
issues.push({
|
|
560
539
|
file: registration.file,
|
|
561
540
|
check: 'staged-dependency-unused',
|
|
541
|
+
patches: [entry.filename],
|
|
562
542
|
fingerprint: `staged-dependency-unused|${entry.filename}|${registration.file}|reg:${registration.line}|${registration.creates}|${registration.owner ?? ''}`,
|
|
563
543
|
message: `${entry.filename} declares a staged registration in ${registration.file} ` +
|
|
564
544
|
`("${registration.line}") for ${registration.creates}, but ${describeRegistrationFailure(failure)} ` +
|
|
@@ -631,5 +611,9 @@ function isRegistrationLinePresent(entry, registration) {
|
|
|
631
611
|
* manually for projected/hypothetical states)
|
|
632
612
|
*/
|
|
633
613
|
export function lintPatchQueue(ctx) {
|
|
634
|
-
return [
|
|
614
|
+
return [
|
|
615
|
+
...lintPatchQueueDuplicateCreations(ctx),
|
|
616
|
+
...lintPatchQueueForwardImports(ctx),
|
|
617
|
+
...lintPatchQueueModuleRegistrations(ctx),
|
|
618
|
+
];
|
|
635
619
|
}
|
|
@@ -15,7 +15,7 @@ import { pathExists, readText } from '../utils/fs.js';
|
|
|
15
15
|
import { verbose } from '../utils/logger.js';
|
|
16
16
|
import { hasRawCssColors } from '../utils/regex.js';
|
|
17
17
|
import { loadFurnaceConfig } from './furnace-config.js';
|
|
18
|
-
import { extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
18
|
+
import { extractAddedLineNumbersPerFile, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
19
19
|
/**
|
|
20
20
|
* Loads the furnace token-prefix lint inputs gracefully — returns
|
|
21
21
|
* undefined (skipping the token-prefix check) when furnace.json cannot
|
|
@@ -38,11 +38,48 @@ async function loadCssTokenContext(repoDir) {
|
|
|
38
38
|
}
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Masks CSS block comments with spaces, preserving newlines so line
|
|
43
|
+
* numbers stay stable. An unclosed trailing `/*` is masked to EOF —
|
|
44
|
+
* matching how a CSS parser treats it (FORGE G4).
|
|
45
|
+
*/
|
|
46
|
+
function maskCssComments(source) {
|
|
47
|
+
const masked = source.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '));
|
|
48
|
+
const openerIndex = masked.indexOf('/*');
|
|
49
|
+
if (openerIndex === -1)
|
|
50
|
+
return masked;
|
|
51
|
+
return (masked.slice(0, openerIndex) +
|
|
52
|
+
masked
|
|
53
|
+
.slice(openerIndex)
|
|
54
|
+
.split('\n')
|
|
55
|
+
.map((line) => ' '.repeat(line.length))
|
|
56
|
+
.join('\n'));
|
|
57
|
+
}
|
|
58
|
+
const RAW_COLOR_IGNORE_MARKER = 'fireforge-ignore: raw-color-value';
|
|
59
|
+
/**
|
|
60
|
+
* Builds the scan source for the diff branch with full-file comment
|
|
61
|
+
* awareness: comments are masked across the whole post-patch file so a
|
|
62
|
+
* comment spanning the context/added boundary suppresses its hex, then
|
|
63
|
+
* only the patch's added line numbers are scanned. Returns undefined
|
|
64
|
+
* when any added line number falls outside the on-disk file (patch not
|
|
65
|
+
* applied / drifted) — the caller then falls back to the legacy
|
|
66
|
+
* joined-added-lines scan rather than misattributing lines.
|
|
67
|
+
*/
|
|
68
|
+
function buildAddedLinesScanSource(rawCss, addedNumbers) {
|
|
69
|
+
const rawLines = rawCss.split('\n');
|
|
70
|
+
if (!addedNumbers.every((n) => n >= 1 && n <= rawLines.length))
|
|
71
|
+
return undefined;
|
|
72
|
+
const maskedLines = maskCssComments(rawCss).split('\n');
|
|
73
|
+
return addedNumbers
|
|
74
|
+
.filter((n) => !(rawLines[n - 1] ?? '').includes(RAW_COLOR_IGNORE_MARKER))
|
|
75
|
+
.map((n) => maskedLines[n - 1] ?? '')
|
|
76
|
+
.join('\n');
|
|
77
|
+
}
|
|
41
78
|
/**
|
|
42
79
|
* Raw-color check for one patched CSS file, scoped to introduced lines
|
|
43
80
|
* when diff context is available. Pushes onto `issues`.
|
|
44
81
|
*/
|
|
45
|
-
function checkRawColorValues(file, rawCss, addedLinesByFile, config, issues) {
|
|
82
|
+
function checkRawColorValues(file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues) {
|
|
46
83
|
// Check only introduced raw color values when diff context is available.
|
|
47
84
|
// Skip files on the raw-color allowlist (exact path or basename match) and
|
|
48
85
|
// auto-exempt files under `browser/branding/` — those are the fork's
|
|
@@ -55,25 +92,33 @@ function checkRawColorValues(file, rawCss, addedLinesByFile, config, issues) {
|
|
|
55
92
|
const allowlist = config?.patchLint?.rawColorAllowlist;
|
|
56
93
|
const isAllowlisted = allowlist?.some((entry) => file === entry || file.endsWith('/' + entry));
|
|
57
94
|
const isBranding = file.startsWith('browser/branding/');
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
95
|
+
if (isAllowlisted || isBranding)
|
|
96
|
+
return;
|
|
97
|
+
let suppressedContent;
|
|
98
|
+
const addedNumbers = addedLineNumbersByFile?.get(file);
|
|
99
|
+
if (addedNumbers !== undefined) {
|
|
100
|
+
suppressedContent = buildAddedLinesScanSource(rawCss, addedNumbers);
|
|
101
|
+
}
|
|
102
|
+
if (suppressedContent === undefined) {
|
|
103
|
+
// Legacy scan: full file (no diff), or the joined added lines when
|
|
104
|
+
// the diff and on-disk file disagree. Waiver lines are filtered
|
|
105
|
+
// against the pre-strip text so the CSS comment marker is present.
|
|
61
106
|
const sourceForSuppression = addedLinesByFile
|
|
62
107
|
? (addedLinesByFile.get(file) ?? []).join('\n')
|
|
63
108
|
: rawCss;
|
|
64
|
-
|
|
109
|
+
suppressedContent = sourceForSuppression
|
|
65
110
|
.split('\n')
|
|
66
|
-
.filter((line) => !line.includes(
|
|
111
|
+
.filter((line) => !line.includes(RAW_COLOR_IGNORE_MARKER))
|
|
67
112
|
.join('\n')
|
|
68
113
|
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
114
|
+
}
|
|
115
|
+
if (hasRawCssColors(suppressedContent)) {
|
|
116
|
+
issues.push({
|
|
117
|
+
file,
|
|
118
|
+
check: 'raw-color-value',
|
|
119
|
+
message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
|
|
120
|
+
severity: 'error',
|
|
121
|
+
});
|
|
77
122
|
}
|
|
78
123
|
}
|
|
79
124
|
/**
|
|
@@ -157,6 +202,9 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
|
|
|
157
202
|
const tokenContext = await loadCssTokenContext(repoDir);
|
|
158
203
|
const issues = [];
|
|
159
204
|
const addedLinesByFile = diffContent ? extractAddedLinesPerFile(diffContent) : undefined;
|
|
205
|
+
const addedLineNumbersByFile = diffContent
|
|
206
|
+
? extractAddedLineNumbersPerFile(diffContent)
|
|
207
|
+
: undefined;
|
|
160
208
|
for (const file of cssFiles) {
|
|
161
209
|
const filePath = join(repoDir, file);
|
|
162
210
|
if (!(await pathExists(filePath)))
|
|
@@ -164,7 +212,7 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
|
|
|
164
212
|
const rawCss = await readText(filePath);
|
|
165
213
|
// Strip block comments before scanning
|
|
166
214
|
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
167
|
-
checkRawColorValues(file, rawCss, addedLinesByFile, config, issues);
|
|
215
|
+
checkRawColorValues(file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues);
|
|
168
216
|
checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues);
|
|
169
217
|
}
|
|
170
218
|
return issues;
|
|
@@ -18,6 +18,14 @@ export declare function detectNewFilesInDiff(diffContent: string): Set<string>;
|
|
|
18
18
|
* Returns a map of file path → array of added line contents (without the leading `+`).
|
|
19
19
|
*/
|
|
20
20
|
export declare function extractAddedLinesPerFile(diffContent: string): Map<string, string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts 1-based post-patch line numbers of added lines per file from a
|
|
23
|
+
* unified diff. Walking each hunk with a new-file line counter (`+` and
|
|
24
|
+
* context lines advance it, `-` lines do not) lets rules that need
|
|
25
|
+
* full-file context — e.g. comment masking across the context/added
|
|
26
|
+
* boundary (FORGE G4) — map the patch's additions onto the applied file.
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractAddedLineNumbersPerFile(diffContent: string): Map<string, number[]>;
|
|
21
29
|
/**
|
|
22
30
|
* Builds the `modifiedFileAdditions` map the cross-patch lint expects for
|
|
23
31
|
* a given unified diff. Exposed so callers that construct synthetic /
|
|
@@ -45,6 +45,37 @@ export function extractAddedLinesPerFile(diffContent) {
|
|
|
45
45
|
}
|
|
46
46
|
return result;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Extracts 1-based post-patch line numbers of added lines per file from a
|
|
50
|
+
* unified diff. Walking each hunk with a new-file line counter (`+` and
|
|
51
|
+
* context lines advance it, `-` lines do not) lets rules that need
|
|
52
|
+
* full-file context — e.g. comment masking across the context/added
|
|
53
|
+
* boundary (FORGE G4) — map the patch's additions onto the applied file.
|
|
54
|
+
*/
|
|
55
|
+
export function extractAddedLineNumbersPerFile(diffContent) {
|
|
56
|
+
const result = new Map();
|
|
57
|
+
for (const section of parseDiffSections(diffContent)) {
|
|
58
|
+
for (const hunk of section.hunks) {
|
|
59
|
+
let newLine = hunk.newStart;
|
|
60
|
+
for (const line of hunk.lines) {
|
|
61
|
+
if (line.startsWith('\\'))
|
|
62
|
+
continue; // ""
|
|
63
|
+
if (line.startsWith('-'))
|
|
64
|
+
continue;
|
|
65
|
+
if (line.startsWith('+')) {
|
|
66
|
+
let arr = result.get(section.targetPath);
|
|
67
|
+
if (!arr) {
|
|
68
|
+
arr = [];
|
|
69
|
+
result.set(section.targetPath, arr);
|
|
70
|
+
}
|
|
71
|
+
arr.push(newLine);
|
|
72
|
+
}
|
|
73
|
+
newLine++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
48
79
|
/**
|
|
49
80
|
* Builds the `modifiedFileAdditions` map the cross-patch lint expects for
|
|
50
81
|
* a given unified diff. Exposed so callers that construct synthetic /
|