@hominis/fireforge 0.31.0 → 0.33.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 +22 -0
- package/dist/src/commands/export-all.js +4 -1
- package/dist/src/commands/export-shared.js +10 -1
- package/dist/src/commands/export.js +5 -1
- package/dist/src/commands/lint-per-patch.d.ts +2 -0
- package/dist/src/commands/lint-per-patch.js +206 -44
- package/dist/src/commands/lint.js +100 -7
- package/dist/src/commands/patch/split-plan.d.ts +18 -2
- package/dist/src/commands/patch/split-plan.js +90 -16
- package/dist/src/commands/patch/split.js +12 -3
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/test-run.d.ts +10 -0
- package/dist/src/commands/test-run.js +13 -4
- package/dist/src/commands/test.js +46 -7
- package/dist/src/commands/token.js +12 -1
- package/dist/src/commands/typecheck.js +35 -0
- package/dist/src/core/build-prepare.js +23 -3
- package/dist/src/core/config-validate.js +52 -0
- package/dist/src/core/furnace-apply-dry-run.d.ts +17 -0
- package/dist/src/core/furnace-apply-dry-run.js +105 -0
- package/dist/src/core/furnace-apply-ftl.d.ts +12 -0
- package/dist/src/core/furnace-apply-ftl.js +97 -1
- package/dist/src/core/furnace-apply-helpers.js +10 -80
- package/dist/src/core/furnace-jsconfig.js +22 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -0
- package/dist/src/core/git-file-ops.js +82 -1
- package/dist/src/core/mach-resource-shim.d.ts +21 -0
- package/dist/src/core/mach-resource-shim.js +92 -0
- package/dist/src/core/mach.d.ts +17 -0
- package/dist/src/core/mach.js +30 -2
- package/dist/src/core/manifest-helpers.js +29 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +213 -67
- package/dist/src/core/patch-lint-cross.d.ts +31 -0
- package/dist/src/core/patch-lint-cross.js +83 -63
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint-reexports.js +1 -1
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +19 -163
- package/dist/src/core/test-harness-crash.d.ts +6 -3
- package/dist/src/core/test-harness-crash.js +32 -4
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +9 -4
- package/dist/src/core/token-dark-mode.d.ts +9 -0
- package/dist/src/core/token-dark-mode.js +1 -1
- package/dist/src/core/token-docs.d.ts +32 -0
- package/dist/src/core/token-docs.js +101 -0
- package/dist/src/core/token-manager.d.ts +8 -0
- package/dist/src/core/token-manager.js +77 -95
- package/dist/src/core/token-variant.d.ts +39 -0
- package/dist/src/core/token-variant.js +141 -0
- package/dist/src/core/typecheck-shim.d.ts +3 -1
- package/dist/src/core/typecheck-shim.js +43 -3
- package/dist/src/core/typecheck.js +56 -28
- package/dist/src/types/commands/options.d.ts +22 -0
- package/dist/src/types/config.d.ts +24 -2
- package/package.json +3 -3
package/dist/src/core/mach.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
|
8
8
|
import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
|
|
9
9
|
import { explainMachError } from './mach-error-hints.js';
|
|
10
10
|
import { getPython } from './mach-python.js';
|
|
11
|
+
import { resolveMachBuildEnv } from './mach-resource-shim.js';
|
|
11
12
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
12
13
|
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
|
|
13
14
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
@@ -156,7 +157,10 @@ export async function build(engineDir, jobs) {
|
|
|
156
157
|
if (jobs !== undefined) {
|
|
157
158
|
args.push('-j', String(jobs));
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
+
// Inject the resource-monitor degrade shim so a host psutil failure does
|
|
161
|
+
// not abort the build regardless of which mach entry FireForge spawns.
|
|
162
|
+
const env = await resolveMachBuildEnv();
|
|
163
|
+
const result = await runMachInheritCapture(args, engineDir, { env });
|
|
160
164
|
if (result.exitCode !== 0) {
|
|
161
165
|
surfaceMachErrorHints(result);
|
|
162
166
|
}
|
|
@@ -170,7 +174,10 @@ export async function build(engineDir, jobs) {
|
|
|
170
174
|
* @returns Captured mach result
|
|
171
175
|
*/
|
|
172
176
|
export async function buildUI(engineDir) {
|
|
173
|
-
|
|
177
|
+
// Same resource-monitor degrade shim as the full build: raw
|
|
178
|
+
// `./mach build faster` aborts on the host psutil monitor without it.
|
|
179
|
+
const env = await resolveMachBuildEnv();
|
|
180
|
+
const result = await runMachInheritCapture(['build', 'faster'], engineDir, { env });
|
|
174
181
|
if (result.exitCode !== 0) {
|
|
175
182
|
surfaceMachErrorHints(result);
|
|
176
183
|
}
|
|
@@ -320,4 +327,25 @@ export async function test(engineDir, testPaths = [], args = []) {
|
|
|
320
327
|
export async function testWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
321
328
|
return runMachCapture(['test', ...testPaths, ...args], engineDir, env ? { env } : {});
|
|
322
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Runs `mach xpcshell-test` (the suite-specific xpcshell command) while
|
|
332
|
+
* capturing output. Unlike the generic `mach test`, the suite-specific
|
|
333
|
+
* commands degrade a broken mozlog resource monitor to a warning instead of
|
|
334
|
+
* crashing at startup, so `fireforge test` dispatches single-suite runs here
|
|
335
|
+
* to stay resilient to the host psutil failure (field report E1).
|
|
336
|
+
*
|
|
337
|
+
* Signature mirrors {@link testWithOutput} so the two are interchangeable in
|
|
338
|
+
* the dispatch path.
|
|
339
|
+
*/
|
|
340
|
+
export async function xpcshellTestWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
341
|
+
return runMachCapture(['xpcshell-test', ...testPaths, ...args], engineDir, env ? { env } : {});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Runs `mach mochitest` (covers browser-chrome / mochitest flavors) while
|
|
345
|
+
* capturing output. The suite-specific counterpart to {@link testWithOutput}
|
|
346
|
+
* for non-xpcshell single-suite runs — see {@link xpcshellTestWithOutput}.
|
|
347
|
+
*/
|
|
348
|
+
export async function mochitestWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
349
|
+
return runMachCapture(['mochitest', ...testPaths, ...args], engineDir, env ? { env } : {});
|
|
350
|
+
}
|
|
323
351
|
//# sourceMappingURL=mach.js.map
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordering comparator matching mozbuild's `StrictOrderingOnAppendList`
|
|
3
|
+
* (`mozbuild.util.UnsortedError`): entries are compared **case-insensitively**,
|
|
4
|
+
* so `HominisAppearanceController` (`appe`) sorts before
|
|
5
|
+
* `HominisAppMenuIntegration` (`appm`) even though a raw byte comparison
|
|
6
|
+
* places the uppercase `M` (0x4D) before the lowercase `e` (0x65).
|
|
7
|
+
*
|
|
8
|
+
* A naive `a > b` insertion landed the new entry in byte order, which
|
|
9
|
+
* `mach configure` then rejected with `UnsortedError`, aborting the build.
|
|
10
|
+
* Ties on the lower-cased key fall back to byte order so the comparison is
|
|
11
|
+
* total and stable.
|
|
12
|
+
*/
|
|
13
|
+
function mozbuildSortCompare(a, b) {
|
|
14
|
+
const la = a.toLowerCase();
|
|
15
|
+
const lb = b.toLowerCase();
|
|
16
|
+
if (la < lb)
|
|
17
|
+
return -1;
|
|
18
|
+
if (la > lb)
|
|
19
|
+
return 1;
|
|
20
|
+
if (a < b)
|
|
21
|
+
return -1;
|
|
22
|
+
if (a > b)
|
|
23
|
+
return 1;
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
1
26
|
/**
|
|
2
27
|
* Inserts a line into an array of lines in alphabetical order within a
|
|
3
28
|
* specified range. The comparison key is extracted from each line.
|
|
@@ -16,7 +41,7 @@ export function findAlphabeticalPosition(lines, startLine, endLine, newKey, extr
|
|
|
16
41
|
existingKeys.push(key);
|
|
17
42
|
}
|
|
18
43
|
}
|
|
19
|
-
const isSorted = existingKeys.every((k, idx) => idx === 0 || k
|
|
44
|
+
const isSorted = existingKeys.every((k, idx) => idx === 0 || mozbuildSortCompare(k, existingKeys[idx - 1] ?? '') >= 0);
|
|
20
45
|
if (!isSorted) {
|
|
21
46
|
return { insertIndex: endLine, previousEntry: undefined };
|
|
22
47
|
}
|
|
@@ -29,7 +54,7 @@ export function findAlphabeticalPosition(lines, startLine, endLine, newKey, extr
|
|
|
29
54
|
const key = extractKey(line);
|
|
30
55
|
if (key === undefined)
|
|
31
56
|
continue;
|
|
32
|
-
if (key >
|
|
57
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
33
58
|
insertIndex = i;
|
|
34
59
|
break;
|
|
35
60
|
}
|
|
@@ -61,7 +86,7 @@ export function findAlphabeticalTokenPosition(tokens, sectionTargetPattern, newK
|
|
|
61
86
|
continue;
|
|
62
87
|
const match = sectionTargetPattern.exec(entry.parsed.target);
|
|
63
88
|
const key = match?.[1] ?? entry.parsed.target;
|
|
64
|
-
if (key >
|
|
89
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
65
90
|
insertLineIndex = entry.lineIndex;
|
|
66
91
|
break;
|
|
67
92
|
}
|
|
@@ -84,7 +109,7 @@ export function findAlphabeticalMozBuildPosition(tokens, newKey) {
|
|
|
84
109
|
let previousEntry;
|
|
85
110
|
for (const item of items) {
|
|
86
111
|
const key = item.parsed?.value ?? '';
|
|
87
|
-
if (key >
|
|
112
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
88
113
|
insertLineIndex = item.lineIndex;
|
|
89
114
|
break;
|
|
90
115
|
}
|
|
@@ -15,35 +15,89 @@
|
|
|
15
15
|
* `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
|
|
16
16
|
* and optional allowlisted `checkJsCompilerOptions`; it does not change
|
|
17
17
|
* shim composition or suppressed diagnostic codes.
|
|
18
|
+
*
|
|
19
|
+
* Resolution scope vs reporting scope: the TS program is built over a
|
|
20
|
+
* *resolution* set (every patch-owned `.sys.mjs` the run cares about, so
|
|
21
|
+
* cross-patch `resource:///` imports resolve to their real sources), while
|
|
22
|
+
* diagnostics are emitted only for files in the *report* scope. Splitting
|
|
23
|
+
* the two lets per-patch lint build one queue-wide program and attribute
|
|
24
|
+
* findings per patch, and lets export/re-export resolve cross-patch imports
|
|
25
|
+
* while reporting only the patch under export.
|
|
18
26
|
*/
|
|
19
27
|
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
20
28
|
import type { PatchLintCheckJsCompilerOptions, PatchLintConfig } from '../types/config.js';
|
|
29
|
+
/** How a checkJs run controls reporting and resolution; see module docs. */
|
|
30
|
+
export interface CheckJsMode {
|
|
31
|
+
strict: boolean;
|
|
32
|
+
compilerOptions?: PatchLintCheckJsCompilerOptions;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Result of {@link runCheckJsGrouped}: diagnostics attributed to the
|
|
36
|
+
* patch-owned file they originate in (`byFile`, keyed by repo-relative
|
|
37
|
+
* path), plus run-level errors that have no owning file (`global` — e.g.
|
|
38
|
+
* TypeScript missing or an unreadable extra shim).
|
|
39
|
+
*/
|
|
40
|
+
export interface GroupedCheckJsResult {
|
|
41
|
+
byFile: Map<string, PatchLintIssue[]>;
|
|
42
|
+
global: PatchLintIssue[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Builds the checkJs program **once** over `resolutionOwned` and returns its
|
|
46
|
+
* diagnostics grouped by originating file. Callers slice the result by their
|
|
47
|
+
* own report scope — per-patch lint attributes each file to its owning
|
|
48
|
+
* patch, export/re-export keeps only the patch under export. Resolution
|
|
49
|
+
* always spans every file in `resolutionOwned`, so cross-patch
|
|
50
|
+
* `resource:///`/`chrome://` imports resolve to real sources.
|
|
51
|
+
*
|
|
52
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
53
|
+
* @param resolutionOwned - Patch-owned `.sys.mjs` paths (relative to repoDir)
|
|
54
|
+
* the program should see and resolve against
|
|
55
|
+
* @param extraShimPath - Optional project-relative extra `.d.ts` appended to
|
|
56
|
+
* the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
|
|
57
|
+
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
58
|
+
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
59
|
+
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
60
|
+
*/
|
|
61
|
+
export declare function runCheckJsGrouped(repoDir: string, resolutionOwned: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode): Promise<GroupedCheckJsResult>;
|
|
21
62
|
/**
|
|
22
|
-
*
|
|
63
|
+
* Flattens a {@link runCheckJsGrouped} run into a single issue list. When
|
|
64
|
+
* `reportScope` is supplied, only diagnostics from files in that set are
|
|
65
|
+
* returned (resolution still spans every owned file); omitting it reports
|
|
66
|
+
* every owned file's diagnostics — the historical whole-set behaviour.
|
|
23
67
|
*
|
|
24
|
-
* @param repoDir - Absolute
|
|
25
|
-
* @param patchOwnedFiles -
|
|
26
|
-
* @param extraShimPath - Optional project-relative
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* the project root, so the caller passes both).
|
|
32
|
-
* @param projectRoot - Absolute project root for resolving `extraShimPath`.
|
|
33
|
-
* Defaults to `repoDir` for back-compat with callers that don't
|
|
34
|
-
* pass an extra shim (no resolution actually happens in that case).
|
|
35
|
-
* @param mode - When `strict` is true, enables `strict` and `noImplicitAny`
|
|
36
|
-
* (CI-style). Optional `compilerOptions` merges allowlisted boolean
|
|
37
|
-
* overrides after that preset (from `patchLint.checkJsCompilerOptions`).
|
|
38
|
-
* Omitted or `{ strict: false }` preserves the historical loose preset.
|
|
68
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
69
|
+
* @param patchOwnedFiles - Patch-owned `.sys.mjs` paths to resolve against
|
|
70
|
+
* @param extraShimPath - Optional project-relative extra `.d.ts`
|
|
71
|
+
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
72
|
+
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
73
|
+
* @param reportScope - When set, restrict reported diagnostics to these
|
|
74
|
+
* repo-relative files
|
|
39
75
|
* @returns Array of lint issues from TS diagnostics
|
|
40
76
|
*/
|
|
41
|
-
export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>, extraShimPath?: string, projectRoot?: string, mode?:
|
|
42
|
-
strict: boolean;
|
|
43
|
-
compilerOptions?: PatchLintCheckJsCompilerOptions;
|
|
44
|
-
}): Promise<PatchLintIssue[]>;
|
|
77
|
+
export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode, reportScope?: ReadonlySet<string>): Promise<PatchLintIssue[]>;
|
|
45
78
|
/**
|
|
46
79
|
* Invokes {@link runCheckJs} for a `patchLint` block with `checkJs: true`.
|
|
47
80
|
* `projectRoot` is the FireForge project root (`dirname(engine)`).
|
|
81
|
+
*
|
|
82
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
83
|
+
* @param patchOwnedFiles - Patch-owned `.sys.mjs` paths to resolve against
|
|
84
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
85
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
86
|
+
* @param reportScope - Optional repo-relative files to report on (export /
|
|
87
|
+
* re-export passes the patch under export so cross-patch resolution does
|
|
88
|
+
* not surface other patches' diagnostics)
|
|
89
|
+
*/
|
|
90
|
+
export declare function invokePatchLintCheckJs(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, reportScope?: ReadonlySet<string>): Promise<PatchLintIssue[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Grouped variant of {@link invokePatchLintCheckJs}: builds one queue-wide
|
|
93
|
+
* checkJs program over `patchOwnedFiles` and returns its findings grouped by
|
|
94
|
+
* owning file. The per-patch lint orchestrator calls this **once per run**
|
|
95
|
+
* and attributes each file's findings to its owning patch, instead of
|
|
96
|
+
* rebuilding the same program for every patch in the queue.
|
|
97
|
+
*
|
|
98
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
99
|
+
* @param patchOwnedFiles - Every patch-owned `.sys.mjs` in the queue
|
|
100
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
101
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
48
102
|
*/
|
|
49
|
-
export declare function
|
|
103
|
+
export declare function invokePatchLintCheckJsGrouped(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string): Promise<GroupedCheckJsResult>;
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
* `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
|
|
17
17
|
* and optional allowlisted `checkJsCompilerOptions`; it does not change
|
|
18
18
|
* shim composition or suppressed diagnostic codes.
|
|
19
|
+
*
|
|
20
|
+
* Resolution scope vs reporting scope: the TS program is built over a
|
|
21
|
+
* *resolution* set (every patch-owned `.sys.mjs` the run cares about, so
|
|
22
|
+
* cross-patch `resource:///` imports resolve to their real sources), while
|
|
23
|
+
* diagnostics are emitted only for files in the *report* scope. Splitting
|
|
24
|
+
* the two lets per-patch lint build one queue-wide program and attribute
|
|
25
|
+
* findings per patch, and lets export/re-export resolve cross-patch imports
|
|
26
|
+
* while reporting only the patch under export.
|
|
19
27
|
*/
|
|
20
28
|
import { basename, resolve } from 'node:path';
|
|
21
29
|
import { pathExists } from '../utils/fs.js';
|
|
@@ -62,57 +70,121 @@ function createOwnedSpecifierResolver(ts, ownedAbsolute) {
|
|
|
62
70
|
};
|
|
63
71
|
};
|
|
64
72
|
}
|
|
73
|
+
/** Maps a resolved file path to the TS extension enum the host must report. */
|
|
74
|
+
function extensionForFile(ts, file) {
|
|
75
|
+
if (file.endsWith('.d.ts'))
|
|
76
|
+
return ts.Extension.Dts;
|
|
77
|
+
if (file.endsWith('.ts'))
|
|
78
|
+
return ts.Extension.Ts;
|
|
79
|
+
if (file.endsWith('.tsx'))
|
|
80
|
+
return ts.Extension.Tsx;
|
|
81
|
+
if (file.endsWith('.cjs'))
|
|
82
|
+
return ts.Extension.Cjs;
|
|
83
|
+
if (file.endsWith('.jsx'))
|
|
84
|
+
return ts.Extension.Jsx;
|
|
85
|
+
if (file.endsWith('.json'))
|
|
86
|
+
return ts.Extension.Json;
|
|
87
|
+
return ts.Extension.Mjs;
|
|
88
|
+
}
|
|
65
89
|
/**
|
|
66
|
-
*
|
|
90
|
+
* Builds a resolver for a reviewed `paths` mapping (route 2 of the
|
|
91
|
+
* cross-patch resolution work). Each pattern may contain a single `*`;
|
|
92
|
+
* matching targets are resolved relative to `baseDir` (the engine dir, like
|
|
93
|
+
* the rest of `patchLint` which is engine-relative). Resolved files are
|
|
94
|
+
* recorded via `onResolved` so the compiler host knows to read them from
|
|
95
|
+
* disk rather than returning empty content. No `baseUrl` is set, so this is
|
|
96
|
+
* TS5090-safe: `paths` resolution is host-driven here, not config-driven.
|
|
97
|
+
*/
|
|
98
|
+
function createPathsResolver(ts, paths, baseDir, fileExists, onResolved) {
|
|
99
|
+
const entries = Object.entries(paths);
|
|
100
|
+
return (specifier) => {
|
|
101
|
+
for (const [pattern, targets] of entries) {
|
|
102
|
+
const star = pattern.indexOf('*');
|
|
103
|
+
let captured;
|
|
104
|
+
if (star === -1) {
|
|
105
|
+
if (specifier !== pattern)
|
|
106
|
+
continue;
|
|
107
|
+
captured = '';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const prefix = pattern.slice(0, star);
|
|
111
|
+
const suffix = pattern.slice(star + 1);
|
|
112
|
+
if (specifier.length < prefix.length + suffix.length)
|
|
113
|
+
continue;
|
|
114
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix))
|
|
115
|
+
continue;
|
|
116
|
+
captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
117
|
+
}
|
|
118
|
+
for (const target of targets) {
|
|
119
|
+
const rel = target.includes('*') ? target.replace('*', captured) : target;
|
|
120
|
+
const abs = resolve(baseDir, rel);
|
|
121
|
+
if (!fileExists(abs))
|
|
122
|
+
continue;
|
|
123
|
+
onResolved(abs);
|
|
124
|
+
return {
|
|
125
|
+
resolvedFileName: abs,
|
|
126
|
+
extension: extensionForFile(ts, abs),
|
|
127
|
+
isExternalLibraryImport: false,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Builds the checkJs program **once** over `resolutionOwned` and returns its
|
|
136
|
+
* diagnostics grouped by originating file. Callers slice the result by their
|
|
137
|
+
* own report scope — per-patch lint attributes each file to its owning
|
|
138
|
+
* patch, export/re-export keeps only the patch under export. Resolution
|
|
139
|
+
* always spans every file in `resolutionOwned`, so cross-patch
|
|
140
|
+
* `resource:///`/`chrome://` imports resolve to real sources.
|
|
67
141
|
*
|
|
68
|
-
* @param repoDir - Absolute
|
|
69
|
-
* @param
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* Firefox-globals shim
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* @param projectRoot - Absolute project root for resolving `extraShimPath`.
|
|
77
|
-
* Defaults to `repoDir` for back-compat with callers that don't
|
|
78
|
-
* pass an extra shim (no resolution actually happens in that case).
|
|
79
|
-
* @param mode - When `strict` is true, enables `strict` and `noImplicitAny`
|
|
80
|
-
* (CI-style). Optional `compilerOptions` merges allowlisted boolean
|
|
81
|
-
* overrides after that preset (from `patchLint.checkJsCompilerOptions`).
|
|
82
|
-
* Omitted or `{ strict: false }` preserves the historical loose preset.
|
|
83
|
-
* @returns Array of lint issues from TS diagnostics
|
|
142
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
143
|
+
* @param resolutionOwned - Patch-owned `.sys.mjs` paths (relative to repoDir)
|
|
144
|
+
* the program should see and resolve against
|
|
145
|
+
* @param extraShimPath - Optional project-relative extra `.d.ts` appended to
|
|
146
|
+
* the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
|
|
147
|
+
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
148
|
+
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
149
|
+
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
84
150
|
*/
|
|
85
|
-
export async function
|
|
86
|
-
|
|
87
|
-
|
|
151
|
+
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode) {
|
|
152
|
+
const empty = { byFile: new Map(), global: [] };
|
|
153
|
+
if (resolutionOwned.size === 0)
|
|
154
|
+
return empty;
|
|
88
155
|
// Dynamic import — typescript stays as a dev dependency
|
|
89
156
|
let ts;
|
|
90
157
|
try {
|
|
91
158
|
ts = await import('typescript');
|
|
92
159
|
}
|
|
93
160
|
catch {
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
161
|
+
return {
|
|
162
|
+
byFile: new Map(),
|
|
163
|
+
global: [
|
|
164
|
+
{
|
|
165
|
+
file: '(checkJs)',
|
|
166
|
+
check: 'checkjs-type-error',
|
|
167
|
+
message: 'patchLint.checkJs is enabled but the "typescript" package is not installed. ' +
|
|
168
|
+
'Run "npm install typescript" to enable type checking.',
|
|
169
|
+
severity: 'error',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
103
173
|
}
|
|
104
174
|
// Resolve absolute paths for root files, filtering to files that exist
|
|
105
175
|
const rootFiles = [];
|
|
106
176
|
const ownedAbsolute = new Set();
|
|
107
|
-
|
|
177
|
+
const relByAbsolute = new Map();
|
|
178
|
+
for (const rel of resolutionOwned) {
|
|
108
179
|
const abs = resolve(repoDir, rel);
|
|
109
180
|
if (await pathExists(abs)) {
|
|
110
181
|
rootFiles.push(abs);
|
|
111
182
|
ownedAbsolute.add(abs);
|
|
183
|
+
relByAbsolute.set(abs, rel);
|
|
112
184
|
}
|
|
113
185
|
}
|
|
114
186
|
if (rootFiles.length === 0)
|
|
115
|
-
return
|
|
187
|
+
return empty;
|
|
116
188
|
// Compose the shim. `extraShimPath` is project-relative (validated
|
|
117
189
|
// by config-validate); resolve it against `projectRoot`. When the
|
|
118
190
|
// caller passes neither, fall back to `repoDir` — the only way the
|
|
@@ -127,14 +199,17 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
127
199
|
}
|
|
128
200
|
}
|
|
129
201
|
catch (err) {
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
202
|
+
return {
|
|
203
|
+
byFile: new Map(),
|
|
204
|
+
global: [
|
|
205
|
+
{
|
|
206
|
+
file: extraShimPath ?? '(checkJs)',
|
|
207
|
+
check: 'checkjs-type-error',
|
|
208
|
+
message: err instanceof Error ? err.message : String(err),
|
|
209
|
+
severity: 'error',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
};
|
|
138
213
|
}
|
|
139
214
|
const shimPath = resolve(repoDir, SHIM_FILENAME);
|
|
140
215
|
rootFiles.push(shimPath);
|
|
@@ -146,12 +221,23 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
146
221
|
strict: false,
|
|
147
222
|
noImplicitAny: false,
|
|
148
223
|
};
|
|
224
|
+
// Allowlisted overrides. Booleans merge directly; a reviewed `paths`
|
|
225
|
+
// mapping is applied to the compiler options AND wired into the host
|
|
226
|
+
// resolver below so patch-owned modules can be typed from their real
|
|
227
|
+
// sources without a hand-generated ambient stub shim.
|
|
149
228
|
const overrides = {};
|
|
229
|
+
let pathsMapping;
|
|
150
230
|
const co = mode?.compilerOptions;
|
|
151
231
|
if (co) {
|
|
152
232
|
for (const key of Object.keys(co)) {
|
|
153
233
|
const v = co[key];
|
|
154
|
-
if (v
|
|
234
|
+
if (v === undefined)
|
|
235
|
+
continue;
|
|
236
|
+
if (key === 'paths') {
|
|
237
|
+
pathsMapping = v;
|
|
238
|
+
overrides.paths = pathsMapping;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
155
241
|
overrides[key] = v;
|
|
156
242
|
}
|
|
157
243
|
}
|
|
@@ -180,13 +266,19 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
180
266
|
// the shim for the shim path, and returns empty content for
|
|
181
267
|
// anything else to avoid reading the full Firefox tree.
|
|
182
268
|
const defaultHost = ts.createCompilerHost(options);
|
|
269
|
+
// Files pulled in via a reviewed `paths` mapping — outside the owned set
|
|
270
|
+
// but read from disk so the resolver's targets actually type-check.
|
|
271
|
+
const pathsResolved = new Set();
|
|
272
|
+
const resolveViaPaths = pathsMapping
|
|
273
|
+
? createPathsResolver(ts, pathsMapping, repoDir, (f) => defaultHost.fileExists(f), (abs) => pathsResolved.add(abs))
|
|
274
|
+
: undefined;
|
|
183
275
|
const host = {
|
|
184
276
|
...defaultHost,
|
|
185
277
|
getSourceFile(fileName, languageVersion, onError) {
|
|
186
278
|
if (fileName === shimPath) {
|
|
187
279
|
return ts.createSourceFile(fileName, shimSource, languageVersion, true);
|
|
188
280
|
}
|
|
189
|
-
if (ownedAbsolute.has(fileName)) {
|
|
281
|
+
if (ownedAbsolute.has(fileName) || pathsResolved.has(fileName)) {
|
|
190
282
|
return defaultHost.getSourceFile(fileName, languageVersion, onError);
|
|
191
283
|
}
|
|
192
284
|
// For lib files (lib.es*.d.ts) delegate to the default host
|
|
@@ -201,7 +293,7 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
201
293
|
fileExists(fileName) {
|
|
202
294
|
if (fileName === shimPath)
|
|
203
295
|
return true;
|
|
204
|
-
if (ownedAbsolute.has(fileName))
|
|
296
|
+
if (ownedAbsolute.has(fileName) || pathsResolved.has(fileName))
|
|
205
297
|
return true;
|
|
206
298
|
return defaultHost.fileExists(fileName);
|
|
207
299
|
},
|
|
@@ -211,61 +303,115 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
|
|
|
211
303
|
return defaultHost.readFile(fileName);
|
|
212
304
|
},
|
|
213
305
|
resolveModuleNameLiterals(moduleLiterals) {
|
|
214
|
-
return moduleLiterals.map((literal) =>
|
|
215
|
-
|
|
216
|
-
|
|
306
|
+
return moduleLiterals.map((literal) => {
|
|
307
|
+
const owned = resolveOwnedSpecifier(literal.text);
|
|
308
|
+
if (owned)
|
|
309
|
+
return { resolvedModule: owned };
|
|
310
|
+
return { resolvedModule: resolveViaPaths?.(literal.text) };
|
|
311
|
+
});
|
|
217
312
|
},
|
|
218
313
|
};
|
|
219
314
|
const program = ts.createProgram(rootFiles, options, host);
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
315
|
+
const byFile = groupOwnedDiagnostics(ts, [...program.getSemanticDiagnostics(), ...program.getSyntacticDiagnostics()], relByAbsolute);
|
|
316
|
+
verbose(`checkJs: analyzed ${rootFiles.length - 1} file(s) across ${byFile.size} owning file(s)`);
|
|
317
|
+
return { byFile, global: [] };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Groups TS diagnostics by the patch-owned file they originate in,
|
|
321
|
+
* suppressing module-resolution / unknown-name noise inherent to checking
|
|
322
|
+
* Firefox JS outside Mozilla's build system. Diagnostics from `paths`-resolved
|
|
323
|
+
* or shim files are dropped — only owned files (in `relByAbsolute`) carry
|
|
324
|
+
* findings.
|
|
325
|
+
*/
|
|
326
|
+
function groupOwnedDiagnostics(ts, diagnostics, relByAbsolute) {
|
|
327
|
+
const byFile = new Map();
|
|
328
|
+
for (const diag of diagnostics) {
|
|
229
329
|
if (SUPPRESSED_DIAGNOSTIC_CODES.has(diag.code))
|
|
230
330
|
continue;
|
|
231
331
|
const sourceFile = diag.file;
|
|
232
332
|
if (!sourceFile)
|
|
233
333
|
continue;
|
|
234
|
-
|
|
334
|
+
const relPath = relByAbsolute.get(sourceFile.fileName);
|
|
335
|
+
if (relPath === undefined)
|
|
235
336
|
continue;
|
|
236
337
|
const lineInfo = sourceFile.getLineAndCharacterOfPosition(diag.start ?? 0);
|
|
237
338
|
const line = lineInfo.line + 1;
|
|
238
339
|
const messageText = typeof diag.messageText === 'string'
|
|
239
340
|
? diag.messageText
|
|
240
341
|
: ts.flattenDiagnosticMessageText(diag.messageText, '\n');
|
|
241
|
-
// Find the relative path for the issue
|
|
242
|
-
let relPath = sourceFile.fileName;
|
|
243
|
-
for (const [rel, abs] of [...patchOwnedFiles].map((r) => [r, resolve(repoDir, r)])) {
|
|
244
|
-
if (abs === sourceFile.fileName) {
|
|
245
|
-
relPath = rel;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
342
|
const severity = diag.category === ts.DiagnosticCategory.Error ? 'error' : 'warning';
|
|
250
|
-
|
|
343
|
+
const bucket = byFile.get(relPath) ?? [];
|
|
344
|
+
bucket.push({
|
|
251
345
|
file: relPath,
|
|
252
346
|
check: 'checkjs-type-error',
|
|
253
347
|
message: `Line ${line}: ${messageText}`,
|
|
254
348
|
severity,
|
|
255
349
|
});
|
|
350
|
+
byFile.set(relPath, bucket);
|
|
351
|
+
}
|
|
352
|
+
return byFile;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Flattens a {@link runCheckJsGrouped} run into a single issue list. When
|
|
356
|
+
* `reportScope` is supplied, only diagnostics from files in that set are
|
|
357
|
+
* returned (resolution still spans every owned file); omitting it reports
|
|
358
|
+
* every owned file's diagnostics — the historical whole-set behaviour.
|
|
359
|
+
*
|
|
360
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
361
|
+
* @param patchOwnedFiles - Patch-owned `.sys.mjs` paths to resolve against
|
|
362
|
+
* @param extraShimPath - Optional project-relative extra `.d.ts`
|
|
363
|
+
* @param projectRoot - Absolute project root for resolving `extraShimPath`
|
|
364
|
+
* @param mode - Strictness preset plus allowlisted compiler-option overrides
|
|
365
|
+
* @param reportScope - When set, restrict reported diagnostics to these
|
|
366
|
+
* repo-relative files
|
|
367
|
+
* @returns Array of lint issues from TS diagnostics
|
|
368
|
+
*/
|
|
369
|
+
export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projectRoot, mode, reportScope) {
|
|
370
|
+
const { byFile, global } = await runCheckJsGrouped(repoDir, patchOwnedFiles, extraShimPath, projectRoot, mode);
|
|
371
|
+
const issues = [...global];
|
|
372
|
+
for (const [rel, list] of byFile) {
|
|
373
|
+
if (reportScope && !reportScope.has(rel))
|
|
374
|
+
continue;
|
|
375
|
+
issues.push(...list);
|
|
256
376
|
}
|
|
257
|
-
verbose(`checkJs: analyzed ${rootFiles.length - 1} file(s), found ${issues.length} issue(s)`);
|
|
258
377
|
return issues;
|
|
259
378
|
}
|
|
260
379
|
/**
|
|
261
380
|
* Invokes {@link runCheckJs} for a `patchLint` block with `checkJs: true`.
|
|
262
381
|
* `projectRoot` is the FireForge project root (`dirname(engine)`).
|
|
382
|
+
*
|
|
383
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
384
|
+
* @param patchOwnedFiles - Patch-owned `.sys.mjs` paths to resolve against
|
|
385
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
386
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
387
|
+
* @param reportScope - Optional repo-relative files to report on (export /
|
|
388
|
+
* re-export passes the patch under export so cross-patch resolution does
|
|
389
|
+
* not surface other patches' diagnostics)
|
|
390
|
+
*/
|
|
391
|
+
export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint, projectRoot, reportScope) {
|
|
392
|
+
const strict = patchLint.checkJsStrict === true;
|
|
393
|
+
const mode = strict && patchLint.checkJsCompilerOptions
|
|
394
|
+
? { strict, compilerOptions: patchLint.checkJsCompilerOptions }
|
|
395
|
+
: { strict };
|
|
396
|
+
return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode, reportScope);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Grouped variant of {@link invokePatchLintCheckJs}: builds one queue-wide
|
|
400
|
+
* checkJs program over `patchOwnedFiles` and returns its findings grouped by
|
|
401
|
+
* owning file. The per-patch lint orchestrator calls this **once per run**
|
|
402
|
+
* and attributes each file's findings to its owning patch, instead of
|
|
403
|
+
* rebuilding the same program for every patch in the queue.
|
|
404
|
+
*
|
|
405
|
+
* @param repoDir - Absolute engine (repository) directory
|
|
406
|
+
* @param patchOwnedFiles - Every patch-owned `.sys.mjs` in the queue
|
|
407
|
+
* @param patchLint - The resolved `patchLint` config block
|
|
408
|
+
* @param projectRoot - FireForge project root for shim resolution
|
|
263
409
|
*/
|
|
264
|
-
export async function
|
|
410
|
+
export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, patchLint, projectRoot) {
|
|
265
411
|
const strict = patchLint.checkJsStrict === true;
|
|
266
412
|
const mode = strict && patchLint.checkJsCompilerOptions
|
|
267
413
|
? { strict, compilerOptions: patchLint.checkJsCompilerOptions }
|
|
268
414
|
: { strict };
|
|
269
|
-
return
|
|
415
|
+
return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode);
|
|
270
416
|
}
|
|
271
417
|
//# sourceMappingURL=patch-lint-checkjs.js.map
|
|
@@ -180,6 +180,37 @@ export declare function findForwardImportIgnoreLines(source: string): Set<number
|
|
|
180
180
|
* engine file — not our concern).
|
|
181
181
|
* - Imports on a line suppressed by the ignore marker.
|
|
182
182
|
*/
|
|
183
|
+
/** One discovered forward-import edge: a site importing a later-created file. */
|
|
184
|
+
export interface ForwardImportEdge {
|
|
185
|
+
/** Importing patch filename. */
|
|
186
|
+
entry: string;
|
|
187
|
+
/** Importing file path relative to engine/. */
|
|
188
|
+
sitePath: string;
|
|
189
|
+
/** Exact import specifier as it appears in source. */
|
|
190
|
+
specifier: string;
|
|
191
|
+
/** Specifier with any query/hash stripped (used for fingerprints). */
|
|
192
|
+
cleaned: string;
|
|
193
|
+
/** Later-ordered patch creating the imported file. */
|
|
194
|
+
owner: string;
|
|
195
|
+
/** Path of the later-created file (relative to engine/). */
|
|
196
|
+
creates: string;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns every forward-import edge in the queue, one per (site, later
|
|
200
|
+
* creator) pair, regardless of any staged-dependency declarations. Used by
|
|
201
|
+
* `patch split` to discover the forward edges it introduces into the new
|
|
202
|
+
* patch so it can auto-declare them (and so its projected lint matches the
|
|
203
|
+
* real per-patch gate).
|
|
204
|
+
*/
|
|
205
|
+
export declare function collectForwardImportEdges(ctx: PatchQueueContext): ForwardImportEdge[];
|
|
206
|
+
/**
|
|
207
|
+
* Cross-patch lint rule: flag imports of a module a later-ordered patch is
|
|
208
|
+
* responsible for creating, unless the patch declares an exact staged
|
|
209
|
+
* forward-import for it. Also warns on staged-dependency declarations that
|
|
210
|
+
* never matched (stale). Matching is conservative — by basename, not by
|
|
211
|
+
* resolving `resource://`/`chrome://` URLs — with an inline ignore marker as
|
|
212
|
+
* an escape hatch for unrelated basename collisions.
|
|
213
|
+
*/
|
|
183
214
|
export declare function lintPatchQueueForwardImports(ctx: PatchQueueContext): PatchLintIssue[];
|
|
184
215
|
/**
|
|
185
216
|
* Cross-patch lint orchestrator. Runs every cross-patch rule against the
|