diff --git a/src/rules/__tests__/expect-expect.test.ts b/src/rules/__tests__/expect-expect.test.ts index 3ef7ed9a8..78b9ebbc8 100644 --- a/src/rules/__tests__/expect-expect.test.ts +++ b/src/rules/__tests__/expect-expect.test.ts @@ -17,6 +17,7 @@ ruleTester.run('expect-expect', rule, { 'it("should pass", () => expect(true).toBeDefined())', 'test("should pass", () => expect(true).toBeDefined())', 'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))', + 'it("should pass", myTest); function myTest() { expect(true).toBeDefined() }', { code: 'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })', @@ -50,6 +51,15 @@ ruleTester.run('expect-expect', rule, { }, ], }, + { + code: 'it("should fail", myTest); function myTest() {}', + errors: [ + { + messageId: 'noAssertions', + type: AST_NODE_TYPES.CallExpression, + }, + ], + }, { code: 'test("should fail", () => {});', errors: [ diff --git a/src/rules/__tests__/no-if.test.ts b/src/rules/__tests__/no-if.test.ts index fe4e13580..8f54de3fc 100644 --- a/src/rules/__tests__/no-if.test.ts +++ b/src/rules/__tests__/no-if.test.ts @@ -17,6 +17,9 @@ ruleTester.run('no-if', rule, { { code: `it('foo', () => {})`, }, + { + code: `it('foo', () => {}); function myTest() { if('bar') {} }`, + }, { code: `foo('bar', () => { if(baz) {} @@ -302,6 +305,14 @@ ruleTester.run('no-if', rule, { }, ], }, + { + code: `it('foo', myTest); function myTest() { if ('bar') {} }`, + errors: [ + { + messageId: 'noIf', + }, + ], + }, { code: `describe('foo', () => { it('bar', () => { diff --git a/src/rules/__tests__/no-test-return-statement.test.ts b/src/rules/__tests__/no-test-return-statement.test.ts index 7e10d132e..8d14933ab 100644 --- a/src/rules/__tests__/no-test-return-statement.test.ts +++ b/src/rules/__tests__/no-test-return-statement.test.ts @@ -23,6 +23,16 @@ ruleTester.run('no-test-prefixes', rule, { expect(1).toBe(1); }); `, + ` + it("one", myTest); + function myTest() { + expect(1).toBe(1); + } + `, + ` + it("one", () => expect(1).toBe(1)); + function myHelper() {} + `, ], invalid: [ { @@ -41,5 +51,14 @@ ruleTester.run('no-test-prefixes', rule, { `, errors: [{ messageId: 'noReturnValue', column: 9, line: 3 }], }, + { + code: ` + it("one", myTest); + function myTest () { + return expect(1).toBe(1); + } + `, + errors: [{ messageId: 'noReturnValue', column: 11, line: 4 }], + }, ], }); diff --git a/src/rules/__tests__/no-try-expect.test.ts b/src/rules/__tests__/no-try-expect.test.ts index 8c6ce8e37..008f9f230 100644 --- a/src/rules/__tests__/no-try-expect.test.ts +++ b/src/rules/__tests__/no-try-expect.test.ts @@ -14,17 +14,21 @@ ruleTester.run('no-try-catch', rule, { `it('foo', () => { expect('foo').toEqual('foo'); })`, + `it('foo', () => {}) + function myTest() { + try { + } catch { + } + }`, `it('foo', () => { expect('bar').toEqual('bar'); }); try { - } catch { expect('foo').toEqual('foo'); }`, `it.skip('foo'); try { - } catch { expect('foo').toEqual('foo'); }`, @@ -50,6 +54,22 @@ ruleTester.run('no-try-catch', rule, { }, ], }, + { + code: `it('foo', myTest) + function myTest() { + try { + + } catch (err) { + expect(err).toMatch('Error'); + } + } + `, + errors: [ + { + messageId: 'noTryExpect', + }, + ], + }, { code: `it('foo', async () => { await wrapper('production', async () => { diff --git a/src/rules/expect-expect.ts b/src/rules/expect-expect.ts index 80669947e..5dd5374ca 100644 --- a/src/rules/expect-expect.ts +++ b/src/rules/expect-expect.ts @@ -7,7 +7,12 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { TestCaseName, createRule, getNodeName } from './utils'; +import { + TestCaseName, + createRule, + getNodeName, + getTestCallExpressionsFromDeclaredVariables, +} from './utils'; export default createRule< [Partial<{ assertFunctionNames: readonly string[] }>], @@ -41,6 +46,29 @@ export default createRule< create(context, [{ assertFunctionNames = ['expect'] }]) { const unchecked: TSESTree.CallExpression[] = []; + function checkCallExpressionUsed(nodes: TSESTree.Node[]) { + for (const node of nodes) { + const index = + node.type === AST_NODE_TYPES.CallExpression + ? unchecked.indexOf(node) + : -1; + + if (node.type === AST_NODE_TYPES.FunctionDeclaration) { + const declaredVariables = context.getDeclaredVariables(node); + const testCallExpressions = getTestCallExpressionsFromDeclaredVariables( + declaredVariables, + ); + + checkCallExpressionUsed(testCallExpressions); + } + + if (index !== -1) { + unchecked.splice(index, 1); + break; + } + } + } + return { CallExpression(node) { const name = getNodeName(node.callee); @@ -48,17 +76,7 @@ export default createRule< unchecked.push(node); } else if (name && assertFunctionNames.includes(name)) { // Return early in case of nested `it` statements. - for (const ancestor of context.getAncestors()) { - const index = - ancestor.type === AST_NODE_TYPES.CallExpression - ? unchecked.indexOf(ancestor) - : -1; - - if (index !== -1) { - unchecked.splice(index, 1); - break; - } - } + checkCallExpressionUsed(context.getAncestors()); } }, 'Program:exit'() { diff --git a/src/rules/no-if.ts b/src/rules/no-if.ts index f511e7100..610bbb8d4 100644 --- a/src/rules/no-if.ts +++ b/src/rules/no-if.ts @@ -2,7 +2,13 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { TestCaseName, createRule, getNodeName, isTestCase } from './utils'; +import { + TestCaseName, + createRule, + getNodeName, + getTestCallExpressionsFromDeclaredVariables, + isTestCase, +} from './utils'; const testCaseNames = new Set([ ...Object.keys(TestCaseName), @@ -68,8 +74,13 @@ export default createRule({ FunctionExpression() { stack.push(false); }, - FunctionDeclaration() { - stack.push(false); + FunctionDeclaration(node) { + const declaredVariables = context.getDeclaredVariables(node); + const testCallExpressions = getTestCallExpressionsFromDeclaredVariables( + declaredVariables, + ); + + stack.push(testCallExpressions.length > 0); }, ArrowFunctionExpression(node) { stack.push(isTestArrowFunction(node)); diff --git a/src/rules/no-test-return-statement.ts b/src/rules/no-test-return-statement.ts index 8a0447a54..126d1af9a 100644 --- a/src/rules/no-test-return-statement.ts +++ b/src/rules/no-test-return-statement.ts @@ -2,7 +2,12 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { createRule, isFunction, isTestCase } from './utils'; +import { + createRule, + getTestCallExpressionsFromDeclaredVariables, + isFunction, + isTestCase, +} from './utils'; const getBody = (args: TSESTree.Expression[]) => { const [, secondArg] = args; @@ -43,6 +48,20 @@ export default createRule({ ); if (!returnStmt) return; + context.report({ messageId: 'noReturnValue', node: returnStmt }); + }, + FunctionDeclaration(node) { + const declaredVariables = context.getDeclaredVariables(node); + const testCallExpressions = getTestCallExpressionsFromDeclaredVariables( + declaredVariables, + ); + if (testCallExpressions.length === 0) return; + + const returnStmt = node.body.body.find( + t => t.type === AST_NODE_TYPES.ReturnStatement, + ); + if (!returnStmt) return; + context.report({ messageId: 'noReturnValue', node: returnStmt }); }, }; diff --git a/src/rules/no-try-expect.ts b/src/rules/no-try-expect.ts index efe85d328..b95c7f4e8 100644 --- a/src/rules/no-try-expect.ts +++ b/src/rules/no-try-expect.ts @@ -1,5 +1,10 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { createRule, isExpectCall, isTestCase } from './utils'; +import { + createRule, + getTestCallExpressionsFromDeclaredVariables, + isExpectCall, + isTestCase, +} from './utils'; export default createRule({ name: __filename, @@ -39,6 +44,16 @@ export default createRule({ }); } }, + FunctionDeclaration(node) { + const declaredVariables = context.getDeclaredVariables(node); + const testCallExpressions = getTestCallExpressionsFromDeclaredVariables( + declaredVariables, + ); + + if (testCallExpressions.length > 0) { + isTest = true; + } + }, CatchClause() { if (isTest) { ++catchDepth; @@ -54,6 +69,16 @@ export default createRule({ isTest = false; } }, + 'FunctionDeclaration:exit'(node) { + const declaredVariables = context.getDeclaredVariables(node); + const testCallExpressions = getTestCallExpressionsFromDeclaredVariables( + declaredVariables, + ); + + if (testCallExpressions.length > 0) { + isTest = false; + } + }, }; }, }); diff --git a/src/rules/utils.ts b/src/rules/utils.ts index 9cc2e7a03..46a7bd402 100644 --- a/src/rules/utils.ts +++ b/src/rules/utils.ts @@ -633,6 +633,27 @@ export const isHook = ( node.callee.type === AST_NODE_TYPES.Identifier && HookName.hasOwnProperty(node.callee.name); +export const getTestCallExpressionsFromDeclaredVariables = ( + declaredVaiables: TSESLint.Scope.Variable[], +): Array> => { + return declaredVaiables.reduce< + Array> + >( + (acc, { references }) => + acc.concat( + references + .map(({ identifier }) => identifier.parent) + .filter( + (node): node is JestFunctionCallExpression => + !!node && + node.type === AST_NODE_TYPES.CallExpression && + isTestCase(node), + ), + ), + [], + ); +}; + export const isTestCase = ( node: TSESTree.CallExpression, ): node is JestFunctionCallExpression =>