From 920bffb547120490a8bdf70b95f8d77174403146 Mon Sep 17 00:00:00 2001 From: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:47:28 +1000 Subject: [PATCH] refactor(eslint): Manual rule setup with rule documentation (#108) * Add setup section * More rule refactoring * Start documenting rules * Modularise * Remove js from extraFileExtensions * More documenting * Add more typescript rules/comments * Comment rule sources --- src/eslint/import.js | 36 +++++++++++++ src/eslint/index.js | 111 +++++++-------------------------------- src/eslint/javascript.js | 77 +++++++++++++++++++++++++++ src/eslint/typescript.js | 58 ++++++++++++++++++++ tsconfig.json | 2 +- 5 files changed, 191 insertions(+), 93 deletions(-) create mode 100644 src/eslint/import.js create mode 100644 src/eslint/javascript.js create mode 100644 src/eslint/typescript.js diff --git a/src/eslint/import.js b/src/eslint/import.js new file mode 100644 index 0000000..17d08a1 --- /dev/null +++ b/src/eslint/import.js @@ -0,0 +1,36 @@ +// https://github.com/un-ts/eslint-plugin-import-x + +/** @type {import('eslint').Linter.FlatConfig} */ +export const importRules = { + name: 'tanstack/import', + rules: { + /** Stylistic preference */ + 'import/newline-after-import': 'error', + /** No require() or module.exports */ + 'import/no-commonjs': 'error', + /** No import loops */ + 'import/no-cycle': 'error', + /** Reports if a resolved path is imported more than once */ + 'import/no-duplicates': 'error', + /** Default export name cannot match named export */ + 'import/no-named-as-default': 'error', + /** Don't access named imports from default export */ + 'import/no-named-as-default-member': 'error', + /** Stylistic preference */ + 'import/order': [ + 'error', + { + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index', + 'object', + 'type', + ], + }, + ], + }, +} diff --git a/src/eslint/index.js b/src/eslint/index.js index d67aab9..74730eb 100644 --- a/src/eslint/index.js +++ b/src/eslint/index.js @@ -1,109 +1,36 @@ import tseslint from 'typescript-eslint' import pluginImport from 'eslint-plugin-import-x' import globals from 'globals' -// @ts-expect-error -import eslint from '@eslint/js' -// @ts-expect-error -import configPrettier from 'eslint-config-prettier' +import { javascriptRules } from './javascript.js' +import { importRules } from './import.js' +import { typescriptRules } from './typescript.js' /** @type {import('eslint').Linter.FlatConfig[]} */ export const rootConfig = [ { - name: 'eslint/rules', - rules: { - ...eslint.configs.recommended.rules, - 'no-async-promise-executor': 'off', - 'no-empty': 'off', - 'no-redeclare': 'off', - 'no-shadow': 'error', - 'no-undef': 'off', - 'sort-imports': ['error', { ignoreDeclarationSort: true }], - }, - }, - { - name: 'prettier/rules', - ...configPrettier, - }, - ...tseslint.configs.recommended, - ...tseslint.configs.stylistic, - { - name: 'import/rules', - plugins: { - import: pluginImport, - }, - rules: { - 'import/newline-after-import': 'error', - 'import/no-cycle': 'error', - 'import/order': [ - 'error', - { - groups: [ - 'builtin', - 'external', - 'internal', - 'parent', - 'sibling', - 'index', - 'object', - 'type', - ], - }, - ], - }, - }, - { - name: 'tanstack/custom', + name: 'tanstack/setup', languageOptions: { - globals: { - ...globals.browser, - }, - ecmaVersion: 2020, sourceType: 'module', + ecmaVersion: 2020, + // @ts-expect-error + parser: tseslint.parser, parserOptions: { - extraFileExtensions: ['.js', '.svelte', '.vue'], project: true, + extraFileExtensions: ['.svelte', '.vue'], + parser: tseslint.parser, + }, + globals: { + ...globals.browser, }, }, plugins: { - '@typescript-eslint': tseslint.plugin, - }, - rules: { - '@typescript-eslint/array-type': [ - 'error', - { default: 'generic', readonly: 'generic' }, - ], - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/consistent-type-definitions': 'off', - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports' }, - ], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/method-signature-style': ['error', 'property'], - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'typeParameter', - format: ['PascalCase'], - leadingUnderscore: 'forbid', - trailingUnderscore: 'forbid', - custom: { - regex: '^(T|T[A-Z][A-Za-z]+)$', - match: true, - }, - }, - ], - '@typescript-eslint/no-empty-interface': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unnecessary-condition': 'error', - '@typescript-eslint/no-unnecessary-type-assertion': 'error', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-inferrable-types': [ - 'error', - { ignoreParameters: true }, - ], + // @ts-expect-error + import: pluginImport, + // @ts-expect-error + ts: tseslint.plugin, }, }, + javascriptRules, + importRules, + typescriptRules, ] diff --git a/src/eslint/javascript.js b/src/eslint/javascript.js new file mode 100644 index 0000000..59b6d82 --- /dev/null +++ b/src/eslint/javascript.js @@ -0,0 +1,77 @@ +// https://eslint.org/docs/latest/rules/ + +/** @type {import('eslint').Linter.FlatConfig} */ +export const javascriptRules = { + name: 'tanstack/javascript', + rules: { + /** Constructors of derived classes must call super() */ + 'constructor-super': 'error', + /** TODO */ + 'for-direction': 'error', + 'getter-return': 'error', + 'no-async-promise-executor': 'error', + 'no-case-declarations': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': 'error', + 'no-const-assign': 'error', + 'no-constant-binary-expression': 'error', + 'no-constant-condition': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-else-if': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-empty-character-class': 'error', + 'no-empty-pattern': 'error', + 'no-empty-static-block': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-import-assign': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-new-native-nonconstructor': 'error', + 'no-nonoctal-decimal-escape': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': 'error', + 'no-regex-spaces': 'error', + 'no-self-assign': 'error', + 'no-setter-return': 'error', + 'no-shadow': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-this-before-super': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unsafe-optional-chaining': 'error', + 'no-unused-labels': 'error', + 'no-unused-private-class-members': 'error', + 'no-unused-vars': 'error', + 'no-useless-backreference': 'error', + 'no-useless-catch': 'error', + 'no-useless-escape': 'error', + /** Prefer let and const */ + 'no-var': 'error', + 'no-with': 'error', + /** Prefer const if never re-assigned */ + 'prefer-const': 'error', + 'require-yield': 'error', + /** Stylistic consistency */ + 'sort-imports': ['error', { ignoreDeclarationSort: true }], + 'use-isnan': 'error', + /** Enforce comparing typeof against valid strings */ + 'valid-typeof': 'error', + }, +} diff --git a/src/eslint/typescript.js b/src/eslint/typescript.js new file mode 100644 index 0000000..4549270 --- /dev/null +++ b/src/eslint/typescript.js @@ -0,0 +1,58 @@ +// https://typescript-eslint.io/rules/ + +/** @type {import('eslint').Linter.FlatConfig} */ +export const typescriptRules = { + name: 'tanstack/typescript', + rules: { + /** Prefer Array format */ + 'ts/array-type': ['error', { default: 'generic', readonly: 'generic' }], + /** Prevent @ts-ignore, allow @ts-expect-error */ + 'ts/ban-ts-comment': ['error', { 'ts-expect-error': false }], + /** Bans specific built-in types and can suggest alternatives */ + 'ts/ban-types': 'error', + /** Enforce import type { T } */ + 'ts/consistent-type-imports': ['error', { prefer: 'type-imports' }], + /** Shorthand method style is less strict */ + 'ts/method-signature-style': ['error', 'property'], + /** Enforces generic type convention */ + 'ts/naming-convention': [ + 'error', + { + selector: 'typeParameter', + format: ['PascalCase'], + leadingUnderscore: 'forbid', + trailingUnderscore: 'forbid', + custom: { + regex: '^(T|T[A-Z][A-Za-z]+)$', + match: true, + }, + }, + ], + /** Duplicate values can lead to bugs that are hard to track down */ + 'ts/no-duplicate-enum-values': 'error', + /** Using the operator any more than once does nothing */ + 'ts/no-extra-non-null-assertion': 'error', + /** There are several potential bugs with this compared to other loops */ + 'ts/no-for-in-array': 'error', + /** Enforce valid definition of new and constructor */ + 'ts/no-misused-new': 'error', + /** Disallow TypeScript namespaces */ + 'ts/no-namespace': 'error', + /** Disallow non-null assertions after an optional chain expression */ + 'ts/no-non-null-asserted-optional-chain': 'error', + /** Detects conditionals which will always evaluate truthy or falsy */ + 'ts/no-unnecessary-condition': 'error', + /** Checks if the the explicit type is identical to the inferred type */ + 'ts/no-unnecessary-type-assertion': 'error', + /** Don't over-define types for simple things like strings */ + 'ts/no-inferrable-types': ['error', { ignoreParameters: true }], + /** Enforce the use of as const over literal type */ + 'ts/prefer-as-const': 'error', + /** From recommended preset */ + 'ts/prefer-for-of': 'error', + /** Disallow async functions which have no await expression */ + 'ts/require-await': 'error', + /** Prefer of ES6-style import declarations */ + 'ts/triple-slash-reference': 'error', + }, +} diff --git a/tsconfig.json b/tsconfig.json index 2e53d34..fb99d15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,7 @@ "include": [ "bin", "src", - ".eslintrc.cjs", + "eslint.config.js", "prettier.config.js", "tanstack.config.js" ]