From 02c106dd6d8847774fde50f58d81de3b367f4cc0 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 23 Jun 2025 07:12:59 +1200 Subject: [PATCH 1/4] chore: migrate to flat eslint config --- .eslintignore | 4 -- .eslintrc.js | 136 ---------------------------------------------- eslint.config.js | 137 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- tsconfig.json | 2 +- 5 files changed, 139 insertions(+), 142 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 224070368..000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -coverage/ -lib/ -!.eslintrc.js -src/rules/__tests__/fixtures/ diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index c12cd6de3..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,136 +0,0 @@ -'use strict'; - -const globals = require('./src/globals.json'); - -/** @type {import('eslint').Linter.LegacyConfig} */ -const config = { - ignorePatterns: ['!.eslint-doc-generatorrc.js', '!.eslintrc.js'], - parser: require.resolve('@typescript-eslint/parser'), - extends: [ - 'plugin:eslint-plugin/recommended', - 'plugin:@eslint-community/eslint-comments/recommended', - 'plugin:n/recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:prettier/recommended', - ], - plugins: [ - 'eslint-plugin', - '@eslint-community/eslint-comments', - 'n', - 'import', - '@typescript-eslint', - ], - parserOptions: { - ecmaVersion: 2018, - warnOnUnsupportedTypeScriptVersion: false, - }, - env: { - node: true, - es6: true, - }, - rules: { - '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], - '@typescript-eslint/no-require-imports': 'error', - '@typescript-eslint/ban-ts-comment': 'error', - '@typescript-eslint/no-empty-object-type': 'error', - '@typescript-eslint/no-unsafe-function-type': 'error', - '@typescript-eslint/no-wrapper-object-types': 'error', - '@typescript-eslint/consistent-type-imports': [ - 'error', - { disallowTypeAnnotations: false, fixStyle: 'inline-type-imports' }, - ], - '@typescript-eslint/no-import-type-side-effects': 'error', - '@typescript-eslint/no-unused-vars': 'error', - '@eslint-community/eslint-comments/no-unused-disable': 'error', - 'eslint-plugin/require-meta-docs-description': [ - 'error', - { pattern: '^(Enforce|Require|Disallow|Suggest|Prefer)' }, - ], - 'eslint-plugin/test-case-property-ordering': 'error', - 'no-else-return': 'error', - 'no-negated-condition': 'error', - eqeqeq: ['error', 'smart'], - strict: 'error', - 'prefer-template': 'error', - 'object-shorthand': [ - 'error', - 'always', - { avoidExplicitReturnArrows: true }, - ], - 'prefer-destructuring': [ - 'error', - { VariableDeclarator: { array: true, object: true } }, - ], - 'sort-imports': ['error', { ignoreDeclarationSort: true }], - 'require-unicode-regexp': 'error', - // TS covers these 2 - 'n/no-missing-import': 'off', - 'n/no-missing-require': 'off', - 'n/no-unsupported-features/es-syntax': 'off', - 'n/no-unsupported-features/es-builtins': 'error', - 'import/no-commonjs': 'error', - 'import/no-duplicates': 'error', - 'import/no-extraneous-dependencies': 'error', - 'import/no-unused-modules': 'error', - 'import/order': [ - 'error', - { alphabetize: { order: 'asc' }, 'newlines-between': 'never' }, - ], - 'padding-line-between-statements': [ - 'error', - { blankLine: 'always', prev: '*', next: 'return' }, - { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, - { - blankLine: 'any', - prev: ['const', 'let', 'var'], - next: ['const', 'let', 'var'], - }, - { blankLine: 'always', prev: 'directive', next: '*' }, - { blankLine: 'any', prev: 'directive', next: 'directive' }, - ], - - 'prefer-spread': 'error', - 'prefer-rest-params': 'error', - 'prefer-const': ['error', { destructuring: 'all' }], - 'no-var': 'error', - curly: 'error', - }, - overrides: [ - { - files: ['*.js'], - rules: { - '@typescript-eslint/no-require-imports': 'off', - }, - }, - { - files: ['*.test.js', '*.test.ts'], - globals, - }, - { - files: [ - 'src/**/*', - 'dangerfile.ts', - './jest.config.ts', - './eslint-remote-tester.config.ts', - ], - parserOptions: { - sourceType: 'module', - }, - }, - { - files: ['.eslint-doc-generatorrc.js', '.eslintrc.js', 'babel.config.js'], - rules: { - '@typescript-eslint/no-require-imports': 'off', - 'import/no-commonjs': 'off', - }, - }, - { - files: ['*.d.ts'], - rules: { - strict: 'off', - }, - }, - ], -}; - -module.exports = config; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..2ea629725 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,137 @@ +'use strict'; + +const pluginEslintCommentsConfigs = require('@eslint-community/eslint-plugin-eslint-comments/configs'); +const pluginTypeScriptESLint = require('@typescript-eslint/eslint-plugin'); +const parserTypeScriptESLint = require('@typescript-eslint/parser'); +const pluginESLintPlugin = require('eslint-plugin-eslint-plugin'); +const pluginImport = require('eslint-plugin-import'); +const pluginN = require('eslint-plugin-n'); +const pluginPrettier = require('eslint-plugin-prettier'); +const pluginPrettierRecommended = require('eslint-plugin-prettier/recommended'); +const globals = require('./src/globals.json'); + +/** @type {import('eslint').Linter.FlatConfig[]} */ +const config = [ + { files: ['**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}'] }, + { ignores: ['.yarn', 'coverage/', 'lib/', 'src/rules/__tests__/fixtures'] }, + { + plugins: { + '@typescript-eslint': pluginTypeScriptESLint, + ...pluginEslintCommentsConfigs.recommended.plugins, + import: pluginImport, + 'eslint-plugin': pluginESLintPlugin, + n: pluginN, + prettier: pluginPrettier, + }, + languageOptions: { parser: parserTypeScriptESLint }, + linterOptions: { + reportUnusedDisableDirectives: 'error', + }, + rules: { + ...pluginESLintPlugin.configs['flat/recommended'].rules, + ...pluginEslintCommentsConfigs.recommended.rules, + ...pluginPrettierRecommended.rules, + }, + }, + { + rules: { + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], + '@typescript-eslint/no-require-imports': 'error', + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/no-empty-object-type': 'error', + '@typescript-eslint/no-unsafe-function-type': 'error', + '@typescript-eslint/no-wrapper-object-types': 'error', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { disallowTypeAnnotations: false, fixStyle: 'inline-type-imports' }, + ], + '@typescript-eslint/no-import-type-side-effects': 'error', + '@typescript-eslint/no-unused-vars': 'error', + '@eslint-community/eslint-comments/no-unused-disable': 'error', + 'eslint-plugin/require-meta-docs-description': [ + 'error', + { pattern: '^(Enforce|Require|Disallow|Suggest|Prefer)' }, + ], + 'eslint-plugin/test-case-property-ordering': 'error', + 'no-else-return': 'error', + 'no-negated-condition': 'error', + eqeqeq: ['error', 'smart'], + strict: 'error', + 'prefer-template': 'error', + 'object-shorthand': [ + 'error', + 'always', + { avoidExplicitReturnArrows: true }, + ], + 'prefer-destructuring': [ + 'error', + { VariableDeclarator: { array: true, object: true } }, + ], + 'sort-imports': ['error', { ignoreDeclarationSort: true }], + 'require-unicode-regexp': 'error', + // TS covers these 2 + 'n/no-missing-import': 'off', + 'n/no-missing-require': 'off', + 'n/no-unsupported-features/es-syntax': 'off', + 'n/no-unsupported-features/es-builtins': 'error', + 'import/no-commonjs': 'error', + 'import/no-duplicates': 'error', + 'import/no-extraneous-dependencies': 'error', + 'import/no-unused-modules': 'error', + 'import/order': [ + 'error', + { alphabetize: { order: 'asc' }, 'newlines-between': 'never' }, + ], + 'padding-line-between-statements': [ + 'error', + { blankLine: 'always', prev: '*', next: 'return' }, + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + { blankLine: 'always', prev: 'directive', next: '*' }, + { blankLine: 'any', prev: 'directive', next: 'directive' }, + ], + + 'prefer-spread': 'error', + 'prefer-rest-params': 'error', + 'prefer-const': ['error', { destructuring: 'all' }], + 'no-var': 'error', + curly: 'error', + }, + }, + { + files: ['*.js'], + rules: { + '@typescript-eslint/no-require-imports': 'off', + }, + }, + { + files: ['*.test.js', '*.test.ts'], + languageOptions: { globals }, + }, + { + files: [ + '.eslint-doc-generatorrc.js', + 'eslint.config.js', + 'babel.config.js', + ], + languageOptions: { + sourceType: 'commonjs', + }, + rules: { + '@typescript-eslint/no-require-imports': 'off', + 'import/no-commonjs': 'off', + }, + }, + { + files: ['*.d.ts'], + rules: { + strict: 'off', + }, + }, +]; + +module.exports = config; diff --git a/package.json b/package.json index 028d6b39b..e7c18ca54 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "build": "babel --extensions .js,.ts src --out-dir lib --copy-files && rimraf --glob lib/__tests__ 'lib/**/__tests__'", "postinstall": "is-ci || husky", - "lint": "eslint . --ignore-pattern '!.eslintrc.js' --ext js,ts", + "lint": "eslint .", "prepack": "rimraf lib && yarn build", "prepublishOnly": "pinst --disable", "prettier:check": "prettier --check 'docs/**/*.md' README.md '.github/**' package.json tsconfig.json src/globals.json .yarnrc.yml", diff --git a/tsconfig.json b/tsconfig.json index de3fb9fb9..89083c387 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ }, "files": [ ".eslint-doc-generatorrc.js", - ".eslintrc.js", + "eslint.config.js", "babel.config.js", "eslint-remote-tester.config.ts", "jest.config.ts" From ce6d12f7198e37c346d00d00b6dc2c99e71b16e9 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:31:11 +1200 Subject: [PATCH 2/4] chore: add types for untyped plugins --- tsconfig.json | 3 ++- types.d.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 types.d.ts diff --git a/tsconfig.json b/tsconfig.json index 89083c387..71c3a11e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,8 @@ "eslint.config.js", "babel.config.js", "eslint-remote-tester.config.ts", - "jest.config.ts" + "jest.config.ts", + "types.d.ts" ], "include": ["src/**/*"], "exclude": ["src/rules/__tests__/fixtures/**/*"] diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 000000000..6aa4bd406 --- /dev/null +++ b/types.d.ts @@ -0,0 +1,38 @@ +declare module 'eslint-plugin-eslint-plugin' { + import type * as ESLint from 'eslint'; + + const plugin: ESLint.ESLint.Plugin & { + configs: { + all: ESLint.Linter.LegacyConfig; + 'all-type-checked': ESLint.Linter.LegacyConfig; + recommended: ESLint.Linter.LegacyConfig; + rules: ESLint.Linter.LegacyConfig; + tests: ESLint.Linter.LegacyConfig; + 'rules-recommended': ESLint.Linter.LegacyConfig; + 'tests-recommended': ESLint.Linter.LegacyConfig; + + 'flat/all': ESLint.Linter.FlatConfig; + 'flat/all-type-checked': ESLint.Linter.FlatConfig; + 'flat/recommended': ESLint.Linter.FlatConfig; + 'flat/rules': ESLint.Linter.FlatConfig; + 'flat/tests': ESLint.Linter.FlatConfig; + 'flat/rules-recommended': ESLint.Linter.FlatConfig; + 'flat/tests-recommended': ESLint.Linter.FlatConfig; + }; + }; + export = plugin; +} + +// todo: see https://github.com/eslint-community/eslint-plugin-eslint-comments/pull/246 +declare module '@eslint-community/eslint-plugin-eslint-comments/configs' { + import type * as ESLint from 'eslint'; + + const configs: { + recommended: ESLint.Linter.FlatConfig & { + plugins: { + '@eslint-community/eslint-plugin-eslint-comments': ESLint.ESLint.Plugin; + }; + }; + }; + export = configs; +} From 8f87e57355f21caccdc32152854c43d5a97b1a5b Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:31:22 +1200 Subject: [PATCH 3/4] chore: ignore a ts error with `@typescript-eslint` plugin for now --- eslint.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eslint.config.js b/eslint.config.js index 2ea629725..a00c4320e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -16,6 +16,8 @@ const config = [ { ignores: ['.yarn', 'coverage/', 'lib/', 'src/rules/__tests__/fixtures'] }, { plugins: { + // @ts-expect-error types are currently not considered compatible currently + // todo: suspect this is because of differences in ESLint (types) versions '@typescript-eslint': pluginTypeScriptESLint, ...pluginEslintCommentsConfigs.recommended.plugins, import: pluginImport, From 51c70af41a83531f8b16708ce13c191fb632c46b Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 23 Jun 2025 13:29:51 +1200 Subject: [PATCH 4/4] test: always run linting --- jest.config.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 90cd725be..002d6dfec 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,6 +1,4 @@ -import { version as eslintVersion } from 'eslint/package.json'; import type { Config } from 'jest'; -import * as semver from 'semver'; const config = { clearMocks: true, @@ -36,11 +34,4 @@ const config = { ], } satisfies Config; -if (semver.major(eslintVersion) !== 8) { - // our eslint config only works for v8 - config.projects = config.projects.filter( - ({ displayName }) => displayName !== 'lint', - ); -} - export default config;