#!/usr/bin/env node /** * Drama Ling 元件驗證工具 * 功能:驗證HTML元件是否符合設計規範 * 作者:Drama Ling 開發團隊 * 日期:2025-09-15 */ const fs = require('fs'); const path = require('path'); // ANSI 顏色碼 const colors = { red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', reset: '\x1b[0m' }; // 設計規範定義 const DESIGN_SPECS = { // 必須使用的CSS類別前綴 classPrefixes: ['btn-', 'card-', 'input-', 'alert-', 'badge-', 'modal-'], // 必須包含的屬性 requiredAttributes: { 'button': ['type', 'class'], 'input': ['type', 'id', 'name'], 'img': ['alt', 'src'], 'a': ['href'] }, // 顏色變數 colorVariables: [ '--primary', '--primary-dark', '--primary-light', '--secondary', '--secondary-dark', '--secondary-light', '--success', '--warning', '--danger', '--info', '--gray-50', '--gray-100', '--gray-200', '--gray-300', '--gray-400', '--gray-500', '--gray-600', '--gray-700', '--gray-800', '--gray-900' ], // 間距變數 spacingVariables: [ '--space-1', '--space-2', '--space-3', '--space-4', '--space-5', '--space-6', '--space-8', '--space-10' ] }; class ComponentValidator { constructor() { this.errors = []; this.warnings = []; this.passed = 0; this.failed = 0; } // 日誌方法 logError(file, message) { this.errors.push({ file, message }); console.log(`${colors.red}[ERROR]${colors.reset} ${file}: ${message}`); } logWarning(file, message) { this.warnings.push({ file, message }); console.log(`${colors.yellow}[WARNING]${colors.reset} ${file}: ${message}`); } logSuccess(message) { console.log(`${colors.green}[✓]${colors.reset} ${message}`); } // 驗證HTML文件 validateHTMLFile(filePath) { const fileName = path.basename(filePath); console.log(`\n檢查文件: ${fileName}`); try { const content = fs.readFileSync(filePath, 'utf8'); // 檢查基本結構 this.checkHTMLStructure(fileName, content); // 檢查必要屬性 this.checkRequiredAttributes(fileName, content); // 檢查CSS類別命名 this.checkCSSClasses(fileName, content); // 檢查無障礙性 this.checkAccessibility(fileName, content); // 檢查響應式設計 this.checkResponsive(fileName, content); this.passed++; this.logSuccess(`${fileName} 驗證通過`); } catch (error) { this.failed++; this.logError(fileName, `無法讀取文件: ${error.message}`); } } // 檢查HTML基本結構 checkHTMLStructure(file, content) { // 檢查DOCTYPE if (!content.includes('')) { this.logWarning(file, '缺少 聲明'); } // 檢查meta viewport if (!content.includes('viewport')) { this.logError(file, '缺少 viewport meta 標籤(響應式設計必需)'); } // 檢查字符編碼 if (!content.includes('charset="UTF-8"') && !content.includes('charset=UTF-8')) { this.logError(file, '缺少 UTF-8 字符編碼聲明'); } } // 檢查必要屬性 checkRequiredAttributes(file, content) { for (const [element, attributes] of Object.entries(DESIGN_SPECS.requiredAttributes)) { const regex = new RegExp(`<${element}[^>]*>`, 'gi'); const matches = content.match(regex) || []; matches.forEach(match => { attributes.forEach(attr => { if (!match.includes(attr)) { this.logWarning(file, `<${element}> 元素缺少 ${attr} 屬性`); } }); }); } } // 檢查CSS類別命名規範 checkCSSClasses(file, content) { const classRegex = /class="([^"]*)"/g; let match; while ((match = classRegex.exec(content)) !== null) { const classes = match[1].split(' '); classes.forEach(className => { // 檢查是否使用 BEM 命名或設計系統前綴 const isValidClass = DESIGN_SPECS.classPrefixes.some(prefix => className.startsWith(prefix)) || className.includes('__') || // BEM element className.includes('--'); // BEM modifier if (!isValidClass && className && !className.startsWith('library-') && !className.startsWith('showcase-')) { this.logWarning(file, `CSS類別 "${className}" 可能不符合命名規範`); } }); } } // 檢查無障礙性 checkAccessibility(file, content) { // 檢查圖片alt屬性 const imgRegex = /]*>/g; let match; while ((match = imgRegex.exec(content)) !== null) { if (!match[0].includes('alt=')) { this.logError(file, '圖片缺少 alt 屬性(無障礙性要求)'); } } // 檢查表單標籤 const inputRegex = /]*>/g; const inputs = content.match(inputRegex) || []; inputs.forEach(input => { if (!input.includes('type="hidden"') && !input.includes('aria-label')) { // 檢查是否有對應的label const idMatch = input.match(/id="([^"]*)"/); if (idMatch) { const hasLabel = content.includes(`for="${idMatch[1]}"`); if (!hasLabel) { this.logWarning(file, `輸入框缺少對應的