// scripts/check-hardcoded-strings.js const fs = require('fs'); const path = require('path'); const babel = require('@babel/parser'); const traverse = require('@babel/traverse').default; // Patterns pour les classes Tailwind const TAILWIND_PATTERNS = [ // Layout /^(container|flex|grid|block|inline|hidden)/, // Spacing /^(m|p|mt|mb|ml|mr|mx|my|pt|pb|pl|pr|px|py)-[0-9]+/, // Sizing /^(w|h|min-w|min-h|max-w|max-h)-[0-9]+/, // Typography /^(text|font|leading)-/, // Backgrounds /^bg-/, // Borders /^(border|rounded|divide)-/, // Effects /^(shadow|opacity|transition)-/, // Interactivity /^(cursor|pointer-events|select|resize)-/, // Layout /^(z|top|right|bottom|left|float|clear)-/, // Flexbox & Grid /^(justify|items|content|self|place|gap)-/, // États /^(hover|focus|active|disabled|group|dark):/, // Couleurs /-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+$/ ]; // Nouveaux patterns pour ignorer les logs et imports const CODE_PATTERNS = [ // Console et debug avec contenu /console\.(log|error|warn|info|debug)\(['"].*['"]/, /^console\.(log|error|warn|info|debug)\(/, /^debugger/, // Imports et requires avec leurs chaînes /^import\s+.*\s+from\s+['"].*['"]/, /^import\s+['"].*['"]/, /^require\(['"].*['"]\)/, /^from\s+['"].*['"]/, /^@/, // Autres cas courants /^process\.env\./, /^module\.exports/, /^export\s+/, ]; function isHardcodedString(str) { // Ignorer les chaînes vides ou trop courtes if (!str || str.length <= 1) return false; // Vérifier si la chaîne fait partie d'un console.log ou d'un import const context = str.trim(); if (CODE_PATTERNS.some(pattern => pattern.test(context))) { return false; } // Vérifier si c'est une chaîne dans un console.log if (context.includes('console.log(')) { return false; } // Vérifier si c'est une chaîne dans un import if (context.includes('from \'') || context.includes('from "')) { return false; } // Vérifier si c'est une classe Tailwind const classes = str.split(' '); if (classes.some(cls => TAILWIND_PATTERNS.some(pattern => pattern.test(cls)) )) { return false; } // Autres patterns à ignorer const IGNORE_PATTERNS = [ /^[A-Z][A-Za-z]+$/, // Noms de composants /^@/, // Imports /^[./]/, // Chemins de fichiers /^[0-9]+$/, // Nombres /^style=/, // Props style /^id=/, // Props id /^data-/, // Data attributes /^aria-/, // Aria attributes /^role=/, // Role attributes /^className=/, // className attributes ]; return !IGNORE_PATTERNS.some(pattern => pattern.test(str)); } async function scanFile(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const hardcodedStrings = new Map(); // Utiliser Map pour stocker string -> ligne try { const ast = babel.parse(content, { sourceType: 'module', plugins: ['jsx', 'typescript'], locations: true // Active le tracking des positions }); traverse(ast, { StringLiteral(path) { const value = path.node.value; const line = path.node.loc.start.line; if (isHardcodedString(value)) { hardcodedStrings.set(value, line); } }, JSXText(path) { const value = path.node.value.trim(); const line = path.node.loc.start.line; if (isHardcodedString(value)) { hardcodedStrings.set(value, line); } }, }); } catch (error) { console.error(`Erreur dans le fichier ${filePath}:`, error); } return Array.from(hardcodedStrings.entries()); } async function scanDirectory(dir) { const results = {}; const files = fs.readdirSync(dir); for (const file of files) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') { Object.assign(results, await scanDirectory(filePath)); } else if ( stat.isFile() && (file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.tsx')) ) { const strings = await scanFile(filePath); if (strings.length > 0) { results[filePath] = strings; } } } return results; } async function logStringsToFile(results) { const outputPath = path.join(process.cwd(), 'hardcoded-strings-report.md'); let content = '# Rapport des chaînes en dur\n\n'; let totalStrings = 0; for (const [file, strings] of Object.entries(results)) { if (strings.length > 0) { const relativePath = path.relative(process.cwd(), file); content += `\n## ${relativePath}\n\n`; strings.forEach(([str, line]) => { totalStrings++; content += `- Ligne ${line}: \`${str}\`\n`; content += ` → [Voir dans le code](${relativePath}:${line}:1)\n\n`; }); } } content += `\n## Résumé\n`; content += `- Total des chaînes trouvées: ${totalStrings}\n`; content += `- Date du scan: ${new Date().toLocaleString('fr-FR')}\n`; fs.writeFileSync(outputPath, content, 'utf8'); console.log(`\nRapport généré: ${outputPath}`); } // Modifier la fonction main existante async function main() { const rootDir = process.cwd(); const results = await scanDirectory(path.join(rootDir, 'src')); await logStringsToFile(results); } main().catch(console.error);