UNPKG

eslint-plugin-oxlint

Version:
170 lines (140 loc) 5.44 kB
import { aliasPluginNames, reactHookRulesInsideReactScope, typescriptRulesExtendEslintRules, } from '../constants.js'; import { BuildFromOxlintConfigOptions, OxlintConfigRules } from './types.js'; import * as allRulesObjects from '../generated/rules-by-category.js'; import { isObject } from './utilities.js'; const allRules: string[] = Object.values(allRulesObjects).flatMap((rulesObject) => Object.keys(rulesObject) ); const typeAwareRules = new Set( Object.entries(allRulesObjects) .filter(([key]) => key.endsWith('TypeAwareRules')) .flatMap(([, rulesObject]) => Object.keys(rulesObject)) ); const getEsLintRuleName = ( rule: string, options: BuildFromOxlintConfigOptions = {} ): string | undefined => { // there is no plugin prefix, it can be all plugin if (!rule.includes('/')) { const found = allRules.find((search) => search.endsWith(`/${rule}`) || search === rule); if (!found) { return undefined; } // Filter out nursery rules unless explicitly enabled if (!options.withNursery && found in allRulesObjects.nurseryRules) { return undefined; } // Check for type-aware rules when enabled if (!options.typeAware && typeAwareRules.has(found)) { return undefined; } return found; } // greedy works with `@next/next/no-img-element` as an example const match = rule.match(/(^.*)\/(.*)/); if (match === null) { return undefined; } const pluginName = match[1]; const ruleName = match[2]; // map to the right eslint plugin let esPluginName = pluginName in aliasPluginNames ? aliasPluginNames[pluginName] : pluginName; // special case for eslint-plugin-react-hooks if (esPluginName === 'react' && reactHookRulesInsideReactScope.includes(ruleName)) { esPluginName = 'react-hooks'; } // special case for eslint-plugin-react-refresh if (esPluginName === 'react' && ruleName == 'only-export-components') { esPluginName = 'react-refresh'; } // extra check for eslint const expectedRule = esPluginName === '' ? ruleName : `${esPluginName}/${ruleName}`; const found = allRules.find((rule) => rule === expectedRule); if (!found) { return undefined; } // Filter out nursery rules unless explicitly enabled if (!options.withNursery && found in allRulesObjects.nurseryRules) { return undefined; } // Check for type-aware rules when enabled if (!options.typeAware && typeAwareRules.has(found)) { return undefined; } return found; }; /** * checks if value is validSet, or if validSet is an array, check if value is first value of it */ const isValueInSet = (value: unknown, validSet: unknown[]) => validSet.includes(value) || (Array.isArray(value) && validSet.includes(value[0])); /** * check if the value is "off", 0, ["off", ...], or [0, ...] */ const isDeactivateValue = (value: unknown) => isValueInSet(value, ['off', 0]); /** * check if the value is "error", "warn", 1, 2, ["error", ...], ["warn", ...], [1, ...], or [2, ...] */ const isActiveValue = (value: unknown) => isValueInSet(value, ['error', 'warn', 1, 2]); /** * checks if the oxlint rule is activated/deactivated and append/remove it. */ export const handleRulesScope = ( oxlintRules: OxlintConfigRules, rules: Record<string, 'off'>, options: BuildFromOxlintConfigOptions = {} ): void => { for (const rule in oxlintRules) { const eslintName = getEsLintRuleName(rule, options); if (eslintName === undefined) { continue; } // is this rules not turned off if (isActiveValue(oxlintRules[rule])) { rules[eslintName] = 'off'; // If this is an ESLint rule that has a TypeScript alias, disable that too if (!eslintName.includes('/') && typescriptRulesExtendEslintRules.includes(eslintName)) { const tsAlias = `@typescript-eslint/${eslintName}`; // Only add the alias if it exists in allRules if (allRules.includes(tsAlias)) { rules[tsAlias] = 'off'; } } // If this is a TypeScript rule that has an ESLint base, disable that too if (eslintName.startsWith('@typescript-eslint/')) { const baseRule = eslintName.replace('@typescript-eslint/', ''); if (typescriptRulesExtendEslintRules.includes(baseRule) && allRules.includes(baseRule)) { rules[baseRule] = 'off'; } } } else if (rule in rules && isDeactivateValue(oxlintRules[rule])) { // rules extended by categories or plugins can be disabled manually delete rules[eslintName]; // Also delete the TypeScript alias if it exists if (!eslintName.includes('/') && typescriptRulesExtendEslintRules.includes(eslintName)) { const tsAlias = `@typescript-eslint/${eslintName}`; if (tsAlias in rules) { delete rules[tsAlias]; } } // Also delete the base ESLint rule if this is a TypeScript rule if (eslintName.startsWith('@typescript-eslint/')) { const baseRule = eslintName.replace('@typescript-eslint/', ''); if (baseRule in rules) { delete rules[baseRule]; } } } } }; /** * tries to return the "rules" section from the config. * it returns `undefined` when not found or invalid. */ export const readRulesFromConfig = (config: unknown): OxlintConfigRules | undefined => { return isObject(config) && 'rules' in config && isObject(config.rules) ? config.rules : undefined; };