From fe61c858c56d6439402b50ec86df2b5ceef49c51 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 14 Oct 2024 19:15:11 +0200 Subject: [PATCH] feat(build): output rules for flat config in dts file --- package.json | 2 +- scripts/config-generator.ts | 92 ++++++++++++++++++++++++++++++ scripts/generate.ts | 15 +++-- scripts/rules-generator.ts | 7 +-- src/configs-by-category.ts | 108 ++++++++++++++++++++++++++++++++++++ src/configs-by-scope.ts | 96 ++++++++++++++++++++++++++++++++ src/index.ts | 9 +-- src/utils.ts | 38 ------------- vite.config.ts | 1 + 9 files changed, 315 insertions(+), 53 deletions(-) create mode 100644 scripts/config-generator.ts create mode 100644 src/configs-by-category.ts create mode 100644 src/configs-by-scope.ts delete mode 100644 src/utils.ts 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/config-generator.ts b/scripts/config-generator.ts new file mode 100644 index 0000000..35be7be --- /dev/null +++ b/scripts/config-generator.ts @@ -0,0 +1,92 @@ +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import type { Rule } from './traverse-rules.js'; +import { camelCase, kebabCase } 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 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 += `\tname: 'oxlint/${kebabCase(grouping)}',\n`; + code += `\trules: rules.${camelCase(grouping)}Rules,`; + code += '\n} as const;\n\n'; + } + + code += 'export {\n'; + code += exportGrouping + .map((grouping) => { + return `\t${camelCase(grouping)}Config as "flat/${kebabCase(grouping)}"`; + }) + .join(',\n'); + code += '\n}'; + + 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..2a5ac0a --- /dev/null +++ b/src/configs-by-category.ts @@ -0,0 +1,108 @@ +// 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, +} as const; + +const nurseryConfig = { + name: 'oxlint/nursery', + rules: rules.nurseryRules, +} as const; + +const restrictionConfig = { + name: 'oxlint/restriction', + rules: rules.restrictionRules, +} as const; + +const styleConfig = { + name: 'oxlint/style', + rules: rules.styleRules, +} as const; + +const conditionalFixConfig = { + name: 'oxlint/conditional-fix', + rules: rules.conditionalFixRules, +} as const; + +const dangerousFixConfig = { + name: 'oxlint/dangerous-fix', + rules: rules.dangerousFixRules, +} as const; + +const conditionalFixSuggestionConfig = { + name: 'oxlint/conditional-fix-suggestion', + rules: rules.conditionalFixSuggestionRules, +} as const; + +const pendingConfig = { + name: 'oxlint/pending', + rules: rules.pendingRules, +} as const; + +const correctnessConfig = { + name: 'oxlint/correctness', + rules: rules.correctnessRules, +} as const; + +const perfConfig = { + name: 'oxlint/perf', + rules: rules.perfRules, +} as const; + +const conditionalSuggestionFixConfig = { + name: 'oxlint/conditional-suggestion-fix', + rules: rules.conditionalSuggestionFixRules, +} as const; + +const fixConfig = { + name: 'oxlint/fix', + rules: rules.fixRules, +} as const; + +const suggestionConfig = { + name: 'oxlint/suggestion', + rules: rules.suggestionRules, +} as const; + +const fixDangerousConfig = { + name: 'oxlint/fix-dangerous', + rules: rules.fixDangerousRules, +} as const; + +const suspiciousConfig = { + name: 'oxlint/suspicious', + rules: rules.suspiciousRules, +} as const; + +const conditionalSuggestionConfig = { + name: 'oxlint/conditional-suggestion', + rules: rules.conditionalSuggestionRules, +} as const; + +const dangerousSuggestionConfig = { + name: 'oxlint/dangerous-suggestion', + rules: rules.dangerousSuggestionRules, +} as const; + +export { + pedanticConfig as "flat/pedantic", + nurseryConfig as "flat/nursery", + restrictionConfig as "flat/restriction", + styleConfig as "flat/style", + conditionalFixConfig as "flat/conditional-fix", + dangerousFixConfig as "flat/dangerous-fix", + conditionalFixSuggestionConfig as "flat/conditional-fix-suggestion", + pendingConfig as "flat/pending", + correctnessConfig as "flat/correctness", + perfConfig as "flat/perf", + conditionalSuggestionFixConfig as "flat/conditional-suggestion-fix", + fixConfig as "flat/fix", + suggestionConfig as "flat/suggestion", + fixDangerousConfig as "flat/fix-dangerous", + suspiciousConfig as "flat/suspicious", + conditionalSuggestionConfig as "flat/conditional-suggestion", + dangerousSuggestionConfig as "flat/dangerous-suggestion" +} \ No newline at end of file diff --git a/src/configs-by-scope.ts b/src/configs-by-scope.ts new file mode 100644 index 0000000..a386e9e --- /dev/null +++ b/src/configs-by-scope.ts @@ -0,0 +1,96 @@ +// 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, +} as const; + +const typescriptConfig = { + name: 'oxlint/typescript', + rules: rules.typescriptRules, +} as const; + +const importConfig = { + name: 'oxlint/import', + rules: rules.importRules, +} as const; + +const jestConfig = { + name: 'oxlint/jest', + rules: rules.jestRules, +} as const; + +const jsdocConfig = { + name: 'oxlint/jsdoc', + rules: rules.jsdocRules, +} as const; + +const jsxA11yConfig = { + name: 'oxlint/jsx-a11y', + rules: rules.jsxA11yRules, +} as const; + +const nextjsConfig = { + name: 'oxlint/nextjs', + rules: rules.nextjsRules, +} as const; + +const nodeConfig = { + name: 'oxlint/node', + rules: rules.nodeRules, +} as const; + +const promiseConfig = { + name: 'oxlint/promise', + rules: rules.promiseRules, +} as const; + +const reactConfig = { + name: 'oxlint/react', + rules: rules.reactRules, +} as const; + +const reactPerfConfig = { + name: 'oxlint/react-perf', + rules: rules.reactPerfRules, +} as const; + +const securityConfig = { + name: 'oxlint/security', + rules: rules.securityRules, +} as const; + +const treeShakingConfig = { + name: 'oxlint/tree-shaking', + rules: rules.treeShakingRules, +} as const; + +const unicornConfig = { + name: 'oxlint/unicorn', + rules: rules.unicornRules, +} as const; + +const vitestConfig = { + name: 'oxlint/vitest', + rules: rules.vitestRules, +} as const; + +export { + eslintConfig as "flat/eslint", + typescriptConfig as "flat/typescript", + importConfig as "flat/import", + jestConfig as "flat/jest", + jsdocConfig as "flat/jsdoc", + jsxA11yConfig as "flat/jsx-a11y", + nextjsConfig as "flat/nextjs", + nodeConfig as "flat/node", + promiseConfig as "flat/promise", + reactConfig as "flat/react", + reactPerfConfig as "flat/react-perf", + securityConfig as "flat/security", + treeShakingConfig as "flat/tree-shaking", + unicornConfig as "flat/unicorn", + vitestConfig as "flat/vitest" +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ac50668..5daa254 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 * as configMapsByScope from './configs-by-scope.js'; +import * as configMapsByCategory 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), + ...configMapsByScope, + ...configMapsByCategory, }, -}; +} as const; 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; -} diff --git a/vite.config.ts b/vite.config.ts index 43fef22..60bb669 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ }, }, build: { + target: 'node20', lib: { entry: [path.resolve(import.meta.dirname, 'src/index.ts')], fileName: (format, entryName) => {