UNPKG

@tupe12334/eslint-config

Version:

ESLint configuration package with TypeScript support

293 lines (243 loc) • 8.67 kB
#!/usr/bin/env node import { ESLint } from 'eslint'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { readdir, stat } from 'fs/promises'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const projectRoot = join(__dirname, '..'); // Test categories and their expected behaviors const testCategories = { 'valid': { description: 'Files that should have minimal or no errors', files: ['test/valid.tsx', 'test/preact-test.tsx', 'test/typescript-rules.ts', 'test/type-assertions.ts'], maxErrors: 0, maxWarnings: 10, }, 'invalid': { description: 'Files that should trigger specific errors', files: ['test/invalid.tsx', 'test/jsx-extension-test.js', 'test/type-assertions-invalid.ts'], maxErrors: 20, maxWarnings: 25, expectedRules: ['no-restricted-syntax', 'react/jsx-filename-extension'], }, 'warnings': { description: 'Files that should trigger warnings', files: ['test/long-function.tsx'], maxErrors: 2, maxWarnings: 5, expectedRules: ['max-lines-per-function'], }, 'hooks': { description: 'React hooks rules testing', files: ['test/react-hooks-rules.tsx'], maxErrors: 10, maxWarnings: 10, expectedRules: ['react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks'], }, 'imports': { description: 'Import/export patterns testing', files: ['test/import-export-rules.ts'], maxErrors: 2, maxWarnings: 5, }, 'edge-cases': { description: 'Edge cases and boundary testing', files: ['test/edge-cases.tsx'], maxErrors: 5, maxWarnings: 20, }, 'performance': { description: 'Performance and large file testing', files: ['test/performance-test.tsx'], maxErrors: 10, maxWarnings: 15, }, }; async function findTestFiles() { const testDir = join(projectRoot, 'test'); const files = []; try { const entries = await readdir(testDir); for (const entry of entries) { const fullPath = join(testDir, entry); const stats = await stat(fullPath); if (stats.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry)) { files.push(`test/${entry}`); } } } catch (error) { console.warn('āš ļø Could not read test directory:', error.message); } return files.sort(); } async function runTestCategory(eslint, category, config) { console.log(`\nšŸ“ Testing category: ${category}`); console.log(` ${config.description}`); let categoryPassed = true; const categoryResults = { totalErrors: 0, totalWarnings: 0, rulesCovered: new Set(), fileResults: [], }; for (const file of config.files) { const filePath = join(projectRoot, file); try { const results = await eslint.lintFiles([filePath]); const result = results[0]; if (result) { const errorCount = result.errorCount; const warningCount = result.warningCount; categoryResults.totalErrors += errorCount; categoryResults.totalWarnings += warningCount; categoryResults.fileResults.push({ file, errors: errorCount, warnings: warningCount, messages: result.messages, }); // Collect rules that were triggered result.messages.forEach(msg => { if (msg.ruleId) { categoryResults.rulesCovered.add(msg.ruleId); } }); console.log(` šŸ“„ ${file}: ${errorCount} errors, ${warningCount} warnings`); // Show top errors/warnings for debugging if (result.messages.length > 0) { const topMessages = result.messages.slice(0, 3); topMessages.forEach(msg => { const level = msg.severity === 2 ? 'āŒ' : 'āš ļø '; console.log(` ${level} Line ${msg.line}: ${msg.message} (${msg.ruleId || 'unknown'})`); }); if (result.messages.length > 3) { console.log(` ... and ${result.messages.length - 3} more issues`); } } } } catch (error) { console.error(` āŒ Error linting ${file}:`, error.message); categoryPassed = false; } } // Validate category expectations if (categoryResults.totalErrors > config.maxErrors) { console.log(` āŒ Too many errors: ${categoryResults.totalErrors} > ${config.maxErrors}`); categoryPassed = false; } if (categoryResults.totalWarnings > config.maxWarnings) { console.log(` āŒ Too many warnings: ${categoryResults.totalWarnings} > ${config.maxWarnings}`); categoryPassed = false; } // Check for expected rules if (config.expectedRules) { const missingRules = config.expectedRules.filter(rule => !categoryResults.rulesCovered.has(rule) ); if (missingRules.length > 0) { console.log(` āš ļø Expected rules not found: ${missingRules.join(', ')}`); } if (categoryResults.rulesCovered.size > 0) { console.log(` āœ… Rules covered: ${Array.from(categoryResults.rulesCovered).join(', ')}`); } } const status = categoryPassed ? 'āœ…' : 'āŒ'; console.log(` ${status} Category result: ${categoryResults.totalErrors} errors, ${categoryResults.totalWarnings} warnings`); return { passed: categoryPassed, results: categoryResults }; } async function generateTestReport(allResults) { console.log('\n' + '='.repeat(80)); console.log('šŸ“Š TEST REPORT SUMMARY'); console.log('='.repeat(80)); let overallPassed = true; let totalErrors = 0; let totalWarnings = 0; const allRulesCovered = new Set(); for (const [category, { passed, results }] of Object.entries(allResults)) { const status = passed ? 'āœ…' : 'āŒ'; console.log(`${status} ${category}: ${results.totalErrors} errors, ${results.totalWarnings} warnings`); if (!passed) overallPassed = false; totalErrors += results.totalErrors; totalWarnings += results.totalWarnings; results.rulesCovered.forEach(rule => allRulesCovered.add(rule)); } console.log('\nšŸ“‹ Overall Statistics:'); console.log(` Total Errors: ${totalErrors}`); console.log(` Total Warnings: ${totalWarnings}`); console.log(` Rules Covered: ${allRulesCovered.size}`); console.log(` Categories Tested: ${Object.keys(allResults).length}`); console.log('\nšŸ”§ Rules Coverage:'); const sortedRules = Array.from(allRulesCovered).sort(); for (let i = 0; i < sortedRules.length; i += 3) { const chunk = sortedRules.slice(i, i + 3); console.log(` ${chunk.join(', ')}`); } console.log('\n' + '='.repeat(80)); if (overallPassed) { console.log('šŸŽ‰ ALL TESTS PASSED!'); return true; } else { console.log('šŸ’„ SOME TESTS FAILED!'); return false; } } async function runComprehensiveTests() { console.log('šŸš€ Starting Comprehensive ESLint Configuration Tests'); console.log('='.repeat(60)); try { // Initialize ESLint const eslint = new ESLint({ overrideConfigFile: join(projectRoot, 'eslint.config.js'), }); // Discover all test files const allTestFiles = await findTestFiles(); console.log(`šŸ“ Discovered ${allTestFiles.length} test files:`); allTestFiles.forEach(file => console.log(` - ${file}`)); // Run tests by category const allResults = {}; for (const [category, config] of Object.entries(testCategories)) { // Filter files that exist const existingFiles = config.files.filter(file => allTestFiles.includes(file) ); if (existingFiles.length === 0) { console.log(`\nāš ļø Skipping category '${category}' - no files found`); continue; } const categoryConfig = { ...config, files: existingFiles }; const result = await runTestCategory(eslint, category, categoryConfig); allResults[category] = result; } // Generate final report const overallPassed = await generateTestReport(allResults); if (overallPassed) { process.exit(0); } else { process.exit(1); } } catch (error) { console.error('šŸ’„ Test runner failed:', error); process.exit(1); } } // Handle CLI arguments const args = process.argv.slice(2); const showHelp = args.includes('--help') || args.includes('-h'); if (showHelp) { console.log(` ESLint Configuration Test Runner Usage: node scripts/test-runner.js [options] Options: -h, --help Show this help message --verbose Show detailed output --category Run specific category only Examples: node scripts/test-runner.js node scripts/test-runner.js --verbose node scripts/test-runner.js --category=hooks `); process.exit(0); } runComprehensiveTests();