@hominis/fireforge 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/src/commands/export-all.js +4 -1
- package/dist/src/commands/export-shared.js +10 -1
- package/dist/src/commands/export.js +5 -1
- package/dist/src/commands/lint-per-patch.d.ts +2 -0
- package/dist/src/commands/lint-per-patch.js +206 -44
- package/dist/src/commands/lint.js +100 -7
- package/dist/src/commands/patch/split-plan.d.ts +18 -2
- package/dist/src/commands/patch/split-plan.js +90 -16
- package/dist/src/commands/patch/split.js +12 -3
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/test-run.d.ts +10 -0
- package/dist/src/commands/test-run.js +13 -4
- package/dist/src/commands/test.js +46 -7
- package/dist/src/commands/token.js +12 -1
- package/dist/src/commands/typecheck.js +35 -0
- package/dist/src/core/build-prepare.js +23 -3
- package/dist/src/core/config-validate.js +52 -0
- package/dist/src/core/furnace-apply-dry-run.d.ts +17 -0
- package/dist/src/core/furnace-apply-dry-run.js +105 -0
- package/dist/src/core/furnace-apply-ftl.d.ts +12 -0
- package/dist/src/core/furnace-apply-ftl.js +97 -1
- package/dist/src/core/furnace-apply-helpers.js +10 -80
- package/dist/src/core/furnace-jsconfig.js +22 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -0
- package/dist/src/core/git-file-ops.js +82 -1
- package/dist/src/core/mach-resource-shim.d.ts +21 -0
- package/dist/src/core/mach-resource-shim.js +92 -0
- package/dist/src/core/mach.d.ts +17 -0
- package/dist/src/core/mach.js +30 -2
- package/dist/src/core/manifest-helpers.js +29 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +213 -67
- package/dist/src/core/patch-lint-cross.d.ts +31 -0
- package/dist/src/core/patch-lint-cross.js +83 -63
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint-reexports.js +1 -1
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +19 -163
- package/dist/src/core/test-harness-crash.d.ts +6 -3
- package/dist/src/core/test-harness-crash.js +32 -4
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +9 -4
- package/dist/src/core/token-dark-mode.d.ts +9 -0
- package/dist/src/core/token-dark-mode.js +1 -1
- package/dist/src/core/token-docs.d.ts +32 -0
- package/dist/src/core/token-docs.js +101 -0
- package/dist/src/core/token-manager.d.ts +8 -0
- package/dist/src/core/token-manager.js +77 -95
- package/dist/src/core/token-variant.d.ts +39 -0
- package/dist/src/core/token-variant.js +141 -0
- package/dist/src/core/typecheck-shim.d.ts +3 -1
- package/dist/src/core/typecheck-shim.js +43 -3
- package/dist/src/core/typecheck.js +56 -28
- package/dist/src/types/commands/options.d.ts +22 -0
- package/dist/src/types/config.d.ts +24 -2
- package/package.json +3 -3
|
@@ -343,27 +343,8 @@ function findMatchingStagedDependency(entry, sitePath, specifier, laterOwners) {
|
|
|
343
343
|
laterOwners.some((owner) => owner.fullPath === dependency.creates &&
|
|
344
344
|
(dependency.owner === undefined || dependency.owner === owner.filename)));
|
|
345
345
|
}
|
|
346
|
-
/**
|
|
347
|
-
|
|
348
|
-
* responsible for creating.
|
|
349
|
-
*
|
|
350
|
-
* Approach is deliberately conservative — we do not resolve `resource://`
|
|
351
|
-
* URLs to engine file paths. Instead we build a cross-queue index of
|
|
352
|
-
* newly-created files keyed by their basename, and flag imports whose leaf
|
|
353
|
-
* matches an entry owned by a later-ordered patch. False positives from
|
|
354
|
-
* unrelated basename collisions (two different directories happening to
|
|
355
|
-
* create files named `Helper.sys.mjs`) are possible; the README documents
|
|
356
|
-
* the limitation and the inline ignore marker above provides an escape
|
|
357
|
-
* hatch.
|
|
358
|
-
*
|
|
359
|
-
* Rules out:
|
|
360
|
-
* - Imports whose leaf matches a newly-created file in the *same* or an
|
|
361
|
-
* *earlier* patch (legitimate use).
|
|
362
|
-
* - Imports whose leaf is not in the new-file index at all (pre-existing
|
|
363
|
-
* engine file — not our concern).
|
|
364
|
-
* - Imports on a line suppressed by the ignore marker.
|
|
365
|
-
*/
|
|
366
|
-
export function lintPatchQueueForwardImports(ctx) {
|
|
346
|
+
/** Builds the basename → later-creators index used by the forward-import scan. */
|
|
347
|
+
function buildNewFileIndex(ctx) {
|
|
367
348
|
const newFileIndex = new Map();
|
|
368
349
|
for (const entry of ctx.entries) {
|
|
369
350
|
for (const fullPath of entry.newFiles.keys()) {
|
|
@@ -376,23 +357,24 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
376
357
|
owners.push({ filename: entry.filename, order: entry.order, fullPath });
|
|
377
358
|
}
|
|
378
359
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
360
|
+
return newFileIndex;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Visits every forward-import site (an import whose leaf is created by a
|
|
364
|
+
* later-ordered patch). Shared by {@link lintPatchQueueForwardImports} (which
|
|
365
|
+
* resolves/reports each) and {@link collectForwardImportEdges} (which returns
|
|
366
|
+
* them structurally). We scan a created file's full body and a modified
|
|
367
|
+
* file's added lines only — flagging imports *this patch introduces*, not
|
|
368
|
+
* pre-existing HEAD imports that happen to collide with a later-created file.
|
|
369
|
+
*/
|
|
370
|
+
function eachForwardImportSite(ctx, newFileIndex, visit) {
|
|
387
371
|
const checkSite = (entry, sitePath, content) => {
|
|
388
372
|
if (!isForwardImportableFile(sitePath))
|
|
389
373
|
return;
|
|
390
374
|
const ignoreLines = findForwardImportIgnoreLines(content);
|
|
391
|
-
const
|
|
392
|
-
for (const { specifier, line } of extracted) {
|
|
375
|
+
for (const { specifier, line } of extractImportSpecifiersWithLines(content)) {
|
|
393
376
|
if (ignoreLines.has(line))
|
|
394
377
|
continue;
|
|
395
|
-
// Take the leaf and strip query/hash if any.
|
|
396
378
|
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
397
379
|
const leaf = basename(cleaned);
|
|
398
380
|
if (!leaf || !isForwardImportableFile(leaf))
|
|
@@ -400,41 +382,11 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
400
382
|
const owners = newFileIndex.get(leaf);
|
|
401
383
|
if (!owners)
|
|
402
384
|
continue;
|
|
403
|
-
// Is the owner a later-ordered patch (or one ordered equal but
|
|
404
|
-
// lexicographically later as a tiebreaker)?
|
|
405
385
|
const laterOwners = owners.filter((owner) => owner.order > entry.order ||
|
|
406
386
|
(owner.order === entry.order && owner.filename > entry.filename));
|
|
407
387
|
if (laterOwners.length === 0)
|
|
408
388
|
continue;
|
|
409
|
-
|
|
410
|
-
if (stagedDependency) {
|
|
411
|
-
usedStagedDeclarations.add(stagedDependencyKey(entry, stagedDependency));
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
const ownersSummary = laterOwners
|
|
415
|
-
.map((o) => `${o.filename} (creates ${o.fullPath})`)
|
|
416
|
-
.join(', ');
|
|
417
|
-
const fingerprintOwners = [...laterOwners]
|
|
418
|
-
.map((o) => `${o.filename}:${o.fullPath}`)
|
|
419
|
-
.sort((a, b) => a.localeCompare(b))
|
|
420
|
-
.join(',');
|
|
421
|
-
// Lowest ordinal that lands AFTER every later-ordered creator —
|
|
422
|
-
// turns the operator's "guess and re-run" loop into a single shot
|
|
423
|
-
// when the only fix is reordering.
|
|
424
|
-
const suggestedOrder = Math.max(...laterOwners.map((o) => o.order)) + 1;
|
|
425
|
-
issues.push({
|
|
426
|
-
file: sitePath,
|
|
427
|
-
check: 'forward-import',
|
|
428
|
-
fingerprint: `forward-import|${sitePath}|${cleaned}|${fingerprintOwners}`,
|
|
429
|
-
message: `${sitePath} in ${entry.filename} imports "${specifier}", ` +
|
|
430
|
-
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
431
|
-
'Reorder the patches so the dependency is created first, move the import ' +
|
|
432
|
-
'into the later patch, declare the intentional staged dependency with ' +
|
|
433
|
-
'"fireforge patch staged-dependency --add", or mark the import with ' +
|
|
434
|
-
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive. ` +
|
|
435
|
-
`Closest legal ordinal that satisfies this dependency: ${suggestedOrder}.`,
|
|
436
|
-
severity: 'error',
|
|
437
|
-
});
|
|
389
|
+
visit(entry, sitePath, specifier, cleaned, laterOwners);
|
|
438
390
|
}
|
|
439
391
|
};
|
|
440
392
|
for (const entry of ctx.entries) {
|
|
@@ -443,6 +395,74 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
443
395
|
for (const [path, added] of entry.modifiedFileAdditions)
|
|
444
396
|
checkSite(entry, path, added);
|
|
445
397
|
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Returns every forward-import edge in the queue, one per (site, later
|
|
401
|
+
* creator) pair, regardless of any staged-dependency declarations. Used by
|
|
402
|
+
* `patch split` to discover the forward edges it introduces into the new
|
|
403
|
+
* patch so it can auto-declare them (and so its projected lint matches the
|
|
404
|
+
* real per-patch gate).
|
|
405
|
+
*/
|
|
406
|
+
export function collectForwardImportEdges(ctx) {
|
|
407
|
+
const newFileIndex = buildNewFileIndex(ctx);
|
|
408
|
+
const edges = [];
|
|
409
|
+
eachForwardImportSite(ctx, newFileIndex, (entry, sitePath, specifier, cleaned, laterOwners) => {
|
|
410
|
+
for (const owner of laterOwners) {
|
|
411
|
+
edges.push({
|
|
412
|
+
entry: entry.filename,
|
|
413
|
+
sitePath,
|
|
414
|
+
specifier,
|
|
415
|
+
cleaned,
|
|
416
|
+
owner: owner.filename,
|
|
417
|
+
creates: owner.fullPath,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return edges;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Cross-patch lint rule: flag imports of a module a later-ordered patch is
|
|
425
|
+
* responsible for creating, unless the patch declares an exact staged
|
|
426
|
+
* forward-import for it. Also warns on staged-dependency declarations that
|
|
427
|
+
* never matched (stale). Matching is conservative — by basename, not by
|
|
428
|
+
* resolving `resource://`/`chrome://` URLs — with an inline ignore marker as
|
|
429
|
+
* an escape hatch for unrelated basename collisions.
|
|
430
|
+
*/
|
|
431
|
+
export function lintPatchQueueForwardImports(ctx) {
|
|
432
|
+
const newFileIndex = buildNewFileIndex(ctx);
|
|
433
|
+
const issues = [];
|
|
434
|
+
const usedStagedDeclarations = new Set();
|
|
435
|
+
eachForwardImportSite(ctx, newFileIndex, (entry, sitePath, specifier, cleaned, laterOwners) => {
|
|
436
|
+
const stagedDependency = findMatchingStagedDependency(entry, sitePath, specifier, laterOwners);
|
|
437
|
+
if (stagedDependency) {
|
|
438
|
+
usedStagedDeclarations.add(stagedDependencyKey(entry, stagedDependency));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const ownersSummary = laterOwners
|
|
442
|
+
.map((o) => `${o.filename} (creates ${o.fullPath})`)
|
|
443
|
+
.join(', ');
|
|
444
|
+
const fingerprintOwners = [...laterOwners]
|
|
445
|
+
.map((o) => `${o.filename}:${o.fullPath}`)
|
|
446
|
+
.sort((a, b) => a.localeCompare(b))
|
|
447
|
+
.join(',');
|
|
448
|
+
// Lowest ordinal that lands AFTER every later-ordered creator —
|
|
449
|
+
// turns the operator's "guess and re-run" loop into a single shot
|
|
450
|
+
// when the only fix is reordering.
|
|
451
|
+
const suggestedOrder = Math.max(...laterOwners.map((o) => o.order)) + 1;
|
|
452
|
+
issues.push({
|
|
453
|
+
file: sitePath,
|
|
454
|
+
check: 'forward-import',
|
|
455
|
+
fingerprint: `forward-import|${sitePath}|${cleaned}|${fingerprintOwners}`,
|
|
456
|
+
message: `${sitePath} in ${entry.filename} imports "${specifier}", ` +
|
|
457
|
+
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
458
|
+
'Reorder the patches so the dependency is created first, move the import ' +
|
|
459
|
+
'into the later patch, declare the intentional staged dependency with ' +
|
|
460
|
+
'"fireforge patch staged-dependency --add", or mark the import with ' +
|
|
461
|
+
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive. ` +
|
|
462
|
+
`Closest legal ordinal that satisfies this dependency: ${suggestedOrder}.`,
|
|
463
|
+
severity: 'error',
|
|
464
|
+
});
|
|
465
|
+
});
|
|
446
466
|
for (const entry of ctx.entries) {
|
|
447
467
|
for (const dependency of entry.metadata?.stagedDependencies?.forwardImports ?? []) {
|
|
448
468
|
if (usedStagedDeclarations.has(stagedDependencyKey(entry, dependency)))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS patch-lint rules: introduced raw color values and non-tokenized
|
|
3
|
+
* custom-property references.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `patch-lint.ts` so the per-patch and CSS rule bodies each
|
|
6
|
+
* stay within the project's per-file line budget — the same separation
|
|
7
|
+
* already applied to the JSDoc, observer, import, ownership, checkJs, and
|
|
8
|
+
* cross-patch rule families. `patch-lint.ts` re-exports `lintPatchedCss`
|
|
9
|
+
* so existing callers keep importing from the single module.
|
|
10
|
+
*/
|
|
11
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
12
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
13
|
+
/**
|
|
14
|
+
* Lints patched CSS files for introduced raw color values and non-tokenized
|
|
15
|
+
* custom properties.
|
|
16
|
+
*
|
|
17
|
+
* @param repoDir - Absolute path to the engine (repository) directory
|
|
18
|
+
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
19
|
+
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
20
|
+
* @param config - Project configuration
|
|
21
|
+
* @returns Array of lint issues found
|
|
22
|
+
*/
|
|
23
|
+
export declare function lintPatchedCss(repoDir: string, affectedFiles: string[], diffContent?: string, config?: FireForgeConfig): Promise<PatchLintIssue[]>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* CSS patch-lint rules: introduced raw color values and non-tokenized
|
|
4
|
+
* custom-property references.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `patch-lint.ts` so the per-patch and CSS rule bodies each
|
|
7
|
+
* stay within the project's per-file line budget — the same separation
|
|
8
|
+
* already applied to the JSDoc, observer, import, ownership, checkJs, and
|
|
9
|
+
* cross-patch rule families. `patch-lint.ts` re-exports `lintPatchedCss`
|
|
10
|
+
* so existing callers keep importing from the single module.
|
|
11
|
+
*/
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { toError } from '../utils/errors.js';
|
|
14
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
15
|
+
import { verbose } from '../utils/logger.js';
|
|
16
|
+
import { hasRawCssColors } from '../utils/regex.js';
|
|
17
|
+
import { loadFurnaceConfig } from './furnace-config.js';
|
|
18
|
+
import { extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
19
|
+
/**
|
|
20
|
+
* Loads the furnace token-prefix lint inputs gracefully — returns
|
|
21
|
+
* undefined (skipping the token-prefix check) when furnace.json cannot
|
|
22
|
+
* be loaded or no tokenPrefix is configured.
|
|
23
|
+
*/
|
|
24
|
+
async function loadCssTokenContext(repoDir) {
|
|
25
|
+
try {
|
|
26
|
+
const root = join(repoDir, '..');
|
|
27
|
+
const furnaceConfig = await loadFurnaceConfig(root);
|
|
28
|
+
if (furnaceConfig.tokenPrefix) {
|
|
29
|
+
return {
|
|
30
|
+
tokenPrefix: furnaceConfig.tokenPrefix,
|
|
31
|
+
tokenAllowlist: new Set(furnaceConfig.tokenAllowlist ?? []),
|
|
32
|
+
runtimeVariables: new Set(furnaceConfig.runtimeVariables ?? []),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
verbose(`Skipping furnace token-prefix lint hints because furnace.json could not be loaded: ${toError(error).message}`);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Raw-color check for one patched CSS file, scoped to introduced lines
|
|
43
|
+
* when diff context is available. Pushes onto `issues`.
|
|
44
|
+
*/
|
|
45
|
+
function checkRawColorValues(file, rawCss, addedLinesByFile, config, issues) {
|
|
46
|
+
// Check only introduced raw color values when diff context is available.
|
|
47
|
+
// Skip files on the raw-color allowlist (exact path or basename match) and
|
|
48
|
+
// auto-exempt files under `browser/branding/` — those are the fork's
|
|
49
|
+
// visual identity assets (app-about dialogs, installer pages, branded
|
|
50
|
+
// CSS copied from Firefox's `unofficial` template) and belong to the
|
|
51
|
+
// design-decision layer the design-token system does not govern.
|
|
52
|
+
// Without this auto-exemption, every first-time setup's copied CSS
|
|
53
|
+
// failed `raw-color-value` with no actionable fix other than manually
|
|
54
|
+
// listing each path in `rawColorAllowlist`.
|
|
55
|
+
const allowlist = config?.patchLint?.rawColorAllowlist;
|
|
56
|
+
const isAllowlisted = allowlist?.some((entry) => file === entry || file.endsWith('/' + entry));
|
|
57
|
+
const isBranding = file.startsWith('browser/branding/');
|
|
58
|
+
if (!isAllowlisted && !isBranding) {
|
|
59
|
+
// Strip lines with inline fireforge-ignore: raw-color-value suppression.
|
|
60
|
+
// Check against rawCss (before comment stripping) so the CSS comment marker is still present.
|
|
61
|
+
const sourceForSuppression = addedLinesByFile
|
|
62
|
+
? (addedLinesByFile.get(file) ?? []).join('\n')
|
|
63
|
+
: rawCss;
|
|
64
|
+
const suppressedContent = sourceForSuppression
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter((line) => !line.includes('fireforge-ignore: raw-color-value'))
|
|
67
|
+
.join('\n')
|
|
68
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
69
|
+
if (hasRawCssColors(suppressedContent)) {
|
|
70
|
+
issues.push({
|
|
71
|
+
file,
|
|
72
|
+
check: 'raw-color-value',
|
|
73
|
+
message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
|
|
74
|
+
severity: 'error',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Token-prefix check for one patched CSS file: flags `var(--x)` references
|
|
81
|
+
* that match neither the configured prefix, the allowlist, the runtime
|
|
82
|
+
* variables, nor a same-file declaration. Pushes onto `issues`.
|
|
83
|
+
*/
|
|
84
|
+
function checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues) {
|
|
85
|
+
// Check for non-tokenized custom properties. A variable that is both
|
|
86
|
+
// declared and consumed inside the same file is auto-exempted as a
|
|
87
|
+
// runtime state channel (see furnace.json → runtimeVariables).
|
|
88
|
+
//
|
|
89
|
+
// When diff context is available, scope the `var(...)` scan to
|
|
90
|
+
// added/modified lines only. `cssContent` (full-file) is still the
|
|
91
|
+
// source of `localDeclarations` so vars declared anywhere in the file
|
|
92
|
+
// are recognised as same-file refs regardless of where the consuming
|
|
93
|
+
// `var(...)` appears. Before this scoping change, a small edit to a
|
|
94
|
+
// Furnace override of a stock component (e.g. moz-card) produced a
|
|
95
|
+
// `token-prefix-violation` for every stock `var(--moz-card-*)` the
|
|
96
|
+
// upstream file already carried, because the scanner saw the full
|
|
97
|
+
// applied file and flagged each inherited reference as if the fork
|
|
98
|
+
// had introduced it.
|
|
99
|
+
if (tokenContext) {
|
|
100
|
+
const declarationPattern = /(?:^|[{;,\s])(--[\w-]+)\s*:/g;
|
|
101
|
+
const localDeclarations = new Set();
|
|
102
|
+
let declMatch;
|
|
103
|
+
while ((declMatch = declarationPattern.exec(cssContent)) !== null) {
|
|
104
|
+
const name = declMatch[1];
|
|
105
|
+
if (name)
|
|
106
|
+
localDeclarations.add(name);
|
|
107
|
+
}
|
|
108
|
+
const prefixScanSource = addedLinesByFile
|
|
109
|
+
? (addedLinesByFile.get(file) ?? []).join('\n').replace(/\/\*[\s\S]*?\*\//g, '')
|
|
110
|
+
: cssContent;
|
|
111
|
+
if (prefixScanSource.length > 0) {
|
|
112
|
+
const varPattern = /var\(\s*(--[\w-]+)/g;
|
|
113
|
+
const flaggedProps = new Set();
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = varPattern.exec(prefixScanSource)) !== null) {
|
|
116
|
+
const prop = match[1];
|
|
117
|
+
if (!prop)
|
|
118
|
+
continue;
|
|
119
|
+
if (prop.startsWith(tokenContext.tokenPrefix))
|
|
120
|
+
continue;
|
|
121
|
+
if (tokenContext.tokenAllowlist.has(prop))
|
|
122
|
+
continue;
|
|
123
|
+
if (tokenContext.runtimeVariables.has(prop))
|
|
124
|
+
continue;
|
|
125
|
+
if (localDeclarations.has(prop))
|
|
126
|
+
continue;
|
|
127
|
+
// De-duplicate per (file, prop) pair so the same introduced var
|
|
128
|
+
// used five times in the added hunk doesn't produce five
|
|
129
|
+
// identical issue entries.
|
|
130
|
+
if (flaggedProps.has(prop))
|
|
131
|
+
continue;
|
|
132
|
+
flaggedProps.add(prop);
|
|
133
|
+
issues.push({
|
|
134
|
+
file,
|
|
135
|
+
check: 'token-prefix-violation',
|
|
136
|
+
message: `CSS references var(${prop}) which does not match the required token prefix "${tokenContext.tokenPrefix}". Use a design token, add to tokenAllowlist, or (for runtime state channels) list the variable in runtimeVariables.`,
|
|
137
|
+
severity: 'error',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Lints patched CSS files for introduced raw color values and non-tokenized
|
|
145
|
+
* custom properties.
|
|
146
|
+
*
|
|
147
|
+
* @param repoDir - Absolute path to the engine (repository) directory
|
|
148
|
+
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
149
|
+
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
150
|
+
* @param config - Project configuration
|
|
151
|
+
* @returns Array of lint issues found
|
|
152
|
+
*/
|
|
153
|
+
export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config) {
|
|
154
|
+
const cssFiles = affectedFiles.filter((f) => f.endsWith('.css'));
|
|
155
|
+
if (cssFiles.length === 0)
|
|
156
|
+
return [];
|
|
157
|
+
const tokenContext = await loadCssTokenContext(repoDir);
|
|
158
|
+
const issues = [];
|
|
159
|
+
const addedLinesByFile = diffContent ? extractAddedLinesPerFile(diffContent) : undefined;
|
|
160
|
+
for (const file of cssFiles) {
|
|
161
|
+
const filePath = join(repoDir, file);
|
|
162
|
+
if (!(await pathExists(filePath)))
|
|
163
|
+
continue;
|
|
164
|
+
const rawCss = await readText(filePath);
|
|
165
|
+
// Strip block comments before scanning
|
|
166
|
+
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
167
|
+
checkRawColorValues(file, rawCss, addedLinesByFile, config, issues);
|
|
168
|
+
checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues);
|
|
169
|
+
}
|
|
170
|
+
return issues;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=patch-lint-css.js.map
|
|
@@ -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, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
|
|
5
|
+
export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, type ForwardImportEdge, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
|
|
6
6
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Public re-exports for {@link ./patch-lint.ts}. Split out so the
|
|
4
4
|
* orchestrator stays within the ESLint `max-lines` budget.
|
|
5
5
|
*/
|
|
6
|
-
export { buildPatchQueueContext, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
|
|
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
8
|
//# sourceMappingURL=patch-lint-reexports.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
2
2
|
import type { FireForgeConfig } from '../types/config.js';
|
|
3
3
|
import { type CommentStyle } from './license-headers.js';
|
|
4
|
+
import { lintPatchedCss } from './patch-lint-css.js';
|
|
4
5
|
export * from './patch-lint-reexports.js';
|
|
6
|
+
export { lintPatchedCss };
|
|
5
7
|
/**
|
|
6
8
|
* Counts the total lines in a unified diff and the number of non-binary
|
|
7
9
|
* text lines, so binary hunks do not inflate patch size checks.
|
|
@@ -23,16 +25,6 @@ export declare function isTestFile(file: string): boolean;
|
|
|
23
25
|
* Detects comment style from file extension for license header checks.
|
|
24
26
|
*/
|
|
25
27
|
export declare function commentStyleForFile(file: string): CommentStyle | null;
|
|
26
|
-
/**
|
|
27
|
-
* Lints patched CSS files for introduced raw color values and non-tokenized
|
|
28
|
-
* custom properties.
|
|
29
|
-
*
|
|
30
|
-
* @param repoDir - Absolute path to the engine (repository) directory
|
|
31
|
-
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
32
|
-
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
33
|
-
* @returns Array of lint issues found
|
|
34
|
-
*/
|
|
35
|
-
export declare function lintPatchedCss(repoDir: string, affectedFiles: string[], diffContent?: string, config?: FireForgeConfig): Promise<PatchLintIssue[]>;
|
|
36
28
|
/**
|
|
37
29
|
* Checks new files for required license headers.
|
|
38
30
|
*
|
|
@@ -121,6 +113,35 @@ export declare function lintPatchSize(filesAffected: string[], lineCount: number
|
|
|
121
113
|
* @returns Warning-level lint issues for files missing any recognized header
|
|
122
114
|
*/
|
|
123
115
|
export declare function lintModifiedFileHeaders(repoDir: string, affectedFiles: string[], newFiles: Set<string>): Promise<PatchLintIssue[]>;
|
|
116
|
+
/**
|
|
117
|
+
* Optional behaviour switches for {@link lintExportedPatch}.
|
|
118
|
+
*/
|
|
119
|
+
export interface LintExportedPatchOptions {
|
|
120
|
+
/**
|
|
121
|
+
* Skip the patch-size rules (`large-patch-files` / `large-patch-lines`).
|
|
122
|
+
* The ad-hoc `fireforge lint <files>` path passes a cross-patch file
|
|
123
|
+
* list that does not correspond to a single patch, so it suppresses the
|
|
124
|
+
* synthetic combined-list size check here and re-evaluates the size
|
|
125
|
+
* rules per owning patch instead — never synthesising a phantom
|
|
126
|
+
* oversized patch from the operator's file selection.
|
|
127
|
+
*/
|
|
128
|
+
skipPatchSize?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Restrict checkJs diagnostics to these repo-relative files; module
|
|
131
|
+
* resolution still spans every owned file in `patchQueueCtx`. Export and
|
|
132
|
+
* re-export pass the patch under export so cross-patch `resource:///`
|
|
133
|
+
* imports resolve against the whole queue while only that patch's
|
|
134
|
+
* findings surface.
|
|
135
|
+
*/
|
|
136
|
+
checkJsReportScope?: ReadonlySet<string>;
|
|
137
|
+
/**
|
|
138
|
+
* Pre-computed checkJs issues for this patch. When provided, the internal
|
|
139
|
+
* checkJs invocation is skipped and these are appended verbatim — the
|
|
140
|
+
* per-patch lint path builds one queue-wide checkJs program and
|
|
141
|
+
* attributes findings per patch instead of rebuilding per patch.
|
|
142
|
+
*/
|
|
143
|
+
precomputedCheckJs?: readonly PatchLintIssue[];
|
|
144
|
+
}
|
|
124
145
|
/**
|
|
125
146
|
* Runs all patch lint checks and returns combined issues.
|
|
126
147
|
*
|
|
@@ -140,6 +161,8 @@ export declare function lintModifiedFileHeaders(repoDir: string, affectedFiles:
|
|
|
140
161
|
* per-patch manifest context (re-export, per-patch lint) should
|
|
141
162
|
* pass this; aggregate-mode callers without a specific patch
|
|
142
163
|
* context skip it and fall through to auto-detection.
|
|
164
|
+
* @param options - Optional behaviour switches; see
|
|
165
|
+
* {@link LintExportedPatchOptions}.
|
|
143
166
|
* @returns Array of all lint issues found
|
|
144
167
|
*/
|
|
145
|
-
export declare function lintExportedPatch(repoDir: string, affectedFiles: string[], diffContent: string, config: FireForgeConfig, patchQueueCtx?: import('./patch-lint-cross.js').PatchQueueContext, ignoreChecks?: ReadonlySet<string>, patchTier?: 'branding'): Promise<PatchLintIssue[]>;
|
|
168
|
+
export declare function lintExportedPatch(repoDir: string, affectedFiles: string[], diffContent: string, config: FireForgeConfig, patchQueueCtx?: import('./patch-lint-cross.js').PatchQueueContext, ignoreChecks?: ReadonlySet<string>, patchTier?: 'branding', options?: LintExportedPatchOptions): Promise<PatchLintIssue[]>;
|