@hominis/fireforge 0.18.8 → 0.18.10
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/lint.d.ts +36 -0
- package/dist/src/commands/lint.js +61 -1
- package/dist/src/commands/patch/index.d.ts +5 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore.d.ts +8 -0
- package/dist/src/commands/patch/lint-ignore.js +8 -4
- package/dist/src/commands/patch/rename.d.ts +36 -0
- package/dist/src/commands/patch/rename.js +244 -0
- 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/license-headers.d.ts +5 -0
- package/dist/src/core/license-headers.js +46 -5
- package/dist/src/core/marionette-port.d.ts +29 -0
- package/dist/src/core/marionette-port.js +82 -0
- package/dist/src/core/patch-export.d.ts +10 -0
- package/dist/src/core/patch-export.js +8 -2
- 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-cross.js +6 -1
- 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/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +36 -0
- package/dist/src/types/config.d.ts +12 -1
- package/package.json +1 -1
|
@@ -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
|
|
@@ -29,6 +29,11 @@ export declare function getLicenseHeader(license: ProjectLicense, style: Comment
|
|
|
29
29
|
* standard MPL header — operators were forced to `--skip-lint` over a real
|
|
30
30
|
* false positive.
|
|
31
31
|
*
|
|
32
|
+
* Editor-directive block comments (`/* -*- ... -*- *\/`, `/* vim: ... *\/`)
|
|
33
|
+
* leading the file are tolerated — Mozilla's canonical layout puts those
|
|
34
|
+
* on lines 1–2 with the MPL header on lines 3+, which the raw
|
|
35
|
+
* `startsWith` check would otherwise miss.
|
|
36
|
+
*
|
|
32
37
|
* @param content - File content to check
|
|
33
38
|
* @param style - Comment syntax of the file
|
|
34
39
|
*/
|
|
@@ -53,6 +53,39 @@ export function getLicenseHeader(license, style) {
|
|
|
53
53
|
return lines.map((l) => `# ${l}`).join('\n');
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Single-line `/* ... *\/` block comments containing either an Emacs
|
|
58
|
+
* file-mode marker (`-*-`) or a vim modeline (`vim:`) — Mozilla's
|
|
59
|
+
* canonical first-line editor directives that legitimately precede the
|
|
60
|
+
* license header in many Firefox source files.
|
|
61
|
+
*
|
|
62
|
+
* Restricted to single-line blocks so a multi-line license header never
|
|
63
|
+
* gets accidentally consumed.
|
|
64
|
+
*/
|
|
65
|
+
const EDITOR_DIRECTIVE_BLOCK_COMMENT = /^[ \t]*\/\*[^\r\n]*?(?:-\*-|\bvim:)[^\r\n]*?\*\/[ \t]*\r?\n?/;
|
|
66
|
+
/**
|
|
67
|
+
* Strips any leading run of editor-directive block comments and blank
|
|
68
|
+
* lines, returning the remaining content.
|
|
69
|
+
*
|
|
70
|
+
* Mozilla's coding convention places editor directives like
|
|
71
|
+
* `/* -*- Mode: javascript; ... -*- *\/` and `/* vim: set ... *\/` on
|
|
72
|
+
* lines 1–2, with the canonical license header following on lines 3+.
|
|
73
|
+
* The raw `content.startsWith(...)` check used by {@link hasAnyLicenseHeader}
|
|
74
|
+
* never matches in that shape; this helper lets the caller test the
|
|
75
|
+
* post-directive prefix as a fallback.
|
|
76
|
+
*
|
|
77
|
+
* @param content - File content to strip
|
|
78
|
+
*/
|
|
79
|
+
function stripLeadingEditorDirectives(content) {
|
|
80
|
+
let result = content;
|
|
81
|
+
let prev;
|
|
82
|
+
do {
|
|
83
|
+
prev = result;
|
|
84
|
+
result = result.replace(/^[ \t]*\r?\n/, '');
|
|
85
|
+
result = result.replace(EDITOR_DIRECTIVE_BLOCK_COMMENT, '');
|
|
86
|
+
} while (result !== prev);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
56
89
|
/**
|
|
57
90
|
* Returns true if `content` starts with any known license header for the
|
|
58
91
|
* given comment style.
|
|
@@ -65,16 +98,24 @@ export function getLicenseHeader(license, style) {
|
|
|
65
98
|
* standard MPL header — operators were forced to `--skip-lint` over a real
|
|
66
99
|
* false positive.
|
|
67
100
|
*
|
|
101
|
+
* Editor-directive block comments (`/* -*- ... -*- *\/`, `/* vim: ... *\/`)
|
|
102
|
+
* leading the file are tolerated — Mozilla's canonical layout puts those
|
|
103
|
+
* on lines 1–2 with the MPL header on lines 3+, which the raw
|
|
104
|
+
* `startsWith` check would otherwise miss.
|
|
105
|
+
*
|
|
68
106
|
* @param content - File content to check
|
|
69
107
|
* @param style - Comment syntax of the file
|
|
70
108
|
*/
|
|
71
109
|
export function hasAnyLicenseHeader(content, style) {
|
|
110
|
+
const candidates = [content, stripLeadingEditorDirectives(content)];
|
|
72
111
|
const licenses = Object.keys(HEADER_LINES);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
if (licenses.some((license) => candidate.startsWith(getLicenseHeader(license, style)))) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (style === 'js' && candidate.startsWith(getLicenseHeader('MPL-2.0', 'css'))) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
78
119
|
}
|
|
79
120
|
return false;
|
|
80
121
|
}
|
|
@@ -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
|
|
@@ -5,6 +5,16 @@ import type { PatchCategory, PatchesManifest, PatchInfo, PatchMetadata } from '.
|
|
|
5
5
|
* @returns Next patch number (e.g., "005" for 4 existing patches)
|
|
6
6
|
*/
|
|
7
7
|
export declare function getNextPatchNumber(patchesDir: string): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Sanitizes a human-readable name into a filename slug.
|
|
10
|
+
*
|
|
11
|
+
* Exported so `patch rename` can produce a filename slug from its
|
|
12
|
+
* `--to <new-name>` argument using the exact same convention `export`
|
|
13
|
+
* uses, without duplicating the lowercase + non-alnum collapse + length
|
|
14
|
+
* cap rules. Drift between the two would let an operator rename a patch
|
|
15
|
+
* to a slug `export` could never reach.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sanitizeName(name: string): string;
|
|
8
18
|
/**
|
|
9
19
|
* Generates the next patch filename with category.
|
|
10
20
|
* @param patchesDir - Path to the patches directory
|
|
@@ -25,9 +25,15 @@ export async function getNextPatchNumber(patchesDir) {
|
|
|
25
25
|
return String(nextNumber).padStart(Math.max(3, String(nextNumber).length), '0');
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Sanitizes a
|
|
28
|
+
* Sanitizes a human-readable name into a filename slug.
|
|
29
|
+
*
|
|
30
|
+
* Exported so `patch rename` can produce a filename slug from its
|
|
31
|
+
* `--to <new-name>` argument using the exact same convention `export`
|
|
32
|
+
* uses, without duplicating the lowercase + non-alnum collapse + length
|
|
33
|
+
* cap rules. Drift between the two would let an operator rename a patch
|
|
34
|
+
* to a slug `export` could never reach.
|
|
29
35
|
*/
|
|
30
|
-
function sanitizeName(name) {
|
|
36
|
+
export function sanitizeName(name) {
|
|
31
37
|
return name
|
|
32
38
|
.toLowerCase()
|
|
33
39
|
.replace(/[^a-z0-9]+/g, '-')
|
|
@@ -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
|
|
@@ -396,6 +396,10 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
396
396
|
.map((o) => `${o.filename}:${o.fullPath}`)
|
|
397
397
|
.sort((a, b) => a.localeCompare(b))
|
|
398
398
|
.join(',');
|
|
399
|
+
// Lowest ordinal that lands AFTER every later-ordered creator —
|
|
400
|
+
// turns the operator's "guess and re-run" loop into a single shot
|
|
401
|
+
// when the only fix is reordering.
|
|
402
|
+
const suggestedOrder = Math.max(...laterOwners.map((o) => o.order)) + 1;
|
|
399
403
|
issues.push({
|
|
400
404
|
file: sitePath,
|
|
401
405
|
check: 'forward-import',
|
|
@@ -404,7 +408,8 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
404
408
|
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
405
409
|
'Reorder the patches so the dependency is created first, move the import ' +
|
|
406
410
|
'into the later patch, or mark the import with ' +
|
|
407
|
-
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive
|
|
411
|
+
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive. ` +
|
|
412
|
+
`Closest legal ordinal that satisfies this dependency: ${suggestedOrder}.`,
|
|
408
413
|
severity: 'error',
|
|
409
414
|
});
|
|
410
415
|
}
|
|
@@ -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>;
|