@hominis/fireforge 0.49.2 → 0.50.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 +40 -1
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +93 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +51 -15
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency.js +14 -41
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +37 -24
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +59 -35
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +97 -0
- package/dist/src/core/coverage-records.js +350 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +12 -2
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +157 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +323 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +20 -0
- package/dist/src/core/run-log.js +89 -3
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +34 -6
- package/dist/src/utils/options.js +86 -5
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch-size lint: the `large-patch-lines` and `large-patch-files` rules,
|
|
3
|
+
* the tier decision they share, and the measurement they read.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `patch-lint.ts` (per-file line budget). `patch-lint.ts`
|
|
6
|
+
* re-exports this module's surface so callers keep importing from there.
|
|
7
|
+
*/
|
|
8
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
9
|
+
import type { FireForgeConfig, PatchLintPatchSizeMetric } from '../types/config.js';
|
|
10
|
+
import { type PatchSizeThresholds, type PatchSizeTierName } from './patch-lint-size-thresholds.js';
|
|
11
|
+
/**
|
|
12
|
+
* Returns true if the file path looks like a test file.
|
|
13
|
+
* Matches paths containing `/test/` or filenames starting with
|
|
14
|
+
* `browser_`, `test_`, or `xpcshell_` (all `.js`). This is the test class
|
|
15
|
+
* of both size rules: `file-too-large` picks the test thresholds with it,
|
|
16
|
+
* and the patch-size tier and the `addedCode` per-class split read it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isTestFile(file: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Counts the total lines in a unified diff and the number of non-binary
|
|
21
|
+
* text lines, so binary hunks do not inflate patch size checks.
|
|
22
|
+
*
|
|
23
|
+
* Line counting agrees with `wc -l`: a trailing newline does not add a
|
|
24
|
+
* phantom empty line to either `total` or the binary-hunk accounting.
|
|
25
|
+
*
|
|
26
|
+
* @param diffContent - Raw unified diff string
|
|
27
|
+
* @returns Object with `total` line count and `textLines` (total minus binary hunk lines)
|
|
28
|
+
*/
|
|
29
|
+
export declare function countNonBinaryDiffLines(diffContent: string): {
|
|
30
|
+
total: number;
|
|
31
|
+
textLines: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Describes which tier `resolvePatchSizeTier` selected and why.
|
|
35
|
+
* Consumers that want to surface the tier choice to the operator
|
|
36
|
+
* (e.g. a one-line `info()` when branding thresholds kick in) read
|
|
37
|
+
* this alongside the issues array from `lintPatchSize`.
|
|
38
|
+
*/
|
|
39
|
+
export type PatchSizeTierDecision = {
|
|
40
|
+
tier: 'general';
|
|
41
|
+
} | {
|
|
42
|
+
tier: 'test';
|
|
43
|
+
} | {
|
|
44
|
+
tier: 'branding';
|
|
45
|
+
source: 'auto' | 'explicit';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Decides which patch-size threshold tier applies to a patch.
|
|
49
|
+
* Exported so `runPatchLint` and the per-patch `lint` command can
|
|
50
|
+
* surface the tier choice to the operator *without* depending on
|
|
51
|
+
* `lintPatchSize`'s internal return shape.
|
|
52
|
+
*
|
|
53
|
+
* Precedence: test > branding (explicit) > branding (auto) > general.
|
|
54
|
+
* The test tier beats branding because a table-driven regression test
|
|
55
|
+
* is legitimately large independent of whether the patch also claims
|
|
56
|
+
* branding shape, and the test-tier thresholds are already more
|
|
57
|
+
* permissive than branding, so "tests beat branding" is the
|
|
58
|
+
* defensive-for-tests choice.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
61
|
+
/**
|
|
62
|
+
* The size thresholds a tier enforces, with `patchLint.patchSizeThresholds`
|
|
63
|
+
* applied when `config` is passed. Public-API companion to
|
|
64
|
+
* {@link resolvePatchSizeTier} / {@link countNonBinaryDiffLines} so
|
|
65
|
+
* consumers can build waiver-freshness checks against the same numbers
|
|
66
|
+
* `large-patch-lines` / `large-patch-files` fire on instead of mirroring
|
|
67
|
+
* them. Without `config` it returns the built-in defaults.
|
|
68
|
+
*
|
|
69
|
+
* @param tier - The tier to read
|
|
70
|
+
* @param config - The project configuration, whose overrides apply
|
|
71
|
+
*/
|
|
72
|
+
export declare function getPatchSizeThresholds(tier: PatchSizeTierName, config?: Pick<FireForgeConfig, 'patchLint'>): PatchSizeThresholds;
|
|
73
|
+
/** What a patch measures under the configured patch-size metric. */
|
|
74
|
+
export interface PatchSizeMeasurement {
|
|
75
|
+
/** The metric `lines` is counted in. */
|
|
76
|
+
metric: PatchLintPatchSizeMetric;
|
|
77
|
+
/** Counted lines, registration files left out. */
|
|
78
|
+
lines: number;
|
|
79
|
+
/**
|
|
80
|
+
* Of `lines`, those in test files. Only `addedCode` attributes lines per
|
|
81
|
+
* file, so this is 0 under `diff`.
|
|
82
|
+
*/
|
|
83
|
+
testLines: number;
|
|
84
|
+
/** Files `large-patch-files` counts: the patch's files minus registration files. */
|
|
85
|
+
files: number;
|
|
86
|
+
/** Files left out of both counts as registration files. */
|
|
87
|
+
registrationFiles: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Measures a patch under `patchLint.patchSizeMetric`.
|
|
91
|
+
*
|
|
92
|
+
* `diff` counts the non-binary lines of the unified diff, headers and
|
|
93
|
+
* context included. `addedCode` counts the added lines that carry code,
|
|
94
|
+
* judged on each file's post-image read from `repoDir` (a hunk can start
|
|
95
|
+
* inside a block comment, so the `+` lines alone cannot tell), and skips
|
|
96
|
+
* lines inside Furnace fragment expansions. Files matching
|
|
97
|
+
* `patchLint.patchSizeRegistrationFiles` count toward neither number.
|
|
98
|
+
*
|
|
99
|
+
* @param repoDir - Engine directory holding the post-images
|
|
100
|
+
* @param diffContent - The patch's unified diff
|
|
101
|
+
* @param filesAffected - The patch's files
|
|
102
|
+
* @param config - Project configuration
|
|
103
|
+
*/
|
|
104
|
+
export declare function measurePatchSize(repoDir: string, diffContent: string, filesAffected: readonly string[], config: Pick<FireForgeConfig, 'patchLint'>): Promise<PatchSizeMeasurement>;
|
|
105
|
+
/**
|
|
106
|
+
* Checks patch size against the tier's thresholds.
|
|
107
|
+
*
|
|
108
|
+
* @param filesAffected - Files touched by the patch
|
|
109
|
+
* @param measured - The {@link measurePatchSize} result, or a bare
|
|
110
|
+
* non-binary diff line count (the `diff` metric over every file)
|
|
111
|
+
* @param patchTier - Optional explicit tier override declared on
|
|
112
|
+
* `PatchMetadata.tier`. When `"branding"`, forces the branding
|
|
113
|
+
* thresholds regardless of `filesAffected`. Tests still win over
|
|
114
|
+
* branding (precedence `test > branding > general`).
|
|
115
|
+
* @param config - Project configuration, for `patchLint.patchSizeThresholds`
|
|
116
|
+
*/
|
|
117
|
+
export declare function lintPatchSize(filesAffected: string[], measured: number | PatchSizeMeasurement, patchTier?: 'branding', config?: Pick<FireForgeConfig, 'patchLint'>): PatchLintIssue[];
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Patch-size lint: the `large-patch-lines` and `large-patch-files` rules,
|
|
4
|
+
* the tier decision they share, and the measurement they read.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `patch-lint.ts` (per-file line budget). `patch-lint.ts`
|
|
7
|
+
* re-exports this module's surface so callers keep importing from there.
|
|
8
|
+
*/
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
11
|
+
import { codeLineMask } from './patch-lint-code-lines.js';
|
|
12
|
+
import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
|
|
13
|
+
import { isPatchSizeRegistrationFile, resolvePatchSizeThresholds, } from './patch-lint-size-thresholds.js';
|
|
14
|
+
import { parseDiffGitHeader, parseDiffSections } from './patch-parse.js';
|
|
15
|
+
/**
|
|
16
|
+
* Shared remedy sentence for the over-budget findings. Every place an
|
|
17
|
+
* operator stands when a patch outgrows its budget must name the
|
|
18
|
+
* sanctioned one-transaction way out, or they reach for the wrong tools
|
|
19
|
+
* first. One constant, so the wording cannot drift.
|
|
20
|
+
*/
|
|
21
|
+
const SPLIT_HINT = 'Consider splitting into smaller, focused patches — ' +
|
|
22
|
+
'"fireforge patch move-files <from> <to> --file <path> --create --order <n>" ' +
|
|
23
|
+
'moves files into a new patch in one transaction.';
|
|
24
|
+
/**
|
|
25
|
+
* Fixed allowlist of non-branding sibling paths that real-world Firefox
|
|
26
|
+
* branding patches legitimately need to touch to register the new branding
|
|
27
|
+
* flavor with the top-level configure. A predicate requiring every file to
|
|
28
|
+
* live under `browser/branding/` drops a branding patch that also touches
|
|
29
|
+
* `browser/moz.configure` (the canonical registration point) into the
|
|
30
|
+
* general lint tier.
|
|
31
|
+
*
|
|
32
|
+
* Narrow on purpose: add entries only when a genuine branding patch
|
|
33
|
+
* cannot be expressed without a specific registration sibling. Pinned
|
|
34
|
+
* against ESR 140.x conventions at time of writing.
|
|
35
|
+
*/
|
|
36
|
+
const BRANDING_REGISTRATION_FILES = new Set([
|
|
37
|
+
'browser/moz.configure',
|
|
38
|
+
'browser/confvars.sh',
|
|
39
|
+
]);
|
|
40
|
+
/**
|
|
41
|
+
* Returns true when a patch qualifies for the branding threshold tier:
|
|
42
|
+
* every file lives either under `browser/branding/` or in the narrow
|
|
43
|
+
* registration allowlist, and the patch contains at least one file
|
|
44
|
+
* under `browser/branding/` (guard against a config-only patch
|
|
45
|
+
* accidentally qualifying as branding).
|
|
46
|
+
*/
|
|
47
|
+
function isBrandingOnlyPatch(files) {
|
|
48
|
+
if (files.length === 0)
|
|
49
|
+
return false;
|
|
50
|
+
let hasBrandingFile = false;
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
if (file.startsWith('browser/branding/')) {
|
|
53
|
+
hasBrandingFile = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (BRANDING_REGISTRATION_FILES.has(file))
|
|
57
|
+
continue;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return hasBrandingFile;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns true if the file path looks like a test file.
|
|
64
|
+
* Matches paths containing `/test/` or filenames starting with
|
|
65
|
+
* `browser_`, `test_`, or `xpcshell_` (all `.js`). This is the test class
|
|
66
|
+
* of both size rules: `file-too-large` picks the test thresholds with it,
|
|
67
|
+
* and the patch-size tier and the `addedCode` per-class split read it.
|
|
68
|
+
*/
|
|
69
|
+
export function isTestFile(file) {
|
|
70
|
+
if (file.includes('/test/'))
|
|
71
|
+
return true;
|
|
72
|
+
const basename = file.split('/').pop() ?? '';
|
|
73
|
+
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Counts the total lines in a unified diff and the number of non-binary
|
|
77
|
+
* text lines, so binary hunks do not inflate patch size checks.
|
|
78
|
+
*
|
|
79
|
+
* Line counting agrees with `wc -l`: a trailing newline does not add a
|
|
80
|
+
* phantom empty line to either `total` or the binary-hunk accounting.
|
|
81
|
+
*
|
|
82
|
+
* @param diffContent - Raw unified diff string
|
|
83
|
+
* @returns Object with `total` line count and `textLines` (total minus binary hunk lines)
|
|
84
|
+
*/
|
|
85
|
+
export function countNonBinaryDiffLines(diffContent) {
|
|
86
|
+
return countDiffLines(diffContent, () => false);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* {@link countNonBinaryDiffLines} with whole file sections left out of
|
|
90
|
+
* `textLines` when `isExcluded` names their path (registration files).
|
|
91
|
+
*/
|
|
92
|
+
function countDiffLines(diffContent, isExcluded) {
|
|
93
|
+
const lines = diffContent.split('\n');
|
|
94
|
+
if (lines[lines.length - 1] === '')
|
|
95
|
+
lines.pop();
|
|
96
|
+
const total = lines.length;
|
|
97
|
+
let uncounted = 0;
|
|
98
|
+
let inBinaryHunk = false;
|
|
99
|
+
let inExcluded = false;
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
if (line.startsWith('GIT binary patch')) {
|
|
102
|
+
inBinaryHunk = true;
|
|
103
|
+
}
|
|
104
|
+
else if (line.startsWith('diff --git ')) {
|
|
105
|
+
inBinaryHunk = false;
|
|
106
|
+
const header = parseDiffGitHeader(line);
|
|
107
|
+
inExcluded = header !== null && isExcluded(header.targetPath);
|
|
108
|
+
}
|
|
109
|
+
if (inBinaryHunk || inExcluded) {
|
|
110
|
+
uncounted++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { total, textLines: total - uncounted };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Decides which patch-size threshold tier applies to a patch.
|
|
117
|
+
* Exported so `runPatchLint` and the per-patch `lint` command can
|
|
118
|
+
* surface the tier choice to the operator *without* depending on
|
|
119
|
+
* `lintPatchSize`'s internal return shape.
|
|
120
|
+
*
|
|
121
|
+
* Precedence: test > branding (explicit) > branding (auto) > general.
|
|
122
|
+
* The test tier beats branding because a table-driven regression test
|
|
123
|
+
* is legitimately large independent of whether the patch also claims
|
|
124
|
+
* branding shape, and the test-tier thresholds are already more
|
|
125
|
+
* permissive than branding, so "tests beat branding" is the
|
|
126
|
+
* defensive-for-tests choice.
|
|
127
|
+
*/
|
|
128
|
+
export function resolvePatchSizeTier(filesAffected, patchTier) {
|
|
129
|
+
const allTests = filesAffected.length > 0 && filesAffected.every(isTestFile);
|
|
130
|
+
if (allTests)
|
|
131
|
+
return { tier: 'test' };
|
|
132
|
+
if (patchTier === 'branding')
|
|
133
|
+
return { tier: 'branding', source: 'explicit' };
|
|
134
|
+
if (isBrandingOnlyPatch(filesAffected))
|
|
135
|
+
return { tier: 'branding', source: 'auto' };
|
|
136
|
+
return { tier: 'general' };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The size thresholds a tier enforces, with `patchLint.patchSizeThresholds`
|
|
140
|
+
* applied when `config` is passed. Public-API companion to
|
|
141
|
+
* {@link resolvePatchSizeTier} / {@link countNonBinaryDiffLines} so
|
|
142
|
+
* consumers can build waiver-freshness checks against the same numbers
|
|
143
|
+
* `large-patch-lines` / `large-patch-files` fire on instead of mirroring
|
|
144
|
+
* them. Without `config` it returns the built-in defaults.
|
|
145
|
+
*
|
|
146
|
+
* @param tier - The tier to read
|
|
147
|
+
* @param config - The project configuration, whose overrides apply
|
|
148
|
+
*/
|
|
149
|
+
export function getPatchSizeThresholds(tier, config) {
|
|
150
|
+
return resolvePatchSizeThresholds(tier, config?.patchLint?.patchSizeThresholds);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Measures a patch under `patchLint.patchSizeMetric`.
|
|
154
|
+
*
|
|
155
|
+
* `diff` counts the non-binary lines of the unified diff, headers and
|
|
156
|
+
* context included. `addedCode` counts the added lines that carry code,
|
|
157
|
+
* judged on each file's post-image read from `repoDir` (a hunk can start
|
|
158
|
+
* inside a block comment, so the `+` lines alone cannot tell), and skips
|
|
159
|
+
* lines inside Furnace fragment expansions. Files matching
|
|
160
|
+
* `patchLint.patchSizeRegistrationFiles` count toward neither number.
|
|
161
|
+
*
|
|
162
|
+
* @param repoDir - Engine directory holding the post-images
|
|
163
|
+
* @param diffContent - The patch's unified diff
|
|
164
|
+
* @param filesAffected - The patch's files
|
|
165
|
+
* @param config - Project configuration
|
|
166
|
+
*/
|
|
167
|
+
export async function measurePatchSize(repoDir, diffContent, filesAffected, config) {
|
|
168
|
+
const globs = config.patchLint?.patchSizeRegistrationFiles ?? [];
|
|
169
|
+
const isRegistration = (file) => isPatchSizeRegistrationFile(file, globs);
|
|
170
|
+
const registrationFiles = filesAffected.filter(isRegistration).length;
|
|
171
|
+
const files = filesAffected.length - registrationFiles;
|
|
172
|
+
if (config.patchLint?.patchSizeMetric !== 'addedCode') {
|
|
173
|
+
const lines = countDiffLines(diffContent, isRegistration).textLines;
|
|
174
|
+
return { metric: 'diff', lines, testLines: 0, files, registrationFiles };
|
|
175
|
+
}
|
|
176
|
+
let lines = 0;
|
|
177
|
+
let testLines = 0;
|
|
178
|
+
for (const section of parseDiffSections(diffContent)) {
|
|
179
|
+
if (section.isBinary || isRegistration(section.targetPath))
|
|
180
|
+
continue;
|
|
181
|
+
const counted = await countAddedCodeLines(repoDir, section);
|
|
182
|
+
lines += counted;
|
|
183
|
+
if (isTestFile(section.targetPath))
|
|
184
|
+
testLines += counted;
|
|
185
|
+
}
|
|
186
|
+
return { metric: 'addedCode', lines, testLines, files, registrationFiles };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Counts one section's added lines that carry code in the post-image.
|
|
190
|
+
* Without a readable post-image, every non-blank added line counts: the
|
|
191
|
+
* upper bound, so a missing file never shrinks the measurement.
|
|
192
|
+
*/
|
|
193
|
+
async function countAddedCodeLines(repoDir, section) {
|
|
194
|
+
const path = join(repoDir, section.targetPath);
|
|
195
|
+
const mask = (await pathExists(path))
|
|
196
|
+
? codeLineMask(section.targetPath, await readText(path)).code
|
|
197
|
+
: undefined;
|
|
198
|
+
let count = 0;
|
|
199
|
+
for (const hunk of section.hunks) {
|
|
200
|
+
let newLine = hunk.newStart;
|
|
201
|
+
for (const line of hunk.lines) {
|
|
202
|
+
if (line.startsWith('\\') || line.startsWith('-'))
|
|
203
|
+
continue;
|
|
204
|
+
if (line.startsWith('+')) {
|
|
205
|
+
const isCode = mask ? mask[newLine - 1] === true : line.slice(1).trim() !== '';
|
|
206
|
+
if (isCode)
|
|
207
|
+
count++;
|
|
208
|
+
}
|
|
209
|
+
newLine++;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return count;
|
|
213
|
+
}
|
|
214
|
+
function bandFor(count, thresholds) {
|
|
215
|
+
if (thresholds.error !== null && count > thresholds.error)
|
|
216
|
+
return 'error';
|
|
217
|
+
if (count > thresholds.warning)
|
|
218
|
+
return 'warning';
|
|
219
|
+
if (count > thresholds.notice)
|
|
220
|
+
return 'notice';
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Renders a patch-size finding. The notice band is advisory and says so;
|
|
225
|
+
* the warning band names what a `--max-warnings 0` gate does with it and
|
|
226
|
+
* where to tune it; the split hint rides on the bands that can fail a gate.
|
|
227
|
+
*/
|
|
228
|
+
function formatPatchSizeMessage(subject, band, thresholds) {
|
|
229
|
+
const errorPart = thresholds.error === null ? '' : `, error threshold: ${thresholds.error}`;
|
|
230
|
+
if (band === 'error') {
|
|
231
|
+
return `${subject} (error threshold: ${thresholds.error}). ${SPLIT_HINT}`;
|
|
232
|
+
}
|
|
233
|
+
if (band === 'notice') {
|
|
234
|
+
return (`${subject}, over the design target of ${thresholds.notice} ` +
|
|
235
|
+
`(warning threshold: ${thresholds.warning}${errorPart}). ` +
|
|
236
|
+
'Advisory; does not fail --max-warnings 0.');
|
|
237
|
+
}
|
|
238
|
+
return (`${subject} (warning threshold: ${thresholds.warning}${errorPart}). ` +
|
|
239
|
+
'Reported as a warning, which a gate running --max-warnings 0 treats as a failure; ' +
|
|
240
|
+
'tune "patchLint.patchSizeThresholds" if these limits are wrong for this project. ' +
|
|
241
|
+
SPLIT_HINT);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The line-count comparisons for a patch. Under `addedCode` a general-tier
|
|
245
|
+
* patch splits per class: test files against the test tier's lines,
|
|
246
|
+
* everything else against the general tier's, so a test riding with its
|
|
247
|
+
* subject is held to the same budget as a test-only patch.
|
|
248
|
+
*/
|
|
249
|
+
function lineChecks(measurement, tier, thresholds, config) {
|
|
250
|
+
const perClass = measurement.metric === 'addedCode' && tier === 'general';
|
|
251
|
+
if (!perClass || measurement.testLines === 0) {
|
|
252
|
+
return [{ count: measurement.lines, thresholds: thresholds.lines, scope: '' }];
|
|
253
|
+
}
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
count: measurement.lines - measurement.testLines,
|
|
257
|
+
thresholds: thresholds.lines,
|
|
258
|
+
scope: ' in non-test files',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
count: measurement.testLines,
|
|
262
|
+
thresholds: getPatchSizeThresholds('test', config).lines,
|
|
263
|
+
scope: ' in test files (test tier)',
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Checks patch size against the tier's thresholds.
|
|
269
|
+
*
|
|
270
|
+
* @param filesAffected - Files touched by the patch
|
|
271
|
+
* @param measured - The {@link measurePatchSize} result, or a bare
|
|
272
|
+
* non-binary diff line count (the `diff` metric over every file)
|
|
273
|
+
* @param patchTier - Optional explicit tier override declared on
|
|
274
|
+
* `PatchMetadata.tier`. When `"branding"`, forces the branding
|
|
275
|
+
* thresholds regardless of `filesAffected`. Tests still win over
|
|
276
|
+
* branding (precedence `test > branding > general`).
|
|
277
|
+
* @param config - Project configuration, for `patchLint.patchSizeThresholds`
|
|
278
|
+
*/
|
|
279
|
+
export function lintPatchSize(filesAffected, measured, patchTier, config) {
|
|
280
|
+
const measurement = typeof measured === 'number'
|
|
281
|
+
? {
|
|
282
|
+
metric: 'diff',
|
|
283
|
+
lines: measured,
|
|
284
|
+
testLines: 0,
|
|
285
|
+
files: filesAffected.length,
|
|
286
|
+
registrationFiles: 0,
|
|
287
|
+
}
|
|
288
|
+
: measured;
|
|
289
|
+
const issues = [];
|
|
290
|
+
// Both rules read off the same decision so the file-count and line-count
|
|
291
|
+
// rules cannot disagree about which tier applies.
|
|
292
|
+
const decision = resolvePatchSizeTier(filesAffected, patchTier);
|
|
293
|
+
const thresholds = getPatchSizeThresholds(decision.tier, config);
|
|
294
|
+
const filesBand = bandFor(measurement.files, thresholds.files);
|
|
295
|
+
if (filesBand !== undefined) {
|
|
296
|
+
const registration = measurement.registrationFiles > 0
|
|
297
|
+
? ` (not counting ${measurement.registrationFiles} registration file(s))`
|
|
298
|
+
: '';
|
|
299
|
+
issues.push({
|
|
300
|
+
file: AGGREGATE_PATCH_FILE,
|
|
301
|
+
check: 'large-patch-files',
|
|
302
|
+
message: formatPatchSizeMessage(`Patch affects ${measurement.files} files${registration}`, filesBand, thresholds.files),
|
|
303
|
+
severity: filesBand,
|
|
304
|
+
measurement: measurement.files,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
for (const check of lineChecks(measurement, decision.tier, thresholds, config)) {
|
|
308
|
+
const band = bandFor(check.count, check.thresholds);
|
|
309
|
+
if (band === undefined)
|
|
310
|
+
continue;
|
|
311
|
+
const subject = measurement.metric === 'addedCode'
|
|
312
|
+
? `Patch adds ${check.count} code lines${check.scope}`
|
|
313
|
+
: `Patch is ${check.count} lines`;
|
|
314
|
+
issues.push({
|
|
315
|
+
file: AGGREGATE_PATCH_FILE,
|
|
316
|
+
check: 'large-patch-lines',
|
|
317
|
+
message: formatPatchSizeMessage(subject, band, check.thresholds),
|
|
318
|
+
severity: band,
|
|
319
|
+
measurement: check.count,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return issues;
|
|
323
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type { PatchStagedRegistration } from '../types/commands/index.js';
|
|
1
|
+
import type { PatchStagedForwardImport, PatchStagedRegistration } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The `--reason` argument every printed `patch staged-dependency --add`
|
|
4
|
+
* remedy ends with. A declaration without a reason reads as unreviewed
|
|
5
|
+
* residue to a queue audit, so the paste-and-run command asks for one
|
|
6
|
+
* rather than inviting the operator to leave it out.
|
|
7
|
+
*/
|
|
8
|
+
export declare const STAGED_DEPENDENCY_REASON_PLACEHOLDER = "--reason '<why this patch references a file the owner creates later>'";
|
|
2
9
|
/** A patch that creates a given path, with its position in the queue. */
|
|
3
10
|
export interface NewFileOwner {
|
|
4
11
|
filename: string;
|
|
@@ -59,3 +66,13 @@ export declare function describeRegistrationFailure(failure: StagedRegistrationF
|
|
|
59
66
|
* forward-registration remedy leave the declaring patch red.
|
|
60
67
|
*/
|
|
61
68
|
export declare function quoteRegistrationLine(line: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* The `staged-dependency-unused` fingerprint for one declaration on
|
|
71
|
+
* `holder`, the one spelling both the rule and the callers that need to
|
|
72
|
+
* recognise its findings (`patch split`/`move-files --create` deciding which
|
|
73
|
+
* declarations a move made unused) build it with.
|
|
74
|
+
*
|
|
75
|
+
* @param holder - Filename of the patch carrying the declaration
|
|
76
|
+
* @param declaration - Either declaration kind
|
|
77
|
+
*/
|
|
78
|
+
export declare function stagedDependencyUnusedFingerprint(holder: string, declaration: PatchStagedForwardImport | PatchStagedRegistration): string;
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* `patch-lint-forward-registration.ts`, with no registrar or rule of its own.
|
|
13
13
|
*/
|
|
14
14
|
import { basename } from 'node:path';
|
|
15
|
+
/**
|
|
16
|
+
* The `--reason` argument every printed `patch staged-dependency --add`
|
|
17
|
+
* remedy ends with. A declaration without a reason reads as unreviewed
|
|
18
|
+
* residue to a queue audit, so the paste-and-run command asks for one
|
|
19
|
+
* rather than inviting the operator to leave it out.
|
|
20
|
+
*/
|
|
21
|
+
export const STAGED_DEPENDENCY_REASON_PLACEHOLDER = "--reason '<why this patch references a file the owner creates later>'";
|
|
15
22
|
/**
|
|
16
23
|
* Later-in-apply-order predicate shared by the forward-import scan, the
|
|
17
24
|
* forward-registration rule and the registration validation: strictly
|
|
@@ -88,3 +95,16 @@ function isRegistrationLinePresent(entry, registration) {
|
|
|
88
95
|
export function quoteRegistrationLine(line) {
|
|
89
96
|
return line.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
90
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The `staged-dependency-unused` fingerprint for one declaration on
|
|
100
|
+
* `holder`, the one spelling both the rule and the callers that need to
|
|
101
|
+
* recognise its findings (`patch split`/`move-files --create` deciding which
|
|
102
|
+
* declarations a move made unused) build it with.
|
|
103
|
+
*
|
|
104
|
+
* @param holder - Filename of the patch carrying the declaration
|
|
105
|
+
* @param declaration - Either declaration kind
|
|
106
|
+
*/
|
|
107
|
+
export function stagedDependencyUnusedFingerprint(holder, declaration) {
|
|
108
|
+
const matcher = 'specifier' in declaration ? declaration.specifier : `reg:${declaration.line}`;
|
|
109
|
+
return `staged-dependency-unused|${holder}|${declaration.file}|${matcher}|${declaration.creates}|${declaration.owner ?? ''}`;
|
|
110
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `token-scale`: values of governed CSS properties must resolve to a token
|
|
3
|
+
* on the property's scale (`patchLint.tokenScales`).
|
|
4
|
+
*
|
|
5
|
+
* The raw-colour and token-prefix rules stop a patch from inventing colours
|
|
6
|
+
* or referencing foreign custom properties, but say nothing about
|
|
7
|
+
* `gap: 7px`, `font-size: 13px` or `z-index: 9999`. Those are the values a
|
|
8
|
+
* design scale exists to remove, and without a rule they drift back one
|
|
9
|
+
* literal at a time. A scale names its properties, the token prefix that
|
|
10
|
+
* defines its steps, and the literals that are not steps at all (`0`,
|
|
11
|
+
* `auto`, a percentage).
|
|
12
|
+
*
|
|
13
|
+
* Judged per value atom, after peeling `var()` innermost-first so a fallback
|
|
14
|
+
* is held to the same scale as the token it backs: `var(--x-gap, 7px)` is a
|
|
15
|
+
* literal in disguise. `calc()`, `min()`, `max()` and `clamp()` are
|
|
16
|
+
* transparent, so a value built from tokens passes and one that adds a raw
|
|
17
|
+
* length does not. Unitless multipliers inside them are literals too and
|
|
18
|
+
* need an `allowLiterals` entry.
|
|
19
|
+
*
|
|
20
|
+
* Runs over CSS files the patch creates, not files it modifies: an upstream
|
|
21
|
+
* stylesheet is not the fork's to put on a scale. A line carrying
|
|
22
|
+
* `fireforge-ignore: token-scale` is skipped.
|
|
23
|
+
*/
|
|
24
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
25
|
+
import type { PatchLintTokenScale } from '../types/config.js';
|
|
26
|
+
/**
|
|
27
|
+
* Returns the first atom of `value` that is off `scale`, or undefined when
|
|
28
|
+
* every atom resolves to the scale.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findOffScaleAtom(value: string, scale: PatchLintTokenScale): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Lints one patch-created CSS file against the configured scales.
|
|
33
|
+
*
|
|
34
|
+
* @param file - Engine-relative path, for the issue
|
|
35
|
+
* @param css - The file's full content
|
|
36
|
+
* @param scales - `patchLint.tokenScales`
|
|
37
|
+
* @returns One `token-scale` error per off-scale declaration
|
|
38
|
+
*/
|
|
39
|
+
export declare function lintTokenScales(file: string, css: string, scales: readonly PatchLintTokenScale[]): PatchLintIssue[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `token-scale`: values of governed CSS properties must resolve to a token
|
|
4
|
+
* on the property's scale (`patchLint.tokenScales`).
|
|
5
|
+
*
|
|
6
|
+
* The raw-colour and token-prefix rules stop a patch from inventing colours
|
|
7
|
+
* or referencing foreign custom properties, but say nothing about
|
|
8
|
+
* `gap: 7px`, `font-size: 13px` or `z-index: 9999`. Those are the values a
|
|
9
|
+
* design scale exists to remove, and without a rule they drift back one
|
|
10
|
+
* literal at a time. A scale names its properties, the token prefix that
|
|
11
|
+
* defines its steps, and the literals that are not steps at all (`0`,
|
|
12
|
+
* `auto`, a percentage).
|
|
13
|
+
*
|
|
14
|
+
* Judged per value atom, after peeling `var()` innermost-first so a fallback
|
|
15
|
+
* is held to the same scale as the token it backs: `var(--x-gap, 7px)` is a
|
|
16
|
+
* literal in disguise. `calc()`, `min()`, `max()` and `clamp()` are
|
|
17
|
+
* transparent, so a value built from tokens passes and one that adds a raw
|
|
18
|
+
* length does not. Unitless multipliers inside them are literals too and
|
|
19
|
+
* need an `allowLiterals` entry.
|
|
20
|
+
*
|
|
21
|
+
* Runs over CSS files the patch creates, not files it modifies: an upstream
|
|
22
|
+
* stylesheet is not the fork's to put on a scale. A line carrying
|
|
23
|
+
* `fireforge-ignore: token-scale` is skipped.
|
|
24
|
+
*/
|
|
25
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
26
|
+
const IGNORE_MARKER = 'fireforge-ignore: token-scale';
|
|
27
|
+
/** CSS-wide keywords: never a step on any scale, always legal. */
|
|
28
|
+
const CSS_WIDE_KEYWORDS = new Set(['inherit', 'initial', 'unset', 'revert', 'revert-layer']);
|
|
29
|
+
const TOKEN_PLACEHOLDER = '\u0000token\u0000';
|
|
30
|
+
/** Exact name, or a `*`-suffixed prefix. */
|
|
31
|
+
function matchesName(name, patterns) {
|
|
32
|
+
return patterns.some((pattern) => pattern.endsWith('*') ? name.startsWith(pattern.slice(0, -1)) : name === pattern);
|
|
33
|
+
}
|
|
34
|
+
function matchesLiteral(atom, patterns) {
|
|
35
|
+
return patterns.some((pattern) => new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`).test(atom));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the first atom of `value` that is off `scale`, or undefined when
|
|
39
|
+
* every atom resolves to the scale.
|
|
40
|
+
*/
|
|
41
|
+
export function findOffScaleAtom(value, scale) {
|
|
42
|
+
let rest = value.replace(/!\s*important\s*$/i, '');
|
|
43
|
+
const varPattern = /var\(\s*(--[\w-]+)\s*(?:,([^()]*))?\)/;
|
|
44
|
+
for (let match = varPattern.exec(rest); match !== null; match = varPattern.exec(rest)) {
|
|
45
|
+
const name = match[1] ?? '';
|
|
46
|
+
if (!name.startsWith(scale.prefix) && !matchesName(name, scale.allow ?? [])) {
|
|
47
|
+
return `var(${name})`;
|
|
48
|
+
}
|
|
49
|
+
const fallback = match[2];
|
|
50
|
+
if (fallback !== undefined && fallback.trim() !== '') {
|
|
51
|
+
const offending = findOffScaleAtom(fallback, scale);
|
|
52
|
+
if (offending !== undefined)
|
|
53
|
+
return offending;
|
|
54
|
+
}
|
|
55
|
+
rest = `${rest.slice(0, match.index)} ${TOKEN_PLACEHOLDER} ${rest.slice(match.index + match[0].length)}`;
|
|
56
|
+
}
|
|
57
|
+
const atoms = rest
|
|
58
|
+
.replace(/\b(?:calc|min|max|clamp)\(/gi, ' ')
|
|
59
|
+
.replace(/[(),]/g, ' ')
|
|
60
|
+
.split(/\s+/)
|
|
61
|
+
.filter((atom) => atom !== '' && !['+', '-', '*', '/'].includes(atom));
|
|
62
|
+
return atoms.find((atom) => atom !== TOKEN_PLACEHOLDER &&
|
|
63
|
+
!CSS_WIDE_KEYWORDS.has(atom.toLowerCase()) &&
|
|
64
|
+
!matchesLiteral(atom, scale.allowLiterals ?? []));
|
|
65
|
+
}
|
|
66
|
+
/** Blanks block comments, keeping newlines so offsets map to lines. */
|
|
67
|
+
function maskComments(css) {
|
|
68
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\n]/g, ' '));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Lints one patch-created CSS file against the configured scales.
|
|
72
|
+
*
|
|
73
|
+
* @param file - Engine-relative path, for the issue
|
|
74
|
+
* @param css - The file's full content
|
|
75
|
+
* @param scales - `patchLint.tokenScales`
|
|
76
|
+
* @returns One `token-scale` error per off-scale declaration
|
|
77
|
+
*/
|
|
78
|
+
export function lintTokenScales(file, css, scales) {
|
|
79
|
+
if (scales.length === 0)
|
|
80
|
+
return [];
|
|
81
|
+
const rawLines = css.split('\n');
|
|
82
|
+
const masked = maskComments(css);
|
|
83
|
+
const issues = [];
|
|
84
|
+
const declaration = /(^|[{;\s])([a-zA-Z][a-zA-Z-]*)\s*:\s*([^;{}]*)/g;
|
|
85
|
+
for (let match = declaration.exec(masked); match !== null; match = declaration.exec(masked)) {
|
|
86
|
+
const property = (match[2] ?? '').toLowerCase();
|
|
87
|
+
const value = (match[3] ?? '').trim();
|
|
88
|
+
const scale = scales.find((candidate) => matchesName(property, candidate.properties));
|
|
89
|
+
if (scale === undefined || value === '')
|
|
90
|
+
continue;
|
|
91
|
+
const start = match.index + (match[1] ?? '').length;
|
|
92
|
+
const line = masked.slice(0, start).split('\n').length;
|
|
93
|
+
if ((rawLines[line - 1] ?? '').includes(IGNORE_MARKER))
|
|
94
|
+
continue;
|
|
95
|
+
const atom = findOffScaleAtom(value, scale);
|
|
96
|
+
if (atom === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
issues.push({
|
|
99
|
+
file,
|
|
100
|
+
check: 'token-scale',
|
|
101
|
+
fingerprint: `token-scale:${file}:${property}:${value}`,
|
|
102
|
+
message: `Line ${String(line)}: ${property}: ${value} is off the ${scale.prefix}* scale ` +
|
|
103
|
+
`("${atom}"). Use a ${scale.prefix}* token, or list a reviewed exception in ` +
|
|
104
|
+
`patchLint.tokenScales (allowLiterals / allow) or mark the line "${IGNORE_MARKER}".`,
|
|
105
|
+
severity: 'error',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return issues;
|
|
109
|
+
}
|