Skip to content

Commit

Permalink
feat(require-hook): add allowedFunctionCalls setting (#983)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrey Nelyubin <nelyubin_a_a@sunlight.net>
  • Loading branch information
2 people authored and G-Rath committed Nov 23, 2021
1 parent c0a00a1 commit 9d9336a
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 7 deletions.
37 changes: 37 additions & 0 deletions docs/rules/require-hook.md
Expand Up @@ -148,3 +148,40 @@ afterEach(() => {
clearCityDatabase();
});
```

## Options

If there are methods that you want to call outside of hooks and tests, you can
mark them as allowed using the `allowedFunctionCalls` option.

```json
{
"jest/require-hook": [
"error",
{
"allowedFunctionCalls": ["enableAutoDestroy"]
}
]
}
```

Examples of **correct** code when using
`{ "allowedFunctionCalls": ["enableAutoDestroy"] }` option:

```js
/* eslint jest/require-hook: ["error", { "allowedFunctionCalls": ["enableAutoDestroy"] }] */

import { enableAutoDestroy, mount } from '@vue/test-utils';
import { initDatabase, tearDownDatabase } from './databaseUtils';

enableAutoDestroy(afterEach);

beforeEach(initDatabase);
afterEach(tearDownDatabase);

describe('Foo', () => {
test('always returns 42', () => {
expect(global.getAnswer()).toBe(42);
});
});
```
31 changes: 31 additions & 0 deletions src/rules/__tests__/require-hook.test.ts
Expand Up @@ -152,6 +152,18 @@ ruleTester.run('require-hook', rule, {
});
});
`,
{
code: dedent`
enableAutoDestroy(afterEach);
describe('some tests', () => {
it('is false', () => {
expect(true).toBe(true);
});
});
`,
options: [{ allowedFunctionCalls: ['enableAutoDestroy'] }],
},
],
invalid: [
{
Expand Down Expand Up @@ -374,6 +386,25 @@ ruleTester.run('require-hook', rule, {
},
],
},
{
code: dedent`
enableAutoDestroy(afterEach);
describe('some tests', () => {
it('is false', () => {
expect(true).toBe(true);
});
});
`,
options: [{ allowedFunctionCalls: ['someOtherName'] }],
errors: [
{
messageId: 'useHook',
line: 1,
column: 1,
},
],
},
],
});

Expand Down
40 changes: 33 additions & 7 deletions src/rules/require-hook.ts
Expand Up @@ -27,12 +27,18 @@ const isNullOrUndefined = (node: TSESTree.Expression): boolean => {
);
};

const shouldBeInHook = (node: TSESTree.Node): boolean => {
const shouldBeInHook = (
node: TSESTree.Node,
allowedFunctionCalls: readonly string[] = [],
): boolean => {
switch (node.type) {
case AST_NODE_TYPES.ExpressionStatement:
return shouldBeInHook(node.expression);
return shouldBeInHook(node.expression, allowedFunctionCalls);
case AST_NODE_TYPES.CallExpression:
return !isJestFnCall(node);
return !(
isJestFnCall(node) ||
allowedFunctionCalls.includes(getNodeName(node) as string)
);
case AST_NODE_TYPES.VariableDeclaration: {
if (node.kind === 'const') {
return false;
Expand All @@ -48,7 +54,10 @@ const shouldBeInHook = (node: TSESTree.Node): boolean => {
}
};

export default createRule({
export default createRule<
[{ allowedFunctionCalls?: readonly string[] }],
'useHook'
>({
name: __filename,
meta: {
docs: {
Expand All @@ -60,13 +69,30 @@ export default createRule({
useHook: 'This should be done within a hook',
},
type: 'suggestion',
schema: [],
schema: [
{
type: 'object',
properties: {
allowedFunctionCalls: {
type: 'array',
items: { type: 'string' },
},
},
additionalProperties: false,
},
],
},
defaultOptions: [],
defaultOptions: [
{
allowedFunctionCalls: [],
},
],
create(context) {
const { allowedFunctionCalls } = context.options[0] ?? {};

const checkBlockBody = (body: TSESTree.BlockStatement['body']) => {
for (const statement of body) {
if (shouldBeInHook(statement)) {
if (shouldBeInHook(statement, allowedFunctionCalls)) {
context.report({
node: statement,
messageId: 'useHook',
Expand Down

0 comments on commit 9d9336a

Please sign in to comment.