diff --git a/src/build-from-oxlint-config.spec.ts b/src/build-from-oxlint-config.spec.ts index 1429f7b..a285039 100644 --- a/src/build-from-oxlint-config.spec.ts +++ b/src/build-from-oxlint-config.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { assert, describe, expect, it } from 'vitest'; import { buildFromOxlintConfig, buildFromOxlintConfigFile, @@ -195,6 +195,80 @@ describe('buildFromOxlintConfig', () => { expect(configs[0].ignores).toStrictEqual(['./tests/.*ts']); }); }); + + describe('overrides', () => { + it('supports simple files + rules overrides', () => { + const configs = buildFromOxlintConfig({ + rules: { + eqeqeq: 'warn', + }, + overrides: [ + { + files: ['./*.ts'], + rules: { + 'no-alert': 'error', + }, + }, + ], + }); + + expect(configs.length).toBe(2); + assert(configs[0].rules !== undefined); + expect('eqeqeq' in configs[0].rules).toBe(true); + expect('no-alert' in configs[0].rules).toBe(false); + + assert(configs[1].rules !== undefined); + expect('eqeqeq' in configs[1].rules).toBe(false); + expect('no-alert' in configs[1].rules).toBe(true); + }); + + it('supports simple files + plugins overrides', () => { + const configs = buildFromOxlintConfig({ + rules: { + eqeqeq: 'warn', + }, + overrides: [ + { + files: ['./*.test.ts'], + plugins: ['vitest'], + }, + ], + }); + + expect(configs.length).toBe(2); + assert(configs[0].rules !== undefined); + expect('eqeqeq' in configs[0].rules).toBe(true); + expect('vitest/no-conditional-tests' in configs[0].rules).toBe(false); + + assert(configs[1].rules !== undefined); + expect('eqeqeq' in configs[1].rules).toBe(false); + expect('vitest/no-conditional-tests' in configs[1].rules).toBe(true); + }); + + it(' rule in overrides', () => { + const configs = buildFromOxlintConfig({ + rules: { + 'no-debugger': 'warn', + }, + overrides: [ + { + files: ['./*.test.ts'], + rules: { + 'no-debugger': 'off', + }, + }, + ], + }); + + expect(configs.length).toBe(2); + assert(configs[0].rules !== undefined); + expect('no-debugger' in configs[0].rules).toBe(true); + + console.log(configs[1].rules); + assert(configs[1].rules !== undefined); + expect('no-debugger' in configs[1].rules).toBe(false); + }); + }); }); const createConfigFileAndBuildFromIt = ( diff --git a/src/build-from-oxlint-config/index.ts b/src/build-from-oxlint-config/index.ts index 9f67c30..1ec4d26 100644 --- a/src/build-from-oxlint-config/index.ts +++ b/src/build-from-oxlint-config/index.ts @@ -17,6 +17,7 @@ import { handleIgnorePatternsScope, readIgnorePatternsFromConfig, } from './ignore-patterns.js'; +import { handleOverridesScope, readOverridesFromConfig } from './overrides.js'; // default plugins, see const defaultPlugins: OxlintConfigPlugins = ['react', 'unicorn', 'typescript']; @@ -66,6 +67,7 @@ export const buildFromOxlintConfig = ( ): EslintPluginOxlintConfig[] => { const rules: Record = {}; const plugins = readPluginsFromConfig(config) ?? defaultPlugins; + const categories = readCategoriesFromConfig(config) ?? defaultCategories; // it is not a plugin but it is activated by default plugins.push('eslint'); @@ -76,11 +78,7 @@ export const buildFromOxlintConfig = ( plugins.push('react-hooks'); } - handleCategoriesScope( - plugins, - readCategoriesFromConfig(config) ?? defaultCategories, - rules - ); + handleCategoriesScope(plugins, categories, rules); const configRules = readRulesFromConfig(config); @@ -99,7 +97,14 @@ export const buildFromOxlintConfig = ( handleIgnorePatternsScope(ignorePatterns, baseConfig); } - return [baseConfig]; + const overrides = readOverridesFromConfig(config); + const configs = [baseConfig]; + + if (overrides !== undefined) { + handleOverridesScope(overrides, configs, categories); + } + + return configs; }; /** diff --git a/src/build-from-oxlint-config/overrides.ts b/src/build-from-oxlint-config/overrides.ts new file mode 100644 index 0000000..a8a5684 --- /dev/null +++ b/src/build-from-oxlint-config/overrides.ts @@ -0,0 +1,47 @@ +import { handleCategoriesScope } from './categories.js'; +import { readPluginsFromConfig } from './plugins.js'; +import { handleRulesScope, readRulesFromConfig } from './rules.js'; +import { + EslintPluginOxlintConfig, + OxlintConfig, + OxlintConfigCategories, + OxlintConfigOverride, +} from './types.js'; + +export const handleOverridesScope = ( + overrides: OxlintConfigOverride[], + configs: EslintPluginOxlintConfig[], + baseCategories?: OxlintConfigCategories +): void => { + for (const overrideIndex in overrides) { + const override = overrides[overrideIndex]; + const eslintRules: Record = {}; + const eslintConfig: EslintPluginOxlintConfig = { + name: `oxlint/from-oxlint-config-override-${overrideIndex}`, + }; + + // expect that oxlint `files` syntax is the same as eslint + eslintConfig.files = override.files; + + const plugins = readPluginsFromConfig(override); + if (baseCategories !== undefined && plugins !== undefined) { + handleCategoriesScope(plugins, baseCategories, eslintRules); + } + + const rules = readRulesFromConfig(override); + if (rules !== undefined) { + handleRulesScope(rules, eslintRules); + } + + eslintConfig.rules = eslintRules; + configs.push(eslintConfig); + } +}; + +export const readOverridesFromConfig = ( + config: OxlintConfig +): OxlintConfigOverride[] | undefined => { + return 'overrides' in config && Array.isArray(config.overrides) + ? (config.overrides as OxlintConfigOverride[]) + : undefined; +}; diff --git a/src/build-from-oxlint-config/plugins.ts b/src/build-from-oxlint-config/plugins.ts index db18287..1181a1d 100644 --- a/src/build-from-oxlint-config/plugins.ts +++ b/src/build-from-oxlint-config/plugins.ts @@ -1,11 +1,15 @@ -import { OxlintConfig, OxlintConfigPlugins } from './types.js'; +import { + OxlintConfig, + OxlintConfigOverride, + OxlintConfigPlugins, +} from './types.js'; /** * tries to return the "plugins" section from the config. * it returns `undefined` when not found or invalid. */ export const readPluginsFromConfig = ( - config: OxlintConfig + config: OxlintConfig | OxlintConfigOverride ): OxlintConfigPlugins | undefined => { return 'plugins' in config && Array.isArray(config.plugins) ? (config.plugins as OxlintConfigPlugins) diff --git a/src/build-from-oxlint-config/rules.ts b/src/build-from-oxlint-config/rules.ts index 971534b..ae8661b 100644 --- a/src/build-from-oxlint-config/rules.ts +++ b/src/build-from-oxlint-config/rules.ts @@ -2,7 +2,11 @@ import { aliasPluginNames, reactHookRulesInsideReactScope, } from '../constants.js'; -import { OxlintConfig, OxlintConfigRules } from './types.js'; +import { + OxlintConfig, + OxlintConfigOverride, + OxlintConfigRules, +} from './types.js'; import configByCategory from '../generated/configs-by-category.js'; import { isObject } from './utils.js'; @@ -100,7 +104,7 @@ export const handleRulesScope = ( * it returns `undefined` when not found or invalid. */ export const readRulesFromConfig = ( - config: OxlintConfig + config: OxlintConfig | OxlintConfigOverride ): OxlintConfigRules | undefined => { return 'rules' in config && isObject(config.rules) ? (config.rules as OxlintConfigRules) diff --git a/src/build-from-oxlint-config/types.ts b/src/build-from-oxlint-config/types.ts index a388987..f903199 100644 --- a/src/build-from-oxlint-config/types.ts +++ b/src/build-from-oxlint-config/types.ts @@ -10,6 +10,12 @@ export type OxlintConfigIgnorePatterns = string[]; export type EslintPluginOxlintConfig = Linter.Config>; +export type OxlintConfigOverride = { + files: string[]; + plugins?: OxlintConfigPlugins; + rules?: OxlintConfigRules; +}; + export type OxlintConfig = { [key: string]: unknown; plugins?: OxlintConfigPlugins;