@foray1010/eslint-config
Version:
It aims at providing a general eslint config for:
361 lines (352 loc) • 14.2 kB
JavaScript
import js from '@eslint/js'
import eslintPluginEslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
import { hasDep, isESM } from '@foray1010/common-presets-utils'
import eslintPluginFoxglove from '@foxglove/eslint-plugin'
import { defineConfig } from 'eslint/config'
import eslintPluginImportX from 'eslint-plugin-import-x'
import eslintPluginJest from 'eslint-plugin-jest'
import eslintPluginRegexp from 'eslint-plugin-regexp'
import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import globals from 'globals'
import {
testFileGlobs,
typeScriptFileGlobs,
typeScriptTestFileGlobs,
} from '../constants.mjs'
async function generateTypeScriptConfig() {
// typescript plugins are depended on `typescript` package
if (!hasDep('typescript')) return []
const tseslint = (await import('typescript-eslint')).default
return defineConfig(
{
files: typeScriptFileGlobs,
extends: [
tseslint.configs.eslintRecommended,
tseslint.configs.recommendedTypeChecked,
eslintPluginImportX.configs['typescript'],
esmConfig,
],
plugins: {
'@foxglove': eslintPluginFoxglove,
},
languageOptions: {
parserOptions: {
// faster linting on cli
// https://github.com/typescript-eslint/typescript-eslint/issues/3528
// turned off because @typescript-eslint/no-unsafe-* rules will output wrong errors
// allowAutomaticSingleRunInference: true,
project: ['./tsconfig*.json', './packages/*/tsconfig*.json'],
},
},
rules: {
// promote named parameters by disallowing boolean parameters (boolean trap)
'@foxglove/no-boolean-parameters': 'error',
// separate type exports which allow certain optimizations within compilers
'@typescript-eslint/consistent-type-exports': [
'error',
{
fixMixedExportsWithInlineTypeSpecifier: true,
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
// separate type imports which allow certain optimizations within compilers
prefer: 'type-imports',
// Use inline type imports to avoid duplicate imports, but can be overridden by @typescript-eslint/no-import-type-side-effects
fixStyle: 'inline-type-imports',
},
],
// disable the base rule as it can report incorrect errors, use @typescript-eslint/dot-notation instead
'dot-notation': 'off',
// only allow indexed syntax (e.g. `obj['key']`) for accessing undefined fields
'@typescript-eslint/dot-notation': [
'error',
{
allowIndexSignaturePropertyAccess: true,
},
],
// encourage to use private accessibility modifier
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'explicit',
},
],
// do not allow usage of deprecated code
'@typescript-eslint/no-deprecated': 'error',
// TypeScript team suggests to use `<T extends {}>` https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#unconstrained-generics-no-longer-assignable-to
'@typescript-eslint/no-empty-object-type': 'off',
// when using typescript 5.0 with verbatimModuleSyntax flag on, compiler will not remove import statements with only inline type imports which lead to side effects
'@typescript-eslint/no-import-type-side-effects': 'error',
// enforce correct usage of `void` type
'@typescript-eslint/no-invalid-void-type': 'error',
// allow using async function as event handler in frontend
'@typescript-eslint/no-misused-promises': [
'error',
{ checksVoidReturn: false },
],
// do not block functions referring to other functions
'@typescript-eslint/no-use-before-define': [
'error',
{
/* options from eslint/no-use-before-define */
// allow use function before defined as they could be hoisted
functions: false,
classes: true,
variables: true,
/* options from @typescript-eslint/no-use-before-define */
// confusing option, it will disable `typedefs`
ignoreTypeReferences: false,
// tsc allows types to be used before define
typedefs: false,
},
],
// make sure functions which return a promise will just return a rejected promise instead of throwing an error
'@typescript-eslint/promise-function-async': 'error',
// avoid missed switch-case by requiring switch-case statements to be exhaustive with union type
'@typescript-eslint/switch-exhaustiveness-check': 'error',
// ignore static function as those are not supposed to use `this`
'@typescript-eslint/unbound-method': ['error', { ignoreStatic: true }],
'no-restricted-syntax': [
'error',
{
// encourage to use JS standard #private over TS private accessibility modifier, but excluding constructor because it cannot be private in JS: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#description
selector:
':matches(PropertyDefinition, MethodDefinition, TSParameterProperty)[accessibility="private"]:not([kind="constructor"])',
message:
'Use #private instead (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields)',
},
{
selector: 'TSEnumDeclaration',
message:
'"Use string literals with unions or `as const` instead because they work with Node.js type stripping, see https://news.ycombinator.com/item?id=42767627',
},
],
// @typescript-eslint/eslint-plugin suggests to disable it: https://github.com/typescript-eslint/typescript-eslint/blob/2588e9ea55f78352fdd6ae92a306135aabb49a1a/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
// It is disabled in recommended config but re-enabled here to enforce a subset of global variables that supported by both node.js and browsers
'no-undef': 'error',
},
},
{
files: typeScriptTestFileGlobs,
rules: {
// doesn't work with jest.fn<void>()
'@typescript-eslint/no-invalid-void-type': 'off',
// replace by jest/unbound-method
'@typescript-eslint/unbound-method': 'off',
// allow passing an unbound method to `expect` calls
'jest/unbound-method': ['error', { ignoreStatic: true }],
},
},
)
}
const cjsConfig = defineConfig({
languageOptions: {
globals: globals.commonjs,
sourceType: 'script',
},
rules: {
// commonjs must use strict mode
strict: ['error', 'global'],
},
})
const esmConfig = defineConfig({
languageOptions: {
sourceType: 'module',
},
plugins: {
'simple-import-sort': eslintPluginSimpleImportSort,
},
rules: {
// auto sort export statements
'simple-import-sort/exports': 'error',
// auto sort import statements
'simple-import-sort/imports': 'error',
},
})
const baseConfig = defineConfig(
js.configs.recommended,
{
extends: [eslintPluginEslintCommentsConfigs['recommended']],
rules: {
// allow disable eslint rules for whole file without re-enable it in the end of the file
'@eslint-community/eslint-comments/disable-enable-pair': [
'error',
{ allowWholeFile: true },
],
// make sure every eslint-disable comments are in use
'@eslint-community/eslint-comments/no-unused-disable': 'error',
},
},
{
extends: [eslintPluginImportX.flatConfigs.recommended],
rules: {
// this rule doesn't support commonjs, some dependencies are using commonjs
'import-x/default': 'off',
// Does not work after upgrading to eslint-plugin-import-x v4, got this error message: `sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options. (undefined:undefined)`
'import-x/export': 'off',
// enforce extensions for both cjs and esm
'import-x/extensions': [
'error',
// https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_mandatory_file_extensions
'ignorePackages',
],
// make sure import statements above the others
'import-x/first': 'error',
// separate import statements from the others
'import-x/newline-after-import': 'error',
// avoid anonymous function or class for easier to debug via stack trace
// for other types, enforcing named data can improve autocomplete when importing
'import-x/no-anonymous-default-export': [
'error',
{
allowArray: false,
allowArrowFunction: false,
allowAnonymousClass: false,
allowAnonymousFunction: false,
allowCallExpression: false,
allowLiteral: false,
allowObject: false,
},
],
// no circular dependency
'import-x/no-cycle': [
'error',
{
// speed up linting time
ignoreExternal: true,
},
],
// do not allow import packages that are not listed in dependencies or peerDependencies
'import-x/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'!**/src/**', // allow files outside of src/
'**/.*', // allow config rc files
...testFileGlobs,
],
},
],
// forbid a module from importing itself
'import-x/no-self-import': 'error',
// use the shortest path in import statement, but allow /index because it will be standard to omit index as default file in directory
'import-x/no-useless-path-segments': [
'error',
{
commonjs: true,
noUselessIndex: false,
},
],
// turn off these rules as they do not support flat config: https://github.com/import-js/eslint-plugin-import/issues/2556
// Getting this error for eslint-plugin-import-x: `sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options. (undefined:undefined)`
'import-x/namespace': 'off',
'import-x/no-named-as-default': 'off',
'import-x/no-named-as-default-member': 'off',
},
},
{
extends: [eslintPluginRegexp.configs.recommended],
rules: {
// enable regexp strict mode (use `v` flag instead when it is widely supported)
'regexp/require-unicode-regexp': 'error',
},
},
{
plugins: {
unicorn: eslintPluginUnicorn,
},
rules: {
...Object.fromEntries(
Object.entries(
eslintPluginUnicorn.configs['flat/recommended']?.rules ?? {},
).filter(([ruleName]) => {
// only use a subset of recommended rules as other rules are too strict
return (
ruleName.startsWith('unicorn/no-useless-') ||
ruleName.startsWith('unicorn/prefer-')
)
}),
),
// use with `unicorn/throw-new-error`
// disallow builtins to be created without `new` operator, to be consistent with es6 class syntax
'unicorn/new-for-builtins': 'error',
// some legacy projects still use commonjs
'unicorn/prefer-module': 'off',
// `querySelector` is slower than `getElementById`
'unicorn/prefer-query-selector': 'off',
// `Array.from(iterable)` is more readable than `[...iterable]`
'unicorn/prefer-spread': 'off',
// sometimes it is less readable using ternary expressions
'unicorn/prefer-ternary': 'off',
// webpack support on `top level await` is still experimental, and some legacy projects still use commonjs
'unicorn/prefer-top-level-await': 'off',
// use with `unicorn/new-for-builtins`
'unicorn/throw-new-error': 'error',
},
},
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
reportUnusedInlineConfigs: 'error',
},
languageOptions: {
ecmaVersion: 2024,
globals: {
...globals.es2024,
/* Not using `node` to explicitly import node.js only built-in modules, e.g.
* import { Buffer } from 'node:buffer'
* import process from 'node:process'
*/
...globals['shared-node-browser'],
},
},
rules: {
// always use named function for easier to debug via stack trace
'func-names': ['error', 'as-needed'],
// promote named parameters by limiting function parameters
'max-params': ['error', { max: 3, countThis: 'never' }],
// prefer explicitly convert type for readability
'no-implicit-coercion': 'error',
// make sure private class members are in-use
'no-unused-private-class-members': 'error',
// avoid assigning anonymous function to object key which is harder to trace when debug
'object-shorthand': ['error', 'always'],
// prefer `const` when the variable won't be reassigned
'prefer-const': [
'error',
{
// if one of the variables will be reassigned, do not enforce `const`
destructuring: 'all',
},
],
},
},
{
files: ['**/*.js'],
extends: [isESM() ? esmConfig : cjsConfig],
},
{
files: ['**/*.cjs'],
extends: [cjsConfig],
},
{
files: ['**/*.mjs'],
extends: [esmConfig],
},
{
files: testFileGlobs,
extends: [
eslintPluginJest.configs['flat/recommended'],
eslintPluginJest.configs['flat/style'],
],
rules: {
// make sure lifecycle hooks on the top for readability
'jest/prefer-hooks-on-top': 'error',
},
},
await generateTypeScriptConfig(),
)
export default baseConfig