@hominis/fireforge 0.18.8 → 0.18.9
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/dist/src/commands/test.js +50 -3
- package/dist/src/core/ast-utils.d.ts +5 -1
- package/dist/src/core/ast-utils.js +10 -3
- package/dist/src/core/build-audit-resolve.d.ts +5 -3
- package/dist/src/core/build-audit-resolve.js +12 -3
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +1 -0
- package/dist/src/core/config-validate.js +4 -0
- package/dist/src/core/marionette-port.d.ts +29 -0
- package/dist/src/core/marionette-port.js +82 -0
- package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +47 -0
- package/dist/src/core/patch-lint-chrome-jsdoc.js +87 -0
- package/dist/src/core/patch-lint-jsdoc.d.ts +37 -0
- package/dist/src/core/patch-lint-jsdoc.js +24 -3
- package/dist/src/core/patch-lint-ownership.d.ts +21 -3
- package/dist/src/core/patch-lint-ownership.js +45 -18
- package/dist/src/core/patch-lint.d.ts +7 -2
- package/dist/src/core/patch-lint.js +24 -6
- package/dist/src/types/commands/options.d.ts +8 -0
- package/dist/src/types/config.d.ts +12 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { prepareBuildEnvironment } from '../core/build-prepare.js';
|
|
4
4
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
5
5
|
import { buildArtifactMismatchMessage, buildUI, hasBuildArtifacts, hasRunnableBundle, testWithOutput, } from '../core/mach.js';
|
|
6
|
-
import { assertMarionettePortAvailable } from '../core/marionette-port.js';
|
|
6
|
+
import { assertMarionettePortAvailable, extractForwardedMarionettePort, isMarionetteFlavor, } from '../core/marionette-port.js';
|
|
7
7
|
import { formatMarionettePreflightLine, reportMarionettePreflight, runMarionettePreflight, } from '../core/marionette-preflight.js';
|
|
8
8
|
import { checkStaleBuildForTest, formatStaleBuildWarning } from '../core/test-stale-check.js';
|
|
9
9
|
import { operatorAlreadySetAppPath, resolveXpcshellAppdirArg, } from '../core/xpcshell-appdir.js';
|
|
@@ -238,6 +238,20 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
238
238
|
warn(formatStaleBuildWarning(stale));
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
+
// Resolve the effective Marionette port. Operator precedence:
|
|
242
|
+
// 1. `--marionette-port` (first-class option, parsed at the CLI layer)
|
|
243
|
+
// 2. forwarded `--mach-arg --marionette-port=NNNN` /
|
|
244
|
+
// `--mach-arg --setpref=marionette.port=NNNN`
|
|
245
|
+
// 3. fall back to `DEFAULT_MARIONETTE_PORT` semantics inside the probes
|
|
246
|
+
// (passed as `undefined`).
|
|
247
|
+
// Without (2), an operator working around a stale listener via the
|
|
248
|
+
// documented `--mach-arg --marionette-port=NNNN` workaround would still
|
|
249
|
+
// hit the wrapper preflight refusing on 2828 before the forwarded arg
|
|
250
|
+
// ever reached mach.
|
|
251
|
+
const forwardedPort = options.machArg
|
|
252
|
+
? extractForwardedMarionettePort(options.machArg)
|
|
253
|
+
: undefined;
|
|
254
|
+
const effectivePort = options.marionettePort ?? forwardedPort;
|
|
241
255
|
// Stale-browser probe: an interrupted earlier test run can leave a
|
|
242
256
|
// Firefox/ForgeFresh/Hominis instance listening on the Marionette
|
|
243
257
|
// control port, which breaks the next mach test launch with a
|
|
@@ -246,7 +260,7 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
246
260
|
// generic bind failure. 2026-04-21 eval (Finding #20): a stale
|
|
247
261
|
// `-marionette` process from `fresh/` poisoned a later test run in
|
|
248
262
|
// the sibling `hominis/` workspace.
|
|
249
|
-
await assertMarionettePortAvailable(
|
|
263
|
+
await assertMarionettePortAvailable(effectivePort, { binaryName: projectConfig.binaryName });
|
|
250
264
|
// `--doctor` runs a short marionette handshake probe. When test paths are
|
|
251
265
|
// supplied the probe gates the mach test invocation (a FAIL bails out). When
|
|
252
266
|
// no paths are supplied this is the only step — it's the fastest way to tell
|
|
@@ -259,7 +273,9 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
259
273
|
// clack box-drawing framing.
|
|
260
274
|
process.stdout.write('Running marionette preflight...\n');
|
|
261
275
|
info('Running marionette preflight...');
|
|
262
|
-
const preflight =
|
|
276
|
+
const preflight = effectivePort !== undefined
|
|
277
|
+
? await runMarionettePreflight(paths.engine, { port: effectivePort })
|
|
278
|
+
: await runMarionettePreflight(paths.engine);
|
|
263
279
|
// 2026-04-24 eval Finding 7: the pre-0.18.1 code used
|
|
264
280
|
// `success()` + `outro()` + a direct `process.stdout.write` as a
|
|
265
281
|
// belt-and-suspenders but still reproducibly dropped the PASS summary
|
|
@@ -303,6 +319,30 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
303
319
|
if (options.machArg && options.machArg.length > 0) {
|
|
304
320
|
extraArgs.push(...options.machArg);
|
|
305
321
|
}
|
|
322
|
+
// Auto-forward the Marionette port to mach when `--marionette-port` is
|
|
323
|
+
// set. We use `--setpref=marionette.port=<n>` because the marionette
|
|
324
|
+
// listener reads that pref before binding (browser-chrome / mochitest
|
|
325
|
+
// path); xpcshell never reads it, so the pref is a no-op there.
|
|
326
|
+
//
|
|
327
|
+
// Skip forwarding when the operator already supplied an equivalent arg
|
|
328
|
+
// via `--mach-arg` — duplicates would be confusing without changing
|
|
329
|
+
// semantics. Skip with a notice for clearly-non-marionette flavours
|
|
330
|
+
// (xpcshell, or paths that don't look browser-chrome/mochitest) so the
|
|
331
|
+
// operator knows the preflight took the override but mach was not
|
|
332
|
+
// auto-configured. Same escape valve applies: any mach arg can still
|
|
333
|
+
// be supplied via `--mach-arg`.
|
|
334
|
+
if (options.marionettePort !== undefined) {
|
|
335
|
+
const operatorAlreadyForwarded = forwardedPort !== undefined;
|
|
336
|
+
if (operatorAlreadyForwarded) {
|
|
337
|
+
info(`--marionette-port=${options.marionettePort} set, but the same port is already forwarded via --mach-arg; skipping auto-forward.`);
|
|
338
|
+
}
|
|
339
|
+
else if (isMarionetteFlavor(normalizedPaths, options.machArg ?? [])) {
|
|
340
|
+
extraArgs.push(`--setpref=marionette.port=${options.marionettePort}`);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
info(`--marionette-port=${options.marionettePort} applied to the preflight probe, but the test paths do not look browser-chrome/mochitest — mach is not auto-configured. Pass --mach-arg --setpref=marionette.port=${options.marionettePort} explicitly if mach should also use this port.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
306
346
|
// xpcshell appdir auto-injection — see src/core/xpcshell-appdir.ts for the
|
|
307
347
|
// full motivation. On rebranded forks (appname != "firefox") the upstream
|
|
308
348
|
// harness silently ignores `firefox-appdir = "browser"` directives in the
|
|
@@ -368,6 +408,13 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
|
368
408
|
acc.push(value);
|
|
369
409
|
return acc;
|
|
370
410
|
}, [])
|
|
411
|
+
.option('--marionette-port <port>', 'Override the Marionette control port (default 2828) for the stale-browser probe, the --doctor preflight, and the auto-forwarded --setpref=marionette.port=<n> arg passed to mach. Use this when a stale process holds 2828 or a CI runner reserves a different port.', (raw) => {
|
|
412
|
+
const n = Number.parseInt(raw, 10);
|
|
413
|
+
if (!Number.isFinite(n) || n < 1 || n > 65535) {
|
|
414
|
+
throw new GeneralError(`--marionette-port must be an integer in 1..65535 (got "${raw}")`);
|
|
415
|
+
}
|
|
416
|
+
return n;
|
|
417
|
+
})
|
|
371
418
|
.action(withErrorHandling(async (paths, options) => {
|
|
372
419
|
await testCommand(getProjectRoot(), paths, pickDefined(options));
|
|
373
420
|
}));
|
|
@@ -15,8 +15,12 @@ export type AcornESTreeNode<T extends estree.Node = estree.Node> = T & {
|
|
|
15
15
|
* Parse JavaScript source as a **script** (not an ES module).
|
|
16
16
|
* All Mozilla chrome JS files (`browser-main.js`, `browser-init.js`,
|
|
17
17
|
* `customElements.js`, etc.) are scripts that run in a privileged scope.
|
|
18
|
+
*
|
|
19
|
+
* @param content - Source text to parse
|
|
20
|
+
* @param onComment - Optional array that acorn fills with comment nodes
|
|
21
|
+
* @returns Parsed program AST with character-offset positions
|
|
18
22
|
*/
|
|
19
|
-
export declare function parseScript(content: string): AcornESTreeNode<estree.Program>;
|
|
23
|
+
export declare function parseScript(content: string, onComment?: acorn.Comment[]): AcornESTreeNode<estree.Program>;
|
|
20
24
|
/**
|
|
21
25
|
* Parse JavaScript source as an **ES module**.
|
|
22
26
|
* Used for `.sys.mjs` files which use static import/export syntax.
|
|
@@ -5,12 +5,19 @@ import { walk } from 'estree-walker';
|
|
|
5
5
|
* Parse JavaScript source as a **script** (not an ES module).
|
|
6
6
|
* All Mozilla chrome JS files (`browser-main.js`, `browser-init.js`,
|
|
7
7
|
* `customElements.js`, etc.) are scripts that run in a privileged scope.
|
|
8
|
+
*
|
|
9
|
+
* @param content - Source text to parse
|
|
10
|
+
* @param onComment - Optional array that acorn fills with comment nodes
|
|
11
|
+
* @returns Parsed program AST with character-offset positions
|
|
8
12
|
*/
|
|
9
|
-
export function parseScript(content) {
|
|
10
|
-
|
|
13
|
+
export function parseScript(content, onComment) {
|
|
14
|
+
const opts = {
|
|
11
15
|
sourceType: 'script',
|
|
12
16
|
ecmaVersion: 'latest',
|
|
13
|
-
}
|
|
17
|
+
};
|
|
18
|
+
if (onComment)
|
|
19
|
+
opts.onComment = onComment;
|
|
20
|
+
return acorn.parse(content, opts);
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Parse JavaScript source as an **ES module**.
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Heuristic test for "this looks like a packaged-test source file" — the
|
|
3
3
|
* audit routes such paths to `_tests/` instead of `dist/`. Matches
|
|
4
4
|
* mochitest / xpcshell / browser-chrome conventions: any source under a
|
|
5
|
-
* `/test/` or `/tests/` directory,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `/test/` or `/tests/` directory, anywhere under a `testing/` subtree
|
|
6
|
+
* (which holds mochitest / marionette / xpcshell harness sources), or
|
|
7
|
+
* with a `browser_` / `test_` prefix on a `.js`/`.toml` basename. Test
|
|
8
|
+
* manifests (`*.toml`, `*.list`, `*.ini`) under those directories also
|
|
9
|
+
* qualify.
|
|
8
10
|
*
|
|
9
11
|
* @param sourcePath Engine-relative POSIX path
|
|
10
12
|
* @returns True when the file belongs to the test tree, not the bundle
|
|
@@ -30,9 +30,11 @@ const MAX_SCAN_DEPTH = 12;
|
|
|
30
30
|
* Heuristic test for "this looks like a packaged-test source file" — the
|
|
31
31
|
* audit routes such paths to `_tests/` instead of `dist/`. Matches
|
|
32
32
|
* mochitest / xpcshell / browser-chrome conventions: any source under a
|
|
33
|
-
* `/test/` or `/tests/` directory,
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* `/test/` or `/tests/` directory, anywhere under a `testing/` subtree
|
|
34
|
+
* (which holds mochitest / marionette / xpcshell harness sources), or
|
|
35
|
+
* with a `browser_` / `test_` prefix on a `.js`/`.toml` basename. Test
|
|
36
|
+
* manifests (`*.toml`, `*.list`, `*.ini`) under those directories also
|
|
37
|
+
* qualify.
|
|
36
38
|
*
|
|
37
39
|
* @param sourcePath Engine-relative POSIX path
|
|
38
40
|
* @returns True when the file belongs to the test tree, not the bundle
|
|
@@ -41,6 +43,13 @@ export function isTestPath(sourcePath) {
|
|
|
41
43
|
if (sourcePath.includes('/test/') || sourcePath.includes('/tests/')) {
|
|
42
44
|
return true;
|
|
43
45
|
}
|
|
46
|
+
// `testing/{mochitest,marionette,xpcshell,...}` are test-infrastructure
|
|
47
|
+
// trees that ship under `_tests/`, not `dist/`. Match both as a root
|
|
48
|
+
// segment (e.g. `testing/mochitest/api.js`) and as an interior segment
|
|
49
|
+
// (e.g. a vendored harness under `third_party/.../testing/...`).
|
|
50
|
+
if (sourcePath.startsWith('testing/') || sourcePath.includes('/testing/')) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
44
53
|
const name = basename(sourcePath);
|
|
45
54
|
if (/^browser_.+\.(js|toml|ini)$/.test(name))
|
|
46
55
|
return true;
|
|
@@ -19,7 +19,7 @@ export declare const SRC_DIR = "src";
|
|
|
19
19
|
/** Supported top-level fireforge.json keys backed by the current schema. */
|
|
20
20
|
export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "license", "wire", "patchLint", "markerComment"];
|
|
21
21
|
/** Supported config paths that can be read or set without --force. */
|
|
22
|
-
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "build", "build.jobs", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "markerComment"];
|
|
22
|
+
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "build", "build.jobs", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "markerComment"];
|
|
23
23
|
/**
|
|
24
24
|
* Gets all project paths based on a root directory.
|
|
25
25
|
* @param root - Root directory of the project
|
|
@@ -229,6 +229,10 @@ function parsePatchLintBlock(rec) {
|
|
|
229
229
|
if (testAssertionFloor !== undefined) {
|
|
230
230
|
out.testAssertionFloor = testAssertionFloor;
|
|
231
231
|
}
|
|
232
|
+
const chromeScriptJsDoc = parseSeverityGate(rec.raw('chromeScriptJsDoc'), 'patchLint.chromeScriptJsDoc');
|
|
233
|
+
if (chromeScriptJsDoc !== undefined) {
|
|
234
|
+
out.chromeScriptJsDoc = chromeScriptJsDoc;
|
|
235
|
+
}
|
|
232
236
|
return out;
|
|
233
237
|
}
|
|
234
238
|
//# sourceMappingURL=config-validate.js.map
|
|
@@ -48,3 +48,32 @@ export declare function probeMarionettePort(port?: number): Promise<MarionettePo
|
|
|
48
48
|
export declare function assertMarionettePortAvailable(port?: number, options?: {
|
|
49
49
|
binaryName?: string;
|
|
50
50
|
}): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Extracts a `--marionette-port=N` (or `--marionette-port N`) argument from
|
|
53
|
+
* a list of forwarded mach args, if present. Used so an operator passing the
|
|
54
|
+
* port via `--mach-arg --marionette-port=NNNN` gets the same preflight
|
|
55
|
+
* override they would from a first-class `--marionette-port` option, rather
|
|
56
|
+
* than the wrapper probing the default port and refusing.
|
|
57
|
+
*
|
|
58
|
+
* Also recognises `--setpref=marionette.port=NNNN` since that is the path
|
|
59
|
+
* the test command auto-forwards to mach.
|
|
60
|
+
*
|
|
61
|
+
* @param machArgs - Forwarded mach args as they would appear on the command
|
|
62
|
+
* line (one element per token; `--foo=bar` and `--foo bar` both supported).
|
|
63
|
+
* @returns The integer port if a recognised arg is present and parses; else
|
|
64
|
+
* `undefined`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractForwardedMarionettePort(machArgs: string[]): number | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Heuristic: do the test paths or forwarded mach args indicate a flavour
|
|
69
|
+
* that actually launches a Marionette-driven browser? Browser-chrome and
|
|
70
|
+
* mochitest do; xpcshell does not. Used to decide whether to auto-forward
|
|
71
|
+
* `--setpref=marionette.port=<n>` to mach when the operator passed
|
|
72
|
+
* `--marionette-port`. A no-paths invocation (the default "run all tests"
|
|
73
|
+
* shape) is treated as marionette-relevant since it includes browser-chrome.
|
|
74
|
+
*
|
|
75
|
+
* @param testPaths - Engine-relative paths after `stripEnginePrefix`.
|
|
76
|
+
* @param machArgs - Forwarded mach args (post-`--mach-arg`).
|
|
77
|
+
* @returns `true` when the run is likely to bind a Marionette listener.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isMarionetteFlavor(testPaths: string[], machArgs: string[]): boolean;
|
|
@@ -212,4 +212,86 @@ export async function assertMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
|
|
|
212
212
|
throw new GeneralError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
|
|
213
213
|
`This is not a FireForge-launched browser; stop the holder process or free the port before rerunning.`);
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Extracts a `--marionette-port=N` (or `--marionette-port N`) argument from
|
|
217
|
+
* a list of forwarded mach args, if present. Used so an operator passing the
|
|
218
|
+
* port via `--mach-arg --marionette-port=NNNN` gets the same preflight
|
|
219
|
+
* override they would from a first-class `--marionette-port` option, rather
|
|
220
|
+
* than the wrapper probing the default port and refusing.
|
|
221
|
+
*
|
|
222
|
+
* Also recognises `--setpref=marionette.port=NNNN` since that is the path
|
|
223
|
+
* the test command auto-forwards to mach.
|
|
224
|
+
*
|
|
225
|
+
* @param machArgs - Forwarded mach args as they would appear on the command
|
|
226
|
+
* line (one element per token; `--foo=bar` and `--foo bar` both supported).
|
|
227
|
+
* @returns The integer port if a recognised arg is present and parses; else
|
|
228
|
+
* `undefined`.
|
|
229
|
+
*/
|
|
230
|
+
export function extractForwardedMarionettePort(machArgs) {
|
|
231
|
+
for (let i = 0; i < machArgs.length; i++) {
|
|
232
|
+
const arg = machArgs[i];
|
|
233
|
+
if (arg === undefined)
|
|
234
|
+
continue;
|
|
235
|
+
// `--marionette-port=NNNN`
|
|
236
|
+
let match = /^--marionette-port=(\d+)$/.exec(arg);
|
|
237
|
+
if (match?.[1]) {
|
|
238
|
+
const n = Number.parseInt(match[1], 10);
|
|
239
|
+
if (Number.isFinite(n))
|
|
240
|
+
return n;
|
|
241
|
+
}
|
|
242
|
+
// `--marionette-port NNNN` (two tokens)
|
|
243
|
+
if (arg === '--marionette-port') {
|
|
244
|
+
const next = machArgs[i + 1];
|
|
245
|
+
if (next !== undefined) {
|
|
246
|
+
const n = Number.parseInt(next, 10);
|
|
247
|
+
if (Number.isFinite(n))
|
|
248
|
+
return n;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// `--setpref=marionette.port=NNNN` — the auto-forward shape; recognised
|
|
252
|
+
// here so a duplicate check at the call site can spot operator-supplied
|
|
253
|
+
// setprefs without re-implementing the parse.
|
|
254
|
+
match = /^--setpref=marionette\.port=(\d+)$/.exec(arg);
|
|
255
|
+
if (match?.[1]) {
|
|
256
|
+
const n = Number.parseInt(match[1], 10);
|
|
257
|
+
if (Number.isFinite(n))
|
|
258
|
+
return n;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Heuristic: do the test paths or forwarded mach args indicate a flavour
|
|
265
|
+
* that actually launches a Marionette-driven browser? Browser-chrome and
|
|
266
|
+
* mochitest do; xpcshell does not. Used to decide whether to auto-forward
|
|
267
|
+
* `--setpref=marionette.port=<n>` to mach when the operator passed
|
|
268
|
+
* `--marionette-port`. A no-paths invocation (the default "run all tests"
|
|
269
|
+
* shape) is treated as marionette-relevant since it includes browser-chrome.
|
|
270
|
+
*
|
|
271
|
+
* @param testPaths - Engine-relative paths after `stripEnginePrefix`.
|
|
272
|
+
* @param machArgs - Forwarded mach args (post-`--mach-arg`).
|
|
273
|
+
* @returns `true` when the run is likely to bind a Marionette listener.
|
|
274
|
+
*/
|
|
275
|
+
export function isMarionetteFlavor(testPaths, machArgs) {
|
|
276
|
+
for (const arg of machArgs) {
|
|
277
|
+
if (/^--flavor=xpcshell\b/.test(arg) || arg === '--flavor=xpcshell-tests')
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
for (const arg of machArgs) {
|
|
281
|
+
if (/^--flavor=(browser-chrome|mochitest|chrome|a11y)\b/.test(arg))
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (testPaths.length === 0)
|
|
285
|
+
return true;
|
|
286
|
+
for (const path of testPaths) {
|
|
287
|
+
const base = path.split('/').pop() ?? path;
|
|
288
|
+
if (/^browser_.+\.(js|ini|toml)$/.test(base))
|
|
289
|
+
return true;
|
|
290
|
+
if (path.includes('/mochitest/') || path.startsWith('mochitest/'))
|
|
291
|
+
return true;
|
|
292
|
+
if (path.includes('/browser-chrome/') || path.startsWith('browser-chrome/'))
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
215
297
|
//# sourceMappingURL=marionette-port.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based JSDoc validation for top-level declarations in patch-owned
|
|
3
|
+
* chrome subscripts (`browser/base/content/<binaryName>*.js` and similar
|
|
4
|
+
* `.js` files loaded via `Services.scriptloader.loadSubScript`).
|
|
5
|
+
*
|
|
6
|
+
* Why a separate module from {@link ./patch-lint-jsdoc.ts}: chrome
|
|
7
|
+
* subscripts are NOT ES modules. They are parsed as scripts (no static
|
|
8
|
+
* `export`) and their top-level `class`/`function` declarations are
|
|
9
|
+
* exposed to the loading window as globals rather than declared exports.
|
|
10
|
+
* The export-walker in `patch-lint-jsdoc.ts` would never visit them.
|
|
11
|
+
*
|
|
12
|
+
* The rule shape — "every top-level class needs a JSDoc, every method
|
|
13
|
+
* needs one when `chromeScriptJsDoc` is at `warning`/`error`, every
|
|
14
|
+
* top-level function needs a matching @param/@returns block" — is
|
|
15
|
+
* identical to the `.sys.mjs` rule once you remove the `export` framing,
|
|
16
|
+
* so the per-declaration validators are reused verbatim from the export
|
|
17
|
+
* module.
|
|
18
|
+
*/
|
|
19
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
20
|
+
import type { PatchLintSeverityGate } from '../types/config.js';
|
|
21
|
+
import type { JsDocIssue, ValidateExportJsDocOptions } from './patch-lint-jsdoc.js';
|
|
22
|
+
/**
|
|
23
|
+
* Validates JSDoc on top-level declarations in a chrome-subscript `.js`
|
|
24
|
+
* source file. A parse failure returns an empty issue list — chrome
|
|
25
|
+
* subscripts that use module-only syntax (rare) silently disable the
|
|
26
|
+
* rule rather than emitting confusing parse-error issues.
|
|
27
|
+
*
|
|
28
|
+
* @param source - File content
|
|
29
|
+
* @param options - Optional gates (e.g. class-method JSDoc severity).
|
|
30
|
+
* `classMethodMode` defaults to `'off'` — the orchestrator passes the
|
|
31
|
+
* `chromeScriptJsDoc` severity here so a single knob controls both
|
|
32
|
+
* class-level and method-level enforcement.
|
|
33
|
+
* @returns Array of JSDoc issues found
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateChromeScriptJsDoc(source: string, options?: ValidateExportJsDocOptions): JsDocIssue[];
|
|
36
|
+
/**
|
|
37
|
+
* Per-file dispatch: applies the chrome-subscript JSDoc rule to one file
|
|
38
|
+
* if it qualifies, mapping {@link JsDocIssue} into {@link PatchLintIssue}.
|
|
39
|
+
* Extracted so the orchestrator in `patch-lint.ts` stays under the
|
|
40
|
+
* project's per-file line budget — `patch-lint.ts` invokes this helper
|
|
41
|
+
* inline once per affected JS/MJS file. Returns an empty array when the
|
|
42
|
+
* file does not qualify (not a chrome subscript, not patch-owned, or the
|
|
43
|
+
* mode is `'off'` / unset). The orchestrator pre-computes `isChromeOwned`
|
|
44
|
+
* (true iff the file is a patch-owned `.js` non-`.sys.mjs`) so the call
|
|
45
|
+
* site fits on a single line.
|
|
46
|
+
*/
|
|
47
|
+
export declare function lintChromeScriptJsDocForFile(file: string, content: string, isChromeOwned: boolean, mode: PatchLintSeverityGate | undefined): PatchLintIssue[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* AST-based JSDoc validation for top-level declarations in patch-owned
|
|
4
|
+
* chrome subscripts (`browser/base/content/<binaryName>*.js` and similar
|
|
5
|
+
* `.js` files loaded via `Services.scriptloader.loadSubScript`).
|
|
6
|
+
*
|
|
7
|
+
* Why a separate module from {@link ./patch-lint-jsdoc.ts}: chrome
|
|
8
|
+
* subscripts are NOT ES modules. They are parsed as scripts (no static
|
|
9
|
+
* `export`) and their top-level `class`/`function` declarations are
|
|
10
|
+
* exposed to the loading window as globals rather than declared exports.
|
|
11
|
+
* The export-walker in `patch-lint-jsdoc.ts` would never visit them.
|
|
12
|
+
*
|
|
13
|
+
* The rule shape — "every top-level class needs a JSDoc, every method
|
|
14
|
+
* needs one when `chromeScriptJsDoc` is at `warning`/`error`, every
|
|
15
|
+
* top-level function needs a matching @param/@returns block" — is
|
|
16
|
+
* identical to the `.sys.mjs` rule once you remove the `export` framing,
|
|
17
|
+
* so the per-declaration validators are reused verbatim from the export
|
|
18
|
+
* module.
|
|
19
|
+
*/
|
|
20
|
+
import { parseScript } from './ast-utils.js';
|
|
21
|
+
import { validateClassDecl, validateClassMethods, validateFunctionDecl, } from './patch-lint-jsdoc.js';
|
|
22
|
+
/**
|
|
23
|
+
* Validates JSDoc on top-level declarations in a chrome-subscript `.js`
|
|
24
|
+
* source file. A parse failure returns an empty issue list — chrome
|
|
25
|
+
* subscripts that use module-only syntax (rare) silently disable the
|
|
26
|
+
* rule rather than emitting confusing parse-error issues.
|
|
27
|
+
*
|
|
28
|
+
* @param source - File content
|
|
29
|
+
* @param options - Optional gates (e.g. class-method JSDoc severity).
|
|
30
|
+
* `classMethodMode` defaults to `'off'` — the orchestrator passes the
|
|
31
|
+
* `chromeScriptJsDoc` severity here so a single knob controls both
|
|
32
|
+
* class-level and method-level enforcement.
|
|
33
|
+
* @returns Array of JSDoc issues found
|
|
34
|
+
*/
|
|
35
|
+
export function validateChromeScriptJsDoc(source, options) {
|
|
36
|
+
const classMethodMode = options?.classMethodMode ?? 'off';
|
|
37
|
+
const comments = [];
|
|
38
|
+
let ast;
|
|
39
|
+
try {
|
|
40
|
+
ast = parseScript(source, comments);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const issues = [];
|
|
46
|
+
const body = ast.body;
|
|
47
|
+
for (const node of body) {
|
|
48
|
+
if (node.type === 'FunctionDeclaration') {
|
|
49
|
+
validateFunctionDecl(node, comments, source, issues);
|
|
50
|
+
}
|
|
51
|
+
else if (node.type === 'ClassDeclaration') {
|
|
52
|
+
validateClassDecl(node, comments, source, issues);
|
|
53
|
+
if (classMethodMode !== 'off') {
|
|
54
|
+
validateClassMethods(node, comments, source, issues, classMethodMode);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Top-level `var Foo = class {...}` / `let Foo = function() {...}`
|
|
58
|
+
// patterns are intentionally out of scope for V1 — chrome subscripts
|
|
59
|
+
// overwhelmingly use bare `class`/`function` declarations and the
|
|
60
|
+
// variable-init form would require unwrapping the initializer. Add
|
|
61
|
+
// it later if a real chrome subscript uses that shape.
|
|
62
|
+
}
|
|
63
|
+
return issues;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Per-file dispatch: applies the chrome-subscript JSDoc rule to one file
|
|
67
|
+
* if it qualifies, mapping {@link JsDocIssue} into {@link PatchLintIssue}.
|
|
68
|
+
* Extracted so the orchestrator in `patch-lint.ts` stays under the
|
|
69
|
+
* project's per-file line budget — `patch-lint.ts` invokes this helper
|
|
70
|
+
* inline once per affected JS/MJS file. Returns an empty array when the
|
|
71
|
+
* file does not qualify (not a chrome subscript, not patch-owned, or the
|
|
72
|
+
* mode is `'off'` / unset). The orchestrator pre-computes `isChromeOwned`
|
|
73
|
+
* (true iff the file is a patch-owned `.js` non-`.sys.mjs`) so the call
|
|
74
|
+
* site fits on a single line.
|
|
75
|
+
*/
|
|
76
|
+
export function lintChromeScriptJsDocForFile(file, content, isChromeOwned, mode) {
|
|
77
|
+
if (!isChromeOwned || !mode || mode === 'off')
|
|
78
|
+
return [];
|
|
79
|
+
const jsdocIssues = validateChromeScriptJsDoc(content, { classMethodMode: mode });
|
|
80
|
+
return jsdocIssues.map((issue) => ({
|
|
81
|
+
file,
|
|
82
|
+
check: issue.check,
|
|
83
|
+
message: issue.message,
|
|
84
|
+
severity: issue.severity ?? mode,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=patch-lint-chrome-jsdoc.js.map
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* Separated from `patch-lint.ts` to keep both files within the
|
|
7
7
|
* project's per-file line budget.
|
|
8
8
|
*/
|
|
9
|
+
import type * as acorn from 'acorn';
|
|
10
|
+
import type { ClassDeclaration, FunctionDeclaration } from 'estree';
|
|
11
|
+
import type { AcornESTreeNode } from './ast-utils.js';
|
|
9
12
|
export type JsDocCheck = 'missing-jsdoc' | 'jsdoc-param-mismatch' | 'jsdoc-missing-returns' | 'missing-jsdoc-class-method' | 'jsdoc-class-method-param-mismatch' | 'jsdoc-class-method-missing-returns';
|
|
10
13
|
export interface JsDocIssue {
|
|
11
14
|
line: number;
|
|
@@ -19,6 +22,40 @@ export interface ValidateExportJsDocOptions {
|
|
|
19
22
|
/** Gate for class-method JSDoc enforcement. Default 'off' (no walking). */
|
|
20
23
|
classMethodMode?: ClassMethodMode;
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Validates a top-level function declaration: requires an attached JSDoc
|
|
27
|
+
* comment, then checks @param name matching and @returns presence. Exported
|
|
28
|
+
* so the chrome-subscript validator (`patch-lint-chrome-jsdoc.ts`) can reuse
|
|
29
|
+
* it on `parseScript`-produced declarations — the rule shape is identical
|
|
30
|
+
* between ES-module exports and chrome-subscript top-level declarations.
|
|
31
|
+
*
|
|
32
|
+
* @param fn - FunctionDeclaration AST node
|
|
33
|
+
* @param comments - All Acorn comments collected from the source
|
|
34
|
+
* @param source - Original source text (for line-number resolution)
|
|
35
|
+
* @param issues - Output sink for JSDoc issues
|
|
36
|
+
* @param lookupStart - Optional offset to use when locating the attached
|
|
37
|
+
* JSDoc (defaults to `fn.start`). Used by the export-walker so the JSDoc
|
|
38
|
+
* is found relative to the `export` keyword rather than the inner decl.
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateFunctionDecl(fn: AcornESTreeNode<FunctionDeclaration>, comments: acorn.Comment[], source: string, issues: JsDocIssue[], lookupStart?: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* Validates a top-level class declaration: requires an attached JSDoc
|
|
43
|
+
* comment on the class itself. Method-level checks live in
|
|
44
|
+
* {@link validateClassMethods}. Exported for reuse in the chrome-subscript
|
|
45
|
+
* validator.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateClassDecl(cls: AcornESTreeNode<ClassDeclaration>, comments: acorn.Comment[], source: string, issues: JsDocIssue[], lookupStart?: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* Walks an exported class body and emits class-method JSDoc issues per
|
|
50
|
+
* the configured severity. Skip rules (in evaluation order):
|
|
51
|
+
* 1. private syntax (`#foo`) and underscore-prefixed names
|
|
52
|
+
* 2. zero-parameter constructors
|
|
53
|
+
* 3. methods whose JSDoc carries `@private` or `@internal`
|
|
54
|
+
*
|
|
55
|
+
* Pure-override skip (`super.method(...args)`-only bodies bypassing the
|
|
56
|
+
* @returns check) is deferred — V1 keeps the rule simple.
|
|
57
|
+
*/
|
|
58
|
+
export declare function validateClassMethods(cls: AcornESTreeNode<ClassDeclaration>, comments: acorn.Comment[], source: string, issues: JsDocIssue[], severity: 'warning' | 'error'): void;
|
|
22
59
|
/**
|
|
23
60
|
* Validates JSDoc on exported declarations in a `.sys.mjs` source file.
|
|
24
61
|
*
|
|
@@ -166,7 +166,22 @@ function validateParamsAndReturns(fnNode, jsDoc, issues, ctx) {
|
|
|
166
166
|
// ---------------------------------------------------------------------------
|
|
167
167
|
// Top-level export validation
|
|
168
168
|
// ---------------------------------------------------------------------------
|
|
169
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Validates a top-level function declaration: requires an attached JSDoc
|
|
171
|
+
* comment, then checks @param name matching and @returns presence. Exported
|
|
172
|
+
* so the chrome-subscript validator (`patch-lint-chrome-jsdoc.ts`) can reuse
|
|
173
|
+
* it on `parseScript`-produced declarations — the rule shape is identical
|
|
174
|
+
* between ES-module exports and chrome-subscript top-level declarations.
|
|
175
|
+
*
|
|
176
|
+
* @param fn - FunctionDeclaration AST node
|
|
177
|
+
* @param comments - All Acorn comments collected from the source
|
|
178
|
+
* @param source - Original source text (for line-number resolution)
|
|
179
|
+
* @param issues - Output sink for JSDoc issues
|
|
180
|
+
* @param lookupStart - Optional offset to use when locating the attached
|
|
181
|
+
* JSDoc (defaults to `fn.start`). Used by the export-walker so the JSDoc
|
|
182
|
+
* is found relative to the `export` keyword rather than the inner decl.
|
|
183
|
+
*/
|
|
184
|
+
export function validateFunctionDecl(fn, comments, source, issues, lookupStart) {
|
|
170
185
|
const name = fn.id.name;
|
|
171
186
|
const start = lookupStart !== undefined ? lookupStart : fn.start;
|
|
172
187
|
const line = lineAt(source, start);
|
|
@@ -186,7 +201,13 @@ function validateFunctionDecl(fn, comments, source, issues, lookupStart) {
|
|
|
186
201
|
returnsCheck: 'jsdoc-missing-returns',
|
|
187
202
|
});
|
|
188
203
|
}
|
|
189
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Validates a top-level class declaration: requires an attached JSDoc
|
|
206
|
+
* comment on the class itself. Method-level checks live in
|
|
207
|
+
* {@link validateClassMethods}. Exported for reuse in the chrome-subscript
|
|
208
|
+
* validator.
|
|
209
|
+
*/
|
|
210
|
+
export function validateClassDecl(cls, comments, source, issues, lookupStart) {
|
|
190
211
|
const name = cls.id.name;
|
|
191
212
|
const start = lookupStart !== undefined ? lookupStart : cls.start;
|
|
192
213
|
const line = lineAt(source, start);
|
|
@@ -254,7 +275,7 @@ function classMethodLabel(className, method, name) {
|
|
|
254
275
|
* Pure-override skip (`super.method(...args)`-only bodies bypassing the
|
|
255
276
|
* @returns check) is deferred — V1 keeps the rule simple.
|
|
256
277
|
*/
|
|
257
|
-
function validateClassMethods(cls, comments, source, issues, severity) {
|
|
278
|
+
export function validateClassMethods(cls, comments, source, issues, severity) {
|
|
258
279
|
const className = cls.id.name;
|
|
259
280
|
for (const member of cls.body.body) {
|
|
260
281
|
if (member.type !== 'MethodDefinition')
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Patch ownership resolution for
|
|
2
|
+
* Patch ownership resolution for JS-shaped files.
|
|
3
3
|
*
|
|
4
4
|
* A file is "patch-owned" when it was created by the project's patch
|
|
5
5
|
* queue rather than being an upstream Firefox file that happens to be
|
|
6
|
-
* modified. This module computes the set of patch-owned
|
|
7
|
-
*
|
|
6
|
+
* modified. This module computes the set of patch-owned paths so lint
|
|
7
|
+
* rules can scope enforcement to project code only.
|
|
8
|
+
*
|
|
9
|
+
* The two resolvers are kept separate (one per extension predicate)
|
|
10
|
+
* because the downstream rules differ: `.sys.mjs` files go through
|
|
11
|
+
* `runCheckJs` (TypeScript checkJs) and the export-walker JSDoc rule;
|
|
12
|
+
* chrome subscripts (`.js` non-`.sys.mjs`) only get the script-walker
|
|
13
|
+
* JSDoc rule. Mixing them in a single set would silently broaden
|
|
14
|
+
* `runCheckJs` to chrome subscripts, which it is not designed for.
|
|
8
15
|
*/
|
|
9
16
|
import type { PatchQueueContext } from './patch-lint-cross.js';
|
|
10
17
|
/**
|
|
@@ -23,3 +30,14 @@ import type { PatchQueueContext } from './patch-lint-cross.js';
|
|
|
23
30
|
* @returns Set of patch-owned `.sys.mjs` file paths
|
|
24
31
|
*/
|
|
25
32
|
export declare function resolvePatchOwnedSysMjs(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the set of file paths that are patch-owned chrome subscripts
|
|
35
|
+
* (`.js` files that are not `.sys.mjs` modules — typically
|
|
36
|
+
* `browser/base/content/<binaryName>*.js` and similar). Same ownership
|
|
37
|
+
* semantics as {@link resolvePatchOwnedSysMjs}.
|
|
38
|
+
*
|
|
39
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
40
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
41
|
+
* @returns Set of patch-owned chrome-subscript file paths
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Patch ownership resolution for
|
|
3
|
+
* Patch ownership resolution for JS-shaped files.
|
|
4
4
|
*
|
|
5
5
|
* A file is "patch-owned" when it was created by the project's patch
|
|
6
6
|
* queue rather than being an upstream Firefox file that happens to be
|
|
7
|
-
* modified. This module computes the set of patch-owned
|
|
8
|
-
*
|
|
7
|
+
* modified. This module computes the set of patch-owned paths so lint
|
|
8
|
+
* rules can scope enforcement to project code only.
|
|
9
|
+
*
|
|
10
|
+
* The two resolvers are kept separate (one per extension predicate)
|
|
11
|
+
* because the downstream rules differ: `.sys.mjs` files go through
|
|
12
|
+
* `runCheckJs` (TypeScript checkJs) and the export-walker JSDoc rule;
|
|
13
|
+
* chrome subscripts (`.js` non-`.sys.mjs`) only get the script-walker
|
|
14
|
+
* JSDoc rule. Mixing them in a single set would silently broaden
|
|
15
|
+
* `runCheckJs` to chrome subscripts, which it is not designed for.
|
|
9
16
|
*/
|
|
10
17
|
import { collectNewFileCreatorsByPath } from './patch-lint-cross.js';
|
|
18
|
+
/**
|
|
19
|
+
* Returns the set of patch-owned files matching `predicate`. Internal
|
|
20
|
+
* helper shared by the per-extension resolvers below.
|
|
21
|
+
*/
|
|
22
|
+
function resolveOwned(currentNewFiles, patchQueueCtx, predicate) {
|
|
23
|
+
const owned = new Set();
|
|
24
|
+
for (const file of currentNewFiles) {
|
|
25
|
+
if (predicate(file)) {
|
|
26
|
+
owned.add(file);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (patchQueueCtx) {
|
|
30
|
+
const creators = collectNewFileCreatorsByPath(patchQueueCtx);
|
|
31
|
+
for (const [file, owners] of creators) {
|
|
32
|
+
if (predicate(file) && owners.length > 0) {
|
|
33
|
+
owned.add(file);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return owned;
|
|
38
|
+
}
|
|
11
39
|
/**
|
|
12
40
|
* Returns the set of file paths that are patch-owned `.sys.mjs` files.
|
|
13
41
|
*
|
|
@@ -24,20 +52,19 @@ import { collectNewFileCreatorsByPath } from './patch-lint-cross.js';
|
|
|
24
52
|
* @returns Set of patch-owned `.sys.mjs` file paths
|
|
25
53
|
*/
|
|
26
54
|
export function resolvePatchOwnedSysMjs(currentNewFiles, patchQueueCtx) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return owned;
|
|
55
|
+
return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.sys.mjs'));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the set of file paths that are patch-owned chrome subscripts
|
|
59
|
+
* (`.js` files that are not `.sys.mjs` modules — typically
|
|
60
|
+
* `browser/base/content/<binaryName>*.js` and similar). Same ownership
|
|
61
|
+
* semantics as {@link resolvePatchOwnedSysMjs}.
|
|
62
|
+
*
|
|
63
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
64
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
65
|
+
* @returns Set of patch-owned chrome-subscript file paths
|
|
66
|
+
*/
|
|
67
|
+
export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
|
|
68
|
+
return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
|
|
42
69
|
}
|
|
43
70
|
//# sourceMappingURL=patch-lint-ownership.js.map
|
|
@@ -5,7 +5,7 @@ export { runCheckJs } from './patch-lint-checkjs.js';
|
|
|
5
5
|
export { buildPatchQueueContext, collectNewFileCreatorsByPath, type ExtractedSpecifier, 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';
|
|
7
7
|
export { type JsDocCheck, type JsDocIssue, validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
8
|
-
export { resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
8
|
+
export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
9
9
|
/**
|
|
10
10
|
* Counts the total lines in a unified diff and the number of non-binary
|
|
11
11
|
* text lines, so binary hunks do not inflate patch size checks.
|
|
@@ -55,9 +55,14 @@ export declare function lintNewFileHeaders(repoDir: string, newFiles: string[],
|
|
|
55
55
|
* @param newFiles - Set of files that are newly created in this patch
|
|
56
56
|
* @param config - Project configuration
|
|
57
57
|
* @param patchOwnedFiles - Optional set of patch-owned `.sys.mjs` paths for scoped JSDoc enforcement
|
|
58
|
+
* @param patchOwnedChromeScripts - Optional set of patch-owned chrome
|
|
59
|
+
* subscripts (`.js` non-`.sys.mjs`) for scoped chrome-script JSDoc
|
|
60
|
+
* enforcement. Built by {@link resolvePatchOwnedChromeScripts}; passed
|
|
61
|
+
* separately from `patchOwnedFiles` so the `runCheckJs` consumer (which
|
|
62
|
+
* only accepts `.sys.mjs`) is not silently broadened.
|
|
58
63
|
* @returns Array of lint issues
|
|
59
64
|
*/
|
|
60
|
-
export declare function lintPatchedJs(repoDir: string, affectedFiles: string[], newFiles: Set<string>, config: FireForgeConfig, patchOwnedFiles?: Set<string>): Promise<PatchLintIssue[]>;
|
|
65
|
+
export declare function lintPatchedJs(repoDir: string, affectedFiles: string[], newFiles: Set<string>, config: FireForgeConfig, patchOwnedFiles?: Set<string>, patchOwnedChromeScripts?: Set<string>): Promise<PatchLintIssue[]>;
|
|
61
66
|
/**
|
|
62
67
|
* Checks that modifications to existing (non-new) JS/MJS files include at
|
|
63
68
|
* least one `// BINARYNAME:` comment in the added lines.
|
|
@@ -7,10 +7,11 @@ import { hasRawCssColors, stripJsComments } from '../utils/regex.js';
|
|
|
7
7
|
import { loadFurnaceConfig } from './furnace-config.js';
|
|
8
8
|
import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, } from './license-headers.js';
|
|
9
9
|
import { runCheckJs } from './patch-lint-checkjs.js';
|
|
10
|
+
import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
|
|
10
11
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
11
12
|
import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
|
|
12
13
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
|
-
import { resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
14
|
+
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Cross-patch lint re-exports
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -24,7 +25,7 @@ export { runCheckJs } from './patch-lint-checkjs.js';
|
|
|
24
25
|
export { buildPatchQueueContext, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
|
|
25
26
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
26
27
|
export { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
27
|
-
export { resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
28
|
+
export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Helpers
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
@@ -403,9 +404,14 @@ export async function lintNewFileHeaders(repoDir, newFiles, config) {
|
|
|
403
404
|
* @param newFiles - Set of files that are newly created in this patch
|
|
404
405
|
* @param config - Project configuration
|
|
405
406
|
* @param patchOwnedFiles - Optional set of patch-owned `.sys.mjs` paths for scoped JSDoc enforcement
|
|
407
|
+
* @param patchOwnedChromeScripts - Optional set of patch-owned chrome
|
|
408
|
+
* subscripts (`.js` non-`.sys.mjs`) for scoped chrome-script JSDoc
|
|
409
|
+
* enforcement. Built by {@link resolvePatchOwnedChromeScripts}; passed
|
|
410
|
+
* separately from `patchOwnedFiles` so the `runCheckJs` consumer (which
|
|
411
|
+
* only accepts `.sys.mjs`) is not silently broadened.
|
|
406
412
|
* @returns Array of lint issues
|
|
407
413
|
*/
|
|
408
|
-
export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles) {
|
|
414
|
+
export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles, patchOwnedChromeScripts) {
|
|
409
415
|
const jsFiles = affectedFiles.filter(isJsFile);
|
|
410
416
|
if (jsFiles.length === 0)
|
|
411
417
|
return [];
|
|
@@ -476,9 +482,20 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
476
482
|
});
|
|
477
483
|
}
|
|
478
484
|
}
|
|
479
|
-
//
|
|
485
|
+
// 3a. JSDoc on top-level declarations in patch-owned chrome subscripts.
|
|
486
|
+
// Dispatched out-of-line to keep this file under the per-file line
|
|
487
|
+
// budget. See `patch-lint-chrome-jsdoc.ts` for the rule body.
|
|
488
|
+
const isChromeOwned = file.endsWith('.js') &&
|
|
489
|
+
!isSysMjs &&
|
|
490
|
+
(patchOwnedChromeScripts ? patchOwnedChromeScripts.has(file) : isNew);
|
|
491
|
+
const chromeMode = config.patchLint?.chromeScriptJsDoc;
|
|
492
|
+
issues.push(...lintChromeScriptJsDocForFile(file, content, isChromeOwned, chromeMode));
|
|
493
|
+
// 3b. Assertion floor for browser-chrome tests touched by this patch
|
|
494
|
+
// (covers both newly introduced files and modified upstream tests —
|
|
495
|
+
// a patch that strips the last `Assert.equal` from an existing
|
|
496
|
+
// browser_*.js silently passed under the old `isNew` gate).
|
|
480
497
|
const assertionFloor = config.patchLint?.testAssertionFloor;
|
|
481
|
-
if (assertionFloor && assertionFloor !== 'off' &&
|
|
498
|
+
if (assertionFloor && assertionFloor !== 'off' && isBrowserChromeTestFile(file)) {
|
|
482
499
|
const ASSERTION_TOKENS = ['Assert.', 'ok(', 'is(', 'isnot(', 'isDeeply('];
|
|
483
500
|
const hasAssertion = ASSERTION_TOKENS.some((tok) => strippedContent.includes(tok));
|
|
484
501
|
if (!hasAssertion) {
|
|
@@ -703,10 +720,11 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
703
720
|
const newFiles = detectNewFilesInDiff(diffContent);
|
|
704
721
|
const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
|
|
705
722
|
const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
|
|
723
|
+
const patchOwnedChromeScripts = resolvePatchOwnedChromeScripts(newFiles, patchQueueCtx);
|
|
706
724
|
const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues] = await Promise.all([
|
|
707
725
|
lintPatchedCss(repoDir, affectedFiles, diffContent, config),
|
|
708
726
|
lintNewFileHeaders(repoDir, [...newFiles], config),
|
|
709
|
-
lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles),
|
|
727
|
+
lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles, patchOwnedChromeScripts),
|
|
710
728
|
lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
|
|
711
729
|
]);
|
|
712
730
|
const modCommentIssues = lintModificationComments(diffContent, config);
|
|
@@ -316,6 +316,14 @@ export interface TestOptions {
|
|
|
316
316
|
* values appear after `--headless` if both are set.
|
|
317
317
|
*/
|
|
318
318
|
machArg?: string[];
|
|
319
|
+
/**
|
|
320
|
+
* Override the Marionette control port (default 2828) used by the
|
|
321
|
+
* stale-browser probe, the `--doctor` preflight, and the auto-forwarded
|
|
322
|
+
* `--setpref=marionette.port=<n>` arg passed to mach. Set this when a
|
|
323
|
+
* stale process holds the default port and `kill` is not an option, or
|
|
324
|
+
* when a CI runner reserves a different port for parallel test runs.
|
|
325
|
+
*/
|
|
326
|
+
marionettePort?: number;
|
|
319
327
|
}
|
|
320
328
|
/**
|
|
321
329
|
* Options for the furnace apply command.
|
|
@@ -74,8 +74,19 @@ export interface PatchLintConfig {
|
|
|
74
74
|
rawColorAllowlist?: string[];
|
|
75
75
|
/** Enforce JSDoc on class-method exports in patch-owned .sys.mjs files. Default: 'off'. */
|
|
76
76
|
jsdocClassMethods?: PatchLintSeverityGate;
|
|
77
|
-
/** Require ≥1 assertion in patch-
|
|
77
|
+
/** Require ≥1 assertion in any patch-touched browser_*.js test file (new or modified). Default: 'off'. */
|
|
78
78
|
testAssertionFloor?: PatchLintSeverityGate;
|
|
79
|
+
/**
|
|
80
|
+
* Enforce JSDoc on top-level classes (and their methods) and functions
|
|
81
|
+
* in patch-owned chrome subscripts (`.js` files loaded via
|
|
82
|
+
* `Services.scriptloader.loadSubScript`, e.g.
|
|
83
|
+
* `browser/base/content/<binaryName>*.js`). Distinct from
|
|
84
|
+
* `jsdocClassMethods` because chrome subscripts are parsed as scripts,
|
|
85
|
+
* not ES modules — using one flag for both would silently disable the
|
|
86
|
+
* rule when a chrome subscript was fed to the module parser. Default:
|
|
87
|
+
* 'off'.
|
|
88
|
+
*/
|
|
89
|
+
chromeScriptJsDoc?: PatchLintSeverityGate;
|
|
79
90
|
}
|
|
80
91
|
/**
|
|
81
92
|
* Build mode for mach.
|