UNPKG

@bfra.me/eslint-config

Version:
176 lines (166 loc) 6.32 kB
import type {Config} from '../config' import type { Flatten, OptionsFiles, OptionsOverrides, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes, } from '../options' import {isPackageExists} from 'local-pkg' import {GLOB_ASTRO_TS, GLOB_MARKDOWN, GLOB_SRC, GLOB_TS, GLOB_TSX} from '../globs' import {anyParser} from '../parsers/any-parser' import {requireOf} from '../require-of' import {interopDefault} from '../utils' import {fallback} from './fallback' const ReactRefreshAllowConstantExportPackages = ['vite'] const RemixPackages = ['@remix-run/node', '@remix-run/react', '@remix-run/serve', '@remix-run/dev'] const ReactRouterPackages = [ '@react-router/node', '@react-router/react', '@react-router/serve', '@react-router/dev', ] const NextJsPackages = ['next'] const ReactTypeAwareRules: Config['rules'] = { '@eslint-react/no-implicit-key': 'warn', '@eslint-react/no-leaked-conditional-rendering': 'warn', } /** * Configuration options for React ESLint rules. * * Extends multiple option interfaces to provide a comprehensive set of configuration * options including file patterns, rule overrides, TypeScript parser options, and * TypeScript type checking options. */ export interface ReactOptions extends Flatten< OptionsFiles & OptionsOverrides & OptionsTypeScriptParserOptions & OptionsTypeScriptWithTypes > {} /** * Creates ESLint configuration for React applications with comprehensive rule sets. * * This function sets up ESLint rules for React development including: * - React DOM rules for proper DOM usage * - React Hooks rules for hooks best practices * - React Refresh rules for hot reloading compatibility * - React Web API rules for preventing memory leaks * - Core React rules for component development * - Optional type-aware rules when TypeScript is configured * * The configuration automatically detects common React frameworks (Next.js, Remix, React Router) * and adjusts rules accordingly to allow framework-specific exports. * * @param options - Configuration options for the React ESLint setup * @param options.files - File patterns to apply React rules to. Defaults to source files. * @param options.overrides - Custom rule overrides to merge with the base configuration * @param options.tsconfigPath - Path to TypeScript configuration file for type-aware linting * @param options.typeAware - Configuration for type-aware rules including file patterns and ignores * @param options.typeAware.files - File patterns for type-aware linting (TypeScript files) * @param options.typeAware.ignores - File patterns to exclude from type-aware linting * * @returns Promise resolving to an array of ESLint configuration objects */ export async function react(options: ReactOptions = {}): Promise<Config[]> { const { files = [GLOB_SRC], overrides = {}, tsconfigPath, typeAware = { files: [GLOB_TS, GLOB_TSX], ignores: [`${GLOB_MARKDOWN}/**`, GLOB_ASTRO_TS], }, } = options const isTypeAware = typeof tsconfigPath === 'string' && tsconfigPath.trim().length > 0 return requireOf( ['@eslint-react/eslint-plugin', 'eslint-plugin-react-refresh'], async () => { const [pluginReact, pluginReactRefresh] = await Promise.all([ interopDefault(import('@eslint-react/eslint-plugin')), import('eslint-plugin-react-refresh').then(m => m.reactRefresh), ] as const) const plugins = pluginReact.configs.all.plugins as NonNullable<Config['plugins']> const isAllowConstantExport = ReactRefreshAllowConstantExportPackages.some(i => isPackageExists(i), ) const isUsingRemix = RemixPackages.some(i => isPackageExists(i)) const isUsingReactRouter = ReactRouterPackages.some(i => isPackageExists(i)) const isUsingNext = NextJsPackages.some(i => isPackageExists(i)) return [ { name: '@bfra.me/react/setup', plugins: { ...plugins, 'react-refresh': pluginReactRefresh.plugin, }, }, { name: '@bfra.me/react/rules', files, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true, }, }, sourceType: 'module', }, rules: { ...pluginReact.configs.recommended.rules, // preconfigured rules from eslint-plugin-react-refresh https://github.com/ArnaudBarre/eslint-plugin-react-refresh/tree/main/src 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: isAllowConstantExport, allowExportNames: [ ...(isUsingNext ? [ 'dynamic', 'dynamicParams', 'revalidate', 'fetchCache', 'runtime', 'preferredRegion', 'maxDuration', 'config', 'generateStaticParams', 'metadata', 'generateMetadata', 'viewport', 'generateViewport', ] : []), ...(isUsingRemix || isUsingReactRouter ? [ 'meta', 'links', 'headers', 'loader', 'action', 'clientLoader', 'clientAction', 'handle', 'shouldRevalidate', ] : []), ], }, ], ...overrides, }, }, ...(isTypeAware ? [ { name: '@bfra.me/react/type-aware-rules', files: typeAware.files, ignores: typeAware.ignores, rules: { ...ReactTypeAwareRules, }, }, ] : []), ] }, async missingList => fallback(missingList, {files, languageOptions: {parser: anyParser}}), ) }