@hominis/fireforge 0.39.0 → 0.41.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 +100 -0
- package/README.md +35 -5
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +27 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +27 -9
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +30 -10
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +73 -51
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint-report.d.ts +21 -0
- package/dist/src/commands/lint-report.js +62 -0
- package/dist/src/commands/lint.js +26 -22
- package/dist/src/commands/manifest.js +3 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +152 -51
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +112 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +105 -0
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +11 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +123 -289
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +29 -23
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +48 -0
- package/dist/src/commands/status-check.js +106 -0
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +190 -170
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +59 -7
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +19 -0
- package/dist/src/commands/tree.js +320 -0
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +45 -30
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +49 -14
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.d.ts +2 -2
- package/dist/src/core/patch-export.js +4 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +42 -2
- package/dist/src/core/patch-lint-checkjs.js +132 -8
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-css.js +64 -16
- package/dist/src/core/patch-lint-diff.d.ts +8 -0
- package/dist/src/core/patch-lint-diff.js +31 -0
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +2 -1
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +31 -0
- package/dist/src/core/patch-lint.js +114 -8
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +24 -16
- package/dist/src/core/smoke-patterns.js +26 -14
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +22 -1
- package/dist/src/core/status-classify.js +112 -70
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/token-category.d.ts +40 -0
- package/dist/src/core/token-category.js +264 -0
- package/dist/src/core/token-manager.d.ts +7 -0
- package/dist/src/core/token-manager.js +48 -196
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-cow.d.ts +18 -0
- package/dist/src/core/tree-cow.js +71 -0
- package/dist/src/core/tree-guard.d.ts +35 -0
- package/dist/src/core/tree-guard.js +179 -0
- package/dist/src/core/tree-store.d.ts +129 -0
- package/dist/src/core/tree-store.js +452 -0
- package/dist/src/core/typecheck-shim.d.ts +14 -0
- package/dist/src/core/typecheck-shim.js +41 -2
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
- package/dist/src/core/xpcshell-profile-dir.js +45 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/git.d.ts +8 -0
- package/dist/src/errors/git.js +17 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types/commands/options.d.ts +119 -3
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/config.d.ts +19 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +33 -2
- package/dist/src/utils/validation.js +57 -2
- package/package.json +13 -13
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
import type * as acorn from 'acorn';
|
|
10
10
|
import type { ClassDeclaration, FunctionDeclaration } from 'estree';
|
|
11
11
|
import type { AcornESTreeNode } from './ast-utils.js';
|
|
12
|
-
export type JsDocCheck =
|
|
12
|
+
export type JsDocCheck =
|
|
13
|
+
/** The source could not be parsed, so no JSDoc rule could run on it. */
|
|
14
|
+
'jsdoc-unparseable-source' | 'missing-jsdoc' | 'jsdoc-param-mismatch' | 'jsdoc-missing-returns' | 'missing-jsdoc-class-method' | 'jsdoc-class-method-param-mismatch' | 'jsdoc-class-method-missing-returns';
|
|
13
15
|
export interface JsDocIssue {
|
|
14
16
|
line: number;
|
|
15
17
|
check: JsDocCheck;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Separated from `patch-lint.ts` to keep both files within the
|
|
8
8
|
* project's per-file line budget.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { toError } from '../utils/errors.js';
|
|
11
|
+
import { asEstree, parseModule } from './ast-utils.js';
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// JSDoc comment helpers
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
@@ -339,7 +340,7 @@ export function validateClassMethods(cls, comments, source, issues, severity) {
|
|
|
339
340
|
for (const member of cls.body.body) {
|
|
340
341
|
if (member.type !== 'MethodDefinition')
|
|
341
342
|
continue;
|
|
342
|
-
const method = member;
|
|
343
|
+
const method = asEstree(member);
|
|
343
344
|
if (isPrivateMethodKey(method))
|
|
344
345
|
continue;
|
|
345
346
|
const name = staticMethodName(method);
|
|
@@ -369,7 +370,7 @@ export function validateClassMethods(cls, comments, source, issues, severity) {
|
|
|
369
370
|
continue;
|
|
370
371
|
}
|
|
371
372
|
const skipReturns = method.kind === 'constructor' || method.kind === 'set';
|
|
372
|
-
validateParamsAndReturns(method.value, jsDoc, issues, {
|
|
373
|
+
validateParamsAndReturns(asEstree(method.value), jsDoc, issues, {
|
|
373
374
|
label,
|
|
374
375
|
line,
|
|
375
376
|
paramCheck: 'jsdoc-class-method-param-mismatch',
|
|
@@ -396,8 +397,17 @@ export function validateExportJsDoc(source, options) {
|
|
|
396
397
|
try {
|
|
397
398
|
ast = parseModule(source, comments);
|
|
398
399
|
}
|
|
399
|
-
catch {
|
|
400
|
-
|
|
400
|
+
catch (error) {
|
|
401
|
+
// An unparseable source is NOT a clean file. Returning `[]` here was the
|
|
402
|
+
// same value as "fully documented", so a syntax error silently cleared
|
|
403
|
+
// every rule this module enforces. Report it instead.
|
|
404
|
+
return [
|
|
405
|
+
{
|
|
406
|
+
line: 1,
|
|
407
|
+
check: 'jsdoc-unparseable-source',
|
|
408
|
+
message: `Source could not be parsed for JSDoc analysis: ${toError(error).message}`,
|
|
409
|
+
},
|
|
410
|
+
];
|
|
401
411
|
}
|
|
402
412
|
const issues = [];
|
|
403
413
|
const body = ast.body;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
2
|
+
interface ModuleRegistrationQueueEntry {
|
|
3
|
+
filename: string;
|
|
4
|
+
newFiles: ReadonlyMap<string, string>;
|
|
5
|
+
modifiedFileAdditions: ReadonlyMap<string, string>;
|
|
6
|
+
}
|
|
7
|
+
interface ModuleRegistrationQueueContext {
|
|
8
|
+
entries: readonly ModuleRegistrationQueueEntry[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Flags a new `.sys.mjs` imported through a Firefox resource URL when the
|
|
12
|
+
* projected queue never adds it to a moz.build list.
|
|
13
|
+
*/
|
|
14
|
+
export declare function lintPatchQueueModuleRegistrations(ctx: ModuleRegistrationQueueContext): PatchLintIssue[];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Queue-wide registration lint for newly-created Firefox system modules.
|
|
4
|
+
*/
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
import { escapeRegex, stripJsComments } from '../utils/regex.js';
|
|
7
|
+
const IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
8
|
+
/** Maps Firefox's common system-module resource URLs to their source suffix. */
|
|
9
|
+
function moduleSourceSuffix(specifier) {
|
|
10
|
+
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
11
|
+
for (const prefix of ['resource:///modules/', 'resource://gre/modules/']) {
|
|
12
|
+
if (cleaned.startsWith(prefix))
|
|
13
|
+
return `/modules/${cleaned.slice(prefix.length)}`;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Resource-module strings in actual code (comments removed), de-duplicated. */
|
|
18
|
+
function extractResourceModuleSpecifiers(content) {
|
|
19
|
+
const stripped = stripJsComments(content);
|
|
20
|
+
const found = new Set();
|
|
21
|
+
const pattern = /["'](resource:\/\/(?:\/|gre\/)modules\/[^"']+\.sys\.mjs(?:[?#][^"']*)?)["']/g;
|
|
22
|
+
for (const match of stripped.matchAll(pattern)) {
|
|
23
|
+
if (match[1] !== undefined)
|
|
24
|
+
found.add(match[1]);
|
|
25
|
+
}
|
|
26
|
+
return [...found];
|
|
27
|
+
}
|
|
28
|
+
/** True when some patch in the projected queue adds the module to moz.build. */
|
|
29
|
+
function queueRegistersModule(ctx, leaf) {
|
|
30
|
+
const escaped = escapeRegex(leaf);
|
|
31
|
+
const quotedLeaf = new RegExp(`["']${escaped}["']`);
|
|
32
|
+
for (const entry of ctx.entries) {
|
|
33
|
+
for (const [path, content] of [...entry.newFiles, ...entry.modifiedFileAdditions]) {
|
|
34
|
+
if ((path === 'moz.build' || path.endsWith('/moz.build')) && quotedLeaf.test(content)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Flags a new `.sys.mjs` imported through a Firefox resource URL when the
|
|
43
|
+
* projected queue never adds it to a moz.build list.
|
|
44
|
+
*/
|
|
45
|
+
export function lintPatchQueueModuleRegistrations(ctx) {
|
|
46
|
+
const imported = new Map();
|
|
47
|
+
const collect = (entry, path, content) => {
|
|
48
|
+
if (!IMPORTABLE_EXTENSIONS.some((extension) => path.endsWith(extension)))
|
|
49
|
+
return;
|
|
50
|
+
for (const specifier of extractResourceModuleSpecifiers(content)) {
|
|
51
|
+
const suffix = moduleSourceSuffix(specifier);
|
|
52
|
+
if (suffix === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
const importers = imported.get(suffix) ?? new Set();
|
|
55
|
+
importers.add(entry.filename);
|
|
56
|
+
imported.set(suffix, importers);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
for (const entry of ctx.entries) {
|
|
60
|
+
for (const [path, content] of entry.newFiles)
|
|
61
|
+
collect(entry, path, content);
|
|
62
|
+
for (const [path, content] of entry.modifiedFileAdditions)
|
|
63
|
+
collect(entry, path, content);
|
|
64
|
+
}
|
|
65
|
+
const issues = [];
|
|
66
|
+
for (const entry of ctx.entries) {
|
|
67
|
+
for (const path of entry.newFiles.keys()) {
|
|
68
|
+
if (!path.endsWith('.sys.mjs'))
|
|
69
|
+
continue;
|
|
70
|
+
const matched = [...imported.entries()].find(([suffix]) => path.endsWith(suffix));
|
|
71
|
+
if (!matched || queueRegistersModule(ctx, basename(path)))
|
|
72
|
+
continue;
|
|
73
|
+
const [suffix, importers] = matched;
|
|
74
|
+
issues.push({
|
|
75
|
+
file: path,
|
|
76
|
+
check: 'unregistered-system-module',
|
|
77
|
+
patches: [...new Set([entry.filename, ...importers])],
|
|
78
|
+
fingerprint: `unregistered-system-module|${path}|${suffix}`,
|
|
79
|
+
message: `${path} is newly created and imported as a resource module, but no patch adds ` +
|
|
80
|
+
`${basename(path)} to a moz.build list. Register it in EXTRA_JS_MODULES (or the ` +
|
|
81
|
+
'appropriate namespaced list) before export; source-level typechecks can resolve the ' +
|
|
82
|
+
'file even though the packaged browser cannot.',
|
|
83
|
+
severity: 'error',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return issues;
|
|
88
|
+
}
|
|
@@ -31,40 +31,128 @@ const STRICT_ORDERED_MOZBUILD_LISTS = [
|
|
|
31
31
|
'XPIDL_SOURCES',
|
|
32
32
|
];
|
|
33
33
|
const LIST_OPENER = new RegExp(`^\\s*((?:${STRICT_ORDERED_MOZBUILD_LISTS.join('|')})(?:\\.[A-Za-z_][A-Za-z0-9_]*)*)\\s*\\+?=\\s*\\[`);
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Strips a Python trailing comment from a moz.build line.
|
|
36
|
+
*
|
|
37
|
+
* `#` inside a quoted string is not a comment, so the scan tracks quote state
|
|
38
|
+
* rather than using `indexOf('#')`.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* True when `line` contains a `]` outside any quoted span. A bracket inside an
|
|
42
|
+
* item (`"icons[2x].png"`) is filename text, not the list's close — reading it
|
|
43
|
+
* as one truncated the item set and left every later item unchecked.
|
|
44
|
+
*/
|
|
45
|
+
function hasUnquotedCloseBracket(line) {
|
|
46
|
+
let quote;
|
|
47
|
+
for (let i = 0; i < line.length; i++) {
|
|
48
|
+
const ch = line[i];
|
|
49
|
+
if (quote !== undefined) {
|
|
50
|
+
if (ch === '\\')
|
|
51
|
+
i += 1;
|
|
52
|
+
else if (ch === quote)
|
|
53
|
+
quote = undefined;
|
|
54
|
+
}
|
|
55
|
+
else if (ch === '"' || ch === "'") {
|
|
56
|
+
quote = ch;
|
|
57
|
+
}
|
|
58
|
+
else if (ch === ']') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function stripMozBuildComment(line) {
|
|
65
|
+
let quote;
|
|
66
|
+
for (let i = 0; i < line.length; i++) {
|
|
67
|
+
const ch = line[i];
|
|
68
|
+
if (quote !== undefined) {
|
|
69
|
+
if (ch === '\\')
|
|
70
|
+
i += 1;
|
|
71
|
+
else if (ch === quote)
|
|
72
|
+
quote = undefined;
|
|
73
|
+
}
|
|
74
|
+
else if (ch === '"' || ch === "'") {
|
|
75
|
+
quote = ch;
|
|
76
|
+
}
|
|
77
|
+
else if (ch === '#') {
|
|
78
|
+
return line.slice(0, i);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return line;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extracts every strict-ordered list (with items in file order) from moz.build
|
|
85
|
+
* content.
|
|
86
|
+
*
|
|
87
|
+
* Comments are stripped before both the item scan and the close-bracket test.
|
|
88
|
+
* moz.build is Python, and before 0.41.0 neither step knew that:
|
|
89
|
+
*
|
|
90
|
+
* - a quoted string in a trailing comment was scraped in as a phantom list
|
|
91
|
+
* item (`"Zeta.cpp", # replaces "Beta.cpp"` yielded a `Beta.cpp` entry), so
|
|
92
|
+
* the sort check reported an unsorted list naming a file that is not in it —
|
|
93
|
+
* with a fingerprint that could never stabilise, because the item does not
|
|
94
|
+
* exist to be moved;
|
|
95
|
+
* - a `]` anywhere in a comment (`# see foo[0]`) closed the list early and
|
|
96
|
+
* silently truncated the item set.
|
|
97
|
+
*
|
|
98
|
+
* The scan also no longer advances the caller's loop counter: an unterminated
|
|
99
|
+
* list used to consume the rest of the file, so every *later* list in it was
|
|
100
|
+
* skipped entirely.
|
|
101
|
+
*
|
|
102
|
+
* Nor does an unterminated list borrow the NEXT list's bracket. The forward
|
|
103
|
+
* scan stops at the next list opener rather than reading through it: without
|
|
104
|
+
* that, an unclosed `EXTRA_COMPONENTS` swallowed the following
|
|
105
|
+
* `EXTRA_JS_MODULES` — its items merged into the first list's item set and its
|
|
106
|
+
* `]` accepted as the first list's close — reporting a sorting error against a
|
|
107
|
+
* variable that never contained those items, and skipping the second list.
|
|
108
|
+
*/
|
|
35
109
|
function collectStrictOrderedLists(content) {
|
|
36
110
|
const lines = content.split('\n');
|
|
37
111
|
const occurrences = [];
|
|
38
112
|
for (let i = 0; i < lines.length; i++) {
|
|
39
|
-
const
|
|
40
|
-
const opener = LIST_OPENER.exec(line);
|
|
113
|
+
const opener = LIST_OPENER.exec(lines[i] ?? '');
|
|
41
114
|
if (!opener)
|
|
42
115
|
continue;
|
|
43
116
|
const varName = opener[1] ?? '';
|
|
44
117
|
const items = [];
|
|
45
118
|
// Items may start on the opener line (`VAR += ["a", "b"]`) or span
|
|
46
119
|
// following lines until the closing bracket.
|
|
47
|
-
let rest =
|
|
120
|
+
let rest = stripMozBuildComment((lines[i] ?? '').slice(opener[0].length));
|
|
48
121
|
let closed = false;
|
|
122
|
+
// Scan ahead on a local cursor so an unterminated list costs only itself.
|
|
123
|
+
let cursor = i;
|
|
49
124
|
for (;;) {
|
|
50
|
-
|
|
51
|
-
|
|
125
|
+
// Quote types are matched pairwise so an apostrophe inside a
|
|
126
|
+
// double-quoted item ("don't.cpp") cannot terminate the match early and
|
|
127
|
+
// scrape in a phantom item.
|
|
128
|
+
for (const quoted of rest.matchAll(/"([^"]+)"|'([^']+)'/g)) {
|
|
129
|
+
items.push(quoted[1] ?? quoted[2] ?? '');
|
|
52
130
|
}
|
|
53
|
-
if (
|
|
131
|
+
if (hasUnquotedCloseBracket(rest)) {
|
|
54
132
|
closed = true;
|
|
55
133
|
break;
|
|
56
134
|
}
|
|
57
|
-
|
|
58
|
-
if (
|
|
135
|
+
cursor += 1;
|
|
136
|
+
if (cursor >= lines.length)
|
|
59
137
|
break;
|
|
60
|
-
rest = lines[
|
|
138
|
+
rest = stripMozBuildComment(lines[cursor] ?? '');
|
|
139
|
+
if (LIST_OPENER.test(rest)) {
|
|
140
|
+
// A new list starts here, so the previous one never closed. Rewind so
|
|
141
|
+
// the outer loop examines this line as its own opener.
|
|
142
|
+
cursor -= 1;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
61
145
|
if (/^\s*\]/.test(rest)) {
|
|
62
146
|
closed = true;
|
|
63
147
|
break;
|
|
64
148
|
}
|
|
65
149
|
}
|
|
66
|
-
if (closed)
|
|
150
|
+
if (closed) {
|
|
67
151
|
occurrences.push({ varName, items });
|
|
152
|
+
// Resume after the list we just consumed; a list that never closed
|
|
153
|
+
// leaves `i` untouched so the next line is still examined.
|
|
154
|
+
i = cursor;
|
|
155
|
+
}
|
|
68
156
|
}
|
|
69
157
|
return occurrences;
|
|
70
158
|
}
|
|
@@ -41,3 +41,24 @@ export declare function resolvePatchOwnedSysMjs(currentNewFiles: Set<string>, pa
|
|
|
41
41
|
* @returns Set of patch-owned chrome-subscript file paths
|
|
42
42
|
*/
|
|
43
43
|
export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Test-script predicate for the `checkJsTestFiles` pass (FORGE G5):
|
|
46
|
+
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
47
|
+
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
48
|
+
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
49
|
+
* patch-lint.ts imports this module, so the reverse edge would create an
|
|
50
|
+
* import cycle (dpdm gate). The agreement is pinned by
|
|
51
|
+
* `patch-lint-ownership.test.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isTestScriptFile(file: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Returns the set of patch-owned test `.js` files (FORGE G5). Same
|
|
56
|
+
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
57
|
+
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
58
|
+
* script-scope program.
|
|
59
|
+
*
|
|
60
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
61
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
62
|
+
* @returns Set of patch-owned test-script file paths
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolvePatchOwnedTestScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
@@ -67,3 +67,33 @@ export function resolvePatchOwnedSysMjs(currentNewFiles, patchQueueCtx) {
|
|
|
67
67
|
export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
|
|
68
68
|
return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Test-script predicate for the `checkJsTestFiles` pass (FORGE G5):
|
|
72
|
+
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
73
|
+
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
74
|
+
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
75
|
+
* patch-lint.ts imports this module, so the reverse edge would create an
|
|
76
|
+
* import cycle (dpdm gate). The agreement is pinned by
|
|
77
|
+
* `patch-lint-ownership.test.ts`.
|
|
78
|
+
*/
|
|
79
|
+
export function isTestScriptFile(file) {
|
|
80
|
+
if (!file.endsWith('.js') || file.endsWith('.sys.mjs'))
|
|
81
|
+
return false;
|
|
82
|
+
if (file.includes('/test/'))
|
|
83
|
+
return true;
|
|
84
|
+
const basename = file.split('/').pop() ?? '';
|
|
85
|
+
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the set of patch-owned test `.js` files (FORGE G5). Same
|
|
89
|
+
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
90
|
+
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
91
|
+
* script-scope program.
|
|
92
|
+
*
|
|
93
|
+
* @param currentNewFiles - Files newly created in the current diff
|
|
94
|
+
* @param patchQueueCtx - Optional cross-patch context for queue-wide ownership
|
|
95
|
+
* @returns Set of patch-owned test-script file paths
|
|
96
|
+
*/
|
|
97
|
+
export function resolvePatchOwnedTestScripts(currentNewFiles, patchQueueCtx) {
|
|
98
|
+
return resolveOwned(currentNewFiles, patchQueueCtx, isTestScriptFile);
|
|
99
|
+
}
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* Public re-exports for {@link ./patch-lint.ts}. Split out so the
|
|
3
3
|
* orchestrator stays within the ESLint `max-lines` budget.
|
|
4
4
|
*/
|
|
5
|
-
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER,
|
|
5
|
+
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
|
|
6
6
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
7
|
+
export { lintPatchQueueModuleRegistrations } from './patch-lint-module-registration.js';
|
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
|
|
7
7
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
8
|
+
export { lintPatchQueueModuleRegistrations } from './patch-lint-module-registration.js';
|
|
@@ -4,6 +4,12 @@ import { type CommentStyle } from './license-headers.js';
|
|
|
4
4
|
import { lintPatchedCss } from './patch-lint-css.js';
|
|
5
5
|
export * from './patch-lint-reexports.js';
|
|
6
6
|
export { lintPatchedCss };
|
|
7
|
+
/**
|
|
8
|
+
* Canonical `[check] file: message` rendering of a patch-lint issue — the
|
|
9
|
+
* one string shape every reporter and refusal message uses, so operators
|
|
10
|
+
* can grep one format across lint output and refusal details.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatPatchLintIssue(issue: Pick<PatchLintIssue, 'check' | 'file' | 'message'>): string;
|
|
7
13
|
/**
|
|
8
14
|
* Counts the total lines in a unified diff and the number of non-binary
|
|
9
15
|
* text lines, so binary hunks do not inflate patch size checks.
|
|
@@ -24,6 +30,8 @@ export declare function countNonBinaryDiffLines(diffContent: string): {
|
|
|
24
30
|
* `browser_`, `test_`, or `xpcshell_` (all `.js`).
|
|
25
31
|
*/
|
|
26
32
|
export declare function isTestFile(file: string): boolean;
|
|
33
|
+
/** True when `strippedContent` contains at least one assertion call. */
|
|
34
|
+
export declare function hasBrowserChromeAssertion(strippedContent: string): boolean;
|
|
27
35
|
/**
|
|
28
36
|
* Detects comment style from file extension for license header checks.
|
|
29
37
|
*/
|
|
@@ -128,6 +136,21 @@ export type PatchSizeTierDecision = {
|
|
|
128
136
|
* defensive-for-tests choice.
|
|
129
137
|
*/
|
|
130
138
|
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
139
|
+
/**
|
|
140
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
141
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
142
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
143
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
144
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
145
|
+
*/
|
|
146
|
+
export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): {
|
|
147
|
+
lines: {
|
|
148
|
+
notice: number;
|
|
149
|
+
warning: number;
|
|
150
|
+
error: number;
|
|
151
|
+
};
|
|
152
|
+
maxFiles: number;
|
|
153
|
+
};
|
|
131
154
|
/**
|
|
132
155
|
* Checks patch size and emits advisory warnings.
|
|
133
156
|
*
|
|
@@ -179,6 +202,14 @@ export interface LintExportedPatchOptions {
|
|
|
179
202
|
* attributes findings per patch instead of rebuilding per patch.
|
|
180
203
|
*/
|
|
181
204
|
precomputedCheckJs?: readonly PatchLintIssue[];
|
|
205
|
+
/**
|
|
206
|
+
* Invoked with the issues DROPPED by the `ignoreChecks` waiver filter,
|
|
207
|
+
* when any were (FORGE G10). Suppression semantics are unchanged — the
|
|
208
|
+
* returned issue list still excludes them — but the caller can report
|
|
209
|
+
* the measurement a waived size finding carried (per-patch NOTICE lines
|
|
210
|
+
* and the `--report` JSON) instead of losing it entirely.
|
|
211
|
+
*/
|
|
212
|
+
onSuppressed?: (suppressed: PatchLintIssue[]) => void;
|
|
182
213
|
}
|
|
183
214
|
/**
|
|
184
215
|
* Runs all patch lint checks and returns combined issues.
|
|
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { pathExists, readText } from '../utils/fs.js';
|
|
4
4
|
import { stripJsComments } from '../utils/regex.js';
|
|
5
5
|
import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, hasUpstreamMplBlockHeader, } from './license-headers.js';
|
|
6
|
-
import { invokePatchLintCheckJs } from './patch-lint-checkjs.js';
|
|
6
|
+
import { invokePatchLintCheckJs, runCheckJsTestFilesGrouped } from './patch-lint-checkjs.js';
|
|
7
7
|
import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
|
|
8
8
|
import { lintPatchedCss } from './patch-lint-css.js';
|
|
9
9
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
@@ -12,7 +12,7 @@ import { hasRelativeImport } from './patch-lint-imports.js';
|
|
|
12
12
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
13
|
import { lintMozBuildSortedLists } from './patch-lint-mozbuild.js';
|
|
14
14
|
import { lintObserverTopics } from './patch-lint-observer.js';
|
|
15
|
-
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
15
|
+
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Cross-patch lint re-exports
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
@@ -49,6 +49,14 @@ function countContentLines(content) {
|
|
|
49
49
|
lines.pop();
|
|
50
50
|
return lines.length;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Canonical `[check] file: message` rendering of a patch-lint issue — the
|
|
54
|
+
* one string shape every reporter and refusal message uses, so operators
|
|
55
|
+
* can grep one format across lint output and refusal details.
|
|
56
|
+
*/
|
|
57
|
+
export function formatPatchLintIssue(issue) {
|
|
58
|
+
return `[${issue.check}] ${issue.file}: ${issue.message}`;
|
|
59
|
+
}
|
|
52
60
|
/**
|
|
53
61
|
* Counts the total lines in a unified diff and the number of non-binary
|
|
54
62
|
* text lines, so binary hunks do not inflate patch size checks.
|
|
@@ -136,6 +144,16 @@ const PATCH_FILES_THRESHOLDS = {
|
|
|
136
144
|
test: 5,
|
|
137
145
|
branding: 60,
|
|
138
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* Shared remedy sentence for the over-budget findings (FORGE H4): the two
|
|
149
|
+
* places an operator stands when a patch outgrows its budget must name the
|
|
150
|
+
* sanctioned one-transaction way out, or they reach for the wrong tools
|
|
151
|
+
* first (the field report tried two before finding it). One constant, four
|
|
152
|
+
* emission sites — the wording cannot drift.
|
|
153
|
+
*/
|
|
154
|
+
const SPLIT_HINT = 'Consider splitting into smaller, focused patches — ' +
|
|
155
|
+
'"fireforge patch move-files <from> <to> --file <path> --create --order <n>" ' +
|
|
156
|
+
'moves files into a new patch in one transaction.';
|
|
139
157
|
/**
|
|
140
158
|
* Fixed allowlist of non-branding sibling paths that real-world Firefox
|
|
141
159
|
* branding patches legitimately need to touch to register the new
|
|
@@ -214,6 +232,67 @@ function isBrowserChromeTestFile(file) {
|
|
|
214
232
|
return false;
|
|
215
233
|
return basename.startsWith('browser_');
|
|
216
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Bare assertion-call detection for the `test-needs-assertion` floor.
|
|
237
|
+
*
|
|
238
|
+
* Each alternative must sit at a call boundary: preceded by something that is
|
|
239
|
+
* not an identifier character, `.`, `#`, or `-`. Until 0.41.0 this was a plain
|
|
240
|
+
* `strippedContent.includes(tok)` over the tokens `Assert.`, `ok(`, `is(`,
|
|
241
|
+
* `isnot(`, `isDeeply(`, so any identifier merely *ending* in one satisfied
|
|
242
|
+
* the gate — `this.axis(3)` contains `is(`, `book(` contains `ok(`,
|
|
243
|
+
* `promiseIsDeeply(` contains `isDeeply(`. An assertion-free smoke test using
|
|
244
|
+
* any such call passed the floor, which is exactly what the rule exists to
|
|
245
|
+
* catch.
|
|
246
|
+
*
|
|
247
|
+
* The namespaced arm must name a member and a call. Matching the namespace and
|
|
248
|
+
* its dot alone — as `(?:Assert|SimpleTest)\.` did — re-opened the same hole
|
|
249
|
+
* from the other side: `SimpleTest` is mostly *harness* API, so
|
|
250
|
+
* `SimpleTest.finish()`, `SimpleTest.waitForExplicitFinish()` and
|
|
251
|
+
* `SimpleTest.requestCompleteLog()` each cleared the floor on their own. Those
|
|
252
|
+
* are the exact calls an assertion-free smoke test contains. The enumeration
|
|
253
|
+
* below is the mochitest/xpcshell assertion surface both namespaces share, and
|
|
254
|
+
* it is what the user-facing message already names.
|
|
255
|
+
*
|
|
256
|
+
* Two qualified spellings of the bare four still count: `window.ok(...)` (the
|
|
257
|
+
* globals ARE window properties) and an optional-chained `win?.ok(...)`.
|
|
258
|
+
* Blocking those flagged tests whose only assertions are real as
|
|
259
|
+
* assertion-free. `SpecialPowers.ok(` stays excluded — that is API surface,
|
|
260
|
+
* not an assertion.
|
|
261
|
+
*
|
|
262
|
+
* Deliberately still textual, not AST-based: this runs over patch bodies
|
|
263
|
+
* rather than resolvable sources, and the surrounding rule is a floor, not a
|
|
264
|
+
* proof.
|
|
265
|
+
*/
|
|
266
|
+
const ASSERTION_MEMBERS = [
|
|
267
|
+
'ok',
|
|
268
|
+
'is',
|
|
269
|
+
'isnot',
|
|
270
|
+
'isDeeply',
|
|
271
|
+
'equal',
|
|
272
|
+
'notEqual',
|
|
273
|
+
'deepEqual',
|
|
274
|
+
'notDeepEqual',
|
|
275
|
+
'strictEqual',
|
|
276
|
+
'notStrictEqual',
|
|
277
|
+
'greater',
|
|
278
|
+
'greaterOrEqual',
|
|
279
|
+
'less',
|
|
280
|
+
'lessOrEqual',
|
|
281
|
+
'stringMatches',
|
|
282
|
+
'stringContains',
|
|
283
|
+
'throws',
|
|
284
|
+
'rejects',
|
|
285
|
+
// `todo`, `todo_is`, `todo_isnot`, `info`-free — todo* record an expected
|
|
286
|
+
// failure, which is still a recorded assertion outcome.
|
|
287
|
+
'todo',
|
|
288
|
+
'todo_is',
|
|
289
|
+
'todo_isnot',
|
|
290
|
+
].join('|');
|
|
291
|
+
const BROWSER_CHROME_ASSERTION = new RegExp(`(?<![\\w.#$-])(?:(?:Assert|SimpleTest)\\.(?:${ASSERTION_MEMBERS})\\s*\\(|(?:window\\.|[\\w$]+\\?\\.)?(?:ok|is|isnot|isDeeply)\\s*\\()`);
|
|
292
|
+
/** True when `strippedContent` contains at least one assertion call. */
|
|
293
|
+
export function hasBrowserChromeAssertion(strippedContent) {
|
|
294
|
+
return BROWSER_CHROME_ASSERTION.test(strippedContent);
|
|
295
|
+
}
|
|
217
296
|
/**
|
|
218
297
|
* Detects comment style from file extension for license header checks.
|
|
219
298
|
*/
|
|
@@ -406,8 +485,7 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
406
485
|
// browser_*.js silently passed under the old `isNew` gate).
|
|
407
486
|
const assertionFloor = config.patchLint?.testAssertionFloor;
|
|
408
487
|
if (assertionFloor && assertionFloor !== 'off' && isBrowserChromeTestFile(file)) {
|
|
409
|
-
const
|
|
410
|
-
const hasAssertion = ASSERTION_TOKENS.some((tok) => strippedContent.includes(tok));
|
|
488
|
+
const hasAssertion = hasBrowserChromeAssertion(strippedContent);
|
|
411
489
|
if (!hasAssertion) {
|
|
412
490
|
issues.push({
|
|
413
491
|
file,
|
|
@@ -481,6 +559,19 @@ export function resolvePatchSizeTier(filesAffected, patchTier) {
|
|
|
481
559
|
return { tier: 'branding', source: 'auto' };
|
|
482
560
|
return { tier: 'general' };
|
|
483
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
564
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
565
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
566
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
567
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
568
|
+
*/
|
|
569
|
+
export function getPatchSizeThresholds(tier) {
|
|
570
|
+
return {
|
|
571
|
+
lines: { ...PATCH_LINE_THRESHOLDS[tier] },
|
|
572
|
+
maxFiles: PATCH_FILES_THRESHOLDS[tier],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
484
575
|
/**
|
|
485
576
|
* Checks patch size and emits advisory warnings.
|
|
486
577
|
*
|
|
@@ -522,7 +613,7 @@ export function lintPatchSize(filesAffected, lineCount, patchTier) {
|
|
|
522
613
|
issues.push({
|
|
523
614
|
file: AGGREGATE_PATCH_FILE,
|
|
524
615
|
check: 'large-patch-files',
|
|
525
|
-
message: `Patch affects ${filesAffected.length} files (recommended: ≤${fileThreshold}).
|
|
616
|
+
message: `Patch affects ${filesAffected.length} files (recommended: ≤${fileThreshold}). ${SPLIT_HINT}`,
|
|
526
617
|
severity: 'warning',
|
|
527
618
|
});
|
|
528
619
|
}
|
|
@@ -530,7 +621,7 @@ export function lintPatchSize(filesAffected, lineCount, patchTier) {
|
|
|
530
621
|
issues.push({
|
|
531
622
|
file: AGGREGATE_PATCH_FILE,
|
|
532
623
|
check: 'large-patch-lines',
|
|
533
|
-
message: `Patch is ${lineCount} lines (hard limit: ${lineThresholds.error}).
|
|
624
|
+
message: `Patch is ${lineCount} lines (hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
|
|
534
625
|
severity: 'error',
|
|
535
626
|
});
|
|
536
627
|
}
|
|
@@ -538,7 +629,7 @@ export function lintPatchSize(filesAffected, lineCount, patchTier) {
|
|
|
538
629
|
issues.push({
|
|
539
630
|
file: AGGREGATE_PATCH_FILE,
|
|
540
631
|
check: 'large-patch-lines',
|
|
541
|
-
message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}).
|
|
632
|
+
message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
|
|
542
633
|
severity: 'warning',
|
|
543
634
|
});
|
|
544
635
|
}
|
|
@@ -546,7 +637,7 @@ export function lintPatchSize(filesAffected, lineCount, patchTier) {
|
|
|
546
637
|
issues.push({
|
|
547
638
|
file: AGGREGATE_PATCH_FILE,
|
|
548
639
|
check: 'large-patch-lines',
|
|
549
|
-
message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}).
|
|
640
|
+
message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
|
|
550
641
|
severity: 'notice',
|
|
551
642
|
});
|
|
552
643
|
}
|
|
@@ -644,6 +735,17 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
644
735
|
}
|
|
645
736
|
else if (config.patchLint?.checkJs) {
|
|
646
737
|
issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir), options?.checkJsReportScope)));
|
|
738
|
+
// Export-time twin of per-patch lint's test-file pass (FORGE G5), so
|
|
739
|
+
// export/re-export and `lint --per-patch` agree on the checked surface.
|
|
740
|
+
if (config.patchLint.checkJsTestFiles === true) {
|
|
741
|
+
const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir));
|
|
742
|
+
issues.push(...testGrouped.global);
|
|
743
|
+
for (const [rel, list] of testGrouped.byFile) {
|
|
744
|
+
if (options?.checkJsReportScope && !options.checkJsReportScope.has(rel))
|
|
745
|
+
continue;
|
|
746
|
+
issues.push(...list);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
647
749
|
}
|
|
648
750
|
// Filter out ignored checks last so every rule still runs (keeps the
|
|
649
751
|
// implementation uniform) but suppressed rules do not surface. We do not
|
|
@@ -651,6 +753,10 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
651
753
|
// inline `fireforge-ignore: <check>` markers work in the CSS and
|
|
652
754
|
// forward-import rules.
|
|
653
755
|
if (ignoreChecks && ignoreChecks.size > 0) {
|
|
756
|
+
const suppressed = issues.filter((issue) => ignoreChecks.has(issue.check));
|
|
757
|
+
if (suppressed.length > 0) {
|
|
758
|
+
options?.onSuppressed?.(suppressed);
|
|
759
|
+
}
|
|
654
760
|
return issues.filter((issue) => !ignoreChecks.has(issue.check));
|
|
655
761
|
}
|
|
656
762
|
return issues;
|