@1stg/eslint-config
Version:
Personal but Shareable ESLint Configuration for all 1stG.me projects
580 lines (545 loc) • 13.5 kB
JavaScript
const path = require('node:path')
const { jsoncFiles, nonJsonRcFiles, preferPrettier } = require('@1stg/config')
const {
isAngularAvailable,
isReactAvailable,
isPkgAvailable,
isSvelteAvailable,
isTsAvailable,
isVueAvailable,
tryFile,
tryPkg,
} = require('@pkgr/utils')
const { magicNumbers, prettierExtends } = require('./_util')
const configFile =
tryFile(path.resolve('babel.config.js')) ||
tryFile(path.resolve('.babelrc.js')) ||
tryPkg('@1stg/babel-preset/config')
const jsBase = {
files: ['*.cjs', '*.mjs', '*.js', '*.jsx'],
parser: '@babel/eslint-parser',
parserOptions: configFile
? {
babelOptions: {
configFile,
},
}
: {
requireConfigFile: false,
},
plugins: ['@babel'],
rules: {
camelcase: [
2,
{
properties: 'never',
ignoreDestructuring: true,
},
],
'new-cap': 0,
'no-invalid-this': 0,
'no-unused-expressions': 0,
'@babel/new-cap': 2,
'@babel/no-invalid-this': 2,
'@babel/no-unused-expressions': 2,
},
}
exports.js = {
...jsBase,
extends: ['plugin:jsdoc/recommended'],
rules: {
...jsBase.rules,
'jsdoc/require-jsdoc': 0,
'jsdoc/require-param-description': 0,
},
settings: isTsAvailable
? {
jsdoc: {
mode: 'typescript',
},
}
: undefined,
}
const project =
tryFile(path.resolve('tsconfig.eslint.json')) ||
tryFile(path.resolve('tsconfig.base.json')) ||
tryFile(path.resolve('tsconfig.json')) ||
tryPkg('@1stg/tsconfig')
const resolveSettings = {
'import/external-module-folders': [
'node_modules',
'node_modules/@d-ts',
'node_modules/@types',
],
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project,
},
},
}
const tsBase = {
files: ['*.cts', '*.mts', '*.ts', '*.tsx'],
parserOptions: {
project,
},
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/typescript',
...prettierExtends,
],
settings: resolveSettings,
rules: {
'@typescript-eslint/adjacent-overload-signatures': 2,
'@typescript-eslint/array-type': [
2,
{
default: 'array-simple',
},
],
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/ban-types': [
2,
{
types: {
object: false,
},
},
],
'@typescript-eslint/consistent-type-definitions': [2, 'interface'],
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-member-accessibility': [
2,
{
accessibility: 'no-public',
overrides: {
parameterProperties: 'off',
},
},
],
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/member-ordering': 2,
'@typescript-eslint/naming-convention': 0, // TODO: find better config
'@typescript-eslint/no-empty-function': 2,
'@typescript-eslint/no-extraneous-class': [
2,
{
allowWithDecorator: true,
},
],
'@typescript-eslint/no-for-in-array': 2,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-parameter-properties': 0,
'@typescript-eslint/no-require-imports': 2,
'@typescript-eslint/no-this-alias': [
2,
{
allowDestructuring: true,
allowedNames: ['self'],
},
],
'@typescript-eslint/no-use-before-define': [
2,
{
classes: false,
functions: false,
},
],
'@typescript-eslint/no-useless-constructor': 2,
'@typescript-eslint/no-unused-vars': [
2,
{
argsIgnorePattern: '^_',
},
],
'@typescript-eslint/prefer-for-of': 2,
'@typescript-eslint/prefer-function-type': 2,
'@typescript-eslint/prefer-ts-expect-error': 2,
'@typescript-eslint/sort-type-union-intersection-members': 2,
'@typescript-eslint/triple-slash-reference': [
2,
{
lib: 'never',
path: 'always',
types: 'prefer-import',
},
],
'@typescript-eslint/unified-signatures': 2,
camelcase: 0,
'import/default': 0,
'import/named': 0,
'import/namespace': 0,
'import/no-duplicates': 2,
'import/no-named-as-default': 0,
'import/no-named-as-default-member': 0,
'no-empty-function': 0,
'no-restricted-syntax': [
2,
{
selector:
'TSTypeAliasDeclaration > .typeAnnotation[type=/^(TS.*Keyword|TSTypeReference)$/]:not(:has(TSTypeParameterInstantiation)):not(:has(TSQualifiedName))',
message:
'Type alias references simple `Ts*Keyword`s or `TSTypeReference` only is not allowed',
},
],
'no-unused-vars': 0,
'no-use-before-define': 0,
'no-useless-constructor': 0,
'n/shebang': 0,
// covered by @typescript-eslint/no-floating-promises
'promise/always-return': 0,
'promise/catch-or-return': 0,
// ts itself has guaranteed it
'unicorn/no-array-callback-reference': 0,
// covered by @typescript-eslint/no-extraneous-class
'unicorn/no-static-only-class': 0,
// covered by @typescript-eslint/no-this-alias
'unicorn/no-this-assignment': 0,
},
}
exports.ts = [
tsBase,
{
files: '{bin,cli}.ts',
rules: {
'n/shebang': 0,
},
},
{
files: ['*.cts', '*.mts', '*.ts', '*.tsx'],
excludedFiles: ['*.d.cts', '*.d.mts', '*.d.ts'],
extends: ['plugin:@typescript-eslint/recommended-requiring-type-checking'],
rules: {
'@typescript-eslint/no-floating-promises': [
2,
{
ignoreVoid: true,
},
],
'@typescript-eslint/no-magic-numbers': [
2,
{
enforceConst: true,
ignore: magicNumbers,
ignoreArrayIndexes: true,
ignoreEnums: true,
ignoreNumericLiteralTypes: true,
ignoreReadonlyClassProperties: true,
},
],
'@typescript-eslint/no-unnecessary-condition': 2,
'@typescript-eslint/no-unnecessary-qualifier': 2,
'@typescript-eslint/no-unnecessary-type-arguments': 2,
'@typescript-eslint/prefer-optional-chain': 2,
'@typescript-eslint/prefer-readonly': 2,
'@typescript-eslint/prefer-reduce-type-parameter': 2,
'@typescript-eslint/prefer-string-starts-ends-with': 2,
'@typescript-eslint/require-array-sort-compare': [
2,
{
ignoreStringArrays: true,
},
],
'@typescript-eslint/restrict-plus-operands': 2,
'no-constant-condition': 0,
'no-magic-numbers': 0,
},
},
]
exports.dTs = {
files: ['*.d.cts', '*.d.mts', '*.d.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-extraneous-class': 0,
'@typescript-eslint/no-namespace': 0,
'@typescript-eslint/no-unused-vars': 0,
'import/no-duplicates': 0,
'import/order': 0,
},
}
const reactJsx = {
extends: ['standard-react', 'plugin:react/recommended', ...prettierExtends],
settings: {
react: {
version: 'detect',
},
},
}
exports.react = [
{
files: '*.{js,jsx,tsx}',
rules: {
'react/jsx-boolean-value': [2, 'always'],
'react/jsx-handler-names': [
2,
{
eventHandlerPrefix: false,
eventHandlerPropPrefix: 'on',
},
],
'sonar/function-name': isTsAvailable
? [2, { format: '^_?[a-zA-Z][a-zA-Z0-9]*\\$?$' }]
: 0,
},
...reactJsx,
},
{
files: '*.tsx',
rules: {
'react/display-name': 0,
},
},
]
exports.reactHooks = {
files: '*.{js,jsx,ts,tsx}',
plugins: ['react-hooks'],
rules: {
'react-hooks/rules-of-hooks': 2,
'react-hooks/exhaustive-deps': 2,
},
}
exports.reactTs = {
files: '*.{ts,tsx}',
rules: {
'no-restricted-imports': [2, 'prop-types'],
'react/prop-types': 0,
},
}
const vueExtends = ['plugin:vue/recommended', ...prettierExtends]
exports.vue = [
{
...jsBase,
files: [...jsBase.files, !isTsAvailable && '*.vue'].filter(Boolean),
parser: 'vue-eslint-parser',
parserOptions: {
...jsBase.parserOptions,
parser: jsBase.parser,
},
extends: vueExtends,
},
isTsAvailable && {
...tsBase,
files: [...tsBase.files, '*.vue'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue'],
},
extends: [...tsBase.extends, ...vueExtends],
},
{
files: '*.vue',
extends: [
// eslint-disable-next-line sonarjs/no-duplicate-string
'plugin:markup/recommended',
...vueExtends,
],
rules: {
'no-unused-vars': 0,
'@typescript-eslint/no-unused-vars': 0,
},
},
].filter(Boolean)
const svelteBase = {
files: '*.svelte',
extends: [
'plugin:markup/recommended',
'plugin:svelte/recommended',
'plugin:svelte/prettier',
...prettierExtends,
],
rules: {
'sonar/label-position': 0,
'sonar/no-labels': 0,
},
}
exports.svelte = isTsAvailable
? {
...tsBase,
...svelteBase,
parserOptions: {
extraFileExtensions: ['.svelte'],
},
}
: {
...jsBase,
...svelteBase,
}
exports.angular = [
{
files: '*.ts',
excludedFiles: '*.d.ts',
parserOptions: {
project,
},
extends: [
'plugin:@angular-eslint/recommended',
'plugin:@angular-eslint/template/process-inline-templates',
...prettierExtends,
],
rules: {
'@angular-eslint/prefer-on-push-component-change-detection': 1,
},
},
{
files: '*.html',
extends: [
'plugin:@angular-eslint/template/recommended',
'plugin:markup/recommended',
...prettierExtends,
],
parser: 'angular-eslint-template-parser',
rules: {
'@angular-eslint/template/eqeqeq': [
2,
{
allowNullOrUndefined: true,
},
],
'prettier/prettier': [
preferPrettier ? 0 : 2,
{
parser: 'angular',
},
],
'spaced-comment': 0,
},
},
{
files: '*inline-template-*.component.html',
rules: {
'unicorn/no-empty-file': 0,
},
},
]
exports.markup = [
{
files: '*.html',
extends: 'plugin:markup/recommended',
rules: {
'prettier/prettier': [
preferPrettier ? 0 : 2,
{
parser: 'html',
},
],
},
},
{
files: '*.pug',
extends: ['plugin:markup/recommended', ...prettierExtends],
},
]
exports.md = {
files: '*.md',
extends: ['plugin:mdx/recommended'],
rules: {
'prettier/prettier': preferPrettier ? 0 : 2,
},
}
exports.mdx = {
files: '*.mdx',
extends: [...reactJsx.extends, 'plugin:mdx/recommended'],
parserOptions: jsBase.parserOptions,
settings: { ...reactJsx.settings, ...resolveSettings },
rules: {
'prettier/prettier': preferPrettier ? 0 : 2,
},
}
const nonSourceRules = {
'n/no-extraneous-import': 0,
'n/no-extraneous-require': 0,
'n/no-unsupported-features/es-builtins': 0,
}
exports.test = {
files: '**/{__test__,test,tests}/**/*',
rules: nonSourceRules,
}
exports.jest = {
files: '*.{spec,test}.{js,jsx,ts,tsx}',
extends: ['plugin:jest/recommended'],
rules: exports.test.rules,
}
exports.script = exports.scripts = {
files: '**/scripts/**/*',
rules: nonSourceRules,
}
exports.story = exports.stories = {
files: ['**/.storybook/**/*', '**/stories/**/*'],
rules: nonSourceRules,
}
exports.config = exports.configs = {
files: ['.*.js', '*.config.{js,ts}'],
rules: nonSourceRules,
}
exports.json = {
files: ['.*rc', '*.json'],
excludedFiles: [...nonJsonRcFiles, ...jsoncFiles],
extends: [
'plugin:jsonc/recommended-with-json',
// eslint-disable-next-line sonarjs/no-duplicate-string
'plugin:json-schema-validator/recommended',
// eslint-disable-next-line sonarjs/no-duplicate-string
'plugin:jsonc/prettier',
],
}
exports.jsonc = {
files: jsoncFiles,
extends: [
'plugin:jsonc/recommended-with-jsonc',
'plugin:json-schema-validator/recommended',
'plugin:jsonc/prettier',
],
}
exports.json5 = {
files: '*.json5',
extends: [
'plugin:jsonc/recommended-with-json5',
'plugin:json-schema-validator/recommended',
'plugin:jsonc/prettier',
],
}
exports.toml = {
files: '*.toml',
extends: [
'plugin:toml/recommended',
'plugin:json-schema-validator/recommended',
],
}
exports.yml = exports.yaml = {
files: ['*.yml', '*.yaml'],
extends: [
'plugin:yml/recommended',
'plugin:json-schema-validator/recommended',
'plugin:yml/prettier',
],
}
exports.overrides = []
// eslint-disable-next-line unicorn/prefer-spread
.concat(
isPkgAvailable('@babel/core') && exports.js,
isTsAvailable && exports.ts,
isReactAvailable && exports.react,
isReactAvailable && exports.reactHooks,
isReactAvailable && exports.reactTs,
// The order matters, the later should to be preferred
exports.markup,
isAngularAvailable && exports.angular,
isVueAvailable && exports.vue,
isSvelteAvailable && exports.svelte,
exports.md,
exports.mdx,
isPkgAvailable('jest') && exports.jest,
exports.test,
exports.scripts,
exports.stories,
exports.configs,
exports.dTs,
exports.json,
exports.jsonc,
exports.json5,
exports.toml,
exports.yaml,
)
.filter(Boolean)