Skip to content

Commit 80063e6

Browse files
committed
feat: change rule name + add tests + configurable module file extensions + faster return branches
Signed-off-by: hainenber <[email protected]>
1 parent 4fb2681 commit 80063e6

File tree

8 files changed

+198
-104
lines changed

8 files changed

+198
-104
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ Manually fixable by
386386
| [valid-describe-callback](docs/rules/valid-describe-callback.md) | Enforce valid `describe()` callback || | | |
387387
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage || | 🔧 | |
388388
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Require promises that have expectations in their chain to be valid || | | |
389-
| [valid-mocked-module-path](docs/rules/valid-mocked-module-path.md) | Disallow mocking of non-existing module path | | | | |
389+
| [valid-mock-module-path](docs/rules/valid-mock-module-path.md) | Disallow mocking of non-existing module path | | | | |
390390
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles || | 🔧 | |
391391

392392
### Requires Type Checking

docs/rules/valid-mocked-module-path.md renamed to docs/rules/valid-mock-module-path.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Disallow mocking of non-existing module path (`valid-mocked-module-path`)
1+
# Disallow mocking of non-existing module path (`valid-mock-module-path`)
22

33
<!-- end auto-generated rule header -->
44

@@ -38,6 +38,36 @@ jest.mock('../../this/module/really/does/exist');
3838
jest.mock('../../this/path/really/does/exist.js');
3939
```
4040

41+
## Options
42+
43+
```json
44+
{
45+
"jest/valid-mock-module-path": [
46+
"error",
47+
{
48+
"moduleFileExtensions": [".tsx", ".ts"]
49+
}
50+
]
51+
}
52+
```
53+
54+
### `moduleFileExtensions`
55+
56+
This array option controls which file extensions the plugin checks for
57+
existence. Valid values are:
58+
59+
- `".js"`
60+
- `".ts"`
61+
- `".jsx"`
62+
- `".tsx"`
63+
- `".json"`
64+
65+
For any custom extension, a preceding dot **must** be present before the file
66+
extension for desired effect.
67+
68+
The default value for this option is
69+
`{ "moduleFileExtensions": [".js", ".ts", ".jsx", ".tsx", ".json"] }`.
70+
4171
## When Not To Use It
4272

4373
Don't use this rule on non-jest test files.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
7272
"jest/valid-describe-callback": "error",
7373
"jest/valid-expect": "error",
7474
"jest/valid-expect-in-promise": "error",
75-
"jest/valid-mocked-module-path": "error",
75+
"jest/valid-mock-module-path": "error",
7676
"jest/valid-title": "error",
7777
},
7878
},
@@ -165,7 +165,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
165165
"jest/valid-describe-callback": "error",
166166
"jest/valid-expect": "error",
167167
"jest/valid-expect-in-promise": "error",
168-
"jest/valid-mocked-module-path": "error",
168+
"jest/valid-mock-module-path": "error",
169169
"jest/valid-title": "error",
170170
},
171171
},

src/rules/__tests__/fixtures/module/jsx/foo.jsx

Whitespace-only changes.

src/rules/__tests__/fixtures/module/tsx/foo.jsx

Whitespace-only changes.

src/rules/__tests__/valid-mocked-module-path.test.ts renamed to src/rules/__tests__/valid-mock-module-path.test.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dedent from 'dedent';
2-
import rule from '../valid-mocked-module-path';
2+
import rule from '../valid-mock-module-path';
33
import { FlatCompatRuleTester as RuleTester, espreeParser } from './test-utils';
44

55
const ruleTester = new RuleTester({
@@ -9,7 +9,7 @@ const ruleTester = new RuleTester({
99
},
1010
});
1111

12-
ruleTester.run('valid-mocked-module-path', rule, {
12+
ruleTester.run('valid-mock-module-path', rule, {
1313
valid: [
1414
{ filename: __filename, code: 'jest.mock("./fixtures/module")' },
1515
{ filename: __filename, code: 'jest.mock("./fixtures/module", () => {})' },
@@ -32,6 +32,21 @@ ruleTester.run('valid-mocked-module-path', rule, {
3232
'jest.mock("eslint")',
3333
'jest.doMock("eslint")',
3434
'jest.mock("child_process")',
35+
'jest.mock(() => {})',
36+
{
37+
filename: __filename,
38+
code: dedent`
39+
const a = "../module/does/not/exist";
40+
jest.mock(a);
41+
`,
42+
},
43+
{ filename: __filename, code: 'jest.mock("./fixtures/module/jsx/foo")' },
44+
{ filename: __filename, code: 'jest.mock("./fixtures/module/tsx/foo")' },
45+
{
46+
filename: __filename,
47+
code: 'jest.mock("./fixtures/module/tsx/foo")',
48+
options: [{ moduleFileExtensions: ['.jsx'] }],
49+
},
3550
],
3651
invalid: [
3752
{
@@ -58,6 +73,32 @@ ruleTester.run('valid-mocked-module-path', rule, {
5873
},
5974
],
6075
},
76+
{
77+
filename: __filename,
78+
code: 'jest.mock("./fixtures/module/foo.jsx")',
79+
options: [{ moduleFileExtensions: ['.tsx'] }],
80+
errors: [
81+
{
82+
messageId: 'invalidMockModulePath',
83+
data: { moduleName: '"./fixtures/module/foo.jsx"' },
84+
column: 1,
85+
line: 1,
86+
},
87+
],
88+
},
89+
{
90+
filename: __filename,
91+
code: 'jest.mock("./fixtures/module/foo.jsx")',
92+
options: [{ moduleFileExtensions: undefined }],
93+
errors: [
94+
{
95+
messageId: 'invalidMockModulePath',
96+
data: { moduleName: '"./fixtures/module/foo.jsx"' },
97+
column: 1,
98+
line: 1,
99+
},
100+
],
101+
},
61102
{
62103
filename: __filename,
63104
code: 'jest.mock("@doesnotexist/module")',
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { statSync } from 'fs';
2+
import path from 'path';
3+
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
4+
import {
5+
createRule,
6+
findModuleName,
7+
getAccessorValue,
8+
isSupportedAccessor,
9+
isTypeOfJestFnCall,
10+
} from './utils';
11+
12+
export default createRule<
13+
[
14+
Partial<{
15+
moduleFileExtensions: readonly string[];
16+
}>,
17+
],
18+
'invalidMockModulePath'
19+
>({
20+
name: __filename,
21+
meta: {
22+
type: 'problem',
23+
docs: {
24+
description: 'Disallow mocking of non-existing module path',
25+
},
26+
messages: {
27+
invalidMockModulePath: 'Module path {{ moduleName }} does not exist',
28+
},
29+
schema: [
30+
{
31+
type: 'object',
32+
properties: {
33+
moduleFileExtensions: {
34+
type: 'array',
35+
items: { type: 'string' },
36+
additionalItems: false,
37+
},
38+
},
39+
additionalProperties: false,
40+
},
41+
],
42+
},
43+
defaultOptions: [
44+
{
45+
moduleFileExtensions: ['.js', '.ts', '.tsx', '.jsx', '.json'],
46+
},
47+
],
48+
create(
49+
context,
50+
[{ moduleFileExtensions = ['.js', '.ts', '.tsx', '.jsx', '.json'] }],
51+
) {
52+
return {
53+
CallExpression(node: TSESTree.CallExpression): void {
54+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
55+
return;
56+
}
57+
58+
if (
59+
!node.arguments.length ||
60+
!isTypeOfJestFnCall(node, context, ['jest']) ||
61+
!(
62+
isSupportedAccessor(node.callee.property) &&
63+
['mock', 'doMock'].includes(getAccessorValue(node.callee.property))
64+
)
65+
) {
66+
return;
67+
}
68+
69+
const moduleName = findModuleName(node.arguments[0]);
70+
71+
if (!moduleName) {
72+
return;
73+
}
74+
75+
try {
76+
if (!moduleName.value.startsWith('.')) {
77+
require.resolve(moduleName.value);
78+
79+
return;
80+
}
81+
82+
const resolvedModulePath = path.resolve(
83+
path.dirname(context.filename),
84+
moduleName.value,
85+
);
86+
87+
const hasPossiblyModulePaths = ['', ...moduleFileExtensions].some(
88+
ext => {
89+
try {
90+
statSync(`${resolvedModulePath}${ext}`);
91+
92+
return true;
93+
} catch {
94+
return false;
95+
}
96+
},
97+
);
98+
99+
if (!hasPossiblyModulePaths) {
100+
throw { code: 'MODULE_NOT_FOUND' };
101+
}
102+
} catch (err: any) {
103+
// Reports unexpected issues when attempt to verify mocked module path.
104+
// The list of possible errors is non-exhaustive.
105+
/* istanbul ignore if */
106+
if (!['MODULE_NOT_FOUND', 'ENOENT'].includes(err.code)) {
107+
throw new Error(
108+
`Error when trying to validate mock module path from \`jest.mock\`: ${err}`,
109+
);
110+
}
111+
112+
context.report({
113+
messageId: 'invalidMockModulePath',
114+
data: { moduleName: moduleName.raw },
115+
node,
116+
});
117+
}
118+
},
119+
};
120+
},
121+
});

src/rules/valid-mocked-module-path.ts

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)