(html: string)
| 97 | } |
| 98 | |
| 99 | function qualityCheck(html: string): string[] { |
| 100 | const issues: string[] = []; |
| 101 | |
| 102 | const mainCount = countStartTags(html, 'main'); |
| 103 | if (mainCount > 1) issues.push(`${mainCount}x <main> elements`); |
| 104 | |
| 105 | // Emoji used as content icons (anti-slop). Heuristic: emoji codepoint sitting |
| 106 | // inside a <div> with aria-hidden or a role suggesting decoration. Broad, |
| 107 | // intentionally — false positives are cheap signals here. |
| 108 | const emojiInIcon = html.match( |
| 109 | /aria-hidden=["']true["'][^>]*>[\s]*[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}]/gu, |
| 110 | ); |
| 111 | if (emojiInIcon) issues.push(`${emojiInIcon.length} emoji icon(s)`); |
| 112 | |
| 113 | // Validate every <script> body parses as JS. We deliberately don't |
| 114 | // distinguish module from script (Babel handles JSX in the artifact at |
| 115 | // runtime) — just check it's lexically clean enough that tools like esbuild |
| 116 | // wouldn't choke. |
| 117 | let scriptSyntaxFailed = false; |
| 118 | transformHtmlElementBlocks(html, 'script', ({ body, tag }) => { |
| 119 | if (scriptSyntaxFailed) return tag; |
| 120 | const trimmed = body.trim(); |
| 121 | if (!trimmed) return tag; |
| 122 | try { |
| 123 | Parser.parse(trimmed, { ecmaVersion: 'latest', sourceType: 'script' }); |
| 124 | } catch (err) { |
| 125 | issues.push(`script syntax error: ${(err as Error).message.split('\n')[0]}`); |
| 126 | scriptSyntaxFailed = true; |
| 127 | } |
| 128 | return tag; |
| 129 | }); |
| 130 | |
| 131 | if (!html.includes('TWEAK_DEFAULTS') || !html.includes('/*EDITMODE-BEGIN*/')) { |
| 132 | issues.push('no EDITMODE block'); |
| 133 | } |
| 134 | |
| 135 | return issues; |
| 136 | } |
| 137 | |
| 138 | function countStartTags(html: string, tagName: string): number { |
| 139 | const lower = html.toLowerCase(); |
no test coverage detected