/** * Development-time accessibility audit utility. * * Scans DOM elements for common accessibility issues such as missing * alt text on images, missing labels on form inputs, empty links/buttons, * and incorrect ARIA usage. * * @module bquery/a11y */ import type { AuditFinding, AuditResult, AuditSeverity } from './types'; /** * Creates a finding object. * @internal */ const finding = ( severity: AuditSeverity, message: string, element: Element, rule: string ): AuditFinding => ({ severity, message, element, rule, }); /** * Checks images for missing alt attributes. * @internal */ const auditImages = (container: Element): AuditFinding[] => { const findings: AuditFinding[] = []; const images = container.querySelectorAll('img'); for (const img of images) { if (!img.hasAttribute('alt')) { findings.push( finding( 'error', 'Image is missing an alt attribute. Add alt="" for decorative images or a descriptive alt text.', img, 'img-alt' ) ); } else if (img.getAttribute('alt') === '' && !img.hasAttribute('role')) { findings.push( finding( 'info', 'Image has empty alt text. Consider adding role="presentation" if decorative.', img, 'img-decorative' ) ); } } return findings; }; /** * Checks form inputs for missing labels. * @internal */ const auditFormInputs = (container: Element): AuditFinding[] => { const findings: AuditFinding[] = []; const inputs = container.querySelectorAll('input, select, textarea'); for (const input of inputs) { const type = input.getAttribute('type'); // Hidden, submit, and button inputs don't need labels if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'reset') { continue; } const id = input.getAttribute('id'); const hasLabel = id ? !!container.querySelector(`label[for="${id}"]`) : false; const hasAriaLabel = input.hasAttribute('aria-label') || input.hasAttribute('aria-labelledby'); const hasTitle = input.hasAttribute('title'); const isWrappedInLabel = input.closest('label') !== null; if (!hasLabel && !hasAriaLabel && !hasTitle && !isWrappedInLabel) { findings.push( finding( 'error', `Form input is missing a label. Add a