UNPKG

@bfra.me/eslint-config

Version:
306 lines (270 loc) 7.32 kB
import type {Config, ConfigNames, FlatConfigComposer, ResolvableFlatConfig} from './config' import type {Options} from './options' import {isPackageExists} from 'local-pkg' import {isInEditorEnv} from '../../es/src/env' import {composeConfig} from './compose-config' import { astro, command, epilogue, eslintComments, gitignore, ignores, imports, javascript, jsdoc, jsonc, markdown, nextjs, node, packageJson, perfectionist, pnpm, prettier, react, regexp, sortPackageJson, sortRenovateConfig, sortTsconfig, stylistic, toml, typescript, unicorn, vitest, yaml, } from './configs' import {jsx} from './configs/jsx' import {GLOB_JS, GLOB_JSX} from './globs' // These are merged into the Options interface type AllowedConfigForOptions = Omit<Config, 'files'> const AllowedConfigPropertiesForOptions = [ 'name', 'ignores', 'language', 'languageOptions', 'linterOptions', 'plugins', 'processor', 'rules', 'settings', ] satisfies (keyof AllowedConfigForOptions)[] /** * Define a new ESLint config. * * @param options - Options to configure the ESLint config. * @param userConfigs - Additional ESLint configs to include. * @returns A composable ESLint config. */ export async function defineConfig<C extends Config = Config, CN extends ConfigNames = ConfigNames>( options: Options = {}, ...userConfigs: ResolvableFlatConfig<Config extends C ? C : Config>[] // @ts-expect-error - TypeScript insists that the return type should be `Promise<T>`, but it's actually // `FlatConfigComposer<>` which acts like a `Promise<T>`. ): FlatConfigComposer<Config extends C ? C : Config, CN> { const { astro: enableAstro = false, gitignore: enableGitignore = true, ignores: userIgnores = [], imports: enableImports = true, jsx: enableJsx = true, nextjs: enableNextjs = false, packageJson: enablePackageJson = false, perfectionist: enablePerfectionist = true, pnpm: enableCatalogs = false, prettier: enablePrettier = isPackageExists('prettier'), react: enableReact = false, regexp: enableRegexp = true, typescript: enableTypeScript = isPackageExists('typescript'), unicorn: enableUnicorn = true, } = options let isInEditor = options.isInEditor if (isInEditor == null) { isInEditor = isInEditorEnv() if (isInEditor) { // eslint-disable-next-line no-console console.log( '[@bfra.me/eslint-config] Editor specific config is enabled. Some rules may be disabled.', ) } } const stylisticOptions = options.stylistic === false ? false : typeof options.stylistic === 'object' ? options.stylistic : {} if (stylisticOptions && !('jsx' in stylisticOptions)) { stylisticOptions.jsx = typeof enableJsx === 'object' ? true : enableJsx } const configs: (Config[] | Promise<Config[]>)[] = [] if (enableGitignore) { configs.push(gitignore(enableGitignore === true ? {strict: false} : enableGitignore)) } configs.push( ignores(userIgnores), javascript({isInEditor, overrides: getOverrides(options, 'javascript')}), eslintComments(), node(), jsdoc({stylistic: stylisticOptions}), imports(), command(), ) if (enablePackageJson) { configs.push(packageJson(resolveSubOptions(options, 'packageJson'))) } if (enablePerfectionist) { configs.push( perfectionist({ isInEditor, overrides: getOverrides(options, 'perfectionist'), ...resolveSubOptions(options, 'perfectionist'), }), ) } if (enableImports) { configs.push( imports({ stylistic: stylisticOptions, ...(enableImports === true ? {} : enableImports), }), ) } if (enableUnicorn) { const unicornOptions = resolveSubOptions(options, 'unicorn') configs.push( unicorn({ ...unicornOptions, files: unicornOptions.files ?? (enableTypeScript ? undefined : [GLOB_JS, GLOB_JSX]), overrides: getOverrides(options, 'unicorn'), }), ) } if (enableJsx) { configs.push(jsx(enableJsx === true ? {} : enableJsx)) } const typescriptOptions = resolveSubOptions(options, 'typescript') const tsconfigPath = 'tsconfigPath' in typescriptOptions ? typescriptOptions.tsconfigPath : undefined if (enableTypeScript) { configs.push( typescript({ ...typescriptOptions, overrides: getOverrides(options, 'typescript'), }), ) } if (stylisticOptions) { configs.push( stylistic({ ...stylisticOptions, overrides: getOverrides(options, 'stylistic'), }), ) } if (enableRegexp) { configs.push(regexp({overrides: getOverrides(options, 'regexp')})) } if (options.vitest) { configs.push( vitest({ isInEditor, overrides: getOverrides(options, 'vitest'), tsconfigPath, }), ) } if (enableReact) { configs.push( react({ ...typescriptOptions, overrides: getOverrides(options, 'react'), tsconfigPath, }), ) } if (enableNextjs) { configs.push( nextjs({ overrides: getOverrides(options, 'nextjs'), }), ) } if (enableAstro) { configs.push( astro({ ...resolveSubOptions(options, 'astro'), overrides: getOverrides(options, 'astro'), stylistic: stylisticOptions, }), ) } if (options.jsonc ?? true) { configs.push( jsonc({overrides: getOverrides(options, 'jsonc'), stylistic: stylisticOptions}), sortPackageJson(), sortRenovateConfig(), sortTsconfig(), ) } if (enableCatalogs) { configs.push(pnpm()) } if (options.toml ?? true) { configs.push( toml({ overrides: getOverrides(options, 'toml'), stylistic: stylisticOptions, }), ) } if (options.yaml ?? true) { configs.push( yaml({ overrides: getOverrides(options, 'yaml'), stylistic: stylisticOptions, }), ) } if (options.markdown ?? true) { configs.push( markdown({ ...resolveSubOptions(options, 'markdown'), overrides: getOverrides(options, 'markdown'), }), ) } if (enablePrettier) { configs.push( prettier({ isInEditor, overrides: getOverrides(options, 'prettier'), }), ) } // Epilogue config is always added last configs.push(epilogue()) const optionsConfig = AllowedConfigPropertiesForOptions.reduce( (config, key) => ({ ...config, ...(key in options ? {[key]: options[key]} : {}), }), {} as Config, ) if (Object.keys(optionsConfig).length) { configs.push([optionsConfig]) } return composeConfig(...configs, ...userConfigs) } type ResolvedOptions<T> = T extends boolean ? never : NonNullable<T> function resolveSubOptions<K extends keyof Options>( options: Options, key: K, ): ResolvedOptions<Options[K]> { return (typeof options[key] === 'boolean' ? {} : (options[key] ?? {})) as ResolvedOptions< Options[K] > } function getOverrides<K extends keyof Options>(options: Options, key: K): Config['rules'] { const sub = resolveSubOptions(options, key) return 'overrides' in sub ? (sub.overrides as Config['rules']) : {} }