diff --git a/README.md b/README.md index 80790dceb..2a5a5cecb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ yarn add --dev eslint eslint-plugin-jest **Note:** If you installed ESLint globally then you must also install `eslint-plugin-jest` globally. -## Usage +## Usage (legacy: `.eslintrc*`) Add `jest` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: @@ -59,7 +59,7 @@ doing: This is included in all configs shared by this plugin, so can be omitted if extending them. -#### Aliased Jest globals +### Aliased Jest globals You can tell this plugin about any global Jests you have aliased using the `globalAliases` setting: @@ -143,7 +143,64 @@ module.exports = { }; ``` -## Shareable configurations +## Usage (new: `eslint.config.js`) + +From [`v8.21.0`](https://github.com/eslint/eslint/releases/tag/v8.21.0), eslint +announced a new config system. In the new system, `.eslintrc*` is no longer +used. `eslint.config.js` would be the default config file name. + +And from [`v8.23.0`](https://github.com/eslint/eslint/releases/tag/v8.23.0), +eslint CLI starts to look up `eslint.config.js`. **So, if your eslint is +`>=8.23.0`, you're 100% ready to use the new config system.** + +You might want to check out the official blog posts, + +- +- +- + +and the +[official docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new). + +The default export of `eslint-plugin-jest` is a plugin object. + +```js +import jest from 'eslint-plugin-jest'; +import jestGlobals from 'eslint-plugin-jest/globals'; + +export default [ + { + // A config for source code (non-test files) + files: ['**/*.test.{js,jsx}'], + // ... + }, + // --- snip --- + { + // The config in case you use jest snapshot test + files: ['**/*.snap'], + plugins: { + jest, + }, + processor: 'jest/.snap', + }, + { + // A config for test files (non-source-code) + files: ['**/*.test.{js,jsx}'], + languageOptions: { + globals: jestGlobals, + }, + plugins: { + jest, + }, + rules: { + // rules you want + 'jest/better-regex': 'warn', + }, + }, +]; +``` + +## Shareable configurations (legacy: `.eslintrc*`) ### Recommended @@ -193,6 +250,39 @@ While the `recommended` and `style` configurations only change in major versions the `all` configuration may change in any release and is thus unsuited for installations requiring long-term consistency. +## Shareable configurations (new: `eslint.config.js`) + +If you use the new config system (`eslint.config.js`), there're 3 shareable +configs. + +- `eslint-plugin-jest/all` +- `eslint-plugin-jest/recommended` +- `eslint-plugin-jest/style` + +**Note**: Shareable configs will enable the +[`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). + +In the new config system, `plugin:` protocol(e.g. `plugin:jest/recommended`) is +no longer valid. As eslint does not automatically import the preset config +(shareable config), you explicitly do it by yourself. + +**Note**: The new plugin object does not have `configs` property as well. + +```js +import jest from 'eslint-plugin-jest/all'; + +export default [ + { + // A config for source code (non-test files) + files: ['**/*.test.{js,jsx}'], + // ... + }, + // --- snip --- + ...jest, // This is not a plugin object, but a shareable config object(array) + // --- snip --- +]; +``` + ## Rules diff --git a/package.json b/package.json index 6e7463721..502cb4901 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,19 @@ "email": "hello@jkimbo.com", "url": "jkimbo.com" }, - "main": "lib/", + "main": "lib/legacy.js", + "exports": { + ".": { + "import": "./lib/index.js", + "require": "./lib/legacy.js" + }, + "./new": "./lib/index.js", + "./all": "./lib/configs/all.js", + "./recommended": "./lib/configs/recommended.js", + "./style": "./lib/configs/style.js", + "./globals": "./lib/globals.json", + "./globals.json": "./lib/globals.json" + }, "files": [ "docs/", "lib/" diff --git a/src/__tests__/rules.test.ts b/src/__tests__/rules.test.ts index a8c66cc18..d973f552d 100644 --- a/src/__tests__/rules.test.ts +++ b/src/__tests__/rules.test.ts @@ -1,6 +1,6 @@ import { existsSync } from 'fs'; import { resolve } from 'path'; -import plugin from '../'; +import plugin from '../legacy'; const numberOfRules = 50; const ruleNames = Object.keys(plugin.rules); diff --git a/src/configs/all.ts b/src/configs/all.ts new file mode 100644 index 000000000..c77d5cbb1 --- /dev/null +++ b/src/configs/all.ts @@ -0,0 +1,26 @@ +import globals from '../globals.json'; +import jest from '../index'; +import legacy from '../legacy'; + +export default [ + { + files: ['**/*.snap'], + plugins: { + jest, + }, + processor: 'jest/.snap', + }, + { + files: [ + '**/*.{test,spec}.{js,cjs,mjs,jsx,ts,tsx}', + '**/__tests__/*.{js,cjs,mjs,jsx,ts,tsx}', + ], + languageOptions: { + globals, + }, + plugins: { + jest, + }, + rules: legacy.configs.all.rules, + }, +]; diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts new file mode 100644 index 000000000..24c9a87fc --- /dev/null +++ b/src/configs/recommended.ts @@ -0,0 +1,12 @@ +import legacy from '../legacy'; +import all from './all'; + +const [snap, test] = all; + +export default [ + snap, + { + ...test, + rules: legacy.configs.recommended.rules, + }, +]; diff --git a/src/configs/style.ts b/src/configs/style.ts new file mode 100644 index 000000000..66d66136d --- /dev/null +++ b/src/configs/style.ts @@ -0,0 +1,12 @@ +import legacy from '../legacy'; +import all from './all'; + +const [snap, test] = all; + +export default [ + snap, + { + ...test, + rules: legacy.configs.style.rules, + }, +]; diff --git a/src/index.ts b/src/index.ts index 06e45e2e1..2e363e5f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,87 +1,6 @@ -import { readdirSync } from 'fs'; -import { join, parse } from 'path'; -import type { TSESLint } from '@typescript-eslint/utils'; -import globals from './globals.json'; -import * as snapshotProcessor from './processors/snapshot-processor'; +import legacy from './legacy'; -type RuleModule = TSESLint.RuleModule & { - meta: Required, 'docs'>>; -}; - -// v5 of `@typescript-eslint/experimental-utils` removed this -declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { - export interface RuleMetaDataDocs { - category: 'Best Practices' | 'Possible Errors'; - } -} - -// copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606 -/* istanbul ignore next */ -const interopRequireDefault = (obj: any): { default: any } => - obj && obj.__esModule ? obj : { default: obj }; - -const importDefault = (moduleName: string) => - // eslint-disable-next-line @typescript-eslint/no-require-imports - interopRequireDefault(require(moduleName)).default; - -const rulesDir = join(__dirname, 'rules'); -const excludedFiles = ['__tests__', 'detectJestVersion', 'utils']; - -const rules = readdirSync(rulesDir) - .map(rule => parse(rule).name) - .filter(rule => !excludedFiles.includes(rule)) - .reduce>( - (acc, curr) => ({ - ...acc, - [curr]: importDefault(join(rulesDir, curr)) as RuleModule, - }), - {}, - ); - -const recommendedRules = Object.entries(rules) - .filter(([, rule]) => rule.meta.docs.recommended) - .reduce( - (acc, [name, rule]) => ({ - ...acc, - [`jest/${name}`]: rule.meta.docs.recommended, - }), - {}, - ); - -const allRules = Object.entries(rules) - .filter(([, rule]) => !rule.meta.deprecated) - .reduce( - (acc, [name]) => ({ - ...acc, - [`jest/${name}`]: 'error', - }), - {}, - ); - -const createConfig = (rules: Record) => ({ - plugins: ['jest'], - env: { 'jest/globals': true }, - rules, -}); - -export = { - configs: { - all: createConfig(allRules), - recommended: createConfig(recommendedRules), - style: createConfig({ - 'jest/no-alias-methods': 'warn', - 'jest/prefer-to-be': 'error', - 'jest/prefer-to-contain': 'error', - 'jest/prefer-to-have-length': 'error', - }), - }, - environments: { - globals: { - globals, - }, - }, - processors: { - '.snap': snapshotProcessor, - }, - rules, +export default { + rules: legacy.rules, + processors: legacy.processors, }; diff --git a/src/legacy.ts b/src/legacy.ts new file mode 100644 index 000000000..06e45e2e1 --- /dev/null +++ b/src/legacy.ts @@ -0,0 +1,87 @@ +import { readdirSync } from 'fs'; +import { join, parse } from 'path'; +import type { TSESLint } from '@typescript-eslint/utils'; +import globals from './globals.json'; +import * as snapshotProcessor from './processors/snapshot-processor'; + +type RuleModule = TSESLint.RuleModule & { + meta: Required, 'docs'>>; +}; + +// v5 of `@typescript-eslint/experimental-utils` removed this +declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' { + export interface RuleMetaDataDocs { + category: 'Best Practices' | 'Possible Errors'; + } +} + +// copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606 +/* istanbul ignore next */ +const interopRequireDefault = (obj: any): { default: any } => + obj && obj.__esModule ? obj : { default: obj }; + +const importDefault = (moduleName: string) => + // eslint-disable-next-line @typescript-eslint/no-require-imports + interopRequireDefault(require(moduleName)).default; + +const rulesDir = join(__dirname, 'rules'); +const excludedFiles = ['__tests__', 'detectJestVersion', 'utils']; + +const rules = readdirSync(rulesDir) + .map(rule => parse(rule).name) + .filter(rule => !excludedFiles.includes(rule)) + .reduce>( + (acc, curr) => ({ + ...acc, + [curr]: importDefault(join(rulesDir, curr)) as RuleModule, + }), + {}, + ); + +const recommendedRules = Object.entries(rules) + .filter(([, rule]) => rule.meta.docs.recommended) + .reduce( + (acc, [name, rule]) => ({ + ...acc, + [`jest/${name}`]: rule.meta.docs.recommended, + }), + {}, + ); + +const allRules = Object.entries(rules) + .filter(([, rule]) => !rule.meta.deprecated) + .reduce( + (acc, [name]) => ({ + ...acc, + [`jest/${name}`]: 'error', + }), + {}, + ); + +const createConfig = (rules: Record) => ({ + plugins: ['jest'], + env: { 'jest/globals': true }, + rules, +}); + +export = { + configs: { + all: createConfig(allRules), + recommended: createConfig(recommendedRules), + style: createConfig({ + 'jest/no-alias-methods': 'warn', + 'jest/prefer-to-be': 'error', + 'jest/prefer-to-contain': 'error', + 'jest/prefer-to-have-length': 'error', + }), + }, + environments: { + globals: { + globals, + }, + }, + processors: { + '.snap': snapshotProcessor, + }, + rules, +}; diff --git a/tools/regenerate-docs.ts b/tools/regenerate-docs.ts index af0f4bd72..2e81cc2d2 100644 --- a/tools/regenerate-docs.ts +++ b/tools/regenerate-docs.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import type { JSONSchema, TSESLint } from '@typescript-eslint/utils'; import prettier, { Options } from 'prettier'; import { prettier as prettierRC } from '../package.json'; -import plugin from '../src/index'; +import plugin from '../src/legacy'; import { getRuleNoticeLines } from './rule-notices'; // Marker so that rule doc header (title/notices) can be automatically updated. diff --git a/tools/rule-notices.ts b/tools/rule-notices.ts index d73a09605..08d545002 100644 --- a/tools/rule-notices.ts +++ b/tools/rule-notices.ts @@ -1,4 +1,4 @@ -import plugin from '../src'; +import plugin from '../src/legacy'; enum MESSAGE_TYPE { CONFIGS = 'configs',