mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Skip page-level checks on partials/components
Page-level typography checks (flat hierarchy, single font, overused font) now only run on files that look like full pages (have <!DOCTYPE, <html>, or <head> tags). Partials and components still get element-level border checks. isFullPage() strips HTML comments before checking to avoid false matches on prose that mentions tag names. Added partial-component.html fixture that has Inter, flat sizes, and a side-tab border — verifies only the border is flagged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
37393f1793
commit
ed1579ee37
@@ -79,6 +79,13 @@ const ANTIPATTERNS = [
|
||||
},
|
||||
];
|
||||
|
||||
/** Check if content looks like a full page (not a component/partial) */
|
||||
function isFullPage(content) {
|
||||
// Strip HTML comments before checking — they might mention <html>/<head> in prose
|
||||
const stripped = content.replace(/<!--[\s\S]*?-->/g, '');
|
||||
return /<!doctype\s|<html[\s>]|<head[\s>]/i.test(stripped);
|
||||
}
|
||||
|
||||
function getAP(id) {
|
||||
return ANTIPATTERNS.find(a => a.id === id);
|
||||
}
|
||||
@@ -289,9 +296,11 @@ async function detectHtml(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Page-level typography checks
|
||||
for (const f of checkPageTypography(document, window)) {
|
||||
findings.push(finding(f.id, filePath, f.snippet));
|
||||
// Page-level typography checks (only for full pages, not partials)
|
||||
if (isFullPage(html)) {
|
||||
for (const f of checkPageTypography(document, window)) {
|
||||
findings.push(finding(f.id, filePath, f.snippet));
|
||||
}
|
||||
}
|
||||
|
||||
window.close();
|
||||
@@ -540,8 +549,11 @@ function detectText(content, filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const analyzer of REGEX_ANALYZERS) {
|
||||
findings.push(...analyzer(content, filePath));
|
||||
// Page-level analyzers only run on full pages
|
||||
if (isFullPage(content)) {
|
||||
for (const analyzer of REGEX_ANALYZERS) {
|
||||
findings.push(...analyzer(content, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
@@ -712,7 +724,7 @@ if (isMainModule) main();
|
||||
|
||||
export {
|
||||
ANTIPATTERNS, SAFE_TAGS, OVERUSED_FONTS, GENERIC_FONTS,
|
||||
checkElementBorders, checkPageTypography, isNeutralColor,
|
||||
checkElementBorders, checkPageTypography, isNeutralColor, isFullPage,
|
||||
detectHtml, detectUrl, detectText,
|
||||
walkDir, formatFindings, SCANNABLE_EXTENSIONS, SKIP_DIRS,
|
||||
};
|
||||
|
||||
@@ -79,6 +79,13 @@ const ANTIPATTERNS = [
|
||||
},
|
||||
];
|
||||
|
||||
/** Check if content looks like a full page (not a component/partial) */
|
||||
function isFullPage(content) {
|
||||
// Strip HTML comments before checking — they might mention <html>/<head> in prose
|
||||
const stripped = content.replace(/<!--[\s\S]*?-->/g, '');
|
||||
return /<!doctype\s|<html[\s>]|<head[\s>]/i.test(stripped);
|
||||
}
|
||||
|
||||
function getAP(id) {
|
||||
return ANTIPATTERNS.find(a => a.id === id);
|
||||
}
|
||||
@@ -289,9 +296,11 @@ async function detectHtml(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Page-level typography checks
|
||||
for (const f of checkPageTypography(document, window)) {
|
||||
findings.push(finding(f.id, filePath, f.snippet));
|
||||
// Page-level typography checks (only for full pages, not partials)
|
||||
if (isFullPage(html)) {
|
||||
for (const f of checkPageTypography(document, window)) {
|
||||
findings.push(finding(f.id, filePath, f.snippet));
|
||||
}
|
||||
}
|
||||
|
||||
window.close();
|
||||
@@ -540,8 +549,11 @@ function detectText(content, filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const analyzer of REGEX_ANALYZERS) {
|
||||
findings.push(...analyzer(content, filePath));
|
||||
// Page-level analyzers only run on full pages
|
||||
if (isFullPage(content)) {
|
||||
for (const analyzer of REGEX_ANALYZERS) {
|
||||
findings.push(...analyzer(content, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
@@ -712,7 +724,7 @@ if (isMainModule) main();
|
||||
|
||||
export {
|
||||
ANTIPATTERNS, SAFE_TAGS, OVERUSED_FONTS, GENERIC_FONTS,
|
||||
checkElementBorders, checkPageTypography, isNeutralColor,
|
||||
checkElementBorders, checkPageTypography, isNeutralColor, isFullPage,
|
||||
detectHtml, detectUrl, detectText,
|
||||
walkDir, formatFindings, SCANNABLE_EXTENSIONS, SKIP_DIRS,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
import {
|
||||
ANTIPATTERNS, checkElementBorders, isNeutralColor,
|
||||
ANTIPATTERNS, checkElementBorders, isNeutralColor, isFullPage,
|
||||
detectHtml, detectText,
|
||||
walkDir, SCANNABLE_EXTENSIONS,
|
||||
} from '../source/skills/critique/scripts/detect-antipatterns.mjs';
|
||||
@@ -145,12 +145,14 @@ describe('detectText — overused fonts', () => {
|
||||
|
||||
describe('detectText — flat type hierarchy', () => {
|
||||
test('flags sizes too close together', () => {
|
||||
const f = detectText('h1{font-size:18px}h2{font-size:16px}h3{font-size:15px}p{font-size:14px}.s{font-size:13px}', 'test.css');
|
||||
const page = '<!DOCTYPE html><html><style>h1{font-size:18px}h2{font-size:16px}h3{font-size:15px}p{font-size:14px}.s{font-size:13px}</style></html>';
|
||||
const f = detectText(page, 'test.html');
|
||||
expect(f.some(r => r.antipattern === 'flat-type-hierarchy')).toBe(true);
|
||||
});
|
||||
|
||||
test('passes good hierarchy', () => {
|
||||
const f = detectText('h1{font-size:48px}h2{font-size:32px}p{font-size:16px}.s{font-size:12px}', 'test.css');
|
||||
const page = '<!DOCTYPE html><html><style>h1{font-size:48px}h2{font-size:32px}p{font-size:16px}.s{font-size:12px}</style></html>';
|
||||
const f = detectText(page, 'test.html');
|
||||
expect(f.filter(r => r.antipattern === 'flat-type-hierarchy')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -192,6 +194,14 @@ describe('detectHtml — jsdom', () => {
|
||||
expect(cleanFindings).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('partial-component: flags borders, skips page-level', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'partial-component.html'));
|
||||
expect(f.some(r => r.antipattern === 'side-tab')).toBe(true);
|
||||
expect(f.filter(r => r.antipattern === 'flat-type-hierarchy')).toHaveLength(0);
|
||||
expect(f.filter(r => r.antipattern === 'single-font')).toHaveLength(0);
|
||||
expect(f.filter(r => r.antipattern === 'overused-font')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('legitimate-borders has minimal false positives', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'legitimate-borders.html'));
|
||||
const borderFindings = f.filter(r => r.antipattern === 'side-tab' || r.antipattern === 'border-accent-on-rounded');
|
||||
@@ -212,6 +222,47 @@ describe('detectHtml — jsdom', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Full page vs partial detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('isFullPage', () => {
|
||||
test('detects DOCTYPE', () => expect(isFullPage('<!DOCTYPE html><html>')).toBe(true));
|
||||
test('detects <html>', () => expect(isFullPage('<html><head></head>')).toBe(true));
|
||||
test('detects <head>', () => expect(isFullPage('<head><meta charset="UTF-8"></head>')).toBe(true));
|
||||
test('rejects component/partial', () => expect(isFullPage('<div class="card">content</div>')).toBe(false));
|
||||
test('rejects JSX', () => expect(isFullPage('export default function Card() { return <div>hi</div> }')).toBe(false));
|
||||
});
|
||||
|
||||
describe('partials skip page-level checks', () => {
|
||||
test('regex: partial with flat hierarchy is not flagged', () => {
|
||||
const partial = '<div style="font-size: 14px">text</div>\n<div style="font-size: 16px">text</div>\n<div style="font-size: 15px">text</div>';
|
||||
const f = detectText(partial, 'card.tsx');
|
||||
expect(f.filter(r => r.antipattern === 'flat-type-hierarchy')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('regex: partial with single overused font is not flagged for single-font', () => {
|
||||
const partial = `<div style="font-family: 'Inter', sans-serif; font-size: 14px">text</div>\n`.repeat(25);
|
||||
const f = detectText(partial, 'card.tsx');
|
||||
expect(f.filter(r => r.antipattern === 'single-font')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('regex: partial still flags border anti-patterns', () => {
|
||||
const partial = '<div class="border-l-4 border-blue-500 rounded-lg">card</div>';
|
||||
const f = detectText(partial, 'card.tsx');
|
||||
expect(f.some(r => r.antipattern === 'side-tab')).toBe(true);
|
||||
});
|
||||
|
||||
test('regex: full page with flat hierarchy IS flagged', () => {
|
||||
const page = '<!DOCTYPE html><html><head></head><body>\n' +
|
||||
'<h1 style="font-size: 18px">h1</h1>\n<h2 style="font-size: 16px">h2</h2>\n' +
|
||||
'<p style="font-size: 14px">p</p>\n<span style="font-size: 15px">s</span>\n' +
|
||||
'<small style="font-size: 13px">sm</small>\n</body></html>';
|
||||
const f = detectText(page, 'index.html');
|
||||
expect(f.some(r => r.antipattern === 'flat-type-hierarchy')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ANTIPATTERNS registry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- This is a partial/component — no DOCTYPE, no <html>, no <head> -->
|
||||
<!-- Page-level checks (flat hierarchy, single font) should NOT run -->
|
||||
<!-- But element-level border checks SHOULD still run -->
|
||||
|
||||
<div class="card" style="font-family: 'Inter', sans-serif;">
|
||||
<div style="font-size: 14px; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 1rem; background: white;">
|
||||
<h3 style="font-size: 16px; font-weight: 600;">Card Title</h3>
|
||||
<p style="font-size: 15px; color: #6b7280;">Card description with close font sizes.</p>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/detect-antipatterns-browser.js"></script>
|
||||
Reference in New Issue
Block a user