Socket
Socket
Sign inDemoInstall

eslint-plugin-jest

Package Overview
Dependencies
Maintainers
11
Versions
325
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-jest - npm Package Compare versions

Comparing version 23.20.0 to 27.6.0

docs/rules/max-expects.md

21

docs/rules/consistent-test-it.md

@@ -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,7 +55,6 @@ **emptyTitle**

Titles for test blocks should always be a string literal or expression.
Titles for `describe`, `test`, and `it` blocks should always be a string; you
can disable this with the `ignoreTypeOfDescribeName` and `ignoreTypeOfTestName`
options.
This is also applied to `describe` blocks by default, but can be turned off via
the `ignoreTypeOfDescribeName` option:
Examples of **incorrect** code for this rule:

@@ -60,3 +67,3 @@

xdescribe(myFunction, () => {});
describe(6, function() {});
describe(6, function () {});
```

@@ -88,5 +95,17 @@

xdescribe(myFunction, () => {});
describe(6, function() {});
describe(6, function () {});
```
Examples of **correct** code when `ignoreTypeOfTestName` is `true`:
```js
const myTestName = 'is a string';
it(String(/.+/), () => {});
it(myFunction, () => {});
it(myTestName, () => {});
xit(myFunction, () => {});
it(6, function () {});
```
**duplicatePrefix**

@@ -124,3 +143,4 @@

A `describe` / `test` block should not contain accidentalSpace
A `describe` / `test` block should not contain accidentalSpace, but can be
turned off via the `ignoreSpaces` option:

@@ -161,2 +181,3 @@ Examples of **incorrect** code for this rule

interface Options {
ignoreSpaces?: boolean;
ignoreTypeOfDescribeName?: boolean;

@@ -169,2 +190,8 @@ disallowedWords?: string[];

#### `ignoreSpaces`
Default: `false`
When enabled, the leading and trailing spaces won't be checked.
#### `ignoreTypeOfDescribeName`

@@ -208,4 +235,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`).

@@ -215,7 +243,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', () => {});

@@ -229,7 +257,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', () => {});

@@ -239,1 +267,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 _package = require("../package.json");
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(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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 +18,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,17 +34,16 @@ plugins: ['jest'],

});
module.exports = {
meta: {
name: _package.name,
version: _package.version
},
configs: {
all: createConfig(allRules),
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'
})
},

@@ -70,0 +50,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 = exports.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,25 @@ 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,9 @@ * 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -52,2 +50,8 @@ meta: {

}]
},
additionalTestBlockFunctions: {
type: 'array',
items: {
type: 'string'
}
}

@@ -60,20 +64,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 +87,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,15 +101,10 @@ checkCallExpressionUsed(context.getAncestors());

},
'Program:exit'() {
unchecked.forEach(node => context.report({
messageId: 'noAssertions',
node
node: node.callee
}));
}
};
}
});
exports.default = _default;
});

@@ -7,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -17,3 +15,3 @@ meta: {

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,9 @@ 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,10 +7,7 @@ "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)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -30,6 +27,4 @@ meta: {

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,4 @@ 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 = exports.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,2 @@ '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 = exports.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,13 @@ 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,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -21,4 +19,2 @@ meta: {

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,5 @@ 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,13 +7,4 @@ "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)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -33,38 +24,34 @@ meta: {

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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -22,3 +19,3 @@ meta: {

messages: {
unexpectedExport: `Do not export from a test file.`
unexpectedExport: `Do not export from a test file`
},

@@ -29,3 +26,2 @@ type: 'suggestion',

defaultOptions: [],
create(context) {

@@ -45,13 +41,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) {

@@ -62,4 +55,3 @@ let {

} = node;
if (object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
if (object.type === _utils.AST_NODE_TYPES.MemberExpression) {
({

@@ -70,13 +62,8 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -33,50 +19,49 @@ 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: jestFnCall.head.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,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -37,3 +35,2 @@ meta: {

}],
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,4 @@ });

}
};
}
});
exports.default = _default;
});

@@ -7,5 +7,3 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
const newDescribeContext = () => ({

@@ -15,4 +13,3 @@ describeTitles: [],

});
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -26,4 +23,4 @@ meta: {

messages: {
multipleTestTitle: 'Test title is used multiple times in the same describe block.',
multipleDescribeTitle: 'Describe block title is used multiple times in the same describe block.'
multipleTestTitle: 'Test title is used multiple times in the same describe block',
multipleDescribeTitle: 'Describe block title is used multiple times in the same describe block'
},

@@ -34,3 +31,2 @@ schema: [],

defaultOptions: [],
create(context) {

@@ -41,16 +37,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)) {

@@ -62,10 +60,7 @@ context.report({

}
currentLayer.testTitles.push(title);
}
if (!(0, _utils.isDescribe)(node)) {
if (jestFnCall.type !== 'describe') {
return;
}
if (currentLayer.describeTitles.includes(title)) {

@@ -77,17 +72,11 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -32,4 +26,6 @@ meta: {

messages: {
conditionalInTest: 'Test should not contain {{ condition }} statements.'
conditionalInTest: 'Test should not contain {{ condition }} statements'
},
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,40 @@ 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 = exports.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,4 @@ 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 = exports.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,4 @@ 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 = exports.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,19 @@ 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,14 +7,8 @@ "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)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -29,3 +23,3 @@ meta: {

messages: {
noManualImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path.`
noManualImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path`
},

@@ -35,3 +29,2 @@ schema: []

defaultOptions: [],
create(context) {

@@ -47,6 +40,4 @@ return {

},
'CallExpression[callee.name="require"]'(node) {
const [arg] = node.arguments;
if (arg && isMockImportLiteral(arg)) {

@@ -59,8 +50,4 @@ context.report({

}
};
}
});
exports.default = _default;
});

@@ -7,6 +7,10 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
const isChainRestricted = (chain, restriction) => {
if (_utils.ModifierName.hasOwnProperty(restriction) || restriction.endsWith('.not')) {
return chain.startsWith(restriction);
}
return chain === restriction;
};
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -27,3 +31,3 @@ meta: {

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,15 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -53,3 +46,3 @@ meta: {

messages: {
unexpectedExpect: 'Expect must be inside of a test block.'
unexpectedExpect: 'Expect must be inside of a test block'
},

@@ -72,3 +65,2 @@ type: 'suggestion',

}],
create(context, [{

@@ -78,13 +70,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({

@@ -95,26 +87,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) {

@@ -124,17 +109,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'() {

@@ -145,8 +126,4 @@ 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 = exports.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,9 @@ 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;
}
});

@@ -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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -32,3 +26,3 @@ meta: {

messages: {
noReturnValue: 'Jest tests should not return a value.'
noReturnValue: 'Jest tests should not return a value'
},

@@ -39,10 +33,13 @@ schema: [],

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);
if (!returnStmt) return;
const returnStmt = body.find(t => t.type === _utils.AST_NODE_TYPES.ReturnStatement);
if (!returnStmt) {
return;
}
context.report({

@@ -53,9 +50,12 @@ messageId: 'noReturnValue',

},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
if (testCallExpressions.length === 0) return;
const returnStmt = node.body.body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement);
if (!returnStmt) return;
const testCallExpressions = (0, _utils2.getTestCallExpressionsFromDeclaredVariables)(declaredVariables, context);
if (testCallExpressions.length === 0) {
return;
}
const returnStmt = node.body.body.find(t => t.type === _utils.AST_NODE_TYPES.ReturnStatement);
if (!returnStmt) {
return;
}
context.report({

@@ -66,8 +66,4 @@ messageId: 'noReturnValue',

}
};
}
});
exports.default = _default;
});

@@ -7,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -20,3 +18,3 @@ meta: {

messages: {
preferCalledWith: 'Prefer {{name}}With(/* expected args */)'
preferCalledWith: 'Prefer {{ matcherName }}With(/* expected args */)'
},

@@ -27,34 +25,28 @@ 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 = exports.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,156 @@ 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,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -20,3 +18,3 @@ meta: {

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,9 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -53,3 +52,3 @@ meta: {

messages: {
useJestSpyOn: 'Use jest.spyOn() instead.'
useJestSpyOn: 'Use jest.spyOn() instead'
},

@@ -61,3 +60,2 @@ fixable: 'code',

defaultOptions: [],
create(context) {

@@ -70,25 +68,21 @@ return {

} = node;
if (left.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) return;
if (left.type !== _utils.AST_NODE_TYPES.MemberExpression) {
return;
}
const jestFnCall = getJestFnCall(right);
if (!jestFnCall) return;
if (!jestFnCall) {
return;
}
context.report({
node,
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,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -17,4 +15,3 @@ meta: {

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,4 @@ });

}
};
}
});
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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -101,49 +38,48 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -29,43 +26,36 @@ 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 = exports.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,15 @@ 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,6 +7,4 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
var _default = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -26,16 +24,14 @@ meta: {

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,10 @@ context.report({

data: {
matcherName: matcher.name
matcherName
},
node: matcher.node.property
node: matcher
});
}
}
};
}
});
exports.default = _default;
});

@@ -7,6 +7,9 @@ "use strict";

exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
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 = exports.default = (0, _utils.createRule)({
name: __filename,

@@ -19,22 +22,47 @@ meta: {

},
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,9 @@ 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 = exports.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,97 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -70,9 +69,9 @@ meta: {

messages: {
tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}.',
notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
matcherNotFound: 'Expect must have a corresponding matcher call.',
matcherNotCalled: 'Matchers must be called to assert.',
asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}.',
promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}.'
tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}',
notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}',
modifierUnknown: 'Expect has an unknown modifier',
matcherNotFound: 'Expect must have a corresponding matcher call',
matcherNotCalled: 'Matchers must be called to assert',
asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}',
promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}'
},

@@ -87,2 +86,8 @@ type: 'suggestion',

},
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,5 @@ 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 = exports.default = (0, _utils2.createRule)({
name: __filename,

@@ -52,3 +55,3 @@ meta: {

description: 'Enforce valid titles',
recommended: false
recommended: 'error'
},

@@ -60,5 +63,7 @@ messages: {

accidentalSpace: 'should not have leading or trailing spaces',
disallowedWord: '"{{ word }}" is not allowed in test titles.',
disallowedWord: '"{{ word }}" is not allowed in test titles',
mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}',
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}'
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}',
mustNotMatchCustom: '{{ message }}',
mustMatchCustom: '{{ message }}'
},

@@ -69,2 +74,6 @@ type: 'suggestion',

properties: {
ignoreSpaces: {
type: 'boolean',
default: false
},
ignoreTypeOfDescribeName: {

@@ -74,2 +83,6 @@ type: 'boolean',

},
ignoreTypeOfTestName: {
type: 'boolean',
default: false
},
disallowedWords: {

@@ -80,39 +93,18 @@ type: 'array',

}
},
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]
}
}]

@@ -126,8 +118,11 @@ }

defaultOptions: [{
ignoreSpaces: false,
ignoreTypeOfDescribeName: false,
ignoreTypeOfTestName: false,
disallowedWords: []
}],
create(context, [{
ignoreSpaces,
ignoreTypeOfDescribeName,
ignoreTypeOfTestName,
disallowedWords = [],

@@ -138,22 +133,19 @@ mustNotMatch,

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 (!(jestFnCall.type === 'describe' && ignoreTypeOfDescribeName || jestFnCall.type === 'test' && ignoreTypeOfTestName) && argument.type !== _utils.AST_NODE_TYPES.TemplateLiteral) {
context.report({

@@ -164,8 +156,5 @@ messageId: 'titleMustBeString',

}
return;
}
const title = (0, _utils.getStringValue)(argument);
const title = (0, _utils2.getStringValue)(argument);
if (!title) {

@@ -175,3 +164,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
},

@@ -182,6 +171,4 @@ node

}
if (disallowedWords.length > 0) {
const disallowedMatch = disallowedWordsRegexp.exec(title);
if (disallowedMatch) {

@@ -198,4 +185,3 @@ context.report({

}
if (title.trim().length !== title.length) {
if (ignoreSpaces === false && title.trim().length !== title.length) {
context.report({

@@ -207,7 +193,5 @@ messageId: 'accidentalSpace',

}
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({

@@ -219,14 +203,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
}

@@ -237,13 +220,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
}

@@ -255,8 +237,4 @@ });

}
};
}
});
exports.default = _default;
});
{
"name": "eslint-plugin-jest",
"version": "23.20.0",
"description": "Eslint rules for Jest",
"version": "27.6.0",
"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"
},

@@ -60,5 +51,21 @@ "prettier": {

"proseWrap": "always",
"singleQuote": true,
"trailingComma": "all"
"singleQuote": true
},
"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 +83,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 +103,3 @@ },

"dependencies": {
"@typescript-eslint/experimental-utils": "^2.5.0"
"@typescript-eslint/utils": "^5.10.0"
},

@@ -103,52 +111,61 @@ "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",
"@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",
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3",
"@schemastore/package": "^0.0.10",
"@semantic-release/changelog": "^6.0.0",
"@semantic-release/git": "^10.0.0",
"@tsconfig/node14": "^14.1.0",
"@types/eslint": "^8.4.6",
"@types/jest": "^29.0.0",
"@types/node": "^14.18.26",
"@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",
"dedent": "^1.5.0",
"eslint": "^7.0.0 || ^8.0.0",
"eslint-config-prettier": "^9.0.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-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-eslint-plugin": "^5.0.6",
"eslint-plugin-import": "^2.25.1",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-prettier": "^5.0.0",
"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": "^3.0.0",
"rimraf": "^5.0.0",
"semantic-release": "^22.0.0",
"semver": "^7.3.5",
"strip-ansi": "^6.0.0",
"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.4",
"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
}
}
<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,92 @@

<!-- 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-confusing-set-timeout](docs/rules/no-confusing-set-timeout.md) | Disallow confusing usages of jest.setTimeout | | | | | |
| [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 +301,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 +315,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
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc