Fix detector review edge cases

AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.
This commit is contained in:
Paul Bakaus
2026-09-02 09:24:23 -07:00
parent 312e627eea
commit 45148003af
8 changed files with 79 additions and 12 deletions
+24 -2
View File
@@ -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;
}
+24 -2
View File
@@ -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 -2
View File
@@ -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
+9 -4
View File
@@ -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 () => {
+14
View File
@@ -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;
}
+2
View File
@@ -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>