diff --git a/package.json b/package.json index 8508f4d..ae1d43a 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "license": "MIT", "scripts": { - "generate": "node --import @oxc-node/core/register ./scripts/generate.ts && pnpm format", + "generate": "node --import @oxc-node/core/register ./scripts/generate.ts", "clone": "node --import @oxc-node/core/register ./scripts/sparse-clone.ts", "build": "vite build", "lint": "npx oxlint && npx eslint --flag unstable_ts_config", diff --git a/scripts/__snapshots__/rules-generator.test.ts.snap b/scripts/__snapshots__/rules-generator.test.ts.snap index edd8fe6..072a391 100644 --- a/scripts/__snapshots__/rules-generator.test.ts.snap +++ b/scripts/__snapshots__/rules-generator.test.ts.snap @@ -5,11 +5,11 @@ exports[`RulesGenerator > RulesGenerator generates rules correctly > byCategory const styleRules = { 'rulename-with-mod': "off" -} as const +} as const; const correctnessRules = { '@typescript-eslint/rulename-without-mod': "off" -} as const +} as const; export { styleRules, @@ -22,11 +22,11 @@ exports[`RulesGenerator > RulesGenerator generates rules correctly > byScope 1`] const eslintRules = { 'rulename-with-mod': "off" -} as const +} as const; const typescriptRules = { '@typescript-eslint/rulename-without-mod': "off" -} as const +} as const; export { eslintRules, diff --git a/scripts/config-generator.ts b/scripts/config-generator.ts new file mode 100644 index 0000000..792c722 --- /dev/null +++ b/scripts/config-generator.ts @@ -0,0 +1,95 @@ +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import type { Rule } from './traverse-rules.js'; +import { camelCase, kebabCase, pascalCase } from 'scule'; + +const __dirname = new URL('.', import.meta.url).pathname; + +export enum RulesGrouping { + CATEGORY = 'category', + SCOPE = 'scope', +} + +export type ResultMap = Map; + +export class ConfigGenerator { + private oxlintVersion: string; + private rulesGrouping: RulesGrouping; + private rulesArray: Rule[]; + constructor( + oxlintVersion: string, + rulesArray: Rule[] = [], + rulesGrouping: RulesGrouping = RulesGrouping.SCOPE + ) { + this.oxlintVersion = oxlintVersion; + this.rulesArray = rulesArray; + this.rulesGrouping = rulesGrouping; + } + + public setRulesGrouping(rulesGrouping: RulesGrouping) { + this.rulesGrouping = rulesGrouping; + } + + private groupItemsBy( + rules: Rule[], + rulesGrouping: RulesGrouping + ): Map { + const map = new Map(); + for (const item of rules) { + const key = item[rulesGrouping]; + const group = map.get(key) || []; + group.push(item.value); + map.set(key, group); + } + + return map; + } + + public async generateRulesCode() { + console.log( + `Generating config for ${this.oxlintVersion}, grouped by ${this.rulesGrouping}` + ); + + const rulesGrouping = this.rulesGrouping; + const rulesArray = this.rulesArray; + + const rulesMap = this.groupItemsBy(rulesArray, rulesGrouping); + const exportName = pascalCase(this.rulesGrouping); + + const exportGrouping: string[] = []; + let code = + '// These rules are automatically generated by scripts/generate-rules.ts\n\n'; + + code += `import * as rules from "./rules-by-${this.rulesGrouping}.js";\n\n`; + + for (const grouping of rulesMap.keys()) { + exportGrouping.push(grouping); + + code += `const ${camelCase(grouping)}Config = {\n`; + + code += ` name: 'oxlint/${kebabCase(grouping)}',\n`; + code += ` rules: rules.${camelCase(grouping)}Rules,`; + code += '\n};\n\n'; + } + + code += `const configBy${exportName} = {\n`; + code += exportGrouping + .map((grouping) => { + return ` 'flat/${kebabCase(grouping)}': ${camelCase(grouping)}Config`; + }) + .join(',\n'); + code += '\n}\n\n'; + + code += `export default configBy${exportName}`; + + return code; + } + + public async generateRules() { + const output = await this.generateRulesCode(); + writeFileSync( + path.resolve(__dirname, '..', `src/configs-by-${this.rulesGrouping}.ts`), + output + ); + } +} diff --git a/scripts/generate.ts b/scripts/generate.ts index d8f4ddc..c8b4c18 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -1,5 +1,6 @@ import { writeFileSync } from 'node:fs'; import { RulesGenerator, RulesGrouping } from './rules-generator.js'; +import { ConfigGenerator } from './config-generator.js'; import { traverseRules } from './traverse-rules.js'; import { getLatestVersionFromClonedRepo } from './oxlint-version.js'; import { TARGET_DIRECTORY, VERSION_PREFIX } from './constants.js'; @@ -24,13 +25,15 @@ if (!oxlintVersion) { ); } -const generator = new RulesGenerator(oxlintVersion, successResultArray); - -generator.setRulesGrouping(RulesGrouping.SCOPE); -await generator.generateRules(); -generator.setRulesGrouping(RulesGrouping.CATEGORY); -await generator.generateRules(); +const rulesGenerator = new RulesGenerator(oxlintVersion, successResultArray); +const configGenerator = new ConfigGenerator(oxlintVersion, successResultArray); +[rulesGenerator, configGenerator].forEach(async (generator) => { + generator.setRulesGrouping(RulesGrouping.SCOPE); + await generator.generateRules(); + generator.setRulesGrouping(RulesGrouping.CATEGORY); + await generator.generateRules(); +}); // Update package.json version writeFileSync( '../package.json', diff --git a/scripts/rules-generator.ts b/scripts/rules-generator.ts index 7a6d47b..3457209 100644 --- a/scripts/rules-generator.ts +++ b/scripts/rules-generator.ts @@ -1,6 +1,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Rule } from './traverse-rules.js'; +import { camelCase } from 'scule'; const __dirname = new URL('.', import.meta.url).pathname; @@ -62,16 +63,14 @@ export class RulesGenerator { exportGrouping.push(grouping); const rules = rulesMap.get(grouping); - code += `const ${grouping.replace(/_(\w)/g, (_, c) => - c.toUpperCase() - )}Rules = {\n`; + code += `const ${camelCase(grouping)}Rules = {\n`; code += rules ?.map((rule) => { return ` '${rule.replace(/_/g, '-')}': "off"`; }) .join(',\n'); - code += '\n} as const\n\n'; + code += '\n} as const;\n\n'; } code += 'export {\n'; diff --git a/src/configs-by-category.ts b/src/configs-by-category.ts new file mode 100644 index 0000000..8c8074d --- /dev/null +++ b/src/configs-by-category.ts @@ -0,0 +1,110 @@ +// These rules are automatically generated by scripts/generate-rules.ts + +import * as rules from './rules-by-category.js'; + +const pedanticConfig = { + name: 'oxlint/pedantic', + rules: rules.pedanticRules, +}; + +const nurseryConfig = { + name: 'oxlint/nursery', + rules: rules.nurseryRules, +}; + +const restrictionConfig = { + name: 'oxlint/restriction', + rules: rules.restrictionRules, +}; + +const styleConfig = { + name: 'oxlint/style', + rules: rules.styleRules, +}; + +const conditionalFixConfig = { + name: 'oxlint/conditional-fix', + rules: rules.conditionalFixRules, +}; + +const dangerousFixConfig = { + name: 'oxlint/dangerous-fix', + rules: rules.dangerousFixRules, +}; + +const conditionalFixSuggestionConfig = { + name: 'oxlint/conditional-fix-suggestion', + rules: rules.conditionalFixSuggestionRules, +}; + +const pendingConfig = { + name: 'oxlint/pending', + rules: rules.pendingRules, +}; + +const correctnessConfig = { + name: 'oxlint/correctness', + rules: rules.correctnessRules, +}; + +const perfConfig = { + name: 'oxlint/perf', + rules: rules.perfRules, +}; + +const conditionalSuggestionFixConfig = { + name: 'oxlint/conditional-suggestion-fix', + rules: rules.conditionalSuggestionFixRules, +}; + +const fixConfig = { + name: 'oxlint/fix', + rules: rules.fixRules, +}; + +const suggestionConfig = { + name: 'oxlint/suggestion', + rules: rules.suggestionRules, +}; + +const fixDangerousConfig = { + name: 'oxlint/fix-dangerous', + rules: rules.fixDangerousRules, +}; + +const suspiciousConfig = { + name: 'oxlint/suspicious', + rules: rules.suspiciousRules, +}; + +const conditionalSuggestionConfig = { + name: 'oxlint/conditional-suggestion', + rules: rules.conditionalSuggestionRules, +}; + +const dangerousSuggestionConfig = { + name: 'oxlint/dangerous-suggestion', + rules: rules.dangerousSuggestionRules, +}; + +const configByCategory = { + 'flat/pedantic': pedanticConfig, + 'flat/nursery': nurseryConfig, + 'flat/restriction': restrictionConfig, + 'flat/style': styleConfig, + 'flat/conditional-fix': conditionalFixConfig, + 'flat/dangerous-fix': dangerousFixConfig, + 'flat/conditional-fix-suggestion': conditionalFixSuggestionConfig, + 'flat/pending': pendingConfig, + 'flat/correctness': correctnessConfig, + 'flat/perf': perfConfig, + 'flat/conditional-suggestion-fix': conditionalSuggestionFixConfig, + 'flat/fix': fixConfig, + 'flat/suggestion': suggestionConfig, + 'flat/fix-dangerous': fixDangerousConfig, + 'flat/suspicious': suspiciousConfig, + 'flat/conditional-suggestion': conditionalSuggestionConfig, + 'flat/dangerous-suggestion': dangerousSuggestionConfig, +}; + +export default configByCategory; diff --git a/src/configs-by-scope.ts b/src/configs-by-scope.ts new file mode 100644 index 0000000..f170cdc --- /dev/null +++ b/src/configs-by-scope.ts @@ -0,0 +1,98 @@ +// These rules are automatically generated by scripts/generate-rules.ts + +import * as rules from './rules-by-scope.js'; + +const eslintConfig = { + name: 'oxlint/eslint', + rules: rules.eslintRules, +}; + +const typescriptConfig = { + name: 'oxlint/typescript', + rules: rules.typescriptRules, +}; + +const importConfig = { + name: 'oxlint/import', + rules: rules.importRules, +}; + +const jestConfig = { + name: 'oxlint/jest', + rules: rules.jestRules, +}; + +const jsdocConfig = { + name: 'oxlint/jsdoc', + rules: rules.jsdocRules, +}; + +const jsxA11yConfig = { + name: 'oxlint/jsx-a11y', + rules: rules.jsxA11yRules, +}; + +const nextjsConfig = { + name: 'oxlint/nextjs', + rules: rules.nextjsRules, +}; + +const nodeConfig = { + name: 'oxlint/node', + rules: rules.nodeRules, +}; + +const promiseConfig = { + name: 'oxlint/promise', + rules: rules.promiseRules, +}; + +const reactConfig = { + name: 'oxlint/react', + rules: rules.reactRules, +}; + +const reactPerfConfig = { + name: 'oxlint/react-perf', + rules: rules.reactPerfRules, +}; + +const securityConfig = { + name: 'oxlint/security', + rules: rules.securityRules, +}; + +const treeShakingConfig = { + name: 'oxlint/tree-shaking', + rules: rules.treeShakingRules, +}; + +const unicornConfig = { + name: 'oxlint/unicorn', + rules: rules.unicornRules, +}; + +const vitestConfig = { + name: 'oxlint/vitest', + rules: rules.vitestRules, +}; + +const configByScope = { + 'flat/eslint': eslintConfig, + 'flat/typescript': typescriptConfig, + 'flat/import': importConfig, + 'flat/jest': jestConfig, + 'flat/jsdoc': jsdocConfig, + 'flat/jsx-a11y': jsxA11yConfig, + 'flat/nextjs': nextjsConfig, + 'flat/node': nodeConfig, + 'flat/promise': promiseConfig, + 'flat/react': reactConfig, + 'flat/react-perf': reactPerfConfig, + 'flat/security': securityConfig, + 'flat/tree-shaking': treeShakingConfig, + 'flat/unicorn': unicornConfig, + 'flat/vitest': vitestConfig, +}; + +export default configByScope; diff --git a/src/index.ts b/src/index.ts index ac50668..041df77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import * as ruleMapsByScope from './rules-by-scope.js'; import * as ruleMapsByCategory from './rules-by-category.js'; -import { createFlatRulesConfig } from './utils.js'; +import configByScope from './configs-by-scope.js'; +import configByCategory from './configs-by-category.js'; type UnionToIntersection = (U extends any ? (x: U) => void : never) extends ( x: infer I @@ -36,7 +37,7 @@ export default { name: 'oxlint ignore rules recommended', rules: ruleMapsByCategory.correctnessRules, }, - ...createFlatRulesConfig(ruleMapsByScope), - ...createFlatRulesConfig(ruleMapsByCategory), + ...configByScope, + ...configByCategory, }, }; diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 61124b1..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { kebabCase } from 'scule'; -import type { KebabCase } from 'scule'; - -type WithoutRulesSuffix = T extends `${infer P}Rules` ? P : never; - -export function createFlatRulesConfig< - InputConfigs extends Record, - RuleRecord extends Record, - ConfigNameVariable extends keyof InputConfigs, - ConfigName extends WithoutRulesSuffix, - OutputConfigs extends Record< - `flat/${KebabCase}`, - { - name: string; - rules: RuleRecord; - } - >, ->(rulesModule: InputConfigs): OutputConfigs { - const flatRulesConfig = {} as OutputConfigs; - - // Iterate over each property in the rules module - for (const key of Object.keys(rulesModule)) { - if (key.endsWith('Rules')) { - // Ensure the property is a rules set - const ruleName = kebabCase(key.replace('Rules', '')); - const flatKey = `flat/${ruleName}` as `flat/${KebabCase}`; // Create the new key - - // @ts-ignore TS2322 -- "could be instantiated with a different subtype of constraint". - // we do not care at the moment, we only want our index.d.ts to include the names of the config - flatRulesConfig[flatKey] = { - name: `oxlint/${ruleName}`, - rules: rulesModule[key], - }; // Assign the rules to the new key - } - } - - return flatRulesConfig; -}