From 86c83f1cb794fb6a9368b437a9e2b0be891c6d9b Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 20 Jul 2019 22:51:10 +1200 Subject: [PATCH] chore(no-hooks): convert to typescript (#322) --- src/rules/__tests__/no-hooks.test.js | 51 ----------------------- src/rules/__tests__/no-hooks.test.ts | 60 ++++++++++++++++++++++++++++ src/rules/no-hooks.js | 46 --------------------- src/rules/no-hooks.ts | 50 +++++++++++++++++++++++ src/rules/tsUtils.ts | 1 - src/rules/util.js | 13 ------ 6 files changed, 110 insertions(+), 111 deletions(-) delete mode 100644 src/rules/__tests__/no-hooks.test.js create mode 100644 src/rules/__tests__/no-hooks.test.ts delete mode 100644 src/rules/no-hooks.js create mode 100644 src/rules/no-hooks.ts diff --git a/src/rules/__tests__/no-hooks.test.js b/src/rules/__tests__/no-hooks.test.js deleted file mode 100644 index d9d9ef6b9..000000000 --- a/src/rules/__tests__/no-hooks.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { RuleTester } from 'eslint'; -import rule from '../no-hooks'; - -const ruleTester = new RuleTester({ - parserOptions: { - ecmaVersion: 6, - }, -}); - -ruleTester.run('no-hooks', rule, { - valid: [ - 'test("foo")', - 'describe("foo", () => { it("bar") })', - 'test("foo", () => { expect(subject.beforeEach()).toBe(true) })', - { - code: 'afterEach(() => {}); afterAll(() => {});', - options: [{ allow: ['afterEach', 'afterAll'] }], - }, - ], - invalid: [ - { - code: 'beforeAll(() => {})', - errors: [ - { messageId: 'unexpectedHook', data: { hookName: 'beforeAll' } }, - ], - }, - { - code: 'beforeEach(() => {})', - errors: [ - { messageId: 'unexpectedHook', data: { hookName: 'beforeEach' } }, - ], - }, - { - code: 'afterAll(() => {})', - errors: [{ messageId: 'unexpectedHook', data: { hookName: 'afterAll' } }], - }, - { - code: 'afterEach(() => {})', - errors: [ - { messageId: 'unexpectedHook', data: { hookName: 'afterEach' } }, - ], - }, - { - code: 'beforeEach(() => {}); afterEach(() => { jest.resetModules() });', - options: [{ allow: ['afterEach'] }], - errors: [ - { messageId: 'unexpectedHook', data: { hookName: 'beforeEach' } }, - ], - }, - ], -}); diff --git a/src/rules/__tests__/no-hooks.test.ts b/src/rules/__tests__/no-hooks.test.ts new file mode 100644 index 000000000..c12156197 --- /dev/null +++ b/src/rules/__tests__/no-hooks.test.ts @@ -0,0 +1,60 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import rule from '../no-hooks'; +import { HookName } from '../tsUtils'; + +const ruleTester = new TSESLint.RuleTester({ + parserOptions: { + ecmaVersion: 6, + }, +}); + +ruleTester.run('no-hooks', rule, { + valid: [ + 'test("foo")', + 'describe("foo", () => { it("bar") })', + 'test("foo", () => { expect(subject.beforeEach()).toBe(true) })', + { + code: 'afterEach(() => {}); afterAll(() => {});', + options: [{ allow: [HookName.afterEach, HookName.afterAll] }], + }, + ], + invalid: [ + { + code: 'beforeAll(() => {})', + errors: [ + { messageId: 'unexpectedHook', data: { hookName: HookName.beforeAll } }, + ], + }, + { + code: 'beforeEach(() => {})', + errors: [ + { + messageId: 'unexpectedHook', + data: { hookName: HookName.beforeEach }, + }, + ], + }, + { + code: 'afterAll(() => {})', + errors: [ + { messageId: 'unexpectedHook', data: { hookName: HookName.afterAll } }, + ], + }, + { + code: 'afterEach(() => {})', + errors: [ + { messageId: 'unexpectedHook', data: { hookName: HookName.afterEach } }, + ], + }, + { + code: 'beforeEach(() => {}); afterEach(() => { jest.resetModules() });', + options: [{ allow: [HookName.afterEach] }], + errors: [ + { + messageId: 'unexpectedHook', + data: { hookName: HookName.beforeEach }, + }, + ], + }, + ], +}); diff --git a/src/rules/no-hooks.js b/src/rules/no-hooks.js deleted file mode 100644 index 29dca2df1..000000000 --- a/src/rules/no-hooks.js +++ /dev/null @@ -1,46 +0,0 @@ -import { getDocsUrl, isHook } from './util'; - -export default { - meta: { - docs: { - url: getDocsUrl(__filename), - }, - messages: { - unexpectedHook: "Unexpected '{{ hookName }}' hook", - }, - }, - schema: [ - { - type: 'object', - properties: { - allow: { - type: 'array', - contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'], - }, - }, - additionalProperties: false, - }, - ], - create(context) { - const whitelistedHookNames = ( - context.options[0] || { allow: [] } - ).allow.reduce((hashMap, value) => { - hashMap[value] = true; - return hashMap; - }, Object.create(null)); - - const isWhitelisted = node => whitelistedHookNames[node.callee.name]; - - return { - CallExpression(node) { - if (isHook(node) && !isWhitelisted(node)) { - context.report({ - node, - messageId: 'unexpectedHook', - data: { hookName: node.callee.name }, - }); - } - }, - }; - }, -}; diff --git a/src/rules/no-hooks.ts b/src/rules/no-hooks.ts new file mode 100644 index 000000000..7f9e330b4 --- /dev/null +++ b/src/rules/no-hooks.ts @@ -0,0 +1,50 @@ +import { HookName, createRule, isHook } from './tsUtils'; + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Disallow setup and teardown hooks', + recommended: false, + }, + messages: { + unexpectedHook: "Unexpected '{{ hookName }}' hook", + }, + schema: [ + { + type: 'object', + properties: { + allow: { + type: 'array', + contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'], + }, + }, + additionalProperties: false, + }, + ], + type: 'suggestion', + }, + defaultOptions: [{ allow: [] } as { allow: readonly HookName[] }], + create(context, [{ allow }]) { + const whitelistedHookNames = allow.reduce((hashMap, value) => { + hashMap[value] = true; + return hashMap; + }, Object.create(null)); + + const isWhitelisted = (node: { callee: { name: string } }) => + whitelistedHookNames[node.callee.name]; + + return { + CallExpression(node) { + if (isHook(node) && !isWhitelisted(node)) { + context.report({ + node, + messageId: 'unexpectedHook', + data: { hookName: node.callee.name }, + }); + } + }, + }; + }, +}); diff --git a/src/rules/tsUtils.ts b/src/rules/tsUtils.ts index 7d143aad2..eef02d37a 100644 --- a/src/rules/tsUtils.ts +++ b/src/rules/tsUtils.ts @@ -95,7 +95,6 @@ export const isFunction = (node: TSESTree.Node): node is FunctionExpression => node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression; -/* istanbul ignore next */ export const isHook = ( node: TSESTree.CallExpression, ): node is JestFunctionCallExpressionWithIdentifierCallee => { diff --git a/src/rules/util.js b/src/rules/util.js index 97dd34162..8fe5ae664 100644 --- a/src/rules/util.js +++ b/src/rules/util.js @@ -101,13 +101,6 @@ const describeAliases = new Set(['describe', 'fdescribe', 'xdescribe']); const testCaseNames = new Set(['fit', 'it', 'test', 'xit', 'xtest']); -const testHookNames = new Set([ - 'beforeAll', - 'beforeEach', - 'afterAll', - 'afterEach', -]); - export const getNodeName = node => { function joinNames(a, b) { return a && b ? `${a}.${b}` : null; @@ -128,12 +121,6 @@ export const getNodeName = node => { return null; }; -export const isHook = node => - node && - node.type === 'CallExpression' && - node.callee.type === 'Identifier' && - testHookNames.has(node.callee.name); - export const isTestCase = node => node && node.type === 'CallExpression' &&