mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Fix: harden live overlay detector waivers (#639 follow-up)
Read waiver config from every live root (appRoot, contextRoot, repoRoot), so monorepo projects whose config lives at the repo root reach the overlay; serialize served roots and page identities repo-relative there. Resolve each page URL to its actual serving file via the inject config's resolved page list before applying file-scoped waivers; ambiguous URLs keep the conservative common-ancestor fallback (PR #645 review discussion r3840011436). Honour detector.ignoreFiles: a wholly waived page now scans to zero findings in the overlay, matching the CLI and the edit hook. Guard the resolver call so a throwing resolver degrades to an unfiltered scan instead of breaking the detect toggle. Match design-system-color waivers by color value across hex and rgb() spellings, and stop extracting font values for bounce-easing findings, mirroring extractFindingIgnoreValue. Regenerate the browser bundle. AI-assisted change: reviewed, planned, and implemented with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Abdul Wahab
co-authored by
Claude Fable 5
parent
09506a9bb5
commit
152d6940b0
@@ -1473,6 +1473,14 @@ if (IS_BROWSER) {
|
||||
}
|
||||
|
||||
function collectBrowserFindings() {
|
||||
// A page matched by detector.ignoreFiles is waived wholesale: answer the
|
||||
// scan with the empty shape so the badge and toast read zero. Mirrors
|
||||
// shouldIgnoreDetectionFile in cli/lib/impeccable-config.mjs; the live
|
||||
// overlay resolves the globs per page (live-browser-ignores.js) and
|
||||
// forwards the verdict as config.skipScan.
|
||||
if (EXTENSION_MODE && window.__IMPECCABLE_CONFIG__?.skipScan === true) {
|
||||
return { groupMap: new Map(), allFindings: [], pageLevelFindings: [] };
|
||||
}
|
||||
const groupMap = new Map();
|
||||
const _disabled = EXTENSION_MODE ? (window.__IMPECCABLE_CONFIG__?.disabledRules || []) : [];
|
||||
const _ruleOk = (id) => !_disabled.length || !_disabled.includes(id);
|
||||
@@ -1702,15 +1710,20 @@ if (IS_BROWSER) {
|
||||
]);
|
||||
// The design-system checks set `ignoreValue` on their findings; the
|
||||
// detail fallbacks catch overused-font, whose value lives in its
|
||||
// sentence. Two CLI matchers are not mirrored here: the motion
|
||||
// extractor (a value-scoped bounce-easing waiver only matches when the
|
||||
// finding carries ignoreValue directly) and the design-system-color
|
||||
// color-equality fallback (a waiver written as rgb() will not match a
|
||||
// finding reported as hex; store the reported form).
|
||||
// sentence. One CLI matcher is not mirrored here: the motion extractor
|
||||
// (a value-scoped bounce-easing waiver only matches when the finding
|
||||
// carries ignoreValue directly). The CLI's [?&]family= URL fallback is
|
||||
// also omitted on purpose: browser findings for these rules always
|
||||
// carry ignoreValue or a "Primary font:" / "Google Fonts:" /
|
||||
// font-family sentence, so it is unreachable here.
|
||||
const _findingValue = (f) => {
|
||||
if (!f || !_directValueRules.has(f.type || f.id)) return '';
|
||||
const direct = f.ignoreValue || f.value;
|
||||
if (direct) return _normValue(direct);
|
||||
// The CLI routes bounce-easing through extractMotionIgnoreValue and
|
||||
// never the font regexes; without a direct ignoreValue there is no
|
||||
// value to match, so do not invent one from unrelated CSS text.
|
||||
if ((f.type || f.id) === 'bounce-easing') return '';
|
||||
for (const text of [f.detail, f.snippet]) {
|
||||
if (typeof text !== 'string' || !text) continue;
|
||||
const primary = text.match(/Primary font:\s*([^()\n;]+)/i);
|
||||
@@ -1722,11 +1735,56 @@ if (IS_BROWSER) {
|
||||
}
|
||||
return '';
|
||||
};
|
||||
// design-system-color compares by color value, not by spelling: the
|
||||
// browser reports computed rgb(...) strings while waivers are usually
|
||||
// written as hex. Mirrors ignoreValueMatches -> colorIgnoreKey in
|
||||
// cli/lib/impeccable-config.mjs for the hex and rgb()/rgba() forms;
|
||||
// hsl stays CLI-only.
|
||||
const _colorKey = (value) => {
|
||||
const text = String(value || '').trim().toLowerCase();
|
||||
const hex = text.match(/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/);
|
||||
if (hex) {
|
||||
const expanded = hex[1].length <= 4 ? [...hex[1]].map(d => d + d).join('') : hex[1];
|
||||
const [r, g, b, a = 255] = expanded.match(/../g).map(ch => parseInt(ch, 16));
|
||||
return `${r},${g},${b},${a}`;
|
||||
}
|
||||
const rgb = text.match(/^rgba?\((.*)\)$/);
|
||||
if (!rgb) return '';
|
||||
const body = rgb[1].trim().replace(/\s*\/\s*/g, ' / ');
|
||||
let parts;
|
||||
if (body.includes(',')) {
|
||||
parts = body.split(',').map(p => p.trim()).filter(Boolean);
|
||||
const last = parts[parts.length - 1];
|
||||
if (last && last.includes('/')) {
|
||||
parts = [...parts.slice(0, -1), ...last.split('/').map(p => p.trim()).filter(Boolean)];
|
||||
}
|
||||
} else {
|
||||
parts = body.split(/\s+/).filter(p => p && p !== '/');
|
||||
}
|
||||
if (parts.length < 3 || parts.length > 4) return '';
|
||||
const channel = (raw, isAlpha) => {
|
||||
const m = String(raw).trim().match(/^(-?\d*\.?\d+)(%)?$/);
|
||||
if (!m) return null;
|
||||
let v = parseFloat(m[1]);
|
||||
if (m[2]) v = isAlpha ? v / 100 : v * 2.55;
|
||||
const max = isAlpha ? 1 : 255;
|
||||
if (!Number.isFinite(v) || v < 0 || v > max) return null;
|
||||
return isAlpha ? v : Math.round(v);
|
||||
};
|
||||
const r = channel(parts[0], false);
|
||||
const g = channel(parts[1], false);
|
||||
const b = channel(parts[2], false);
|
||||
const a = parts[3] === undefined ? 1 : channel(parts[3], true);
|
||||
if ([r, g, b, a].some(v => v === null)) return '';
|
||||
return `${r},${g},${b},${Math.round(a * 255)}`;
|
||||
};
|
||||
const _valueIgnored = (f) => {
|
||||
const value = _findingValue(f);
|
||||
if (!value) return false;
|
||||
const rule = f.type || f.id;
|
||||
return _disabledValues.some(e => e.rule === rule && e.value === value);
|
||||
return _disabledValues.some(e => e.rule === rule && (e.value === value
|
||||
|| (rule === 'design-system-color'
|
||||
&& _colorKey(e.value) !== '' && _colorKey(e.value) === _colorKey(value))));
|
||||
};
|
||||
for (const [el, list] of [...groupMap.entries()]) {
|
||||
const kept = list.filter(f => !_valueIgnored(f));
|
||||
|
||||
Reference in New Issue
Block a user