diff --git a/.eslintrc.json b/.eslintrc.json index 902c911..333cb76 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,7 @@ "parserOptions": { "sourceType": "script", - "ecmaVersion": 2017, + "ecmaVersion": 2019, "ecmaFeatures": { "jsx": false, "globalReturn": false, diff --git a/docs/rules/README.md b/docs/rules/README.md index 29c1d83..beab601 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -24,3 +24,4 @@ ||:wrench:| [prefer-arrow-callback](prefer-arrow-callback.md) | prefer arrow function callbacks (mocha-aware) ||| [valid-suite-description](valid-suite-description.md) | match suite descriptions against a pre-configured regular expression ||| [valid-test-description](valid-test-description.md) | match test descriptions against a pre-configured regular expression +|:heavy_check_mark:|| [no-empty-description](no-empty-description.md) | Disallow empty test descriptions diff --git a/docs/rules/no-empty-description.md b/docs/rules/no-empty-description.md new file mode 100644 index 0000000..8eba62d --- /dev/null +++ b/docs/rules/no-empty-description.md @@ -0,0 +1,45 @@ +# Disallow empty test descriptions (no-empty-description) + +This rule enforces you to specify the suite/test descriptions for each test. + +## Rule Details + +This rule checks each mocha test function to have a non-empty description. + +The following patterns are considered problems: + +```js +it(); + +suite(""); + +test(function() { }) + +test.only(" ", function() { }) + +``` + +These patterns would not be considered problems: + +```js +describe('foo', function () { + it('bar'); +}); + +suite('foo', function () { + test('bar'); +}); +``` + +## Options + +Example of a custom rule configuration: + +```js + rules: { + "mocha/no-empty-description": [ "warn", { + testNames: ["it", "specify", "test", "mytestname"], + message: 'custom error message' + } ] + } +``` \ No newline at end of file diff --git a/index.js b/index.js index 6d6aac7..05a0e9f 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,8 @@ module.exports = { 'no-top-level-hooks': require('./lib/rules/no-top-level-hooks'), 'prefer-arrow-callback': require('./lib/rules/prefer-arrow-callback'), 'valid-suite-description': require('./lib/rules/valid-suite-description'), - 'valid-test-description': require('./lib/rules/valid-test-description') + 'valid-test-description': require('./lib/rules/valid-test-description'), + 'no-empty-description': require('./lib/rules/no-empty-description.js') }, configs: { all: { @@ -51,7 +52,8 @@ module.exports = { 'mocha/no-top-level-hooks': 'error', 'mocha/prefer-arrow-callback': 'error', 'mocha/valid-suite-description': 'error', - 'mocha/valid-test-description': 'error' + 'mocha/valid-test-description': 'error', + 'mocha/no-empty-description': 'error' } }, @@ -80,7 +82,8 @@ module.exports = { 'mocha/no-top-level-hooks': 'warn', 'mocha/prefer-arrow-callback': 'off', 'mocha/valid-suite-description': 'off', - 'mocha/valid-test-description': 'off' + 'mocha/valid-test-description': 'off', + 'no-empty-description': 'error' } } } diff --git a/lib/rules/no-empty-description.js b/lib/rules/no-empty-description.js new file mode 100644 index 0000000..d40932b --- /dev/null +++ b/lib/rules/no-empty-description.js @@ -0,0 +1,82 @@ +'use strict'; + +const { getStringIfConstant } = require('eslint-utils'); + +const DEFAULT_TEST_NAMES = [ 'describe', 'context', 'suite', 'it', 'test', 'specify' ]; +const ERROR_MESSAGE = 'Unexpected empty test description.'; + +function objectOptions(options = {}) { + const { + testNames = DEFAULT_TEST_NAMES, + message + } = options; + + return { testNames, message }; +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow empty test descriptions', + url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/no-empty-description.md' + }, + messages: { + error: ERROR_MESSAGE + }, + schema: [ + { + type: 'object', + properties: { + testNames: { + type: 'array', + items: { + type: 'string' + } + }, + message: { + type: 'string' + } + }, + additionalProperties: false + } + ] + }, + create(context) { + const options = context.options[0]; + + const { testNames, message } = objectOptions(options); + + function isTest(node) { + return node.callee && node.callee.name && testNames.includes(node.callee.name); + } + + function isTemplateString(node) { + return [ 'TaggedTemplateExpression', 'TemplateLiteral' ].includes(node && node.type); + } + + function checkDescription(mochaCallExpression) { + const description = mochaCallExpression.arguments[0]; + const text = getStringIfConstant(description); + + if (isTemplateString(description) && text === null) { + return true; + } + + return text && text.trim().length; + } + + return { + CallExpression(node) { + if (isTest(node)) { + if (!checkDescription(node)) { + context.report({ + node, + message: message || ERROR_MESSAGE + }); + } + } + } + }; + } +}; diff --git a/test/rules/no-empty-description.js b/test/rules/no-empty-description.js new file mode 100644 index 0000000..3faa7b1 --- /dev/null +++ b/test/rules/no-empty-description.js @@ -0,0 +1,83 @@ +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const rules = require('../..').rules; +const ruleTester = new RuleTester(); +const defaultErrorMessage = 'Unexpected empty test description.'; +const firstLine = { column: 1, line: 1 }; + +ruleTester.run('no-empty-description', rules['no-empty-description'], { + + valid: [ + 'describe("some text")', + 'describe.only("some text")', + 'describe("some text", function() { })', + + 'context("some text")', + 'context.only("some text")', + 'context("some text", function() { })', + + 'it("some text")', + 'it.only("some text")', + 'it("some text", function() { })', + + 'suite("some text")', + 'suite.only("some text")', + 'suite("some text", function() { })', + + 'test("some text")', + 'test.only("some text")', + 'test("some text", function() { })', + + 'notTest()', + + { + parserOptions: { ecmaVersion: 2019 }, + code: 'it(string`template`, function () {});' + }, + { + parserOptions: { ecmaVersion: 2019 }, + code: 'it(`template strings`, function () {});' + }, + { + parserOptions: { ecmaVersion: 2019 }, + code: 'it(`${foo} template strings`, function () {});' + }, + { + options: [ { testNames: [ 'someFunction' ] } ], + code: 'someFunction("this is a test", function () { });' + } + ], + + invalid: [ + { + code: 'test()', + errors: [ { message: defaultErrorMessage, ...firstLine } ] + }, + { + code: 'test(function() { })', + errors: [ { message: defaultErrorMessage, ...firstLine } ] + }, + { + code: 'test("", function() { })', + errors: [ { message: defaultErrorMessage, ...firstLine } ] + }, + { + code: 'test(" ", function() { })', + errors: [ { message: defaultErrorMessage, ...firstLine } ] + }, + + { + options: [ { testNames: [ 'someFunction' ], message: 'Custom Error' } ], + code: 'someFunction(function() { })', + errors: [ { message: 'Custom Error', ...firstLine } ] + }, + { + parserOptions: { ecmaVersion: 2019 }, + code: 'it(` `, function () { });', + errors: [ { message: defaultErrorMessage, ...firstLine } ] + } + ] + +}); +