Last active
January 13, 2025 17:26
-
-
Save kesor/616a529f783571d01a94f0f03edc747d to your computer and use it in GitHub Desktop.
ESLing Flat Config Typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file | |
* ESLint configuration for a monorepo project | |
*/ | |
import { default as pluginTs, Config } from 'typescript-eslint' | |
import { FlatCompat } from '@eslint/eslintrc' | |
import globals from 'globals' | |
import jsdoc from 'eslint-plugin-jsdoc' | |
import pluginJs from '@eslint/js' | |
import pluginJson from 'eslint-plugin-json' | |
import pluginMocha from 'eslint-plugin-mocha' | |
import pluginTW from 'eslint-plugin-tailwindcss' | |
import pluginVue from 'eslint-plugin-vue' | |
import pluginRE from 'eslint-plugin-regexp' | |
import vueParser from 'vue-eslint-parser' | |
import pluginESx from 'eslint-plugin-es-x' | |
import pluginChai from 'eslint-plugin-chai-expect' | |
import pluginVitest from 'eslint-plugin-vitest' | |
const compat = new FlatCompat() | |
/** | |
* Configuration is using internal ESLint merging | |
* every object in the configuration is standalone, | |
* and other objects below are merged to override it | |
* this happens internally inside of ESLint by specifying | |
* the same `files:[]` with extra rules. | |
* The `plugins:[]` directive does not have/need `files:[]` | |
* which is why it is standalone. | |
* Objects without `files:[]` are global and apply to everything. | |
*/ | |
export const recommended: Config = pluginTs.config( | |
{ linterOptions: { reportUnusedDisableDirectives: 'error' } }, | |
{ ignores: ['!.*', '**/node_modules/', '.npm/', '**/dist/', '**/*.min.*',] }, | |
// jsdoc in javascript | |
{ plugins: jsdoc.configs['flat/recommended'].plugins }, | |
{ ...jsdoc.configs['flat/recommended-error'], files: ['**/*.js'] }, | |
{ | |
name: '@myproject/eslint-shared-jsdoc-js', | |
files: ['**/*.js'], | |
rules: { | |
'jsdoc/require-file-overview': 'warn', | |
'jsdoc/require-description': 'warn' | |
} | |
}, | |
// jsdoc in typescript | |
{ ...jsdoc.configs['flat/recommended-error'], files: ['**/*.ts'] }, | |
{ ...jsdoc.configs['flat/recommended-typescript-error'], files: ['**/*.ts'] }, | |
{ | |
name: '@myproject/eslint-shared-jsdoc-typescript', | |
files: ['**/*.ts'], | |
rules: { | |
'jsdoc/require-file-overview': ['warn', { tags: { file: { initialCommentsOnly: false } } }], | |
'jsdoc/require-description': 'warn', | |
} | |
}, | |
// json files | |
{ ...pluginJson.configs['recommended'], files: ['**/*.json'] }, | |
// javascript files | |
{ | |
name: '@myproject/eslint-shared-js-recommended', | |
...pluginJs.configs.recommended, | |
files: ['**/*.js'], | |
}, | |
{ ...pluginTs.configs.disableTypeChecked, files: ['**/*.js'], }, | |
{ ...pluginESx.configs['flat/restrict-to-es2022'], files: ['**/*.js'], }, | |
{ | |
name: '@myproject/eslint-shared-js', | |
files: ['**/*.js'], | |
languageOptions: { | |
globals: { ...globals.node }, | |
sourceType: 'module', | |
ecmaVersion: 2022 | |
}, | |
rules: { | |
'new-cap': ['error'], | |
'no-invalid-this': ['error'], | |
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], | |
'object-curly-spacing': ['error', 'always'], | |
'quote-props': ['error', 'consistent-as-needed'], | |
'quotes': ['error', 'single'], | |
'semi': ['error', 'never'], | |
} | |
}, | |
// typescript files | |
...pluginTs.configs.strictTypeChecked.map(cfg => { | |
cfg.files = ['**/*.ts'] | |
return cfg | |
}), | |
{ ...pluginESx.configs['flat/no-new-in-esnext'], files: ['**/*.ts'] }, | |
{ | |
files: ['**/*.ts'], | |
name: '@myproject/eslint-shared-typescript', | |
plugins: { | |
'@typescript-eslint': pluginTs.plugin, | |
// ...compat.plugins('tsdoc')[0].plugins, | |
}, | |
languageOptions: { | |
parser: pluginTs.parser, | |
globals: globals.node, | |
sourceType: 'module', | |
ecmaVersion: 2022, | |
parserOptions: { | |
project: true, | |
EXPERIMENTAL_useProjectService: true, | |
} | |
}, | |
rules: { | |
'new-cap': ['error'], | |
'no-invalid-this': ['error'], | |
'object-curly-spacing': ['error', 'always'], | |
'no-unused-vars': 'off', | |
'quote-props': 'off', | |
'quotes': 'off', | |
'semi': 'off', | |
'no-dupe-class-members': 'off', | |
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^(_|log$)', argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' }], | |
'@typescript-eslint/quotes': ['error', 'single'], | |
'@typescript-eslint/semi': ['error', 'never'], | |
'@typescript-eslint/indent': ['error', 2], | |
'@typescript-eslint/no-dupe-class-members': 'error', | |
'@typescript-eslint/strict-boolean-expressions': ['error', { allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: true }], | |
// 'tsdoc/syntax': 'error', | |
} | |
}, | |
// promises | |
...compat.extends('plugin:promise/recommended').map(cfg => { | |
cfg.files = ['**/*.js', '**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
// imports in javascript files | |
...compat.extends('plugin:import-x/recommended').map(cfg => { | |
cfg.files = ['**/*.js'] | |
return cfg | |
}), | |
{ | |
name: '@myproject/eslint-shared-import-x-js', | |
files: ['**/*.js'], | |
rules: { | |
'import-x/order': 'error', | |
'import-x/first': 'error', | |
'import-x/newline-after-import': 'error', | |
}, | |
settings: { | |
'import-x/internal-regex': '^@myproject/', | |
}, | |
}, | |
// imports in typescript files | |
...compat.extends('plugin:import-x/recommended').map(cfg => { | |
cfg.files = ['**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
...compat.extends('plugin:import-x/typescript').map(cfg => { | |
cfg.files = ['**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
{ | |
name: '@myproject/eslint-shared-import-x-typescript', | |
files: ['**/*.ts', '**/web/**/*.vue'], | |
rules: { | |
'import-x/order': 'error', | |
'import-x/first': 'error', | |
'import-x/newline-after-import': 'error', | |
}, | |
settings: { | |
'import-x/internal-regex': '^@myproject/', | |
'import-x/parsers': { | |
'@typescript-eslint/parser': ['.ts', '.tsx'], | |
[vueParser.meta.name]: ['.vue'], | |
}, | |
'import-x/resolver': { | |
'eslint-import-resolver-vite-ts': { | |
extensions: ['.vue'] | |
}, | |
typescript: { | |
extensions: ['.ts'], | |
project: [ | |
'packages/*/tsconfig.app.json', | |
'apps/*/tsconfig.app.json', | |
] | |
}, | |
node: { extensions: ['.js', '.jsx'] }, | |
} | |
}, | |
}, | |
// vue files | |
...pluginVue.configs['flat/recommended'].map(cfg => { | |
cfg.files = ['**/web/**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
{ | |
files: ['**/web/test/**/*.ts'], | |
plugins: { vitest: pluginVitest }, | |
rules: pluginVitest.configs.recommended.rules, | |
settings: { | |
vitest: { typecheck: true } | |
}, | |
languageOptions: { | |
sourceType: 'module', | |
globals: pluginVitest.environments.env.globals, | |
}, | |
}, | |
{ | |
name: '@myproject/eslint-vue-typescript', | |
files: ['**/web/**/*.ts', '**/web/**/*.vue'], | |
languageOptions: { | |
globals: { ...globals.browser }, | |
parser: vueParser, | |
parserOptions: { | |
parser: { | |
'ts': '@typescript-eslint/parser', | |
'<template>': 'espree' | |
}, | |
project: true, | |
EXPERIMENTAL_useProjectService: true, | |
ecmaFeatures: { jsx: true } | |
} | |
}, | |
}, | |
// regex | |
{ ...pluginRE.configs['flat/recommended'], files: ['**/*.js', '**/*.ts', '**/*.vue'] }, | |
// mocha tests | |
{ ...pluginMocha.configs.flat.recommended, files: ['**/test/**.js', '**/test/**.ts'], }, | |
{ ...pluginChai.configs['recommended-flat'], files: ['**/test/**.js', '**/test/**.ts'], }, | |
// tailwind css | |
...pluginTW.configs['flat/recommended'].map(cfg => { | |
cfg.files = ['**/web/**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
{ | |
name: '@myproject/eslint-shared-tailwindcss', | |
files: ['**/web/**/*.ts', '**/web/**/*.vue'], | |
settings: { | |
tailwindcss: { config: 'tailwind.config.ts', }, | |
}, | |
rules: { | |
'tailwindcss/classnames-order': 'warn' | |
} | |
}, | |
// css | |
...compat.extends('plugin:css/recommended').map(cfg => { | |
cfg.files = ['**/web/**/*.ts', '**/web/**/*.vue'] | |
return cfg | |
}), | |
// { | |
// files: ['**/*'], | |
// rules: { | |
// 'linebreak-style': ['error', 'unix'], | |
// } | |
// }, | |
// editorconfig overrides js/ts rules with its own | |
...compat.plugins('editorconfig').map(cfg => { | |
cfg.files = ['**/*.js', '**/*.json', '**/*.ts', '**/*.vue'] | |
return cfg | |
}), | |
...compat.extends('plugin:editorconfig/all').map(cfg => { | |
cfg.files = ['**/*.js', '**/*.json', '**/*.ts', '**/*.vue'] | |
return cfg | |
}), | |
) | |
// DEBUG the generated configuration by inspecting /tmp/eslint-config.js | |
// import fs from 'node:fs' | |
// import { inspect } from 'node:util' | |
// fs.writeFileSync('/tmp/eslint-config.js', inspect(recommended)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment