From ad187e2eea4901f5faf017919739c56b15e1dda7 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Mon, 22 Jul 2019 14:56:31 -0700 Subject: [PATCH 01/29] This probably doesn't work --- src/rules/no-expect-out-of-test.ts | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/rules/no-expect-out-of-test.ts diff --git a/src/rules/no-expect-out-of-test.ts b/src/rules/no-expect-out-of-test.ts new file mode 100644 index 000000000..f778a0745 --- /dev/null +++ b/src/rules/no-expect-out-of-test.ts @@ -0,0 +1,52 @@ +import { + AST_NODE_TYPES, + TSESTree, + } from '@typescript-eslint/experimental-utils'; + import { createRule, isExpectCall, isTestCase } from './tsUtils'; + + export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: + 'Prevents expects that are outside of an it or test block.', + recommended: false, + }, + messages: { + unexpectedExpect: `Expect must be inside of a test block.`, + }, + type: 'suggestion', + schema: [], + }, + defaultOptions: [], + create(context) { + let expectsOut = []; + + return { + 'Program:exit'() { + if (expectsOut.length > 0) { + for (const node of expectsOut) { + context.report({ node, messageId: 'unexpectedExpect' }); + } + } + }, + + CallExpression(node) { + if (isExpectCall(node)) { + let foundTestCase = false; + const parent = node.parent; + while(parent) { + if (isTestCase(parent)) { + foundTestCase = true; + break; + } + } + + if (!foundTestCase) expectsOut.push(node); + } + } + }; + }, + }); + \ No newline at end of file From 91561bd4d153d021a3d4e3476ccaf3f082de5155 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Mon, 22 Jul 2019 16:16:48 -0700 Subject: [PATCH 02/29] please commit --- .../__tests__/no-standalone-expect-test.js | 27 +++++++++ src/rules/no-expect-out-of-test.ts | 52 ---------------- src/rules/no-standalone-expect.ts | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 src/rules/__tests__/no-standalone-expect-test.js delete mode 100644 src/rules/no-expect-out-of-test.ts create mode 100644 src/rules/no-standalone-expect.ts diff --git a/src/rules/__tests__/no-standalone-expect-test.js b/src/rules/__tests__/no-standalone-expect-test.js new file mode 100644 index 000000000..2f35fe595 --- /dev/null +++ b/src/rules/__tests__/no-standalone-expect-test.js @@ -0,0 +1,27 @@ +import { RuleTester } from 'eslint'; +import rule from '../no-standalone-expect'; + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + }, +}); + +ruleTester.run('no-export-out-of-test', rule, { + valid: [ + 'describe("a test", () => { it("an it", () => {expect(1).toBe(1); }); });', + ], + invalid: [ + { + code: 'describe("a test", () => { expect(1).toBe(1); });', + parserOptions: { sourceType: 'module' }, + errors: [{ endColumn: 37, column: 28, messageId: 'unexpectedExpect' }], + }, + { + code: 'expect(1).toBe(1);', + parserOptions: { sourceType: 'module' }, + errors: [{ endColumn: 10, column: 1, messageId: 'unexpectedExpect' }], + }, + ], +}); diff --git a/src/rules/no-expect-out-of-test.ts b/src/rules/no-expect-out-of-test.ts deleted file mode 100644 index f778a0745..000000000 --- a/src/rules/no-expect-out-of-test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - AST_NODE_TYPES, - TSESTree, - } from '@typescript-eslint/experimental-utils'; - import { createRule, isExpectCall, isTestCase } from './tsUtils'; - - export default createRule({ - name: __filename, - meta: { - docs: { - category: 'Best Practices', - description: - 'Prevents expects that are outside of an it or test block.', - recommended: false, - }, - messages: { - unexpectedExpect: `Expect must be inside of a test block.`, - }, - type: 'suggestion', - schema: [], - }, - defaultOptions: [], - create(context) { - let expectsOut = []; - - return { - 'Program:exit'() { - if (expectsOut.length > 0) { - for (const node of expectsOut) { - context.report({ node, messageId: 'unexpectedExpect' }); - } - } - }, - - CallExpression(node) { - if (isExpectCall(node)) { - let foundTestCase = false; - const parent = node.parent; - while(parent) { - if (isTestCase(parent)) { - foundTestCase = true; - break; - } - } - - if (!foundTestCase) expectsOut.push(node); - } - } - }; - }, - }); - \ No newline at end of file diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts new file mode 100644 index 000000000..e85721ab4 --- /dev/null +++ b/src/rules/no-standalone-expect.ts @@ -0,0 +1,60 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import { createRule, isTestCase } from './tsUtils'; + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Prevents expects that are outside of an it or test block.', + recommended: false, + }, + messages: { + unexpectedExpect: `Expect must be inside of a test block.`, + }, + type: 'suggestion', + schema: [], + }, + defaultOptions: [], + create(context) { + const expectsOut: Array = []; + + const isExpectCall = (node: TSESTree.Node) => { + return ( + node && + node.type === AST_NODE_TYPES.CallExpression && + node.callee.type === AST_NODE_TYPES.Identifier && + node.callee.name === 'expect' + ); + }; + + return { + 'Program:exit'() { + if (expectsOut.length > 0) { + for (const node of expectsOut) { + context.report({ node, messageId: 'unexpectedExpect' }); + } + } + }, + + CallExpression(node) { + if (isExpectCall(node)) { + let foundTestCase = false; + let { parent } = node; + while (parent) { + if (parent.callee && isTestCase(parent)) { + foundTestCase = true; + break; + } + parent = parent.parent; + } + + if (!foundTestCase) expectsOut.push(node); + } + }, + }; + }, +}); From 8f9be1c5e0a8b3d8ff92954fc22d56bcc94ca0f0 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 10:12:34 -0700 Subject: [PATCH 03/29] Fixed lint errors --- .../__tests__/no-standalone-expect-test.js | 18 ++++- src/rules/no-standalone-expect.ts | 70 ++++++++++--------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.js b/src/rules/__tests__/no-standalone-expect-test.js index 2f35fe595..11a3c40ff 100644 --- a/src/rules/__tests__/no-standalone-expect-test.js +++ b/src/rules/__tests__/no-standalone-expect-test.js @@ -8,9 +8,13 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run('no-export-out-of-test', rule, { +ruleTester.run('no-standalone-expect', rule, { valid: [ 'describe("a test", () => { it("an it", () => {expect(1).toBe(1); }); });', + 'describe("a test", () => { it("an it", () => { const func = () => { expect(1).toBe(1); }; }); });', + 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; });', + 'describe("a test", () => { function func() { expect(1).toBe(1); }; });', + 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', ], invalid: [ { @@ -18,6 +22,18 @@ ruleTester.run('no-export-out-of-test', rule, { parserOptions: { sourceType: 'module' }, errors: [{ endColumn: 37, column: 28, messageId: 'unexpectedExpect' }], }, + { + code: + 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; expect(1).toBe(1); });', + parserOptions: { sourceType: 'module' }, + errors: [{ endColumn: 80, column: 71, messageId: 'unexpectedExpect' }], + }, + { + code: + 'describe("a test", () => { it(() => { expect(1).toBe(1); }); expect(1).toBe(1); });', + parserOptions: { sourceType: 'module' }, + errors: [{ endColumn: 72, column: 63, messageId: 'unexpectedExpect' }], + }, { code: 'expect(1).toBe(1);', parserOptions: { sourceType: 'module' }, diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index e85721ab4..cf27fb6fd 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -1,8 +1,26 @@ -import { - AST_NODE_TYPES, - TSESTree, -} from '@typescript-eslint/experimental-utils'; -import { createRule, isTestCase } from './tsUtils'; +import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; + +const getBlockType = (stmt: TSESTree.BlockStatement) => { + const func = stmt.parent; + // functionDeclaration: function func() {} + if (func && func.type === 'FunctionDeclaration') { + return 'function'; + } else if (func && isFunction(func) && func.parent) { + const expr = func.parent; + // arrowfunction or function expr + if (expr.type === 'VariableDeclarator') { + return 'function'; + // if it's not a variable, it will be callExpr, we only care about describe + } else if (isDescribe(expr as TSESTree.CallExpression)) { + return 'describe'; + } else { + return 'callExpr'; + } + } else { + return false; + } +}; export default createRule({ name: __filename, @@ -20,39 +38,27 @@ export default createRule({ }, defaultOptions: [], create(context) { - const expectsOut: Array = []; - - const isExpectCall = (node: TSESTree.Node) => { - return ( - node && - node.type === AST_NODE_TYPES.CallExpression && - node.callee.type === AST_NODE_TYPES.Identifier && - node.callee.name === 'expect' - ); - }; + const callStack: Array = []; return { - 'Program:exit'() { - if (expectsOut.length > 0) { - for (const node of expectsOut) { + CallExpression(node) { + if (isExpectCall(node)) { + const parent = callStack[callStack.length - 1]; + if (!parent || parent === 'describe') { context.report({ node, messageId: 'unexpectedExpect' }); } } }, - - CallExpression(node) { - if (isExpectCall(node)) { - let foundTestCase = false; - let { parent } = node; - while (parent) { - if (parent.callee && isTestCase(parent)) { - foundTestCase = true; - break; - } - parent = parent.parent; - } - - if (!foundTestCase) expectsOut.push(node); + BlockStatement(stmt) { + const blockType = getBlockType(stmt); + if (blockType) { + callStack.push(blockType); + } + }, + 'BlockStatement:exit'(stmt) { + const blockType = getBlockType(stmt); + if (blockType && blockType === callStack[callStack.length - 1]) { + callStack.pop(); } }, }; From 5bc1a4d5160d21fad89f4335bdcd820960baf971 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 10:13:20 -0700 Subject: [PATCH 04/29] Increased number of rules --- src/__tests__/rules.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/rules.test.js b/src/__tests__/rules.test.js index aa2882b6f..1167b20fb 100644 --- a/src/__tests__/rules.test.js +++ b/src/__tests__/rules.test.js @@ -3,7 +3,7 @@ import { resolve } from 'path'; import { rules } from '../'; const ruleNames = Object.keys(rules); -const numberOfRules = 36; +const numberOfRules = 37; describe('rules', () => { it('should have a corresponding doc for each rule', () => { From ba1b60fdc3ed2f49375bd139bd5b1bd4e1b04c3b Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 10:14:06 -0700 Subject: [PATCH 05/29] Added export to isExpectCall so I could use it in my rule --- src/rules/tsUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rules/tsUtils.ts b/src/rules/tsUtils.ts index 95f3c642c..ee98c131e 100644 --- a/src/rules/tsUtils.ts +++ b/src/rules/tsUtils.ts @@ -59,7 +59,9 @@ interface JestExpectNamespaceMemberExpression * * @return {node is JestExpectCallExpression} */ -const isExpectCall = (node: TSESTree.Node): node is JestExpectCallExpression => +export const isExpectCall = ( + node: TSESTree.Node, +): node is JestExpectCallExpression => node.type === AST_NODE_TYPES.CallExpression && isExpectIdentifier(node.callee); From a40edf71335264609ad7ce8eac1420803b304873 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 10:38:23 -0700 Subject: [PATCH 06/29] Added another test --- src/rules/__tests__/no-standalone-expect-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rules/__tests__/no-standalone-expect-test.js b/src/rules/__tests__/no-standalone-expect-test.js index 11a3c40ff..fc10340d9 100644 --- a/src/rules/__tests__/no-standalone-expect-test.js +++ b/src/rules/__tests__/no-standalone-expect-test.js @@ -15,6 +15,7 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; });', 'describe("a test", () => { function func() { expect(1).toBe(1); }; });', 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', + 'const func = function(){ expect(1).toBe(1); };', ], invalid: [ { From 0b68d600838c1dc750ed45cef1931ac402448402 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 10:38:59 -0700 Subject: [PATCH 07/29] Added Docs --- docs/rules/no-standalone-expect.md | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/rules/no-standalone-expect.md diff --git a/docs/rules/no-standalone-expect.md b/docs/rules/no-standalone-expect.md new file mode 100644 index 000000000..0ece66b09 --- /dev/null +++ b/docs/rules/no-standalone-expect.md @@ -0,0 +1,60 @@ +# No standalone expect in a describe block (no-standalone-expect) + +Prevents expects outside of a test or it block. An expect within a helper +function (but outside of a test or it block) will not trigger this rule. + +## Rule Details + +This rule aims to eliminate expects that will not be executed. An expect inside +of a describe block but outside of a test or it block or outside of a describe +will not execute and therefore will trigger this rule. It is viable, however, to +have an expect in a helper function that is called from within a test or it +block so expects in a function will not trigger this rule. + +Examples of **incorrect** code for this rule: + +```js +// in describe +describe('a test', () => { + expect(1).toBe(1); +}); + +// below other tests +describe('a test', () => { + it('an it', () => { + expect(1).toBe(1); + }); + + expect(1).toBe(1); +}); +``` + +Examples of **correct** code for this rule: + +```js +// in it block +describe('a test', () => { + it('an it', () => { + expect(1).toBe(1); + }); +}); + +// in helper function +describe('a test', () => { + const helper = () => { + expect(1).toBe(1); + }; + + it('an it', () => { + helper(); + }); +}); +``` + +\*Note that this rule will not trigger if the helper function is never used even +thought the expect will not execute. Rely on a rule like no-unused-vars for this +case. + +## When Not To Use It + +Don't use this rule on non-jest test files. From f5d7fcedd04c335b8e0568d2175f0e3e8130a2cb Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 13:52:51 -0700 Subject: [PATCH 08/29] Resolving PR comments --- ...t-test.js => no-standalone-expect-test.ts} | 4 ++-- src/rules/no-standalone-expect.ts | 21 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) rename src/rules/__tests__/{no-standalone-expect-test.js => no-standalone-expect-test.ts} (93%) diff --git a/src/rules/__tests__/no-standalone-expect-test.js b/src/rules/__tests__/no-standalone-expect-test.ts similarity index 93% rename from src/rules/__tests__/no-standalone-expect-test.js rename to src/rules/__tests__/no-standalone-expect-test.ts index fc10340d9..6fd7035b3 100644 --- a/src/rules/__tests__/no-standalone-expect-test.js +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -1,7 +1,7 @@ -import { RuleTester } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../no-standalone-expect'; -const ruleTester = new RuleTester({ +const ruleTester = new TSESLint.RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module', diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index cf27fb6fd..2726272cc 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -1,25 +1,26 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; -import { TSESTree } from '@typescript-eslint/experimental-utils'; const getBlockType = (stmt: TSESTree.BlockStatement) => { const func = stmt.parent; // functionDeclaration: function func() {} - if (func && func.type === 'FunctionDeclaration') { + if (func && func.type === AST_NODE_TYPES.FunctionDeclaration) { return 'function'; } else if (func && isFunction(func) && func.parent) { const expr = func.parent; // arrowfunction or function expr - if (expr.type === 'VariableDeclarator') { + if (expr.type === AST_NODE_TYPES.VariableDeclarator) { return 'function'; // if it's not a variable, it will be callExpr, we only care about describe - } else if (isDescribe(expr as TSESTree.CallExpression)) { + } else if (expr.type === 'CallExpression' && isDescribe(expr)) { return 'describe'; - } else { - return 'callExpr'; } - } else { - return false; + return 'callExpr'; } + return false; }; export default createRule({ @@ -31,14 +32,14 @@ export default createRule({ recommended: false, }, messages: { - unexpectedExpect: `Expect must be inside of a test block.`, + unexpectedExpect: 'Expect must be inside of a test block.', }, type: 'suggestion', schema: [], }, defaultOptions: [], create(context) { - const callStack: Array = []; + const callStack: String[] = []; return { CallExpression(node) { From e2f0f5a73e284ccdfb6b76b9ffeb71d886ddaaf2 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Wed, 24 Jul 2019 16:40:04 -0700 Subject: [PATCH 09/29] fix: added test cases and updated docs to address expect.hasAssertions --- docs/rules/no-standalone-expect.md | 7 +++++++ src/rules/__tests__/no-standalone-expect-test.ts | 9 +++++---- src/rules/no-standalone-expect.ts | 5 ++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/rules/no-standalone-expect.md b/docs/rules/no-standalone-expect.md index 0ece66b09..38fddb0ab 100644 --- a/docs/rules/no-standalone-expect.md +++ b/docs/rules/no-standalone-expect.md @@ -11,6 +11,9 @@ will not execute and therefore will trigger this rule. It is viable, however, to have an expect in a helper function that is called from within a test or it block so expects in a function will not trigger this rule. +Statements like `expect.hasAssertions()` will NOT trigger this rule since these +calls will execute if they are not in a test block. + Examples of **incorrect** code for this rule: ```js @@ -49,6 +52,10 @@ describe('a test', () => { helper(); }); }); + +describe('a test', () => { + expect.hasAssertions(1); +}); ``` \*Note that this rule will not trigger if the helper function is never used even diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 6fd7035b3..e4cdac4a2 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -16,28 +16,29 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { function func() { expect(1).toBe(1); }; });', 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', 'const func = function(){ expect(1).toBe(1); };', + 'expect.hasAssertions()', ], invalid: [ { code: 'describe("a test", () => { expect(1).toBe(1); });', - parserOptions: { sourceType: 'module' }, errors: [{ endColumn: 37, column: 28, messageId: 'unexpectedExpect' }], }, { code: 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; expect(1).toBe(1); });', - parserOptions: { sourceType: 'module' }, errors: [{ endColumn: 80, column: 71, messageId: 'unexpectedExpect' }], }, { code: 'describe("a test", () => { it(() => { expect(1).toBe(1); }); expect(1).toBe(1); });', - parserOptions: { sourceType: 'module' }, errors: [{ endColumn: 72, column: 63, messageId: 'unexpectedExpect' }], }, { code: 'expect(1).toBe(1);', - parserOptions: { sourceType: 'module' }, + errors: [{ endColumn: 10, column: 1, messageId: 'unexpectedExpect' }], + }, + { + code: 'expect(1).toBe', errors: [{ endColumn: 10, column: 1, messageId: 'unexpectedExpect' }], }, ], diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 2726272cc..731efdad1 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -15,7 +15,10 @@ const getBlockType = (stmt: TSESTree.BlockStatement) => { if (expr.type === AST_NODE_TYPES.VariableDeclarator) { return 'function'; // if it's not a variable, it will be callExpr, we only care about describe - } else if (expr.type === 'CallExpression' && isDescribe(expr)) { + } else if ( + expr.type === AST_NODE_TYPES.CallExpression && + isDescribe(expr) + ) { return 'describe'; } return 'callExpr'; From 557e9a7ed6553e542357b2cc5c04e98f3ec6ac25 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 11:08:02 -0700 Subject: [PATCH 10/29] fix: added some more typescript, removed sourcetype etc --- src/rules/__tests__/no-standalone-expect-test.ts | 1 - src/rules/no-standalone-expect.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index e4cdac4a2..7f39b4773 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -4,7 +4,6 @@ import rule from '../no-standalone-expect'; const ruleTester = new TSESLint.RuleTester({ parserOptions: { ecmaVersion: 2015, - sourceType: 'module', }, }); diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 731efdad1..119508537 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -53,13 +53,13 @@ export default createRule({ } } }, - BlockStatement(stmt) { + BlockStatement(stmt: TSESTree.BlockStatement) { const blockType = getBlockType(stmt); if (blockType) { callStack.push(blockType); } }, - 'BlockStatement:exit'(stmt) { + 'BlockStatement:exit'(stmt: TSESTree.BlockStatement) { const blockType = getBlockType(stmt); if (blockType && blockType === callStack[callStack.length - 1]) { callStack.pop(); From 306b0efd5d23beba085b87b55c4f64dbad3dafe5 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 11:34:44 -0700 Subject: [PATCH 11/29] fix: couple more trivial edits --- docs/rules/no-standalone-expect.md | 20 +++++++++++--------- src/rules/no-standalone-expect.ts | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/rules/no-standalone-expect.md b/docs/rules/no-standalone-expect.md index 38fddb0ab..025f8c86c 100644 --- a/docs/rules/no-standalone-expect.md +++ b/docs/rules/no-standalone-expect.md @@ -1,15 +1,17 @@ # No standalone expect in a describe block (no-standalone-expect) -Prevents expects outside of a test or it block. An expect within a helper -function (but outside of a test or it block) will not trigger this rule. +Prevents `expect` statements outside of a `test` or `it` block. An `expect` +within a helper function (but outside of a `test` or `it` block) will not +trigger this rule. ## Rule Details -This rule aims to eliminate expects that will not be executed. An expect inside -of a describe block but outside of a test or it block or outside of a describe -will not execute and therefore will trigger this rule. It is viable, however, to -have an expect in a helper function that is called from within a test or it -block so expects in a function will not trigger this rule. +This rule aims to eliminate `expect` statements that will not be executed. An +`expect` inside of a `describe` block but outside of a `test` or `it` block or +outside of a `describe` will not execute and therefore will trigger this rule. +It is viable, however, to have an `expect` in a helper function that is called +from within a `test` or `it` block so `expect` statements in a function will not +trigger this rule. Statements like `expect.hasAssertions()` will NOT trigger this rule since these calls will execute if they are not in a test block. @@ -59,8 +61,8 @@ describe('a test', () => { ``` \*Note that this rule will not trigger if the helper function is never used even -thought the expect will not execute. Rely on a rule like no-unused-vars for this -case. +thought the `expect` will not execute. Rely on a rule like no-unused-vars for +this case. ## When Not To Use It diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 119508537..f2187ae04 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -4,7 +4,7 @@ import { } from '@typescript-eslint/experimental-utils'; import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; -const getBlockType = (stmt: TSESTree.BlockStatement) => { +const getBlockType = (stmt: TSESTree.BlockStatement): String | false => { const func = stmt.parent; // functionDeclaration: function func() {} if (func && func.type === AST_NODE_TYPES.FunctionDeclaration) { From 70d3a06d63db5c6947fbf938cf5aa33a60ba3b7d Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 25 Jul 2019 21:05:48 +0200 Subject: [PATCH 12/29] chore: fix lint --- src/rules/no-standalone-expect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index f2187ae04..3a8087603 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -4,7 +4,7 @@ import { } from '@typescript-eslint/experimental-utils'; import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; -const getBlockType = (stmt: TSESTree.BlockStatement): String | false => { +const getBlockType = (stmt: TSESTree.BlockStatement): string | false => { const func = stmt.parent; // functionDeclaration: function func() {} if (func && func.type === AST_NODE_TYPES.FunctionDeclaration) { @@ -42,7 +42,7 @@ export default createRule({ }, defaultOptions: [], create(context) { - const callStack: String[] = []; + const callStack: string[] = []; return { CallExpression(node) { From 8aec8c2f29e37378190b90005ed18aac10515374 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 25 Jul 2019 21:09:06 +0200 Subject: [PATCH 13/29] chore: narrow type def and ditch extra else --- src/rules/no-standalone-expect.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 3a8087603..74063aa74 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -4,21 +4,26 @@ import { } from '@typescript-eslint/experimental-utils'; import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; -const getBlockType = (stmt: TSESTree.BlockStatement): string | false => { +const getBlockType = ( + stmt: TSESTree.BlockStatement, +): 'function' | 'describe' | 'callExpr' | false => { const func = stmt.parent; + + if (!func) { + return false; + } // functionDeclaration: function func() {} - if (func && func.type === AST_NODE_TYPES.FunctionDeclaration) { + if (func.type === AST_NODE_TYPES.FunctionDeclaration) { return 'function'; - } else if (func && isFunction(func) && func.parent) { + } + if (isFunction(func) && func.parent) { const expr = func.parent; // arrowfunction or function expr if (expr.type === AST_NODE_TYPES.VariableDeclarator) { return 'function'; - // if it's not a variable, it will be callExpr, we only care about describe - } else if ( - expr.type === AST_NODE_TYPES.CallExpression && - isDescribe(expr) - ) { + } + // if it's not a variable, it will be callExpr, we only care about describe + if (expr.type === AST_NODE_TYPES.CallExpression && isDescribe(expr)) { return 'describe'; } return 'callExpr'; @@ -53,7 +58,7 @@ export default createRule({ } } }, - BlockStatement(stmt: TSESTree.BlockStatement) { + BlockStatement(stmt) { const blockType = getBlockType(stmt); if (blockType) { callStack.push(blockType); From 7db9a6f4d6c2ffcb082a31e1332daf7adb1043d7 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 25 Jul 2019 21:18:23 +0200 Subject: [PATCH 14/29] chore: add rule to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 171dc723e..3c6e9616b 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ installations requiring long-term consistency. | [no-jest-import][] | Disallow importing `jest` | ![recommended][] | | | [no-large-snapshots][] | Disallow large snapshots | | | | [no-mocks-import][] | Disallow manually importing from `__mocks__` | | | +| [no-standalone-expect][] | Prevents `expect` statements outside of a `test` or `it` block | | | | [no-test-callback][] | Using a callback in asynchronous tests | | ![fixable-green][] | | [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | ![recommended][] | ![fixable-green][] | | [no-test-return-statement][] | Disallow explicitly returning from tests | | | @@ -174,6 +175,7 @@ https://github.com/dangreenisrael/eslint-plugin-jest-formatting [no-jest-import]: docs/rules/no-jest-import.md [no-large-snapshots]: docs/rules/no-large-snapshots.md [no-mocks-import]: docs/rules/no-mocks-import.md +[no-standalone-expect]: docs/rules/no-standalone-expect.md [no-test-callback]: docs/rules/no-test-callback.md [no-test-prefixes]: docs/rules/no-test-prefixes.md [no-test-return-statement]: docs/rules/no-test-return-statement.md From 9656d908b7a2526ac01e52652948d2cb16b9f0c2 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 12:48:28 -0700 Subject: [PATCH 15/29] fix: added more tests --- src/rules/__tests__/no-standalone-expect-test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 7f39b4773..828f780d3 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -16,6 +16,7 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', 'const func = function(){ expect(1).toBe(1); };', 'expect.hasAssertions()', + '{}', ], invalid: [ { @@ -40,5 +41,9 @@ ruleTester.run('no-standalone-expect', rule, { code: 'expect(1).toBe', errors: [{ endColumn: 10, column: 1, messageId: 'unexpectedExpect' }], }, + { + code: '{expect(1).toBe(1)}', + errors: [{ endColumn: 11, column: 2, messageId: 'unexpectedExpect' }], + }, ], }); From a6f6253ec9b76fd3e6ea831f107c7184fbab29e1 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 13:36:37 -0700 Subject: [PATCH 16/29] fix: refactored code to handle es6 functions without block statements --- .../__tests__/no-standalone-expect-test.ts | 1 + src/rules/no-standalone-expect.ts | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 828f780d3..f8080dcc5 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -15,6 +15,7 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { function func() { expect(1).toBe(1); }; });', 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', 'const func = function(){ expect(1).toBe(1); };', + 'it("an it", () => expect(1).toBe(1))', 'expect.hasAssertions()', '{}', ], diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 74063aa74..5221f5b08 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -2,7 +2,13 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { createRule, isDescribe, isExpectCall, isFunction } from './tsUtils'; +import { + createRule, + isDescribe, + isExpectCall, + isFunction, + isTestCase, +} from './tsUtils'; const getBlockType = ( stmt: TSESTree.BlockStatement, @@ -26,7 +32,7 @@ const getBlockType = ( if (expr.type === AST_NODE_TYPES.CallExpression && isDescribe(expr)) { return 'describe'; } - return 'callExpr'; + return false; } return false; }; @@ -57,6 +63,14 @@ export default createRule({ context.report({ node, messageId: 'unexpectedExpect' }); } } + if (isTestCase(node)) { + callStack.push('test'); + } + }, + 'CallExpression:exit'(node) { + if (isTestCase(node) && callStack[callStack.length - 1] === 'test') { + callStack.pop(); + } }, BlockStatement(stmt) { const blockType = getBlockType(stmt); From 0a693deb77b53316cf150234c8e8637b5c1a95a6 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 13:48:05 -0700 Subject: [PATCH 17/29] fix: added error for block statemnet with not parent --- src/rules/__tests__/no-standalone-expect-test.ts | 2 +- src/rules/no-standalone-expect.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index f8080dcc5..22204431a 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -14,8 +14,8 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; });', 'describe("a test", () => { function func() { expect(1).toBe(1); }; });', 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', - 'const func = function(){ expect(1).toBe(1); };', 'it("an it", () => expect(1).toBe(1))', + 'const func = function(){ expect(1).toBe(1); };', 'expect.hasAssertions()', '{}', ], diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 5221f5b08..2f2bdab04 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -15,8 +15,12 @@ const getBlockType = ( ): 'function' | 'describe' | 'callExpr' | false => { const func = stmt.parent; + /* istanbul ignore if */ + if (!func) { - return false; + throw new Error( + `Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`, + ); } // functionDeclaration: function func() {} if (func.type === AST_NODE_TYPES.FunctionDeclaration) { From 0ef09d8fa849c83bd7b6aaaa4422653d0605ac70 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 13:58:13 -0700 Subject: [PATCH 18/29] chore: added another test case --- src/rules/__tests__/no-standalone-expect-test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 22204431a..898bea915 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -24,6 +24,10 @@ ruleTester.run('no-standalone-expect', rule, { code: 'describe("a test", () => { expect(1).toBe(1); });', errors: [{ endColumn: 37, column: 28, messageId: 'unexpectedExpect' }], }, + { + code: 'describe("a test", () => expect(1).toBe(1));', + errors: [{ endColumn: 35, column: 26, messageId: 'unexpectedExpect' }], + }, { code: 'describe("a test", () => { const func = () => { expect(1).toBe(1); }; expect(1).toBe(1); });', From ff3e800f46c87919837b7b8acb048198354d7ea0 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 14:06:42 -0700 Subject: [PATCH 19/29] fix: fixed the bug for arrowfunctionsgit statusgit status --- src/rules/__tests__/no-standalone-expect-test.ts | 1 + src/rules/no-standalone-expect.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 898bea915..f9b2626ef 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -16,6 +16,7 @@ ruleTester.run('no-standalone-expect', rule, { 'describe("a test", () => { const func = function(){ expect(1).toBe(1); }; });', 'it("an it", () => expect(1).toBe(1))', 'const func = function(){ expect(1).toBe(1); };', + 'const func = () => expect(1).toBe(1);', 'expect.hasAssertions()', '{}', ], diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 2f2bdab04..adc277cdb 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -88,6 +88,16 @@ export default createRule({ callStack.pop(); } }, + ArrowFunctionExpression(node) { + if (node.parent && node.parent.type !== AST_NODE_TYPES.CallExpression) { + callStack.push('arrowFunc'); + } + }, + 'ArrowFunctionExpression:exit'(node) { + if (callStack[callStack.length - 1] === 'arrowFunc') { + callStack.pop(); + } + }, }; }, }); From 9278be41485435d10f27efa9a396f6f044b7700c Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 14:10:17 -0700 Subject: [PATCH 20/29] fix: added type for callexpression exit --- src/rules/no-standalone-expect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index adc277cdb..60f3e5215 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -71,7 +71,7 @@ export default createRule({ callStack.push('test'); } }, - 'CallExpression:exit'(node) { + 'CallExpression:exit'(node: TSESTree.CallExpression) { if (isTestCase(node) && callStack[callStack.length - 1] === 'test') { callStack.pop(); } @@ -93,7 +93,7 @@ export default createRule({ callStack.push('arrowFunc'); } }, - 'ArrowFunctionExpression:exit'(node) { + 'ArrowFunctionExpression:exit'() { if (callStack[callStack.length - 1] === 'arrowFunc') { callStack.pop(); } From 1f4a8386750832dd6a72746553b51a16aae04cb9 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 25 Jul 2019 23:25:47 +0200 Subject: [PATCH 21/29] chore: tighten type --- src/rules/no-standalone-expect.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 60f3e5215..cf8b9c8cd 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -12,11 +12,10 @@ import { const getBlockType = ( stmt: TSESTree.BlockStatement, -): 'function' | 'describe' | 'callExpr' | false => { +): 'function' | 'describe' | false => { const func = stmt.parent; /* istanbul ignore if */ - if (!func) { throw new Error( `Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`, @@ -57,7 +56,7 @@ export default createRule({ }, defaultOptions: [], create(context) { - const callStack: string[] = []; + const callStack: Array<'test' | 'function' | 'describe' | 'arrowFunc'> = []; return { CallExpression(node) { @@ -66,6 +65,7 @@ export default createRule({ if (!parent || parent === 'describe') { context.report({ node, messageId: 'unexpectedExpect' }); } + return; } if (isTestCase(node)) { callStack.push('test'); From 91b9f2fb1373171938272cf3c964142c7a3d1c7c Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 25 Jul 2019 23:29:59 +0200 Subject: [PATCH 22/29] chore: return null over false --- src/rules/no-standalone-expect.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index cf8b9c8cd..abe4b3930 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -12,7 +12,7 @@ import { const getBlockType = ( stmt: TSESTree.BlockStatement, -): 'function' | 'describe' | false => { +): 'function' | 'describe' | null => { const func = stmt.parent; /* istanbul ignore if */ @@ -35,9 +35,8 @@ const getBlockType = ( if (expr.type === AST_NODE_TYPES.CallExpression && isDescribe(expr)) { return 'describe'; } - return false; } - return false; + return null; }; export default createRule({ From 0fd6c7e947e7e0ca9c59d1b8c3443bccdc7a3d2c Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 14:30:38 -0700 Subject: [PATCH 23/29] fix: false -> null, removed extra return --- src/rules/no-standalone-expect.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index cf8b9c8cd..abe4b3930 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -12,7 +12,7 @@ import { const getBlockType = ( stmt: TSESTree.BlockStatement, -): 'function' | 'describe' | false => { +): 'function' | 'describe' | null => { const func = stmt.parent; /* istanbul ignore if */ @@ -35,9 +35,8 @@ const getBlockType = ( if (expr.type === AST_NODE_TYPES.CallExpression && isDescribe(expr)) { return 'describe'; } - return false; } - return false; + return null; }; export default createRule({ From a6ab052c3bf73cc88e18be97c125695ac96f9290 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 18:14:18 -0700 Subject: [PATCH 24/29] fix: it.each now works --- .../__tests__/no-standalone-expect-test.ts | 13 +++++++++++++ src/rules/no-standalone-expect.ts | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index f9b2626ef..fb7e6c740 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -19,6 +19,9 @@ ruleTester.run('no-standalone-expect', rule, { 'const func = () => expect(1).toBe(1);', 'expect.hasAssertions()', '{}', + 'it.each([1, true])("trues", value => { expect(value).toBe(true); });', + 'it.each([1, true])("trues", value => { expect(value).toBe(true); }); it("an it", () => { expect(1).toBe(1) });', + 'it.only("an only", value => { expect(value).toBe(true); });', ], invalid: [ { @@ -51,5 +54,15 @@ ruleTester.run('no-standalone-expect', rule, { code: '{expect(1).toBe(1)}', errors: [{ endColumn: 11, column: 2, messageId: 'unexpectedExpect' }], }, + { + code: + 'it.each([1, true])("trues", value => { expect(value).toBe(true); }); expect(1).toBe(1);', + errors: [{ endColumn: 79, column: 70, messageId: 'unexpectedExpect' }], + }, + { + code: + 'describe.each([1, true])("trues", value => { expect(value).toBe(true); });', + errors: [{ endColumn: 59, column: 46, messageId: 'unexpectedExpect' }], + }, ], }); diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index abe4b3930..4d0c13803 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -39,6 +39,19 @@ const getBlockType = ( return null; }; +const isEach = (node: TSESTree.CallExpression): boolean => { + if ( + node && + node.callee && + node.callee.callee && + node.callee.callee.property && + node.callee.callee.property.name === 'each' + ) { + return true; + } + return false; +}; + export default createRule({ name: __filename, meta: { @@ -71,7 +84,11 @@ export default createRule({ } }, 'CallExpression:exit'(node: TSESTree.CallExpression) { - if (isTestCase(node) && callStack[callStack.length - 1] === 'test') { + if ( + (isTestCase(node) && + node.callee.type !== AST_NODE_TYPES.MemberExpression) || + isEach(node) + ) { callStack.pop(); } }, From e9b48bacda52b1a58fbe2eac465360f3377062bf Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Thu, 25 Jul 2019 19:20:41 -0700 Subject: [PATCH 25/29] fix: made sure describe.each also works --- src/rules/__tests__/no-standalone-expect-test.ts | 1 + src/rules/no-standalone-expect.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index fb7e6c740..84d1f1f79 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -22,6 +22,7 @@ ruleTester.run('no-standalone-expect', rule, { 'it.each([1, true])("trues", value => { expect(value).toBe(true); });', 'it.each([1, true])("trues", value => { expect(value).toBe(true); }); it("an it", () => { expect(1).toBe(1) });', 'it.only("an only", value => { expect(value).toBe(true); });', + 'describe.each([1, true])("trues", value => { it("an it", () => expect(value).toBe(true) ); });', ], invalid: [ { diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 4d0c13803..e1bc767e1 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -3,6 +3,7 @@ import { TSESTree, } from '@typescript-eslint/experimental-utils'; import { + TestCaseName, createRule, isDescribe, isExpectCall, @@ -45,7 +46,9 @@ const isEach = (node: TSESTree.CallExpression): boolean => { node.callee && node.callee.callee && node.callee.callee.property && - node.callee.callee.property.name === 'each' + node.callee.callee.property.name === 'each' && + node.callee.callee.object && + TestCaseName.hasOwnProperty(node.callee.callee.object.name) ) { return true; } From e8fa3e2cd3f2522d7560b77d008fc35c85c85d8b Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 26 Jul 2019 10:57:08 +0200 Subject: [PATCH 26/29] chore: fix type error --- src/rules/no-standalone-expect.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index e1bc767e1..d7141a808 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -44,10 +44,14 @@ const isEach = (node: TSESTree.CallExpression): boolean => { if ( node && node.callee && + node.callee.type === AST_NODE_TYPES.CallExpression && node.callee.callee && + node.callee.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.callee.property && + node.callee.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.callee.property.name === 'each' && node.callee.callee.object && + node.callee.callee.object.type === AST_NODE_TYPES.Identifier && TestCaseName.hasOwnProperty(node.callee.callee.object.name) ) { return true; @@ -55,6 +59,8 @@ const isEach = (node: TSESTree.CallExpression): boolean => { return false; }; +type callStackEntry = 'test' | 'function' | 'describe' | 'arrowFunc'; + export default createRule({ name: __filename, meta: { @@ -71,7 +77,7 @@ export default createRule({ }, defaultOptions: [], create(context) { - const callStack: Array<'test' | 'function' | 'describe' | 'arrowFunc'> = []; + const callStack: callStackEntry[] = []; return { CallExpression(node) { From 6231ed2bde0a2302f78905a15dbb4ec63059f21b Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 26 Jul 2019 11:03:57 +0200 Subject: [PATCH 27/29] chore: add failing test --- src/rules/__tests__/no-standalone-expect-test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rules/__tests__/no-standalone-expect-test.ts b/src/rules/__tests__/no-standalone-expect-test.ts index 84d1f1f79..b8543e069 100644 --- a/src/rules/__tests__/no-standalone-expect-test.ts +++ b/src/rules/__tests__/no-standalone-expect-test.ts @@ -21,6 +21,14 @@ ruleTester.run('no-standalone-expect', rule, { '{}', 'it.each([1, true])("trues", value => { expect(value).toBe(true); });', 'it.each([1, true])("trues", value => { expect(value).toBe(true); }); it("an it", () => { expect(1).toBe(1) });', + ` + it.each\` + num | value + \${1} | \${true} + \`('trues', ({ value }) => { + expect(value).toBe(true); + }); + `, 'it.only("an only", value => { expect(value).toBe(true); });', 'describe.each([1, true])("trues", value => { it("an it", () => expect(value).toBe(true) ); });', ], From 00ec789725083eed81258d2dde44de9201212d03 Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Fri, 26 Jul 2019 10:25:12 -0700 Subject: [PATCH 28/29] fix: added functionality for tagged template expressions --- src/rules/no-standalone-expect.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index d7141a808..97efc1777 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -59,7 +59,12 @@ const isEach = (node: TSESTree.CallExpression): boolean => { return false; }; -type callStackEntry = 'test' | 'function' | 'describe' | 'arrowFunc'; +type callStackEntry = + | 'test' + | 'function' + | 'describe' + | 'arrowFunc' + | 'template'; export default createRule({ name: __filename, @@ -93,10 +98,12 @@ export default createRule({ } }, 'CallExpression:exit'(node: TSESTree.CallExpression) { + const top = callStack[callStack.length - 1]; if ( - (isTestCase(node) && + ((isTestCase(node) && node.callee.type !== AST_NODE_TYPES.MemberExpression) || - isEach(node) + isEach(node)) && + top === 'test' ) { callStack.pop(); } @@ -123,6 +130,14 @@ export default createRule({ callStack.pop(); } }, + 'CallExpression > TaggedTemplateExpression'() { + if (callStack[callStack.length - 1] === 'template') { + callStack.pop(); + } + }, + 'CallExpression > TaggedTemplateExpression:exit'() { + callStack.push('template'); + }, }; }, }); From e662eed31e601eba43abc37af869059f313427ca Mon Sep 17 00:00:00 2001 From: Rachel Fisher Date: Fri, 26 Jul 2019 10:36:10 -0700 Subject: [PATCH 29/29] fix: taggedtemplate expressions have to be in the callExpression --- src/rules/no-standalone-expect.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/rules/no-standalone-expect.ts b/src/rules/no-standalone-expect.ts index 97efc1777..d753e5562 100644 --- a/src/rules/no-standalone-expect.ts +++ b/src/rules/no-standalone-expect.ts @@ -96,14 +96,19 @@ export default createRule({ if (isTestCase(node)) { callStack.push('test'); } + if (node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression) { + callStack.push('template'); + } }, 'CallExpression:exit'(node: TSESTree.CallExpression) { const top = callStack[callStack.length - 1]; if ( - ((isTestCase(node) && + (((isTestCase(node) && node.callee.type !== AST_NODE_TYPES.MemberExpression) || isEach(node)) && - top === 'test' + top === 'test') || + (node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression && + top === 'template') ) { callStack.pop(); } @@ -130,14 +135,6 @@ export default createRule({ callStack.pop(); } }, - 'CallExpression > TaggedTemplateExpression'() { - if (callStack[callStack.length - 1] === 'template') { - callStack.pop(); - } - }, - 'CallExpression > TaggedTemplateExpression:exit'() { - callStack.push('template'); - }, }; }, });