mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abfa8d2b90 |
@@ -8,20 +8,24 @@ function createDetectorProfile() {
|
|||||||
return { events: [] };
|
return { events: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordProfileEvent(profile, event) {
|
function normalizeProfileEvent(event) {
|
||||||
if (!profile) return;
|
return {
|
||||||
const normalized = {
|
|
||||||
engine: event.engine || 'unknown',
|
engine: event.engine || 'unknown',
|
||||||
phase: event.phase || 'unknown',
|
phase: event.phase || 'unknown',
|
||||||
ruleId: event.ruleId || 'unknown',
|
ruleId: event.ruleId || 'unknown',
|
||||||
target: event.target || '',
|
target: event.target || '',
|
||||||
ms: Number.isFinite(event.ms) ? event.ms : 0,
|
ms: Number.isFinite(event.ms) ? event.ms : 0,
|
||||||
findings: Number.isFinite(event.findings) ? event.findings : 0,
|
findings: Number.isFinite(event.findings) ? event.findings : 0,
|
||||||
|
...(event.detail ? { detail: event.detail } : {}),
|
||||||
|
...(Array.isArray(event.findingIds) && event.findingIds.length
|
||||||
|
? { findingIds: event.findingIds }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
if (event.detail) normalized.detail = event.detail;
|
}
|
||||||
if (Array.isArray(event.findingIds) && event.findingIds.length) {
|
|
||||||
normalized.findingIds = event.findingIds;
|
function recordProfileEvent(profile, event) {
|
||||||
}
|
if (!profile) return;
|
||||||
|
const normalized = normalizeProfileEvent(event);
|
||||||
if (typeof profile === 'function') {
|
if (typeof profile === 'function') {
|
||||||
profile(normalized);
|
profile(normalized);
|
||||||
} else if (typeof profile.record === 'function') {
|
} else if (typeof profile.record === 'function') {
|
||||||
@@ -38,10 +42,7 @@ function extractFindingIds(findings) {
|
|||||||
return [...new Set(findings.map(f => f?.id || f?.type || f?.antipattern).filter(Boolean))];
|
return [...new Set(findings.map(f => f?.id || f?.type || f?.antipattern).filter(Boolean))];
|
||||||
}
|
}
|
||||||
|
|
||||||
function profileFindings(profile, meta, callback) {
|
function recordDuration(profile, meta, started, findings) {
|
||||||
if (!profile) return callback();
|
|
||||||
const started = profileNow();
|
|
||||||
const findings = callback();
|
|
||||||
recordProfileEvent(profile, {
|
recordProfileEvent(profile, {
|
||||||
...meta,
|
...meta,
|
||||||
ms: profileNow() - started,
|
ms: profileNow() - started,
|
||||||
@@ -51,31 +52,26 @@ function profileFindings(profile, meta, callback) {
|
|||||||
return findings;
|
return findings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function profileFindings(profile, meta, callback) {
|
||||||
|
if (!profile) return callback();
|
||||||
|
const started = profileNow();
|
||||||
|
return recordDuration(profile, meta, started, callback());
|
||||||
|
}
|
||||||
|
|
||||||
function profileStep(profile, meta, callback) {
|
function profileStep(profile, meta, callback) {
|
||||||
if (!profile) return callback();
|
if (!profile) return callback();
|
||||||
const started = profileNow();
|
const started = profileNow();
|
||||||
try {
|
try {
|
||||||
return callback();
|
return callback();
|
||||||
} finally {
|
} finally {
|
||||||
recordProfileEvent(profile, {
|
recordDuration(profile, meta, started);
|
||||||
...meta,
|
|
||||||
ms: profileNow() - started,
|
|
||||||
findings: 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function profileFindingsAsync(profile, meta, callback) {
|
async function profileFindingsAsync(profile, meta, callback) {
|
||||||
if (!profile) return callback();
|
if (!profile) return callback();
|
||||||
const started = profileNow();
|
const started = profileNow();
|
||||||
const findings = await callback();
|
return recordDuration(profile, meta, started, await callback());
|
||||||
recordProfileEvent(profile, {
|
|
||||||
...meta,
|
|
||||||
ms: profileNow() - started,
|
|
||||||
findings: Array.isArray(findings) ? findings.length : 0,
|
|
||||||
findingIds: extractFindingIds(findings),
|
|
||||||
});
|
|
||||||
return findings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function profileStepAsync(profile, meta, callback) {
|
async function profileStepAsync(profile, meta, callback) {
|
||||||
@@ -84,11 +80,7 @@ async function profileStepAsync(profile, meta, callback) {
|
|||||||
try {
|
try {
|
||||||
return await callback();
|
return await callback();
|
||||||
} finally {
|
} finally {
|
||||||
recordProfileEvent(profile, {
|
recordDuration(profile, meta, started);
|
||||||
...meta,
|
|
||||||
ms: profileNow() - started,
|
|
||||||
findings: 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,19 +99,15 @@ function summarizeDetectorProfile(profile) {
|
|||||||
: (Array.isArray(profile?.events) ? profile.events : []);
|
: (Array.isArray(profile?.events) ? profile.events : []);
|
||||||
const groups = new Map();
|
const groups = new Map();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const key = [
|
const { engine, phase, ruleId, target, ms, findings } = normalizeProfileEvent(event);
|
||||||
event.engine || 'unknown',
|
const key = [engine, phase, ruleId, target].join('\u0000');
|
||||||
event.phase || 'unknown',
|
|
||||||
event.ruleId || 'unknown',
|
|
||||||
event.target || '',
|
|
||||||
].join('\u0000');
|
|
||||||
let group = groups.get(key);
|
let group = groups.get(key);
|
||||||
if (!group) {
|
if (!group) {
|
||||||
group = {
|
group = {
|
||||||
engine: event.engine || 'unknown',
|
engine,
|
||||||
phase: event.phase || 'unknown',
|
phase,
|
||||||
ruleId: event.ruleId || 'unknown',
|
ruleId,
|
||||||
target: event.target || '',
|
target,
|
||||||
calls: 0,
|
calls: 0,
|
||||||
totalMs: 0,
|
totalMs: 0,
|
||||||
findings: 0,
|
findings: 0,
|
||||||
@@ -127,10 +115,9 @@ function summarizeDetectorProfile(profile) {
|
|||||||
};
|
};
|
||||||
groups.set(key, group);
|
groups.set(key, group);
|
||||||
}
|
}
|
||||||
const ms = Number.isFinite(event.ms) ? event.ms : 0;
|
|
||||||
group.calls += 1;
|
group.calls += 1;
|
||||||
group.totalMs += ms;
|
group.totalMs += ms;
|
||||||
group.findings += Number.isFinite(event.findings) ? event.findings : 0;
|
group.findings += findings;
|
||||||
group.samples.push(ms);
|
group.samples.push(ms);
|
||||||
}
|
}
|
||||||
return [...groups.values()]
|
return [...groups.values()]
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ import {
|
|||||||
scanHtmlForShapeAssembledIllustration,
|
scanHtmlForShapeAssembledIllustration,
|
||||||
} from '../cli/engine/rules/checks.mjs';
|
} from '../cli/engine/rules/checks.mjs';
|
||||||
import { parseGradientColors } from '../cli/engine/shared/color.mjs';
|
import { parseGradientColors } from '../cli/engine/shared/color.mjs';
|
||||||
|
import {
|
||||||
|
createDetectorProfile,
|
||||||
|
profileFindings,
|
||||||
|
profileFindingsAsync,
|
||||||
|
profileStep,
|
||||||
|
profileStepAsync,
|
||||||
|
recordProfileEvent,
|
||||||
|
summarizeDetectorProfile,
|
||||||
|
} from '../cli/engine/profile/profiler.mjs';
|
||||||
|
|
||||||
const FIXTURES = path.join(import.meta.dir, 'fixtures', 'antipatterns');
|
const FIXTURES = path.join(import.meta.dir, 'fixtures', 'antipatterns');
|
||||||
const SCRIPT = path.join(import.meta.dir, '..', 'cli', 'engine', 'detect-antipatterns.mjs');
|
const SCRIPT = path.join(import.meta.dir, '..', 'cli', 'engine', 'detect-antipatterns.mjs');
|
||||||
@@ -97,6 +106,53 @@ function pageTypographyForGoogleFonts(href) {
|
|||||||
return checkPageTypography(doc, win);
|
return checkPageTypography(doc, win);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('detector profiler', () => {
|
||||||
|
test('normalizes recorded events before summarizing them', () => {
|
||||||
|
const profile = createDetectorProfile();
|
||||||
|
recordProfileEvent(profile, {
|
||||||
|
engine: 'regex',
|
||||||
|
ms: Number.NaN,
|
||||||
|
findings: Number.POSITIVE_INFINITY,
|
||||||
|
detail: 'source scan',
|
||||||
|
findingIds: ['side-tab'],
|
||||||
|
});
|
||||||
|
recordProfileEvent(profile, { phase: 'parse', ms: 2, findings: 1 });
|
||||||
|
|
||||||
|
expect(profile.events[0]).toEqual({
|
||||||
|
engine: 'regex', phase: 'unknown', ruleId: 'unknown', target: '', ms: 0, findings: 0,
|
||||||
|
detail: 'source scan', findingIds: ['side-tab'],
|
||||||
|
});
|
||||||
|
expect(summarizeDetectorProfile(profile).map(({ phase, totalMs, findings }) => (
|
||||||
|
{ phase, totalMs, findings }
|
||||||
|
))).toEqual([
|
||||||
|
{ phase: 'parse', totalMs: 2, findings: 1 },
|
||||||
|
{ phase: 'unknown', totalMs: 0, findings: 0 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves sync and async result and error contracts', async () => {
|
||||||
|
const profile = [];
|
||||||
|
const findings = [{ id: 'side-tab' }, { type: 'side-tab' }, { antipattern: 'dark-glow' }];
|
||||||
|
|
||||||
|
expect(profileFindings(profile, { phase: 'sync-findings' }, () => findings)).toBe(findings);
|
||||||
|
expect(await profileFindingsAsync(profile, { phase: 'async-findings' }, async () => findings)).toBe(findings);
|
||||||
|
expect(() => profileStep(profile, { phase: 'sync-step' }, () => {
|
||||||
|
throw new Error('sync failure');
|
||||||
|
})).toThrow('sync failure');
|
||||||
|
await expect(profileStepAsync(profile, { phase: 'async-step' }, async () => {
|
||||||
|
throw new Error('async failure');
|
||||||
|
})).rejects.toThrow('async failure');
|
||||||
|
|
||||||
|
expect(profile.map(({ phase, findings: count, findingIds }) => ({ phase, count, findingIds }))).toEqual([
|
||||||
|
{ phase: 'sync-findings', count: 3, findingIds: ['side-tab', 'dark-glow'] },
|
||||||
|
{ phase: 'async-findings', count: 3, findingIds: ['side-tab', 'dark-glow'] },
|
||||||
|
{ phase: 'sync-step', count: 0, findingIds: undefined },
|
||||||
|
{ phase: 'async-step', count: 0, findingIds: undefined },
|
||||||
|
]);
|
||||||
|
expect(profile.every(event => Number.isFinite(event.ms) && event.ms >= 0)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Core: checkElementBorders (computed style simulation)
|
// Core: checkElementBorders (computed style simulation)
|
||||||
|
|||||||
Reference in New Issue
Block a user