| 24 | ]; |
| 25 | |
| 26 | export async function lintHyperframeHtml( |
| 27 | html: string, |
| 28 | options: HyperframeLinterOptions = {}, |
| 29 | ): Promise<HyperframeLintResult> { |
| 30 | const ctx = buildLintContext(html, options); |
| 31 | const findings: HyperframeLintFinding[] = []; |
| 32 | const seen = new Set<string>(); |
| 33 | |
| 34 | for (const rule of ALL_RULES) { |
| 35 | for (const finding of await Promise.resolve(rule(ctx))) { |
| 36 | const dedupeKey = [ |
| 37 | finding.code, |
| 38 | finding.severity, |
| 39 | finding.selector || "", |
| 40 | finding.elementId || "", |
| 41 | finding.message, |
| 42 | ].join("|"); |
| 43 | if (seen.has(dedupeKey)) continue; |
| 44 | seen.add(dedupeKey); |
| 45 | findings.push(options.filePath ? { ...finding, file: options.filePath } : finding); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | const errorCount = findings.filter((f) => f.severity === "error").length; |
| 50 | const warningCount = findings.filter((f) => f.severity === "warning").length; |
| 51 | const infoCount = findings.filter((f) => f.severity === "info").length; |
| 52 | |
| 53 | return { |
| 54 | ok: errorCount === 0, |
| 55 | errorCount, |
| 56 | warningCount, |
| 57 | infoCount, |
| 58 | findings, |
| 59 | }; |
| 60 | } |
| 61 | |
| 62 | // ── Async media URL accessibility checker ───────────────────────────────── |
| 63 | |