@tupe12334/eslint-config
Version:
ESLint configuration package with TypeScript support
293 lines (243 loc) ⢠8.67 kB
JavaScript
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();