Repository: lo1tuma/eslint-plugin-mocha (ts)

Issue:
no-setup-in-describe function expression false positives
no-setup-in-describe thinks that the bodies of function expressions are executed as part of the describe block:

```javascript
describe('description', function () {
  const x = {};

  const foo = function () {
    // mocha/no-setup-in-describe: Unexpected member expression in describe block. Member
    // expressions may call functions via getters
    x.a = 'foo';
  };

  function bar() {
    x.a = 'bar'; // OK
  }

  const baz = () => {
    x.a = 'baz'; // OK
  };

  it('example', async function () {
    foo();
    bar();
    baz();
  });
});
```

Versions:
* eslint-plugin-mocha 8.0.0
* eslint 7.13.0
* mocha 8.2.1
* node 14.14.0


Pull request description:
Fix false positive in no-setup-in-describe
Fixes #274

Patch:
diff --git a/benchmarks/runtime.bench.js b/benchmarks/runtime.bench.js
index 293be26..402a005 100644
--- a/benchmarks/runtime.bench.js
+++ b/benchmarks/runtime.bench.js
@@ -92,7 +92,7 @@ function lintManyFilesWithAllRecommendedRules({ numberOfFiles }) {
 describe('runtime', () => {
     it('should not take longer as the defined budget to lint many files with the recommended config', () => {
         const nodeVersionMultiplier = getNodeVersionMultiplier();
-        const budget = 3750000 / cpuSpeed * nodeVersionMultiplier;
+        const budget = 3500000 / cpuSpeed * nodeVersionMultiplier;
 
         const { medianDuration } = runBenchmark(() => {
             lintManyFilesWithAllRecommendedRules({ numberOfFiles: 350 });
@@ -102,7 +102,7 @@ describe('runtime', () => {
     });
 
     it('should not consume more memory as the defined budget to lint many files with the recommended config', () => {
-        const budget = 2750000;
+        const budget = 2500000;
 
         const { medianMemory } = runBenchmark(() => {
             lintManyFilesWithAllRecommendedRules({ numberOfFiles: 350 });
diff --git a/lib/rules/no-setup-in-describe.js b/lib/rules/no-setup-in-describe.js
index cffc84f..0d0f097 100644
--- a/lib/rules/no-setup-in-describe.js
+++ b/lib/rules/no-setup-in-describe.js
@@ -107,6 +107,17 @@ module.exports = {
                 }
             },
 
+            FunctionExpression(node) {
+                if (nesting.length && !isParentDescribe(node)) {
+                    nesting.push(FUNCTION);
+                }
+            },
+            'FunctionExpression:exit'(node) {
+                if (nesting.length && !isParentDescribe(node)) {
+                    nesting.pop();
+                }
+            },
+
             ArrowFunctionExpression(node) {
                 if (nesting.length && !isParentDescribe(node)) {
                     nesting.push(FUNCTION);


Test patch:
diff --git a/lib/rules/no-skipped-tests.js b/lib/rules/no-skipped-tests.js
index 8c0133e..34edd70 100644
--- a/lib/rules/no-skipped-tests.js
+++ b/lib/rules/no-skipped-tests.js
@@ -11,18 +11,14 @@ module.exports = {
     },
     create(context) {
         const astUtils = createAstUtils(context.settings);
-        const options = { modifiers: [ 'skip' ], modifiersOnly: true };
-        const isTestCase = astUtils.buildIsTestCaseAnswerer(options);
-        const isDescribe = astUtils.buildIsDescribeAnswerer(options);
 
         return {
             CallExpression(node) {
-                if (isDescribe(node) || isTestCase(node)) {
+                const options = { modifiers: [ 'skip' ], modifiersOnly: true };
+
+                if (astUtils.isDescribe(node, options) || astUtils.isTestCase(node, options)) {
                     const callee = node.callee;
-                    const nodeToReport =
-                        callee.type === 'MemberExpression' ?
-                            callee.property :
-                            callee;
+                    const nodeToReport = callee.type === 'MemberExpression' ? callee.property : callee;
 
                     context.report({
                         node: nodeToReport,
diff --git a/test/rules/no-setup-in-describe.js b/test/rules/no-setup-in-describe.js
index cd13e47..b1cf57e 100644
--- a/test/rules/no-setup-in-describe.js
+++ b/test/rules/no-setup-in-describe.js
@@ -3,8 +3,9 @@
 const RuleTester = require('eslint').RuleTester;
 const rule = require('../../lib/rules/no-setup-in-describe');
 const ruleTester = new RuleTester();
-const memberExpressionError = 'Unexpected member expression in describe block. ' +
-        'Member expressions may call functions via getters.';
+const memberExpressionError =
+    'Unexpected member expression in describe block. ' +
+    'Member expressions may call functions via getters.';
 
 ruleTester.run('no-setup-in-describe', rule, {
     valid: [
@@ -50,26 +51,40 @@ ruleTester.run('no-setup-in-describe', rule, {
             code: 'describe("", function () { before(() => { b(); }); it(); })',
             parserOptions: { ecmaVersion: 6 }
         },
-        { code: 'describe("", function () { var a = () => b(); it(); })', parserOptions: { ecmaVersion: 6 } },
-        { code: 'describe("", function () { var a = () => b.c; it(); })', parserOptions: { ecmaVersion: 6 } },
+        {
+            code: 'describe("", function () { var a = () => b(); it(); })',
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: 'describe("", function () { var a = () => b.c; it(); })',
+            parserOptions: { ecmaVersion: 6 }
+        },
         'describe("", function () { describe("", function () { it(); }); it(); })',
         {
             code: 'foo("", function () { it(); })',
             settings: {
-                'mocha/additionalCustomNames': [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                'mocha/additionalCustomNames': [
+                    { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                ]
             }
-        }, {
+        },
+        {
             code: 'foo("", function () { it(); })',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             }
-        }, {
+        },
+        {
             code: 'foo("", function () { it("", function () { b(); }); })',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             }
         },
@@ -82,125 +97,176 @@ ruleTester.run('no-setup-in-describe', rule, {
               });
             });`,
             parserOptions: { ecmaVersion: 2015 }
-        }
+        },
+        'describe("", function () { function bar() { a.b = "c"; } it(); })',
+        {
+            code:
+                'describe("", function () { const bar = () => { a.b = "c"; }; it(); })',
+            parserOptions: { ecmaVersion: 2015 }
+        },
+        'describe("", function () { var bar = function () { a.b = "c"; }; it(); })'
     ],
 
     invalid: [
         {
             code: 'suite("", function () { a(); });',
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 25
-            } ]
-        }, {
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 25
+                }
+            ]
+        },
+        {
             code: 'describe("", function () { a(); });',
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 28
-            } ]
-        }, {
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 28
+                }
+            ]
+        },
+        {
             code: 'describe("", () => { a(); });',
             parserOptions: { ecmaVersion: 2015 },
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 22
-            } ]
-        }, {
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 22
+                }
+            ]
+        },
+        {
             code: 'foo("", function () { a(); });',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             },
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 23
-            } ]
-        }, {
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 23
+                }
+            ]
+        },
+        {
             code: 'foo("", function () { a[b]; });',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             },
-            errors: [ {
-                message: memberExpressionError,
-                line: 1,
-                column: 23
-            } ]
-        }, {
+            errors: [
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 23
+                }
+            ]
+        },
+        {
             code: 'foo("", function () { a["b"]; });',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             },
-            errors: [ {
-                message: memberExpressionError,
-                line: 1,
-                column: 23
-            } ]
+            errors: [
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 23
+                }
+            ]
         },
         {
             code: 'describe("", function () { a.b; });',
-            errors: [ {
-                message: memberExpressionError,
-                line: 1,
-                column: 28
-            } ]
-        }, {
+            errors: [
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 28
+                }
+            ]
+        },
+        {
             code: 'describe("", function () { this.a(); });',
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 28
-            }, {
-                message: memberExpressionError,
-                line: 1,
-                column: 28
-            } ]
-        }, {
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 28
+                },
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 28
+                }
+            ]
+        },
+        {
             code: 'foo("", function () { a.b; });',
             settings: {
                 mocha: {
-                    additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ]
+                    additionalCustomNames: [
+                        { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] }
+                    ]
                 }
             },
-            errors: [ {
-                message: memberExpressionError,
-                line: 1,
-                column: 23
-            } ]
-        }, {
+            errors: [
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 23
+                }
+            ]
+        },
+        {
             code: 'describe("", function () { it("", function () {}).a(); });',
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 28
-            }, {
-                message: memberExpressionError,
-                line: 1,
-                column: 28
-            } ]
-        }, {
-            code: 'describe("", function () { something("", function () {}).timeout(); });',
-            errors: [ {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 28
-            }, {
-                message: memberExpressionError,
-                line: 1,
-                column: 28
-            }, {
-                message: 'Unexpected function call in describe block.',
-                line: 1,
-                column: 28
-            } ]
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 28
+                },
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 28
+                }
+            ]
+        },
+        {
+            code:
+                'describe("", function () { something("", function () {}).timeout(); });',
+            errors: [
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 28
+                },
+                {
+                    message: memberExpressionError,
+                    line: 1,
+                    column: 28
+                },
+                {
+                    message: 'Unexpected function call in describe block.',
+                    line: 1,
+                    column: 28
+                }
+            ]
         }
     ]
 });