UNPKG

eslint-config-ts-strict

Version:

Very strict ESLint config for projects using TypeScript, React and Prettier. ESLint v9 flat config only. Formatting rules disabled to prevent Prettier conflicts.

253 lines (223 loc) 9.55 kB
#!/usr/bin/env node /** * Custom script to check that all available ESLint rules have been implemented * in our configuration files. This replaces eslint-find-rules for ESLint v9 compatibility. */ import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const configPath = path.join(__dirname, '../index.js'); async function getAllAvailableRules() { // Get all available rules from installed plugins const rules = new Map(); // ESLint core rules const coreRules = (await import('eslint/use-at-your-own-risk')).builtinRules; for (const [name] of coreRules) { rules.set(name, 'core'); } // TypeScript ESLint rules try { const typescriptPlugin = await import('@typescript-eslint/eslint-plugin'); if (typescriptPlugin.default?.rules) { for (const ruleName of Object.keys(typescriptPlugin.default.rules)) { rules.set(`@typescript-eslint/${ruleName}`, '@typescript-eslint'); } } } catch (e) { console.warn('Could not load @typescript-eslint rules:', e.message); } // Import plugin rules try { const importPlugin = await import('eslint-plugin-import'); if (importPlugin.default?.rules) { for (const ruleName of Object.keys(importPlugin.default.rules)) { rules.set(`import/${ruleName}`, 'import'); } } } catch (e) { console.warn('Could not load import plugin rules:', e.message); } // Stylistic rules try { const stylisticPlugin = await import('@stylistic/eslint-plugin'); if (stylisticPlugin.default?.rules) { for (const ruleName of Object.keys(stylisticPlugin.default.rules)) { rules.set(`@stylistic/${ruleName}`, '@stylistic'); } } } catch (e) { console.warn('Could not load stylistic plugin rules:', e.message); } // Simple import sort rules try { const simpleImportSortPlugin = await import('eslint-plugin-simple-import-sort'); if (simpleImportSortPlugin.default?.rules) { for (const ruleName of Object.keys(simpleImportSortPlugin.default.rules)) { rules.set(`simple-import-sort/${ruleName}`, 'simple-import-sort'); } } } catch (e) { console.warn('Could not load simple-import-sort plugin rules:', e.message); } // Unused imports rules try { const unusedImportsPlugin = await import('eslint-plugin-unused-imports'); if (unusedImportsPlugin.default?.rules) { for (const ruleName of Object.keys(unusedImportsPlugin.default.rules)) { rules.set(`unused-imports/${ruleName}`, 'unused-imports'); } } } catch (e) { console.warn('Could not load unused-imports plugin rules:', e.message); } return rules; } async function getConfiguredRules() { const config = await import(configPath); const configArray = config.default; const configuredRules = new Set(); // Extract rules from all config objects for (const configObj of configArray) { if (configObj.rules) { for (const ruleName of Object.keys(configObj.rules)) { configuredRules.add(ruleName); } } } return configuredRules; } async function main() { try { console.log('🔍 Checking ESLint rules coverage...\n'); const [availableRules, configuredRules] = await Promise.all([ getAllAvailableRules(), getConfiguredRules() ]); const unusedRules = []; for (const [ruleName, plugin] of availableRules) { if (!configuredRules.has(ruleName)) { // Skip deprecated, legacy, or problematic rules if ( // Legacy/deprecated rules ruleName === 'id-blacklist' || ruleName === 'indent-legacy' || ruleName === 'no-unassigned-vars' || // Platform-specific rules ruleName === 'linebreak-style' || ruleName === '@stylistic/linebreak-style' || // Rules we handle with other tools (like Prettier) ruleName.includes('indent') && plugin === '@stylistic' || ruleName === 'max-len' || ruleName === '@stylistic/max-len' || // JSX-specific rules (we don't focus on JSX) ruleName.startsWith('@stylistic/jsx-') || // Overly restrictive rules ruleName === 'prefer-regex-literals' || ruleName === '@typescript-eslint/prefer-readonly-parameter-types' || // Stylistic rules that are usually handled by Prettier ruleName === '@stylistic/array-bracket-newline' || ruleName === '@stylistic/array-bracket-spacing' || ruleName === '@stylistic/array-element-newline' || ruleName === '@stylistic/arrow-parens' || ruleName === '@stylistic/arrow-spacing' || ruleName === '@stylistic/block-spacing' || ruleName === '@stylistic/brace-style' || ruleName === '@stylistic/comma-dangle' || ruleName === '@stylistic/comma-spacing' || ruleName === '@stylistic/comma-style' || ruleName === '@stylistic/computed-property-spacing' || ruleName === '@stylistic/curly-newline' || ruleName === '@stylistic/dot-location' || ruleName === '@stylistic/eol-last' || ruleName === '@stylistic/function-call-argument-newline' || ruleName === '@stylistic/function-call-spacing' || ruleName === '@stylistic/function-paren-newline' || ruleName === '@stylistic/generator-star-spacing' || ruleName === '@stylistic/implicit-arrow-linebreak' || ruleName === '@stylistic/key-spacing' || ruleName === '@stylistic/keyword-spacing' || ruleName === '@stylistic/line-comment-position' || ruleName === '@stylistic/lines-around-comment' || ruleName === '@stylistic/lines-between-class-members' || ruleName === '@stylistic/max-statements-per-line' || ruleName === '@stylistic/member-delimiter-style' || ruleName === '@stylistic/multiline-comment-style' || ruleName === '@stylistic/multiline-ternary' || ruleName === '@stylistic/new-parens' || ruleName === '@stylistic/newline-per-chained-call' || ruleName === '@stylistic/no-confusing-arrow' || ruleName === '@stylistic/no-extra-parens' || ruleName === '@stylistic/no-extra-semi' || ruleName === '@stylistic/no-floating-decimal' || ruleName === '@stylistic/no-mixed-operators' || ruleName === '@stylistic/no-mixed-spaces-and-tabs' || ruleName === '@stylistic/no-multi-spaces' || ruleName === '@stylistic/no-multiple-empty-lines' || ruleName === '@stylistic/no-tabs' || ruleName === '@stylistic/no-trailing-spaces' || ruleName === '@stylistic/no-whitespace-before-property' || ruleName === '@stylistic/nonblock-statement-body-position' || ruleName === '@stylistic/object-curly-newline' || ruleName === '@stylistic/object-curly-spacing' || ruleName === '@stylistic/object-property-newline' || ruleName === '@stylistic/one-var-declaration-per-line' || ruleName === '@stylistic/operator-linebreak' || ruleName === '@stylistic/padded-blocks' || ruleName === '@stylistic/padding-line-between-statements' || ruleName === '@stylistic/quote-props' || ruleName === '@stylistic/quotes' || ruleName === '@stylistic/rest-spread-spacing' || ruleName === '@stylistic/semi' || ruleName === '@stylistic/semi-spacing' || ruleName === '@stylistic/semi-style' || ruleName === '@stylistic/space-before-blocks' || ruleName === '@stylistic/space-before-function-paren' || ruleName === '@stylistic/space-in-parens' || ruleName === '@stylistic/space-infix-ops' || ruleName === '@stylistic/space-unary-ops' || ruleName === '@stylistic/spaced-comment' || ruleName === '@stylistic/switch-colon-spacing' || ruleName === '@stylistic/template-curly-spacing' || ruleName === '@stylistic/template-tag-spacing' || ruleName === '@stylistic/type-annotation-spacing' || ruleName === '@stylistic/type-generic-spacing' || ruleName === '@stylistic/type-named-tuple-spacing' || ruleName === '@stylistic/wrap-iife' || ruleName === '@stylistic/wrap-regex' || ruleName === '@stylistic/yield-star-spacing' ) { continue; } unusedRules.push({ name: ruleName, plugin }); } } if (unusedRules.length === 0) { console.log('✅ All relevant ESLint rules are configured!'); process.exit(0); } else { console.log(`❌ Found ${unusedRules.length} unused rules:\n`); // Group by plugin const byPlugin = {}; for (const rule of unusedRules) { if (!byPlugin[rule.plugin]) { byPlugin[rule.plugin] = []; } byPlugin[rule.plugin].push(rule.name); } for (const [plugin, rules] of Object.entries(byPlugin)) { console.log(`📦 ${plugin}:`); for (const ruleName of rules.sort()) { console.log(` - ${ruleName}`); } console.log(); } console.log('Consider adding these rules to your configuration or documenting why they are excluded.'); process.exit(1); } } catch (error) { console.error('❌ Error checking rules:', error.message); process.exit(1); } } main();