@hominis/fireforge 0.38.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 +41 -0
- package/README.md +25 -1
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/build.js +8 -3
- package/dist/src/commands/discard.js +75 -50
- package/dist/src/commands/export-flow.d.ts +1 -1
- package/dist/src/commands/export-flow.js +21 -10
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.js +31 -5
- package/dist/src/commands/import.js +5 -0
- package/dist/src/commands/lint-per-patch.js +97 -18
- 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 +23 -19
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +20 -1
- package/dist/src/commands/patch/move-files-into.d.ts +47 -0
- package/dist/src/commands/patch/move-files-into.js +245 -0
- package/dist/src/commands/patch/move-files.js +27 -13
- package/dist/src/commands/patch/rename.js +5 -2
- 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/reset.js +5 -0
- 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-output.d.ts +9 -0
- package/dist/src/commands/status-output.js +33 -1
- package/dist/src/commands/status.js +33 -8
- package/dist/src/commands/test-diagnose.d.ts +1 -1
- package/dist/src/commands/test-diagnose.js +9 -3
- package/dist/src/commands/test-modes.d.ts +21 -0
- package/dist/src/commands/test-modes.js +42 -0
- package/dist/src/commands/test-run.d.ts +2 -0
- package/dist/src/commands/test-run.js +26 -5
- package/dist/src/commands/test.js +38 -32
- 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/build-baseline-types.d.ts +7 -0
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +2 -1
- package/dist/src/core/build-prepare.d.ts +9 -0
- package/dist/src/core/build-prepare.js +24 -0
- package/dist/src/core/config-validate.js +25 -0
- package/dist/src/core/discard-baseline.d.ts +60 -0
- package/dist/src/core/discard-baseline.js +232 -0
- package/dist/src/core/license-headers.d.ts +12 -0
- package/dist/src/core/license-headers.js +27 -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 +12 -3
- package/dist/src/core/patch-export.js +13 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -2
- package/dist/src/core/patch-lint-checkjs.js +85 -16
- package/dist/src/core/patch-lint-cross.d.ts +9 -1
- package/dist/src/core/patch-lint-cross.js +69 -9
- 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-mozbuild.d.ts +18 -0
- package/dist/src/core/patch-lint-mozbuild.js +104 -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 +34 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-policy.d.ts +14 -1
- package/dist/src/core/patch-policy.js +13 -2
- 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/test-harness-crash.d.ts +10 -0
- package/dist/src/core/test-harness-crash.js +16 -0
- package/dist/src/core/test-stale-check.d.ts +15 -0
- package/dist/src/core/test-stale-check.js +37 -1
- 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 +82 -193
- package/dist/src/core/token-variant.d.ts +7 -0
- package/dist/src/core/token-variant.js +9 -0
- 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 +27 -0
- package/dist/src/core/typecheck-shim.js +58 -4
- package/dist/src/core/typecheck.js +14 -3
- 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 +45 -2
- package/dist/src/types/config.d.ts +34 -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 } 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
|
}
|
|
@@ -312,7 +316,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
312
316
|
},
|
|
313
317
|
};
|
|
314
318
|
const program = ts.createProgram(rootFiles, options, host);
|
|
315
|
-
const byFile = groupOwnedDiagnostics(ts, [...program.getSemanticDiagnostics(), ...program.getSyntacticDiagnostics()], relByAbsolute);
|
|
319
|
+
const byFile = groupOwnedDiagnostics(ts, [...program.getSemanticDiagnostics(), ...program.getSyntacticDiagnostics()], relByAbsolute, mode?.undefinedIdentifiers ?? 'warning');
|
|
316
320
|
verbose(`checkJs: analyzed ${rootFiles.length - 1} file(s) across ${byFile.size} owning file(s)`);
|
|
317
321
|
return { byFile, global: [] };
|
|
318
322
|
}
|
|
@@ -323,11 +327,14 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
323
327
|
* or shim files are dropped — only owned files (in `relByAbsolute`) carry
|
|
324
328
|
* findings.
|
|
325
329
|
*/
|
|
326
|
-
function groupOwnedDiagnostics(ts, diagnostics, relByAbsolute) {
|
|
330
|
+
function groupOwnedDiagnostics(ts, diagnostics, relByAbsolute, undefinedIdentifiers = 'warning') {
|
|
327
331
|
const byFile = new Map();
|
|
328
332
|
for (const diag of diagnostics) {
|
|
329
333
|
if (SUPPRESSED_DIAGNOSTIC_CODES.has(diag.code))
|
|
330
334
|
continue;
|
|
335
|
+
const isUndefinedIdentifier = UNDEFINED_IDENTIFIER_CODES.has(diag.code);
|
|
336
|
+
if (isUndefinedIdentifier && undefinedIdentifiers === 'off')
|
|
337
|
+
continue;
|
|
331
338
|
const sourceFile = diag.file;
|
|
332
339
|
if (!sourceFile)
|
|
333
340
|
continue;
|
|
@@ -339,12 +346,20 @@ function groupOwnedDiagnostics(ts, diagnostics, relByAbsolute) {
|
|
|
339
346
|
const messageText = typeof diag.messageText === 'string'
|
|
340
347
|
? diag.messageText
|
|
341
348
|
: ts.flattenDiagnosticMessageText(diag.messageText, '\n');
|
|
342
|
-
const severity =
|
|
349
|
+
const severity = isUndefinedIdentifier
|
|
350
|
+
? undefinedIdentifiers === 'error'
|
|
351
|
+
? 'error'
|
|
352
|
+
: 'warning'
|
|
353
|
+
: diag.category === ts.DiagnosticCategory.Error
|
|
354
|
+
? 'error'
|
|
355
|
+
: 'warning';
|
|
343
356
|
const bucket = byFile.get(relPath) ?? [];
|
|
344
357
|
bucket.push({
|
|
345
358
|
file: relPath,
|
|
346
359
|
check: 'checkjs-type-error',
|
|
347
|
-
message:
|
|
360
|
+
message: isUndefinedIdentifier
|
|
361
|
+
? `Line ${line}: ${messageText} ${UNDEFINED_IDENTIFIER_HINT}`
|
|
362
|
+
: `Line ${line}: ${messageText}`,
|
|
348
363
|
severity,
|
|
349
364
|
});
|
|
350
365
|
byFile.set(relPath, bucket);
|
|
@@ -389,11 +404,69 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
389
404
|
* not surface other patches' diagnostics)
|
|
390
405
|
*/
|
|
391
406
|
export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint, projectRoot, reportScope) {
|
|
407
|
+
return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), reportScope);
|
|
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
|
+
}
|
|
458
|
+
/** Derives the {@link CheckJsMode} from a `patchLint` config block. */
|
|
459
|
+
function modeFromPatchLintConfig(patchLint) {
|
|
392
460
|
const strict = patchLint.checkJsStrict === true;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
461
|
+
return {
|
|
462
|
+
strict,
|
|
463
|
+
...(strict && patchLint.checkJsCompilerOptions
|
|
464
|
+
? { compilerOptions: patchLint.checkJsCompilerOptions }
|
|
465
|
+
: {}),
|
|
466
|
+
...(patchLint.undefinedIdentifiers !== undefined
|
|
467
|
+
? { undefinedIdentifiers: patchLint.undefinedIdentifiers }
|
|
468
|
+
: {}),
|
|
469
|
+
};
|
|
397
470
|
}
|
|
398
471
|
/**
|
|
399
472
|
* Grouped variant of {@link invokePatchLintCheckJs}: builds one queue-wide
|
|
@@ -408,9 +481,5 @@ export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint
|
|
|
408
481
|
* @param projectRoot - FireForge project root for shim resolution
|
|
409
482
|
*/
|
|
410
483
|
export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, patchLint, projectRoot) {
|
|
411
|
-
|
|
412
|
-
const mode = strict && patchLint.checkJsCompilerOptions
|
|
413
|
-
? { strict, compilerOptions: patchLint.checkJsCompilerOptions }
|
|
414
|
-
: { strict };
|
|
415
|
-
return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode);
|
|
484
|
+
return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint));
|
|
416
485
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* single module.
|
|
11
11
|
*/
|
|
12
12
|
import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
13
|
+
import type { FireForgeConfig, PatchPolicyConfig } from '../types/config.js';
|
|
13
14
|
/**
|
|
14
15
|
* One patch's contribution to {@link PatchQueueContext}.
|
|
15
16
|
*
|
|
@@ -57,6 +58,13 @@ export interface PatchQueueEntry {
|
|
|
57
58
|
export interface PatchQueueContext {
|
|
58
59
|
/** Entries in application order (lowest `order` first). */
|
|
59
60
|
entries: PatchQueueEntry[];
|
|
61
|
+
/**
|
|
62
|
+
* Optional patchPolicy config. When present, the forward-import rule
|
|
63
|
+
* validates its "Closest legal ordinal" suggestion against the importing
|
|
64
|
+
* patch's category range and suppresses it when no legal ordinal exists
|
|
65
|
+
* (FORGE F14). Absent = current behavior (hint always printed).
|
|
66
|
+
*/
|
|
67
|
+
patchPolicy?: PatchPolicyConfig;
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
70
|
* Builds a {@link PatchQueueContext} by reading every .patch file in the
|
|
@@ -70,7 +78,7 @@ export interface PatchQueueContext {
|
|
|
70
78
|
*
|
|
71
79
|
* @param patchesDir - Path to the patches directory
|
|
72
80
|
*/
|
|
73
|
-
export declare function buildPatchQueueContext(patchesDir: string): Promise<PatchQueueContext>;
|
|
81
|
+
export declare function buildPatchQueueContext(patchesDir: string, config?: FireForgeConfig): Promise<PatchQueueContext>;
|
|
74
82
|
/**
|
|
75
83
|
* Returns the raw `path → patches[]` map of files created in `new file
|
|
76
84
|
* mode` by at least one patch in the queue. Paths created by only one
|
|
@@ -18,6 +18,7 @@ import { stripJsComments } from '../utils/regex.js';
|
|
|
18
18
|
import { discoverPatches } from './patch-files.js';
|
|
19
19
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
20
20
|
import { loadPatchesManifest } from './patch-manifest-io.js';
|
|
21
|
+
import { categoryRangeForOrder, categoryRangeLabel } from './patch-policy.js';
|
|
21
22
|
import { extractNewFileContent } from './patch-transform.js';
|
|
22
23
|
/**
|
|
23
24
|
* Builds a {@link PatchQueueContext} by reading every .patch file in the
|
|
@@ -31,7 +32,7 @@ import { extractNewFileContent } from './patch-transform.js';
|
|
|
31
32
|
*
|
|
32
33
|
* @param patchesDir - Path to the patches directory
|
|
33
34
|
*/
|
|
34
|
-
export async function buildPatchQueueContext(patchesDir) {
|
|
35
|
+
export async function buildPatchQueueContext(patchesDir, config) {
|
|
35
36
|
const patches = await discoverPatches(patchesDir);
|
|
36
37
|
const manifest = await loadPatchesManifest(patchesDir);
|
|
37
38
|
const metadataByFilename = new Map();
|
|
@@ -78,7 +79,7 @@ export async function buildPatchQueueContext(patchesDir) {
|
|
|
78
79
|
}
|
|
79
80
|
// Sort by order so rules can rely on entries being in apply order.
|
|
80
81
|
entries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
81
|
-
return { entries };
|
|
82
|
+
return { entries, ...(config?.patchPolicy ? { patchPolicy: config.patchPolicy } : {}) };
|
|
82
83
|
}
|
|
83
84
|
/**
|
|
84
85
|
* Returns the raw `path → patches[]` map of files created in `new file
|
|
@@ -292,6 +293,34 @@ export function extractImportSpecifiersWithLines(source) {
|
|
|
292
293
|
collectGetterSpecifiers(stripped, results, offsetToLine);
|
|
293
294
|
return results;
|
|
294
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Bare `Name: "url"` property lines added inside a PRE-EXISTING
|
|
298
|
+
* `defineESModuleGetters` (or `XPCOMUtils` lazy-getter) map. Applied ONLY to
|
|
299
|
+
* `modifiedFileAdditions`: a patch that inserts one property into an
|
|
300
|
+
* existing getter object never carries the `defineESModuleGetters(` opener
|
|
301
|
+
* in its added lines, so the balanced-brace walk in
|
|
302
|
+
* {@link extractImportSpecifiersWithLines} cannot see it (FORGE F3). The
|
|
303
|
+
* URL-scheme restriction keeps ordinary object literals
|
|
304
|
+
* (`label: "Foo.sys.mjs"`) out; the leaf-extension filter downstream does
|
|
305
|
+
* the rest.
|
|
306
|
+
*/
|
|
307
|
+
const BARE_GETTER_PROPERTY_LINE = /^\s*(?:["'])?[A-Za-z_$][\w$]*(?:["'])?\s*:\s*["']((?:resource|chrome|moz-src):\/\/[^"']+)["']\s*,?\s*$/;
|
|
308
|
+
/**
|
|
309
|
+
* Extracts specifiers from bare getter-property lines in added-lines-only
|
|
310
|
+
* content. Comment stripping preserves line alignment, so the reported line
|
|
311
|
+
* numbers match what {@link findForwardImportIgnoreLines} walks.
|
|
312
|
+
*/
|
|
313
|
+
function extractBareGetterSpecifiers(source) {
|
|
314
|
+
const stripped = stripJsComments(source);
|
|
315
|
+
const results = [];
|
|
316
|
+
const lines = stripped.split('\n');
|
|
317
|
+
for (let i = 0; i < lines.length; i++) {
|
|
318
|
+
const match = BARE_GETTER_PROPERTY_LINE.exec(lines[i] ?? '');
|
|
319
|
+
if (match?.[1])
|
|
320
|
+
results.push({ specifier: match[1], line: i });
|
|
321
|
+
}
|
|
322
|
+
return results;
|
|
323
|
+
}
|
|
295
324
|
/**
|
|
296
325
|
* Marker comment operators can use to suppress the forward-import rule
|
|
297
326
|
* for imports that resolve to a basename false positive (two unrelated
|
|
@@ -376,11 +405,22 @@ function buildNewFileIndex(ctx) {
|
|
|
376
405
|
* pre-existing HEAD imports that happen to collide with a later-created file.
|
|
377
406
|
*/
|
|
378
407
|
function eachForwardImportSite(ctx, newFileIndex, visit) {
|
|
379
|
-
const checkSite = (entry, sitePath, content) => {
|
|
408
|
+
const checkSite = (entry, sitePath, content, includeBareGetterLines = false) => {
|
|
380
409
|
if (!isForwardImportableFile(sitePath))
|
|
381
410
|
return;
|
|
411
|
+
const sites = extractImportSpecifiersWithLines(content);
|
|
412
|
+
if (includeBareGetterLines) {
|
|
413
|
+
// Dedupe by (specifier, line): a patch that adds the whole getter
|
|
414
|
+
// call carries both the opener (found by the balanced walk) and the
|
|
415
|
+
// property line (found here) — report each site once.
|
|
416
|
+
const seen = new Set(sites.map((site) => `${site.specifier}|${String(site.line)}`));
|
|
417
|
+
for (const extra of extractBareGetterSpecifiers(content)) {
|
|
418
|
+
if (!seen.has(`${extra.specifier}|${String(extra.line)}`))
|
|
419
|
+
sites.push(extra);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
382
422
|
const ignoreLines = findForwardImportIgnoreLines(content);
|
|
383
|
-
for (const { specifier, line } of
|
|
423
|
+
for (const { specifier, line } of sites) {
|
|
384
424
|
if (ignoreLines.has(line))
|
|
385
425
|
continue;
|
|
386
426
|
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
@@ -400,7 +440,7 @@ function eachForwardImportSite(ctx, newFileIndex, visit) {
|
|
|
400
440
|
for (const [path, content] of entry.newFiles)
|
|
401
441
|
checkSite(entry, path, content);
|
|
402
442
|
for (const [path, added] of entry.modifiedFileAdditions)
|
|
403
|
-
checkSite(entry, path, added);
|
|
443
|
+
checkSite(entry, path, added, true);
|
|
404
444
|
}
|
|
405
445
|
}
|
|
406
446
|
/**
|
|
@@ -454,8 +494,28 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
454
494
|
.join(',');
|
|
455
495
|
// Lowest ordinal that lands AFTER every later-ordered creator —
|
|
456
496
|
// turns the operator's "guess and re-run" loop into a single shot
|
|
457
|
-
// when the only fix is reordering.
|
|
497
|
+
// when the only fix is reordering. When patchPolicy is available and
|
|
498
|
+
// that ordinal falls outside the importing patch's category range,
|
|
499
|
+
// `patch reorder` would refuse it — suppress the hint and point at
|
|
500
|
+
// the staged-dependency remedy instead (FORGE F14).
|
|
458
501
|
const suggestedOrder = Math.max(...laterOwners.map((o) => o.order)) + 1;
|
|
502
|
+
const category = entry.metadata?.category ?? /^\d+-([a-z]+)-/.exec(entry.filename)?.[1];
|
|
503
|
+
let ordinalHint = `Closest legal ordinal that satisfies this dependency: ${suggestedOrder}.`;
|
|
504
|
+
if (ctx.patchPolicy &&
|
|
505
|
+
category !== undefined &&
|
|
506
|
+
categoryRangeForOrder(ctx.patchPolicy, category, suggestedOrder) === null) {
|
|
507
|
+
const label = categoryRangeLabel(ctx.patchPolicy.ranges, category);
|
|
508
|
+
ordinalHint =
|
|
509
|
+
`No legal ordinal in the ${category} category range (${label}) lands after the ` +
|
|
510
|
+
'creating patch(es); declaring the staged dependency is the recommended remedy.';
|
|
511
|
+
}
|
|
512
|
+
// The exact per-owner invocation turns the refusal into a paste-and-run
|
|
513
|
+
// remedy instead of a flag-discovery exercise (FORGE F3).
|
|
514
|
+
const stagedCommands = laterOwners
|
|
515
|
+
.map((o) => `fireforge patch staged-dependency ${entry.filename} --add ` +
|
|
516
|
+
`--file ${sitePath} --specifier "${specifier}" ` +
|
|
517
|
+
`--creates ${o.fullPath} --owner ${o.filename}`)
|
|
518
|
+
.join('; ');
|
|
459
519
|
issues.push({
|
|
460
520
|
file: sitePath,
|
|
461
521
|
check: 'forward-import',
|
|
@@ -463,10 +523,10 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
463
523
|
message: `${sitePath} in ${entry.filename} imports "${specifier}", ` +
|
|
464
524
|
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
465
525
|
'Reorder the patches so the dependency is created first, move the import ' +
|
|
466
|
-
'into the later patch, declare the intentional staged dependency with ' +
|
|
467
|
-
|
|
526
|
+
'into the later patch, declare the intentional staged dependency with: ' +
|
|
527
|
+
`${stagedCommands}; or mark the import with ` +
|
|
468
528
|
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive. ` +
|
|
469
|
-
|
|
529
|
+
ordinalHint,
|
|
470
530
|
severity: 'error',
|
|
471
531
|
});
|
|
472
532
|
});
|
|
@@ -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 /
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-patch lint check for moz.build sorted-list violations (FORGE F2).
|
|
3
|
+
*
|
|
4
|
+
* mozbuild enforces alphabetical ordering on `StrictOrderingOnAppendList`
|
|
5
|
+
* variables and raises `mozbuild.util.UnsortedError: … expected "X" but got
|
|
6
|
+
* "Y"` — but only at CONFIGURE time, i.e. minutes into a `fireforge build`
|
|
7
|
+
* or `fireforge test --build` dispatch. This check reads the patched
|
|
8
|
+
* `moz.build` files from the engine tree (the working tree carries the
|
|
9
|
+
* applied patch state, same as every other per-patch rule) and reports the
|
|
10
|
+
* exact expected/got pair before any build starts.
|
|
11
|
+
*/
|
|
12
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Lints the patched content of every `moz.build` file the patch touches for
|
|
15
|
+
* unsorted strict-ordered lists, mirroring mozbuild's `UnsortedError`
|
|
16
|
+
* report: the first index where the list diverges from its sorted copy.
|
|
17
|
+
*/
|
|
18
|
+
export declare function lintMozBuildSortedLists(repoDir: string, affectedFiles: string[]): Promise<PatchLintIssue[]>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Per-patch lint check for moz.build sorted-list violations (FORGE F2).
|
|
4
|
+
*
|
|
5
|
+
* mozbuild enforces alphabetical ordering on `StrictOrderingOnAppendList`
|
|
6
|
+
* variables and raises `mozbuild.util.UnsortedError: … expected "X" but got
|
|
7
|
+
* "Y"` — but only at CONFIGURE time, i.e. minutes into a `fireforge build`
|
|
8
|
+
* or `fireforge test --build` dispatch. This check reads the patched
|
|
9
|
+
* `moz.build` files from the engine tree (the working tree carries the
|
|
10
|
+
* applied patch state, same as every other per-patch rule) and reports the
|
|
11
|
+
* exact expected/got pair before any build starts.
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
15
|
+
import { mozbuildSortCompare } from './manifest-helpers.js';
|
|
16
|
+
/**
|
|
17
|
+
* moz.build list variables mozbuild declares as `StrictOrderingOnAppendList`
|
|
18
|
+
* (python/mozbuild/mozbuild/frontend/context.py). configure raises
|
|
19
|
+
* `UnsortedError` on append when out of order. `DIRS` is deliberately absent
|
|
20
|
+
* — directory order is meaningful and not strict-ordered. Dotted namespaces
|
|
21
|
+
* (`EXTRA_JS_MODULES.<subdir>`, `EXPORTS.<ns>`) inherit strict ordering
|
|
22
|
+
* from their parent variable.
|
|
23
|
+
*/
|
|
24
|
+
const STRICT_ORDERED_MOZBUILD_LISTS = [
|
|
25
|
+
'EXTRA_COMPONENTS',
|
|
26
|
+
'EXTRA_JS_MODULES',
|
|
27
|
+
'EXTRA_PP_COMPONENTS',
|
|
28
|
+
'EXPORTS',
|
|
29
|
+
'PYTHON_UNITTEST_MANIFESTS',
|
|
30
|
+
'TESTING_JS_MODULES',
|
|
31
|
+
'XPIDL_SOURCES',
|
|
32
|
+
];
|
|
33
|
+
const LIST_OPENER = new RegExp(`^\\s*((?:${STRICT_ORDERED_MOZBUILD_LISTS.join('|')})(?:\\.[A-Za-z_][A-Za-z0-9_]*)*)\\s*\\+?=\\s*\\[`);
|
|
34
|
+
/** Extracts every strict-ordered list (with items in file order) from moz.build content. */
|
|
35
|
+
function collectStrictOrderedLists(content) {
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
const occurrences = [];
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i] ?? '';
|
|
40
|
+
const opener = LIST_OPENER.exec(line);
|
|
41
|
+
if (!opener)
|
|
42
|
+
continue;
|
|
43
|
+
const varName = opener[1] ?? '';
|
|
44
|
+
const items = [];
|
|
45
|
+
// Items may start on the opener line (`VAR += ["a", "b"]`) or span
|
|
46
|
+
// following lines until the closing bracket.
|
|
47
|
+
let rest = line.slice(opener[0].length);
|
|
48
|
+
let closed = false;
|
|
49
|
+
for (;;) {
|
|
50
|
+
for (const quoted of rest.matchAll(/["']([^"']+)["']/g)) {
|
|
51
|
+
items.push(quoted[1] ?? '');
|
|
52
|
+
}
|
|
53
|
+
if (/\]/.test(rest)) {
|
|
54
|
+
closed = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
i += 1;
|
|
58
|
+
if (i >= lines.length)
|
|
59
|
+
break;
|
|
60
|
+
rest = lines[i] ?? '';
|
|
61
|
+
if (/^\s*\]/.test(rest)) {
|
|
62
|
+
closed = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (closed)
|
|
67
|
+
occurrences.push({ varName, items });
|
|
68
|
+
}
|
|
69
|
+
return occurrences;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Lints the patched content of every `moz.build` file the patch touches for
|
|
73
|
+
* unsorted strict-ordered lists, mirroring mozbuild's `UnsortedError`
|
|
74
|
+
* report: the first index where the list diverges from its sorted copy.
|
|
75
|
+
*/
|
|
76
|
+
export async function lintMozBuildSortedLists(repoDir, affectedFiles) {
|
|
77
|
+
const issues = [];
|
|
78
|
+
for (const file of affectedFiles) {
|
|
79
|
+
if (!(file === 'moz.build' || file.endsWith('/moz.build')))
|
|
80
|
+
continue;
|
|
81
|
+
const filePath = join(repoDir, file);
|
|
82
|
+
if (!(await pathExists(filePath)))
|
|
83
|
+
continue;
|
|
84
|
+
const content = await readText(filePath);
|
|
85
|
+
for (const { varName, items } of collectStrictOrderedLists(content)) {
|
|
86
|
+
const sorted = [...items].sort(mozbuildSortCompare);
|
|
87
|
+
const divergence = items.findIndex((item, index) => item !== sorted[index]);
|
|
88
|
+
if (divergence === -1)
|
|
89
|
+
continue;
|
|
90
|
+
const expected = sorted[divergence] ?? '';
|
|
91
|
+
const got = items[divergence] ?? '';
|
|
92
|
+
issues.push({
|
|
93
|
+
file,
|
|
94
|
+
check: 'mozbuild-unsorted-list',
|
|
95
|
+
fingerprint: `mozbuild-unsorted-list|${file}|${varName}|${expected}|${got}`,
|
|
96
|
+
message: `moz.build list ${varName} is not alphabetically sorted: ` +
|
|
97
|
+
`expected "${expected}" but got "${got}" ` +
|
|
98
|
+
'(mozbuild will fail at configure time with UnsortedError)',
|
|
99
|
+
severity: 'error',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return issues;
|
|
104
|
+
}
|
|
@@ -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>;
|