mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 16:46:31 +03:00
Fix detector review edge cases
AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.
This commit is contained in:
@@ -1228,14 +1228,17 @@ if (IS_BROWSER) {
|
||||
isHidden: isElementHidden(el),
|
||||
findings: findings.map(f => {
|
||||
const ap = ANTIPATTERNS.find(a => a.id === (f.type || f.id));
|
||||
const severity = f.severity || ap?.severity || 'warning';
|
||||
return {
|
||||
type: f.type || f.id,
|
||||
category: ap ? ap.category : 'quality',
|
||||
severity: f.severity || ap?.severity || 'warning',
|
||||
severity,
|
||||
// Advisory findings (em-dash overuse, etc.) are surfaced but never
|
||||
// treated as failures; carry the flag so the overlay/extension can
|
||||
// render them with the mildest affordance and consumers can filter.
|
||||
advisory: ap?.severity === 'advisory' || f.severity === 'advisory' || f.advisory === true,
|
||||
// Per-finding promotions override the registry default, so derive
|
||||
// this strictly from the effective severity.
|
||||
advisory: severity === 'advisory',
|
||||
detail: f.detail || f.snippet,
|
||||
ignoreValue: f.ignoreValue || f.value || '',
|
||||
name: ap ? ap.name : (f.type || f.id),
|
||||
@@ -1293,6 +1296,24 @@ if (IS_BROWSER) {
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
function conditionalCssRuleIsActive(rule) {
|
||||
const type = Number(rule?.type);
|
||||
const constructorName = rule?.constructor?.name || '';
|
||||
if (constructorName === 'CSSMediaRule' || type === 4) {
|
||||
const condition = rule.conditionText || rule.media?.mediaText || '';
|
||||
if (!condition || typeof window.matchMedia !== 'function') return true;
|
||||
try { return window.matchMedia(condition).matches; }
|
||||
catch { return true; }
|
||||
}
|
||||
if (constructorName === 'CSSSupportsRule' || type === 12) {
|
||||
const condition = rule.conditionText || '';
|
||||
if (!condition || typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return true;
|
||||
try { return CSS.supports(condition); }
|
||||
catch { return true; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read CSS that is absent from document.outerHTML. Inline <style> blocks are
|
||||
// already present in the HTML pattern corpus, so limit this walk to linked
|
||||
// stylesheets. Flatten grouping rules so each declaration keeps its selector,
|
||||
@@ -1325,6 +1346,7 @@ if (IS_BROWSER) {
|
||||
}
|
||||
const isKeyframes = /^\s*@(?:-webkit-)?keyframes\b/i.test(cssText);
|
||||
if (hasNestedRules && !isKeyframes) {
|
||||
if (!conditionalCssRuleIsActive(rule)) continue;
|
||||
appendRules(nested);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8129,14 +8129,17 @@ if (IS_BROWSER) {
|
||||
isHidden: isElementHidden(el),
|
||||
findings: findings.map(f => {
|
||||
const ap = ANTIPATTERNS.find(a => a.id === (f.type || f.id));
|
||||
const severity = f.severity || ap?.severity || 'warning';
|
||||
return {
|
||||
type: f.type || f.id,
|
||||
category: ap ? ap.category : 'quality',
|
||||
severity: f.severity || ap?.severity || 'warning',
|
||||
severity,
|
||||
// Advisory findings (em-dash overuse, etc.) are surfaced but never
|
||||
// treated as failures; carry the flag so the overlay/extension can
|
||||
// render them with the mildest affordance and consumers can filter.
|
||||
advisory: ap?.severity === 'advisory' || f.severity === 'advisory' || f.advisory === true,
|
||||
// Per-finding promotions override the registry default, so derive
|
||||
// this strictly from the effective severity.
|
||||
advisory: severity === 'advisory',
|
||||
detail: f.detail || f.snippet,
|
||||
ignoreValue: f.ignoreValue || f.value || '',
|
||||
name: ap ? ap.name : (f.type || f.id),
|
||||
@@ -8194,6 +8197,24 @@ if (IS_BROWSER) {
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
function conditionalCssRuleIsActive(rule) {
|
||||
const type = Number(rule?.type);
|
||||
const constructorName = rule?.constructor?.name || '';
|
||||
if (constructorName === 'CSSMediaRule' || type === 4) {
|
||||
const condition = rule.conditionText || rule.media?.mediaText || '';
|
||||
if (!condition || typeof window.matchMedia !== 'function') return true;
|
||||
try { return window.matchMedia(condition).matches; }
|
||||
catch { return true; }
|
||||
}
|
||||
if (constructorName === 'CSSSupportsRule' || type === 12) {
|
||||
const condition = rule.conditionText || '';
|
||||
if (!condition || typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return true;
|
||||
try { return CSS.supports(condition); }
|
||||
catch { return true; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read CSS that is absent from document.outerHTML. Inline <style> blocks are
|
||||
// already present in the HTML pattern corpus, so limit this walk to linked
|
||||
// stylesheets. Flatten grouping rules so each declaration keeps its selector,
|
||||
@@ -8226,6 +8247,7 @@ if (IS_BROWSER) {
|
||||
}
|
||||
const isKeyframes = /^\s*@(?:-webkit-)?keyframes\b/i.test(cssText);
|
||||
if (hasNestedRules && !isKeyframes) {
|
||||
if (!conditionalCssRuleIsActive(rule)) continue;
|
||||
appendRules(nested);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { finding } from '../../findings.mjs';
|
||||
import { deriveAdvisoryFlag, finding } from '../../findings.mjs';
|
||||
import { profileFindingsAsync, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
|
||||
import { captureVisualContrastCandidate } from '../visual/screenshot-contrast.mjs';
|
||||
import { checkContentHiddenAtRest } from '../../rules/checks.mjs';
|
||||
@@ -394,7 +394,7 @@ async function detectUrl(rawUrl, options = {}) {
|
||||
// Per-finding severity promotion (e.g. hero-region pulsing dot)
|
||||
// overrides the registry default carried by finding().
|
||||
if (f.severity && f.severity !== item.severity) item.severity = f.severity;
|
||||
return item;
|
||||
return deriveAdvisoryFlag(item);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../design-system.mjs';
|
||||
import { isFullPage } from '../../shared/page.mjs';
|
||||
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
||||
import { finding } from '../../findings.mjs';
|
||||
import { deriveAdvisoryFlag, finding } from '../../findings.mjs';
|
||||
import { profileFindings, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
|
||||
import {
|
||||
checkElementBorders,
|
||||
@@ -257,7 +257,7 @@ async function detectHtml(filePath, options = {}) {
|
||||
// severity (e.g. a pulsing dot inside a header/nav landmark) that
|
||||
// overrides the registry default.
|
||||
if (f.severity) item.severity = f.severity;
|
||||
findings.push(item);
|
||||
findings.push(deriveAdvisoryFlag(item));
|
||||
}
|
||||
// Text-content analyzers (em-dash overuse, marketing buzzwords,
|
||||
// numbered section markers, aphoristic cadence) live in the regex
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { getAntipattern, isAdvisoryRule } from './registry/antipatterns.mjs';
|
||||
import { getAntipattern } from './registry/antipatterns.mjs';
|
||||
|
||||
function getAP(id) {
|
||||
return getAntipattern(id);
|
||||
}
|
||||
|
||||
function deriveAdvisoryFlag(item) {
|
||||
if (item.severity === 'advisory') item.advisory = true;
|
||||
else delete item.advisory;
|
||||
return item;
|
||||
}
|
||||
|
||||
function finding(id, filePath, snippet, line = 0) {
|
||||
const ap = getAP(id);
|
||||
const base = { antipattern: id, name: ap.name, description: ap.description, severity: ap.severity || 'warning', category: ap.category || null, file: filePath, line, snippet };
|
||||
@@ -11,8 +17,7 @@ function finding(id, filePath, snippet, line = 0) {
|
||||
// failures. Carry the flag on the finding so every consumer (CLI, JSON, hook)
|
||||
// can partition without a registry lookup. Only stamped when true to keep the
|
||||
// finding shape stable for the vast majority of rules.
|
||||
if (isAdvisoryRule(id)) base.advisory = true;
|
||||
return base;
|
||||
return deriveAdvisoryFlag(base);
|
||||
}
|
||||
|
||||
export { getAP, finding };
|
||||
export { getAP, finding, deriveAdvisoryFlag };
|
||||
|
||||
@@ -386,6 +386,8 @@ describe('detectUrl — browser-only fixtures', () => {
|
||||
}
|
||||
assert.doesNotMatch(snippets, /pass-/, `no pass-case cursor should be flagged, got: ${snippets}`);
|
||||
assert.equal(hits.length, 3, `expected 3 blinking-cursor findings, got ${hits.length}: ${snippets}`);
|
||||
assert.equal(hits.every(hit => hit.severity === 'warning'), true, JSON.stringify(hits));
|
||||
assert.equal(hits.some(hit => hit.advisory === true), false, JSON.stringify(hits));
|
||||
});
|
||||
|
||||
it('typography side-by-side: element-level flag cases get regular overlays', async () => {
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* These selectors exist in the live DOM, but their conditions are inactive.
|
||||
URL scans must not treat their declarations as rendered page styles. */
|
||||
@media (max-width: 1px) {
|
||||
.inactive-media-stripes {
|
||||
background: repeating-linear-gradient(45deg, #eee, #eee 10px, #fafafa 10px, #fafafa 20px);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (display: imaginary-layout) {
|
||||
.inactive-supports-stripes {
|
||||
background: repeating-linear-gradient(45deg, #eee, #eee 10px, #fafafa 10px, #fafafa 20px);
|
||||
}
|
||||
}
|
||||
|
||||
.unused-linked-transition {
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<main>
|
||||
<h1>Linked stylesheet pattern</h1>
|
||||
<div class="flag-linked-stripes">Rendered repeating stripes</div>
|
||||
<div class="inactive-media-stripes">Inactive media stripes</div>
|
||||
<div class="inactive-supports-stripes">Inactive supports stripes</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user