@hominis/fireforge 0.50.0 → 0.52.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 +67 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/check-report.d.ts +17 -4
- package/dist/src/commands/check-report.js +19 -6
- package/dist/src/commands/check-run.d.ts +5 -0
- package/dist/src/commands/check-run.js +9 -1
- package/dist/src/commands/check-steps.d.ts +8 -0
- package/dist/src/commands/check-steps.js +31 -6
- package/dist/src/commands/check.js +1 -1
- package/dist/src/commands/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- package/dist/src/commands/export-placement-gate.js +8 -0
- package/dist/src/commands/furnace/rename-registrations.js +7 -2
- package/dist/src/commands/furnace/validate.js +26 -1
- package/dist/src/commands/lint-per-patch.js +6 -2
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.js +7 -2
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -0
- package/dist/src/commands/patch/delete.js +11 -6
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.js +39 -29
- package/dist/src/commands/patch/move-files-create.js +11 -4
- package/dist/src/commands/patch/move-files-into.js +10 -4
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- package/dist/src/commands/patch/rename.js +15 -3
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.js +11 -6
- package/dist/src/commands/patch/split-plan.d.ts +10 -1
- package/dist/src/commands/patch/split-plan.js +16 -1
- package/dist/src/commands/patch/split.js +14 -5
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +40 -98
- package/dist/src/commands/re-export-adjacent.d.ts +5 -0
- package/dist/src/commands/re-export-adjacent.js +4 -3
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- package/dist/src/commands/re-export-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- package/dist/src/commands/re-export-options.js +29 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +15 -1
- package/dist/src/commands/re-export-removed.d.ts +1 -1
- package/dist/src/commands/re-export-removed.js +17 -2
- package/dist/src/commands/re-export-scan.d.ts +2 -0
- package/dist/src/commands/re-export-scan.js +4 -1
- package/dist/src/commands/re-export-single.js +102 -42
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- package/dist/src/commands/re-export.js +35 -34
- package/dist/src/commands/status-output.js +5 -1
- package/dist/src/commands/test-build-furnace.d.ts +3 -2
- package/dist/src/commands/test-build-furnace.js +8 -2
- package/dist/src/commands/test-build-phase.d.ts +2 -0
- package/dist/src/commands/test-build-phase.js +34 -17
- package/dist/src/commands/test-checkjs.js +3 -2
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +220 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -0
- package/dist/src/commands/test-orchestrate.d.ts +14 -0
- package/dist/src/commands/test-orchestrate.js +25 -4
- package/dist/src/commands/test-perf-scope.d.ts +21 -0
- package/dist/src/commands/test-perf-scope.js +55 -0
- package/dist/src/commands/test-prelock.js +100 -4
- package/dist/src/commands/test-register.js +18 -3
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +73 -101
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +11 -2
- package/dist/src/commands/test-verdict.d.ts +2 -1
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +68 -75
- package/dist/src/commands/token-lint.js +48 -8
- package/dist/src/commands/verify.js +9 -2
- package/dist/src/core/build-baseline-types.d.ts +13 -0
- package/dist/src/core/build-baseline-validate.js +9 -0
- package/dist/src/core/build-baseline.js +7 -0
- package/dist/src/core/build-prepare.d.ts +6 -0
- package/dist/src/core/build-prepare.js +29 -3
- 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-check.js +83 -7
- package/dist/src/core/config-validate-test-toolchains.js +14 -0
- package/dist/src/core/config-validate-token-scales.d.ts +3 -0
- package/dist/src/core/config-validate-token-scales.js +47 -5
- package/dist/src/core/config-validate.js +6 -0
- package/dist/src/core/coverage-extend.js +2 -2
- package/dist/src/core/coverage-records.d.ts +11 -0
- package/dist/src/core/coverage-records.js +34 -0
- package/dist/src/core/engine-session-lock.d.ts +6 -0
- package/dist/src/core/engine-session-lock.js +10 -2
- package/dist/src/core/file-lock-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +9 -0
- package/dist/src/core/file-lock.js +58 -2
- package/dist/src/core/furnace-apply.js +10 -10
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +11 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +59 -8
- package/dist/src/core/furnace-config-validate.js +7 -1
- package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +5 -3
- package/dist/src/core/furnace-validate-accessibility.js +75 -30
- package/dist/src/core/furnace-validate-exceptions.d.ts +13 -4
- package/dist/src/core/furnace-validate-exceptions.js +59 -4
- package/dist/src/core/furnace-validate-icon-button.js +30 -1
- package/dist/src/core/history-log-file.js +8 -1
- package/dist/src/core/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +24 -9
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +36 -0
- package/dist/src/core/negative-control-compiled.js +87 -0
- package/dist/src/core/negative-control-diff.d.ts +14 -2
- package/dist/src/core/negative-control-diff.js +49 -6
- package/dist/src/core/negative-control.d.ts +12 -0
- package/dist/src/core/negative-control.js +48 -5
- package/dist/src/core/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +4 -0
- package/dist/src/core/patch-export-update.js +12 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +21 -14
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +6 -4
- package/dist/src/core/patch-lint-checkjs-any-import.js +9 -7
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +13 -3
- package/dist/src/core/patch-lint-cross.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +5 -0
- package/dist/src/core/patch-lint-css.js +14 -10
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +5 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +1 -1
- package/dist/src/core/patch-lint-ignore.d.ts +56 -12
- package/dist/src/core/patch-lint-ignore.js +173 -43
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -0
- package/dist/src/core/patch-lint-size.js +9 -1
- package/dist/src/core/patch-lint-token-scale.d.ts +26 -8
- package/dist/src/core/patch-lint-token-scale.js +206 -29
- package/dist/src/core/patch-lint.js +2 -2
- package/dist/src/core/patch-manifest-io.js +8 -1
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +27 -1
- package/dist/src/core/patch-order-legal.d.ts +2 -1
- package/dist/src/core/patch-order-legal.js +7 -2
- package/dist/src/core/patch-policy.d.ts +6 -2
- package/dist/src/core/patch-policy.js +14 -7
- package/dist/src/core/patch-retired.d.ts +26 -4
- package/dist/src/core/patch-retired.js +40 -2
- package/dist/src/core/run-log.d.ts +6 -0
- package/dist/src/core/run-log.js +17 -1
- package/dist/src/core/test-harness-crash.d.ts +2 -1
- package/dist/src/core/test-harness-crash.js +5 -8
- package/dist/src/core/test-harness-signature.d.ts +16 -0
- package/dist/src/core/test-harness-signature.js +19 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +47 -7
- package/dist/src/core/test-run-completeness.js +70 -23
- package/dist/src/core/test-stale-check.d.ts +10 -1
- package/dist/src/core/test-stale-check.js +15 -1
- package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +5 -2
- package/dist/src/core/token-lint.js +8 -3
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +2 -0
- package/dist/src/core/typecheck-shim.d.ts +6 -1
- package/dist/src/core/typecheck-shim.js +139 -2
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +9 -3
- package/dist/src/index.js +4 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +68 -7
- package/dist/src/types/commands/patches.d.ts +43 -0
- package/dist/src/types/config.d.ts +74 -2
- package/dist/src/types/furnace.d.ts +29 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +3 -2
- package/dist/src/utils/options.js +9 -4
- package/docs/configuration.md +110 -9
- package/docs/furnace.md +61 -1
- package/docs/lifecycle-invariants.md +13 -11
- package/docs/machine-output.md +68 -18
- package/docs/patch-workflow.md +174 -13
- package/docs/testing.md +89 -5
- package/package.json +3 -3
|
@@ -24,6 +24,16 @@ export const LINT_IGNORE_CEILING_RULES = [
|
|
|
24
24
|
'large-patch-lines',
|
|
25
25
|
'large-patch-files',
|
|
26
26
|
];
|
|
27
|
+
/** The only rule whose findings carry a {@link PatchSizeShare}. */
|
|
28
|
+
const SHARE_RULE = 'large-patch-lines';
|
|
29
|
+
const SHARES = ['product', 'test'];
|
|
30
|
+
/**
|
|
31
|
+
* The key a ceiling is stored under: the rule, plus `#<share>` for a
|
|
32
|
+
* share-scoped ceiling, so one rule can carry a ceiling per share.
|
|
33
|
+
*/
|
|
34
|
+
function ceilingKey(rule, share) {
|
|
35
|
+
return share === undefined ? rule : `${rule}#${share}`;
|
|
36
|
+
}
|
|
27
37
|
/** Narrows an entry to the ceiling form. */
|
|
28
38
|
export function isLintIgnoreCeiling(entry) {
|
|
29
39
|
return typeof entry !== 'string';
|
|
@@ -32,9 +42,19 @@ export function isLintIgnoreCeiling(entry) {
|
|
|
32
42
|
export function lintIgnoreRule(entry) {
|
|
33
43
|
return isLintIgnoreCeiling(entry) ? entry.rule : entry;
|
|
34
44
|
}
|
|
35
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* One-word rendering for summaries and dry runs: `file-too-large<=1200`,
|
|
47
|
+
* `large-patch-lines[test]<=900`.
|
|
48
|
+
*/
|
|
36
49
|
export function formatLintIgnoreEntry(entry) {
|
|
37
|
-
|
|
50
|
+
if (!isLintIgnoreCeiling(entry))
|
|
51
|
+
return entry;
|
|
52
|
+
const share = entry.share === undefined ? '' : `[${entry.share}]`;
|
|
53
|
+
return `${entry.rule}${share}<=${String(entry.max)}`;
|
|
54
|
+
}
|
|
55
|
+
/** The identity an entry is upserted and deduplicated by. */
|
|
56
|
+
function entryKey(entry) {
|
|
57
|
+
return isLintIgnoreCeiling(entry) ? ceilingKey(entry.rule, entry.share) : entry;
|
|
38
58
|
}
|
|
39
59
|
/** Renders a list for messages, `(empty)` when there is nothing to show. */
|
|
40
60
|
export function formatLintIgnoreList(entries) {
|
|
@@ -43,7 +63,11 @@ export function formatLintIgnoreList(entries) {
|
|
|
43
63
|
function sameEntry(a, b) {
|
|
44
64
|
if (!isLintIgnoreCeiling(a) || !isLintIgnoreCeiling(b))
|
|
45
65
|
return a === b;
|
|
46
|
-
return a.rule === b.rule &&
|
|
66
|
+
return (a.rule === b.rule &&
|
|
67
|
+
a.max === b.max &&
|
|
68
|
+
a.reason === b.reason &&
|
|
69
|
+
a.share === b.share &&
|
|
70
|
+
JSON.stringify(a.meta) === JSON.stringify(b.meta));
|
|
47
71
|
}
|
|
48
72
|
/**
|
|
49
73
|
* Validates a raw `lintIgnore` value from `patches.json`.
|
|
@@ -53,6 +77,9 @@ function sameEntry(a, b) {
|
|
|
53
77
|
* integer `max` and a non-empty `reason`, and must be the only entry for
|
|
54
78
|
* its rule: a bare ID beside a ceiling for the same rule would make the
|
|
55
79
|
* ceiling dead, and two ceilings would leave the effective one ambiguous.
|
|
80
|
+
* The one exception is `large-patch-lines`, which may carry one ceiling
|
|
81
|
+
* per `share` instead of a single share-less one. `meta` must be a JSON
|
|
82
|
+
* object and is kept verbatim.
|
|
56
83
|
*
|
|
57
84
|
* @param raw - The value under `lintIgnore`
|
|
58
85
|
* @param label - Field label for error messages, e.g. `patches[3].lintIgnore`
|
|
@@ -73,13 +100,21 @@ export function parseLintIgnoreEntries(raw, label) {
|
|
|
73
100
|
}
|
|
74
101
|
entries.push(parseCeiling(item, itemLabel));
|
|
75
102
|
}
|
|
103
|
+
const sharedRules = new Set();
|
|
76
104
|
for (const entry of entries) {
|
|
77
105
|
if (!isLintIgnoreCeiling(entry))
|
|
78
106
|
continue;
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
const key = ceilingKey(entry.rule, entry.share);
|
|
108
|
+
const clash = ceilingRules.has(key) ||
|
|
109
|
+
entries.includes(entry.rule) ||
|
|
110
|
+
(entry.share === undefined ? sharedRules.has(entry.rule) : ceilingRules.has(entry.rule));
|
|
111
|
+
if (clash) {
|
|
112
|
+
throw new Error(`${label} names "${entry.rule}" more than once; keep a single entry (bare or ceiling) ` +
|
|
113
|
+
'per rule, or one ceiling per share');
|
|
81
114
|
}
|
|
82
|
-
ceilingRules.add(
|
|
115
|
+
ceilingRules.add(key);
|
|
116
|
+
if (entry.share !== undefined)
|
|
117
|
+
sharedRules.add(entry.rule);
|
|
83
118
|
}
|
|
84
119
|
return entries;
|
|
85
120
|
}
|
|
@@ -88,11 +123,11 @@ function parseCeiling(item, label) {
|
|
|
88
123
|
throw new Error(`${label} must be a check ID string or a { rule, max, reason } object`);
|
|
89
124
|
}
|
|
90
125
|
const record = item;
|
|
91
|
-
const unknownKeys = Object.keys(record).filter((key) => key
|
|
126
|
+
const unknownKeys = Object.keys(record).filter((key) => !CEILING_KEYS.has(key));
|
|
92
127
|
if (unknownKeys.length > 0) {
|
|
93
128
|
throw new Error(`${label} has unknown field(s): ${unknownKeys.join(', ')}`);
|
|
94
129
|
}
|
|
95
|
-
const { rule, max, reason } = record;
|
|
130
|
+
const { rule, max, reason, share, meta } = record;
|
|
96
131
|
if (typeof rule !== 'string' || !LINT_IGNORE_CEILING_RULES.includes(rule)) {
|
|
97
132
|
throw new Error(`${label}.rule must be one of the measured size rules: ${LINT_IGNORE_CEILING_RULES.join(', ')}`);
|
|
98
133
|
}
|
|
@@ -102,8 +137,22 @@ function parseCeiling(item, label) {
|
|
|
102
137
|
if (typeof reason !== 'string' || reason.trim() === '') {
|
|
103
138
|
throw new Error(`${label}.reason must be a non-empty string`);
|
|
104
139
|
}
|
|
105
|
-
|
|
140
|
+
const ceiling = { rule, max, reason };
|
|
141
|
+
if (share !== undefined) {
|
|
142
|
+
if (rule !== SHARE_RULE || !SHARES.includes(share)) {
|
|
143
|
+
throw new Error(`${label}.share must be "product" or "test", and only on ${SHARE_RULE}`);
|
|
144
|
+
}
|
|
145
|
+
ceiling.share = share;
|
|
146
|
+
}
|
|
147
|
+
if (meta !== undefined) {
|
|
148
|
+
if (meta === null || typeof meta !== 'object' || Array.isArray(meta)) {
|
|
149
|
+
throw new Error(`${label}.meta must be a JSON object`);
|
|
150
|
+
}
|
|
151
|
+
ceiling.meta = meta;
|
|
152
|
+
}
|
|
153
|
+
return ceiling;
|
|
106
154
|
}
|
|
155
|
+
const CEILING_KEYS = new Set(['rule', 'max', 'reason', 'share', 'meta']);
|
|
107
156
|
/**
|
|
108
157
|
* Builds the waiver filter for a patch's entries.
|
|
109
158
|
*
|
|
@@ -117,7 +166,7 @@ export function buildLintIgnoreChecks(entries) {
|
|
|
117
166
|
const ceilings = new Map();
|
|
118
167
|
for (const entry of entries) {
|
|
119
168
|
if (isLintIgnoreCeiling(entry))
|
|
120
|
-
ceilings.set(entry.rule, entry);
|
|
169
|
+
ceilings.set(ceilingKey(entry.rule, entry.share), entry);
|
|
121
170
|
else
|
|
122
171
|
plain.add(entry);
|
|
123
172
|
}
|
|
@@ -138,38 +187,52 @@ export function lintIgnoreCacheTokens(checks) {
|
|
|
138
187
|
* semantics `export` / `re-export --lint-ignore` have always had. A bare ID
|
|
139
188
|
* whose rule already has an entry (bare or ceiling) is a no-op: widening a
|
|
140
189
|
* reviewed ceiling to an unbounded waiver is `patch lint-ignore`'s job, where
|
|
141
|
-
* it is confirmed explicitly.
|
|
190
|
+
* it is confirmed explicitly. Ceilings deduplicate by rule and share, so a
|
|
191
|
+
* rule's per-share ceilings all survive.
|
|
142
192
|
*
|
|
143
193
|
* @returns The merged list, or undefined when it is empty
|
|
144
194
|
*/
|
|
145
195
|
export function mergeLintIgnoreIds(existing, ids) {
|
|
146
196
|
const merged = [];
|
|
197
|
+
const keys = new Set();
|
|
147
198
|
const rules = new Set();
|
|
148
199
|
for (const entry of [...(existing ?? []), ...(ids ?? [])]) {
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
200
|
+
const blocked = isLintIgnoreCeiling(entry) ? keys.has(entryKey(entry)) : rules.has(entry);
|
|
201
|
+
if (blocked)
|
|
151
202
|
continue;
|
|
152
|
-
|
|
203
|
+
keys.add(entryKey(entry));
|
|
204
|
+
rules.add(lintIgnoreRule(entry));
|
|
153
205
|
merged.push(entry);
|
|
154
206
|
}
|
|
155
207
|
return merged.length > 0 ? merged : undefined;
|
|
156
208
|
}
|
|
157
209
|
/**
|
|
158
|
-
* Adds or replaces entries
|
|
159
|
-
*
|
|
160
|
-
* `--add <rule> --max <n>` sets or moves the ceiling
|
|
210
|
+
* Adds or replaces entries, the `patch lint-ignore --add` semantics: an
|
|
211
|
+
* explicit `--add <rule>` turns a rule's ceilings into a bare waiver,
|
|
212
|
+
* `--add <rule> --max <n>` sets or moves the rule's ceiling, and a
|
|
213
|
+
* share-scoped ceiling replaces only its own share (plus any bare or
|
|
214
|
+
* share-less entry for the rule, which it would contradict).
|
|
161
215
|
*
|
|
162
216
|
* @returns The new list, or undefined when it is empty
|
|
163
217
|
*/
|
|
164
218
|
export function upsertLintIgnoreEntries(existing, additions) {
|
|
165
|
-
|
|
219
|
+
let next = [...existing];
|
|
166
220
|
for (const addition of additions) {
|
|
167
221
|
const rule = lintIgnoreRule(addition);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
222
|
+
const scoped = isLintIgnoreCeiling(addition) && addition.share !== undefined;
|
|
223
|
+
const replaced = (entry) => lintIgnoreRule(entry) === rule &&
|
|
224
|
+
(!scoped ||
|
|
225
|
+
!isLintIgnoreCeiling(entry) ||
|
|
226
|
+
entry.share === undefined ||
|
|
227
|
+
entryKey(entry) === entryKey(addition));
|
|
228
|
+
const index = next.findIndex(replaced);
|
|
229
|
+
const kept = next.filter((entry) => !replaced(entry));
|
|
230
|
+
// The addition takes the first replaced entry's place, so a moved
|
|
231
|
+
// ceiling keeps its position in the list.
|
|
232
|
+
next =
|
|
233
|
+
index === -1
|
|
234
|
+
? [...kept, addition]
|
|
235
|
+
: [...kept.slice(0, index), addition, ...kept.slice(index)];
|
|
173
236
|
}
|
|
174
237
|
return mergeLintIgnoreIds(next, undefined);
|
|
175
238
|
}
|
|
@@ -200,7 +263,7 @@ export function partitionLintIgnoredIssues(issues, checks) {
|
|
|
200
263
|
suppressed.push(issue);
|
|
201
264
|
continue;
|
|
202
265
|
}
|
|
203
|
-
const ceiling =
|
|
266
|
+
const ceiling = ceilingFor(issue, checks);
|
|
204
267
|
if (ceiling === undefined) {
|
|
205
268
|
kept.push(issue);
|
|
206
269
|
continue;
|
|
@@ -209,12 +272,31 @@ export function partitionLintIgnoredIssues(issues, checks) {
|
|
|
209
272
|
suppressed.push(issue);
|
|
210
273
|
continue;
|
|
211
274
|
}
|
|
275
|
+
// A notice never gates, so a ceiling does not bound it: one past `max`
|
|
276
|
+
// surfaces as the plain notice it is, not as an exceeded waiver.
|
|
277
|
+
if (issue.severity === 'notice') {
|
|
278
|
+
kept.push(issue);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
212
281
|
kept.push({ ...issue, message: `${issue.message} ${describeExceededCeiling(ceiling)}` });
|
|
213
282
|
}
|
|
214
283
|
return { kept, suppressed };
|
|
215
284
|
}
|
|
285
|
+
/** The ceiling that governs `issue`: its share's, else the rule's share-less one. */
|
|
286
|
+
function ceilingFor(issue, checks) {
|
|
287
|
+
const ceilings = checks?.ceilings;
|
|
288
|
+
if (ceilings === undefined)
|
|
289
|
+
return undefined;
|
|
290
|
+
if (issue.share !== undefined) {
|
|
291
|
+
const scoped = ceilings.get(ceilingKey(issue.check, issue.share));
|
|
292
|
+
if (scoped !== undefined)
|
|
293
|
+
return scoped;
|
|
294
|
+
}
|
|
295
|
+
return ceilings.get(issue.check);
|
|
296
|
+
}
|
|
216
297
|
function describeExceededCeiling(ceiling) {
|
|
217
|
-
|
|
298
|
+
const share = ceiling.share === undefined ? '' : ` (${ceiling.share} share)`;
|
|
299
|
+
return (`This exceeds the patch's lintIgnore ceiling for ${ceiling.rule}${share} ` +
|
|
218
300
|
`(max ${String(ceiling.max)}: ${ceiling.reason}).`);
|
|
219
301
|
}
|
|
220
302
|
/** True when `checks` waives `issue` (see {@link partitionLintIgnoredIssues}). */
|
|
@@ -242,6 +324,31 @@ export function filterLintIgnoredByOwners(issues, owners) {
|
|
|
242
324
|
return issues;
|
|
243
325
|
return issues.filter((issue) => !(byFile.get(issue.file) ?? []).some((checks) => isLintIgnored(issue, checks)));
|
|
244
326
|
}
|
|
327
|
+
function judgeWaiver(rule, max, share, issues, patchMeasurements) {
|
|
328
|
+
const findings = issues.filter((issue) => issue.check === rule && (share === undefined || issue.share === share));
|
|
329
|
+
const measured = findings
|
|
330
|
+
.map((issue) => issue.measurement)
|
|
331
|
+
.filter((value) => value !== undefined);
|
|
332
|
+
// A share-scoped ceiling has no patch-level fallback: the total counts
|
|
333
|
+
// both shares, which is not what it bounds.
|
|
334
|
+
const fallback = share !== undefined
|
|
335
|
+
? null
|
|
336
|
+
: rule === 'large-patch-lines'
|
|
337
|
+
? patchMeasurements.lines
|
|
338
|
+
: rule === 'large-patch-files'
|
|
339
|
+
? patchMeasurements.files
|
|
340
|
+
: null;
|
|
341
|
+
const measurement = measured.length > 0 ? Math.max(...measured) : fallback;
|
|
342
|
+
const gating = findings.filter((issue) => issue.severity !== 'notice');
|
|
343
|
+
const exceeded = max !== null &&
|
|
344
|
+
gating.some((issue) => issue.measurement !== undefined && issue.measurement > max);
|
|
345
|
+
const state = exceeded
|
|
346
|
+
? 'exceeded'
|
|
347
|
+
: gating.length > 0
|
|
348
|
+
? 'within'
|
|
349
|
+
: 'removable';
|
|
350
|
+
return { measurement, state };
|
|
351
|
+
}
|
|
245
352
|
/**
|
|
246
353
|
* Evaluates each ceiling on a patch against the findings its lint produced
|
|
247
354
|
* (both surfaced and suppressed).
|
|
@@ -257,26 +364,48 @@ export function evaluateLintIgnoreCeilings(entries, issues, patchMeasurements) {
|
|
|
257
364
|
for (const entry of entries ?? []) {
|
|
258
365
|
if (!isLintIgnoreCeiling(entry))
|
|
259
366
|
continue;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.
|
|
264
|
-
|
|
265
|
-
?
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
: null;
|
|
269
|
-
const measurement = measured.length > 0 ? Math.max(...measured) : fallback;
|
|
270
|
-
const gating = findings.some((issue) => issue.severity !== 'notice');
|
|
271
|
-
const state = measured.some((value) => value > entry.max)
|
|
272
|
-
? 'exceeded'
|
|
273
|
-
: gating
|
|
274
|
-
? 'within'
|
|
275
|
-
: 'removable';
|
|
276
|
-
statuses.push({ rule: entry.rule, max: entry.max, reason: entry.reason, measurement, state });
|
|
367
|
+
statuses.push({
|
|
368
|
+
rule: entry.rule,
|
|
369
|
+
max: entry.max,
|
|
370
|
+
reason: entry.reason,
|
|
371
|
+
...(entry.share !== undefined ? { share: entry.share } : {}),
|
|
372
|
+
...(entry.meta !== undefined ? { meta: entry.meta } : {}),
|
|
373
|
+
...judgeWaiver(entry.rule, entry.max, entry.share, issues, patchMeasurements),
|
|
374
|
+
});
|
|
277
375
|
}
|
|
278
376
|
return statuses;
|
|
279
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Evaluates every waiver on a patch, bare IDs included, so a waiver audit
|
|
380
|
+
* reads each one's current measurement from the report instead of
|
|
381
|
+
* re-measuring bare waivers from `suppressedIssues`.
|
|
382
|
+
*
|
|
383
|
+
* @param entries - The patch's `lintIgnore`
|
|
384
|
+
* @param issues - Every finding of the patch's lint, surfaced and suppressed
|
|
385
|
+
* @param patchMeasurements - See {@link evaluateLintIgnoreCeilings}
|
|
386
|
+
*/
|
|
387
|
+
export function evaluateLintIgnoreWaivers(entries, issues, patchMeasurements) {
|
|
388
|
+
return (entries ?? []).map((entry) => {
|
|
389
|
+
if (!isLintIgnoreCeiling(entry)) {
|
|
390
|
+
return {
|
|
391
|
+
kind: 'bare',
|
|
392
|
+
rule: entry,
|
|
393
|
+
max: null,
|
|
394
|
+
reason: null,
|
|
395
|
+
...judgeWaiver(entry, null, undefined, issues, patchMeasurements),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
kind: 'ceiling',
|
|
400
|
+
rule: entry.rule,
|
|
401
|
+
max: entry.max,
|
|
402
|
+
reason: entry.reason,
|
|
403
|
+
...(entry.share !== undefined ? { share: entry.share } : {}),
|
|
404
|
+
...(entry.meta !== undefined ? { meta: entry.meta } : {}),
|
|
405
|
+
...judgeWaiver(entry.rule, entry.max, entry.share, issues, patchMeasurements),
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
}
|
|
280
409
|
/**
|
|
281
410
|
* Notice-severity findings for ceilings that are no longer needed, so the
|
|
282
411
|
* waiver's removal is prompted by the tool that enforces it rather than
|
|
@@ -287,10 +416,11 @@ export function removableCeilingIssues(patchFilename, statuses) {
|
|
|
287
416
|
.filter((status) => status.state === 'removable')
|
|
288
417
|
.map((status) => {
|
|
289
418
|
const now = status.measurement === null ? 'below its notice band' : `at ${String(status.measurement)}`;
|
|
419
|
+
const share = status.share === undefined ? '' : `, ${status.share} share`;
|
|
290
420
|
return {
|
|
291
421
|
file: patchFilename,
|
|
292
422
|
check: 'lint-ignore-removable',
|
|
293
|
-
message: `lintIgnore ceiling for ${status.rule} (max ${String(status.max)}) is no longer needed: ` +
|
|
423
|
+
message: `lintIgnore ceiling for ${status.rule} (max ${String(status.max)}${share}) is no longer needed: ` +
|
|
294
424
|
`the measurement is ${now}, under the rule's own warning threshold. ` +
|
|
295
425
|
`Remove it with "fireforge patch lint-ignore ${patchFilename} --remove ${status.rule}".`,
|
|
296
426
|
severity: 'notice',
|
|
@@ -51,6 +51,16 @@ export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<stri
|
|
|
51
51
|
* `patch-lint-ownership.test.ts`.
|
|
52
52
|
*/
|
|
53
53
|
export declare function isTestScriptFile(file: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* For each test script, the ES modules (`.mjs`, `.sys.mjs`) the queue
|
|
56
|
+
* creates up to and including the test's own patch: what its imports can
|
|
57
|
+
* resolve to in a per-test checkJs program. A test no patch touches sees
|
|
58
|
+
* every module the queue creates.
|
|
59
|
+
*
|
|
60
|
+
* @param patchQueueCtx - The queue, in apply order
|
|
61
|
+
* @returns Lookup from a test script path to its resolvable modules
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolvableModulesForTests(patchQueueCtx: PatchQueueContext): (testFile: string) => ReadonlySet<string>;
|
|
54
64
|
/**
|
|
55
65
|
* Returns the set of patch-owned test `.js` files. Same
|
|
56
66
|
* ownership semantics as {@link resolvePatchOwnedSysMjs}. Consumed by the
|
|
@@ -84,6 +84,32 @@ export function isTestScriptFile(file) {
|
|
|
84
84
|
const basename = file.split('/').pop() ?? '';
|
|
85
85
|
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* For each test script, the ES modules (`.mjs`, `.sys.mjs`) the queue
|
|
89
|
+
* creates up to and including the test's own patch: what its imports can
|
|
90
|
+
* resolve to in a per-test checkJs program. A test no patch touches sees
|
|
91
|
+
* every module the queue creates.
|
|
92
|
+
*
|
|
93
|
+
* @param patchQueueCtx - The queue, in apply order
|
|
94
|
+
* @returns Lookup from a test script path to its resolvable modules
|
|
95
|
+
*/
|
|
96
|
+
export function resolvableModulesForTests(patchQueueCtx) {
|
|
97
|
+
const byTest = new Map();
|
|
98
|
+
const upTo = new Set();
|
|
99
|
+
for (const entry of patchQueueCtx.entries) {
|
|
100
|
+
for (const file of entry.newFiles.keys()) {
|
|
101
|
+
if (file.endsWith('.mjs') && !isTestScriptFile(file))
|
|
102
|
+
upTo.add(file);
|
|
103
|
+
}
|
|
104
|
+
const snapshot = new Set(upTo);
|
|
105
|
+
for (const file of [...entry.newFiles.keys(), ...entry.modifiedFileAdditions.keys()]) {
|
|
106
|
+
if (isTestScriptFile(file) && !byTest.has(file))
|
|
107
|
+
byTest.set(file, snapshot);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const all = upTo;
|
|
111
|
+
return (testFile) => byTest.get(testFile) ?? all;
|
|
112
|
+
}
|
|
87
113
|
/**
|
|
88
114
|
* Returns the set of patch-owned test `.js` files. Same
|
|
89
115
|
* ownership semantics as {@link resolvePatchOwnedSysMjs}. Consumed by the
|
|
@@ -248,19 +248,26 @@ function formatPatchSizeMessage(subject, band, thresholds) {
|
|
|
248
248
|
*/
|
|
249
249
|
function lineChecks(measurement, tier, thresholds, config) {
|
|
250
250
|
const perClass = measurement.metric === 'addedCode' && tier === 'general';
|
|
251
|
-
if (!perClass
|
|
251
|
+
if (!perClass) {
|
|
252
252
|
return [{ count: measurement.lines, thresholds: thresholds.lines, scope: '' }];
|
|
253
253
|
}
|
|
254
|
+
if (measurement.testLines === 0) {
|
|
255
|
+
return [
|
|
256
|
+
{ count: measurement.lines, thresholds: thresholds.lines, scope: '', share: 'product' },
|
|
257
|
+
];
|
|
258
|
+
}
|
|
254
259
|
return [
|
|
255
260
|
{
|
|
256
261
|
count: measurement.lines - measurement.testLines,
|
|
257
262
|
thresholds: thresholds.lines,
|
|
258
263
|
scope: ' in non-test files',
|
|
264
|
+
share: 'product',
|
|
259
265
|
},
|
|
260
266
|
{
|
|
261
267
|
count: measurement.testLines,
|
|
262
268
|
thresholds: getPatchSizeThresholds('test', config).lines,
|
|
263
269
|
scope: ' in test files (test tier)',
|
|
270
|
+
share: 'test',
|
|
264
271
|
},
|
|
265
272
|
];
|
|
266
273
|
}
|
|
@@ -317,6 +324,7 @@ export function lintPatchSize(filesAffected, measured, patchTier, config) {
|
|
|
317
324
|
message: formatPatchSizeMessage(subject, band, check.thresholds),
|
|
318
325
|
severity: band,
|
|
319
326
|
measurement: check.count,
|
|
327
|
+
...(check.share !== undefined ? { share: check.share } : {}),
|
|
320
328
|
});
|
|
321
329
|
}
|
|
322
330
|
return issues;
|
|
@@ -6,20 +6,29 @@
|
|
|
6
6
|
* or referencing foreign custom properties, but say nothing about
|
|
7
7
|
* `gap: 7px`, `font-size: 13px` or `z-index: 9999`. Those are the values a
|
|
8
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
|
|
10
|
-
* defines its steps, and the literals that are not steps at
|
|
11
|
-
* `auto`, a percentage).
|
|
9
|
+
* literal at a time. A scale names its properties, the token prefix (or
|
|
10
|
+
* prefixes) that defines its steps, and the literals that are not steps at
|
|
11
|
+
* all (`0`, `auto`, a percentage).
|
|
12
12
|
*
|
|
13
13
|
* Judged per value atom, after peeling `var()` innermost-first so a fallback
|
|
14
14
|
* is held to the same scale as the token it backs: `var(--x-gap, 7px)` is a
|
|
15
15
|
* literal in disguise. `calc()`, `min()`, `max()` and `clamp()` are
|
|
16
16
|
* transparent, so a value built from tokens passes and one that adds a raw
|
|
17
|
-
* length does not.
|
|
18
|
-
*
|
|
17
|
+
* length does not. Inside them, `allowInCalc` names literals legal only
|
|
18
|
+
* there (a unitless multiplier), and `offsetCap` lets a declaration
|
|
19
|
+
* subtract a few px from a token (a border inset) without allowing the px
|
|
20
|
+
* value anywhere else.
|
|
21
|
+
*
|
|
22
|
+
* Declarations inside descriptor at-rules (`@font-face`, `@counter-style`,
|
|
23
|
+
* `@property`, `@page`, …) are not property declarations and are not
|
|
24
|
+
* judged: `font-weight` in `@font-face` names a face, not a type step.
|
|
19
25
|
*
|
|
20
26
|
* 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
|
|
22
|
-
*
|
|
27
|
+
* stylesheet is not the fork's to put on a scale. A file a scale `exclude`s
|
|
28
|
+
* is not judged against that scale. A line carrying
|
|
29
|
+
* `fireforge-ignore: token-scale` is skipped. A scale with `minFiles` also
|
|
30
|
+
* fails the queue (`token-scale-vacuous`) when fewer created files carry
|
|
31
|
+
* its properties, so an emptied scan set cannot pass by judging nothing.
|
|
23
32
|
*/
|
|
24
33
|
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
25
34
|
import type { PatchLintTokenScale } from '../types/config.js';
|
|
@@ -31,9 +40,18 @@ export declare function findOffScaleAtom(value: string, scale: PatchLintTokenSca
|
|
|
31
40
|
/**
|
|
32
41
|
* Lints one patch-created CSS file against the configured scales.
|
|
33
42
|
*
|
|
34
|
-
* @param file - Engine-relative path, for the issue
|
|
43
|
+
* @param file - Engine-relative path, for the issue and the `exclude` globs
|
|
35
44
|
* @param css - The file's full content
|
|
36
45
|
* @param scales - `patchLint.tokenScales`
|
|
37
46
|
* @returns One `token-scale` error per off-scale declaration
|
|
38
47
|
*/
|
|
39
48
|
export declare function lintTokenScales(file: string, css: string, scales: readonly PatchLintTokenScale[]): PatchLintIssue[];
|
|
49
|
+
/**
|
|
50
|
+
* The `minFiles` floor of every scale over the queue's created stylesheets:
|
|
51
|
+
* one `token-scale-vacuous` error per scale that fewer created CSS files
|
|
52
|
+
* (outside its `exclude`) carry a declaration of.
|
|
53
|
+
*
|
|
54
|
+
* @param createdFiles - Every patch-created file's path and content
|
|
55
|
+
* @param scales - `patchLint.tokenScales`
|
|
56
|
+
*/
|
|
57
|
+
export declare function lintTokenScaleFloors(createdFiles: Iterable<readonly [string, string]>, scales: readonly PatchLintTokenScale[]): PatchLintIssue[];
|