#!/usr/bin/env node /** * Rapport des valeurs arbitraires Tailwind (w-[...], h-[...], gap-[...], etc.) * pour prioriser les migrations vers le design system. * Usage: node scripts/report-arbitrary-values.mjs [--json] [--dir src/components] * Sortie: rapport (fichier, ligne, pattern) — pas de remplacement automatique. */ import fs from 'fs'; import path from 'path'; const ARBITRARY_PATTERNS = [ { name: 'width', regex: /(?:^|\s)(w-|min-w-|max-w-)\[[^\]]+\]/g }, { name: 'height', regex: /(?:^|\s)(h-|min-h-|max-h-)\[[^\]]+\]/g }, { name: 'gap', regex: /(?:^|\s)gap-\[[^\]]+\]/g }, { name: 'padding', regex: /(?:^|\s)(p-|px-|py-|pt-|pb-|pl-|pr-)\[[^\]]+\]/g }, { name: 'margin', regex: /(?:^|\s)(m-|mx-|my-|mt-|mb-|ml-|mr-)\[[^\]]+\]/g }, { name: 'space', regex: /(?:^|\s)space-[xy]-\[[^\]]+\]/g }, { name: 'rounded', regex: /(?:^|\s)rounded-\[[^\]]+\]/g }, { name: 'shadow', regex: /(?:^|\s)shadow-\[[^\]]+\]/g }, ]; const DEFAULT_DIRS = ['src/components', 'src/features']; const IGNORE_DIRS = ['node_modules', 'dist', '.storybook', 'dist_verification']; const EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js']); function parseArgs() { const args = process.argv.slice(2); let dirs = DEFAULT_DIRS; let json = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--json') json = true; else if (args[i] === '--dir' && args[i + 1]) { dirs = [args[++i]]; } } return { dirs, json }; } function* walkFiles(dir, base = dir) { if (!fs.existsSync(dir)) return; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const e of entries) { const full = path.join(dir, e.name); const rel = path.relative(base, full); if (e.isDirectory()) { if (!IGNORE_DIRS.includes(e.name)) yield* walkFiles(full, base); } else if (EXTENSIONS.has(path.extname(e.name))) { yield { full, rel }; } } } function scanFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const lines = content.split('\n'); const hits = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const { name, regex } of ARBITRARY_PATTERNS) { regex.lastIndex = 0; let m; while ((m = regex.exec(line)) !== null) { hits.push({ pattern: name, match: m[0].trim(), line: i + 1, }); } } } return hits; } function main() { const { dirs, json } = parseArgs(); const cwd = process.cwd(); const report = []; for (const dir of dirs) { const absDir = path.isAbsolute(dir) ? dir : path.join(cwd, dir); for (const { full, rel } of walkFiles(absDir)) { const hits = scanFile(full); if (hits.length > 0) { report.push({ file: rel, hits }); } } } if (json) { console.log(JSON.stringify(report, null, 2)); return; } console.log('# Rapport des valeurs arbitraires Tailwind\n'); console.log('Prioriser les migrations vers tokens / scale (voir docs/DESIGN_TOKENS.md).\n'); for (const { file, hits } of report) { console.log(`## ${file}`); for (const { pattern, match, line } of hits) { console.log(` ${line}: [${pattern}] ${match}`); } console.log(''); } console.log(`Total: ${report.length} fichiers, ${report.reduce((s, r) => s + r.hits.length, 0)} occurrences.`); } main();