Skip to content

Commit abdeab3

Browse files
authored
feat: adding require-import-vi-mock rule (#820)
Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
1 parent 765eeac commit abdeab3

File tree

6 files changed

+135
-0
lines changed

6 files changed

+135
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export default defineConfig({
254254
| [prefer-vi-mocked](docs/rules/prefer-vi-mocked.md) | require `vi.mocked()` over `fn as Mock` | | 🌐 | | 🔧 | | 💭 | |
255255
| [require-awaited-expect-poll](docs/rules/require-awaited-expect-poll.md) | ensure that every `expect.poll` call is awaited | | 🌐 | | | | | |
256256
| [require-hook](docs/rules/require-hook.md) | require setup and teardown to be within a hook | | 🌐 | | | | | |
257+
| [require-import-vi-mock](docs/rules/require-import-vi-mock.md) | require usage of import in vi.mock() | | 🌐 | | 🔧 | | | |
257258
| [require-local-test-context-for-concurrent-snapshots](docs/rules/require-local-test-context-for-concurrent-snapshots.md) | require local Test Context for concurrent snapshot tests || 🌐 | | | | | |
258259
| [require-mock-type-parameters](docs/rules/require-mock-type-parameters.md) | enforce using type parameters with vitest mock functions | | 🌐 | | 🔧 | | | |
259260
| [require-to-throw-message](docs/rules/require-to-throw-message.md) | require toThrow() to be called with an error message | | 🌐 | | | | | |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Require usage of import in vi.mock() (`vitest/require-import-vi-mock`)
2+
3+
⚠️ This rule _warns_ in the 🌐 `all` config.
4+
5+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
7+
<!-- end auto-generated rule header -->
8+
9+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
10+
11+
<!-- end auto-generated rule header -->
12+
13+
## Rule Details
14+
15+
This rule enforce usage of `import` inside `vi.mock` usage
16+
17+
Examples of **incorrect** code for this rule:
18+
19+
```js
20+
vi.mock('./foo.js')
21+
```
22+
23+
Examples of **correct** code for this rule:
24+
25+
```js
26+
vi.mock(import('./foo.js'))
27+
vi.mock(import('./foo.js'), { spy: true })
28+
vi.mock(import('./foo.js'), () => ({
29+
Foo: vi.fn(),
30+
}))
31+
```

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const allRules = {
9797
'valid-expect': 'warn',
9898
'valid-title': 'warn',
9999
'require-awaited-expect-poll': 'warn',
100+
'require-import-vi-mock': 'warn',
100101
} as const satisfies RuleList
101102

102103
const recommendedRules = {

src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import preferTodo from './prefer-todo'
6666
import preferViMocked from './prefer-vi-mocked'
6767
import requireAwaitedExpectPoll from './require-awaited-expect-poll'
6868
import requireHook from './require-hook'
69+
import requireImportViMock from './require-import-vi-mock'
6970
import requireLocalTestContextForConcurrentSnapshots from './require-local-test-context-for-concurrent-snapshots'
7071
import requireMockTypeParameters from './require-mock-type-parameters'
7172
import requireToThrowMessage from './require-to-throw-message'
@@ -144,6 +145,7 @@ export const rules = {
144145
'prefer-vi-mocked': preferViMocked,
145146
'require-awaited-expect-poll': requireAwaitedExpectPoll,
146147
'require-hook': requireHook,
148+
'require-import-vi-mock': requireImportViMock,
147149
'require-local-test-context-for-concurrent-snapshots':
148150
requireLocalTestContextForConcurrentSnapshots,
149151
'require-mock-type-parameters': requireMockTypeParameters,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { createEslintRule } from '../utils'
2+
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
3+
import { parseVitestFnCall } from '../utils/parse-vitest-fn-call'
4+
5+
export const RULE_NAME = 'require-import-vi-mock'
6+
7+
export default createEslintRule({
8+
name: RULE_NAME,
9+
meta: {
10+
fixable: 'code',
11+
type: 'suggestion',
12+
docs: {
13+
description: 'require usage of import in vi.mock()',
14+
requiresTypeChecking: false,
15+
recommended: false,
16+
},
17+
messages: {
18+
requireImport: "Replace '{{path}}' with import('{{path}}')",
19+
},
20+
schema: [],
21+
},
22+
defaultOptions: [],
23+
create(context) {
24+
return {
25+
CallExpression(node) {
26+
// Only consider vi.mock() calls
27+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return
28+
29+
const vitestCallFn = parseVitestFnCall(node, context)
30+
31+
if (vitestCallFn?.type !== 'vi') {
32+
return false
33+
}
34+
35+
const { property } = node.callee
36+
if (
37+
property.type !== AST_NODE_TYPES.Identifier ||
38+
property.name !== 'mock'
39+
) {
40+
return
41+
}
42+
43+
const pathArg = node.arguments[0]
44+
if (pathArg && pathArg.type === AST_NODE_TYPES.Literal) {
45+
context.report({
46+
messageId: 'requireImport',
47+
data: {
48+
path: pathArg.value,
49+
},
50+
node: pathArg,
51+
fix(fixer) {
52+
return fixer.replaceText(pathArg, `import('${pathArg.value}')`)
53+
},
54+
})
55+
}
56+
},
57+
}
58+
},
59+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import rule, { RULE_NAME } from '../src/rules/require-import-vi-mock'
2+
import { ruleTester } from './ruleTester'
3+
4+
ruleTester.run(RULE_NAME, rule, {
5+
valid: [
6+
'vi.mock(import("./foo.js"))',
7+
'vi.mock(import("node:fs/promises"))',
8+
'vi.mock(import("./foo.js"), () => ({ Foo: vi.fn() }))',
9+
'vi.mock(import("./foo.js"), { spy: true });',
10+
],
11+
invalid: [
12+
{
13+
code: 'vi.mock("./foo.js")',
14+
errors: [{ messageId: 'requireImport', column: 9, line: 1 }],
15+
output: "vi.mock(import('./foo.js'))",
16+
},
17+
{
18+
code: 'vi.mock("node:fs/promises")',
19+
errors: [{ messageId: 'requireImport', column: 9, line: 1 }],
20+
output: "vi.mock(import('node:fs/promises'))",
21+
},
22+
{
23+
code: 'vi.mock("./foo.js", () => ({ Foo: vi.fn() }))',
24+
errors: [{ messageId: 'requireImport', column: 9, line: 1 }],
25+
output: "vi.mock(import('./foo.js'), () => ({ Foo: vi.fn() }))",
26+
},
27+
{
28+
code: `
29+
import { vi as renamedVi } from 'vitest';
30+
31+
renamedVi.mock('./foo.js', () => ({ Foo: vi.fn() }))
32+
`,
33+
errors: [{ messageId: 'requireImport', column: 24, line: 4 }],
34+
output: `
35+
import { vi as renamedVi } from 'vitest';
36+
37+
renamedVi.mock(import('./foo.js'), () => ({ Foo: vi.fn() }))
38+
`,
39+
},
40+
],
41+
})

0 commit comments

Comments
 (0)