UNPKG

@bfra.me/eslint-config

Version:
280 lines (267 loc) 10.7 kB
import type {Config} from '../config' import type { Flatten, OptionsFiles, OptionsOverrides, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes, } from '../options' import process from 'node:process' import {GLOB_MARKDOWN, 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 TypeAwareRules: Config['rules'] = { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/dot-notation': ['error', {allowKeywords: true}], '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', '@typescript-eslint/no-implied-eval': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-unnecessary-qualifier': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-readonly': 'error', '@typescript-eslint/prefer-readonly-parameter-types': 'off', '@typescript-eslint/prefer-string-starts-ends-with': 'error', '@typescript-eslint/promise-function-async': 'error', '@typescript-eslint/require-array-sort-compare': 'error', '@typescript-eslint/restrict-plus-operands': 'error', '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/return-await': ['error', 'in-try-catch'], '@typescript-eslint/strict-boolean-expressions': [ 'error', {allowNullableBoolean: true, allowNullableObject: true}, ], '@typescript-eslint/switch-exhaustiveness-check': 'error', '@typescript-eslint/unbound-method': 'error', 'dot-notation': 'off', 'no-implied-eval': 'off', } /** * Represents the options for configuring the TypeScript ESLint configuration. * This type is a union of several other option types, including: * - {@link OptionsFiles}: Options related to the files to be linted * - {@link OptionsOverrides}: Options for overriding the default configuration * - {@link OptionsTypeScriptParserOptions}: Options for the TypeScript parser * - {@link OptionsTypeScriptWithTypes}: Options related to type-aware linting */ export type TypeScriptOptions = Flatten< OptionsFiles & OptionsOverrides & OptionsTypeScriptParserOptions & OptionsTypeScriptWithTypes > /** * Generates a TypeScript ESLint configuration based on the provided options. * * The configuration includes: * - TypeScript-specific plugins and rules * - Options for type-aware linting (if a tsconfig.json file is provided) * - Overrides for the default configuration * * @param options - The options for configuring the TypeScript ESLint configuration. * @returns An array of ESLint configurations. */ export async function typescript(options: TypeScriptOptions = {}): Promise<Config[]> { const {overrides = {}, parserOptions = {}, typeAware = {overrides: {}}} = options const files = options.files ?? [GLOB_TS, GLOB_TSX] const typeAwareFiles = typeAware.files ?? [GLOB_TS, GLOB_TSX] const typeAwareIgnores = typeAware.ignores ?? [`${GLOB_MARKDOWN}/**`, '**/*.astro/*.ts'] const tsconfigPath = options.tsconfigPath ?? undefined const isTypeAware = Boolean(tsconfigPath) return requireOf( ['typescript-eslint'], async () => { const tselint = await interopDefault(import('typescript-eslint')) const generateTsConfig = ( kind: 'type-aware' | 'default', files: NonNullable<Config['files']>, ignores?: Config['ignores'], ): Config => ({ name: `@bfra.me/typescript/${kind === 'type-aware' ? 'type-aware-' : ''}parser`, files, ...(ignores ? {ignores} : {}), languageOptions: { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment parser: tselint.parser as any, parserOptions: { sourceType: 'module', ...(kind === 'type-aware' ? { projectService: { allowDefaultProject: ['./*.js'], defaultProject: tsconfigPath, }, tsconfigRootDir: process.cwd(), } : {}), ...parserOptions, }, }, }) return [ { name: '@bfra.me/typescript/plugins', // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment plugins: {'@typescript-eslint': tselint.plugin as any}, }, ...(isTypeAware ? [ generateTsConfig('default', files, typeAwareFiles), generateTsConfig('type-aware', typeAwareFiles, typeAwareIgnores), ] : [generateTsConfig('default', files)]), { name: '@bfra.me/typescript/rules', files, rules: { ...tselint.configs.eslintRecommended.rules, ...tselint.configs.strict .map(config => config.rules) .reduce((acc, rules) => ({...acc, ...rules}), {}), '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/array-type': 'error', '@typescript-eslint/ban-ts-comment': [ 'error', {'ts-expect-error': 'allow-with-description'}, ], '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/consistent-type-imports': [ 'error', {disallowTypeAnnotations: false, fixStyle: 'inline-type-imports'}, ], '@typescript-eslint/explicit-function-return-type': [ 'error', { allowExpressions: true, allowFunctionsWithoutTypeParameters: true, allowHigherOrderFunctions: true, allowIIFEs: true, }, ], '@typescript-eslint/explicit-member-accessibility': [ 'error', { accessibility: 'no-public', }, ], '@typescript-eslint/member-ordering': [ 'error', { default: [ 'signature', 'public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field', 'public-constructor', 'protected-constructor', 'private-constructor', 'public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method', ], }, ], '@typescript-eslint/method-signature-style': ['error', 'property'], '@typescript-eslint/naming-convention': [ 'error', { format: ['camelCase', 'PascalCase', 'UPPER_CASE'], selector: 'variableLike', }, { format: ['PascalCase'], selector: 'typeLike', }, { format: ['camelCase'], leadingUnderscore: 'allow', selector: 'parameter', }, { format: ['PascalCase'], selector: 'class', }, { custom: { match: false, regex: '^I[A-Z]', }, format: ['PascalCase'], selector: 'interface', }, ], '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-dupe-class-members': 'error', '@typescript-eslint/no-dynamic-delete': 'off', '@typescript-eslint/no-empty-object-type': ['error', {allowInterfaces: 'always'}], '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-extraneous-class': 'error', '@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/no-invalid-this': 'error', '@typescript-eslint/no-invalid-void-type': 'off', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/no-redeclare': ['error', {builtinGlobals: false}], '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-unused-expressions': [ 'error', { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true, }, ], // This is reported by `unused-imports/no-unused-vars` '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-use-before-define': [ 'error', {classes: false, functions: false, variables: true}, ], '@typescript-eslint/no-useless-constructor': 'off', '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/prefer-for-of': 'warn', '@typescript-eslint/prefer-function-type': 'warn', '@typescript-eslint/triple-slash-reference': 'off', '@typescript-eslint/unified-signatures': 'off', 'no-dupe-class-members': 'off', 'no-invalid-this': 'off', 'no-redeclare': 'off', 'no-use-before-define': 'off', 'no-useless-constructor': 'off', ...overrides, }, }, ...(isTypeAware ? [ { name: '@bfra.me/typescript/type-aware-rules', files: typeAwareFiles, ignores: typeAwareIgnores, rules: {...TypeAwareRules, ...typeAware?.overrides}, }, ] : []), ] }, async missingList => fallback(missingList, { files, languageOptions: { parser: anyParser, }, }), ) }