@hominis/fireforge 0.39.0 → 0.40.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 +21 -0
- package/README.md +25 -1
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/export-flow.js +14 -6
- package/dist/src/commands/export-shared.js +14 -3
- package/dist/src/commands/lint-per-patch.js +83 -14
- 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 +22 -18
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/staged-dependency.js +102 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +106 -0
- package/dist/src/commands/re-export-options.js +3 -0
- package/dist/src/commands/re-export.js +42 -53
- package/dist/src/commands/run.js +18 -2
- package/dist/src/commands/status-check.d.ts +35 -0
- package/dist/src/commands/status-check.js +88 -0
- package/dist/src/commands/status.js +16 -5
- package/dist/src/commands/test-run.js +18 -3
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +16 -0
- package/dist/src/commands/tree.js +174 -0
- package/dist/src/core/config-validate.js +17 -0
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +17 -5
- package/dist/src/core/patch-export.d.ts +2 -2
- package/dist/src/core/patch-export.js +1 -1
- package/dist/src/core/patch-lint-checkjs.d.ts +19 -1
- package/dist/src/core/patch-lint-checkjs.js +56 -3
- 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-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint.d.ts +23 -0
- package/dist/src/core/patch-lint.js +30 -2
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/smoke-patterns.d.ts +24 -8
- package/dist/src/core/smoke-patterns.js +23 -12
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +20 -6
- 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/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 +124 -0
- package/dist/src/core/tree-store.d.ts +53 -0
- package/dist/src/core/tree-store.js +214 -0
- package/dist/src/core/typecheck-shim.d.ts +14 -0
- package/dist/src/core/typecheck-shim.js +41 -2
- 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/git.d.ts +8 -0
- package/dist/src/errors/git.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 +33 -2
- package/dist/src/types/config.d.ts +19 -0
- package/dist/src/utils/validation.d.ts +10 -0
- package/dist/src/utils/validation.js +29 -0
- package/package.json +6 -6
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { basename, resolve } from 'node:path';
|
|
29
29
|
import { pathExists } from '../utils/fs.js';
|
|
30
30
|
import { verbose } from '../utils/logger.js';
|
|
31
|
-
import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
|
|
31
|
+
import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, TEST_HARNESS_SHIM, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
// Public API
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
@@ -148,7 +148,7 @@ function createPathsResolver(ts, paths, baseDir, fileExists, onResolved) {
|
|
|
148
148
|
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
149
149
|
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
150
150
|
*/
|
|
151
|
-
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode) {
|
|
151
|
+
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode, builtinShimSuffix) {
|
|
152
152
|
const empty = { byFile: new Map(), global: [] };
|
|
153
153
|
if (resolutionOwned.size === 0)
|
|
154
154
|
return empty;
|
|
@@ -193,7 +193,11 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
193
193
|
let shimSource;
|
|
194
194
|
try {
|
|
195
195
|
const composed = await composeShimSource(projectRoot ?? repoDir, extraShimPath);
|
|
196
|
-
|
|
196
|
+
// The suffix (e.g. the loose test-harness baseline, FORGE G5) goes
|
|
197
|
+
// AFTER the consumer's extra shim: TypeScript resolves conflicting
|
|
198
|
+
// `declare var` redeclarations to the FIRST declaration, so a typed
|
|
199
|
+
// consumer declaration must precede the loose fallback to win.
|
|
200
|
+
shimSource = builtinShimSuffix ? `${composed.source}\n${builtinShimSuffix}` : composed.source;
|
|
197
201
|
if (composed.extraShimAppended) {
|
|
198
202
|
verbose(`checkJs: extra shim ${extraShimPath ?? ''} appended to Firefox globals shim`);
|
|
199
203
|
}
|
|
@@ -402,6 +406,55 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
402
406
|
export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint, projectRoot, reportScope) {
|
|
403
407
|
return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), reportScope);
|
|
404
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Runs the checkJs pass over patch-owned test `.js` files (FORGE G5,
|
|
411
|
+
* `patchLint.checkJsTestFiles`). Each test file gets its OWN small
|
|
412
|
+
* program — mochitest scripts share top-level scope only with their
|
|
413
|
+
* directory's `head*.js` helpers, so one multi-script program would emit
|
|
414
|
+
* false cross-file redeclaration errors — with roots = the test file plus
|
|
415
|
+
* any same-directory patch-owned `head*.js`, checked against the built-in
|
|
416
|
+
* Firefox shim + {@link TEST_HARNESS_SHIM} + the optional consumer
|
|
417
|
+
* `checkJsTestShim`. Only the target file's diagnostics are reported per
|
|
418
|
+
* program (head helpers report from their own program), and run-level
|
|
419
|
+
* errors are de-duplicated across programs.
|
|
420
|
+
*
|
|
421
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
422
|
+
* @param testFiles - Patch-owned test-script paths (repo-relative)
|
|
423
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
424
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
425
|
+
*/
|
|
426
|
+
export async function runCheckJsTestFilesGrouped(repoDir, testFiles, patchLint, projectRoot) {
|
|
427
|
+
const merged = { byFile: new Map(), global: [] };
|
|
428
|
+
if (testFiles.size === 0)
|
|
429
|
+
return merged;
|
|
430
|
+
const mode = modeFromPatchLintConfig(patchLint);
|
|
431
|
+
const seenGlobal = new Set();
|
|
432
|
+
const files = [...testFiles].sort((a, b) => a.localeCompare(b));
|
|
433
|
+
for (const file of files) {
|
|
434
|
+
const dir = file.slice(0, file.lastIndexOf('/') + 1);
|
|
435
|
+
const roots = new Set([file]);
|
|
436
|
+
for (const candidate of files) {
|
|
437
|
+
if (candidate === file)
|
|
438
|
+
continue;
|
|
439
|
+
const base = candidate.split('/').pop() ?? '';
|
|
440
|
+
if (candidate.startsWith(dir) && /^head(?:_.*)?\.js$/.test(base)) {
|
|
441
|
+
roots.add(candidate);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const result = await runCheckJsGrouped(repoDir, roots, patchLint.checkJsTestShim, projectRoot, mode, TEST_HARNESS_SHIM);
|
|
445
|
+
const own = result.byFile.get(file);
|
|
446
|
+
if (own && own.length > 0) {
|
|
447
|
+
merged.byFile.set(file, [...(merged.byFile.get(file) ?? []), ...own]);
|
|
448
|
+
}
|
|
449
|
+
for (const globalIssue of result.global) {
|
|
450
|
+
if (seenGlobal.has(globalIssue.message))
|
|
451
|
+
continue;
|
|
452
|
+
seenGlobal.add(globalIssue.message);
|
|
453
|
+
merged.global.push(globalIssue);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return merged;
|
|
457
|
+
}
|
|
405
458
|
/** Derives the {@link CheckJsMode} from a `patchLint` config block. */
|
|
406
459
|
function modeFromPatchLintConfig(patchLint) {
|
|
407
460
|
const strict = patchLint.checkJsStrict === true;
|
|
@@ -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 /
|
|
@@ -41,3 +41,24 @@ export declare function resolvePatchOwnedSysMjs(currentNewFiles: Set<string>, pa
|
|
|
41
41
|
* @returns Set of patch-owned chrome-subscript file paths
|
|
42
42
|
*/
|
|
43
43
|
export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Test-script predicate for the `checkJsTestFiles` pass (FORGE G5):
|
|
46
|
+
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
47
|
+
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
48
|
+
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
49
|
+
* patch-lint.ts imports this module, so the reverse edge would create an
|
|
50
|
+
* import cycle (dpdm gate). The agreement is pinned by
|
|
51
|
+
* `patch-lint-ownership.test.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isTestScriptFile(file: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Returns the set of patch-owned test `.js` files (FORGE G5). Same
|
|
56
|
+
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
57
|
+
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
58
|
+
* script-scope program.
|
|
59
|
+
*
|
|
60
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
61
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
62
|
+
* @returns Set of patch-owned test-script file paths
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolvePatchOwnedTestScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
@@ -67,3 +67,33 @@ export function resolvePatchOwnedSysMjs(currentNewFiles, patchQueueCtx) {
|
|
|
67
67
|
export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
|
|
68
68
|
return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Test-script predicate for the `checkJsTestFiles` pass (FORGE G5):
|
|
72
|
+
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
73
|
+
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
74
|
+
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
75
|
+
* patch-lint.ts imports this module, so the reverse edge would create an
|
|
76
|
+
* import cycle (dpdm gate). The agreement is pinned by
|
|
77
|
+
* `patch-lint-ownership.test.ts`.
|
|
78
|
+
*/
|
|
79
|
+
export function isTestScriptFile(file) {
|
|
80
|
+
if (!file.endsWith('.js') || file.endsWith('.sys.mjs'))
|
|
81
|
+
return false;
|
|
82
|
+
if (file.includes('/test/'))
|
|
83
|
+
return true;
|
|
84
|
+
const basename = file.split('/').pop() ?? '';
|
|
85
|
+
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the set of patch-owned test `.js` files (FORGE G5). Same
|
|
89
|
+
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
90
|
+
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
91
|
+
* script-scope program.
|
|
92
|
+
*
|
|
93
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
94
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
95
|
+
* @returns Set of patch-owned test-script file paths
|
|
96
|
+
*/
|
|
97
|
+
export function resolvePatchOwnedTestScripts(currentNewFiles, patchQueueCtx) {
|
|
98
|
+
return resolveOwned(currentNewFiles, patchQueueCtx, isTestScriptFile);
|
|
99
|
+
}
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Public re-exports for {@link ./patch-lint.ts}. Split out so the
|
|
3
3
|
* orchestrator stays within the ESLint `max-lines` budget.
|
|
4
4
|
*/
|
|
5
|
-
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER,
|
|
5
|
+
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
|
|
6
6
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
@@ -128,6 +128,21 @@ export type PatchSizeTierDecision = {
|
|
|
128
128
|
* defensive-for-tests choice.
|
|
129
129
|
*/
|
|
130
130
|
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
131
|
+
/**
|
|
132
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
133
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
134
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
135
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
136
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
137
|
+
*/
|
|
138
|
+
export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): {
|
|
139
|
+
lines: {
|
|
140
|
+
notice: number;
|
|
141
|
+
warning: number;
|
|
142
|
+
error: number;
|
|
143
|
+
};
|
|
144
|
+
maxFiles: number;
|
|
145
|
+
};
|
|
131
146
|
/**
|
|
132
147
|
* Checks patch size and emits advisory warnings.
|
|
133
148
|
*
|
|
@@ -179,6 +194,14 @@ export interface LintExportedPatchOptions {
|
|
|
179
194
|
* attributes findings per patch instead of rebuilding per patch.
|
|
180
195
|
*/
|
|
181
196
|
precomputedCheckJs?: readonly PatchLintIssue[];
|
|
197
|
+
/**
|
|
198
|
+
* Invoked with the issues DROPPED by the `ignoreChecks` waiver filter,
|
|
199
|
+
* when any were (FORGE G10). Suppression semantics are unchanged — the
|
|
200
|
+
* returned issue list still excludes them — but the caller can report
|
|
201
|
+
* the measurement a waived size finding carried (per-patch NOTICE lines
|
|
202
|
+
* and the `--report` JSON) instead of losing it entirely.
|
|
203
|
+
*/
|
|
204
|
+
onSuppressed?: (suppressed: PatchLintIssue[]) => void;
|
|
182
205
|
}
|
|
183
206
|
/**
|
|
184
207
|
* Runs all patch lint checks and returns combined issues.
|
|
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { pathExists, readText } from '../utils/fs.js';
|
|
4
4
|
import { stripJsComments } from '../utils/regex.js';
|
|
5
5
|
import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, hasUpstreamMplBlockHeader, } from './license-headers.js';
|
|
6
|
-
import { invokePatchLintCheckJs } from './patch-lint-checkjs.js';
|
|
6
|
+
import { invokePatchLintCheckJs, runCheckJsTestFilesGrouped } from './patch-lint-checkjs.js';
|
|
7
7
|
import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
|
|
8
8
|
import { lintPatchedCss } from './patch-lint-css.js';
|
|
9
9
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
@@ -12,7 +12,7 @@ import { hasRelativeImport } from './patch-lint-imports.js';
|
|
|
12
12
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
13
|
import { lintMozBuildSortedLists } from './patch-lint-mozbuild.js';
|
|
14
14
|
import { lintObserverTopics } from './patch-lint-observer.js';
|
|
15
|
-
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
15
|
+
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Cross-patch lint re-exports
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
@@ -481,6 +481,19 @@ export function resolvePatchSizeTier(filesAffected, patchTier) {
|
|
|
481
481
|
return { tier: 'branding', source: 'auto' };
|
|
482
482
|
return { tier: 'general' };
|
|
483
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
486
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
487
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
488
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
489
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
490
|
+
*/
|
|
491
|
+
export function getPatchSizeThresholds(tier) {
|
|
492
|
+
return {
|
|
493
|
+
lines: { ...PATCH_LINE_THRESHOLDS[tier] },
|
|
494
|
+
maxFiles: PATCH_FILES_THRESHOLDS[tier],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
484
497
|
/**
|
|
485
498
|
* Checks patch size and emits advisory warnings.
|
|
486
499
|
*
|
|
@@ -644,6 +657,17 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
644
657
|
}
|
|
645
658
|
else if (config.patchLint?.checkJs) {
|
|
646
659
|
issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir), options?.checkJsReportScope)));
|
|
660
|
+
// Export-time twin of per-patch lint's test-file pass (FORGE G5), so
|
|
661
|
+
// export/re-export and `lint --per-patch` agree on the checked surface.
|
|
662
|
+
if (config.patchLint.checkJsTestFiles === true) {
|
|
663
|
+
const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir));
|
|
664
|
+
issues.push(...testGrouped.global);
|
|
665
|
+
for (const [rel, list] of testGrouped.byFile) {
|
|
666
|
+
if (options?.checkJsReportScope && !options.checkJsReportScope.has(rel))
|
|
667
|
+
continue;
|
|
668
|
+
issues.push(...list);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
647
671
|
}
|
|
648
672
|
// Filter out ignored checks last so every rule still runs (keeps the
|
|
649
673
|
// implementation uniform) but suppressed rules do not surface. We do not
|
|
@@ -651,6 +675,10 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
651
675
|
// inline `fireforge-ignore: <check>` markers work in the CSS and
|
|
652
676
|
// forward-import rules.
|
|
653
677
|
if (ignoreChecks && ignoreChecks.size > 0) {
|
|
678
|
+
const suppressed = issues.filter((issue) => ignoreChecks.has(issue.check));
|
|
679
|
+
if (suppressed.length > 0) {
|
|
680
|
+
options?.onSuppressed?.(suppressed);
|
|
681
|
+
}
|
|
654
682
|
return issues.filter((issue) => !ignoreChecks.has(issue.check));
|
|
655
683
|
}
|
|
656
684
|
return issues;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is an implementation detail.
|
|
6
6
|
*/
|
|
7
7
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
8
|
-
export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type
|
|
8
|
+
export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
9
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
10
10
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -22,24 +22,40 @@ export declare const SMOKE_ERROR_PATTERNS: readonly RegExp[];
|
|
|
22
22
|
/**
|
|
23
23
|
* Returns `true` when `line` matches any pattern in
|
|
24
24
|
* {@link SMOKE_ERROR_PATTERNS}. Does not consult the allowlist — that step
|
|
25
|
-
* lives in {@link
|
|
25
|
+
* lives in {@link matchAllowlist}, so the smoke runner can count
|
|
26
26
|
* allowlisted hits separately from raw error matches for its summary.
|
|
27
27
|
*/
|
|
28
28
|
export declare function matchesSmokeError(line: string): boolean;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* One compiled allowlist entry with its provenance retained so the smoke
|
|
31
|
+
* summary can attribute hits per entry (FORGE G8) — an entry that silently
|
|
32
|
+
* stops matching (its suppressed shape changed upstream) is only
|
|
33
|
+
* detectable when zero-hit entries are visible.
|
|
32
34
|
*/
|
|
33
|
-
export
|
|
35
|
+
export interface CompiledAllowlistEntry {
|
|
36
|
+
pattern: RegExp;
|
|
37
|
+
/** Verbatim source text of the entry. */
|
|
38
|
+
source: string;
|
|
39
|
+
/** Human-readable origin: `allow.txt:12` or `--console-allow #2`. */
|
|
40
|
+
origin: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the index of the FIRST entry in `allow` matching `line`, or -1.
|
|
44
|
+
* First-match attribution is deterministic and cheap; a line matching
|
|
45
|
+
* several entries credits the earliest one. Safe to call with an empty
|
|
46
|
+
* allowlist (always returns -1).
|
|
47
|
+
*/
|
|
48
|
+
export declare function matchAllowlist(line: string, allow: readonly CompiledAllowlistEntry[]): number;
|
|
34
49
|
/**
|
|
35
50
|
* Parses a newline-delimited allowlist file body. Lines are trimmed; blank
|
|
36
51
|
* lines and `#`-prefixed comments are skipped. Each remaining line is
|
|
37
|
-
* compiled as a RegExp
|
|
38
|
-
*
|
|
52
|
+
* compiled as a RegExp with its `<file>:<line>` origin retained. A bad
|
|
53
|
+
* pattern throws immediately — better to fail fast at CLI parse time than
|
|
54
|
+
* to silently let a typo match nothing.
|
|
39
55
|
*/
|
|
40
|
-
export declare function compileAllowlistFromFile(body: string, sourcePath: string):
|
|
56
|
+
export declare function compileAllowlistFromFile(body: string, sourcePath: string): CompiledAllowlistEntry[];
|
|
41
57
|
/**
|
|
42
58
|
* Compiles an array of regex-string inputs (e.g. repeated `--console-allow`
|
|
43
59
|
* flag values). Same fail-fast semantics as {@link compileAllowlistFromFile}.
|
|
44
60
|
*/
|
|
45
|
-
export declare function compileAllowlistFromStrings(sources: readonly string[]):
|
|
61
|
+
export declare function compileAllowlistFromStrings(sources: readonly string[]): CompiledAllowlistEntry[];
|
|
@@ -32,7 +32,7 @@ export const SMOKE_ERROR_PATTERNS = [
|
|
|
32
32
|
/**
|
|
33
33
|
* Returns `true` when `line` matches any pattern in
|
|
34
34
|
* {@link SMOKE_ERROR_PATTERNS}. Does not consult the allowlist — that step
|
|
35
|
-
* lives in {@link
|
|
35
|
+
* lives in {@link matchAllowlist}, so the smoke runner can count
|
|
36
36
|
* allowlisted hits separately from raw error matches for its summary.
|
|
37
37
|
*/
|
|
38
38
|
export function matchesSmokeError(line) {
|
|
@@ -44,22 +44,25 @@ export function matchesSmokeError(line) {
|
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Returns
|
|
48
|
-
*
|
|
47
|
+
* Returns the index of the FIRST entry in `allow` matching `line`, or -1.
|
|
48
|
+
* First-match attribution is deterministic and cheap; a line matching
|
|
49
|
+
* several entries credits the earliest one. Safe to call with an empty
|
|
50
|
+
* allowlist (always returns -1).
|
|
49
51
|
*/
|
|
50
|
-
export function
|
|
51
|
-
for (
|
|
52
|
-
if (pattern.test(line)) {
|
|
53
|
-
return
|
|
52
|
+
export function matchAllowlist(line, allow) {
|
|
53
|
+
for (let i = 0; i < allow.length; i++) {
|
|
54
|
+
if (allow[i]?.pattern.test(line)) {
|
|
55
|
+
return i;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
return
|
|
58
|
+
return -1;
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* Parses a newline-delimited allowlist file body. Lines are trimmed; blank
|
|
60
62
|
* lines and `#`-prefixed comments are skipped. Each remaining line is
|
|
61
|
-
* compiled as a RegExp
|
|
62
|
-
*
|
|
63
|
+
* compiled as a RegExp with its `<file>:<line>` origin retained. A bad
|
|
64
|
+
* pattern throws immediately — better to fail fast at CLI parse time than
|
|
65
|
+
* to silently let a typo match nothing.
|
|
63
66
|
*/
|
|
64
67
|
export function compileAllowlistFromFile(body, sourcePath) {
|
|
65
68
|
const lines = body.split(/\r?\n/);
|
|
@@ -69,7 +72,11 @@ export function compileAllowlistFromFile(body, sourcePath) {
|
|
|
69
72
|
if (!line || line.startsWith('#'))
|
|
70
73
|
return;
|
|
71
74
|
try {
|
|
72
|
-
compiled.push(
|
|
75
|
+
compiled.push({
|
|
76
|
+
pattern: new RegExp(line),
|
|
77
|
+
source: line,
|
|
78
|
+
origin: `${sourcePath}:${String(index + 1)}`,
|
|
79
|
+
});
|
|
73
80
|
}
|
|
74
81
|
catch (error) {
|
|
75
82
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -88,7 +95,11 @@ export function compileAllowlistFromStrings(sources) {
|
|
|
88
95
|
const compiled = [];
|
|
89
96
|
sources.forEach((source, index) => {
|
|
90
97
|
try {
|
|
91
|
-
compiled.push(
|
|
98
|
+
compiled.push({
|
|
99
|
+
pattern: new RegExp(source),
|
|
100
|
+
source,
|
|
101
|
+
origin: `--console-allow #${String(index + 1)}`,
|
|
102
|
+
});
|
|
92
103
|
}
|
|
93
104
|
catch (error) {
|
|
94
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -49,6 +49,14 @@ export interface ClassifiedFile extends StatusFile {
|
|
|
49
49
|
* single claim is fully captured by the classification itself.
|
|
50
50
|
*/
|
|
51
51
|
claimedBy?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Owning patch filename when exactly one patch claims this path
|
|
54
|
+
* (patch-backed, patch-owned-drift, and single-owner furnace entries).
|
|
55
|
+
* Unset for unowned, branding-generated, unowned-furnace, and conflict
|
|
56
|
+
* entries — conflicts carry `claimedBy` instead. Exposed through
|
|
57
|
+
* `status --json` as the `patch` field (FORGE G11).
|
|
58
|
+
*/
|
|
59
|
+
owner?: string;
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
54
62
|
* Builds the file → owning-patch-filenames multimap from manifest rows.
|
|
@@ -66,6 +74,14 @@ export declare function buildPatchClaims(manifestPatches: ReadonlyArray<{
|
|
|
66
74
|
* Single source of truth — status-output.ts renders from the same logic.
|
|
67
75
|
*/
|
|
68
76
|
export declare function getPrimaryStatusCode(status: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* True for the branding paths whose content FireForge generates itself
|
|
79
|
+
* (configure.sh, brand.properties/ftl, browser/moz.configure). Unlike the
|
|
80
|
+
* broader branding-root test, a brand-new unowned file under the branding
|
|
81
|
+
* directory is NOT generated — it is a patch candidate and must stay
|
|
82
|
+
* visible as unmanaged (the Assets.car precedent).
|
|
83
|
+
*/
|
|
84
|
+
export declare function isGeneratedBrandingPath(file: string, binaryName: string): boolean;
|
|
69
85
|
/**
|
|
70
86
|
* Classifies files into patch-backed, unmanaged, branding, furnace, or
|
|
71
87
|
* conflict buckets.
|