eslint-config-get-off-my-lawn
Version:
A highly opinionated, sharable config of ESLint rules to produce beautiful, readable JavaScript.
371 lines (354 loc) • 16.5 kB
JavaScript
const {cwd} = require('process');
const semver = require('semver');
const imprt = require('eslint-plugin-import/config/typescript');
const eslint = require('./eslint');
const {jest: jestDependency, prettier, react, typescript} = require('./utils/dependencies');
const {jestConfig, packageJson, tsConfig} = require('./utils/files/contents');
const prettierRules = prettier ? require('./prettier').rules : {};
const modifiedRulesToSupportTypeScript = {
...imprt.rules,
'brace-style': 'off',
'comma-dangle': 'off',
'comma-spacing': 'off',
'default-param-last': 'off',
'dot-notation': 'off',
'func-call-spacing': 'off',
// https://github.com/typescript-eslint/typescript-eslint/blob/main/docs/linting/TROUBLESHOOTING.md#eslint-plugin-import
'import/default': 'off',
'import/named': 'off',
'import/namespace': 'off',
'import/no-named-as-default-member': 'off',
indent: 'off',
'keyword-spacing': 'off',
'no-array-constructor': 'off',
'no-dupe-class-members': 'off',
'no-duplicate-imports': 'off',
'no-empty-function': 'off',
'no-extra-parens': 'off',
'no-extra-semi': 'off',
'no-implied-eval': 'off',
'no-invalid-this': 'off',
'no-loop-func': 'off',
'no-loss-of-precision': 'off',
'no-redeclare': 'off',
'no-restricted-imports': 'off',
'no-return-await': 'off',
'no-shadow': 'off',
'no-throw-literal': 'off',
// https://github.com/typescript-eslint/typescript-eslint/blob/main/docs/linting/TROUBLESHOOTING.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',
'no-unused-expressions': 'off',
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'no-useless-constructor': 'off',
'object-curly-spacing': 'off',
'padding-line-between-statements': 'off',
quotes: 'off',
'react/jsx-filename-extension': [
'error',
{
extensions: ['.jsx', '.tsx'],
},
],
'require-await': 'off',
semi: 'off',
'space-before-function-paren': 'off',
'space-infix-ops': 'off',
// Adds support for triple-slash directives.
'spaced-comment': [
'error',
'always',
{
markers: ['/'],
},
],
};
const config = {
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
project: ['./**/tsconfig.json'],
tsconfigRootDir: cwd(),
},
plugins: ['@typescript-eslint'],
rules: {
...modifiedRulesToSupportTypeScript,
'@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/ban-tslint-comment': 'error',
'@typescript-eslint/brace-style': eslint.rules['brace-style'],
'@typescript-eslint/class-literal-property-style': ['error', 'fields'],
'@typescript-eslint/comma-dangle': eslint.rules['comma-dangle'],
'@typescript-eslint/comma-spacing': eslint.rules['comma-spacing'],
'@typescript-eslint/consistent-indexed-object-style': ['error', 'record'],
'@typescript-eslint/consistent-type-assertions': [
'error',
{
assertionStyle: 'as',
objectLiteralTypeAssertions: 'never',
},
],
// https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c
// https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
'@typescript-eslint/consistent-type-exports': [
'error',
{
fixMixedExportsWithInlineTypeSpecifier: false,
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
},
],
'@typescript-eslint/default-param-last': eslint.rules['default-param-last'],
'@typescript-eslint/dot-notation': eslint.rules['dot-notation'],
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-member-accessibility': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/func-call-spacing': eslint.rules['func-call-spacing'],
'@typescript-eslint/indent': eslint.rules.indent,
'@typescript-eslint/keyword-spacing': eslint.rules['keyword-spacing'],
'@typescript-eslint/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'semi',
requireLast: true,
},
multilineDetection: 'brackets',
singleline: {
delimiter: 'semi',
requireLast: true,
},
},
],
'@typescript-eslint/method-signature-style': 'error',
'@typescript-eslint/no-array-constructor': eslint.rules['no-array-constructor'],
'@typescript-eslint/no-base-to-string': 'error',
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
'@typescript-eslint/no-confusing-void-expression': 'error',
'@typescript-eslint/no-dupe-class-members': eslint.rules['no-dupe-class-members'],
'@typescript-eslint/no-duplicate-imports': eslint.rules['no-duplicate-imports'],
'@typescript-eslint/no-empty-function': eslint.rules['no-empty-function'],
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extra-non-null-assertion': 'error',
'@typescript-eslint/no-extra-parens': eslint.rules['no-extra-parens'],
'@typescript-eslint/no-extra-semi': eslint.rules['no-extra-semi'],
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-floating-promises': [
'error',
{
ignoreIIFE: true,
ignoreVoid: true,
},
],
'@typescript-eslint/no-for-in-array': 'error',
'@typescript-eslint/no-implied-eval': eslint.rules['no-implied-eval'],
'@typescript-eslint/no-inferrable-types': 'error',
'@typescript-eslint/no-invalid-this': eslint.rules['no-invalid-this'],
'@typescript-eslint/no-invalid-void-type': 'error',
'@typescript-eslint/no-loop-func': eslint.rules['no-loop-func'],
'@typescript-eslint/no-loss-of-precision': eslint.rules['no-loss-of-precision'],
'@typescript-eslint/no-meaningless-void-operator': [
'error',
{
checkNever: true,
},
],
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-misused-promises': [
'error',
{
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',
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-parameter-properties': 'error',
'@typescript-eslint/no-redeclare': [
'error',
{
...eslint.rules['no-redeclare'][1],
ignoreDeclarationMerge: false,
},
],
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-restricted-imports': [
'error',
{
paths: eslint.rules['no-restricted-imports'][1].paths.map((path) => ({
...path,
allowTypeImports: true,
})),
},
],
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-throw-literal': [
'error',
{
allowThrowingAny: false,
allowThrowingUnknown: false,
},
],
'@typescript-eslint/no-unnecessary-boolean-literal-compare': [
'error',
{
allowComparingNullableBooleansToFalse: true,
allowComparingNullableBooleansToTrue: false,
},
],
'@typescript-eslint/no-unnecessary-condition': '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',
'@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/no-unused-expressions': eslint.rules['no-unused-expressions'],
'@typescript-eslint/no-unused-vars': eslint.rules['no-unused-vars'],
'@typescript-eslint/no-use-before-define': [
'error',
{
enums: true,
ignoreTypeReferences: false,
typedefs: true,
},
],
'@typescript-eslint/no-useless-constructor': eslint.rules['no-useless-constructor'],
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
'@typescript-eslint/object-curly-spacing': eslint.rules['object-curly-spacing'],
'@typescript-eslint/padding-line-between-statements': [
...eslint.rules['padding-line-between-statements'],
{
blankLine: 'always',
next: ['interface', 'type'],
prev: '*',
},
],
'@typescript-eslint/prefer-as-const': 'error',
'@typescript-eslint/prefer-enum-initializers': 'error',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
// Possible conflict with `unicorn/prefer-includes`?
'@typescript-eslint/prefer-includes': 'error',
'@typescript-eslint/prefer-literal-enum-member': 'error',
'@typescript-eslint/prefer-namespace-keyword': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'@typescript-eslint/prefer-readonly': 'error',
'@typescript-eslint/prefer-reduce-type-parameter': 'error',
'@typescript-eslint/prefer-return-this-type': 'error',
// Possible conflict with `unicorn/prefer-string-starts-ends-with`?
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
'@typescript-eslint/prefer-ts-expect-error': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/quotes': eslint.rules.quotes,
'@typescript-eslint/require-array-sort-compare': [
'error',
{
ignoreStringArrays: true,
},
],
'@typescript-eslint/require-await': eslint.rules['require-await'],
'@typescript-eslint/restrict-plus-operands': [
'error',
{
checkCompoundAssignments: true,
},
],
'@typescript-eslint/restrict-template-expressions': 'error',
'@typescript-eslint/return-await': 'error',
'@typescript-eslint/semi': eslint.rules.semi,
'@typescript-eslint/sort-type-union-intersection-members': 'error',
'@typescript-eslint/space-before-function-paren': eslint.rules['space-before-function-paren'],
'@typescript-eslint/space-infix-ops': eslint.rules['space-infix-ops'],
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/unified-signatures': 'error',
// Since rules from `overrides` come after the base rules specified in the ESLint
// config, we need to reapply the prettier rules.
...prettierRules,
},
settings: {
...imprt.settings,
// https://github.com/alexgorbatchev/eslint-import-resolver-typescript
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
},
};
if (!react) {
// Need to remove if React isn't used, otherwise ESLint will error.
delete config.rules['react/jsx-filename-extension'];
}
if (typescript && semver.satisfies(typescript, '<3.4.0')) {
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-as-const.md#when-not-to-use-it
delete config.rules['@typescript-eslint/prefer-as-const'];
}
if (typescript && semver.satisfies(typescript, '<3.7.0')) {
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md#when-not-to-use-it
delete config.rules['@typescript-eslint/prefer-nullish-coalescing'];
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-optional-chain.md#when-not-to-use-it
delete config.rules['@typescript-eslint/prefer-optional-chain'];
}
if (typescript && semver.satisfies(typescript, '<3.8.0')) {
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/consistent-type-exports.md#when-not-to-use-it
delete config.rules['@typescript-eslint/consistent-type-exports'];
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/consistent-type-imports.md#when-not-to-use-it
delete config.rules['@typescript-eslint/consistent-type-imports'];
}
if (typescript && semver.satisfies(typescript, '<3.9.0')) {
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md#when-not-to-use-it
delete config.rules['@typescript-eslint/prefer-ts-expect-error'];
}
if (!tsConfig.compilerOptions?.strict && !tsConfig.compilerOptions?.strictNullChecks) {
// Warning: Do not use this rule when `strictNullChecks` is disabled.
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md#rule-details
delete config.rules['@typescript-eslint/no-unnecessary-boolean-literal-compare'];
}
if (packageJson.type === 'module') {
// Need to disable this rule for TypeScript because `.ts` / `.tsx`
// files actually require `.js` extensions in ESM and `eslint-plugin-import`
// does not support this.
// https://github.com/import-js/eslint-plugin-import/issues/2104
// https://github.com/import-js/eslint-plugin-import/issues/2111
// https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1194
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
config.rules['import/extensions'] = 'off';
}
const overrides = [config];
if (jestDependency) {
const testMatch = jestConfig?.testMatch?.map((path) => path.replace('<rootDir>', '.')) || [
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
];
overrides.push({
files: testMatch,
rules: {
// If you're working with `jest`, you can use eslint-plugin-jest's version of this rule
// to lint your test files, which knows when it's ok to pass an unbound method to
// `expect` calls.
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/unbound-method.md
'@typescript-eslint/unbound-method': 'off',
// Purposely not enabling `jest/unbound-method` because it will complain about third-party packages that improperly use
// `this` and there's nothing you can do to fix it yourself.
},
});
}
module.exports = typescript ? {overrides} : {};