@hominis/fireforge 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +25 -1
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/build.js +8 -3
- package/dist/src/commands/discard.js +75 -50
- package/dist/src/commands/export-flow.d.ts +1 -1
- package/dist/src/commands/export-flow.js +21 -10
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.js +31 -5
- package/dist/src/commands/import.js +5 -0
- package/dist/src/commands/lint-per-patch.js +97 -18
- package/dist/src/commands/lint-report.d.ts +21 -0
- package/dist/src/commands/lint-report.js +62 -0
- package/dist/src/commands/lint.js +23 -19
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +20 -1
- package/dist/src/commands/patch/move-files-into.d.ts +47 -0
- package/dist/src/commands/patch/move-files-into.js +245 -0
- package/dist/src/commands/patch/move-files.js +27 -13
- package/dist/src/commands/patch/rename.js +5 -2
- package/dist/src/commands/patch/staged-dependency.js +102 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +106 -0
- package/dist/src/commands/re-export-options.js +3 -0
- package/dist/src/commands/re-export.js +42 -53
- package/dist/src/commands/reset.js +5 -0
- package/dist/src/commands/run.js +18 -2
- package/dist/src/commands/status-check.d.ts +35 -0
- package/dist/src/commands/status-check.js +88 -0
- package/dist/src/commands/status-output.d.ts +9 -0
- package/dist/src/commands/status-output.js +33 -1
- package/dist/src/commands/status.js +33 -8
- package/dist/src/commands/test-diagnose.d.ts +1 -1
- package/dist/src/commands/test-diagnose.js +9 -3
- package/dist/src/commands/test-modes.d.ts +21 -0
- package/dist/src/commands/test-modes.js +42 -0
- package/dist/src/commands/test-run.d.ts +2 -0
- package/dist/src/commands/test-run.js +26 -5
- package/dist/src/commands/test.js +38 -32
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +16 -0
- package/dist/src/commands/tree.js +174 -0
- package/dist/src/core/build-baseline-types.d.ts +7 -0
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +2 -1
- package/dist/src/core/build-prepare.d.ts +9 -0
- package/dist/src/core/build-prepare.js +24 -0
- package/dist/src/core/config-validate.js +25 -0
- package/dist/src/core/discard-baseline.d.ts +60 -0
- package/dist/src/core/discard-baseline.js +232 -0
- package/dist/src/core/license-headers.d.ts +12 -0
- package/dist/src/core/license-headers.js +27 -0
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +17 -5
- package/dist/src/core/patch-export.d.ts +12 -3
- package/dist/src/core/patch-export.js +13 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -2
- package/dist/src/core/patch-lint-checkjs.js +85 -16
- package/dist/src/core/patch-lint-cross.d.ts +9 -1
- package/dist/src/core/patch-lint-cross.js +69 -9
- package/dist/src/core/patch-lint-css.js +64 -16
- package/dist/src/core/patch-lint-diff.d.ts +8 -0
- package/dist/src/core/patch-lint-diff.js +31 -0
- package/dist/src/core/patch-lint-mozbuild.d.ts +18 -0
- package/dist/src/core/patch-lint-mozbuild.js +104 -0
- package/dist/src/core/patch-lint-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint.d.ts +23 -0
- package/dist/src/core/patch-lint.js +34 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-policy.d.ts +14 -1
- package/dist/src/core/patch-policy.js +13 -2
- package/dist/src/core/smoke-patterns.d.ts +24 -8
- package/dist/src/core/smoke-patterns.js +23 -12
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +20 -6
- package/dist/src/core/test-harness-crash.d.ts +10 -0
- package/dist/src/core/test-harness-crash.js +16 -0
- package/dist/src/core/test-stale-check.d.ts +15 -0
- package/dist/src/core/test-stale-check.js +37 -1
- package/dist/src/core/token-category.d.ts +40 -0
- package/dist/src/core/token-category.js +264 -0
- package/dist/src/core/token-manager.d.ts +7 -0
- package/dist/src/core/token-manager.js +82 -193
- package/dist/src/core/token-variant.d.ts +7 -0
- package/dist/src/core/token-variant.js +9 -0
- package/dist/src/core/tree-cow.d.ts +18 -0
- package/dist/src/core/tree-cow.js +71 -0
- package/dist/src/core/tree-guard.d.ts +35 -0
- package/dist/src/core/tree-guard.js +124 -0
- package/dist/src/core/tree-store.d.ts +53 -0
- package/dist/src/core/tree-store.js +214 -0
- package/dist/src/core/typecheck-shim.d.ts +27 -0
- package/dist/src/core/typecheck-shim.js +58 -4
- package/dist/src/core/typecheck.js +14 -3
- package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
- package/dist/src/core/xpcshell-profile-dir.js +45 -0
- package/dist/src/errors/git.d.ts +8 -0
- package/dist/src/errors/git.js +17 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types/commands/options.d.ts +45 -2
- package/dist/src/types/config.d.ts +34 -0
- package/dist/src/utils/validation.d.ts +10 -0
- package/dist/src/utils/validation.js +29 -0
- package/package.json +6 -6
|
@@ -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,5 @@
|
|
|
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';
|
|
@@ -128,6 +128,21 @@ export type PatchSizeTierDecision = {
|
|
|
128
128
|
* defensive-for-tests choice.
|
|
129
129
|
*/
|
|
130
130
|
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
131
|
+
/**
|
|
132
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
133
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
134
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
135
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
136
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
137
|
+
*/
|
|
138
|
+
export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): {
|
|
139
|
+
lines: {
|
|
140
|
+
notice: number;
|
|
141
|
+
warning: number;
|
|
142
|
+
error: number;
|
|
143
|
+
};
|
|
144
|
+
maxFiles: number;
|
|
145
|
+
};
|
|
131
146
|
/**
|
|
132
147
|
* Checks patch size and emits advisory warnings.
|
|
133
148
|
*
|
|
@@ -179,6 +194,14 @@ export interface LintExportedPatchOptions {
|
|
|
179
194
|
* attributes findings per patch instead of rebuilding per patch.
|
|
180
195
|
*/
|
|
181
196
|
precomputedCheckJs?: readonly PatchLintIssue[];
|
|
197
|
+
/**
|
|
198
|
+
* Invoked with the issues DROPPED by the `ignoreChecks` waiver filter,
|
|
199
|
+
* when any were (FORGE G10). Suppression semantics are unchanged — the
|
|
200
|
+
* returned issue list still excludes them — but the caller can report
|
|
201
|
+
* the measurement a waived size finding carried (per-patch NOTICE lines
|
|
202
|
+
* and the `--report` JSON) instead of losing it entirely.
|
|
203
|
+
*/
|
|
204
|
+
onSuppressed?: (suppressed: PatchLintIssue[]) => void;
|
|
182
205
|
}
|
|
183
206
|
/**
|
|
184
207
|
* Runs all patch lint checks and returns combined issues.
|
|
@@ -3,15 +3,16 @@ 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';
|
|
10
10
|
import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
|
|
11
11
|
import { hasRelativeImport } from './patch-lint-imports.js';
|
|
12
12
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
|
+
import { lintMozBuildSortedLists } from './patch-lint-mozbuild.js';
|
|
13
14
|
import { lintObserverTopics } from './patch-lint-observer.js';
|
|
14
|
-
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
15
|
+
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Cross-patch lint re-exports
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -480,6 +481,19 @@ export function resolvePatchSizeTier(filesAffected, patchTier) {
|
|
|
480
481
|
return { tier: 'branding', source: 'auto' };
|
|
481
482
|
return { tier: 'general' };
|
|
482
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Read-only view of the size thresholds a tier enforces (FORGE G9).
|
|
486
|
+
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
487
|
+
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
488
|
+
* checks against the SAME numbers `large-patch-lines` /
|
|
489
|
+
* `large-patch-files` fire on instead of mirroring them.
|
|
490
|
+
*/
|
|
491
|
+
export function getPatchSizeThresholds(tier) {
|
|
492
|
+
return {
|
|
493
|
+
lines: { ...PATCH_LINE_THRESHOLDS[tier] },
|
|
494
|
+
maxFiles: PATCH_FILES_THRESHOLDS[tier],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
483
497
|
/**
|
|
484
498
|
* Checks patch size and emits advisory warnings.
|
|
485
499
|
*
|
|
@@ -616,11 +630,12 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
616
630
|
const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
|
|
617
631
|
const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
|
|
618
632
|
const patchOwnedChromeScripts = resolvePatchOwnedChromeScripts(newFiles, patchQueueCtx);
|
|
619
|
-
const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues] = await Promise.all([
|
|
633
|
+
const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues, mozBuildIssues] = await Promise.all([
|
|
620
634
|
lintPatchedCss(repoDir, affectedFiles, diffContent, config),
|
|
621
635
|
lintNewFileHeaders(repoDir, [...newFiles], config),
|
|
622
636
|
lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles, patchOwnedChromeScripts),
|
|
623
637
|
lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
|
|
638
|
+
lintMozBuildSortedLists(repoDir, affectedFiles),
|
|
624
639
|
]);
|
|
625
640
|
const modCommentIssues = lintModificationComments(diffContent, config);
|
|
626
641
|
const sizeIssues = options?.skipPatchSize
|
|
@@ -633,6 +648,7 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
633
648
|
...modifiedHeaderIssues,
|
|
634
649
|
...jsIssues,
|
|
635
650
|
...modCommentIssues,
|
|
651
|
+
...mozBuildIssues,
|
|
636
652
|
];
|
|
637
653
|
if (options?.precomputedCheckJs) {
|
|
638
654
|
// Per-patch lint built one queue-wide program and already attributed
|
|
@@ -641,6 +657,17 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
641
657
|
}
|
|
642
658
|
else if (config.patchLint?.checkJs) {
|
|
643
659
|
issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir), options?.checkJsReportScope)));
|
|
660
|
+
// Export-time twin of per-patch lint's test-file pass (FORGE G5), so
|
|
661
|
+
// export/re-export and `lint --per-patch` agree on the checked surface.
|
|
662
|
+
if (config.patchLint.checkJsTestFiles === true) {
|
|
663
|
+
const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir));
|
|
664
|
+
issues.push(...testGrouped.global);
|
|
665
|
+
for (const [rel, list] of testGrouped.byFile) {
|
|
666
|
+
if (options?.checkJsReportScope && !options.checkJsReportScope.has(rel))
|
|
667
|
+
continue;
|
|
668
|
+
issues.push(...list);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
644
671
|
}
|
|
645
672
|
// Filter out ignored checks last so every rule still runs (keeps the
|
|
646
673
|
// implementation uniform) but suppressed rules do not surface. We do not
|
|
@@ -648,6 +675,10 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
648
675
|
// inline `fireforge-ignore: <check>` markers work in the CSS and
|
|
649
676
|
// forward-import rules.
|
|
650
677
|
if (ignoreChecks && ignoreChecks.size > 0) {
|
|
678
|
+
const suppressed = issues.filter((issue) => ignoreChecks.has(issue.check));
|
|
679
|
+
if (suppressed.length > 0) {
|
|
680
|
+
options?.onSuppressed?.(suppressed);
|
|
681
|
+
}
|
|
651
682
|
return issues.filter((issue) => !ignoreChecks.has(issue.check));
|
|
652
683
|
}
|
|
653
684
|
return issues;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is an implementation detail.
|
|
6
6
|
*/
|
|
7
7
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
8
|
-
export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type
|
|
8
|
+
export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
9
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
10
10
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `patch reorder`, etc.).
|
|
8
8
|
*/
|
|
9
9
|
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
10
|
-
import type { FireForgeConfig } from '../types/config.js';
|
|
10
|
+
import type { FireForgeConfig, PatchPolicyConfig, PatchPolicyRange } from '../types/config.js';
|
|
11
11
|
/** Stable issue codes returned by patch policy evaluation. */
|
|
12
12
|
export type PatchPolicyIssueCode = 'filename-pattern' | 'filename-captures' | 'filename-metadata-mismatch' | 'order-collision' | 'category-range' | 'reserved-range' | 'reserved-documentation' | 'reserved-files' | 'description-required' | 'numeric-gap';
|
|
13
13
|
/** A single patch policy validation finding. */
|
|
@@ -28,6 +28,19 @@ export interface PatchPolicyEnforcementInput {
|
|
|
28
28
|
export declare function getPatchPolicyCategories(config: FireForgeConfig): string[];
|
|
29
29
|
/** Checks whether a category is accepted by legacy defaults or the policy ranges. */
|
|
30
30
|
export declare function isCategoryAllowedByConfig(config: FireForgeConfig, category: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
33
|
+
* Exported for the forward-import hint (FORGE F14), which must render the
|
|
34
|
+
* range it found no legal ordinal in.
|
|
35
|
+
*/
|
|
36
|
+
export declare function categoryRangeLabel(ranges: readonly PatchPolicyRange[], category: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Returns the configured range that contains `order` for `category`, or
|
|
39
|
+
* null when no such range exists. Exported for the forward-import hint
|
|
40
|
+
* (FORGE F14), which suppresses ordinal suggestions the reorder policy
|
|
41
|
+
* would refuse.
|
|
42
|
+
*/
|
|
43
|
+
export declare function categoryRangeForOrder(cfg: PatchPolicyConfig, category: string, order: number): PatchPolicyRange | null;
|
|
31
44
|
/** Evaluates an entire patch manifest against the configured policy. */
|
|
32
45
|
export declare function evaluatePatchPolicy(config: FireForgeConfig, manifest: PatchesManifest): PatchPolicyIssue[];
|
|
33
46
|
/** Builds a sorted manifest snapshot from projected patch metadata. */
|
|
@@ -45,7 +45,12 @@ export function isCategoryAllowedByConfig(config, category) {
|
|
|
45
45
|
function rangeLabel(range) {
|
|
46
46
|
return `${String(range.from).padStart(3, '0')}-${String(range.to).padStart(3, '0')}`;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
50
|
+
* Exported for the forward-import hint (FORGE F14), which must render the
|
|
51
|
+
* range it found no legal ordinal in.
|
|
52
|
+
*/
|
|
53
|
+
export function categoryRangeLabel(ranges, category) {
|
|
49
54
|
const matches = ranges.filter((range) => range.category === category);
|
|
50
55
|
if (matches.length === 0)
|
|
51
56
|
return '(no configured range)';
|
|
@@ -54,7 +59,13 @@ function categoryRangeLabel(ranges, category) {
|
|
|
54
59
|
function reservedRangeForOrder(cfg, order) {
|
|
55
60
|
return cfg.reservedRanges?.find((range) => order >= range.from && order <= range.to) ?? null;
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Returns the configured range that contains `order` for `category`, or
|
|
64
|
+
* null when no such range exists. Exported for the forward-import hint
|
|
65
|
+
* (FORGE F14), which suppresses ordinal suggestions the reorder policy
|
|
66
|
+
* would refuse.
|
|
67
|
+
*/
|
|
68
|
+
export function categoryRangeForOrder(cfg, category, order) {
|
|
58
69
|
return (cfg.ranges.find((range) => {
|
|
59
70
|
return range.category === category && order >= range.from && order <= range.to;
|
|
60
71
|
}) ?? null);
|
|
@@ -22,24 +22,40 @@ export declare const SMOKE_ERROR_PATTERNS: readonly RegExp[];
|
|
|
22
22
|
/**
|
|
23
23
|
* Returns `true` when `line` matches any pattern in
|
|
24
24
|
* {@link SMOKE_ERROR_PATTERNS}. Does not consult the allowlist — that step
|
|
25
|
-
* lives in {@link
|
|
25
|
+
* lives in {@link matchAllowlist}, so the smoke runner can count
|
|
26
26
|
* allowlisted hits separately from raw error matches for its summary.
|
|
27
27
|
*/
|
|
28
28
|
export declare function matchesSmokeError(line: string): boolean;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* One compiled allowlist entry with its provenance retained so the smoke
|
|
31
|
+
* summary can attribute hits per entry (FORGE G8) — an entry that silently
|
|
32
|
+
* stops matching (its suppressed shape changed upstream) is only
|
|
33
|
+
* detectable when zero-hit entries are visible.
|
|
32
34
|
*/
|
|
33
|
-
export
|
|
35
|
+
export interface CompiledAllowlistEntry {
|
|
36
|
+
pattern: RegExp;
|
|
37
|
+
/** Verbatim source text of the entry. */
|
|
38
|
+
source: string;
|
|
39
|
+
/** Human-readable origin: `allow.txt:12` or `--console-allow #2`. */
|
|
40
|
+
origin: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the index of the FIRST entry in `allow` matching `line`, or -1.
|
|
44
|
+
* First-match attribution is deterministic and cheap; a line matching
|
|
45
|
+
* several entries credits the earliest one. Safe to call with an empty
|
|
46
|
+
* allowlist (always returns -1).
|
|
47
|
+
*/
|
|
48
|
+
export declare function matchAllowlist(line: string, allow: readonly CompiledAllowlistEntry[]): number;
|
|
34
49
|
/**
|
|
35
50
|
* Parses a newline-delimited allowlist file body. Lines are trimmed; blank
|
|
36
51
|
* lines and `#`-prefixed comments are skipped. Each remaining line is
|
|
37
|
-
* compiled as a RegExp
|
|
38
|
-
*
|
|
52
|
+
* compiled as a RegExp with its `<file>:<line>` origin retained. A bad
|
|
53
|
+
* pattern throws immediately — better to fail fast at CLI parse time than
|
|
54
|
+
* to silently let a typo match nothing.
|
|
39
55
|
*/
|
|
40
|
-
export declare function compileAllowlistFromFile(body: string, sourcePath: string):
|
|
56
|
+
export declare function compileAllowlistFromFile(body: string, sourcePath: string): CompiledAllowlistEntry[];
|
|
41
57
|
/**
|
|
42
58
|
* Compiles an array of regex-string inputs (e.g. repeated `--console-allow`
|
|
43
59
|
* flag values). Same fail-fast semantics as {@link compileAllowlistFromFile}.
|
|
44
60
|
*/
|
|
45
|
-
export declare function compileAllowlistFromStrings(sources: readonly string[]):
|
|
61
|
+
export declare function compileAllowlistFromStrings(sources: readonly string[]): CompiledAllowlistEntry[];
|
|
@@ -32,7 +32,7 @@ export const SMOKE_ERROR_PATTERNS = [
|
|
|
32
32
|
/**
|
|
33
33
|
* Returns `true` when `line` matches any pattern in
|
|
34
34
|
* {@link SMOKE_ERROR_PATTERNS}. Does not consult the allowlist — that step
|
|
35
|
-
* lives in {@link
|
|
35
|
+
* lives in {@link matchAllowlist}, so the smoke runner can count
|
|
36
36
|
* allowlisted hits separately from raw error matches for its summary.
|
|
37
37
|
*/
|
|
38
38
|
export function matchesSmokeError(line) {
|
|
@@ -44,22 +44,25 @@ export function matchesSmokeError(line) {
|
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Returns
|
|
48
|
-
*
|
|
47
|
+
* Returns the index of the FIRST entry in `allow` matching `line`, or -1.
|
|
48
|
+
* First-match attribution is deterministic and cheap; a line matching
|
|
49
|
+
* several entries credits the earliest one. Safe to call with an empty
|
|
50
|
+
* allowlist (always returns -1).
|
|
49
51
|
*/
|
|
50
|
-
export function
|
|
51
|
-
for (
|
|
52
|
-
if (pattern.test(line)) {
|
|
53
|
-
return
|
|
52
|
+
export function matchAllowlist(line, allow) {
|
|
53
|
+
for (let i = 0; i < allow.length; i++) {
|
|
54
|
+
if (allow[i]?.pattern.test(line)) {
|
|
55
|
+
return i;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
return
|
|
58
|
+
return -1;
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* Parses a newline-delimited allowlist file body. Lines are trimmed; blank
|
|
60
62
|
* lines and `#`-prefixed comments are skipped. Each remaining line is
|
|
61
|
-
* compiled as a RegExp
|
|
62
|
-
*
|
|
63
|
+
* compiled as a RegExp with its `<file>:<line>` origin retained. A bad
|
|
64
|
+
* pattern throws immediately — better to fail fast at CLI parse time than
|
|
65
|
+
* to silently let a typo match nothing.
|
|
63
66
|
*/
|
|
64
67
|
export function compileAllowlistFromFile(body, sourcePath) {
|
|
65
68
|
const lines = body.split(/\r?\n/);
|
|
@@ -69,7 +72,11 @@ export function compileAllowlistFromFile(body, sourcePath) {
|
|
|
69
72
|
if (!line || line.startsWith('#'))
|
|
70
73
|
return;
|
|
71
74
|
try {
|
|
72
|
-
compiled.push(
|
|
75
|
+
compiled.push({
|
|
76
|
+
pattern: new RegExp(line),
|
|
77
|
+
source: line,
|
|
78
|
+
origin: `${sourcePath}:${String(index + 1)}`,
|
|
79
|
+
});
|
|
73
80
|
}
|
|
74
81
|
catch (error) {
|
|
75
82
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -88,7 +95,11 @@ export function compileAllowlistFromStrings(sources) {
|
|
|
88
95
|
const compiled = [];
|
|
89
96
|
sources.forEach((source, index) => {
|
|
90
97
|
try {
|
|
91
|
-
compiled.push(
|
|
98
|
+
compiled.push({
|
|
99
|
+
pattern: new RegExp(source),
|
|
100
|
+
source,
|
|
101
|
+
origin: `--console-allow #${String(index + 1)}`,
|
|
102
|
+
});
|
|
92
103
|
}
|
|
93
104
|
catch (error) {
|
|
94
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -49,6 +49,14 @@ export interface ClassifiedFile extends StatusFile {
|
|
|
49
49
|
* single claim is fully captured by the classification itself.
|
|
50
50
|
*/
|
|
51
51
|
claimedBy?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Owning patch filename when exactly one patch claims this path
|
|
54
|
+
* (patch-backed, patch-owned-drift, and single-owner furnace entries).
|
|
55
|
+
* Unset for unowned, branding-generated, unowned-furnace, and conflict
|
|
56
|
+
* entries — conflicts carry `claimedBy` instead. Exposed through
|
|
57
|
+
* `status --json` as the `patch` field (FORGE G11).
|
|
58
|
+
*/
|
|
59
|
+
owner?: string;
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
54
62
|
* Builds the file → owning-patch-filenames multimap from manifest rows.
|
|
@@ -66,6 +74,14 @@ export declare function buildPatchClaims(manifestPatches: ReadonlyArray<{
|
|
|
66
74
|
* Single source of truth — status-output.ts renders from the same logic.
|
|
67
75
|
*/
|
|
68
76
|
export declare function getPrimaryStatusCode(status: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* True for the branding paths whose content FireForge generates itself
|
|
79
|
+
* (configure.sh, brand.properties/ftl, browser/moz.configure). Unlike the
|
|
80
|
+
* broader branding-root test, a brand-new unowned file under the branding
|
|
81
|
+
* directory is NOT generated — it is a patch candidate and must stay
|
|
82
|
+
* visible as unmanaged (the Assets.car precedent).
|
|
83
|
+
*/
|
|
84
|
+
export declare function isGeneratedBrandingPath(file: string, binaryName: string): boolean;
|
|
69
85
|
/**
|
|
70
86
|
* Classifies files into patch-backed, unmanaged, branding, furnace, or
|
|
71
87
|
* conflict buckets.
|
|
@@ -47,7 +47,14 @@ export function getPrimaryStatusCode(status) {
|
|
|
47
47
|
}
|
|
48
48
|
return status;
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
/**
|
|
51
|
+
* True for the branding paths whose content FireForge generates itself
|
|
52
|
+
* (configure.sh, brand.properties/ftl, browser/moz.configure). Unlike the
|
|
53
|
+
* broader branding-root test, a brand-new unowned file under the branding
|
|
54
|
+
* directory is NOT generated — it is a patch candidate and must stay
|
|
55
|
+
* visible as unmanaged (the Assets.car precedent).
|
|
56
|
+
*/
|
|
57
|
+
export function isGeneratedBrandingPath(file, binaryName) {
|
|
51
58
|
const normalized = file.replace(/\\/g, '/');
|
|
52
59
|
const brandingRoot = `browser/branding/${binaryName}`;
|
|
53
60
|
return (normalized === 'browser/moz.configure' ||
|
|
@@ -64,13 +71,14 @@ function isGeneratedBrandingPath(file, binaryName) {
|
|
|
64
71
|
* deletion the patch does not expect, or a failed comparison) reports
|
|
65
72
|
* `patch-owned-drift`.
|
|
66
73
|
*/
|
|
67
|
-
async function classifySingleOwnerFile(entry, engineDir, patchesDir, matchClassification) {
|
|
74
|
+
async function classifySingleOwnerFile(entry, engineDir, patchesDir, matchClassification, owner) {
|
|
68
75
|
if (getPrimaryStatusCode(entry.status) === 'D') {
|
|
69
76
|
// Deleted file: content matches only if the patch expects deletion
|
|
70
77
|
const expected = await computePatchedContent(patchesDir, engineDir, entry.file);
|
|
71
78
|
return {
|
|
72
79
|
...entry,
|
|
73
80
|
classification: expected === null ? matchClassification : 'patch-owned-drift',
|
|
81
|
+
owner,
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
// File exists on disk — compare actual vs expected
|
|
@@ -82,11 +90,12 @@ async function classifySingleOwnerFile(entry, engineDir, patchesDir, matchClassi
|
|
|
82
90
|
return {
|
|
83
91
|
...entry,
|
|
84
92
|
classification: actual === expected ? matchClassification : 'patch-owned-drift',
|
|
93
|
+
owner,
|
|
85
94
|
};
|
|
86
95
|
}
|
|
87
96
|
catch (error) {
|
|
88
97
|
verbose(`Treating ${entry.file} as patch-owned drift because patch-backed classification failed: ${toError(error).message}`);
|
|
89
|
-
return { ...entry, classification: 'patch-owned-drift' };
|
|
98
|
+
return { ...entry, classification: 'patch-owned-drift', owner };
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
/**
|
|
@@ -146,8 +155,8 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
|
|
|
146
155
|
// `furnace`. Multi-owner and unowned furnace paths keep the
|
|
147
156
|
// short-circuit — the ownership table independently flags
|
|
148
157
|
// filesAffected conflicts.
|
|
149
|
-
if (owners && owners.length === 1) {
|
|
150
|
-
results.push(await classifySingleOwnerFile(entry, engineDir, patchesDir, 'furnace'));
|
|
158
|
+
if (owners && owners.length === 1 && owners[0] !== undefined) {
|
|
159
|
+
results.push(await classifySingleOwnerFile(entry, engineDir, patchesDir, 'furnace', owners[0]));
|
|
151
160
|
}
|
|
152
161
|
else {
|
|
153
162
|
results.push({ ...entry, classification: 'furnace' });
|
|
@@ -174,7 +183,12 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
|
|
|
174
183
|
continue;
|
|
175
184
|
}
|
|
176
185
|
// File is claimed by exactly one patch — compare content.
|
|
177
|
-
|
|
186
|
+
const owner = owners[0];
|
|
187
|
+
if (owner === undefined) {
|
|
188
|
+
results.push({ ...entry, classification: 'unmanaged' });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
results.push(await classifySingleOwnerFile(entry, engineDir, patchesDir, 'patch-backed', owner));
|
|
178
192
|
}
|
|
179
193
|
return results;
|
|
180
194
|
}
|
|
@@ -154,6 +154,16 @@ export declare function classifyHarnessRun(exitCode: number, output: string, req
|
|
|
154
154
|
export declare function buildGreenSummaryRejectedMessage(rejection: GreenSummaryRejection, exitCode: number): string;
|
|
155
155
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
156
156
|
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
157
|
+
/**
|
|
158
|
+
* Optional hint appended to the harness-crash message when a HEADED run on
|
|
159
|
+
* macOS died at the no-output timeout: the display may have slept and frozen
|
|
160
|
+
* the headed browser before any test started (FORGE F17). Pure — the
|
|
161
|
+
* platform is injected so unit tests need no mocking.
|
|
162
|
+
*/
|
|
163
|
+
export declare function headedNoOutputTimeoutHint(signature: HarnessCrashSignature, context: {
|
|
164
|
+
headless: boolean;
|
|
165
|
+
platform: string;
|
|
166
|
+
}): string | undefined;
|
|
157
167
|
/**
|
|
158
168
|
* Builds the message for a run that produced no `TEST-START` despite
|
|
159
169
|
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
@@ -537,6 +537,22 @@ export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'ma
|
|
|
537
537
|
'If it persists across many runs, inspect the mach virtualenv (mach resyncs psutil on its own; ' +
|
|
538
538
|
'patching it manually does not stick).');
|
|
539
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Optional hint appended to the harness-crash message when a HEADED run on
|
|
542
|
+
* macOS died at the no-output timeout: the display may have slept and frozen
|
|
543
|
+
* the headed browser before any test started (FORGE F17). Pure — the
|
|
544
|
+
* platform is injected so unit tests need no mocking.
|
|
545
|
+
*/
|
|
546
|
+
export function headedNoOutputTimeoutHint(signature, context) {
|
|
547
|
+
if (context.platform !== 'darwin' || context.headless)
|
|
548
|
+
return undefined;
|
|
549
|
+
if (!signature.reason.includes('no-output timeout'))
|
|
550
|
+
return undefined;
|
|
551
|
+
return ('Hint: this was a HEADED run on macOS that died at the no-output timeout. ' +
|
|
552
|
+
'A common cause is the display going to sleep mid-run, which freezes the headed browser ' +
|
|
553
|
+
'before any test starts. Wrap headed runs in `caffeinate -dimsu <command>` to keep the ' +
|
|
554
|
+
'display awake, or pass --headless.');
|
|
555
|
+
}
|
|
540
556
|
/**
|
|
541
557
|
* Builds the message for a run that produced no `TEST-START` despite
|
|
542
558
|
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
@@ -108,3 +108,18 @@ export declare function checkStaticComponentsStale(engineDir: string, baseline:
|
|
|
108
108
|
* independently.
|
|
109
109
|
*/
|
|
110
110
|
export declare function formatStaticComponentsRefusal(changedManifests: string[]): string;
|
|
111
|
+
/**
|
|
112
|
+
* Formats the post-mutation advisory printed by `fireforge reset` /
|
|
113
|
+
* `fireforge import` when `components.conf` diverged from the last full
|
|
114
|
+
* build (FORGE F13). Same probe as {@link formatStaticComponentsRefusal},
|
|
115
|
+
* different moment: this fires at mutation time so the operator learns a
|
|
116
|
+
* full build is needed BEFORE the next gate run refuses.
|
|
117
|
+
*/
|
|
118
|
+
export declare function formatPostMutationStaticComponentsWarning(changedManifests: string[]): string;
|
|
119
|
+
/**
|
|
120
|
+
* Post-mutation advisory used by `fireforge reset` / `fireforge import`:
|
|
121
|
+
* warns (never throws, never blocks) when `components.conf` now differs
|
|
122
|
+
* from the last full-build anchor. Silently skipped when no baseline or
|
|
123
|
+
* anchor exists, or when any probe fails.
|
|
124
|
+
*/
|
|
125
|
+
export declare function warnIfStaticComponentsStale(projectRoot: string, engineDir: string): Promise<void>;
|
|
@@ -27,7 +27,7 @@ import { createHash } from 'node:crypto';
|
|
|
27
27
|
import { readFile } from 'node:fs/promises';
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { toError } from '../utils/errors.js';
|
|
30
|
-
import { verbose } from '../utils/logger.js';
|
|
30
|
+
import { verbose, warn } from '../utils/logger.js';
|
|
31
31
|
import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
|
|
32
32
|
import { readBuildBaseline } from './build-baseline.js';
|
|
33
33
|
import { collectChangedEnginePaths } from './engine-changes.js';
|
|
@@ -245,3 +245,39 @@ export function formatStaticComponentsRefusal(changedManifests) {
|
|
|
245
245
|
'it accepts stale packaged content, not a stale compiled registration. Pass ' +
|
|
246
246
|
'--allow-stale-components only if you rebuilt out-of-band and accept the risk.)');
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Formats the post-mutation advisory printed by `fireforge reset` /
|
|
250
|
+
* `fireforge import` when `components.conf` diverged from the last full
|
|
251
|
+
* build (FORGE F13). Same probe as {@link formatStaticComponentsRefusal},
|
|
252
|
+
* different moment: this fires at mutation time so the operator learns a
|
|
253
|
+
* full build is needed BEFORE the next gate run refuses.
|
|
254
|
+
*/
|
|
255
|
+
export function formatPostMutationStaticComponentsWarning(changedManifests) {
|
|
256
|
+
const head = changedManifests.slice(0, STALE_PATHS_LIMIT);
|
|
257
|
+
const truncated = changedManifests.length - head.length;
|
|
258
|
+
const list = head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
|
|
259
|
+
return (`components.conf changed relative to the last full "fireforge build": ${list}.\n` +
|
|
260
|
+
'The compiled StaticComponents table in obj-* no longer matches the tree, so the next ' +
|
|
261
|
+
'"fireforge test" will require a full "fireforge build" first — a scoped ' +
|
|
262
|
+
'"fireforge test --build" cannot regenerate the compiled table.');
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Post-mutation advisory used by `fireforge reset` / `fireforge import`:
|
|
266
|
+
* warns (never throws, never blocks) when `components.conf` now differs
|
|
267
|
+
* from the last full-build anchor. Silently skipped when no baseline or
|
|
268
|
+
* anchor exists, or when any probe fails.
|
|
269
|
+
*/
|
|
270
|
+
export async function warnIfStaticComponentsStale(projectRoot, engineDir) {
|
|
271
|
+
try {
|
|
272
|
+
const baseline = await readBuildBaseline(projectRoot);
|
|
273
|
+
if (baseline === undefined)
|
|
274
|
+
return;
|
|
275
|
+
const result = await checkStaticComponentsStale(engineDir, baseline);
|
|
276
|
+
if (!result.stale)
|
|
277
|
+
return;
|
|
278
|
+
warn(formatPostMutationStaticComponentsWarning(result.changedManifests));
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
verbose(`Static-components post-mutation check skipped: ${toError(error).message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `lines` contain a category header (single-line or multi-line
|
|
3
|
+
* banner shape) whose name EQUALS `category`. Shared by the pre-add
|
|
4
|
+
* assertion, the banner creation path, and the section finder so all
|
|
5
|
+
* agree on what "exists" means.
|
|
6
|
+
*/
|
|
7
|
+
export declare function categoryHeaderExists(lines: string[], category: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Asserts the category banner exists (or that creation was requested).
|
|
10
|
+
* Throws a GeneralError naming the available categories and the
|
|
11
|
+
* `--create-category` remedy otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertTokenCategoryExists(engineDir: string, tokensCssPath: string, category: string, createCategory?: boolean): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Splices a new single-line category banner ("= Name =" comment shape, the
|
|
16
|
+
* same format `discoverCategoryHeaders` recognises) just before the closing
|
|
17
|
+
* brace of the `:root` block, making the new category the last section.
|
|
18
|
+
* Mutates `lines` in place.
|
|
19
|
+
*/
|
|
20
|
+
export declare function declareCategoryBanner(lines: string[], category: string, tokensCssPath: string): void;
|
|
21
|
+
/** Locates and bounds the named category section. Throws when absent. */
|
|
22
|
+
export declare function findCategorySection(lines: string[], category: string, tokensCssPath: string): {
|
|
23
|
+
categoryLine: number;
|
|
24
|
+
sectionEnd: number;
|
|
25
|
+
};
|
|
26
|
+
/** Location of an existing base-`:root` token declaration. */
|
|
27
|
+
export interface TokenDeclarationLocation {
|
|
28
|
+
/** 1-based line number in the tokens CSS file. */
|
|
29
|
+
line: number;
|
|
30
|
+
/** Nearest enclosing category banner name, when one precedes the declaration. */
|
|
31
|
+
category?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Finds an existing declaration of `tokenName` inside the base `:root`
|
|
35
|
+
* block. Dark `@media` and `:root[variant]` companion blocks are
|
|
36
|
+
* deliberately excluded — they mirror the base declaration, they do not
|
|
37
|
+
* own the token. Comment content never matches, and the name match is
|
|
38
|
+
* exact (`--foo-bar` does not match `--x-foo-bar`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function findTokenDeclarationInRoot(lines: string[], tokenName: string): TokenDeclarationLocation | undefined;
|