import assert from 'node:assert/strict' import { readdirSync, readFileSync } from 'node:fs' import path from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' const sourceRoot = path.dirname(fileURLToPath(import.meta.url)) function collectVueFiles(directory) { return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const target = path.join(directory, entry.name) return entry.isDirectory() ? collectVueFiles(target) : entry.name.endsWith('.vue') ? [target] : [] }) } function lineNumber(source, offset) { return source.slice(0, offset).split('\n').length } function hasLiteralAttribute(tag, name) { return new RegExp(`\\s${name}(?:\\s|=|/?>)`).test(tag) } function isEditableNativeTextInput(tag) { if (hasLiteralAttribute(tag, 'disabled') || hasLiteralAttribute(tag, 'readonly')) return false const type = tag.match(/\stype\s*=\s*["']([^"']+)["']/)?.[1]?.toLowerCase() ?? 'text' return ['text', 'search', 'email', 'tel', 'url', 'password'].includes(type) } test('all editable text inputs provide a user-facing placeholder', () => { const violations = [] for (const file of collectVueFiles(sourceRoot)) { const source = readFileSync(file, 'utf8') const template = source.match(/]*)?>([\s\S]*)<\/template>/)?.[1] ?? '' const withoutComments = template.replace(//g, '') const attributeContent = String.raw`(?:[^>"']|"[^"]*"|'[^']*')*` const tagPattern = new RegExp(`|<(?:input|textarea)\\b${attributeContent}/?>`, 'g') for (const match of withoutComments.matchAll(tagPattern)) { const tag = match[0] const isElementInput = tag.startsWith('