mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 17:16:46 +03:00
Fix flat type hierarchy false positives (#702)
* Fix flat type hierarchy false positives Use rendered semantic roles and dominant size frequency, align the adjacent-step guidance, and abstain in source-only scans.\n\nAI assistance: prepared with Codex under maintainer direction. * Fix static hidden typography filtering Honor the hidden attribute in the static wrapper and use raw browser findings in regression coverage. AI assistance: prepared with Codex under maintainer direction. * Align typography sampling with painted content Count visibly painted aria-hidden text and exclude content-visibility hidden subtrees in both static and browser scans. AI assistance: prepared with Codex under maintainer direction.
This commit is contained in:
@@ -607,32 +607,6 @@ const REGEX_MATCHERS = [
|
||||
];
|
||||
|
||||
const REGEX_ANALYZERS = [
|
||||
// Flat type hierarchy
|
||||
(content, filePath) => {
|
||||
const sizes = new Set();
|
||||
const REM = 16;
|
||||
let m;
|
||||
const sizeRe = /font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi;
|
||||
while ((m = sizeRe.exec(content)) !== null) {
|
||||
const px = m[2] === 'px' ? +m[1] : +m[1] * REM;
|
||||
if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
|
||||
}
|
||||
const clampRe = /font-size\s*:\s*clamp\(\s*([\d.]+)(px|rem|em)\s*,\s*[^,]+,\s*([\d.]+)(px|rem|em)\s*\)/gi;
|
||||
while ((m = clampRe.exec(content)) !== null) {
|
||||
sizes.add(Math.round((m[2] === 'px' ? +m[1] : +m[1] * REM) * 10) / 10);
|
||||
sizes.add(Math.round((m[4] === 'px' ? +m[3] : +m[3] * REM) * 10) / 10);
|
||||
}
|
||||
const TW = { 'text-xs': 12, 'text-sm': 14, 'text-base': 16, 'text-lg': 18, 'text-xl': 20, 'text-2xl': 24, 'text-3xl': 30, 'text-4xl': 36, 'text-5xl': 48, 'text-6xl': 60, 'text-7xl': 72, 'text-8xl': 96, 'text-9xl': 128 };
|
||||
for (const [cls, px] of Object.entries(TW)) { if (new RegExp(`\\b${cls}\\b`).test(content)) sizes.add(px); }
|
||||
if (sizes.size < 3) return [];
|
||||
const sorted = [...sizes].sort((a, b) => a - b);
|
||||
const ratio = sorted[sorted.length - 1] / sorted[0];
|
||||
if (ratio >= 2.0) return [];
|
||||
const lines = content.split('\n');
|
||||
let line = 1;
|
||||
for (let i = 0; i < lines.length; i++) { if (/font-size/i.test(lines[i]) || /\btext-(?:xs|sm|base|lg|xl|\d)/i.test(lines[i])) { line = i + 1; break; } }
|
||||
return [finding('flat-type-hierarchy', filePath, `Sizes: ${sorted.map(s => s + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)`, line)];
|
||||
},
|
||||
// Monotonous spacing (regex)
|
||||
(content, filePath) => {
|
||||
const vals = [];
|
||||
@@ -1154,11 +1128,12 @@ const TEXT_CONTENT_ANALYZER_IDS = [
|
||||
function runTextContentAnalyzers(content, filePath, options = {}) {
|
||||
const profile = options?.profile;
|
||||
if (!shouldRunPageAnalyzers(content, filePath)) return [];
|
||||
// The 3 text-content analyzers are at indices 2-4 in REGEX_ANALYZERS
|
||||
// (single-font's removal on 2026-07-29 shifted every index down one).
|
||||
// The 3 text-content analyzers are at indices 1-3 in REGEX_ANALYZERS.
|
||||
// flat-type-hierarchy left this source-only path in issue #619 because it
|
||||
// needs rendered role and usage evidence.
|
||||
const findings = [];
|
||||
for (let i = 0; i < TEXT_CONTENT_ANALYZER_IDS.length; i++) {
|
||||
const analyzer = REGEX_ANALYZERS[2 + i];
|
||||
const analyzer = REGEX_ANALYZERS[1 + i];
|
||||
const ruleId = TEXT_CONTENT_ANALYZER_IDS[i];
|
||||
findings.push(...profileFindings(profile, {
|
||||
engine: 'regex',
|
||||
@@ -1284,7 +1259,6 @@ function detectText(content, filePath, options = {}) {
|
||||
// Page-level analyzers only run on full pages
|
||||
if (shouldRunPageAnalyzers(content, filePath)) {
|
||||
const analyzerIds = [
|
||||
'flat-type-hierarchy',
|
||||
'monotonous-spacing',
|
||||
'em-dash-overuse',
|
||||
'marketing-buzzword',
|
||||
|
||||
@@ -282,6 +282,7 @@ const STATIC_DEFAULT_STYLE = {
|
||||
marginLeft: '0px',
|
||||
position: 'static',
|
||||
visibility: 'visible',
|
||||
contentVisibility: 'visible',
|
||||
opacity: '1',
|
||||
top: 'auto',
|
||||
right: 'auto',
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
checkElementOversizedH1,
|
||||
checkElementQuality,
|
||||
checkElementRadialSpotlight,
|
||||
checkFlatTypeHierarchyFromDoc,
|
||||
checkCreamPalette,
|
||||
checkHtmlPatterns,
|
||||
checkKickerAboveHeadingFromDoc,
|
||||
@@ -59,18 +60,7 @@ function checkStaticPageTypography(document, window) {
|
||||
for (const font of overusedFound) {
|
||||
findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
|
||||
}
|
||||
const sizes = new Set();
|
||||
for (const el of document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div')) {
|
||||
const fontSize = parseFloat(window.getComputedStyle(el).fontSize);
|
||||
if (fontSize >= 8 && fontSize < 200) sizes.add(Math.round(fontSize * 10) / 10);
|
||||
}
|
||||
if (sizes.size >= 3) {
|
||||
const sorted = [...sizes].sort((a, b) => a - b);
|
||||
const ratio = sorted[sorted.length - 1] / sorted[0];
|
||||
if (ratio < 2.0) {
|
||||
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => s + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
||||
}
|
||||
}
|
||||
findings.push(...checkFlatTypeHierarchyFromDoc(document, el => window.getComputedStyle(el)));
|
||||
return findings;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user