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

Issue:
`no-empty-description` incorrectly flags non-empty descriptions.
The following seem like reasonable test descriptions, but they get flagged by `no-empty-description`:

```js
describe('eslint-plugin-mocha', function() {
  const description = 'description';
  it(description, function() {});

  ['test1', 'test2'].forEach(testname => {
    it(testname, function() {});
  });

  const removePrefix = 'testcase.prefix';
  it(removePrefix.replace('.prefix'), function() {});
});
```

```
   3:3  error  Unexpected empty test description  mocha/no-empty-description
   6:5  error  Unexpected empty test description  mocha/no-empty-description
  10:3  error  Unexpected empty test description  mocha/no-empty-description
```

Was this intended with #304?

Pull request description:
Allow dynamic expressions as description argument in no-empty-description
Fixes #313

Patch:
diff --git a/lib/rules/no-empty-description.js b/lib/rules/no-empty-description.js
index d02e880..21b0b6b 100644
--- a/lib/rules/no-empty-description.js
+++ b/lib/rules/no-empty-description.js
@@ -14,22 +14,31 @@ function objectOptions(options = {}) {
     return { testNames, message };
 }
 
-function isTemplateString(node) {
-    return [ 'TaggedTemplateExpression', 'TemplateLiteral' ].includes(node && node.type);
-}
-
-function isIdentifier(node) {
-    return node && node.type === 'Identifier';
+function isLiteral(node) {
+    return node && node.type === 'Literal';
 }
 
 function isStaticallyAnalyzableDescription(node, extractedText) {
     if (extractedText === null) {
-        return !(isTemplateString(node) || isIdentifier(node));
+        return isLiteral(node);
     }
 
     return true;
 }
 
+function isValidDescriptionArgumentNode(node) {
+    if (!node) {
+        return false;
+    }
+
+    return [
+        'Literal', 'TemplateLiteral', 'TaggedTemplateExpression',
+        'Identifier', 'MemberExpression', 'CallExpression',
+        'LogicalExpression', 'BinaryExpression', 'ConditionalExpression',
+        'UnaryExpression', 'SpreadElement', 'AwaitExpression',
+        'YieldExpression', 'NewExpression' ].includes(node.type);
+}
+
 module.exports = {
     meta: {
         type: 'suggestion',
@@ -69,6 +78,11 @@ module.exports = {
 
         function isNonEmptyDescription(mochaCallExpression) {
             const description = mochaCallExpression.arguments[0];
+
+            if (!isValidDescriptionArgumentNode(description)) {
+                return false;
+            }
+
             const text = getStringIfConstant(description, context.getScope());
 
             if (!isStaticallyAnalyzableDescription(description, text)) {


Test patch:
diff --git a/test/rules/no-empty-description.js b/test/rules/no-empty-description.js
index a6d979a..3ec5724 100644
--- a/test/rules/no-empty-description.js
+++ b/test/rules/no-empty-description.js
@@ -31,6 +31,36 @@ ruleTester.run('no-empty-description', rules['no-empty-description'], {
 
         'var dynamicTitle = "foo"; it(dynamicTitle, function() {});',
         'it(dynamicTitle, function() {});',
+        'var dynamicTitle = "foo"; it(dynamicTitle.replace("foo", ""), function() {});',
+        'it(dynamicTitle.replace("foo", ""), function() {});',
+        'it(42, function() { })',
+        'it(true, function() { })',
+        'it(null, function() { })',
+        'it(new Foo(), function() { })',
+        'it(foo.bar, function() { })',
+        'it("foo".toUpperCase(), function() { })',
+        {
+            parserOptions: { ecmaVersion: 2020 },
+            code: 'it(foo ?? "bar", function() { })'
+        },
+        'it(foo || "bar", function() { })',
+        'it(foo ? "bar" : "baz", function() { })',
+        'it(foo ? bar : baz, function() { })',
+        'it(typeof foo, function() { })',
+        'it(foo + bar, function() { })',
+        'it("foo" + "bar", function() { })',
+        {
+            parserOptions: { ecmaVersion: 2019 },
+            code: 'it(...args)'
+        },
+        {
+            parserOptions: { ecmaVersion: 2019 },
+            code: 'async function a() { it(await foo, function () {}); }'
+        },
+        {
+            parserOptions: { ecmaVersion: 2019 },
+            code: 'function* g() { it(yield foo, function () {}); }'
+        },
 
         'notTest()',
 
@@ -53,6 +83,10 @@ ruleTester.run('no-empty-description', rules['no-empty-description'], {
         {
             parserOptions: { ecmaVersion: 2019 },
             code: 'const dynamicTitle = "foo"; it(dynamicTitle, function() {});'
+        },
+        {
+            parserOptions: { ecmaVersion: 2019 },
+            code: 'const dynamicTitle = "foo"; it(dynamicTitle.replace("foo", ""), function() {});'
         }
     ],
 
@@ -88,6 +122,16 @@ ruleTester.run('no-empty-description', rules['no-empty-description'], {
             parserOptions: { ecmaVersion: 2019 },
             code: 'const foo = ""; it(foo);',
             errors: [ { message: defaultErrorMessage, line: 1, column: 17 } ]
+        },
+        {
+            parserOptions: { ecmaVersion: 2019 },
+            code: 'const foo = { bar: "" }; it(foo.bar);',
+            errors: [ { message: defaultErrorMessage, line: 1, column: 26 } ]
+        },
+        {
+            parserOptions: { ecmaVersion: 2020 },
+            code: 'it(foo?.bar);',
+            errors: [ { message: defaultErrorMessage, line: 1, column: 1 } ]
         }
     ]