Skip to content

Commit 4cc2a1b

Browse files
authoredFeb 16, 2024··
feat: add should-be-fine support for flat configs (#1505)
* feat: support flat config * fix: redo flat config * docs: add note about flat config support * fix: handle snapshot processor * chore: remove unneeded cast * chore: exclude flat configs from docs * test: use property matches to reduce snapshot size
1 parent 14eafdd commit 4cc2a1b

File tree

5 files changed

+376
-26
lines changed

5 files changed

+376
-26
lines changed
 

‎.eslint-doc-generatorrc.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ const { prettier: prettierRC } = require('./package.json');
33

44
/** @type {import('eslint-doc-generator').GenerateOptions} */
55
const config = {
6-
ignoreConfig: ['all'],
6+
ignoreConfig: [
7+
'all',
8+
'flat/all',
9+
'flat/recommended',
10+
'flat/style',
11+
'flat/snapshots',
12+
],
713
ruleDocTitleFormat: 'desc-parens-name',
814
ruleDocSectionInclude: ['Rule details'],
915
ruleListColumns: [

‎README.md

+117-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ yarn add --dev eslint eslint-plugin-jest
2222

2323
## Usage
2424

25+
> [!NOTE]
26+
>
27+
> `eslint.config.js` is supported, though most of the plugin documentation still
28+
> currently uses `.eslintrc` syntax.
29+
>
30+
> Refer to the
31+
> [ESLint documentation on the new configuration file format](https://eslint.org/docs/latest/use/configure/configuration-files-new)
32+
> for more.
33+
2534
Add `jest` to the plugins section of your `.eslintrc` configuration file. You
2635
can omit the `eslint-plugin-` prefix:
2736

@@ -85,7 +94,7 @@ test-related. This means it's generally not suitable to include them in your
8594
top-level configuration as that applies to all files being linted which can
8695
include source files.
8796

88-
You can use
97+
For `.eslintrc` configs you can use
8998
[overrides](https://eslint.org/docs/user-guide/configuring/configuration-files#how-do-overrides-work)
9099
to have ESLint apply additional rules to specific files:
91100

@@ -106,6 +115,30 @@ to have ESLint apply additional rules to specific files:
106115
}
107116
```
108117

118+
For `eslint.config.js` you can use
119+
[`files` and `ignores`](https://eslint.org/docs/latest/use/configure/configuration-files-new#specifying-files-and-ignores):
120+
121+
```js
122+
const jest = require('eslint-plugin-jest');
123+
124+
module.exports = [
125+
...require('@eslint/js').configs.recommended,
126+
{
127+
files: ['test/**'],
128+
...jest.configs['flat/recommended'],
129+
rules: {
130+
...jest.configs['flat/recommended'],
131+
'jest/prefer-expect-assertions': 'off',
132+
},
133+
},
134+
// you can also configure jest rules in other objects, so long as some of the `files` match
135+
{
136+
files: ['test/**'],
137+
rules: { 'jest/prefer-expect-assertions': 'off' },
138+
},
139+
];
140+
```
141+
109142
### Jest `version` setting
110143

111144
The behaviour of some rules (specifically [`no-deprecated-functions`][]) change
@@ -145,20 +178,41 @@ module.exports = {
145178

146179
## Shareable configurations
147180

181+
> [!NOTE]
182+
>
183+
> `eslint.config.js` compatible versions of configs are available prefixed with
184+
> `flat/` and may be subject to small breaking changes while ESLint v9 is being
185+
> finalized.
186+
148187
### Recommended
149188

150189
This plugin exports a recommended configuration that enforces good testing
151190
practices.
152191

153-
To enable this configuration use the `extends` property in your `.eslintrc`
154-
config file:
192+
To enable this configuration with `.eslintrc`, use the `extends` property:
155193

156194
```json
157195
{
158196
"extends": ["plugin:jest/recommended"]
159197
}
160198
```
161199

200+
To enable this configuration with `eslint.config.js`, use
201+
`jest.configs['flat/recommended']`:
202+
203+
```js
204+
const jest = require('eslint-plugin-jest');
205+
206+
module.exports = [
207+
{
208+
files: [
209+
/* glob matching your test files */
210+
],
211+
...jest.configs['flat/recommended'],
212+
},
213+
];
214+
```
215+
162216
### Style
163217

164218
This plugin also exports a configuration named `style`, which adds some
@@ -174,9 +228,21 @@ config file:
174228
}
175229
```
176230

177-
See
178-
[ESLint documentation](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files)
179-
for more information about extending configuration files.
231+
To enable this configuration with `eslint.config.js`, use
232+
`jest.configs['flat/style']`:
233+
234+
```js
235+
const jest = require('eslint-plugin-jest');
236+
237+
module.exports = [
238+
{
239+
files: [
240+
/* glob matching your test files */
241+
],
242+
...jest.configs['flat/style'],
243+
},
244+
];
245+
```
180246

181247
### All
182248

@@ -189,10 +255,55 @@ If you want to enable all rules instead of only some you can do so by adding the
189255
}
190256
```
191257

258+
To enable this configuration with `eslint.config.js`, use
259+
`jest.configs['flat/all']`:
260+
261+
```js
262+
const jest = require('eslint-plugin-jest');
263+
264+
module.exports = [
265+
{
266+
files: [
267+
/* glob matching your test files */
268+
],
269+
...jest.configs['flat/all'],
270+
},
271+
];
272+
```
273+
192274
While the `recommended` and `style` configurations only change in major versions
193275
the `all` configuration may change in any release and is thus unsuited for
194276
installations requiring long-term consistency.
195277

278+
## Snapshot processing
279+
280+
> [!NOTE]
281+
>
282+
> This is only relevant for `eslint.config.js`
283+
284+
This plugin provides a
285+
[custom processor](https://eslint.org/docs/latest/extend/custom-processors) to
286+
allow rules to "lint" snapshot files.
287+
288+
For `.eslintrc` based configs, this is automatically enabled out of the box but
289+
must be opted into for `eslint.config.js` using the `flat/snapshots` config:
290+
291+
```js
292+
const jest = require('eslint-plugin-jest');
293+
294+
module.exports = [
295+
{
296+
...jest.configs['flat/snapshots'],
297+
rules: {
298+
'jest/no-large-snapshots': ['error', { maxSize: 1 }],
299+
},
300+
},
301+
];
302+
```
303+
304+
Unlike other configs, this includes a `files` array that matches `.snap` files
305+
meaning you can use it directly
306+
196307
## Rules
197308

198309
<!-- begin auto-generated rules list -->

‎src/__tests__/__snapshots__/rules.test.ts.snap

+176
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,182 @@ exports[`rules should export configs that refer to actual rules 1`] = `
6464
"jest/valid-title": "error",
6565
},
6666
},
67+
"flat/all": {
68+
"languageOptions": {
69+
"globals": {
70+
"afterAll": false,
71+
"afterEach": false,
72+
"beforeAll": false,
73+
"beforeEach": false,
74+
"describe": false,
75+
"expect": false,
76+
"fit": false,
77+
"it": false,
78+
"jest": false,
79+
"test": false,
80+
"xdescribe": false,
81+
"xit": false,
82+
"xtest": false,
83+
},
84+
},
85+
"plugins": {
86+
"jest": ObjectContaining {
87+
"meta": {
88+
"name": "eslint-plugin-jest",
89+
"version": "27.8.0",
90+
},
91+
},
92+
},
93+
"rules": {
94+
"jest/consistent-test-it": "error",
95+
"jest/expect-expect": "error",
96+
"jest/max-expects": "error",
97+
"jest/max-nested-describe": "error",
98+
"jest/no-alias-methods": "error",
99+
"jest/no-commented-out-tests": "error",
100+
"jest/no-conditional-expect": "error",
101+
"jest/no-conditional-in-test": "error",
102+
"jest/no-confusing-set-timeout": "error",
103+
"jest/no-deprecated-functions": "error",
104+
"jest/no-disabled-tests": "error",
105+
"jest/no-done-callback": "error",
106+
"jest/no-duplicate-hooks": "error",
107+
"jest/no-export": "error",
108+
"jest/no-focused-tests": "error",
109+
"jest/no-hooks": "error",
110+
"jest/no-identical-title": "error",
111+
"jest/no-interpolation-in-snapshots": "error",
112+
"jest/no-jasmine-globals": "error",
113+
"jest/no-large-snapshots": "error",
114+
"jest/no-mocks-import": "error",
115+
"jest/no-restricted-jest-methods": "error",
116+
"jest/no-restricted-matchers": "error",
117+
"jest/no-standalone-expect": "error",
118+
"jest/no-test-prefixes": "error",
119+
"jest/no-test-return-statement": "error",
120+
"jest/no-untyped-mock-factory": "error",
121+
"jest/prefer-called-with": "error",
122+
"jest/prefer-comparison-matcher": "error",
123+
"jest/prefer-each": "error",
124+
"jest/prefer-equality-matcher": "error",
125+
"jest/prefer-expect-assertions": "error",
126+
"jest/prefer-expect-resolves": "error",
127+
"jest/prefer-hooks-in-order": "error",
128+
"jest/prefer-hooks-on-top": "error",
129+
"jest/prefer-lowercase-title": "error",
130+
"jest/prefer-mock-promise-shorthand": "error",
131+
"jest/prefer-snapshot-hint": "error",
132+
"jest/prefer-spy-on": "error",
133+
"jest/prefer-strict-equal": "error",
134+
"jest/prefer-to-be": "error",
135+
"jest/prefer-to-contain": "error",
136+
"jest/prefer-to-have-length": "error",
137+
"jest/prefer-todo": "error",
138+
"jest/require-hook": "error",
139+
"jest/require-to-throw-message": "error",
140+
"jest/require-top-level-describe": "error",
141+
"jest/unbound-method": "error",
142+
"jest/valid-describe-callback": "error",
143+
"jest/valid-expect": "error",
144+
"jest/valid-expect-in-promise": "error",
145+
"jest/valid-title": "error",
146+
},
147+
},
148+
"flat/recommended": {
149+
"languageOptions": {
150+
"globals": {
151+
"afterAll": false,
152+
"afterEach": false,
153+
"beforeAll": false,
154+
"beforeEach": false,
155+
"describe": false,
156+
"expect": false,
157+
"fit": false,
158+
"it": false,
159+
"jest": false,
160+
"test": false,
161+
"xdescribe": false,
162+
"xit": false,
163+
"xtest": false,
164+
},
165+
},
166+
"plugins": {
167+
"jest": ObjectContaining {
168+
"meta": {
169+
"name": "eslint-plugin-jest",
170+
"version": "27.8.0",
171+
},
172+
},
173+
},
174+
"rules": {
175+
"jest/expect-expect": "warn",
176+
"jest/no-alias-methods": "error",
177+
"jest/no-commented-out-tests": "warn",
178+
"jest/no-conditional-expect": "error",
179+
"jest/no-deprecated-functions": "error",
180+
"jest/no-disabled-tests": "warn",
181+
"jest/no-done-callback": "error",
182+
"jest/no-export": "error",
183+
"jest/no-focused-tests": "error",
184+
"jest/no-identical-title": "error",
185+
"jest/no-interpolation-in-snapshots": "error",
186+
"jest/no-jasmine-globals": "error",
187+
"jest/no-mocks-import": "error",
188+
"jest/no-standalone-expect": "error",
189+
"jest/no-test-prefixes": "error",
190+
"jest/valid-describe-callback": "error",
191+
"jest/valid-expect": "error",
192+
"jest/valid-expect-in-promise": "error",
193+
"jest/valid-title": "error",
194+
},
195+
},
196+
"flat/snapshots": {
197+
"files": [
198+
"**/*.snap",
199+
],
200+
"plugins": {
201+
"jest": ObjectContaining {
202+
"meta": {
203+
"name": "eslint-plugin-jest",
204+
"version": "27.8.0",
205+
},
206+
},
207+
},
208+
"processor": "jest/snapshots",
209+
},
210+
"flat/style": {
211+
"languageOptions": {
212+
"globals": {
213+
"afterAll": false,
214+
"afterEach": false,
215+
"beforeAll": false,
216+
"beforeEach": false,
217+
"describe": false,
218+
"expect": false,
219+
"fit": false,
220+
"it": false,
221+
"jest": false,
222+
"test": false,
223+
"xdescribe": false,
224+
"xit": false,
225+
"xtest": false,
226+
},
227+
},
228+
"plugins": {
229+
"jest": ObjectContaining {
230+
"meta": {
231+
"name": "eslint-plugin-jest",
232+
"version": "27.8.0",
233+
},
234+
},
235+
},
236+
"rules": {
237+
"jest/no-alias-methods": "warn",
238+
"jest/prefer-to-be": "error",
239+
"jest/prefer-to-contain": "error",
240+
"jest/prefer-to-have-length": "error",
241+
},
242+
},
67243
"recommended": {
68244
"env": {
69245
"jest/globals": true,

‎src/__tests__/rules.test.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,38 @@ describe('rules', () => {
4848
});
4949

5050
it('should export configs that refer to actual rules', () => {
51+
const expectJestPlugin = expect.objectContaining({
52+
meta: {
53+
name: 'eslint-plugin-jest',
54+
version: '27.8.0',
55+
},
56+
});
57+
5158
const recommendedConfigs = plugin.configs;
5259

53-
expect(recommendedConfigs).toMatchSnapshot();
60+
expect(recommendedConfigs).toMatchSnapshot({
61+
'flat/recommended': { plugins: { jest: expectJestPlugin } },
62+
'flat/style': { plugins: { jest: expectJestPlugin } },
63+
'flat/all': { plugins: { jest: expectJestPlugin } },
64+
'flat/snapshots': { plugins: { jest: expectJestPlugin } },
65+
});
5466
expect(Object.keys(recommendedConfigs)).toEqual([
5567
'all',
5668
'recommended',
5769
'style',
70+
'flat/all',
71+
'flat/recommended',
72+
'flat/style',
73+
'flat/snapshots',
5874
]);
5975
expect(Object.keys(recommendedConfigs.all.rules)).toHaveLength(
6076
ruleNames.length - deprecatedRules.length,
6177
);
78+
expect(Object.keys(recommendedConfigs['flat/all'].rules)).toHaveLength(
79+
ruleNames.length - deprecatedRules.length,
80+
);
6281
const allConfigRules = Object.values(recommendedConfigs)
63-
.map(config => Object.keys(config.rules))
82+
.map(config => Object.keys(config.rules ?? {}))
6483
.reduce((previousValue, currentValue) => [
6584
...previousValue,
6685
...currentValue,

‎src/index.ts

+55-17
Original file line numberDiff line numberDiff line change
@@ -79,31 +79,69 @@ const allRules = Object.fromEntries<TSESLint.Linter.RuleLevel>(
7979
.map(([name]) => [`jest/${name}`, 'error']),
8080
);
8181

82-
const createConfig = (rules: Record<string, TSESLint.Linter.RuleLevel>) => ({
83-
plugins: ['jest'],
84-
env: { 'jest/globals': true },
85-
rules,
86-
});
87-
88-
export = {
82+
const plugin = {
8983
meta: { name: packageName, version: packageVersion },
90-
configs: {
91-
all: createConfig(allRules),
92-
recommended: createConfig(recommendedRules),
93-
style: createConfig({
94-
'jest/no-alias-methods': 'warn',
95-
'jest/prefer-to-be': 'error',
96-
'jest/prefer-to-contain': 'error',
97-
'jest/prefer-to-have-length': 'error',
98-
}),
99-
},
84+
// ugly cast for now to keep TypeScript happy since
85+
// we don't have types for flat config yet
86+
configs: {} as Record<
87+
| 'all'
88+
| 'recommended'
89+
| 'style'
90+
| 'flat/all'
91+
| 'flat/recommended'
92+
| 'flat/style'
93+
| 'flat/snapshots',
94+
Pick<Required<TSESLint.Linter.Config>, 'rules'>
95+
>,
10096
environments: {
10197
globals: {
10298
globals,
10399
},
104100
},
105101
processors: {
102+
snapshots: snapshotProcessor,
106103
'.snap': snapshotProcessor,
107104
},
108105
rules,
109106
};
107+
108+
const createRCConfig = (rules: Record<string, TSESLint.Linter.RuleLevel>) => ({
109+
plugins: ['jest'],
110+
env: { 'jest/globals': true },
111+
rules,
112+
});
113+
114+
const createFlatConfig = (
115+
rules: Record<string, TSESLint.Linter.RuleLevel>,
116+
) => ({
117+
plugins: { jest: plugin },
118+
languageOptions: { globals },
119+
rules,
120+
});
121+
122+
plugin.configs = {
123+
all: createRCConfig(allRules),
124+
recommended: createRCConfig(recommendedRules),
125+
style: createRCConfig({
126+
'jest/no-alias-methods': 'warn',
127+
'jest/prefer-to-be': 'error',
128+
'jest/prefer-to-contain': 'error',
129+
'jest/prefer-to-have-length': 'error',
130+
}),
131+
'flat/all': createFlatConfig(allRules),
132+
'flat/recommended': createFlatConfig(recommendedRules),
133+
'flat/style': createFlatConfig({
134+
'jest/no-alias-methods': 'warn',
135+
'jest/prefer-to-be': 'error',
136+
'jest/prefer-to-contain': 'error',
137+
'jest/prefer-to-have-length': 'error',
138+
}),
139+
'flat/snapshots': {
140+
// @ts-expect-error this is introduced in flat config
141+
files: ['**/*.snap'],
142+
plugins: { jest: plugin },
143+
processor: 'jest/snapshots',
144+
},
145+
};
146+
147+
export = plugin;

0 commit comments

Comments
 (0)
Please sign in to comment.