eslint-plugin-jest
Advanced tools
Comparing version 23.20.0 to 27.2.3
@@ -1,3 +0,8 @@ | ||
# Have control over `test` and `it` usages (`consistent-test-it`) | ||
# Enforce `test` and `it` usage conventions (`consistent-test-it`) | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
Jest allows you to choose how you want to define your tests, using the `it` or | ||
@@ -9,5 +14,7 @@ the `test` keywords, with multiple permutations for each: | ||
## Rule details | ||
This rule gives you control over the usage of these keywords in your codebase. | ||
## Rule Details | ||
## Options | ||
@@ -63,3 +70,3 @@ This rule can be configured as follows | ||
it('foo'); // valid | ||
describe('foo', function() { | ||
describe('foo', function () { | ||
test('bar'); // valid | ||
@@ -69,3 +76,3 @@ }); | ||
test('foo'); // invalid | ||
describe('foo', function() { | ||
describe('foo', function () { | ||
it('bar'); // invalid | ||
@@ -75,4 +82,2 @@ }); | ||
### Default configuration | ||
The default configuration forces all top-level tests to use `test` and all tests | ||
@@ -85,3 +90,3 @@ nested within `describe` to use `it`. | ||
test('foo'); // valid | ||
describe('foo', function() { | ||
describe('foo', function () { | ||
it('bar'); // valid | ||
@@ -91,5 +96,5 @@ }); | ||
it('foo'); // invalid | ||
describe('foo', function() { | ||
describe('foo', function () { | ||
test('bar'); // invalid | ||
}); | ||
``` |
# Enforce assertion to be made in a test body (`expect-expect`) | ||
β οΈ This rule _warns_ in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Ensure that there is at least one `expect` call made in a test. | ||
@@ -37,3 +42,4 @@ | ||
{ | ||
"assertFunctionNames": ["expect"] | ||
"assertFunctionNames": ["expect"], | ||
"additionalTestBlockFunctions": [] | ||
} | ||
@@ -60,5 +66,3 @@ ] | ||
test('returns sum', () => { | ||
expectSaga(addSaga, 1, 1) | ||
.returns(2) | ||
.run(); | ||
expectSaga(addSaga, 1, 1).returns(2).run(); | ||
}); | ||
@@ -77,9 +81,7 @@ ``` | ||
test('returns sum', () => { | ||
expectSaga(addSaga, 1, 1) | ||
.returns(2) | ||
.run(); | ||
expectSaga(addSaga, 1, 1).returns(2).run(); | ||
}); | ||
``` | ||
Since the string is compiled into aa regular expression, you'll need to escape | ||
Since the string is compiled into a regular expression, you'll need to escape | ||
special characters such as `$` with a double backslash: | ||
@@ -97,6 +99,6 @@ | ||
[SuperTest](https://www.npmjs.com/package/supertest) with the | ||
`{ "assertFunctionNames": ["expect", "request.*.expect"] }` option: | ||
`{ "assertFunctionNames": ["expect", "request.**.expect"] }` option: | ||
```js | ||
/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "request.*.expect"] }] */ | ||
/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "request.**.expect"] }] */ | ||
const request = require('supertest'); | ||
@@ -107,10 +109,47 @@ const express = require('express'); | ||
describe('GET /user', function() { | ||
it('responds with json', function(done) { | ||
request(app) | ||
.get('/user') | ||
.expect('Content-Type', /json/) | ||
.expect(200, done); | ||
describe('GET /user', function () { | ||
it('responds with json', function (done) { | ||
request(app).get('/user').expect('Content-Type', /json/).expect(200, done); | ||
}); | ||
}); | ||
``` | ||
### `additionalTestBlockFunctions` | ||
This array can be used to specify the names of functions that should also be | ||
treated as test blocks: | ||
```json | ||
{ | ||
"rules": { | ||
"jest/expect-expect": [ | ||
"error", | ||
{ "additionalTestBlockFunctions": ["theoretically"] } | ||
] | ||
} | ||
} | ||
``` | ||
The following is _correct_ when using the above configuration: | ||
```js | ||
import theoretically from 'jest-theories'; | ||
describe('NumberToLongString', () => { | ||
const theories = [ | ||
{ input: 100, expected: 'One hundred' }, | ||
{ input: 1000, expected: 'One thousand' }, | ||
{ input: 10000, expected: 'Ten thousand' }, | ||
{ input: 100000, expected: 'One hundred thousand' }, | ||
]; | ||
theoretically( | ||
'the number {input} is correctly translated to string', | ||
theories, | ||
theory => { | ||
const output = NumberToLongString(theory.input); | ||
expect(output).toBe(theory.expected); | ||
}, | ||
); | ||
}); | ||
``` |
# Disallow alias methods (`no-alias-methods`) | ||
πΌβ οΈ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
This rule _warns_ in the π¨ `style` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
> These aliases are going to be removed in the next major version of Jest - see | ||
> <https://github.com/jestjs/jest/issues/13164> for more | ||
Several Jest methods have alias names, such as `toThrow` having the alias of | ||
@@ -14,4 +27,2 @@ `toThrowError`. This rule ensures that only the canonical name as used in the | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -18,0 +29,0 @@ |
# Disallow commented out tests (`no-commented-out-tests`) | ||
β οΈ This rule _warns_ in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
This rule raises a warning about commented out tests. It's similar to | ||
no-disabled-tests rule. | ||
## Rule Details | ||
## Rule details | ||
@@ -8,0 +13,0 @@ The rule uses fuzzy matching to do its best to determine what constitutes a |
@@ -1,12 +0,20 @@ | ||
# Prevent calling `expect` conditionally (`no-conditional-expect`) | ||
# Disallow calling `expect` conditionally (`no-conditional-expect`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
This rule prevents the use of `expect` in conditional blocks, such as `if`s & | ||
`catch`s. | ||
## Rule Details | ||
This includes using `expect` in callbacks to functions named `catch`, which are | ||
assumed to be promises. | ||
Jest considered a test to have failed if it throws an error, rather than on if | ||
any particular function is called, meaning conditional calls to `expect` could | ||
result in tests silently being skipped. | ||
## Rule details | ||
Jest only considers a test to have failed if it throws an error, meaning if | ||
calls to assertion functions like `expect` occur in conditional code such as a | ||
`catch` statement, tests can end up passing but not actually test anything. | ||
Additionally, conditionals tend to make tests more brittle and complex, as they | ||
@@ -40,2 +48,6 @@ increase the amount of mental thinking needed to understand what is actually | ||
}); | ||
it('throws an error', async () => { | ||
await foo().catch(error => expect(error).toBeInstanceOf(error)); | ||
}); | ||
``` | ||
@@ -71,2 +83,60 @@ | ||
}); | ||
it('throws an error', async () => { | ||
await expect(foo).rejects.toThrow(Error); | ||
}); | ||
``` | ||
### How to catch a thrown error for testing without violating this rule | ||
A common situation that comes up with this rule is when wanting to test | ||
properties on a thrown error, as Jest's `toThrow` matcher only checks the | ||
`message` property. | ||
Most people write something like this: | ||
```typescript | ||
describe('when the http request fails', () => { | ||
it('includes the status code in the error', async () => { | ||
try { | ||
await makeRequest(url); | ||
} catch (error) { | ||
expect(error).toHaveProperty('statusCode', 404); | ||
} | ||
}); | ||
}); | ||
``` | ||
As stated above, the problem with this is that if `makeRequest()` doesn't throw | ||
the test will still pass as if the `expect` had been called. | ||
While you can use `expect.assertions` & `expect.hasAssertions` for these | ||
situations, they only work with `expect`. | ||
A better way to handle this situation is to introduce a wrapper to handle the | ||
catching, and otherwise return a specific "no error thrown" error if nothing is | ||
thrown by the wrapped function: | ||
```typescript | ||
class NoErrorThrownError extends Error {} | ||
const getError = async <TError>(call: () => unknown): Promise<TError> => { | ||
try { | ||
await call(); | ||
throw new NoErrorThrownError(); | ||
} catch (error: unknown) { | ||
return error as TError; | ||
} | ||
}; | ||
describe('when the http request fails', () => { | ||
it('includes the status code in the error', async () => { | ||
const error = await getError(async () => makeRequest(url)); | ||
// check that the returned error wasn't that no error was thrown | ||
expect(error).not.toBeInstanceOf(NoErrorThrownError); | ||
expect(error).toHaveProperty('statusCode', 404); | ||
}); | ||
}); | ||
``` |
# Disallow use of deprecated functions (`no-deprecated-functions`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
Over the years Jest has accrued some debt in the form of functions that have | ||
@@ -9,2 +17,7 @@ either been renamed for clarity, or replaced with more powerful APIs. | ||
This rule requires knowing which version of Jest you're using - see | ||
[this section of the readme](../../README.md#jest-version-setting) for details | ||
on how that is obtained automatically and how you can explicitly provide a | ||
version if needed. | ||
## Rule details | ||
@@ -19,9 +32,8 @@ | ||
This function was renamed to `resetModules` in Jest 15, and is scheduled for | ||
removal in Jest 27. | ||
This function was renamed to `resetModules` in Jest 15 and removed in Jest 27. | ||
### `jest.addMatchers` | ||
This function was replaced with `expect.extend` in Jest 17, and is scheduled for | ||
removal in Jest 27. | ||
This function was replaced with `expect.extend` in Jest 17 and removed in | ||
Jest 27. | ||
@@ -37,9 +49,8 @@ ### `require.requireActual` & `require.requireMock` | ||
for type checkers to handle, and their use via `require` deprecated. Finally, | ||
the release of Jest 26 saw them removed from the `require` function all | ||
together. | ||
the release of Jest 26 saw them removed from the `require` function altogether. | ||
### `jest.runTimersToTime` | ||
This function was renamed to `advanceTimersByTime` in Jest 22, and is scheduled | ||
for removal in Jest 27. | ||
This function was renamed to `advanceTimersByTime` in Jest 22 and removed in | ||
Jest 27. | ||
@@ -49,2 +60,2 @@ ### `jest.genMockFromModule` | ||
This function was renamed to `createMockFromModule` in Jest 26, and is scheduled | ||
for removal in a future version of Jest. | ||
for removal in Jest 30. |
# Disallow disabled tests (`no-disabled-tests`) | ||
β οΈ This rule _warns_ in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Jest has a feature that allows you to temporarily mark tests as disabled. This | ||
@@ -10,3 +15,3 @@ feature is often helpful while debugging or to create placeholders for future | ||
## Rule Details | ||
## Rule details | ||
@@ -13,0 +18,0 @@ There are a number of ways to disable tests in Jest: |
# Disallow duplicate setup and teardown hooks (`no-duplicate-hooks`) | ||
<!-- end auto-generated rule header --> | ||
A `describe` block should not contain duplicate hooks. | ||
## Rule Details | ||
## Rule details | ||
@@ -7,0 +9,0 @@ Examples of **incorrect** code for this rule |
# Disallow using `exports` in files containing tests (`no-export`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Prevents using `exports` if a file has one or more tests in it. | ||
## Rule Details | ||
## Rule details | ||
@@ -18,3 +23,3 @@ This rule aims to eliminate duplicate runs of tests by exporting things from | ||
module.exports = function() {}; | ||
module.exports = function () {}; | ||
@@ -21,0 +26,0 @@ module.exports = { |
# Disallow focused tests (`no-focused-tests`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π‘ This rule is manually fixable by | ||
[editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). | ||
<!-- end auto-generated rule header --> | ||
<!-- prettier-ignore --> | ||
Jest has a feature that allows you to focus tests by appending `.only` or | ||
@@ -12,6 +21,6 @@ prepending `f` to a test-suite or a test-case. This feature is really helpful to | ||
## Rule Details | ||
## Rule details | ||
This rule looks for every `describe.only`, `it.only`, `test.only`, `fdescribe`, | ||
`fit` and `ftest` occurrences within the source code. Of course there are some | ||
and `fit` occurrences within the source code. Of course there are some | ||
edge-cases which canβt be detected by this rule e.g.: | ||
@@ -35,9 +44,5 @@ | ||
fit('foo', () => {}); | ||
ftest('bar', () => {}); | ||
fit.each` | ||
table | ||
`(); | ||
ftest.each` | ||
table | ||
`(); | ||
``` | ||
@@ -44,0 +49,0 @@ |
# Disallow setup and teardown hooks (`no-hooks`) | ||
<!-- end auto-generated rule header --> | ||
Jest provides global functions for setup and teardown tasks, which are called | ||
@@ -7,3 +9,3 @@ before/after each test case and each test suite. The use of these hooks promotes | ||
## Rule Details | ||
## Rule details | ||
@@ -175,3 +177,2 @@ This rule reports for the following function calls: | ||
- [Jest docs - Setup and Teardown](https://facebook.github.io/jest/docs/en/setup-teardown.html) | ||
- [@jamiebuilds Twitter thread](https://twitter.com/jamiebuilds/status/954906997169664000) | ||
- [Jest docs - Setup and Teardown](https://jestjs.io/docs/setup-teardown) |
# Disallow identical titles (`no-identical-title`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Having identical titles for two different tests or test suites may create | ||
@@ -8,5 +13,5 @@ confusion. For example, when a test with the same title as another test in the | ||
## Rule Details | ||
## Rule details | ||
This rule looks at the title of every test and test suites. It will report when | ||
This rule looks at the title of every test and test suite. It will report when | ||
two test suites or two test cases at the same level of a test suite have the | ||
@@ -13,0 +18,0 @@ same title. |
# Disallow conditional logic (`no-if`) | ||
β This rule is deprecated. It was replaced by | ||
[`jest/no-conditional-in-test`](no-conditional-in-test.md). | ||
<!-- end auto-generated rule header --> | ||
Conditional logic in tests is usually an indication that a test is attempting to | ||
@@ -11,3 +16,3 @@ cover too much, and not testing the logic it intends to. Each branch of code | ||
## Rule Details | ||
## Rule details | ||
@@ -14,0 +19,0 @@ This rule prevents the use of if/ else statements and conditional (ternary) |
# Disallow string interpolation inside snapshots (`no-interpolation-in-snapshots`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Prevents the use of string interpolations in snapshots. | ||
## Rule Details | ||
## Rule details | ||
@@ -7,0 +12,0 @@ Interpolation prevents snapshots from being updated. Instead, properties should |
# Disallow Jasmine globals (`no-jasmine-globals`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
`jest` uses `jasmine` as a test runner. A side effect of this is that both a | ||
@@ -10,8 +18,8 @@ `jasmine` object, and some jasmine-specific globals, are exposed to the test | ||
### Rule details | ||
## Rule details | ||
This rule reports on any usage of Jasmine globals which is not ported to Jest, | ||
and suggests alternative from Jest's own API. | ||
This rule reports on any usage of Jasmine globals, which is not ported to Jest, | ||
and suggests alternatives from Jest's own API. | ||
### Default configuration | ||
## Examples | ||
@@ -18,0 +26,0 @@ The following patterns are considered warnings: |
@@ -1,3 +0,5 @@ | ||
# disallow large snapshots (`no-large-snapshots`) | ||
# Disallow large snapshots (`no-large-snapshots`) | ||
<!-- end auto-generated rule header --> | ||
When using Jest's snapshot capability one should be mindful of the size of | ||
@@ -25,4 +27,8 @@ created snapshots. As a general best practice snapshots should be limited in | ||
## Rule Details | ||
In order to check external snapshots, you must also have `eslint` check files | ||
with the `.snap` extension by either passing `--ext snap` on the command line or | ||
by explicitly specifying `.snap` in `overrides`. | ||
## Rule details | ||
This rule looks at all Jest inline and external snapshots (files with `.snap` | ||
@@ -122,4 +128,4 @@ extension) and validates that each stored snapshot within those files does not | ||
[External Snapshots](https://jestjs.io/docs/en/snapshot-testing#snapshot-testing-with-jest). | ||
If only `maxSize` is provided on options, the value of `maxSize` will be used to | ||
both snapshot types (Inline and External). | ||
If only `maxSize` is provided on options, the value of `maxSize` will be used | ||
for both snapshot types (Inline and External). | ||
@@ -126,0 +132,0 @@ Since `eslint-disable` comments are not preserved by Jest when updating |
# Disallow manually importing from `__mocks__` (`no-mocks-import`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
When using `jest.mock`, your tests (just like the code being tested) should | ||
@@ -17,3 +22,3 @@ import from `./x`, not `./__mocks__/x`. Not following this rule can lead to | ||
### Rule details | ||
## Rule details | ||
@@ -20,0 +25,0 @@ This rule reports imports from a path containing a `__mocks__` component. |
# Disallow specific matchers & modifiers (`no-restricted-matchers`) | ||
<!-- end auto-generated rule header --> | ||
You may want to ban specific matchers & modifiers from being used. | ||
## Rule details | ||
This rule bans specific matchers & modifiers from being used, and can suggest | ||
alternatives. | ||
## Rule Details | ||
## Options | ||
@@ -11,4 +17,5 @@ Bans are expressed in the form of a map, with the value being either a string | ||
Both matchers, modifiers, and chains of the two are checked, allowing for | ||
specific variations of a matcher to be banned if desired. | ||
Bans are checked against the start of the `expect` chain - this means that to | ||
ban a specific matcher entirely you must specify all six permutations, but | ||
allows you to ban modifiers as well. | ||
@@ -26,3 +33,8 @@ By default, this map is empty, meaning no matchers or modifiers are banned. | ||
"resolves": "Use `expect(await promise)` instead.", | ||
"not.toHaveBeenCalledWith": null | ||
"toHaveBeenCalledWith": null, | ||
"not.toHaveBeenCalledWith": null, | ||
"resolves.toHaveBeenCalledWith": null, | ||
"rejects.toHaveBeenCalledWith": null, | ||
"resolves.not.toHaveBeenCalledWith": null, | ||
"rejects.not.toHaveBeenCalledWith": null | ||
} | ||
@@ -37,2 +49,3 @@ ] | ||
it('is false', () => { | ||
// if this has a modifer (i.e. `not.toBeFalsy`), it would be considered fine | ||
expect(a).toBeFalsy(); | ||
@@ -42,2 +55,3 @@ }); | ||
it('resolves', async () => { | ||
// all uses of this modifier are disallowed, regardless of matcher | ||
await expect(myPromise()).resolves.toBe(true); | ||
@@ -48,2 +62,3 @@ }); | ||
it('does not upload the file', async () => { | ||
// all uses of this matcher are disallowed | ||
expect(uploadFileMock).not.toHaveBeenCalledWith('file.name'); | ||
@@ -50,0 +65,0 @@ }); |
# Disallow using `expect` outside of `it` or `test` blocks (`no-standalone-expect`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Prevents `expect` statements outside of a `test` or `it` block. An `expect` | ||
@@ -7,9 +12,9 @@ within a helper function (but outside of a `test` or `it` block) will not | ||
## Rule Details | ||
## Rule details | ||
This rule aims to eliminate `expect` statements that will not be executed. An | ||
`expect` inside of a `describe` block but outside of a `test` or `it` block or | ||
outside of a `describe` will not execute and therefore will trigger this rule. | ||
It is viable, however, to have an `expect` in a helper function that is called | ||
from within a `test` or `it` block so `expect` statements in a function will not | ||
outside a `describe` will not execute and therefore will trigger this rule. It | ||
is viable, however, to have an `expect` in a helper function that is called from | ||
within a `test` or `it` block so `expect` statements in a function will not | ||
trigger this rule. | ||
@@ -65,3 +70,3 @@ | ||
\*Note that this rule will not trigger if the helper function is never used even | ||
thought the `expect` will not execute. Rely on a rule like no-unused-vars for | ||
though the `expect` will not execute. Rely on a rule like no-unused-vars for | ||
this case. | ||
@@ -68,0 +73,0 @@ |
@@ -1,3 +0,11 @@ | ||
# Use `.only` and `.skip` over `f` and `x` (`no-test-prefixes`) | ||
# Require using `.only` and `.skip` over `f` and `x` (`no-test-prefixes`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
Jest allows you to choose how you want to define focused and skipped tests, with | ||
@@ -4,0 +12,0 @@ multiple permutations for each: |
# Disallow explicitly returning from tests (`no-test-return-statement`) | ||
<!-- end auto-generated rule header --> | ||
Tests in Jest should be void and not return values. | ||
@@ -10,4 +12,3 @@ | ||
This rule triggers a warning if you use a return statement inside of a test | ||
body. | ||
This rule triggers a warning if you use a return statement inside a test body. | ||
@@ -19,3 +20,3 @@ ```js | ||
it('noop', function() {}); | ||
it('noop', function () {}); | ||
@@ -32,3 +33,3 @@ test('noop', () => {}); | ||
it('one', function() { | ||
it('one', function () { | ||
expect(1).toBe(1); | ||
@@ -47,5 +48,5 @@ }); | ||
it('returning a promise', function() { | ||
it('returning a promise', function () { | ||
return new Promise(res => setTimeout(res, 100)).then(() => expect(1).toBe(1)); | ||
}); | ||
``` |
# Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` (`prefer-called-with`) | ||
<!-- end auto-generated rule header --> | ||
The `toBeCalled()` matcher is used to assert that a mock function has been | ||
@@ -4,0 +6,0 @@ called one or more times, without checking the arguments passed. The assertion |
# Suggest using `expect.assertions()` OR `expect.hasAssertions()` (`prefer-expect-assertions`) | ||
π‘ This rule is manually fixable by | ||
[editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). | ||
<!-- end auto-generated rule header --> | ||
Ensure every test to have either `expect.assertions(<number of assertions>)` OR | ||
@@ -30,4 +35,2 @@ `expect.hasAssertions()` as its first expression. | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -59,1 +62,169 @@ | ||
``` | ||
## Options | ||
This rule can be configured to only check tests that match certain patterns that | ||
typically look like `expect` calls might be missed, such as in promises or | ||
loops. | ||
By default, none of these options are enabled meaning the rule checks _every_ | ||
test for a call to either `expect.hasAssertions` or `expect.assertions`. If any | ||
of the options are enabled the rule checks any test that matches _at least one_ | ||
of the patterns represented by the enabled options (think "OR" rather than | ||
"AND"). | ||
#### `onlyFunctionsWithAsyncKeyword` | ||
When `true`, this rule will only warn for tests that use the `async` keyword. | ||
```json | ||
{ | ||
"rules": { | ||
"jest/prefer-expect-assertions": [ | ||
"warn", | ||
{ "onlyFunctionsWithAsyncKeyword": true } | ||
] | ||
} | ||
} | ||
``` | ||
When `onlyFunctionsWithAsyncKeyword` option is set to `true`, the following | ||
pattern would be a warning: | ||
```js | ||
test('my test', async () => { | ||
const result = await someAsyncFunc(); | ||
expect(result).toBe('foo'); | ||
}); | ||
``` | ||
While the following patterns would not be considered warnings: | ||
```js | ||
test('my test', () => { | ||
const result = someFunction(); | ||
expect(result).toBe('foo'); | ||
}); | ||
test('my test', async () => { | ||
expect.assertions(1); | ||
const result = await someAsyncFunc(); | ||
expect(result).toBe('foo'); | ||
}); | ||
``` | ||
#### `onlyFunctionsWithExpectInLoop` | ||
When `true`, this rule will only warn for tests that have `expect` calls within | ||
a native loop. | ||
```json | ||
{ | ||
"rules": { | ||
"jest/prefer-expect-assertions": [ | ||
"warn", | ||
{ "onlyFunctionsWithExpectInLoop": true } | ||
] | ||
} | ||
} | ||
``` | ||
Examples of **incorrect** code when `'onlyFunctionsWithExpectInLoop'` is `true`: | ||
```js | ||
describe('getNumbers', () => { | ||
it('only returns numbers that are greater than zero', () => { | ||
const numbers = getNumbers(); | ||
for (const number in numbers) { | ||
expect(number).toBeGreaterThan(0); | ||
} | ||
}); | ||
}); | ||
``` | ||
Examples of **correct** code when `'onlyFunctionsWithExpectInLoop'` is `true`: | ||
```js | ||
describe('getNumbers', () => { | ||
it('only returns numbers that are greater than zero', () => { | ||
expect.hasAssertions(); | ||
const numbers = getNumbers(); | ||
for (const number in numbers) { | ||
expect(number).toBeGreaterThan(0); | ||
} | ||
}); | ||
it('returns more than one number', () => { | ||
expect(getNumbers().length).toBeGreaterThan(1); | ||
}); | ||
}); | ||
``` | ||
#### `onlyFunctionsWithExpectInCallback` | ||
When `true`, this rule will only warn for tests that have `expect` calls within | ||
a callback. | ||
```json | ||
{ | ||
"rules": { | ||
"jest/prefer-expect-assertions": [ | ||
"warn", | ||
{ "onlyFunctionsWithExpectInCallback": true } | ||
] | ||
} | ||
} | ||
``` | ||
Examples of **incorrect** code when `'onlyFunctionsWithExpectInCallback'` is | ||
`true`: | ||
```js | ||
describe('getNumbers', () => { | ||
it('only returns numbers that are greater than zero', () => { | ||
const numbers = getNumbers(); | ||
getNumbers().forEach(number => { | ||
expect(number).toBeGreaterThan(0); | ||
}); | ||
}); | ||
}); | ||
describe('/users', () => { | ||
it.each([1, 2, 3])('returns ok', id => { | ||
client.get(`/users/${id}`, response => { | ||
expect(response.status).toBe(200); | ||
}); | ||
}); | ||
}); | ||
``` | ||
Examples of **correct** code when `'onlyFunctionsWithExpectInCallback'` is | ||
`true`: | ||
```js | ||
describe('getNumbers', () => { | ||
it('only returns numbers that are greater than zero', () => { | ||
expect.hasAssertions(); | ||
const numbers = getNumbers(); | ||
getNumbers().forEach(number => { | ||
expect(number).toBeGreaterThan(0); | ||
}); | ||
}); | ||
}); | ||
describe('/users', () => { | ||
it.each([1, 2, 3])('returns ok', id => { | ||
expect.assertions(1); | ||
client.get(`/users/${id}`, response => { | ||
expect(response.status).toBe(200); | ||
}); | ||
}); | ||
}); | ||
``` |
# Suggest having hooks before any test cases (`prefer-hooks-on-top`) | ||
All hooks should be defined before the start of the tests | ||
<!-- end auto-generated rule header --> | ||
## Rule Details | ||
While hooks can be setup anywhere in a test file, they are always called in a | ||
specific order, which means it can be confusing if they're intermixed with test | ||
cases. | ||
This rule helps to ensure that hooks are always defined before test cases. | ||
## Rule details | ||
Examples of **incorrect** code for this rule | ||
@@ -14,43 +20,47 @@ | ||
beforeEach(() => { | ||
//some hook code | ||
seedMyDatabase(); | ||
}); | ||
test('bar', () => { | ||
some_fn(); | ||
it('accepts this input', () => { | ||
// ... | ||
}); | ||
beforeAll(() => { | ||
//some hook code | ||
createMyDatabase(); | ||
}); | ||
test('bar', () => { | ||
some_fn(); | ||
it('returns that value', () => { | ||
// ... | ||
}); | ||
}); | ||
// Nested describe scenario | ||
describe('foo', () => { | ||
beforeAll(() => { | ||
//some hook code | ||
}); | ||
test('bar', () => { | ||
some_fn(); | ||
}); | ||
describe('inner_foo', () => { | ||
describe('when the database has specific values', () => { | ||
const specificValue = '...'; | ||
beforeEach(() => { | ||
//some hook code | ||
seedMyDatabase(specificValue); | ||
}); | ||
test('inner bar', () => { | ||
some_fn(); | ||
it('accepts that input', () => { | ||
// ... | ||
}); | ||
test('inner bar', () => { | ||
some_fn(); | ||
it('throws an error', () => { | ||
// ... | ||
}); | ||
beforeAll(() => { | ||
//some hook code | ||
afterEach(() => { | ||
clearLogger(); | ||
}); | ||
afterAll(() => { | ||
//some hook code | ||
beforeEach(() => { | ||
mockLogger(); | ||
}); | ||
test('inner bar', () => { | ||
some_fn(); | ||
it('logs a message', () => { | ||
// ... | ||
}); | ||
}); | ||
afterAll(() => { | ||
removeMyDatabase(); | ||
}); | ||
}); | ||
@@ -65,34 +75,50 @@ ``` | ||
describe('foo', () => { | ||
beforeAll(() => { | ||
createMyDatabase(); | ||
}); | ||
beforeEach(() => { | ||
//some hook code | ||
seedMyDatabase(); | ||
}); | ||
// Not affected by rule | ||
someSetup(); | ||
afterAll(() => { | ||
clearMyDatabase(); | ||
}); | ||
afterEach(() => { | ||
//some hook code | ||
it('accepts this input', () => { | ||
// ... | ||
}); | ||
test('bar', () => { | ||
some_fn(); | ||
it('returns that value', () => { | ||
// ... | ||
}); | ||
}); | ||
// Nested describe scenario | ||
describe('foo', () => { | ||
beforeEach(() => { | ||
//some hook code | ||
}); | ||
test('bar', () => { | ||
some_fn(); | ||
}); | ||
describe('inner_foo', () => { | ||
describe('when the database has specific values', () => { | ||
const specificValue = '...'; | ||
beforeEach(() => { | ||
//some hook code | ||
seedMyDatabase(specificValue); | ||
}); | ||
test('inner bar', () => { | ||
some_fn(); | ||
beforeEach(() => { | ||
mockLogger(); | ||
}); | ||
afterEach(() => { | ||
clearLogger(); | ||
}); | ||
it('accepts that input', () => { | ||
// ... | ||
}); | ||
it('throws an error', () => { | ||
// ... | ||
}); | ||
it('logs a message', () => { | ||
// ... | ||
}); | ||
}); | ||
}); | ||
``` |
# Suggest using `jest.spyOn()` (`prefer-spy-on`) | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
When mocking a function by overwriting a property you have to manually restore | ||
@@ -27,4 +32,2 @@ the original implementation when cleaning up. When using `jest.spyOn()` Jest | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -31,0 +34,0 @@ |
# Suggest using `toStrictEqual()` (`prefer-strict-equal`) | ||
π‘ This rule is manually fixable by | ||
[editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). | ||
<!-- end auto-generated rule header --> | ||
`toStrictEqual` not only checks that two objects contain the same data but also | ||
@@ -12,4 +17,2 @@ that they have the same structure. It is common to expect objects to not only | ||
### Default configuration | ||
The following pattern is considered warning: | ||
@@ -16,0 +19,0 @@ |
# Suggest using `toContain()` (`prefer-to-contain`) | ||
πΌ This rule is enabled in the π¨ `style` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
In order to have a better failure message, `toContain()` should be used upon | ||
@@ -23,4 +31,2 @@ asserting expectations on an array containing an object. | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -27,0 +33,0 @@ |
# Suggest using `toHaveLength()` (`prefer-to-have-length`) | ||
πΌ This rule is enabled in the π¨ `style` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
In order to have a better failure message, `toHaveLength()` should be used upon | ||
@@ -17,4 +25,2 @@ asserting expectations on objects length property. | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -21,0 +27,0 @@ |
# Suggest using `test.todo` (`prefer-todo`) | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
When test cases are empty then it is better to mark them as `test.todo` as it | ||
@@ -14,4 +19,2 @@ will be highlighted in the summary output. | ||
### Default configuration | ||
The following pattern is considered warning: | ||
@@ -18,0 +21,0 @@ |
# Require a message for `toThrow()` (`require-to-throw-message`) | ||
<!-- end auto-generated rule header --> | ||
`toThrow()` (and its alias `toThrowError()`) is used to check if an error is | ||
@@ -13,4 +15,2 @@ thrown by a function call, such as in `expect(() => a()).toThrow()`. However, if | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -17,0 +17,0 @@ |
# Require test cases and hooks to be inside a `describe` block (`require-top-level-describe`) | ||
<!-- end auto-generated rule header --> | ||
Jest allows you to organise your test files the way you want it. However, the | ||
@@ -8,3 +10,3 @@ more your codebase grows, the more it becomes hard to navigate in your test | ||
## Rule Details | ||
## Rule details | ||
@@ -51,4 +53,34 @@ This rule triggers a warning if a test case (`test` and `it`) or a hook | ||
## Options | ||
You can also enforce a limit on the number of describes allowed at the top-level | ||
using the `maxNumberOfTopLevelDescribes` option: | ||
```json | ||
{ | ||
"jest/require-top-level-describe": [ | ||
"error", | ||
{ | ||
"maxNumberOfTopLevelDescribes": 2 | ||
} | ||
] | ||
} | ||
``` | ||
Examples of **incorrect** code with the above config: | ||
```js | ||
describe('test suite', () => { | ||
it('test', () => {}); | ||
}); | ||
describe('test suite', () => {}); | ||
describe('test suite', () => {}); | ||
``` | ||
This option defaults to `Infinity`, allowing any number of top-level describes. | ||
## When Not To Use It | ||
Don't use this rule on non-jest test files. |
@@ -1,21 +0,44 @@ | ||
# Enforce having return statement when testing with promises (`valid-expect-in-promise`) | ||
# Require promises that have expectations in their chain to be valid (`valid-expect-in-promise`) | ||
Ensure to return promise when having assertions in `then` or `catch` block of | ||
promise | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Ensure promises that include expectations are returned or awaited. | ||
## Rule details | ||
This rule looks for tests that have assertions in `then` and `catch` methods on | ||
promises that are not returned by the test. | ||
This rule flags any promises within the body of a test that include expectations | ||
that have either not been returned or awaited. | ||
### Default configuration | ||
The following patterns is considered warning: | ||
The following pattern is considered warning: | ||
```js | ||
it('promise test', () => { | ||
somePromise.then(data => { | ||
expect(data).toEqual('foo'); | ||
it('promises a person', () => { | ||
api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
}); | ||
it('promises a counted person', () => { | ||
const promise = api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
promise.then(() => { | ||
expect(analytics.gottenPeopleCount).toBe(1); | ||
}); | ||
}); | ||
it('promises multiple people', () => { | ||
const firstPromise = api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
const secondPromise = api.getPersonByName('alice').then(person => { | ||
expect(person).toHaveProperty('name', 'Alice'); | ||
}); | ||
return Promise.any([firstPromise, secondPromise]); | ||
}); | ||
``` | ||
@@ -26,7 +49,30 @@ | ||
```js | ||
it('promise test', () => { | ||
return somePromise.then(data => { | ||
expect(data).toEqual('foo'); | ||
it('promises a person', async () => { | ||
await api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
}); | ||
it('promises a counted person', () => { | ||
let promise = api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
promise = promise.then(() => { | ||
expect(analytics.gottenPeopleCount).toBe(1); | ||
}); | ||
return promise; | ||
}); | ||
it('promises multiple people', () => { | ||
const firstPromise = api.getPersonByName('bob').then(person => { | ||
expect(person).toHaveProperty('name', 'Bob'); | ||
}); | ||
const secondPromise = api.getPersonByName('alice').then(person => { | ||
expect(person).toHaveProperty('name', 'Alice'); | ||
}); | ||
return Promise.allSettled([firstPromise, secondPromise]); | ||
}); | ||
``` |
# Enforce valid `expect()` usage (`valid-expect`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
<!-- end auto-generated rule header --> | ||
Ensure `expect()` is called with a single argument and there is an actual | ||
@@ -41,2 +46,7 @@ expectation made. | ||
}, | ||
asyncMatchers: { | ||
type: 'array', | ||
items: { type: 'string' }, | ||
default: ['toResolve', 'toReject'], | ||
}, | ||
minArgs: { | ||
@@ -60,3 +70,3 @@ type: 'number', | ||
Examples of **incorrect** code for the { "alwaysAwait": **true** } option: | ||
Examples of **incorrect** code for the `{ "alwaysAwait": true }` option: | ||
@@ -71,3 +81,3 @@ ```js | ||
Examples of **correct** code for the { "alwaysAwait": **true** } option: | ||
Examples of **correct** code for the `{ "alwaysAwait": true }` option: | ||
@@ -84,2 +94,10 @@ ```js | ||
### `asyncMatchers` | ||
Allows specifying which matchers return promises, and so should be considered | ||
async when checking if an `expect` should be returned or awaited. | ||
By default, this has a list of all the async matchers provided by | ||
`jest-extended` (namely, `toResolve` and `toReject`). | ||
### `minArgs` & `maxArgs` | ||
@@ -97,4 +115,2 @@ | ||
### Default configuration | ||
The following patterns are considered warnings: | ||
@@ -131,7 +147,7 @@ | ||
); | ||
await Promise.all( | ||
await Promise.all([ | ||
expect(Promise.resolve('hello')).resolves.toEqual('hello'), | ||
expect(Promise.resolve('hi')).resolves.toEqual('hi'), | ||
); | ||
]); | ||
}); | ||
``` |
# Enforce valid titles (`valid-title`) | ||
πΌ This rule is enabled in the β `recommended` | ||
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations). | ||
π§ This rule is automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
<!-- end auto-generated rule header --> | ||
Checks that the title of Jest blocks are valid by ensuring that titles are: | ||
@@ -10,3 +18,3 @@ | ||
## Rule Details | ||
## Rule details | ||
@@ -47,3 +55,3 @@ **emptyTitle** | ||
Titles for test blocks should always be a string literal or expression. | ||
Titles for test blocks should always be a string. | ||
@@ -60,3 +68,3 @@ This is also applied to `describe` blocks by default, but can be turned off via | ||
xdescribe(myFunction, () => {}); | ||
describe(6, function() {}); | ||
describe(6, function () {}); | ||
``` | ||
@@ -88,3 +96,3 @@ | ||
xdescribe(myFunction, () => {}); | ||
describe(6, function() {}); | ||
describe(6, function () {}); | ||
``` | ||
@@ -205,4 +213,5 @@ | ||
Allows enforcing that titles must match or must not match a given Regular | ||
Expression. An object can be provided to apply different Regular Expressions to | ||
specific Jest test function groups (`describe`, `test`, and `it`). | ||
Expression, with an optional message. An object can be provided to apply | ||
different Regular Expressions (with optional messages) to specific Jest test | ||
function groups (`describe`, `test`, and `it`). | ||
@@ -212,7 +221,7 @@ Examples of **incorrect** code when using `mustMatch`: | ||
```js | ||
// with mustMatch: '$that' | ||
// with mustMatch: '^that' | ||
describe('the correct way to do things', () => {}); | ||
fit('this there!', () => {}); | ||
// with mustMatch: { test: '$that' } | ||
// with mustMatch: { test: '^that' } | ||
describe('the tests that will be run', () => {}); | ||
@@ -226,7 +235,7 @@ test('the stuff works', () => {}); | ||
```js | ||
// with mustMatch: '$that' | ||
// with mustMatch: '^that' | ||
describe('that thing that needs to be done', () => {}); | ||
fit('that this there!', () => {}); | ||
// with mustMatch: { test: '$that' } | ||
// with mustMatch: { test: '^that' } | ||
describe('the tests that will be run', () => {}); | ||
@@ -236,1 +245,28 @@ test('that the stuff works', () => {}); | ||
``` | ||
Optionally you can provide a custom message to show for a particular matcher by | ||
using a tuple at any level where you can provide a matcher: | ||
```js | ||
const prefixes = ['when', 'with', 'without', 'if', 'unless', 'for']; | ||
const prefixesList = prefixes.join(' - \n'); | ||
module.exports = { | ||
rules: { | ||
'jest/valid-title': [ | ||
'error', | ||
{ | ||
mustNotMatch: ['\\.$', 'Titles should not end with a full-stop'], | ||
mustMatch: { | ||
describe: [ | ||
new RegExp(`^(?:[A-Z]|\\b(${prefixes.join('|')})\\b`, 'u').source, | ||
`Describe titles should either start with a capital letter or one of the following prefixes: ${prefixesList}`, | ||
], | ||
test: [/[^A-Z]/u.source], | ||
it: /[^A-Z]/u.source, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
``` |
@@ -8,10 +8,5 @@ { | ||
"expect": false, | ||
"fail": false, | ||
"fit": false, | ||
"it": false, | ||
"jasmine": false, | ||
"jest": false, | ||
"pending": false, | ||
"pit": false, | ||
"require": false, | ||
"test": false, | ||
@@ -18,0 +13,0 @@ "xdescribe": false, |
"use strict"; | ||
var _fs = require("fs"); | ||
var _path = require("path"); | ||
var _globals = _interopRequireDefault(require("./globals.json")); | ||
var snapshotProcessor = _interopRequireWildcard(require("./processors/snapshot-processor")); | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } | ||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
// v5 of `@typescript-eslint/experimental-utils` removed this | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
// copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606 | ||
/* istanbul ignore next */ | ||
@@ -29,18 +17,10 @@ const interopRequireDefault = obj => obj && obj.__esModule ? obj : { | ||
}; | ||
const importDefault = moduleName => // eslint-disable-next-line @typescript-eslint/no-require-imports | ||
const importDefault = moduleName => | ||
// eslint-disable-next-line @typescript-eslint/no-require-imports | ||
interopRequireDefault(require(moduleName)).default; | ||
const rulesDir = (0, _path.join)(__dirname, 'rules'); | ||
const excludedFiles = ['__tests__', 'utils']; | ||
const rules = (0, _fs.readdirSync)(rulesDir).map(rule => (0, _path.parse)(rule).name).filter(rule => !excludedFiles.includes(rule)).reduce((acc, curr) => _objectSpread(_objectSpread({}, acc), {}, { | ||
[curr]: importDefault((0, _path.join)(rulesDir, curr)) | ||
}), {}); | ||
const recommendedRules = Object.entries(rules).filter(([, rule]) => rule.meta.docs.recommended).reduce((acc, [name, rule]) => _objectSpread(_objectSpread({}, acc), {}, { | ||
[`jest/${name}`]: rule.meta.docs.recommended | ||
}), {}); | ||
const allRules = Object.keys(rules).reduce((rules, key) => _objectSpread(_objectSpread({}, rules), {}, { | ||
[`jest/${key}`]: 'error' | ||
}), {}); | ||
const excludedFiles = ['__tests__', 'detectJestVersion', 'utils']; | ||
const rules = Object.fromEntries((0, _fs.readdirSync)(rulesDir).map(rule => (0, _path.parse)(rule).name).filter(rule => !excludedFiles.includes(rule)).map(rule => [rule, importDefault((0, _path.join)(rulesDir, rule))])); | ||
const recommendedRules = Object.fromEntries(Object.entries(rules).filter(([, rule]) => rule.meta.docs.recommended).map(([name, rule]) => [`jest/${name}`, rule.meta.docs.recommended])); | ||
const allRules = Object.fromEntries(Object.entries(rules).filter(([, rule]) => !rule.meta.deprecated).map(([name]) => [`jest/${name}`, 'error'])); | ||
const createConfig = rules => ({ | ||
@@ -53,3 +33,2 @@ plugins: ['jest'], | ||
}); | ||
module.exports = { | ||
@@ -59,12 +38,8 @@ configs: { | ||
recommended: createConfig(recommendedRules), | ||
style: { | ||
plugins: ['jest'], | ||
rules: { | ||
'jest/no-alias-methods': 'warn', | ||
'jest/prefer-to-be-null': 'error', | ||
'jest/prefer-to-be-undefined': 'error', | ||
'jest/prefer-to-contain': 'error', | ||
'jest/prefer-to-have-length': 'error' | ||
} | ||
} | ||
style: createConfig({ | ||
'jest/no-alias-methods': 'warn', | ||
'jest/prefer-to-be': 'error', | ||
'jest/prefer-to-contain': 'error', | ||
'jest/prefer-to-have-length': 'error' | ||
}) | ||
}, | ||
@@ -71,0 +46,0 @@ environments: { |
@@ -6,13 +6,11 @@ "use strict"; | ||
}); | ||
exports.postprocess = exports.preprocess = void 0; | ||
exports.preprocess = exports.postprocess = void 0; | ||
// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins | ||
// https://github.com/typescript-eslint/typescript-eslint/issues/808 | ||
const preprocess = source => [source]; | ||
exports.preprocess = preprocess; | ||
const postprocess = messages => // snapshot files should only be linted with snapshot specific rules | ||
const postprocess = messages => | ||
// snapshot files should only be linted with snapshot specific rules | ||
messages[0].filter(message => message.ruleId === 'jest/no-large-snapshots'); | ||
exports.postprocess = postprocess; |
@@ -7,10 +7,6 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const buildFixer = (callee, nodeName, preferredTestKeyword) => fixer => [fixer.replaceText(callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? callee.object : callee, getPreferredNodeName(nodeName, preferredTestKeyword))]; | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const buildFixer = (callee, nodeName, preferredTestKeyword) => fixer => [fixer.replaceText(callee.type === _utils.AST_NODE_TYPES.MemberExpression ? callee.object : callee, getPreferredNodeName(nodeName, preferredTestKeyword))]; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -20,3 +16,3 @@ meta: { | ||
category: 'Best Practices', | ||
description: 'Have control over `test` and `it` usages', | ||
description: 'Enforce `test` and `it` usage conventions', | ||
recommended: false | ||
@@ -33,6 +29,6 @@ }, | ||
fn: { | ||
enum: [_utils.TestCaseName.it, _utils.TestCaseName.test] | ||
enum: [_utils2.TestCaseName.it, _utils2.TestCaseName.test] | ||
}, | ||
withinDescribe: { | ||
enum: [_utils.TestCaseName.it, _utils.TestCaseName.test] | ||
enum: [_utils2.TestCaseName.it, _utils2.TestCaseName.test] | ||
} | ||
@@ -45,24 +41,22 @@ }, | ||
defaultOptions: [{ | ||
fn: _utils.TestCaseName.test, | ||
withinDescribe: _utils.TestCaseName.it | ||
fn: _utils2.TestCaseName.test, | ||
withinDescribe: _utils2.TestCaseName.it | ||
}], | ||
create(context) { | ||
const configObj = context.options[0] || {}; | ||
const testKeyword = configObj.fn || _utils.TestCaseName.test; | ||
const testKeywordWithinDescribe = configObj.withinDescribe || configObj.fn || _utils.TestCaseName.it; | ||
const testKeyword = configObj.fn || _utils2.TestCaseName.test; | ||
const testKeywordWithinDescribe = configObj.withinDescribe || configObj.fn || _utils2.TestCaseName.it; | ||
let describeNestingLevel = 0; | ||
return { | ||
CallExpression(node) { | ||
const nodeName = (0, _utils.getNodeName)(node.callee); | ||
if (!nodeName) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if (!jestFnCall) { | ||
return; | ||
} | ||
if ((0, _utils.isDescribe)(node)) { | ||
if (jestFnCall.type === 'describe') { | ||
describeNestingLevel++; | ||
return; | ||
} | ||
if ((0, _utils.isTestCase)(node) && describeNestingLevel === 0 && !nodeName.includes(testKeyword)) { | ||
const funcNode = node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _utils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee; | ||
if (jestFnCall.type === 'test' && describeNestingLevel === 0 && !jestFnCall.name.endsWith(testKeyword)) { | ||
const oppositeTestKeyword = getOppositeTestKeyword(testKeyword); | ||
@@ -76,7 +70,6 @@ context.report({ | ||
}, | ||
fix: buildFixer(node.callee, nodeName, testKeyword) | ||
fix: buildFixer(funcNode, jestFnCall.name, testKeyword) | ||
}); | ||
} | ||
if ((0, _utils.isTestCase)(node) && describeNestingLevel > 0 && !nodeName.includes(testKeywordWithinDescribe)) { | ||
if (jestFnCall.type === 'test' && describeNestingLevel > 0 && !jestFnCall.name.endsWith(testKeywordWithinDescribe)) { | ||
const oppositeTestKeyword = getOppositeTestKeyword(testKeywordWithinDescribe); | ||
@@ -90,34 +83,26 @@ context.report({ | ||
}, | ||
fix: buildFixer(node.callee, nodeName, testKeywordWithinDescribe) | ||
fix: buildFixer(funcNode, jestFnCall.name, testKeywordWithinDescribe) | ||
}); | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['describe'])) { | ||
describeNestingLevel--; | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; | ||
function getPreferredNodeName(nodeName, preferredTestKeyword) { | ||
if (nodeName === _utils.TestCaseName.fit) { | ||
if (nodeName === _utils2.TestCaseName.fit) { | ||
return 'test.only'; | ||
} | ||
return nodeName.startsWith('f') || nodeName.startsWith('x') ? nodeName.charAt(0) + preferredTestKeyword : preferredTestKeyword; | ||
} | ||
function getOppositeTestKeyword(test) { | ||
if (test === _utils.TestCaseName.test) { | ||
return _utils.TestCaseName.it; | ||
if (test === _utils2.TestCaseName.test) { | ||
return _utils2.TestCaseName.it; | ||
} | ||
return _utils.TestCaseName.test; | ||
return _utils2.TestCaseName.test; | ||
} |
@@ -7,7 +7,4 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
/* | ||
@@ -27,8 +24,7 @@ * This implementation is adapted from eslint-plugin-jasmine. | ||
return patterns.some(p => new RegExp(`^${p.split('.').map(x => { | ||
if (x === '**') return '[a-z\\.]*'; | ||
return x.replace(/\*/gu, '[a-z]*'); | ||
if (x === '**') return '[a-z\\d\\.]*'; | ||
return x.replace(/\*/gu, '[a-z\\d]*'); | ||
}).join('\\.')}(\\.|$)`, 'ui').test(nodeName)); | ||
} | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -52,2 +48,8 @@ meta: { | ||
}] | ||
}, | ||
additionalTestBlockFunctions: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
} | ||
} | ||
@@ -60,20 +62,18 @@ }, | ||
defaultOptions: [{ | ||
assertFunctionNames: ['expect'] | ||
assertFunctionNames: ['expect'], | ||
additionalTestBlockFunctions: [] | ||
}], | ||
create(context, [{ | ||
assertFunctionNames = ['expect'] | ||
assertFunctionNames = ['expect'], | ||
additionalTestBlockFunctions = [] | ||
}]) { | ||
const unchecked = []; | ||
function checkCallExpressionUsed(nodes) { | ||
for (const node of nodes) { | ||
const index = node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1; | ||
if (node.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) { | ||
const index = node.type === _utils.AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1; | ||
if (node.type === _utils.AST_NODE_TYPES.FunctionDeclaration) { | ||
const declaredVariables = context.getDeclaredVariables(node); | ||
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables); | ||
const testCallExpressions = (0, _utils2.getTestCallExpressionsFromDeclaredVariables)(declaredVariables, context); | ||
checkCallExpressionUsed(testCallExpressions); | ||
} | ||
if (index !== -1) { | ||
@@ -85,10 +85,11 @@ unchecked.splice(index, 1); | ||
} | ||
return { | ||
CallExpression(node) { | ||
const name = (0, _utils.getNodeName)(node.callee); | ||
if (name === _utils.TestCaseName.it || name === _utils.TestCaseName.test) { | ||
const name = (0, _utils2.getNodeName)(node.callee) ?? ''; | ||
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) || additionalTestBlockFunctions.includes(name)) { | ||
if (node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'todo')) { | ||
return; | ||
} | ||
unchecked.push(node); | ||
} else if (name && matchesAssertFunctionName(name, assertFunctionNames)) { | ||
} else if (matchesAssertFunctionName(name, assertFunctionNames)) { | ||
// Return early in case of nested `it` statements. | ||
@@ -98,3 +99,2 @@ checkCallExpressionUsed(context.getAncestors()); | ||
}, | ||
'Program:exit'() { | ||
@@ -106,8 +106,5 @@ unchecked.forEach(node => context.report({ | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -17,3 +15,3 @@ name: __filename, | ||
description: 'Disallow alias methods', | ||
recommended: false | ||
recommended: 'error' | ||
}, | ||
@@ -28,3 +26,2 @@ messages: { | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -47,16 +44,10 @@ // map of jest matcher aliases & their canonical names | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (!matcher) { | ||
return; | ||
} | ||
const alias = matcher.name; | ||
} = jestFnCall; | ||
const alias = (0, _utils.getAccessorValue)(matcher); | ||
if (alias in methodNames) { | ||
@@ -70,13 +61,10 @@ const canonical = methodNames[alias]; | ||
}, | ||
node: matcher.node.property, | ||
fix: fixer => [fixer.replaceText(matcher.node.property, canonical)] | ||
node: matcher, | ||
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher, canonical)] | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,9 +7,6 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
function hasTests(node) { | ||
return /^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/mu.test(node.value); | ||
} | ||
var _default = (0, _utils.createRule)({ | ||
@@ -30,6 +27,4 @@ name: __filename, | ||
defaultOptions: [], | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
function checkNode(node) { | ||
@@ -39,3 +34,2 @@ if (!hasTests(node)) { | ||
} | ||
context.report({ | ||
@@ -46,3 +40,2 @@ messageId: 'commentedTests', | ||
} | ||
return { | ||
@@ -53,8 +46,5 @@ Program() { | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,12 +7,12 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const isCatchCall = node => node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'catch'); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
meta: { | ||
docs: { | ||
description: 'Prevent calling `expect` conditionally', | ||
description: 'Disallow calling `expect` conditionally', | ||
category: 'Best Practices', | ||
recommended: false | ||
recommended: 'error' | ||
}, | ||
@@ -26,16 +26,12 @@ messages: { | ||
defaultOptions: [], | ||
create(context) { | ||
let conditionalDepth = 0; | ||
let inTestCase = false; | ||
let inPromiseCatch = false; | ||
const increaseConditionalDepth = () => inTestCase && conditionalDepth++; | ||
const decreaseConditionalDepth = () => inTestCase && conditionalDepth--; | ||
return { | ||
FunctionDeclaration(node) { | ||
const declaredVariables = context.getDeclaredVariables(node); | ||
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables); | ||
const testCallExpressions = (0, _utils2.getTestCallExpressionsFromDeclaredVariables)(declaredVariables, context); | ||
if (testCallExpressions.length > 0) { | ||
@@ -45,9 +41,13 @@ inTestCase = true; | ||
}, | ||
CallExpression(node) { | ||
if ((0, _utils.isTestCase)(node)) { | ||
const { | ||
type: jestFnCallType | ||
} = (0, _utils2.parseJestFnCall)(node, context) ?? {}; | ||
if (jestFnCallType === 'test') { | ||
inTestCase = true; | ||
} | ||
if (inTestCase && (0, _utils.isExpectCall)(node) && conditionalDepth > 0) { | ||
if (isCatchCall(node)) { | ||
inPromiseCatch = true; | ||
} | ||
if (inTestCase && jestFnCallType === 'expect' && conditionalDepth > 0) { | ||
context.report({ | ||
@@ -58,10 +58,17 @@ messageId: 'conditionalExpect', | ||
} | ||
if (inPromiseCatch && jestFnCallType === 'expect') { | ||
context.report({ | ||
messageId: 'conditionalExpect', | ||
node | ||
}); | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isTestCase)(node)) { | ||
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
inTestCase = false; | ||
} | ||
if (isCatchCall(node)) { | ||
inPromiseCatch = false; | ||
} | ||
}, | ||
CatchClause: increaseConditionalDepth, | ||
@@ -79,5 +86,3 @@ 'CatchClause:exit': decreaseConditionalDepth, | ||
} | ||
}); | ||
exports.default = _default; |
@@ -6,44 +6,13 @@ "use strict"; | ||
}); | ||
exports.default = exports._clearCachedJestVersion = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
let cachedJestVersion = null; | ||
/** @internal */ | ||
const _clearCachedJestVersion = () => cachedJestVersion = null; | ||
exports._clearCachedJestVersion = _clearCachedJestVersion; | ||
const detectJestVersion = () => { | ||
if (cachedJestVersion) { | ||
return cachedJestVersion; | ||
exports.default = void 0; | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const parseJestVersion = rawVersion => { | ||
if (typeof rawVersion === 'number') { | ||
return rawVersion; | ||
} | ||
try { | ||
const jestPath = require.resolve('jest/package.json', { | ||
paths: [process.cwd()] | ||
}); // eslint-disable-next-line @typescript-eslint/no-require-imports | ||
const jestPackageJson = require(jestPath); | ||
if (jestPackageJson.version) { | ||
const [majorVersion] = jestPackageJson.version.split('.'); | ||
return cachedJestVersion = parseInt(majorVersion, 10); | ||
} | ||
} catch (_unused) {} | ||
throw new Error('Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly'); | ||
const [majorVersion] = rawVersion.split('.'); | ||
return parseInt(majorVersion, 10); | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -54,3 +23,3 @@ meta: { | ||
description: 'Disallow use of deprecated functions', | ||
recommended: false | ||
recommended: 'error' | ||
}, | ||
@@ -65,33 +34,32 @@ messages: { | ||
defaultOptions: [], | ||
create(context) { | ||
var _ref, _ref$jest; | ||
const jestVersion = ((_ref = context.settings) === null || _ref === void 0 ? void 0 : (_ref$jest = _ref.jest) === null || _ref$jest === void 0 ? void 0 : _ref$jest.version) || detectJestVersion(); | ||
const deprecations = _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, jestVersion >= 15 && { | ||
'jest.resetModuleRegistry': 'jest.resetModules' | ||
}), jestVersion >= 17 && { | ||
'jest.addMatchers': 'expect.extend' | ||
}), jestVersion >= 21 && { | ||
'require.requireMock': 'jest.requireMock', | ||
'require.requireActual': 'jest.requireActual' | ||
}), jestVersion >= 22 && { | ||
'jest.runTimersToTime': 'jest.advanceTimersByTime' | ||
}), jestVersion >= 26 && { | ||
'jest.genMockFromModule': 'jest.createMockFromModule' | ||
}); | ||
var _context$settings; | ||
const jestVersion = parseJestVersion(((_context$settings = context.settings) === null || _context$settings === void 0 || (_context$settings = _context$settings.jest) === null || _context$settings === void 0 ? void 0 : _context$settings.version) || (0, _utils2.detectJestVersion)()); | ||
const deprecations = { | ||
...(jestVersion >= 15 && { | ||
'jest.resetModuleRegistry': 'jest.resetModules' | ||
}), | ||
...(jestVersion >= 17 && { | ||
'jest.addMatchers': 'expect.extend' | ||
}), | ||
...(jestVersion >= 21 && { | ||
'require.requireMock': 'jest.requireMock', | ||
'require.requireActual': 'jest.requireActual' | ||
}), | ||
...(jestVersion >= 22 && { | ||
'jest.runTimersToTime': 'jest.advanceTimersByTime' | ||
}), | ||
...(jestVersion >= 26 && { | ||
'jest.genMockFromModule': 'jest.createMockFromModule' | ||
}) | ||
}; | ||
return { | ||
CallExpression(node) { | ||
if (node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) { | ||
if (node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression) { | ||
return; | ||
} | ||
const deprecation = (0, _utils.getNodeName)(node); | ||
const deprecation = (0, _utils2.getNodeName)(node); | ||
if (!deprecation || !(deprecation in deprecations)) { | ||
return; | ||
} | ||
const replacement = deprecations[deprecation]; | ||
@@ -108,22 +76,14 @@ const { | ||
node, | ||
fix(fixer) { | ||
// eslint-disable-next-line prefer-const | ||
let [name, func] = replacement.split('.'); | ||
if (callee.property.type === _experimentalUtils.AST_NODE_TYPES.Literal) { | ||
if (callee.property.type === _utils.AST_NODE_TYPES.Literal) { | ||
func = `'${func}'`; | ||
} | ||
return [fixer.replaceText(callee.object, name), fixer.replaceText(callee.property, func)]; | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -21,4 +19,2 @@ name: __filename, | ||
missingFunction: 'Test is missing function argument', | ||
skippedTestSuite: 'Skipped test suite', | ||
skippedTest: 'Skipped test', | ||
pending: 'Call to pending()', | ||
@@ -34,3 +30,2 @@ pendingSuite: 'Call to pending() within test suite', | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -40,45 +35,44 @@ let suiteDepth = 0; | ||
return { | ||
'CallExpression[callee.name="describe"]'() { | ||
suiteDepth++; | ||
}, | ||
'CallExpression[callee.name=/^(it|test)$/]'() { | ||
testDepth++; | ||
}, | ||
'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) { | ||
context.report({ | ||
messageId: 'missingFunction', | ||
node | ||
}); | ||
}, | ||
CallExpression(node) { | ||
const functionName = (0, _utils.getNodeName)(node.callee); | ||
switch (functionName) { | ||
case 'describe.skip': | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if (!jestFnCall) { | ||
return; | ||
} | ||
if (jestFnCall.type === 'describe') { | ||
suiteDepth++; | ||
} | ||
if (jestFnCall.type === 'test') { | ||
testDepth++; | ||
if (node.arguments.length < 2 && jestFnCall.members.every(s => (0, _utils.getAccessorValue)(s) !== 'todo')) { | ||
context.report({ | ||
messageId: 'skippedTestSuite', | ||
messageId: 'missingFunction', | ||
node | ||
}); | ||
break; | ||
case 'it.skip': | ||
case 'it.concurrent.skip': | ||
case 'test.skip': | ||
case 'test.concurrent.skip': | ||
context.report({ | ||
messageId: 'skippedTest', | ||
node | ||
}); | ||
break; | ||
} | ||
} | ||
if ( | ||
// the only jest functions that are with "x" are "xdescribe", "xtest", and "xit" | ||
jestFnCall.name.startsWith('x') || jestFnCall.members.some(s => (0, _utils.getAccessorValue)(s) === 'skip')) { | ||
context.report({ | ||
messageId: jestFnCall.type === 'describe' ? 'disabledSuite' : 'disabledTest', | ||
node | ||
}); | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if (!jestFnCall) { | ||
return; | ||
} | ||
if (jestFnCall.type === 'describe') { | ||
suiteDepth--; | ||
} | ||
if (jestFnCall.type === 'test') { | ||
testDepth--; | ||
} | ||
}, | ||
'CallExpression[callee.name="pending"]'(node) { | ||
if ((0, _utils.scopeHasLocalReference)(context.getScope(), 'pending')) { | ||
if ((0, _utils.resolveScope)(context.getScope(), 'pending')) { | ||
return; | ||
} | ||
if (testDepth > 0) { | ||
@@ -100,31 +94,6 @@ context.report({ | ||
} | ||
}, | ||
'CallExpression[callee.name="xdescribe"]'(node) { | ||
context.report({ | ||
messageId: 'disabledSuite', | ||
node | ||
}); | ||
}, | ||
'CallExpression[callee.name=/^xit|xtest$/]'(node) { | ||
context.report({ | ||
messageId: 'disabledTest', | ||
node | ||
}); | ||
}, | ||
'CallExpression[callee.name="describe"]:exit'() { | ||
suiteDepth--; | ||
}, | ||
'CallExpression[callee.name=/^it|test$/]:exit'() { | ||
testDepth--; | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,12 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
const newHookContext = () => ({ | ||
beforeAll: 0, | ||
beforeEach: 0, | ||
afterAll: 0, | ||
afterEach: 0 | ||
}); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -33,38 +24,35 @@ name: __filename, | ||
defaultOptions: [], | ||
create(context) { | ||
const hookContexts = [newHookContext()]; | ||
const hookContexts = [{}]; | ||
return { | ||
CallExpression(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
hookContexts.push(newHookContext()); | ||
var _jestFnCall$name; | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'describe') { | ||
hookContexts.push({}); | ||
} | ||
if ((0, _utils.isHook)(node)) { | ||
const currentLayer = hookContexts[hookContexts.length - 1]; | ||
currentLayer[node.callee.name] += 1; | ||
if (currentLayer[node.callee.name] > 1) { | ||
context.report({ | ||
messageId: 'noDuplicateHook', | ||
data: { | ||
hook: node.callee.name | ||
}, | ||
node | ||
}); | ||
} | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'hook') { | ||
return; | ||
} | ||
const currentLayer = hookContexts[hookContexts.length - 1]; | ||
currentLayer[_jestFnCall$name = jestFnCall.name] || (currentLayer[_jestFnCall$name] = 0); | ||
currentLayer[jestFnCall.name] += 1; | ||
if (currentLayer[jestFnCall.name] > 1) { | ||
context.report({ | ||
messageId: 'noDuplicateHook', | ||
data: { | ||
hook: jestFnCall.name | ||
}, | ||
node | ||
}); | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe'])) { | ||
hookContexts.pop(); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,8 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -28,3 +25,2 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -44,13 +40,10 @@ const exportNodes = []; | ||
}, | ||
CallExpression(node) { | ||
if ((0, _utils.isTestCase)(node)) { | ||
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
hasTestCase = true; | ||
} | ||
}, | ||
'ExportNamedDeclaration, ExportDefaultDeclaration'(node) { | ||
exportNodes.push(node); | ||
}, | ||
'AssignmentExpression > MemberExpression'(node) { | ||
@@ -61,4 +54,3 @@ let { | ||
} = node; | ||
if (object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) { | ||
if (object.type === _utils.AST_NODE_TYPES.MemberExpression) { | ||
({ | ||
@@ -69,13 +61,9 @@ object, | ||
} | ||
if ('name' in object && object.name === 'module' && property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && /^exports?$/u.test(property.name)) { | ||
if ('name' in object && object.name === 'module' && property.type === _utils.AST_NODE_TYPES.Identifier && /^exports?$/u.test(property.name)) { | ||
exportNodes.push(node); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,19 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const validTestCaseNames = [_utils.TestCaseName.test, _utils.TestCaseName.it]; | ||
const testFunctions = new Set([_utils.DescribeAlias.describe, ...validTestCaseNames]); | ||
const isConcurrentExpression = expression => (0, _utils.isSupportedAccessor)(expression.property, _utils.TestCaseProperty.concurrent) && !!expression.parent && expression.parent.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression; | ||
const matchesTestFunction = object => 'name' in object && (object.name in _utils.TestCaseName || object.name in _utils.DescribeAlias); | ||
const isCallToFocusedTestFunction = object => object.name.startsWith('f') && testFunctions.has(object.name.substring(1)); | ||
const isCallToTestOnlyFunction = callee => matchesTestFunction(callee.object) && (0, _utils.isSupportedAccessor)(isConcurrentExpression(callee) ? callee.parent.property : callee.property, 'only'); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -33,50 +19,50 @@ meta: { | ||
messages: { | ||
focusedTest: 'Unexpected focused test.' | ||
focusedTest: 'Unexpected focused test.', | ||
suggestRemoveFocus: 'Remove focus from test.' | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
type: 'suggestion' | ||
type: 'suggestion', | ||
hasSuggestions: true | ||
}, | ||
defaultOptions: [], | ||
create: context => ({ | ||
CallExpression(node) { | ||
const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee; | ||
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) { | ||
if (callee.object.type === _experimentalUtils.AST_NODE_TYPES.Identifier && isCallToFocusedTestFunction(callee.object)) { | ||
context.report({ | ||
messageId: 'focusedTest', | ||
node: callee.object | ||
}); | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'test' && (jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'describe') { | ||
return; | ||
} | ||
if (callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isCallToTestOnlyFunction(callee.object)) { | ||
if (jestFnCall.name.startsWith('f')) { | ||
context.report({ | ||
messageId: 'focusedTest', | ||
node: callee.object.property | ||
node, | ||
suggest: [{ | ||
messageId: 'suggestRemoveFocus', | ||
fix(fixer) { | ||
// don't apply the fixer if we're an aliased import | ||
if (jestFnCall.head.type === 'import' && jestFnCall.name !== jestFnCall.head.local) { | ||
return null; | ||
} | ||
return fixer.removeRange([node.range[0], node.range[0] + 1]); | ||
} | ||
}] | ||
}); | ||
return; | ||
} | ||
if (isCallToTestOnlyFunction(callee)) { | ||
context.report({ | ||
messageId: 'focusedTest', | ||
node: callee.property | ||
}); | ||
const onlyNode = jestFnCall.members.find(s => (0, _utils2.getAccessorValue)(s) === 'only'); | ||
if (!onlyNode) { | ||
return; | ||
} | ||
} | ||
if (callee.type === _experimentalUtils.AST_NODE_TYPES.Identifier && isCallToFocusedTestFunction(callee)) { | ||
context.report({ | ||
messageId: 'focusedTest', | ||
node: callee | ||
node: onlyNode, | ||
suggest: [{ | ||
messageId: 'suggestRemoveFocus', | ||
fix: fixer => fixer.removeRange([onlyNode.range[0] - 1, onlyNode.range[1] + Number(onlyNode.type !== _utils.AST_NODE_TYPES.Identifier)]) | ||
}] | ||
}); | ||
} | ||
} | ||
}) | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -37,3 +35,2 @@ name: __filename, | ||
}], | ||
create(context, [{ | ||
@@ -44,3 +41,4 @@ allow = [] | ||
CallExpression(node) { | ||
if ((0, _utils.isHook)(node) && !allow.includes(node.callee.name)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'hook' && !allow.includes(jestFnCall.name)) { | ||
context.report({ | ||
@@ -50,3 +48,3 @@ node, | ||
data: { | ||
hookName: node.callee.name | ||
hookName: jestFnCall.name | ||
} | ||
@@ -56,8 +54,5 @@ }); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
const newDescribeContext = () => ({ | ||
@@ -15,3 +13,2 @@ describeTitles: [], | ||
}); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -33,3 +30,2 @@ name: __filename, | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -40,16 +36,18 @@ const contexts = [newDescribeContext()]; | ||
const currentLayer = contexts[contexts.length - 1]; | ||
if ((0, _utils.isDescribe)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if (!jestFnCall) { | ||
return; | ||
} | ||
if (jestFnCall.type === 'describe') { | ||
contexts.push(newDescribeContext()); | ||
} | ||
if (jestFnCall.members.find(s => (0, _utils.isSupportedAccessor)(s, 'each'))) { | ||
return; | ||
} | ||
const [argument] = node.arguments; | ||
if (!argument || !(0, _utils.isStringNode)(argument)) { | ||
return; | ||
} | ||
const title = (0, _utils.getStringValue)(argument); | ||
if ((0, _utils.isTestCase)(node)) { | ||
if (jestFnCall.type === 'test') { | ||
if (currentLayer.testTitles.includes(title)) { | ||
@@ -61,10 +59,7 @@ context.report({ | ||
} | ||
currentLayer.testTitles.push(title); | ||
} | ||
if (!(0, _utils.isDescribe)(node)) { | ||
if (jestFnCall.type !== 'describe') { | ||
return; | ||
} | ||
if (currentLayer.describeTitles.includes(title)) { | ||
@@ -76,17 +71,12 @@ context.report({ | ||
} | ||
currentLayer.describeTitles.push(title); | ||
}, | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe'])) { | ||
contexts.pop(); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,18 +7,12 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const testCaseNames = new Set([...Object.keys(_utils.TestCaseName), 'it.only', 'it.concurrent.only', 'it.skip', 'it.concurrent.skip', 'test.only', 'test.concurrent.only', 'test.skip', 'test.concurrent.skip', 'fit.concurrent']); | ||
const isTestArrowFunction = node => node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && testCaseNames.has((0, _utils.getNodeName)(node.parent.callee)); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const testCaseNames = new Set([...Object.keys(_utils2.TestCaseName), 'it.only', 'it.only', 'it.skip', 'it.skip', 'test.only', 'test.only', 'test.skip', 'test.skip', 'fit.concurrent']); | ||
const isTestFunctionExpression = node => node.parent !== undefined && node.parent.type === _utils.AST_NODE_TYPES.CallExpression && testCaseNames.has((0, _utils2.getNodeName)(node.parent.callee)); | ||
const conditionName = { | ||
[_experimentalUtils.AST_NODE_TYPES.ConditionalExpression]: 'conditional', | ||
[_experimentalUtils.AST_NODE_TYPES.SwitchStatement]: 'switch', | ||
[_experimentalUtils.AST_NODE_TYPES.IfStatement]: 'if' | ||
[_utils.AST_NODE_TYPES.ConditionalExpression]: 'conditional', | ||
[_utils.AST_NODE_TYPES.SwitchStatement]: 'switch', | ||
[_utils.AST_NODE_TYPES.IfStatement]: 'if' | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -34,2 +28,4 @@ meta: { | ||
}, | ||
deprecated: true, | ||
replacedBy: ['no-conditional-in-test'], | ||
schema: [], | ||
@@ -39,13 +35,9 @@ type: 'suggestion' | ||
defaultOptions: [], | ||
create(context) { | ||
const stack = []; | ||
function validate(node) { | ||
const lastElementInStack = stack[stack.length - 1]; | ||
if (stack.length === 0 || !lastElementInStack) { | ||
return; | ||
} | ||
context.report({ | ||
@@ -59,49 +51,41 @@ data: { | ||
} | ||
return { | ||
CallExpression(node) { | ||
if ((0, _utils.isTestCase)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'test') { | ||
stack.push(true); | ||
if (jestFnCall.members.some(s => (0, _utils2.getAccessorValue)(s) === 'each')) { | ||
stack.push(true); | ||
} | ||
} | ||
}, | ||
FunctionExpression() { | ||
stack.push(false); | ||
FunctionExpression(node) { | ||
stack.push(isTestFunctionExpression(node)); | ||
}, | ||
FunctionDeclaration(node) { | ||
const declaredVariables = context.getDeclaredVariables(node); | ||
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables); | ||
const testCallExpressions = (0, _utils2.getTestCallExpressionsFromDeclaredVariables)(declaredVariables, context); | ||
stack.push(testCallExpressions.length > 0); | ||
}, | ||
ArrowFunctionExpression(node) { | ||
stack.push(isTestArrowFunction(node)); | ||
stack.push(isTestFunctionExpression(node)); | ||
}, | ||
IfStatement: validate, | ||
SwitchStatement: validate, | ||
ConditionalExpression: validate, | ||
'CallExpression:exit'() { | ||
stack.pop(); | ||
}, | ||
'FunctionExpression:exit'() { | ||
stack.pop(); | ||
}, | ||
'FunctionDeclaration:exit'() { | ||
stack.pop(); | ||
}, | ||
'ArrowFunctionExpression:exit'() { | ||
stack.pop(); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,8 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -19,3 +16,3 @@ meta: { | ||
description: 'Disallow string interpolation inside snapshots', | ||
recommended: false | ||
recommended: 'error' | ||
}, | ||
@@ -29,24 +26,13 @@ messages: { | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (!matcher) { | ||
return; | ||
} | ||
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)) { | ||
var _matcher$arguments; | ||
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes((0, _utils2.getAccessorValue)(jestFnCall.matcher))) { | ||
// Check all since the optional 'propertyMatchers' argument might be present | ||
(_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.forEach(argument => { | ||
if (argument.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) { | ||
jestFnCall.args.forEach(argument => { | ||
if (argument.type === _utils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) { | ||
context.report({ | ||
@@ -60,8 +46,5 @@ messageId: 'noInterpolation', | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,8 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -19,3 +16,3 @@ meta: { | ||
description: 'Disallow Jasmine globals', | ||
recommended: 'warn' | ||
recommended: 'error' | ||
}, | ||
@@ -34,3 +31,2 @@ messages: { | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -42,14 +38,11 @@ return { | ||
} = node; | ||
const calleeName = (0, _utils.getNodeName)(callee); | ||
const calleeName = (0, _utils2.getNodeName)(callee); | ||
if (!calleeName) { | ||
return; | ||
} | ||
if (calleeName === 'spyOn' || calleeName === 'spyOnProperty' || calleeName === 'fail' || calleeName === 'pending') { | ||
if ((0, _utils.scopeHasLocalReference)(context.getScope(), calleeName)) { | ||
if ((0, _utils2.resolveScope)(context.getScope(), calleeName)) { | ||
// It's a local variable, not a jasmine global. | ||
return; | ||
} | ||
switch (calleeName) { | ||
@@ -67,3 +60,2 @@ case 'spyOn': | ||
break; | ||
case 'fail': | ||
@@ -75,3 +67,2 @@ context.report({ | ||
break; | ||
case 'pending': | ||
@@ -84,9 +75,6 @@ context.report({ | ||
} | ||
return; | ||
} | ||
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && calleeName.startsWith('jasmine.')) { | ||
if (callee.type === _utils.AST_NODE_TYPES.MemberExpression && calleeName.startsWith('jasmine.')) { | ||
const functionName = calleeName.replace('jasmine.', ''); | ||
if (functionName === 'any' || functionName === 'anything' || functionName === 'arrayContaining' || functionName === 'objectContaining' || functionName === 'stringMatching') { | ||
@@ -104,3 +92,2 @@ context.report({ | ||
} | ||
if (functionName === 'addMatchers') { | ||
@@ -117,3 +104,2 @@ context.report({ | ||
} | ||
if (functionName === 'createSpy') { | ||
@@ -130,3 +116,2 @@ context.report({ | ||
} | ||
context.report({ | ||
@@ -138,5 +123,4 @@ node, | ||
}, | ||
MemberExpression(node) { | ||
if ((0, _utils.isSupportedAccessor)(node.object, 'jasmine')) { | ||
if ((0, _utils2.isSupportedAccessor)(node.object, 'jasmine')) { | ||
const { | ||
@@ -146,10 +130,8 @@ parent, | ||
} = node; | ||
if (parent && parent.type === _experimentalUtils.AST_NODE_TYPES.AssignmentExpression) { | ||
if ((0, _utils.isSupportedAccessor)(property, 'DEFAULT_TIMEOUT_INTERVAL')) { | ||
if (parent && parent.type === _utils.AST_NODE_TYPES.AssignmentExpression) { | ||
if ((0, _utils2.isSupportedAccessor)(property, 'DEFAULT_TIMEOUT_INTERVAL')) { | ||
const { | ||
right | ||
} = parent; | ||
if (right.type === _experimentalUtils.AST_NODE_TYPES.Literal) { | ||
if (right.type === _utils.AST_NODE_TYPES.Literal) { | ||
context.report({ | ||
@@ -163,3 +145,2 @@ fix: fixer => [fixer.replaceText(parent, `jest.setTimeout(${right.value})`)], | ||
} | ||
context.report({ | ||
@@ -172,8 +153,5 @@ node, | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,19 +7,8 @@ "use strict"; | ||
exports.default = void 0; | ||
var _path = require("path"); | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const reportOnViolation = (context, node, { | ||
maxSize: lineLimit = 50, | ||
whitelistedSnapshots = {}, | ||
allowedSnapshots = whitelistedSnapshots | ||
allowedSnapshots = {} | ||
}) => { | ||
@@ -30,15 +19,11 @@ const startLine = node.loc.start.line; | ||
const allPathsAreAbsolute = Object.keys(allowedSnapshots).every(_path.isAbsolute); | ||
if (!allPathsAreAbsolute) { | ||
throw new Error('All paths for allowedSnapshots must be absolute. You can use JS config and `path.resolve`'); | ||
} | ||
let isAllowed = false; | ||
if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && (0, _utils.isExpectMember)(node.expression.left)) { | ||
if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && node.expression.left.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.expression.left.property)) { | ||
const fileName = context.getFilename(); | ||
const allowedSnapshotsInFile = allowedSnapshots[fileName]; | ||
if (allowedSnapshotsInFile) { | ||
const snapshotName = (0, _utils.getAccessorValue)(node.expression.left.property); | ||
const snapshotName = (0, _utils2.getAccessorValue)(node.expression.left.property); | ||
isAllowed = allowedSnapshotsInFile.some(name => { | ||
@@ -48,3 +33,2 @@ if (name instanceof RegExp) { | ||
} | ||
return snapshotName === name; | ||
@@ -54,3 +38,2 @@ }); | ||
} | ||
if (!isAllowed && lineCount > lineLimit) { | ||
@@ -67,4 +50,3 @@ context.report({ | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -74,3 +56,3 @@ meta: { | ||
category: 'Best Practices', | ||
description: 'disallow large snapshots', | ||
description: 'Disallow large snapshots', | ||
recommended: false | ||
@@ -97,10 +79,2 @@ }, | ||
} | ||
}, | ||
whitelistedSnapshots: { | ||
type: 'object', | ||
patternProperties: { | ||
'.*': { | ||
type: 'array' | ||
} | ||
} | ||
} | ||
@@ -112,8 +86,3 @@ }, | ||
defaultOptions: [{}], | ||
create(context, [options]) { | ||
if ('whitelistedSnapshots' in options) { | ||
console.warn('jest/no-large-snapshots: the "whitelistedSnapshots" option has been renamed to "allowedSnapshots"'); | ||
} | ||
if (context.getFilename().endsWith('.snap')) { | ||
@@ -124,24 +93,20 @@ return { | ||
} | ||
}; | ||
} else if (context.getFilename().endsWith('.js')) { | ||
return { | ||
CallExpression(node) { | ||
if ('property' in node.callee && ((0, _utils.isSupportedAccessor)(node.callee.property, 'toMatchInlineSnapshot') || (0, _utils.isSupportedAccessor)(node.callee.property, 'toThrowErrorMatchingInlineSnapshot'))) { | ||
var _options$inlineMaxSiz; | ||
reportOnViolation(context, node, _objectSpread(_objectSpread({}, options), {}, { | ||
maxSize: (_options$inlineMaxSiz = options.inlineMaxSize) !== null && _options$inlineMaxSiz !== void 0 ? _options$inlineMaxSiz : options.maxSize | ||
})); | ||
} | ||
} | ||
return { | ||
CallExpression(node) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
}; | ||
} | ||
return {}; | ||
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes((0, _utils2.getAccessorValue)(jestFnCall.matcher)) && jestFnCall.args.length) { | ||
reportOnViolation(context, jestFnCall.args[0], { | ||
...options, | ||
maxSize: options.inlineMaxSize ?? options.maxSize | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,13 +7,7 @@ "use strict"; | ||
exports.default = void 0; | ||
var _path = require("path"); | ||
var _utils = require("./utils"); | ||
const mocksDirName = '__mocks__'; | ||
const isMockPath = path => path.split(_path.posix.sep).includes(mocksDirName); | ||
const isMockImportLiteral = expression => (0, _utils.isStringNode)(expression) && isMockPath((0, _utils.getStringValue)(expression)); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -34,3 +28,2 @@ name: __filename, | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -46,6 +39,4 @@ return { | ||
}, | ||
'CallExpression[callee.name="require"]'(node) { | ||
const [arg] = node.arguments; | ||
if (arg && isMockImportLiteral(arg)) { | ||
@@ -58,8 +49,5 @@ context.report({ | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,9 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
const isChainRestricted = (chain, restriction) => { | ||
if (_utils.ModifierName.hasOwnProperty(restriction) || restriction.endsWith('.not')) { | ||
return chain.startsWith(restriction); | ||
} | ||
return chain === restriction; | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
@@ -27,3 +31,3 @@ name: __filename, | ||
messages: { | ||
restrictedChain: 'Use of `{{ chain }}` is disallowed', | ||
restrictedChain: 'Use of `{{ restriction }}` is disallowed', | ||
restrictedChainWithMessage: '{{ message }}' | ||
@@ -33,20 +37,12 @@ } | ||
defaultOptions: [{}], | ||
create(context, [restrictedChains]) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
matcher, | ||
modifier | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (matcher) { | ||
const chain = matcher.name; | ||
if (chain in restrictedChains) { | ||
const message = restrictedChains[chain]; | ||
const chain = jestFnCall.members.map(nod => (0, _utils.getAccessorValue)(nod)).join('.'); | ||
for (const [restriction, message] of Object.entries(restrictedChains)) { | ||
if (isChainRestricted(chain, restriction)) { | ||
context.report({ | ||
@@ -56,53 +52,16 @@ messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain', | ||
message, | ||
chain | ||
restriction | ||
}, | ||
node: matcher.node.property | ||
}); | ||
return; | ||
} | ||
} | ||
if (modifier) { | ||
const chain = modifier.name; | ||
if (chain in restrictedChains) { | ||
const message = restrictedChains[chain]; | ||
context.report({ | ||
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain', | ||
data: { | ||
message, | ||
chain | ||
}, | ||
node: modifier.node.property | ||
}); | ||
return; | ||
} | ||
} | ||
if (matcher && modifier) { | ||
const chain = `${modifier.name}.${matcher.name}`; | ||
if (chain in restrictedChains) { | ||
const message = restrictedChains[chain]; | ||
context.report({ | ||
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain', | ||
data: { | ||
message, | ||
chain | ||
}, | ||
loc: { | ||
start: modifier.node.property.loc.start, | ||
end: matcher.node.property.loc.end | ||
start: jestFnCall.members[0].loc.start, | ||
end: jestFnCall.members[jestFnCall.members.length - 1].loc.end | ||
} | ||
}); | ||
return; | ||
break; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,39 +7,32 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const getBlockType = (statement, context) => { | ||
const func = statement.parent; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const getBlockType = statement => { | ||
const func = statement.parent; | ||
/* istanbul ignore if */ | ||
if (!func) { | ||
throw new Error(`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
} // functionDeclaration: function func() {} | ||
} | ||
if (func.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) { | ||
// functionDeclaration: function func() {} | ||
if (func.type === _utils.AST_NODE_TYPES.FunctionDeclaration) { | ||
return 'function'; | ||
} | ||
if ((0, _utils2.isFunction)(func) && func.parent) { | ||
const expr = func.parent; | ||
if ((0, _utils.isFunction)(func) && func.parent) { | ||
const expr = func.parent; // arrow function or function expr | ||
if (expr.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator) { | ||
// arrow function or function expr | ||
if (expr.type === _utils.AST_NODE_TYPES.VariableDeclarator) { | ||
return 'function'; | ||
} // if it's not a variable, it will be callExpr, we only care about describe | ||
} | ||
if (expr.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isDescribe)(expr)) { | ||
// if it's not a variable, it will be callExpr, we only care about describe | ||
if (expr.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isTypeOfJestFnCall)(expr, context, ['describe'])) { | ||
return 'describe'; | ||
} | ||
} | ||
return null; | ||
}; | ||
const isEach = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && node.callee.callee.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && node.callee.callee.property.name === 'each' && node.callee.callee.object.type === _experimentalUtils.AST_NODE_TYPES.Identifier && _utils.TestCaseName.hasOwnProperty(node.callee.callee.object.name); | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -71,3 +64,2 @@ meta: { | ||
}], | ||
create(context, [{ | ||
@@ -77,13 +69,13 @@ additionalTestBlockFunctions = [] | ||
const callStack = []; | ||
const isCustomTestBlockFunction = node => additionalTestBlockFunctions.includes((0, _utils.getNodeName)(node) || ''); | ||
const isTestBlock = node => (0, _utils.isTestCase)(node) || isCustomTestBlockFunction(node); | ||
const isCustomTestBlockFunction = node => additionalTestBlockFunctions.includes((0, _utils2.getNodeName)(node) || ''); | ||
return { | ||
CallExpression(node) { | ||
if ((0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect') { | ||
var _jestFnCall$head$node; | ||
if (((_jestFnCall$head$node = jestFnCall.head.node.parent) === null || _jestFnCall$head$node === void 0 ? void 0 : _jestFnCall$head$node.type) === _utils.AST_NODE_TYPES.MemberExpression && jestFnCall.members.length === 1 && !['assertions', 'hasAssertions'].includes((0, _utils2.getAccessorValue)(jestFnCall.members[0]))) { | ||
return; | ||
} | ||
const parent = callStack[callStack.length - 1]; | ||
if (!parent || parent === _utils.DescribeAlias.describe) { | ||
if (!parent || parent === _utils2.DescribeAlias.describe) { | ||
context.report({ | ||
@@ -94,26 +86,19 @@ node, | ||
} | ||
return; | ||
} | ||
if (isTestBlock(node)) { | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'test' || isCustomTestBlockFunction(node)) { | ||
callStack.push('test'); | ||
} | ||
if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) { | ||
if (node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression) { | ||
callStack.push('template'); | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
const top = callStack[callStack.length - 1]; | ||
if (top === 'test' && (isEach(node) || isTestBlock(node) && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) || top === 'template' && node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) { | ||
if (top === 'test' && ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) || isCustomTestBlockFunction(node)) && node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || top === 'template' && node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression) { | ||
callStack.pop(); | ||
} | ||
}, | ||
BlockStatement(statement) { | ||
const blockType = getBlockType(statement); | ||
const blockType = getBlockType(statement, context); | ||
if (blockType) { | ||
@@ -123,17 +108,13 @@ callStack.push(blockType); | ||
}, | ||
'BlockStatement:exit'(statement) { | ||
if (callStack[callStack.length - 1] === getBlockType(statement)) { | ||
if (callStack[callStack.length - 1] === getBlockType(statement, context)) { | ||
callStack.pop(); | ||
} | ||
}, | ||
ArrowFunctionExpression(node) { | ||
var _node$parent; | ||
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) { | ||
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) !== _utils.AST_NODE_TYPES.CallExpression) { | ||
callStack.push('arrow'); | ||
} | ||
}, | ||
'ArrowFunctionExpression:exit'() { | ||
@@ -144,8 +125,5 @@ if (callStack[callStack.length - 1] === 'arrow') { | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,6 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -16,3 +15,3 @@ meta: { | ||
category: 'Best Practices', | ||
description: 'Use `.only` and `.skip` over `f` and `x`', | ||
description: 'Require using `.only` and `.skip` over `f` and `x`', | ||
recommended: 'error' | ||
@@ -28,10 +27,14 @@ }, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
const nodeName = (0, _utils.getNodeName)(node.callee); | ||
if (!nodeName || !(0, _utils.isDescribe)(node) && !(0, _utils.isTestCase)(node)) return; | ||
const preferredNodeName = getPreferredNodeName(nodeName); | ||
if (!preferredNodeName) return; | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'describe' && (jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'test') { | ||
return; | ||
} | ||
if (jestFnCall.name[0] !== 'f' && jestFnCall.name[0] !== 'x') { | ||
return; | ||
} | ||
const preferredNodeName = [jestFnCall.name.slice(1), jestFnCall.name[0] === 'f' ? 'only' : 'skip', ...jestFnCall.members.map(s => (0, _utils2.getAccessorValue)(s))].join('.'); | ||
const funcNode = node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _utils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee; | ||
context.report({ | ||
@@ -43,29 +46,10 @@ messageId: 'usePreferredName', | ||
}, | ||
fix(fixer) { | ||
return [fixer.replaceText(node.callee, preferredNodeName)]; | ||
return [fixer.replaceText(funcNode, preferredNodeName)]; | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; | ||
function getPreferredNodeName(nodeName) { | ||
const firstChar = nodeName.charAt(0); | ||
if (firstChar === 'f') { | ||
return `${nodeName.slice(1)}.only`; | ||
} | ||
if (firstChar === 'x') { | ||
return `${nodeName.slice(1)}.skip`; | ||
} | ||
return null; | ||
} | ||
exports.default = _default; |
@@ -7,18 +7,12 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const getBody = args => { | ||
const [, secondArg] = args; | ||
if (secondArg && (0, _utils.isFunction)(secondArg) && secondArg.body && secondArg.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) { | ||
if (secondArg && (0, _utils2.isFunction)(secondArg) && secondArg.body.type === _utils.AST_NODE_TYPES.BlockStatement) { | ||
return secondArg.body.body; | ||
} | ||
return []; | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -38,9 +32,10 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isTestCase)(node)) return; | ||
if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
return; | ||
} | ||
const body = getBody(node.arguments); | ||
const returnStmt = body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement); | ||
const returnStmt = body.find(t => t.type === _utils.AST_NODE_TYPES.ReturnStatement); | ||
if (!returnStmt) return; | ||
@@ -52,8 +47,7 @@ context.report({ | ||
}, | ||
FunctionDeclaration(node) { | ||
const declaredVariables = context.getDeclaredVariables(node); | ||
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables); | ||
const testCallExpressions = (0, _utils2.getTestCallExpressionsFromDeclaredVariables)(declaredVariables, context); | ||
if (testCallExpressions.length === 0) return; | ||
const returnStmt = node.body.body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement); | ||
const returnStmt = node.body.body.find(t => t.type === _utils.AST_NODE_TYPES.ReturnStatement); | ||
if (!returnStmt) return; | ||
@@ -65,8 +59,5 @@ context.report({ | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -20,3 +18,3 @@ name: __filename, | ||
messages: { | ||
preferCalledWith: 'Prefer {{name}}With(/* expected args */)' | ||
preferCalledWith: 'Prefer {{ matcherName }}With(/* expected args */)' | ||
}, | ||
@@ -27,34 +25,29 @@ type: 'suggestion', | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
if (jestFnCall.modifiers.some(nod => (0, _utils.getAccessorValue)(nod) === 'not')) { | ||
return; | ||
} | ||
const { | ||
modifier, | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); // Could check resolves/rejects here but not a likely idiom. | ||
if (matcher && !modifier) { | ||
if (['toBeCalled', 'toHaveBeenCalled'].includes(matcher.name)) { | ||
context.report({ | ||
data: { | ||
name: matcher.name | ||
}, | ||
// todo: rename to 'matcherName' | ||
messageId: 'preferCalledWith', | ||
node: matcher.node.property | ||
}); | ||
} | ||
} = jestFnCall; | ||
const matcherName = (0, _utils.getAccessorValue)(matcher); | ||
if (['toBeCalled', 'toHaveBeenCalled'].includes(matcherName)) { | ||
context.report({ | ||
data: { | ||
matcherName | ||
}, | ||
messageId: 'preferCalledWith', | ||
node: matcher | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,19 +7,28 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const isFirstStatement = node => { | ||
let parent = node; | ||
while (parent) { | ||
var _parent$parent, _parent$parent2; | ||
if (((_parent$parent = parent.parent) === null || _parent$parent === void 0 ? void 0 : _parent$parent.type) === _utils.AST_NODE_TYPES.BlockStatement) { | ||
return parent.parent.body[0] === parent; | ||
} | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
// if we've hit an arrow function, then it must have a single expression | ||
// as its body, as otherwise we would have hit the block statement already | ||
if (((_parent$parent2 = parent.parent) === null || _parent$parent2 === void 0 ? void 0 : _parent$parent2.type) === _utils.AST_NODE_TYPES.ArrowFunctionExpression) { | ||
return true; | ||
} | ||
parent = parent.parent; | ||
} | ||
var _utils = require("./utils"); | ||
const isExpectAssertionsOrHasAssertionsCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(expression.callee.object, 'expect') && (0, _utils.isSupportedAccessor)(expression.callee.property) && ['assertions', 'hasAssertions'].includes((0, _utils.getAccessorValue)(expression.callee.property)); | ||
const isFirstLineExprStmt = functionBody => functionBody[0] && functionBody[0].type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement; | ||
const suggestRemovingExtraArguments = (args, extraArgsStartAt) => ({ | ||
/* istanbul ignore next */ | ||
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
}; | ||
const suggestRemovingExtraArguments = (context, func, from) => ({ | ||
messageId: 'suggestRemovingExtraArguments', | ||
fix: fixer => fixer.removeRange([args[extraArgsStartAt].range[0] - Math.sign(extraArgsStartAt), args[args.length - 1].range[1]]) | ||
fix: fixer => (0, _utils2.removeExtraArgumentsFixer)(fixer, context, func, from) | ||
}); | ||
const suggestions = [['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']]; | ||
var _default = (0, _utils.createRule)({ | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -30,4 +39,3 @@ meta: { | ||
description: 'Suggest using `expect.assertions()` OR `expect.hasAssertions()`', | ||
recommended: false, | ||
suggestion: true | ||
recommended: false | ||
}, | ||
@@ -44,85 +52,157 @@ messages: { | ||
type: 'suggestion', | ||
schema: [] | ||
hasSuggestions: true, | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
onlyFunctionsWithAsyncKeyword: { | ||
type: 'boolean' | ||
}, | ||
onlyFunctionsWithExpectInLoop: { | ||
type: 'boolean' | ||
}, | ||
onlyFunctionsWithExpectInCallback: { | ||
type: 'boolean' | ||
} | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
'CallExpression[callee.name=/^(it|test)$/][arguments.1.body.body]'(node) { | ||
const testFuncBody = node.arguments[1].body.body; | ||
if (!isFirstLineExprStmt(testFuncBody)) { | ||
defaultOptions: [{ | ||
onlyFunctionsWithAsyncKeyword: false, | ||
onlyFunctionsWithExpectInLoop: false, | ||
onlyFunctionsWithExpectInCallback: false | ||
}], | ||
create(context, [options]) { | ||
let expressionDepth = 0; | ||
let hasExpectInCallback = false; | ||
let hasExpectInLoop = false; | ||
let hasExpectAssertionsAsFirstStatement = false; | ||
let inTestCaseCall = false; | ||
let inForLoop = false; | ||
const shouldCheckFunction = testFunction => { | ||
if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInLoop && !options.onlyFunctionsWithExpectInCallback) { | ||
return true; | ||
} | ||
if (options.onlyFunctionsWithAsyncKeyword) { | ||
if (testFunction.async) { | ||
return true; | ||
} | ||
} | ||
if (options.onlyFunctionsWithExpectInLoop) { | ||
if (hasExpectInLoop) { | ||
return true; | ||
} | ||
} | ||
if (options.onlyFunctionsWithExpectInCallback) { | ||
if (hasExpectInCallback) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
const checkExpectHasAssertions = (expectFnCall, func) => { | ||
if ((0, _utils2.getAccessorValue)(expectFnCall.members[0]) === 'hasAssertions') { | ||
if (expectFnCall.args.length) { | ||
context.report({ | ||
messageId: 'haveExpectAssertions', | ||
node, | ||
suggest: suggestions.map(([messageId, text]) => ({ | ||
messageId, | ||
fix: fixer => fixer.insertTextBeforeRange([node.arguments[1].body.range[0] + 1, node.arguments[1].body.range[1]], text) | ||
})) | ||
messageId: 'hasAssertionsTakesNoArguments', | ||
node: expectFnCall.matcher, | ||
suggest: [suggestRemovingExtraArguments(context, func, 0)] | ||
}); | ||
return; | ||
} | ||
const testFuncFirstLine = testFuncBody[0].expression; | ||
if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) { | ||
context.report({ | ||
messageId: 'haveExpectAssertions', | ||
node, | ||
suggest: suggestions.map(([messageId, text]) => ({ | ||
messageId, | ||
fix: fixer => fixer.insertTextBefore(testFuncBody[0], text) | ||
})) | ||
}); | ||
return; | ||
} | ||
if (expectFnCall.args.length !== 1) { | ||
let { | ||
loc | ||
} = expectFnCall.matcher; | ||
const suggest = []; | ||
if (expectFnCall.args.length) { | ||
loc = expectFnCall.args[1].loc; | ||
suggest.push(suggestRemovingExtraArguments(context, func, 1)); | ||
} | ||
context.report({ | ||
messageId: 'assertionsRequiresOneArgument', | ||
suggest, | ||
loc | ||
}); | ||
return; | ||
} | ||
const [arg] = expectFnCall.args; | ||
if (arg.type === _utils.AST_NODE_TYPES.Literal && typeof arg.value === 'number' && Number.isInteger(arg.value)) { | ||
return; | ||
} | ||
context.report({ | ||
messageId: 'assertionsRequiresNumberArgument', | ||
node: arg | ||
}); | ||
}; | ||
const enterExpression = () => inTestCaseCall && expressionDepth++; | ||
const exitExpression = () => inTestCaseCall && expressionDepth--; | ||
const enterForLoop = () => inForLoop = true; | ||
const exitForLoop = () => inForLoop = false; | ||
return { | ||
FunctionExpression: enterExpression, | ||
'FunctionExpression:exit': exitExpression, | ||
ArrowFunctionExpression: enterExpression, | ||
'ArrowFunctionExpression:exit': exitExpression, | ||
ForStatement: enterForLoop, | ||
'ForStatement:exit': exitForLoop, | ||
ForInStatement: enterForLoop, | ||
'ForInStatement:exit': exitForLoop, | ||
ForOfStatement: enterForLoop, | ||
'ForOfStatement:exit': exitForLoop, | ||
CallExpression(node) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'test') { | ||
inTestCaseCall = true; | ||
return; | ||
} | ||
if ((0, _utils.isSupportedAccessor)(testFuncFirstLine.callee.property, 'hasAssertions')) { | ||
if (testFuncFirstLine.arguments.length) { | ||
context.report({ | ||
messageId: 'hasAssertionsTakesNoArguments', | ||
node: testFuncFirstLine.callee.property, | ||
suggest: [suggestRemovingExtraArguments(testFuncFirstLine.arguments, 0)] | ||
}); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect' && inTestCaseCall) { | ||
var _jestFnCall$head$node; | ||
if (expressionDepth === 1 && isFirstStatement(node) && ((_jestFnCall$head$node = jestFnCall.head.node.parent) === null || _jestFnCall$head$node === void 0 ? void 0 : _jestFnCall$head$node.type) === _utils.AST_NODE_TYPES.MemberExpression && jestFnCall.members.length === 1 && ['assertions', 'hasAssertions'].includes((0, _utils2.getAccessorValue)(jestFnCall.members[0]))) { | ||
checkExpectHasAssertions(jestFnCall, node); | ||
hasExpectAssertionsAsFirstStatement = true; | ||
} | ||
if (inForLoop) { | ||
hasExpectInLoop = true; | ||
} | ||
if (expressionDepth > 1) { | ||
hasExpectInCallback = true; | ||
} | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
return; | ||
} | ||
if (!(0, _utils.hasOnlyOneArgument)(testFuncFirstLine)) { | ||
let { | ||
loc | ||
} = testFuncFirstLine.callee.property; | ||
const suggest = []; | ||
if (testFuncFirstLine.arguments.length) { | ||
loc = testFuncFirstLine.arguments[1].loc; | ||
suggest.push(suggestRemovingExtraArguments(testFuncFirstLine.arguments, 1)); | ||
} | ||
context.report({ | ||
messageId: 'assertionsRequiresOneArgument', | ||
suggest, | ||
loc | ||
}); | ||
inTestCaseCall = false; | ||
if (node.arguments.length < 2) { | ||
return; | ||
} | ||
const [arg] = testFuncFirstLine.arguments; | ||
if (arg.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof arg.value === 'number' && Number.isInteger(arg.value)) { | ||
const [, testFn] = node.arguments; | ||
if (!(0, _utils2.isFunction)(testFn) || !shouldCheckFunction(testFn)) { | ||
return; | ||
} | ||
hasExpectInLoop = false; | ||
hasExpectInCallback = false; | ||
if (hasExpectAssertionsAsFirstStatement) { | ||
hasExpectAssertionsAsFirstStatement = false; | ||
return; | ||
} | ||
const suggestions = []; | ||
if (testFn.body.type === _utils.AST_NODE_TYPES.BlockStatement) { | ||
suggestions.push(['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']); | ||
} | ||
context.report({ | ||
messageId: 'assertionsRequiresNumberArgument', | ||
node: arg | ||
messageId: 'haveExpectAssertions', | ||
node, | ||
suggest: suggestions.map(([messageId, text]) => ({ | ||
messageId, | ||
fix: fixer => fixer.insertTextBeforeRange([testFn.body.range[0] + 1, testFn.body.range[1]], text) | ||
})) | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -20,3 +18,3 @@ name: __filename, | ||
messages: { | ||
noHookOnTop: 'Move all hooks before test cases' | ||
noHookOnTop: 'Hooks should come before test cases' | ||
}, | ||
@@ -27,3 +25,2 @@ schema: [], | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -33,7 +30,6 @@ const hooksContext = [false]; | ||
CallExpression(node) { | ||
if (!(0, _utils.isHook)(node) && (0, _utils.isTestCase)(node)) { | ||
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
hooksContext[hooksContext.length - 1] = true; | ||
} | ||
if (hooksContext[hooksContext.length - 1] && (0, _utils.isHook)(node)) { | ||
if (hooksContext[hooksContext.length - 1] && (0, _utils.isTypeOfJestFnCall)(node, context, ['hook'])) { | ||
context.report({ | ||
@@ -44,15 +40,10 @@ messageId: 'noHookOnTop', | ||
} | ||
hooksContext.push(false); | ||
}, | ||
'CallExpression:exit'() { | ||
hooksContext.pop(); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,7 +7,4 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const findNodeObject = node => { | ||
@@ -17,29 +14,31 @@ if ('object' in node) { | ||
} | ||
if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) { | ||
if (node.callee.type === _utils.AST_NODE_TYPES.MemberExpression) { | ||
return node.callee.object; | ||
} | ||
return null; | ||
}; | ||
const getJestFnCall = node => { | ||
if (node.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression && node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) { | ||
if (node.type !== _utils.AST_NODE_TYPES.CallExpression && node.type !== _utils.AST_NODE_TYPES.MemberExpression) { | ||
return null; | ||
} | ||
const obj = findNodeObject(node); | ||
if (!obj) { | ||
return null; | ||
} | ||
if (obj.type === _experimentalUtils.AST_NODE_TYPES.Identifier) { | ||
return node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.getNodeName)(node.callee) === 'jest.fn' ? node : null; | ||
if (obj.type === _utils.AST_NODE_TYPES.Identifier) { | ||
return node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.getNodeName)(node.callee) === 'jest.fn' ? node : null; | ||
} | ||
return getJestFnCall(obj); | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
const getAutoFixMockImplementation = (jestFnCall, context) => { | ||
var _jestFnCall$parent; | ||
const hasMockImplementationAlready = ((_jestFnCall$parent = jestFnCall.parent) === null || _jestFnCall$parent === void 0 ? void 0 : _jestFnCall$parent.type) === _utils.AST_NODE_TYPES.MemberExpression && jestFnCall.parent.property.type === _utils.AST_NODE_TYPES.Identifier && jestFnCall.parent.property.name === 'mockImplementation'; | ||
if (hasMockImplementationAlready) { | ||
return ''; | ||
} | ||
const [arg] = jestFnCall.arguments; | ||
const argSource = arg && context.getSourceCode().getText(arg); | ||
return argSource ? `.mockImplementation(${argSource})` : '.mockImplementation()'; | ||
}; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -60,3 +59,2 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -69,3 +67,3 @@ return { | ||
} = node; | ||
if (left.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) return; | ||
if (left.type !== _utils.AST_NODE_TYPES.MemberExpression) return; | ||
const jestFnCall = getJestFnCall(right); | ||
@@ -76,19 +74,12 @@ if (!jestFnCall) return; | ||
messageId: 'useJestSpyOn', | ||
fix(fixer) { | ||
const leftPropQuote = left.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? "'" : ''; | ||
const [arg] = jestFnCall.arguments; | ||
const argSource = arg && context.getSourceCode().getText(arg); | ||
const mockImplementation = argSource ? `.mockImplementation(${argSource})` : '.mockImplementation()'; | ||
const leftPropQuote = left.property.type === _utils.AST_NODE_TYPES.Identifier && !left.computed ? "'" : ''; | ||
const mockImplementation = getAutoFixMockImplementation(jestFnCall, context); | ||
return [fixer.insertTextBefore(left, `jest.spyOn(`), fixer.replaceTextRange([left.object.range[1], left.property.range[0]], `, ${leftPropQuote}`), fixer.replaceTextRange([left.property.range[1], jestFnCall.range[1]], `${leftPropQuote})${mockImplementation}`)]; | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -17,4 +15,3 @@ name: __filename, | ||
description: 'Suggest using `toStrictEqual()`', | ||
recommended: false, | ||
suggestion: true | ||
recommended: false | ||
}, | ||
@@ -26,24 +23,23 @@ messages: { | ||
type: 'suggestion', | ||
schema: [] | ||
schema: [], | ||
hasSuggestions: true | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (matcher && (0, _utils.isParsedEqualityMatcherCall)(matcher, _utils.EqualityMatcher.toEqual)) { | ||
} = jestFnCall; | ||
if ((0, _utils.isSupportedAccessor)(matcher, 'toEqual')) { | ||
context.report({ | ||
messageId: 'useToStrictEqual', | ||
node: matcher.node.property, | ||
node: matcher, | ||
suggest: [{ | ||
messageId: 'suggestReplaceWithStrictEqual', | ||
fix: fixer => [fixer.replaceText(matcher.node.property, _utils.EqualityMatcher.toStrictEqual)] | ||
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher, _utils.EqualityMatcher.toStrictEqual)] | ||
}] | ||
@@ -53,8 +49,5 @@ }); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,24 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean'; | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
/** | ||
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, | ||
* with a boolean literal as the sole argument. | ||
* | ||
* @example javascript | ||
* toBe(true); | ||
* toEqual(false); | ||
* | ||
* @param {ParsedExpectMatcher} matcher | ||
* | ||
* @return {matcher is ParsedBooleanEqualityMatcher} | ||
*/ | ||
const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0])); | ||
/** | ||
* Checks if the given `node` is a `CallExpression` representing the calling | ||
@@ -36,51 +17,7 @@ * of an `includes`-like method that can be 'fixed' (using `toContain`). | ||
* @return {node is FixableIncludesCallExpression} | ||
* | ||
* @todo support `['includes']()` syntax (remove last property.type check to begin) | ||
* @todo break out into `isMethodCall<Name extends string>(node: TSESTree.Node, method: Name)` util-fn | ||
*/ | ||
const isFixableIncludesCallExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'includes') && node.callee.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (0, _utils.hasOnlyOneArgument)(node); | ||
const isFixableIncludesCallExpression = node => node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils2.hasOnlyOneArgument)(node) && node.arguments[0].type !== _utils.AST_NODE_TYPES.SpreadElement; | ||
const buildToContainFuncExpectation = negated => negated ? `${_utils.ModifierName.not}.toContain` : 'toContain'; | ||
/** | ||
* Finds the first `.` character token between the `object` & `property` of the given `member` expression. | ||
* | ||
* @param {TSESTree.MemberExpression} member | ||
* @param {SourceCode} sourceCode | ||
* | ||
* @return {Token | null} | ||
*/ | ||
const findPropertyDotToken = (member, sourceCode) => sourceCode.getFirstTokenBetween(member.object, member.property, token => token.value === '.'); | ||
const getNegationFixes = (node, modifier, matcher, sourceCode, fixer, fileName) => { | ||
const [containArg] = node.arguments; | ||
const negationPropertyDot = findPropertyDotToken(modifier.node, sourceCode); | ||
const toContainFunc = buildToContainFuncExpectation((0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value); | ||
/* istanbul ignore if */ | ||
if (negationPropertyDot === null) { | ||
throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
} | ||
return [fixer.remove(negationPropertyDot), fixer.remove(modifier.node.property), fixer.replaceText(matcher.node.property, toContainFunc), fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg))]; | ||
}; | ||
const getCommonFixes = (node, sourceCode, fileName) => { | ||
const [containArg] = node.arguments; | ||
const includesCallee = node.callee; | ||
const propertyDot = findPropertyDotToken(includesCallee, sourceCode); | ||
const closingParenthesis = sourceCode.getTokenAfter(containArg); | ||
const openParenthesis = sourceCode.getTokenBefore(containArg); | ||
/* istanbul ignore if */ | ||
if (propertyDot === null || closingParenthesis === null || openParenthesis === null) { | ||
throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
} | ||
return [containArg, includesCallee.property, propertyDot, closingParenthesis, openParenthesis]; | ||
}; // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>) | ||
var _default = (0, _utils.createRule)({ | ||
// expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>) | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -101,49 +38,49 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect' || jestFnCall.args.length === 0) { | ||
return; | ||
} | ||
const { | ||
expect: { | ||
arguments: [includesCall] | ||
}, | ||
matcher, | ||
modifier | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (!matcher || !includesCall || modifier && modifier.name !== _utils.ModifierName.not || !isBooleanEqualityMatcher(matcher) || !isFixableIncludesCallExpression(includesCall)) { | ||
parent: expect | ||
} = jestFnCall.head.node; | ||
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) { | ||
return; | ||
} | ||
const { | ||
arguments: [includesCall], | ||
range: [, expectCallEnd] | ||
} = expect; | ||
const { | ||
matcher | ||
} = jestFnCall; | ||
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall); | ||
if (!includesCall || matcherArg.type === _utils.AST_NODE_TYPES.SpreadElement || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg) || !isFixableIncludesCallExpression(includesCall)) { | ||
return; | ||
} | ||
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not'); | ||
context.report({ | ||
fix(fixer) { | ||
const sourceCode = context.getSourceCode(); | ||
const fileName = context.getFilename(); | ||
const fixArr = getCommonFixes(includesCall, sourceCode, fileName).map(target => fixer.remove(target)); | ||
if (modifier) { | ||
return getNegationFixes(includesCall, modifier, matcher, sourceCode, fixer, fileName).concat(fixArr); | ||
} | ||
const toContainFunc = buildToContainFuncExpectation(!(0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value); | ||
const [containArg] = includesCall.arguments; | ||
fixArr.push(fixer.replaceText(matcher.node.property, toContainFunc)); | ||
fixArr.push(fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg))); | ||
return fixArr; | ||
// we need to negate the expectation if the current expected | ||
// value is itself negated by the "not" modifier | ||
const addNotModifier = matcherArg.value === hasNot; | ||
return [ | ||
// remove the "includes" call entirely | ||
fixer.removeRange([includesCall.callee.property.range[0] - 1, includesCall.range[1]]), | ||
// replace the current matcher with "toContain", adding "not" if needed | ||
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], addNotModifier ? `.${_utils2.ModifierName.not}.toContain` : '.toContain'), | ||
// replace the matcher argument with the value from the "includes" | ||
fixer.replaceText(jestFnCall.args[0], sourceCode.getText(includesCall.arguments[0]))]; | ||
}, | ||
messageId: 'useToContain', | ||
node: (modifier || matcher).node.property | ||
node: matcher | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,8 +7,5 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -29,43 +26,37 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
expect: { | ||
arguments: [argument] | ||
}, | ||
parent: expect | ||
} = jestFnCall.head.node; | ||
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) { | ||
return; | ||
} | ||
const [argument] = expect.arguments; | ||
const { | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (!matcher || !(0, _utils.isParsedEqualityMatcherCall)(matcher) || !argument || argument.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || !(0, _utils.isSupportedAccessor)(argument.property, 'length') || argument.property.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) { | ||
} = jestFnCall; | ||
if (!_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'length')) { | ||
return; | ||
} | ||
context.report({ | ||
fix(fixer) { | ||
const propertyDot = context.getSourceCode().getFirstTokenBetween(argument.object, argument.property, token => token.value === '.'); | ||
/* istanbul ignore if */ | ||
if (propertyDot === null) { | ||
throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
} | ||
return [fixer.remove(propertyDot), fixer.remove(argument.property), fixer.replaceText(matcher.node.property, 'toHaveLength')]; | ||
return [ | ||
// remove the "length" property accessor | ||
fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]), | ||
// replace the current matcher with "toHaveLength" | ||
fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], '.toHaveLength')]; | ||
}, | ||
messageId: 'useToHaveLength', | ||
node: matcher.node.property | ||
node: matcher | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,29 +7,28 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
function isEmptyFunction(node) { | ||
if (!(0, _utils.isFunction)(node)) { | ||
if (!(0, _utils2.isFunction)(node)) { | ||
return false; | ||
} | ||
/* istanbul ignore if https://github.com/typescript-eslint/typescript-eslint/issues/734 */ | ||
if (!node.body) { | ||
throw new Error(`Unexpected null while performing prefer-todo - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
return node.body.type === _utils.AST_NODE_TYPES.BlockStatement && !node.body.body.length; | ||
} | ||
function createTodoFixer(jestFnCall, fixer) { | ||
if (jestFnCall.members.length) { | ||
return (0, _utils2.replaceAccessorFixer)(fixer, jestFnCall.members[0], 'todo'); | ||
} | ||
return node.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && node.body.body && !node.body.body.length; | ||
return fixer.replaceText(jestFnCall.head.node, `${jestFnCall.head.local}.todo`); | ||
} | ||
const isTargetedTestCase = jestFnCall => { | ||
if (jestFnCall.members.some(s => (0, _utils2.getAccessorValue)(s) !== 'skip')) { | ||
return false; | ||
} | ||
function createTodoFixer(node, fixer) { | ||
const testName = (0, _utils.getNodeName)(node.callee).split('.').shift(); | ||
return fixer.replaceText(node.callee, `${testName}.todo`); | ||
} | ||
const isTargetedTestCase = node => (0, _utils.isTestCase)(node) && [_utils.TestCaseName.it, _utils.TestCaseName.test, 'it.skip', 'test.skip'].includes((0, _utils.getNodeName)(node.callee)); | ||
var _default = (0, _utils.createRule)({ | ||
// todo: we should support this too (needs custom fixer) | ||
if (jestFnCall.name.startsWith('x')) { | ||
return false; | ||
} | ||
return !jestFnCall.name.startsWith('f'); | ||
}; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -51,3 +50,2 @@ meta: { | ||
defaultOptions: [], | ||
create(context) { | ||
@@ -57,7 +55,6 @@ return { | ||
const [title, callback] = node.arguments; | ||
if (!title || !isTargetedTestCase(node) || !(0, _utils.isStringNode)(title)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if (!title || (jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'test' || !isTargetedTestCase(jestFnCall) || !(0, _utils2.isStringNode)(title)) { | ||
return; | ||
} | ||
if (callback && isEmptyFunction(callback)) { | ||
@@ -67,20 +64,16 @@ context.report({ | ||
node, | ||
fix: fixer => [fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(node, fixer)] | ||
fix: fixer => [fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(jestFnCall, fixer)] | ||
}); | ||
} | ||
if ((0, _utils.hasOnlyOneArgument)(node)) { | ||
if ((0, _utils2.hasOnlyOneArgument)(node)) { | ||
context.report({ | ||
messageId: 'unimplementedTest', | ||
node, | ||
fix: fixer => [createTodoFixer(node, fixer)] | ||
fix: fixer => createTodoFixer(jestFnCall, fixer) | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,3 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
var _default = (0, _utils.createRule)({ | ||
@@ -26,16 +24,14 @@ name: __filename, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
matcher, | ||
modifier | ||
} = (0, _utils.parseExpectCall)(node); | ||
if (matcher && matcher.arguments && matcher.arguments.length === 0 && ['toThrow', 'toThrowError'].includes(matcher.name) && (!modifier || !(modifier.name === _utils.ModifierName.not || modifier.negation))) { | ||
matcher | ||
} = jestFnCall; | ||
const matcherName = (0, _utils.getAccessorValue)(matcher); | ||
if (jestFnCall.args.length === 0 && ['toThrow', 'toThrowError'].includes(matcherName) && !jestFnCall.modifiers.some(nod => (0, _utils.getAccessorValue)(nod) === 'not')) { | ||
// Look for `toThrow` calls with no arguments. | ||
@@ -45,14 +41,11 @@ context.report({ | ||
data: { | ||
matcherName: matcher.name | ||
matcherName | ||
}, | ||
node: matcher.node.property | ||
node: matcher | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,5 +7,8 @@ "use strict"; | ||
exports.default = void 0; | ||
var _utils = require("./utils"); | ||
const messages = { | ||
tooManyDescribes: 'There should not be more than {{ max }} describe{{ s }} at the top level', | ||
unexpectedTestCase: 'All test cases must be wrapped in a describe block.', | ||
unexpectedHook: 'All hooks must be wrapped in a describe block.' | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
@@ -19,22 +22,47 @@ name: __filename, | ||
}, | ||
messages: { | ||
unexpectedTestCase: 'All test cases must be wrapped in a describe block.', | ||
unexpectedHook: 'All hooks must be wrapped in a describe block.' | ||
}, | ||
messages, | ||
type: 'suggestion', | ||
schema: [] | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
maxNumberOfTopLevelDescribes: { | ||
type: 'number', | ||
minimum: 1 | ||
} | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
defaultOptions: [], | ||
defaultOptions: [{}], | ||
create(context) { | ||
const { | ||
maxNumberOfTopLevelDescribes = Infinity | ||
} = context.options[0] ?? {}; | ||
let numberOfTopLevelDescribeBlocks = 0; | ||
let numberOfDescribeBlocks = 0; | ||
return { | ||
CallExpression(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
const jestFnCall = (0, _utils.parseJestFnCall)(node, context); | ||
if (!jestFnCall) { | ||
return; | ||
} | ||
if (jestFnCall.type === 'describe') { | ||
numberOfDescribeBlocks++; | ||
if (numberOfDescribeBlocks === 1) { | ||
numberOfTopLevelDescribeBlocks++; | ||
if (numberOfTopLevelDescribeBlocks > maxNumberOfTopLevelDescribes) { | ||
context.report({ | ||
node, | ||
messageId: 'tooManyDescribes', | ||
data: { | ||
max: maxNumberOfTopLevelDescribes, | ||
s: maxNumberOfTopLevelDescribes === 1 ? '' : 's' | ||
} | ||
}); | ||
} | ||
} | ||
return; | ||
} | ||
if (numberOfDescribeBlocks === 0) { | ||
if ((0, _utils.isTestCase)(node)) { | ||
if (jestFnCall.type === 'test') { | ||
context.report({ | ||
@@ -46,4 +74,3 @@ node, | ||
} | ||
if ((0, _utils.isHook)(node)) { | ||
if (jestFnCall.type === 'hook') { | ||
context.report({ | ||
@@ -57,14 +84,10 @@ node, | ||
}, | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isDescribe)(node)) { | ||
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe'])) { | ||
numberOfDescribeBlocks--; | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,88 +7,202 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const isThenOrCatchCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(node.callee.property)); | ||
const isExpectCallPresentInFunction = body => { | ||
if (body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) { | ||
return body.body.find(line => { | ||
if (line.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) { | ||
return isFullExpectCall(line.expression); | ||
} | ||
if (line.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && line.argument) { | ||
return isFullExpectCall(line.argument); | ||
} | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const isPromiseChainCall = node => { | ||
if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property)) { | ||
// promise methods should have at least 1 argument | ||
if (node.arguments.length === 0) { | ||
return false; | ||
}); | ||
} | ||
switch ((0, _utils2.getAccessorValue)(node.callee.property)) { | ||
case 'then': | ||
return node.arguments.length < 3; | ||
case 'catch': | ||
case 'finally': | ||
return node.arguments.length < 2; | ||
} | ||
} | ||
return isFullExpectCall(body); | ||
return false; | ||
}; | ||
const isFullExpectCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(expression.callee); | ||
const reportReturnRequired = (context, node) => { | ||
context.report({ | ||
loc: { | ||
end: { | ||
column: node.loc.end.column, | ||
line: node.loc.end.line | ||
}, | ||
start: node.loc.start | ||
}, | ||
messageId: 'returnPromise', | ||
node | ||
}); | ||
const isTestCaseCallWithCallbackArg = (node, context) => { | ||
const jestCallFn = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestCallFn === null || jestCallFn === void 0 ? void 0 : jestCallFn.type) !== 'test') { | ||
return false; | ||
} | ||
const isJestEach = jestCallFn.members.some(s => (0, _utils2.getAccessorValue)(s) === 'each'); | ||
if (isJestEach && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) { | ||
// isJestEach but not a TaggedTemplateExpression, so this must be | ||
// the `jest.each([])()` syntax which this rule doesn't support due | ||
// to its complexity (see jest-community/eslint-plugin-jest#710) | ||
// so we return true to trigger bailout | ||
return true; | ||
} | ||
const [, callback] = node.arguments; | ||
const callbackArgIndex = Number(isJestEach); | ||
return callback && (0, _utils2.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex; | ||
}; | ||
const isPromiseMethodThatUsesValue = (node, identifier) => { | ||
const { | ||
name | ||
} = identifier; | ||
if (node.argument === null) { | ||
return false; | ||
} | ||
if (node.argument.type === _utils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) { | ||
const nodeName = (0, _utils2.getNodeName)(node.argument); | ||
if (['Promise.all', 'Promise.allSettled'].includes(nodeName)) { | ||
const [firstArg] = node.argument.arguments; | ||
if (firstArg.type === _utils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => nod && (0, _utils2.isIdentifier)(nod, name))) { | ||
return true; | ||
} | ||
} | ||
if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) { | ||
return (0, _utils2.isIdentifier)(node.argument.arguments[0], name); | ||
} | ||
} | ||
return (0, _utils2.isIdentifier)(node.argument, name); | ||
}; | ||
const isPromiseReturnedLater = (node, testFunctionBody) => { | ||
let promiseName; | ||
if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && node.expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.expression.callee.object)) { | ||
promiseName = (0, _utils.getAccessorValue)(node.expression.callee.object); | ||
} else if (node.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator && node.id.type === _experimentalUtils.AST_NODE_TYPES.Identifier) { | ||
promiseName = node.id.name; | ||
/** | ||
* Attempts to determine if the runtime value represented by the given `identifier` | ||
* is `await`ed within the given array of elements | ||
*/ | ||
const isValueAwaitedInElements = (name, elements) => { | ||
for (const element of elements) { | ||
if ((element === null || element === void 0 ? void 0 : element.type) === _utils.AST_NODE_TYPES.AwaitExpression && (0, _utils2.isIdentifier)(element.argument, name)) { | ||
return true; | ||
} | ||
if ((element === null || element === void 0 ? void 0 : element.type) === _utils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) { | ||
return true; | ||
} | ||
} | ||
const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1]; | ||
return lastLineInTestFunc.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && lastLineInTestFunc.argument && ('name' in lastLineInTestFunc.argument && lastLineInTestFunc.argument.name === promiseName || !promiseName); | ||
return false; | ||
}; | ||
const isTestFunc = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isSupportedAccessor)(node.callee) && [_utils.TestCaseName.it, _utils.TestCaseName.test].includes((0, _utils.getAccessorValue)(node.callee)); | ||
const findTestFunction = node => { | ||
/** | ||
* Attempts to determine if the runtime value represented by the given `identifier` | ||
* is `await`ed as an argument along the given call expression | ||
*/ | ||
const isValueAwaitedInArguments = (name, call) => { | ||
let node = call; | ||
while (node) { | ||
if ((0, _utils.isFunction)(node) && node.parent && isTestFunc(node.parent)) { | ||
return node; | ||
if (node.type === _utils.AST_NODE_TYPES.CallExpression) { | ||
if (isValueAwaitedInElements(name, node.arguments)) { | ||
return true; | ||
} | ||
node = node.callee; | ||
} | ||
node = node.parent; | ||
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) { | ||
break; | ||
} | ||
node = node.object; | ||
} | ||
return null; | ||
return false; | ||
}; | ||
const getLeftMostCallExpression = call => { | ||
let leftMostCallExpression = call; | ||
let node = call; | ||
while (node) { | ||
if (node.type === _utils.AST_NODE_TYPES.CallExpression) { | ||
leftMostCallExpression = node; | ||
node = node.callee; | ||
} | ||
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) { | ||
break; | ||
} | ||
node = node.object; | ||
} | ||
return leftMostCallExpression; | ||
}; | ||
const isParentThenOrPromiseReturned = (node, testFunctionBody) => node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement || isPromiseReturnedLater(node, testFunctionBody); | ||
const verifyExpectWithReturn = (promiseCallbacks, node, context, testFunctionBody) => { | ||
promiseCallbacks.some(promiseCallback => { | ||
if (promiseCallback && (0, _utils.isFunction)(promiseCallback) && promiseCallback.body) { | ||
if (isExpectCallPresentInFunction(promiseCallback.body) && node.parent && node.parent.parent && !isParentThenOrPromiseReturned(node.parent.parent, testFunctionBody)) { | ||
reportReturnRequired(context, node.parent.parent); | ||
/** | ||
* Attempts to determine if the runtime value represented by the given `identifier` | ||
* is `await`ed or `return`ed within the given `body` of statements | ||
*/ | ||
const isValueAwaitedOrReturned = (identifier, body, context) => { | ||
const { | ||
name | ||
} = identifier; | ||
for (const node of body) { | ||
// skip all nodes that are before this identifier, because they'd probably | ||
// be affecting a different runtime value (e.g. due to reassignment) | ||
if (node.range[0] <= identifier.range[0]) { | ||
continue; | ||
} | ||
if (node.type === _utils.AST_NODE_TYPES.ReturnStatement) { | ||
return isPromiseMethodThatUsesValue(node, identifier); | ||
} | ||
if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement) { | ||
// it's possible that we're awaiting the value as an argument | ||
if (node.expression.type === _utils.AST_NODE_TYPES.CallExpression) { | ||
if (isValueAwaitedInArguments(name, node.expression)) { | ||
return true; | ||
} | ||
const leftMostCall = getLeftMostCallExpression(node.expression); | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node.expression, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) === 'expect' && leftMostCall.arguments.length > 0 && (0, _utils2.isIdentifier)(leftMostCall.arguments[0], name)) { | ||
if (jestFnCall.members.some(m => { | ||
const v = (0, _utils2.getAccessorValue)(m); | ||
return v === _utils2.ModifierName.resolves || v === _utils2.ModifierName.rejects; | ||
})) { | ||
return true; | ||
} | ||
} | ||
} | ||
if (node.expression.type === _utils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) { | ||
return true; | ||
} | ||
// (re)assignment changes the runtime value, so if we've not found an | ||
// await or return already we act as if we've reached the end of the body | ||
if (node.expression.type === _utils.AST_NODE_TYPES.AssignmentExpression) { | ||
var _getNodeName; | ||
// unless we're assigning to the same identifier, in which case | ||
// we might be chaining off the existing promise value | ||
if ((0, _utils2.isIdentifier)(node.expression.left, name) && (_getNodeName = (0, _utils2.getNodeName)(node.expression.right)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) { | ||
continue; | ||
} | ||
break; | ||
} | ||
} | ||
if (node.type === _utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body, context)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
const findFirstBlockBodyUp = node => { | ||
let parent = node; | ||
while (parent) { | ||
if (parent.type === _utils.AST_NODE_TYPES.BlockStatement) { | ||
return parent.body; | ||
} | ||
parent = parent.parent; | ||
} | ||
return false; | ||
}); | ||
/* istanbul ignore next */ | ||
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
}; | ||
const isDirectlyWithinTestCaseCall = (node, context) => { | ||
let parent = node; | ||
while (parent) { | ||
if ((0, _utils2.isFunction)(parent)) { | ||
var _parent; | ||
parent = parent.parent; | ||
return ((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isTypeOfJestFnCall)(parent, context, ['test']); | ||
} | ||
parent = parent.parent; | ||
} | ||
return false; | ||
}; | ||
const isVariableAwaitedOrReturned = (variable, context) => { | ||
const body = findFirstBlockBodyUp(variable); | ||
const isHavingAsyncCallBackParam = testFunction => testFunction.params[0] && testFunction.params[0].type === _experimentalUtils.AST_NODE_TYPES.Identifier; | ||
var _default = (0, _utils.createRule)({ | ||
// it's pretty much impossible for us to track destructuring assignments, | ||
// so we return true to bailout gracefully | ||
if (!(0, _utils2.isIdentifier)(variable.id)) { | ||
return true; | ||
} | ||
return isValueAwaitedOrReturned(variable.id, body, context); | ||
}; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -98,7 +212,7 @@ meta: { | ||
category: 'Best Practices', | ||
description: 'Enforce having return statement when testing with promises', | ||
description: 'Require promises that have expectations in their chain to be valid', | ||
recommended: 'error' | ||
}, | ||
messages: { | ||
returnPromise: 'Promise should be returned to test its fulfillment or rejection' | ||
expectInFloatingPromise: 'This promise should either be returned or awaited to ensure the expects in its chain are called' | ||
}, | ||
@@ -109,41 +223,98 @@ type: 'suggestion', | ||
defaultOptions: [], | ||
create(context) { | ||
let inTestCaseWithDoneCallback = false; | ||
// an array of booleans representing each promise chain we enter, with the | ||
// boolean value representing if we think a given chain contains an expect | ||
// in it's body. | ||
// | ||
// since we only care about the inner-most chain, we represent the state in | ||
// reverse with the inner-most being the first item, as that makes it | ||
// slightly less code to assign to by not needing to know the length | ||
const chains = []; | ||
return { | ||
CallExpression(node) { | ||
if (!isThenOrCatchCall(node) || node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) { | ||
// there are too many ways that the done argument could be used with | ||
// promises that contain expect that would make the promise safe for us | ||
if (isTestCaseCallWithCallbackArg(node, context)) { | ||
inTestCaseWithDoneCallback = true; | ||
return; | ||
} | ||
const testFunction = findTestFunction(node); | ||
// if this call expression is a promise chain, add it to the stack with | ||
// value of "false", as we assume there are no expect calls initially | ||
if (isPromiseChainCall(node)) { | ||
chains.unshift(false); | ||
return; | ||
} | ||
if (testFunction && !isHavingAsyncCallBackParam(testFunction)) { | ||
const { | ||
body | ||
} = testFunction; | ||
/* istanbul ignore if https://github.com/typescript-eslint/typescript-eslint/issues/734 */ | ||
if (!body) { | ||
throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`); | ||
// if we're within a promise chain, and this call expression looks like | ||
// an expect call, mark the deepest chain as having an expect call | ||
if (chains.length > 0 && (0, _utils2.isTypeOfJestFnCall)(node, context, ['expect'])) { | ||
chains[0] = true; | ||
} | ||
}, | ||
'CallExpression:exit'(node) { | ||
// there are too many ways that the "done" argument could be used to | ||
// make promises containing expects safe in a test for us to be able to | ||
// accurately check, so we just bail out completely if it's present | ||
if (inTestCaseWithDoneCallback) { | ||
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) { | ||
inTestCaseWithDoneCallback = false; | ||
} | ||
return; | ||
} | ||
if (!isPromiseChainCall(node)) { | ||
return; | ||
} | ||
if (body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) { | ||
return; | ||
} | ||
// since we're exiting this call expression (which is a promise chain) | ||
// we remove it from the stack of chains, since we're unwinding | ||
const hasExpectCall = chains.shift(); | ||
const testFunctionBody = body.body; | ||
const [fulfillmentCallback, rejectionCallback] = node.arguments; // then block can have two args, fulfillment & rejection | ||
// then block can have one args, fulfillment | ||
// catch block can have one args, rejection | ||
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise | ||
// if the promise chain we're exiting doesn't contain an expect, | ||
// then we don't need to check it for anything | ||
if (!hasExpectCall) { | ||
return; | ||
} | ||
const { | ||
parent | ||
} = (0, _utils2.findTopMostCallExpression)(node); | ||
verifyExpectWithReturn([fulfillmentCallback, rejectionCallback], node.callee, context, testFunctionBody); | ||
// if we don't have a parent (which is technically impossible at runtime) | ||
// or our parent is not directly within the test case, we stop checking | ||
// because we're most likely in the body of a function being defined | ||
// within the test, which we can't track | ||
if (!parent || !isDirectlyWithinTestCaseCall(parent, context)) { | ||
return; | ||
} | ||
switch (parent.type) { | ||
case _utils.AST_NODE_TYPES.VariableDeclarator: | ||
{ | ||
if (isVariableAwaitedOrReturned(parent, context)) { | ||
return; | ||
} | ||
break; | ||
} | ||
case _utils.AST_NODE_TYPES.AssignmentExpression: | ||
{ | ||
if (parent.left.type === _utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent), context)) { | ||
return; | ||
} | ||
break; | ||
} | ||
case _utils.AST_NODE_TYPES.ExpressionStatement: | ||
break; | ||
case _utils.AST_NODE_TYPES.ReturnStatement: | ||
case _utils.AST_NODE_TYPES.AwaitExpression: | ||
default: | ||
return; | ||
} | ||
context.report({ | ||
messageId: 'expectInFloatingPromise', | ||
node: parent | ||
}); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,7 +7,4 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
/* | ||
@@ -26,30 +23,32 @@ * This implementation is ported from from eslint-plugin-jasmine. | ||
const getPromiseCallExpressionNode = node => { | ||
if (node.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) { | ||
if (node.type === _utils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _utils.AST_NODE_TYPES.CallExpression) { | ||
node = node.parent; | ||
} | ||
if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.object) && (0, _utils.getAccessorValue)(node.callee.object) === 'Promise' && node.parent) { | ||
if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.object, 'Promise') && node.parent) { | ||
return node; | ||
} | ||
return null; | ||
}; | ||
const findPromiseCallExpressionNode = node => node.parent && node.parent.parent && [_experimentalUtils.AST_NODE_TYPES.CallExpression, _experimentalUtils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null; | ||
const findPromiseCallExpressionNode = node => { | ||
var _node$parent; | ||
return (_node$parent = node.parent) !== null && _node$parent !== void 0 && _node$parent.parent && [_utils.AST_NODE_TYPES.CallExpression, _utils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null; | ||
}; | ||
const getParentIfThenified = node => { | ||
const grandParentNode = node.parent && node.parent.parent; | ||
if (grandParentNode && grandParentNode.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && grandParentNode.callee && (0, _utils.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) { | ||
var _node$parent2; | ||
const grandParentNode = (_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.parent; | ||
if (grandParentNode && grandParentNode.type === _utils.AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(grandParentNode.callee.property) && ['then', 'catch'].includes((0, _utils2.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) { | ||
// Just in case `then`s are chained look one above. | ||
return getParentIfThenified(grandParentNode); | ||
} | ||
return node; | ||
}; | ||
const isAcceptableReturnNode = (node, allowReturn) => allowReturn && node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement || [_experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression, _experimentalUtils.AST_NODE_TYPES.AwaitExpression].includes(node.type); | ||
const isNoAssertionsParentNode = node => node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement || node.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement; | ||
const isAcceptableReturnNode = (node, allowReturn) => { | ||
if (allowReturn && node.type === _utils.AST_NODE_TYPES.ReturnStatement) { | ||
return true; | ||
} | ||
if (node.type === _utils.AST_NODE_TYPES.ConditionalExpression && node.parent) { | ||
return isAcceptableReturnNode(node.parent, allowReturn); | ||
} | ||
return [_utils.AST_NODE_TYPES.ArrowFunctionExpression, _utils.AST_NODE_TYPES.AwaitExpression].includes(node.type); | ||
}; | ||
const promiseArrayExceptionKey = ({ | ||
@@ -59,4 +58,4 @@ start, | ||
}) => `${start.line}:${start.column}-${end.line}:${end.column}`; | ||
var _default = (0, _utils.createRule)({ | ||
const defaultAsyncMatchers = ['toReject', 'toResolve']; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -72,3 +71,3 @@ meta: { | ||
notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.', | ||
modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".', | ||
modifierUnknown: 'Expect has an unknown modifier.', | ||
matcherNotFound: 'Expect must have a corresponding matcher call.', | ||
@@ -87,2 +86,8 @@ matcherNotCalled: 'Matchers must be called to assert.', | ||
}, | ||
asyncMatchers: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
} | ||
}, | ||
minArgs: { | ||
@@ -102,8 +107,9 @@ type: 'number', | ||
alwaysAwait: false, | ||
asyncMatchers: defaultAsyncMatchers, | ||
minArgs: 1, | ||
maxArgs: 1 | ||
}], | ||
create(context, [{ | ||
alwaysAwait, | ||
asyncMatchers = defaultAsyncMatchers, | ||
minArgs = 1, | ||
@@ -114,4 +120,4 @@ maxArgs = 1 | ||
const arrayExceptions = new Set(); | ||
const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc)); | ||
const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc)); | ||
/** | ||
@@ -124,28 +130,63 @@ * Promise method that accepts an array of promises, | ||
*/ | ||
const promiseArrayExceptionExists = loc => arrayExceptions.has(promiseArrayExceptionKey(loc)); | ||
const findTopMostMemberExpression = node => { | ||
let topMostMemberExpression = node; | ||
let { | ||
parent | ||
} = node; | ||
while (parent) { | ||
if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) { | ||
break; | ||
} | ||
topMostMemberExpression = parent; | ||
parent = parent.parent; | ||
} | ||
return topMostMemberExpression; | ||
}; | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isExpectCall)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCallWithReason)(node, context); | ||
if (typeof jestFnCall === 'string') { | ||
var _node$parent3; | ||
const reportingNode = ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.type) === _utils.AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node; | ||
if (jestFnCall === 'matcher-not-found') { | ||
context.report({ | ||
messageId: 'matcherNotFound', | ||
node: reportingNode | ||
}); | ||
return; | ||
} | ||
if (jestFnCall === 'matcher-not-called') { | ||
context.report({ | ||
messageId: (0, _utils2.isSupportedAccessor)(reportingNode) && _utils2.ModifierName.hasOwnProperty((0, _utils2.getAccessorValue)(reportingNode)) ? 'matcherNotFound' : 'matcherNotCalled', | ||
node: reportingNode | ||
}); | ||
} | ||
if (jestFnCall === 'modifier-unknown') { | ||
context.report({ | ||
messageId: 'modifierUnknown', | ||
node: reportingNode | ||
}); | ||
return; | ||
} | ||
return; | ||
} else if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'expect') { | ||
return; | ||
} | ||
const { | ||
expect, | ||
modifier, | ||
matcher | ||
} = (0, _utils.parseExpectCall)(node); | ||
parent: expect | ||
} = jestFnCall.head.node; | ||
if ((expect === null || expect === void 0 ? void 0 : expect.type) !== _utils.AST_NODE_TYPES.CallExpression) { | ||
return; | ||
} | ||
if (expect.arguments.length < minArgs) { | ||
const expectLength = (0, _utils.getAccessorValue)(expect.callee).length; | ||
const expectLength = (0, _utils2.getAccessorValue)(jestFnCall.head.node).length; | ||
const loc = { | ||
start: { | ||
column: node.loc.start.column + expectLength, | ||
line: node.loc.start.line | ||
column: expect.loc.start.column + expectLength, | ||
line: expect.loc.start.line | ||
}, | ||
end: { | ||
column: node.loc.start.column + expectLength + 1, | ||
line: node.loc.start.line | ||
column: expect.loc.start.column + expectLength + 1, | ||
line: expect.loc.start.line | ||
} | ||
@@ -159,7 +200,6 @@ }; | ||
}, | ||
node, | ||
node: expect, | ||
loc | ||
}); | ||
} | ||
if (expect.arguments.length > maxArgs) { | ||
@@ -171,3 +211,3 @@ const { | ||
end | ||
} = expect.arguments[node.arguments.length - 1].loc; | ||
} = expect.arguments[expect.arguments.length - 1].loc; | ||
const loc = { | ||
@@ -186,42 +226,14 @@ start, | ||
}, | ||
node, | ||
node: expect, | ||
loc | ||
}); | ||
} // something was called on `expect()` | ||
if (!matcher) { | ||
if (modifier) { | ||
context.report({ | ||
messageId: 'matcherNotFound', | ||
node: modifier.node.property | ||
}); | ||
} | ||
return; | ||
} | ||
if (matcher.node.parent && (0, _utils.isExpectMember)(matcher.node.parent)) { | ||
context.report({ | ||
messageId: 'modifierUnknown', | ||
data: { | ||
modifierName: matcher.name | ||
}, | ||
node: matcher.node.property | ||
}); | ||
const { | ||
matcher | ||
} = jestFnCall; | ||
const parentNode = matcher.parent.parent; | ||
const shouldBeAwaited = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) !== 'not') || asyncMatchers.includes((0, _utils2.getAccessorValue)(matcher)); | ||
if (!(parentNode !== null && parentNode !== void 0 && parentNode.parent) || !shouldBeAwaited) { | ||
return; | ||
} | ||
if (!matcher.arguments) { | ||
context.report({ | ||
messageId: 'matcherNotCalled', | ||
node: matcher.node.property | ||
}); | ||
} | ||
const parentNode = matcher.node.parent; | ||
if (!modifier || !parentNode || !parentNode.parent || modifier.name === _utils.ModifierName.not) { | ||
return; | ||
} | ||
/** | ||
@@ -231,5 +243,3 @@ * If parent node is an array expression, we'll report the warning, | ||
*/ | ||
const isParentArrayExpression = parentNode.parent.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression; | ||
const isParentArrayExpression = parentNode.parent.type === _utils.AST_NODE_TYPES.ArrayExpression; | ||
const orReturned = alwaysAwait ? '' : ' or returned'; | ||
@@ -241,8 +251,8 @@ /** | ||
*/ | ||
const targetNode = getParentIfThenified(parentNode); | ||
const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode; | ||
if (finalNode.parent && // If node is not awaited or returned | ||
!isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && // if we didn't warn user already | ||
if (finalNode.parent && | ||
// If node is not awaited or returned | ||
!isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && | ||
// if we didn't warn user already | ||
!promiseArrayExceptionExists(finalNode.loc)) { | ||
@@ -257,3 +267,2 @@ context.report({ | ||
}); | ||
if (isParentArrayExpression) { | ||
@@ -263,19 +272,6 @@ pushPromiseArrayException(finalNode.loc); | ||
} | ||
}, | ||
// nothing called on "expect()" | ||
'CallExpression:exit'(node) { | ||
if ((0, _utils.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) { | ||
context.report({ | ||
messageId: 'matcherNotFound', | ||
node | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
@@ -7,41 +7,44 @@ "use strict"; | ||
exports.default = void 0; | ||
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); | ||
var _utils = require("./utils"); | ||
const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word; | ||
var _utils = require("@typescript-eslint/utils"); | ||
var _utils2 = require("./utils"); | ||
const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substring(1) : word; | ||
const doesBinaryExpressionContainStringNode = binaryExp => { | ||
if ((0, _utils.isStringNode)(binaryExp.right)) { | ||
if ((0, _utils2.isStringNode)(binaryExp.right)) { | ||
return true; | ||
} | ||
if (binaryExp.left.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression) { | ||
if (binaryExp.left.type === _utils.AST_NODE_TYPES.BinaryExpression) { | ||
return doesBinaryExpressionContainStringNode(binaryExp.left); | ||
} | ||
return (0, _utils.isStringNode)(binaryExp.left); | ||
return (0, _utils2.isStringNode)(binaryExp.left); | ||
}; | ||
const quoteStringValue = node => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw; | ||
const quoteStringValue = node => node.type === _utils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw; | ||
const compileMatcherPattern = matcherMaybeWithMessage => { | ||
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage]; | ||
return [new RegExp(matcher, 'u'), message]; | ||
}; | ||
const compileMatcherPatterns = matchers => { | ||
if (typeof matchers === 'string') { | ||
const matcher = new RegExp(matchers, 'u'); | ||
if (typeof matchers === 'string' || Array.isArray(matchers)) { | ||
const compiledMatcher = compileMatcherPattern(matchers); | ||
return { | ||
describe: matcher, | ||
test: matcher, | ||
it: matcher | ||
describe: compiledMatcher, | ||
test: compiledMatcher, | ||
it: compiledMatcher | ||
}; | ||
} | ||
return { | ||
describe: matchers.describe ? new RegExp(matchers.describe, 'u') : null, | ||
test: matchers.test ? new RegExp(matchers.test, 'u') : null, | ||
it: matchers.it ? new RegExp(matchers.it, 'u') : null | ||
describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null, | ||
test: matchers.test ? compileMatcherPattern(matchers.test) : null, | ||
it: matchers.it ? compileMatcherPattern(matchers.it) : null | ||
}; | ||
}; | ||
var _default = (0, _utils.createRule)({ | ||
const MatcherAndMessageSchema = { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
}, | ||
minItems: 1, | ||
maxItems: 2, | ||
additionalItems: false | ||
}; | ||
var _default = (0, _utils2.createRule)({ | ||
name: __filename, | ||
@@ -52,3 +55,3 @@ meta: { | ||
description: 'Enforce valid titles', | ||
recommended: false | ||
recommended: 'error' | ||
}, | ||
@@ -62,3 +65,5 @@ messages: { | ||
mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}', | ||
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}' | ||
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}', | ||
mustNotMatchCustom: '{{ message }}', | ||
mustMatchCustom: '{{ message }}' | ||
}, | ||
@@ -78,39 +83,18 @@ type: 'suggestion', | ||
} | ||
}, | ||
mustNotMatch: { | ||
} | ||
}, | ||
patternProperties: { | ||
[/^must(?:Not)?Match$/u.source]: { | ||
oneOf: [{ | ||
type: 'string' | ||
}, { | ||
}, MatcherAndMessageSchema, { | ||
type: 'object', | ||
properties: { | ||
describe: { | ||
type: 'string' | ||
}, | ||
test: { | ||
type: 'string' | ||
}, | ||
it: { | ||
type: 'string' | ||
} | ||
propertyNames: { | ||
enum: ['describe', 'test', 'it'] | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
mustMatch: { | ||
oneOf: [{ | ||
type: 'string' | ||
}, { | ||
type: 'object', | ||
properties: { | ||
describe: { | ||
additionalProperties: { | ||
oneOf: [{ | ||
type: 'string' | ||
}, | ||
test: { | ||
type: 'string' | ||
}, | ||
it: { | ||
type: 'string' | ||
} | ||
}, | ||
additionalProperties: false | ||
}, MatcherAndMessageSchema] | ||
} | ||
}] | ||
@@ -127,3 +111,2 @@ } | ||
}], | ||
create(context, [{ | ||
@@ -136,22 +119,19 @@ ignoreTypeOfDescribeName, | ||
const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu'); | ||
const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch !== null && mustNotMatch !== void 0 ? mustNotMatch : {}); | ||
const mustMatchPatterns = compileMatcherPatterns(mustMatch !== null && mustMatch !== void 0 ? mustMatch : {}); | ||
const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch ?? {}); | ||
const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {}); | ||
return { | ||
CallExpression(node) { | ||
if (!(0, _utils.isDescribe)(node) && !(0, _utils.isTestCase)(node)) { | ||
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context); | ||
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'describe' && (jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'test') { | ||
return; | ||
} | ||
const [argument] = (0, _utils.getJestFunctionArguments)(node); | ||
const [argument] = node.arguments; | ||
if (!argument) { | ||
return; | ||
} | ||
if (!(0, _utils.isStringNode)(argument)) { | ||
if (argument.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) { | ||
if (!(0, _utils2.isStringNode)(argument)) { | ||
if (argument.type === _utils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) { | ||
return; | ||
} | ||
if (argument.type !== _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && (0, _utils.isDescribe)(node))) { | ||
if (argument.type !== _utils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && jestFnCall.type === 'describe')) { | ||
context.report({ | ||
@@ -162,8 +142,5 @@ messageId: 'titleMustBeString', | ||
} | ||
return; | ||
} | ||
const title = (0, _utils.getStringValue)(argument); | ||
const title = (0, _utils2.getStringValue)(argument); | ||
if (!title) { | ||
@@ -173,3 +150,3 @@ context.report({ | ||
data: { | ||
jestFunctionName: (0, _utils.isDescribe)(node) ? _utils.DescribeAlias.describe : _utils.TestCaseName.test | ||
jestFunctionName: jestFnCall.type === 'describe' ? _utils2.DescribeAlias.describe : _utils2.TestCaseName.test | ||
}, | ||
@@ -180,6 +157,4 @@ node | ||
} | ||
if (disallowedWords.length > 0) { | ||
const disallowedMatch = disallowedWordsRegexp.exec(title); | ||
if (disallowedMatch) { | ||
@@ -196,3 +171,2 @@ context.report({ | ||
} | ||
if (title.trim().length !== title.length) { | ||
@@ -205,7 +179,5 @@ context.report({ | ||
} | ||
const nodeName = trimFXprefix((0, _utils.getNodeName)(node.callee)); | ||
const unprefixedName = trimFXprefix(jestFnCall.name); | ||
const [firstWord] = title.split(' '); | ||
if (firstWord.toLowerCase() === nodeName) { | ||
if (firstWord.toLowerCase() === unprefixedName) { | ||
context.report({ | ||
@@ -217,14 +189,13 @@ messageId: 'duplicatePrefix', | ||
} | ||
const [jestFunctionName] = nodeName.split('.'); | ||
const mustNotMatchPattern = mustNotMatchPatterns[jestFunctionName]; | ||
const jestFunctionName = unprefixedName; | ||
const [mustNotMatchPattern, mustNotMatchMessage] = mustNotMatchPatterns[jestFunctionName] ?? []; | ||
if (mustNotMatchPattern) { | ||
if (mustNotMatchPattern.test(title)) { | ||
context.report({ | ||
messageId: 'mustNotMatch', | ||
messageId: mustNotMatchMessage ? 'mustNotMatchCustom' : 'mustNotMatch', | ||
node: argument, | ||
data: { | ||
jestFunctionName, | ||
pattern: mustNotMatchPattern | ||
pattern: mustNotMatchPattern, | ||
message: mustNotMatchMessage | ||
} | ||
@@ -235,13 +206,12 @@ }); | ||
} | ||
const mustMatchPattern = mustMatchPatterns[jestFunctionName]; | ||
const [mustMatchPattern, mustMatchMessage] = mustMatchPatterns[jestFunctionName] ?? []; | ||
if (mustMatchPattern) { | ||
if (!mustMatchPattern.test(title)) { | ||
context.report({ | ||
messageId: 'mustMatch', | ||
messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch', | ||
node: argument, | ||
data: { | ||
jestFunctionName, | ||
pattern: mustMatchPattern | ||
pattern: mustMatchPattern, | ||
message: mustMatchMessage | ||
} | ||
@@ -253,8 +223,5 @@ }); | ||
} | ||
}; | ||
} | ||
}); | ||
exports.default = _default; |
147
package.json
{ | ||
"name": "eslint-plugin-jest", | ||
"version": "23.20.0", | ||
"description": "Eslint rules for Jest", | ||
"version": "27.2.3", | ||
"description": "ESLint rules for Jest", | ||
"keywords": [ | ||
@@ -17,3 +17,3 @@ "eslint", | ||
}, | ||
"main": "lib/", | ||
"main": "lib/index.js", | ||
"files": [ | ||
@@ -24,17 +24,14 @@ "docs/", | ||
"scripts": { | ||
"build": "babel --extensions .js,.ts src --out-dir lib --copy-files", | ||
"postbuild": "rimraf lib/__tests__ lib/**/__tests__", | ||
"build": "babel --extensions .js,.ts src --out-dir lib --copy-files && rimraf --glob lib/__tests__ 'lib/**/__tests__'", | ||
"_postinstall": "is-ci || husky install", | ||
"lint": "eslint . --ignore-pattern '!.eslintrc.js' --ext js,ts", | ||
"prepack": "yarn build", | ||
"prettylint": "prettylint docs/**/*.md README.md package.json", | ||
"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", | ||
"prettier:write": "prettier --write 'docs/**/*.md' README.md '.github/**' package.json tsconfig.json src/globals.json .yarnrc.yml", | ||
"postpublish": "pinst --enable", | ||
"test": "jest", | ||
"tools:regenerate-docs": "ts-node -T tools/regenerate-docs", | ||
"tools:regenerate-docs": "yarn prepack && eslint-doc-generator", | ||
"typecheck": "tsc -p ." | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged", | ||
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS" | ||
} | ||
}, | ||
"commitlint": { | ||
@@ -46,10 +43,4 @@ "extends": [ | ||
"lint-staged": { | ||
"*.{js,ts}": [ | ||
"eslint --fix", | ||
"git add" | ||
], | ||
"*.{md,json}": [ | ||
"prettier --write", | ||
"git add" | ||
] | ||
"*.{js,ts}": "eslint --fix", | ||
"*.{md,json,yml}": "prettier --write" | ||
}, | ||
@@ -63,2 +54,19 @@ "prettier": { | ||
}, | ||
"release": { | ||
"branches": [ | ||
"main", | ||
{ | ||
"name": "next", | ||
"prerelease": true | ||
} | ||
], | ||
"plugins": [ | ||
"@semantic-release/commit-analyzer", | ||
"@semantic-release/release-notes-generator", | ||
"@semantic-release/changelog", | ||
"@semantic-release/npm", | ||
"@semantic-release/git", | ||
"@semantic-release/github" | ||
] | ||
}, | ||
"jest": { | ||
@@ -76,5 +84,6 @@ "coverageThreshold": { | ||
"displayName": "test", | ||
"testEnvironment": "node", | ||
"testPathIgnorePatterns": [ | ||
"<rootDir>/lib/.*" | ||
"<rootDir>/lib/.*", | ||
"<rootDir>/src/rules/__tests__/fixtures/*", | ||
"<rootDir>/src/rules/__tests__/test-utils.ts" | ||
] | ||
@@ -95,3 +104,3 @@ }, | ||
"dependencies": { | ||
"@typescript-eslint/experimental-utils": "^2.5.0" | ||
"@typescript-eslint/utils": "^5.10.0" | ||
}, | ||
@@ -103,52 +112,62 @@ "devDependencies": { | ||
"@babel/preset-typescript": "^7.3.3", | ||
"@commitlint/cli": "^8.2.0", | ||
"@commitlint/config-conventional": "^8.2.0", | ||
"@schemastore/package": "^0.0.6", | ||
"@semantic-release/changelog": "^3.0.5", | ||
"@semantic-release/git": "^7.0.17", | ||
"@commitlint/cli": "^17.0.3", | ||
"@commitlint/config-conventional": "^17.0.3", | ||
"@schemastore/package": "^0.0.8", | ||
"@semantic-release/changelog": "^6.0.0", | ||
"@semantic-release/git": "^10.0.0", | ||
"@tsconfig/node14": "^14.1.0", | ||
"@types/dedent": "^0.7.0", | ||
"@types/jest": "^25.1.0", | ||
"@types/node": "^12.6.6", | ||
"@types/prettier": "^1.19.0", | ||
"@typescript-eslint/eslint-plugin": "^2.5.0", | ||
"@typescript-eslint/parser": "^2.5.0", | ||
"babel-jest": "^25.2.0", | ||
"@types/eslint": "^8.4.6", | ||
"@types/jest": "^29.0.0", | ||
"@types/node": "^14.18.26", | ||
"@types/prettier": "^2.0.0", | ||
"@typescript-eslint/eslint-plugin": "^5.0.0", | ||
"@typescript-eslint/parser": "^5.0.0", | ||
"babel-jest": "^29.0.0", | ||
"babel-plugin-replace-ts-export-assignment": "^0.0.2", | ||
"dedent": "^0.7.0", | ||
"eslint": "^5.1.0 || ^6.0.0", | ||
"eslint-config-prettier": "^6.5.0", | ||
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-doc-generator": "^1.0.0", | ||
"eslint-plugin-eslint-comments": "^3.1.2", | ||
"eslint-plugin-eslint-config": "^1.0.2", | ||
"eslint-plugin-eslint-plugin": "^2.0.0", | ||
"eslint-plugin-import": "^2.20.2", | ||
"eslint-plugin-eslint-plugin": "^5.0.6", | ||
"eslint-plugin-import": "^2.25.1", | ||
"eslint-plugin-node": "^11.0.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"husky": "^3.0.9", | ||
"jest": "^25.2.0", | ||
"jest-runner-eslint": "^0.10.0", | ||
"lint-staged": "^9.4.2", | ||
"prettier": "^1.19.1", | ||
"prettylint": "^1.0.0", | ||
"resolve-from": "^5.0.0", | ||
"rimraf": "^3.0.0", | ||
"semantic-release": "^15.13.28", | ||
"ts-node": "^8.10.1", | ||
"typescript": "^3.5.3" | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"eslint-remote-tester": "^3.0.0", | ||
"eslint-remote-tester-repositories": "~1.0.0", | ||
"husky": "^8.0.1", | ||
"is-ci": "^3.0.0", | ||
"jest": "^29.0.0", | ||
"jest-runner-eslint": "^2.0.0", | ||
"lint-staged": "^13.0.3", | ||
"markdown-link-check": "^3.10.2", | ||
"pinst": "^3.0.0", | ||
"prettier": "^2.0.5", | ||
"rimraf": "^5.0.0", | ||
"semantic-release": "^21.0.0", | ||
"semver": "^7.3.5", | ||
"ts-node": "^10.2.1", | ||
"typescript": "^5.0.4" | ||
}, | ||
"peerDependencies": { | ||
"eslint": ">=5" | ||
"@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", | ||
"eslint": "^7.0.0 || ^8.0.0", | ||
"jest": "*" | ||
}, | ||
"peerDependenciesMeta": { | ||
"@typescript-eslint/eslint-plugin": { | ||
"optional": true | ||
}, | ||
"jest": { | ||
"optional": true | ||
} | ||
}, | ||
"packageManager": "yarn@3.6.1", | ||
"engines": { | ||
"node": ">=8" | ||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" | ||
}, | ||
"release": { | ||
"plugins": [ | ||
"@semantic-release/commit-analyzer", | ||
"@semantic-release/release-notes-generator", | ||
"@semantic-release/changelog", | ||
"@semantic-release/npm", | ||
"@semantic-release/git", | ||
"@semantic-release/github" | ||
] | ||
"publishConfig": { | ||
"provenance": true | ||
} | ||
} |
247
README.md
<div align="center"> | ||
<a href="https://eslint.org/"> | ||
<img width="150" height="150" src="https://eslint.org/assets/img/logo.svg"> | ||
<img height="150" src="https://eslint.org/assets/images/logo/eslint-logo-color.svg"> | ||
</a> | ||
<a href="https://facebook.github.io/jest/"> | ||
<a href="https://jestjs.io/"> | ||
<img width="150" height="150" vspace="" hspace="25" src="https://jestjs.io/img/jest.png"> | ||
@@ -12,10 +12,9 @@ </a> | ||
[![Actions Status](https://github.com/jest-community/eslint-plugin-jest/workflows/Unit%20tests/badge.svg?branch=master)](https://github.com/jest-community/eslint-plugin-jest/actions) | ||
[![Renovate badge](https://badges.renovateapi.com/github/jest-community/eslint-plugin-jest)](https://renovatebot.com/) | ||
[![Actions Status](https://github.com/jest-community/eslint-plugin-jest/actions/workflows/nodejs.yml/badge.svg?branch=main)](https://github.com/jest-community/eslint-plugin-jest/actions) | ||
## Installation | ||
```bash | ||
yarn add --dev eslint eslint-plugin-jest | ||
``` | ||
$ yarn add --dev eslint eslint-plugin-jest | ||
``` | ||
@@ -61,9 +60,10 @@ **Note:** If you installed ESLint globally then you must also install | ||
The behaviour of some rules (specifically `no-deprecated-functions`) change | ||
depending on the version of `jest` being used. | ||
This is included in all configs shared by this plugin, so can be omitted if | ||
extending them. | ||
This setting is detected automatically based off the version of the `jest` | ||
package installed in `node_modules`, but it can also be provided explicitly if | ||
desired: | ||
#### Aliased Jest globals | ||
You can tell this plugin about any global Jests you have aliased using the | ||
`globalAliases` setting: | ||
```json | ||
@@ -73,3 +73,7 @@ { | ||
"jest": { | ||
"version": 26 | ||
"globalAliases": { | ||
"describe": ["context"], | ||
"fdescribe": ["fcontext"], | ||
"xdescribe": ["xcontext"] | ||
} | ||
} | ||
@@ -80,2 +84,67 @@ } | ||
### Running rules only on test-related files | ||
The rules provided by this plugin assume that the files they are checking are | ||
test-related. This means it's generally not suitable to include them in your | ||
top-level configuration as that applies to all files being linted which can | ||
include source files. | ||
You can use | ||
[overrides](https://eslint.org/docs/user-guide/configuring/configuration-files#how-do-overrides-work) | ||
to have ESLint apply additional rules to specific files: | ||
```json | ||
{ | ||
"extends": ["eslint:recommended"], | ||
"overrides": [ | ||
{ | ||
"files": ["test/**"], | ||
"plugins": ["jest"], | ||
"extends": ["plugin:jest/recommended"], | ||
"rules": { "jest/prefer-expect-assertions": "off" } | ||
} | ||
], | ||
"rules": { | ||
"indent": ["error", 2] | ||
} | ||
} | ||
``` | ||
### Jest `version` setting | ||
The behaviour of some rules (specifically [`no-deprecated-functions`][]) change | ||
depending on the version of Jest being used. | ||
By default, this plugin will attempt to determine to locate Jest using | ||
`require.resolve`, meaning it will start looking in the closest `node_modules` | ||
folder to the file being linted and work its way up. | ||
Since we cache the automatically determined version, if you're linting | ||
sub-folders that have different versions of Jest, you may find that the wrong | ||
version of Jest is considered when linting. You can work around this by | ||
providing the Jest version explicitly in nested ESLint configs: | ||
```json | ||
{ | ||
"settings": { | ||
"jest": { | ||
"version": 27 | ||
} | ||
} | ||
} | ||
``` | ||
To avoid hard-coding a number, you can also fetch it from the installed version | ||
of Jest if you use a JavaScript config file such as `.eslintrc.js`: | ||
```js | ||
module.exports = { | ||
settings: { | ||
jest: { | ||
version: require('jest/package.json').version, | ||
}, | ||
}, | ||
}; | ||
``` | ||
## Shareable configurations | ||
@@ -113,3 +182,3 @@ | ||
See | ||
[ESLint documentation](http://eslint.org/docs/user-guide/configuring#extending-configuration-files) | ||
[ESLint documentation](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) | ||
for more information about extending configuration files. | ||
@@ -134,49 +203,91 @@ | ||
<!-- begin rules list --> | ||
<!-- begin auto-generated rules list --> | ||
| Rule | Description | Configurations | Fixable | | ||
| ---------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------- | ------------ | | ||
| [consistent-test-it](docs/rules/consistent-test-it.md) | Have control over `test` and `it` usages | | ![fixable][] | | ||
| [expect-expect](docs/rules/expect-expect.md) | Enforce assertion to be made in a test body | ![recommended][] | | | ||
| [lowercase-name](docs/rules/lowercase-name.md) | Enforce lowercase test names | | ![fixable][] | | ||
| [no-alias-methods](docs/rules/no-alias-methods.md) | Disallow alias methods | ![style][] | ![fixable][] | | ||
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | ![recommended][] | | | ||
| [no-conditional-expect](docs/rules/no-conditional-expect.md) | Prevent calling `expect` conditionally | | | | ||
| [no-deprecated-functions](docs/rules/no-deprecated-functions.md) | Disallow use of deprecated functions | | ![fixable][] | | ||
| [no-disabled-tests](docs/rules/no-disabled-tests.md) | Disallow disabled tests | ![recommended][] | | | ||
| [no-duplicate-hooks](docs/rules/no-duplicate-hooks.md) | Disallow duplicate setup and teardown hooks | | | | ||
| [no-export](docs/rules/no-export.md) | Disallow using `exports` in files containing tests | ![recommended][] | | | ||
| [no-focused-tests](docs/rules/no-focused-tests.md) | Disallow focused tests | ![recommended][] | ![fixable][] | | ||
| [no-hooks](docs/rules/no-hooks.md) | Disallow setup and teardown hooks | | | | ||
| [no-identical-title](docs/rules/no-identical-title.md) | Disallow identical titles | ![recommended][] | | | ||
| [no-if](docs/rules/no-if.md) | Disallow conditional logic | | | | ||
| [no-interpolation-in-snapshots](docs/rules/no-interpolation-in-snapshots.md) | Disallow string interpolation inside snapshots | | | | ||
| [no-jasmine-globals](docs/rules/no-jasmine-globals.md) | Disallow Jasmine globals | ![recommended][] | ![fixable][] | | ||
| [no-jest-import](docs/rules/no-jest-import.md) | Disallow importing Jest | ![recommended][] | | | ||
| [no-large-snapshots](docs/rules/no-large-snapshots.md) | disallow large snapshots | | | | ||
| [no-mocks-import](docs/rules/no-mocks-import.md) | Disallow manually importing from `__mocks__` | ![recommended][] | | | ||
| [no-restricted-matchers](docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers | | | | ||
| [no-standalone-expect](docs/rules/no-standalone-expect.md) | Disallow using `expect` outside of `it` or `test` blocks | ![recommended][] | | | ||
| [no-test-callback](docs/rules/no-test-callback.md) | Avoid using a callback in asynchronous tests | ![recommended][] | ![suggest][] | | ||
| [no-test-prefixes](docs/rules/no-test-prefixes.md) | Use `.only` and `.skip` over `f` and `x` | ![recommended][] | ![fixable][] | | ||
| [no-test-return-statement](docs/rules/no-test-return-statement.md) | Disallow explicitly returning from tests | | | | ||
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` | | | | ||
| [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | ![suggest][] | | ||
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | ||
| [prefer-spy-on](docs/rules/prefer-spy-on.md) | Suggest using `jest.spyOn()` | | ![fixable][] | | ||
| [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | ![suggest][] | | ||
| [prefer-to-be-null](docs/rules/prefer-to-be-null.md) | Suggest using `toBeNull()` | ![style][] | ![fixable][] | | ||
| [prefer-to-be-undefined](docs/rules/prefer-to-be-undefined.md) | Suggest using `toBeUndefined()` | ![style][] | ![fixable][] | | ||
| [prefer-to-contain](docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | ![style][] | ![fixable][] | | ||
| [prefer-to-have-length](docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` | ![style][] | ![fixable][] | | ||
| [prefer-todo](docs/rules/prefer-todo.md) | Suggest using `test.todo` | | ![fixable][] | | ||
| [require-to-throw-message](docs/rules/require-to-throw-message.md) | Require a message for `toThrow()` | | | | ||
| [require-top-level-describe](docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `describe` block | | | | ||
| [valid-describe](docs/rules/valid-describe.md) | Enforce valid `describe()` callback | ![recommended][] | | | ||
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage | ![recommended][] | | | ||
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Enforce having return statement when testing with promises | ![recommended][] | | | ||
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles | | ![fixable][] | | ||
πΌ | ||
[Configurations](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations) | ||
enabled in.\ | ||
β οΈ [Configurations](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations) | ||
set to warn in.\ | ||
β Set in the `recommended` | ||
[configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\ | ||
π¨ Set in the `style` [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\ | ||
π§ Automatically fixable by the | ||
[`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ | ||
π‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\ | ||
β Deprecated. | ||
<!-- end rules list --> | ||
| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | β οΈ | π§ | π‘ | β | | ||
| :--------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :-- | :-- | :-- | :-- | :-- | | ||
| [consistent-test-it](docs/rules/consistent-test-it.md) | Enforce `test` and `it` usage conventions | | | π§ | | | | ||
| [expect-expect](docs/rules/expect-expect.md) | Enforce assertion to be made in a test body | | β | | | | | ||
| [max-expects](docs/rules/max-expects.md) | Enforces a maximum number assertion calls in a test body | | | | | | | ||
| [max-nested-describe](docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | | | | | | ||
| [no-alias-methods](docs/rules/no-alias-methods.md) | Disallow alias methods | β | π¨ | π§ | | | | ||
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | | β | | | | | ||
| [no-conditional-expect](docs/rules/no-conditional-expect.md) | Disallow calling `expect` conditionally | β | | | | | | ||
| [no-conditional-in-test](docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests | | | | | | | ||
| [no-deprecated-functions](docs/rules/no-deprecated-functions.md) | Disallow use of deprecated functions | β | | π§ | | | | ||
| [no-disabled-tests](docs/rules/no-disabled-tests.md) | Disallow disabled tests | | β | | | | | ||
| [no-done-callback](docs/rules/no-done-callback.md) | Disallow using a callback in asynchronous tests and hooks | β | | | π‘ | | | ||
| [no-duplicate-hooks](docs/rules/no-duplicate-hooks.md) | Disallow duplicate setup and teardown hooks | | | | | | | ||
| [no-export](docs/rules/no-export.md) | Disallow using `exports` in files containing tests | β | | | | | | ||
| [no-focused-tests](docs/rules/no-focused-tests.md) | Disallow focused tests | β | | | π‘ | | | ||
| [no-hooks](docs/rules/no-hooks.md) | Disallow setup and teardown hooks | | | | | | | ||
| [no-identical-title](docs/rules/no-identical-title.md) | Disallow identical titles | β | | | | | | ||
| [no-if](docs/rules/no-if.md) | Disallow conditional logic | | | | | β | | ||
| [no-interpolation-in-snapshots](docs/rules/no-interpolation-in-snapshots.md) | Disallow string interpolation inside snapshots | β | | | | | | ||
| [no-jasmine-globals](docs/rules/no-jasmine-globals.md) | Disallow Jasmine globals | β | | π§ | | | | ||
| [no-large-snapshots](docs/rules/no-large-snapshots.md) | Disallow large snapshots | | | | | | | ||
| [no-mocks-import](docs/rules/no-mocks-import.md) | Disallow manually importing from `__mocks__` | β | | | | | | ||
| [no-restricted-jest-methods](docs/rules/no-restricted-jest-methods.md) | Disallow specific `jest.` methods | | | | | | | ||
| [no-restricted-matchers](docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers | | | | | | | ||
| [no-standalone-expect](docs/rules/no-standalone-expect.md) | Disallow using `expect` outside of `it` or `test` blocks | β | | | | | | ||
| [no-test-prefixes](docs/rules/no-test-prefixes.md) | Require using `.only` and `.skip` over `f` and `x` | β | | π§ | | | | ||
| [no-test-return-statement](docs/rules/no-test-return-statement.md) | Disallow explicitly returning from tests | | | | | | | ||
| [no-untyped-mock-factory](docs/rules/no-untyped-mock-factory.md) | Disallow using `jest.mock()` factories without an explicit type parameter | | | π§ | | | | ||
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` | | | | | | | ||
| [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | | π§ | | | | ||
| [prefer-each](docs/rules/prefer-each.md) | Prefer using `.each` rather than manual loops | | | | | | | ||
| [prefer-equality-matcher](docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | | | π‘ | | | ||
| [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | | | π‘ | | | ||
| [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | | π§ | | | | ||
| [prefer-hooks-in-order](docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | | | | | | | ||
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | | | | ||
| [prefer-lowercase-title](docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | | π§ | | | | ||
| [prefer-mock-promise-shorthand](docs/rules/prefer-mock-promise-shorthand.md) | Prefer mock resolved/rejected shorthands for promises | | | π§ | | | | ||
| [prefer-snapshot-hint](docs/rules/prefer-snapshot-hint.md) | Prefer including a hint with external snapshots | | | | | | | ||
| [prefer-spy-on](docs/rules/prefer-spy-on.md) | Suggest using `jest.spyOn()` | | | π§ | | | | ||
| [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | | | π‘ | | | ||
| [prefer-to-be](docs/rules/prefer-to-be.md) | Suggest using `toBe()` for primitive literals | π¨ | | π§ | | | | ||
| [prefer-to-contain](docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | π¨ | | π§ | | | | ||
| [prefer-to-have-length](docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` | π¨ | | π§ | | | | ||
| [prefer-todo](docs/rules/prefer-todo.md) | Suggest using `test.todo` | | | π§ | | | | ||
| [require-hook](docs/rules/require-hook.md) | Require setup and teardown code to be within a hook | | | | | | | ||
| [require-to-throw-message](docs/rules/require-to-throw-message.md) | Require a message for `toThrow()` | | | | | | | ||
| [require-top-level-describe](docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `describe` block | | | | | | | ||
| [valid-describe-callback](docs/rules/valid-describe-callback.md) | Enforce valid `describe()` callback | β | | | | | | ||
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage | β | | | | | | ||
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Require promises that have expectations in their chain to be valid | β | | | | | | ||
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles | β | | π§ | | | | ||
### Requires Type Checking | ||
| NameΒ Β Β Β Β Β Β Β Β Β | Description | πΌ | β οΈ | π§ | π‘ | β | | ||
| :--------------------------------------------- | :----------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | | ||
| [unbound-method](docs/rules/unbound-method.md) | Enforce unbound methods are called with their expected scope | | | | | | | ||
<!-- end auto-generated rules list --> | ||
In order to use the rules powered by TypeScript type-checking, you must be using | ||
`@typescript-eslint/parser` & adjust your eslint config as outlined | ||
[here](https://typescript-eslint.io/linting/typed-linting). | ||
Note that unlike the type-checking rules in `@typescript-eslint/eslint-plugin`, | ||
the rules here will fallback to doing nothing if type information is not | ||
available, meaning it's safe to include them in shared configs that could be | ||
used on JavaScript and TypeScript projects. | ||
Also note that `unbound-method` depends on `@typescript-eslint/eslint-plugin`, | ||
as it extends the original `unbound-method` rule from that plugin. | ||
## Credit | ||
@@ -189,2 +300,10 @@ | ||
### eslint-plugin-jest-extended | ||
This is a sister plugin to `eslint-plugin-jest` that provides support for the | ||
matchers provided by | ||
[`jest-extended`](https://github.com/jest-community/jest-extended). | ||
<https://github.com/jest-community/eslint-plugin-jest-extended> | ||
### eslint-plugin-jest-formatting | ||
@@ -195,7 +314,11 @@ | ||
https://github.com/dangreenisrael/eslint-plugin-jest-formatting | ||
<https://github.com/dangreenisrael/eslint-plugin-jest-formatting> | ||
[recommended]: https://img.shields.io/badge/-recommended-lightgrey.svg | ||
[suggest]: https://img.shields.io/badge/-suggest-yellow.svg | ||
[fixable]: https://img.shields.io/badge/-fixable-green.svg | ||
[style]: https://img.shields.io/badge/-style-blue.svg | ||
### eslint-plugin-istanbul | ||
A set of rules to enforce good practices for Istanbul, one of the code coverage | ||
tools used by Jest. | ||
<https://github.com/istanbuljs/eslint-plugin-istanbul> | ||
[`no-deprecated-functions`]: docs/rules/no-deprecated-functions.md |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
316743
116
5355
317
4
43
1
+ Added@ampproject/remapping@2.3.0(transitive)
+ Added@babel/code-frame@7.26.0(transitive)
+ Added@babel/compat-data@7.26.0(transitive)
+ Added@babel/core@7.26.0(transitive)
+ Added@babel/generator@7.26.0(transitive)
+ Added@babel/helper-compilation-targets@7.25.9(transitive)
+ Added@babel/helper-module-imports@7.25.9(transitive)
+ Added@babel/helper-module-transforms@7.26.0(transitive)
+ Added@babel/helper-plugin-utils@7.25.9(transitive)
+ Added@babel/helper-string-parser@7.25.9(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@babel/helper-validator-option@7.25.9(transitive)
+ Added@babel/helpers@7.26.0(transitive)
+ Added@babel/parser@7.26.1(transitive)
+ Added@babel/plugin-syntax-async-generators@7.8.4(transitive)
+ Added@babel/plugin-syntax-bigint@7.8.3(transitive)
+ Added@babel/plugin-syntax-class-properties@7.12.13(transitive)
+ Added@babel/plugin-syntax-class-static-block@7.14.5(transitive)
+ Added@babel/plugin-syntax-import-attributes@7.26.0(transitive)
+ Added@babel/plugin-syntax-import-meta@7.10.4(transitive)
+ Added@babel/plugin-syntax-json-strings@7.8.3(transitive)
+ Added@babel/plugin-syntax-jsx@7.25.9(transitive)
+ Added@babel/plugin-syntax-logical-assignment-operators@7.10.4(transitive)
+ Added@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(transitive)
+ Added@babel/plugin-syntax-numeric-separator@7.10.4(transitive)
+ Added@babel/plugin-syntax-object-rest-spread@7.8.3(transitive)
+ Added@babel/plugin-syntax-optional-catch-binding@7.8.3(transitive)
+ Added@babel/plugin-syntax-optional-chaining@7.8.3(transitive)
+ Added@babel/plugin-syntax-private-property-in-object@7.14.5(transitive)
+ Added@babel/plugin-syntax-top-level-await@7.14.5(transitive)
+ Added@babel/plugin-syntax-typescript@7.25.9(transitive)
+ Added@babel/template@7.25.9(transitive)
+ Added@babel/traverse@7.25.9(transitive)
+ Added@babel/types@7.26.0(transitive)
+ Added@bcoe/v8-coverage@0.2.3(transitive)
+ Added@eslint/eslintrc@2.1.4(transitive)
+ Added@eslint/js@8.57.1(transitive)
+ Added@humanwhocodes/config-array@0.13.0(transitive)
+ Added@humanwhocodes/object-schema@2.0.3(transitive)
+ Added@istanbuljs/load-nyc-config@1.1.0(transitive)
+ Added@istanbuljs/schema@0.1.3(transitive)
+ Added@jest/console@29.7.0(transitive)
+ Added@jest/core@29.7.0(transitive)
+ Added@jest/environment@29.7.0(transitive)
+ Added@jest/expect@29.7.0(transitive)
+ Added@jest/expect-utils@29.7.0(transitive)
+ Added@jest/fake-timers@29.7.0(transitive)
+ Added@jest/globals@29.7.0(transitive)
+ Added@jest/reporters@29.7.0(transitive)
+ Added@jest/schemas@29.6.3(transitive)
+ Added@jest/source-map@29.6.3(transitive)
+ Added@jest/test-result@29.7.0(transitive)
+ Added@jest/test-sequencer@29.7.0(transitive)
+ Added@jest/transform@29.7.0(transitive)
+ Added@jest/types@29.6.3(transitive)
+ Added@jridgewell/gen-mapping@0.3.5(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/set-array@1.2.1(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.25(transitive)
+ Added@nodelib/fs.scandir@2.1.5(transitive)
+ Added@nodelib/fs.stat@2.0.5(transitive)
+ Added@nodelib/fs.walk@1.2.8(transitive)
+ Added@sinclair/typebox@0.27.8(transitive)
+ Added@sinonjs/commons@3.0.1(transitive)
+ Added@sinonjs/fake-timers@10.3.0(transitive)
+ Added@types/babel__core@7.20.5(transitive)
+ Added@types/babel__generator@7.6.8(transitive)
+ Added@types/babel__template@7.4.4(transitive)
+ Added@types/babel__traverse@7.20.6(transitive)
+ Added@types/graceful-fs@4.1.9(transitive)
+ Added@types/istanbul-lib-coverage@2.0.6(transitive)
+ Added@types/istanbul-lib-report@3.0.3(transitive)
+ Added@types/istanbul-reports@3.0.4(transitive)
+ Added@types/node@22.8.4(transitive)
+ Added@types/semver@7.5.8(transitive)
+ Added@types/stack-utils@2.0.3(transitive)
+ Added@types/yargs@17.0.33(transitive)
+ Added@types/yargs-parser@21.0.3(transitive)
+ Added@typescript-eslint/eslint-plugin@6.21.0(transitive)
+ Added@typescript-eslint/parser@6.21.0(transitive)
+ Added@typescript-eslint/scope-manager@5.62.06.21.0(transitive)
+ Added@typescript-eslint/type-utils@6.21.0(transitive)
+ Added@typescript-eslint/types@5.62.06.21.0(transitive)
+ Added@typescript-eslint/typescript-estree@5.62.06.21.0(transitive)
+ Added@typescript-eslint/utils@5.62.06.21.0(transitive)
+ Added@typescript-eslint/visitor-keys@5.62.06.21.0(transitive)
+ Added@ungap/structured-clone@1.2.0(transitive)
+ Addedansi-escapes@4.3.2(transitive)
+ Addedansi-regex@5.0.1(transitive)
+ Addedansi-styles@5.2.0(transitive)
+ Addedanymatch@3.1.3(transitive)
+ Addedargparse@1.0.10(transitive)
+ Addedarray-union@2.1.0(transitive)
+ Addedbabel-jest@29.7.0(transitive)
+ Addedbabel-plugin-istanbul@6.1.1(transitive)
+ Addedbabel-plugin-jest-hoist@29.6.3(transitive)
+ Addedbabel-preset-current-node-syntax@1.1.0(transitive)
+ Addedbabel-preset-jest@29.6.3(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedbrowserslist@4.24.2(transitive)
+ Addedbser@2.1.1(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addedcamelcase@5.3.16.3.0(transitive)
+ Addedcaniuse-lite@1.0.30001674(transitive)
+ Addedchar-regex@1.0.2(transitive)
+ Addedci-info@3.9.0(transitive)
+ Addedcjs-module-lexer@1.4.1(transitive)
+ Addedcliui@8.0.1(transitive)
+ Addedco@4.6.0(transitive)
+ Addedcollect-v8-coverage@1.0.2(transitive)
+ Addedconvert-source-map@2.0.0(transitive)
+ Addedcreate-jest@29.7.0(transitive)
+ Addeddedent@1.5.3(transitive)
+ Addeddeepmerge@4.3.1(transitive)
+ Addeddetect-newline@3.1.0(transitive)
+ Addeddiff-sequences@29.6.3(transitive)
+ Addeddir-glob@3.0.1(transitive)
+ Addeddoctrine@3.0.0(transitive)
+ Addedelectron-to-chromium@1.5.49(transitive)
+ Addedemittery@0.13.1(transitive)
+ Addedemoji-regex@8.0.0(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedescape-string-regexp@2.0.0(transitive)
+ Addedeslint@8.57.1(transitive)
+ Addedeslint-scope@7.2.2(transitive)
+ Addedespree@9.6.1(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedexeca@5.1.1(transitive)
+ Addedexit@0.1.2(transitive)
+ Addedexpect@29.7.0(transitive)
+ Addedfast-glob@3.3.2(transitive)
+ Addedfastq@1.17.1(transitive)
+ Addedfb-watchman@2.0.2(transitive)
+ Addedfile-entry-cache@6.0.1(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedfind-up@4.1.0(transitive)
+ Addedflat-cache@3.2.0(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedgensync@1.0.0-beta.2(transitive)
+ Addedget-caller-file@2.0.5(transitive)
+ Addedget-package-type@0.1.0(transitive)
+ Addedget-stream@6.0.1(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedglobals@11.12.013.24.0(transitive)
+ Addedglobby@11.1.0(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedgraphemer@1.4.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhtml-escaper@2.0.2(transitive)
+ Addedhuman-signals@2.1.0(transitive)
+ Addedimport-local@3.2.0(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-core-module@2.15.1(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedis-generator-fn@2.1.0(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedis-path-inside@3.0.3(transitive)
+ Addedis-stream@2.0.1(transitive)
+ Addedistanbul-lib-coverage@3.2.2(transitive)
+ Addedistanbul-lib-instrument@5.2.16.0.3(transitive)
+ Addedistanbul-lib-report@3.0.1(transitive)
+ Addedistanbul-lib-source-maps@4.0.1(transitive)
+ Addedistanbul-reports@3.1.7(transitive)
+ Addedjest@29.7.0(transitive)
+ Addedjest-changed-files@29.7.0(transitive)
+ Addedjest-circus@29.7.0(transitive)
+ Addedjest-cli@29.7.0(transitive)
+ Addedjest-config@29.7.0(transitive)
+ Addedjest-diff@29.7.0(transitive)
+ Addedjest-docblock@29.7.0(transitive)
+ Addedjest-each@29.7.0(transitive)
+ Addedjest-environment-node@29.7.0(transitive)
+ Addedjest-get-type@29.6.3(transitive)
+ Addedjest-haste-map@29.7.0(transitive)
+ Addedjest-leak-detector@29.7.0(transitive)
+ Addedjest-matcher-utils@29.7.0(transitive)
+ Addedjest-message-util@29.7.0(transitive)
+ Addedjest-mock@29.7.0(transitive)
+ Addedjest-pnp-resolver@1.2.3(transitive)
+ Addedjest-regex-util@29.6.3(transitive)
+ Addedjest-resolve@29.7.0(transitive)
+ Addedjest-resolve-dependencies@29.7.0(transitive)
+ Addedjest-runner@29.7.0(transitive)
+ Addedjest-runtime@29.7.0(transitive)
+ Addedjest-snapshot@29.7.0(transitive)
+ Addedjest-util@29.7.0(transitive)
+ Addedjest-validate@29.7.0(transitive)
+ Addedjest-watcher@29.7.0(transitive)
+ Addedjest-worker@29.7.0(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedjsesc@3.0.2(transitive)
+ Addedjson-parse-even-better-errors@2.3.1(transitive)
+ Addedjson5@2.2.3(transitive)
+ Addedkleur@3.0.3(transitive)
+ Addedleven@3.1.0(transitive)
+ Addedlines-and-columns@1.2.4(transitive)
+ Addedlocate-path@5.0.0(transitive)
+ Addedlru-cache@5.1.1(transitive)
+ Addedmake-dir@4.0.0(transitive)
+ Addedmakeerror@1.0.12(transitive)
+ Addedmerge-stream@2.0.0(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedmimic-fn@2.1.0(transitive)
+ Addedminimatch@9.0.3(transitive)
+ Addednode-int64@0.4.0(transitive)
+ Addednode-releases@2.0.18(transitive)
+ Addednormalize-path@3.0.0(transitive)
+ Addednpm-run-path@4.0.1(transitive)
+ Addedonetime@5.1.2(transitive)
+ Addedp-limit@2.3.0(transitive)
+ Addedp-locate@4.1.0(transitive)
+ Addedp-try@2.2.0(transitive)
+ Addedparse-json@5.2.0(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-type@4.0.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedpirates@4.0.6(transitive)
+ Addedpkg-dir@4.2.0(transitive)
+ Addedpretty-format@29.7.0(transitive)
+ Addedprompts@2.4.2(transitive)
+ Addedpure-rand@6.1.0(transitive)
+ Addedqueue-microtask@1.2.3(transitive)
+ Addedreact-is@18.3.1(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedresolve@1.22.8(transitive)
+ Addedresolve-cwd@3.0.0(transitive)
+ Addedresolve-from@5.0.0(transitive)
+ Addedresolve.exports@2.0.2(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedrimraf@3.0.2(transitive)
+ Addedrun-parallel@1.2.0(transitive)
+ Addedsemver@6.3.1(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedsisteransi@1.0.5(transitive)
+ Addedslash@3.0.0(transitive)
+ Addedsource-map@0.6.1(transitive)
+ Addedsource-map-support@0.5.13(transitive)
+ Addedsprintf-js@1.0.3(transitive)
+ Addedstack-utils@2.0.6(transitive)
+ Addedstring-length@4.0.2(transitive)
+ Addedstring-width@4.2.3(transitive)
+ Addedstrip-ansi@6.0.1(transitive)
+ Addedstrip-bom@4.0.0(transitive)
+ Addedstrip-final-newline@2.0.0(transitive)
+ Addedsupports-color@8.1.1(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtest-exclude@6.0.0(transitive)
+ Addedtmpl@1.0.5(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedts-api-utils@1.3.0(transitive)
+ Addedtype-detect@4.0.8(transitive)
+ Addedtype-fest@0.20.20.21.3(transitive)
+ Addedundici-types@6.19.8(transitive)
+ Addedupdate-browserslist-db@1.1.1(transitive)
+ Addedv8-to-istanbul@9.3.0(transitive)
+ Addedwalker@1.0.8(transitive)
+ Addedwrap-ansi@7.0.0(transitive)
+ Addedwrite-file-atomic@4.0.2(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyallist@3.1.1(transitive)
+ Addedyargs@17.7.2(transitive)
+ Addedyargs-parser@21.1.1(transitive)
- Removed@eslint/config-array@0.18.0(transitive)
- Removed@eslint/core@0.7.0(transitive)
- Removed@eslint/eslintrc@3.1.0(transitive)
- Removed@eslint/js@9.14.0(transitive)
- Removed@eslint/object-schema@2.1.4(transitive)
- Removed@eslint/plugin-kit@0.2.2(transitive)
- Removed@humanfs/core@0.19.1(transitive)
- Removed@humanfs/node@0.16.6(transitive)
- Removed@humanwhocodes/retry@0.3.10.4.0(transitive)
- Removed@types/estree@1.0.6(transitive)
- Removed@typescript-eslint/experimental-utils@2.34.0(transitive)
- Removed@typescript-eslint/typescript-estree@2.34.0(transitive)
- Removedeslint@9.14.0(transitive)
- Removedeslint-scope@8.2.0(transitive)
- Removedeslint-utils@2.1.0(transitive)
- Removedeslint-visitor-keys@1.3.04.2.0(transitive)
- Removedespree@10.3.0(transitive)
- Removedfile-entry-cache@8.0.0(transitive)
- Removedflat-cache@4.0.1(transitive)
- Removedglobals@14.0.0(transitive)
- Removedlodash@4.17.21(transitive)