UNPKG

eslint-config-xo-typescript

Version:

ESLint shareable config for TypeScript to be used with eslint-config-xo

762 lines (709 loc) 22.1 kB
import typescriptEslint from 'typescript-eslint'; import jsConfig from 'eslint-config-xo'; import stylistic from '@stylistic/eslint-plugin'; const getNamingConventionRule = ({isTsx}) => ({ '@typescript-eslint/naming-convention': [ 'error', { /// selector: ['variableLike', 'memberLike', 'property', 'method'], // Note: Leaving out `parameter` and `typeProperty` because of the mentioned known issues. // Note: We are intentionally leaving out `enumMember` as it's usually pascal-case or upper-snake-case. selector: ['variable', 'function', 'classProperty', 'objectLiteralProperty', 'parameterProperty', 'classMethod', 'objectLiteralMethod', 'typeMethod', 'accessor'], format: [ 'strictCamelCase', isTsx && 'StrictPascalCase', ].filter(Boolean), // We allow double underscore because of GraphQL type names and some React names. leadingUnderscore: 'allowSingleOrDouble', trailingUnderscore: 'allow', // Ignore `{'Retry-After': retryAfter}` type properties. filter: { regex: '[- ]', match: false } }, { selector: 'typeLike', format: [ 'StrictPascalCase' ] }, { selector: 'variable', types: [ 'boolean' ], format: [ 'StrictPascalCase' ], prefix: [ 'is', 'has', 'can', 'should', 'will', 'did' ] }, { // Interface name should not be prefixed with `I`. selector: 'interface', filter: /^(?!I)[A-Z]/.source, format: [ 'StrictPascalCase' ] }, { // Type parameter name should either be `T` or a descriptive name. selector: 'typeParameter', filter: /^T$|^[A-Z][a-zA-Z]+$/.source, format: [ 'StrictPascalCase' ] }, // Allow these in non-camel-case when quoted. { selector: [ 'classProperty', 'objectLiteralProperty' ], format: null, modifiers: [ 'requiresQuotes' ] } ] }); const rules = { '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/array-type': [ 'error', { default: 'array-simple' } ], '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/ban-ts-comment': [ 'error', { 'ts-expect-error': 'allow-with-description', minimumDescriptionLength: 4 } ], '@typescript-eslint/ban-tslint-comment': 'error', '@typescript-eslint/no-restricted-types': [ 'error', { types: { object: { message: 'The `object` type is hard to use. Use `Record<string, unknown>` instead. See: https://github.com/typescript-eslint/typescript-eslint/pull/848', fixWith: 'Record<string, unknown>' }, null: { message: 'Use `undefined` instead. See: https://github.com/sindresorhus/meta/issues/7', fixWith: 'undefined' }, Buffer: { message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', suggest: [ 'Uint8Array' ] }, '[]': 'Don\'t use the empty array type `[]`. It only allows empty arrays. Use `SomeType[]` instead.', '[[]]': 'Don\'t use `[[]]`. It only allows an array with a single element which is an empty array. Use `SomeType[][]` instead.', '[[[]]]': 'Don\'t use `[[[]]]`. Use `SomeType[][][]` instead.', '[[[[]]]]': 'ur drunk 🤡', '[[[[[]]]]]': '🦄💥' } } ], '@typescript-eslint/class-literal-property-style': [ 'error', 'getters' ], '@typescript-eslint/consistent-generic-constructors': [ 'error', 'constructor' ], '@typescript-eslint/consistent-indexed-object-style': 'error', 'brace-style': 'off', '@stylistic/brace-style': [ 'error', '1tbs', { allowSingleLine: false } ], 'comma-dangle': 'off', '@stylistic/comma-dangle': [ 'error', 'always-multiline' ], 'comma-spacing': 'off', '@stylistic/comma-spacing': [ 'error', { before: false, after: true } ], 'default-param-last': 'off', '@typescript-eslint/default-param-last': 'error', 'dot-notation': 'off', '@typescript-eslint/dot-notation': 'error', '@typescript-eslint/consistent-type-assertions': [ 'error', { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' } ], '@typescript-eslint/consistent-type-definitions': [ 'error', 'type' ], '@typescript-eslint/consistent-type-exports': [ 'error', { fixMixedExportsWithInlineTypeSpecifier: true } ], '@typescript-eslint/consistent-type-imports': [ 'error', { fixStyle: 'inline-type-imports' } ], // Disabled because it's too annoying. Enable it when it's more mature, smarter, and more flexible. // https://github.com/typescript-eslint/typescript-eslint/search?q=%22explicit-function-return-type%22&state=open&type=Issues // '@typescript-eslint/explicit-function-return-type': [ // 'error', // { // allowExpressions: true, // allowTypedFunctionExpressions: true, // allowHigherOrderFunctions: true, // allowConciseArrowFunctionExpressionsStartingWithVoid: false, // allowIIFE: true // } // ], // TODO: This rule should be removed if/when we enable `@typescript-eslint/explicit-function-return-type`. // Disabled for now as it has too many false-positives. // https://github.com/typescript-eslint/typescript-eslint/search?q=%22explicit-module-boundary-types%22&state=open&type=Issues // '@typescript-eslint/explicit-module-boundary-types': [ // 'error', // { // allowTypedFunctionExpressions: true, // allowHigherOrderFunctions: true, // allowDirectConstAssertionInArrowFunctions: true, // shouldTrackReferences: true // } // ], 'func-call-spacing': 'off', '@stylistic/func-call-spacing': [ 'error', 'never' ], indent: 'off', '@stylistic/indent': [ 'error', 'tab', { SwitchCase: 1 } ], 'keyword-spacing': 'off', '@stylistic/keyword-spacing': 'error', 'lines-between-class-members': 'off', '@stylistic/lines-between-class-members': [ 'error', 'always', { // Workaround to allow class fields to not have lines between them. // TODO: Get ESLint to add an option to ignore class fields. exceptAfterSingleLine: true } ], '@stylistic/member-delimiter-style': [ 'error', { multiline: { delimiter: 'semi', requireLast: true }, singleline: { delimiter: 'semi', requireLast: false } } ], '@typescript-eslint/member-ordering': [ 'error', { default: [ 'signature', 'public-static-field', 'public-static-method', 'protected-static-field', 'protected-static-method', 'private-static-field', 'private-static-method', 'static-field', 'static-method', 'public-decorated-field', 'public-instance-field', 'public-abstract-field', 'public-field', 'protected-decorated-field', 'protected-instance-field', 'protected-abstract-field', 'protected-field', 'private-decorated-field', 'private-instance-field', 'private-field', 'instance-field', 'abstract-field', 'decorated-field', 'field', 'public-constructor', 'protected-constructor', 'private-constructor', 'constructor', 'public-decorated-method', 'public-instance-method', 'public-abstract-method', 'public-method', 'protected-decorated-method', 'protected-instance-method', 'protected-abstract-method', 'protected-method', 'private-decorated-method', 'private-instance-method', 'private-method', 'instance-method', 'abstract-method', 'decorated-method', 'method' ] } ], // Disabled for now as it causes too many weird TypeScript issues. I'm not sure whether the problems are caused by bugs in TS or problems in my types. // TODO: Try to re-enable this again in 2026. // '@typescript-eslint/method-signature-style': 'error', // We use `@typescript-eslint/naming-convention` in favor of `camelcase`. camelcase: 'off', // Known issues: // - https://github.com/typescript-eslint/typescript-eslint/issues/1485 // - https://github.com/typescript-eslint/typescript-eslint/issues/1484 // TODO: Prevent `_` prefix on private fields when TypeScript 3.8 is out. ...getNamingConventionRule({isTsx: false}), '@typescript-eslint/no-base-to-string': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-array-delete': 'error', 'no-dupe-class-members': 'off', '@typescript-eslint/no-dupe-class-members': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', '@typescript-eslint/no-deprecated': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-dynamic-delete': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-interface': [ 'error', { allowSingleExtends: true } ], '@typescript-eslint/no-empty-object-type': 'error', // TODO: Try to enable this again in 2025. // Disabled for now. This is a great rule. It's just that TypeScript is not good enough yet to not use `any` in many places. // For example: https://github.com/sindresorhus/refined-github/pull/2391#discussion_r318995182 // '@typescript-eslint/no-explicit-any': [ // 'error', // { // fixToUnknown: true, // ignoreRestArgs: true // } // ], '@typescript-eslint/no-extra-non-null-assertion': 'error', // Disabled because it's buggy. It transforms `...(personalToken ? {Authorization: `token ${personalToken}`} : {})` into `...personalToken ? {Authorization: `token ${personalToken}`} : {}` which is not valid. // https://github.com/typescript-eslint/typescript-eslint/search?q=%22no-extra-parens%22&state=open&type=Issues 'no-extra-parens': 'off', // '@typescript-eslint/no-extra-parens': [ // 'error', // 'all', // { // conditionalAssign: false, // nestedBinaryExpressions: false, // ignoreJSX: 'multi-line', // nestedConditionalExpressions: false, // } // ], 'no-extra-semi': 'off', '@stylistic/no-extra-semi': 'error', 'no-loop-func': 'off', '@typescript-eslint/no-loop-func': 'error', '@typescript-eslint/no-extraneous-class': [ 'error', { allowConstructorOnly: false, allowEmpty: false, allowStaticOnly: false, allowWithDecorator: true } ], 'no-void': [ 'error', { allowAsStatement: true // To allow `ignoreVoid` in `@typescript-eslint/no-floating-promises` } ], '@typescript-eslint/no-floating-promises': [ 'error', { checkThenables: true, ignoreVoid: true, // Prepend a function call with `void` to mark it as not needing to be await'ed, which silences this rule. ignoreIIFE: true } ], '@typescript-eslint/no-for-in-array': 'error', '@typescript-eslint/no-inferrable-types': 'error', // Disabled for now as it has too many false-positives. // '@typescript-eslint/no-invalid-void-type': 'error', '@typescript-eslint/no-meaningless-void-operator': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-misused-promises': [ 'error', { checksConditionals: true, // TODO: I really want this to be `true`, but it makes it inconvenient to use // async functions as event handlers... I need to find a good way to handle that. // https://github.com/sindresorhus/refined-github/pull/2391#discussion_r318990466 checksVoidReturn: false } ], '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', // Disabled for now. There are just too many places where you need to use it because of incorrect types, for example, the Node.js types. // TODO: Try to enable this again in 2023. // '@typescript-eslint/no-non-null-assertion': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': [ 'error', { paths: [ 'error', 'domain', 'freelist', 'smalloc', 'punycode', 'sys', 'querystring', 'colors' ], }, ], // The rule is buggy and keeps inferring `any` for types that are not `any`. Just a lot of false-positives. // '@typescript-eslint/no-redundant-type-constituents': 'error', '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': [ 'error', { allowDestructuring: true } ], 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': [ 'error', { // This should ideally be `false`, but it makes rethrowing errors inconvenient. There should be a separate `allowRethrowingUnknown` option. allowThrowingUnknown: true, allowThrowingAny: false } ], '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', // `no-unnecessary-condition` is essentially a stricter version of `no-constant-condition`, but that isn't currently enabled 'no-constant-condition': 'error', // TODO: Try to enable this again in 2025 *if* the following are resolved: // - https://github.com/microsoft/TypeScript/issues/36393 // - The rule needs a way to ignore runtime type-checks: https://github.com/sindresorhus/refined-github/pull/3168 // - Run the rule on https://github.com/sindresorhus/refined-github and ensure there are no false-positives // // Also related: https://github.com/typescript-eslint/typescript-eslint/issues/1798 // Also disable `no-constant-condition` when this is enabled // '@typescript-eslint/no-unnecessary-condition': [ // 'error', // { // checkTypePredicates: true // } // ], '@typescript-eslint/no-unnecessary-parameter-property-assignment': 'error', '@typescript-eslint/no-unnecessary-qualifier': 'error', '@typescript-eslint/no-unnecessary-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', // TODO: Currently documented as flawed. Enable it if fixed in 2026. // '@typescript-eslint/no-unnecessary-type-conversion': 'error', // TODO: Enable at some point. Currently disabled because it's marked as unstable. // https://typescript-eslint.io/rules/no-unnecessary-type-parameters/ // '@typescript-eslint/no-unnecessary-type-parameters': 'error', '@typescript-eslint/no-unsafe-argument': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-declaration-merging': 'error', '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-type-assertion': 'error', '@typescript-eslint/no-useless-empty-export': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-vars': 'off', // NOTE: TypeScript already catches unused variables. Let us know if there's something this rule catches that TypeScript does not. // '@typescript-eslint/no-unused-vars': [ // 'error', // { // vars: 'all', // args: 'after-used', // ignoreRestSiblings: true, // argsIgnorePattern: /^_/.source, // caughtErrors: 'all', // caughtErrorsIgnorePattern: /^_$/.source // } // ], 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', 'object-curly-spacing': 'off', '@stylistic/object-curly-spacing': [ 'error', 'never' ], 'padding-line-between-statements': 'off', '@stylistic/padding-line-between-statements': [ 'error', { blankLine: 'always', prev: 'multiline-block-like', next: '*' } ], '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', '@typescript-eslint/parameter-properties': [ 'error', { prefer: 'parameter-property' } ], '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-find': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/prefer-nullish-coalescing': [ 'error', { ignoreTernaryTests: false, ignoreConditionalTests: false, ignoreMixedLogicalExpressions: false } ], '@typescript-eslint/prefer-optional-chain': 'error', 'prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-readonly': 'error', // TODO: Try to enable this again in 2023. // Disabled for now as it's too annoying and will cause too much churn. It also has bugs: https://github.com/typescript-eslint/typescript-eslint/search?q=%22prefer-readonly-parameter-types%22+is:issue&state=open&type=issues // '@typescript-eslint/prefer-readonly-parameter-types': [ // 'error', // { // checkParameterProperties: true, // ignoreInferredTypes: true // } // ], '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', '@typescript-eslint/promise-function-async': 'error', '@typescript-eslint/related-getter-setter-pairs': 'error', quotes: 'off', '@stylistic/quotes': [ 'error', 'single' ], '@typescript-eslint/restrict-plus-operands': [ 'error', { allowAny: false } ], '@typescript-eslint/restrict-template-expressions': [ 'error', { allowNumber: true } ], '@typescript-eslint/return-await': 'error', '@typescript-eslint/require-array-sort-compare': [ 'error', { ignoreStringArrays: true } ], // Disabled for now. It's too buggy. It fails to detect when try/catch is used, await inside blocks, etc. It's also common to have async functions without await for various reasons. // 'require-await': 'off', // '@typescript-eslint/require-await': 'error', 'space-before-function-paren': 'off', '@stylistic/space-before-function-paren': [ 'error', { anonymous: 'always', named: 'never', asyncArrow: 'always' } ], 'space-infix-ops': 'off', '@stylistic/space-infix-ops': 'error', semi: 'off', '@stylistic/semi': [ 'error', 'always' ], 'space-before-blocks': 'off', '@stylistic/space-before-blocks': [ 'error', 'always' ], // TODO: Reconsider enabling it again in 2023. // NOTE: The rule was complete redone in typescript-eslint v3, so this config needs to be changed before this is enabled. // Disabled for now as it's too strict. // Relevant discussion: https://github.com/sindresorhus/refined-github/pull/2521#discussion_r343013852 // '@typescript-eslint/strict-boolean-expressions': [ // 'error', // { // allowNullable: true, // allowSafe: true // } // ], 'default-case': 'off', // It conflicts with `@typescript-eslint/switch-exhaustiveness-check`. It would still be nice to have this rule for non-exhaustive switches though. '@typescript-eslint/switch-exhaustiveness-check': [ 'error', { allowDefaultCaseForExhaustiveSwitch: false, requireDefaultForNonUnion: true } ], '@typescript-eslint/triple-slash-reference': [ 'error', { path: 'never', types: 'never', lib: 'never' } ], '@stylistic/type-annotation-spacing': 'error', // Disabled as it crashes on most code. // https://github.com/typescript-eslint/typescript-eslint/search?q=%22unbound-method%22&state=open&type=Issues // '@typescript-eslint/unbound-method': [ // 'error', // { // ignoreStatic: true // } // ], '@typescript-eslint/prefer-regexp-exec': 'error', '@typescript-eslint/prefer-return-this-type': 'error', '@typescript-eslint/unified-signatures': [ 'error', { ignoreDifferentlyNamedParameters: true } ], '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', '@stylistic/type-generic-spacing': 'error', '@stylistic/type-named-tuple-spacing': 'error', // Disabled per typescript-eslint recommendation: https://github.com/typescript-eslint/typescript-eslint/blob/e26e43ffba96f6d46198b22f1c8dd5c814db2652/docs/getting-started/linting/FAQ.md#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors 'no-undef': 'off', // TypeScript might have features not supported in a specific Node.js version. 'node/no-unsupported-features/es-syntax': 'off', 'node/no-unsupported-features/es-builtins': 'off', // Even though we already use `@typescript-eslint/no-restricted-types`, `unicorn/no-null` is useful for catching literal usage. // https://github.com/xojs/eslint-config-xo-typescript/issues/69 // 'unicorn/no-null': 'off', // The rule is buggy with TS and it's not needed as TS already enforces valid imports and references at compile-time. 'import/namespace': 'off', // TypeScript already does a better job at this. 'import/named': 'off', // `import/no-duplicates` works better with TypeScript. 'no-duplicate-imports': 'off' }; export default typescriptEslint.config( ...jsConfig, { plugins: { '@typescript-eslint': typescriptEslint.plugin, '@stylistic': stylistic, }, languageOptions: { sourceType: 'module', parser: typescriptEslint.parser, parserOptions: { projectService: true, warnOnUnsupportedTypeScriptVersion: false, ecmaFeatures: { jsx: true } } }, // Disabled temporarily. // settings: { // 'import/resolver': { // node: { // extensions: [ // '.js', // '.jsx', // '.ts', // '.tsx' // ] // } // }, // 'import/parsers': { // [require.resolve('@typescript-eslint/parser')]: [ // '.ts', // '.tsx' // ] // } // }, rules }, { files: [ '**/*.d.ts' ], rules: { '@typescript-eslint/no-unused-vars': 'off' } }, { files: [ '**/*.test-d.ts' ], rules: { '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-confusing-void-expression': 'off' // Conflicts with `expectError` assertion. } }, { files: [ '**/*.tsx' ], rules: { ...getNamingConventionRule({isTsx: true}) } } );