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)) { 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 } ] } ]