Skip to content

Commit ae01775

Browse files
author
Andrey Nelyubin
committed
fix(require-hook): added optional settings
1 parent 7833de4 commit ae01775

File tree

3 files changed

+142
-7
lines changed

3 files changed

+142
-7
lines changed

docs/rules/require-hook.md

+67
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,70 @@ afterEach(() => {
148148
clearCityDatabase();
149149
});
150150
```
151+
152+
## Options
153+
154+
If there are methods that you want to call outside of hooks and tests,
155+
you can mark them as allowed using the `allowedFunctionCalls` option.
156+
157+
```json
158+
{
159+
"jest/require-hook": [
160+
"error",
161+
{
162+
"allowedFunctionCalls": [
163+
"enableAutoDestroy"
164+
]
165+
}
166+
]
167+
}
168+
```
169+
170+
Examples of **incorrect** code for the `{ "allowedFunctionCalls": ["enableAutoDestroy"] }`
171+
option:
172+
173+
```js
174+
/* eslint jest/require-hook: ["error", { "allowedFunctionCalls": ["enableAutoDestroy"] }] */
175+
176+
import {
177+
enableAutoDestroy,
178+
mount
179+
} from '@vue/test-utils';
180+
import {initDatabase, tearDownDatabase} from './databaseUtils';
181+
182+
enableAutoDestroy(afterEach);
183+
184+
initDatabase(); // this will throw a linting error
185+
tearDownDatabase(); // this will too
186+
187+
describe('Foo', () => {
188+
test('always returns 42', () => {
189+
expect(global.getAnswer()).toBe(42);
190+
})
191+
})
192+
```
193+
194+
195+
Examples of **correct** code for the `{ "allowedFunctionCalls": ["enableAutoDestroy"] }`
196+
option:
197+
198+
```js
199+
/* eslint jest/require-hook: ["error", { "allowedFunctionCalls": ["enableAutoDestroy"] }] */
200+
201+
import {
202+
enableAutoDestroy,
203+
mount
204+
} from '@vue/test-utils';
205+
import {initDatabase, tearDownDatabase} from './databaseUtils';
206+
207+
enableAutoDestroy(afterEach);
208+
209+
beforeEach(initDatabase);
210+
afterEach(tearDownDatabase);
211+
212+
describe('Foo', () => {
213+
test('always returns 42', () => {
214+
expect(global.getAnswer()).toBe(42);
215+
});
216+
});
217+
```

src/rules/__tests__/require-hook.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ ruleTester.run('require-hook', rule, {
152152
});
153153
});
154154
`,
155+
{
156+
code: dedent`
157+
enableAutoDestroy(afterEach);
158+
159+
describe('some tests', () => {
160+
it('is false', () => {
161+
expect(true).toBe(true);
162+
});
163+
});
164+
`,
165+
options: [{ allowedFunctionCalls: ['enableAutoDestroy'] }],
166+
},
155167
],
156168
invalid: [
157169
{
@@ -374,6 +386,25 @@ ruleTester.run('require-hook', rule, {
374386
},
375387
],
376388
},
389+
{
390+
code: dedent`
391+
enableAutoDestroy(afterEach);
392+
393+
describe('some tests', () => {
394+
it('is false', () => {
395+
expect(true).toBe(true);
396+
});
397+
});
398+
`,
399+
options: [{ allowedFunctionCalls: ['someOtherName'] }],
400+
errors: [
401+
{
402+
messageId: 'useHook',
403+
line: 1,
404+
column: 1,
405+
},
406+
],
407+
},
377408
],
378409
});
379410

src/rules/require-hook.ts

+44-7
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,24 @@ const isNullOrUndefined = (node: TSESTree.Expression): boolean => {
2727
);
2828
};
2929

30-
const shouldBeInHook = (node: TSESTree.Node): boolean => {
30+
const isExcludedFnCall = (
31+
node: TSESTree.CallExpression,
32+
allowedFunctionCalls: string[],
33+
): boolean => {
34+
return allowedFunctionCalls.includes(getNodeName(node) as string);
35+
};
36+
37+
const shouldBeInHook = (
38+
node: TSESTree.Node,
39+
allowedFunctionCalls: string[],
40+
): boolean => {
3141
switch (node.type) {
3242
case AST_NODE_TYPES.ExpressionStatement:
33-
return shouldBeInHook(node.expression);
43+
return shouldBeInHook(node.expression, allowedFunctionCalls);
3444
case AST_NODE_TYPES.CallExpression:
35-
return !isJestFnCall(node);
45+
return !(
46+
isJestFnCall(node) || isExcludedFnCall(node, allowedFunctionCalls)
47+
);
3648
case AST_NODE_TYPES.VariableDeclaration: {
3749
if (node.kind === 'const') {
3850
return false;
@@ -48,7 +60,10 @@ const shouldBeInHook = (node: TSESTree.Node): boolean => {
4860
}
4961
};
5062

51-
export default createRule({
63+
export default createRule<
64+
[Partial<{ allowedFunctionCalls?: readonly string[] }>],
65+
'useHook'
66+
>({
5267
name: __filename,
5368
meta: {
5469
docs: {
@@ -60,13 +75,35 @@ export default createRule({
6075
useHook: 'This should be done within a hook',
6176
},
6277
type: 'suggestion',
63-
schema: [],
78+
schema: [
79+
{
80+
type: 'object',
81+
properties: {
82+
allowedFunctionCalls: {
83+
type: 'array',
84+
items: { type: 'string' },
85+
},
86+
},
87+
additionalProperties: false,
88+
},
89+
],
6490
},
65-
defaultOptions: [],
91+
defaultOptions: [
92+
{
93+
allowedFunctionCalls: [],
94+
},
95+
],
6696
create(context) {
97+
const { allowedFunctionCalls } = context.options[0] ?? {};
98+
6799
const checkBlockBody = (body: TSESTree.BlockStatement['body']) => {
68100
for (const statement of body) {
69-
if (shouldBeInHook(statement)) {
101+
if (
102+
shouldBeInHook(
103+
statement,
104+
Array.isArray(allowedFunctionCalls) ? allowedFunctionCalls : [],
105+
)
106+
) {
70107
context.report({
71108
node: statement,
72109
messageId: 'useHook',

0 commit comments

Comments
 (0)