diff --git a/docs/rules/no-error-ctor-with-notthrows.md b/docs/rules/no-error-ctor-with-notthrows.md new file mode 100644 index 0000000..75202b0 --- /dev/null +++ b/docs/rules/no-error-ctor-with-notthrows.md @@ -0,0 +1,35 @@ +# No specifying error type in `t.notThrows()` + +AVA will fail if error constructor is specified in the second argument of `t.notThrows()`. + + +## Fail + +```js +const test = require('ava'); + +test('some test', t => { + t.notThrows(() => { + t.pass(); + }, TypeError); +}); +``` + + +## Pass + +```js +const test = require('ava'); + +test('some test', t => { + t.notThrows(() => { + t.pass(); + }); +}); + +test('some test', t => { + t.throws(() => { + t.pass(); + }, TypeError); +}); +``` diff --git a/index.js b/index.js index 5559539..95ebf33 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,7 @@ module.exports = { 'ava/no-async-fn-without-await': 'error', 'ava/no-cb-test': 'off', 'ava/no-duplicate-modifiers': 'error', + 'ava/no-error-ctor-with-notthrows': 'error', 'ava/no-identical-title': 'error', 'ava/no-ignored-test-files': 'error', 'ava/no-import-test-files': 'error', diff --git a/readme.md b/readme.md index cc80a4e..84a317d 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,7 @@ Configure it in `package.json`. "ava/no-async-fn-without-await": "error", "ava/no-cb-test": "off", "ava/no-duplicate-modifiers": "error", + "ava/no-error-ctor-with-notthrows": "error", "ava/no-identical-title": "error", "ava/no-ignored-test-files": "error", "ava/no-import-test-files": "error", @@ -82,6 +83,7 @@ The rules will only activate in test files. - [no-async-fn-without-await](docs/rules/no-async-fn-without-await.md) - Ensure that async tests use `await`. - [no-cb-test](docs/rules/no-cb-test.md) - Ensure no `test.cb()` is used. - [no-duplicate-modifiers](docs/rules/no-duplicate-modifiers.md) - Ensure tests do not have duplicate modifiers. +- [no-error-ctor-with-notthrows](docs/rules/no-error-ctor-with-notthrows.md) - Ensure no error constructor is specified in `t.notThrows()`. - [no-identical-title](docs/rules/no-identical-title.md) - Ensure no tests have the same title. - [no-ignored-test-files](docs/rules/no-ignored-test-files.md) - Ensure no tests are written in ignored files. - [no-import-test-files](docs/rules/no-import-test-files.md) - Ensure no test files are imported anywhere. diff --git a/rules/no-error-ctor-with-notthrows.js b/rules/no-error-ctor-with-notthrows.js new file mode 100644 index 0000000..e651726 --- /dev/null +++ b/rules/no-error-ctor-with-notthrows.js @@ -0,0 +1,40 @@ +'use strict'; +const {visitIf} = require('enhance-visitors'); +const util = require('../util'); +const createAvaRule = require('../create-ava-rule'); + +const create = context => { + const ava = createAvaRule(); + + return ava.merge({ + CallExpression: visitIf([ + ava.isInTestFile, + ava.isInTestNode + ])(node => { + const functionArgIndex = node.arguments.length - 1; + + if (typeof node.callee.property === 'undefined' || functionArgIndex !== 1 || node.callee.type !== 'MemberExpression' || node.arguments[1].type !== 'Identifier' || util.getNameOfRootNodeObject(node.callee) !== 't') { + return; + } + + const calleeProperty = node.callee.property.name; + const functionArgName = node.arguments[1].name; + if ((calleeProperty === 'notThrows' || calleeProperty === 'notThrowsAsync') && functionArgName.endsWith('Error')) { + context.report({ + node, + message: `Do not specify an error constructor in the second argument of \`t.${calleeProperty}()\`` + }); + } + }) + }); +}; + +module.exports = { + create, + meta: { + docs: { + url: util.getDocsUrl(__filename) + }, + type: 'problem' + } +}; diff --git a/test/no-error-ctor-with-notthrows.js b/test/no-error-ctor-with-notthrows.js new file mode 100644 index 0000000..d444cf7 --- /dev/null +++ b/test/no-error-ctor-with-notthrows.js @@ -0,0 +1,209 @@ +const test = require('ava'); +const avaRuleTester = require('eslint-ava-rule-tester'); +const rule = require('../rules/no-error-ctor-with-notthrows'); + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + }, + parserOptions: { + ecmaVersion: 2019 + } +}); + +const errors = [{}]; + +const header = 'const test = require(\'ava\');\n'; + +ruleTester.run('no-error-ctor-with-notthrows', rule, { + valid: [ + `${header} + test('some test', t => { + t.notThrows(() => { + t.pass(); + }); + });`, + + `${header} + test(t => { + t.notThrows(() => { + t.pass(); + }); + });`, + + `${header} + test(t => { + t.throws(() => { + t.pass(); + }, TypeError); + });`, + + `${header} + test(t => { t.end(); })`, + + `${header} + test('some test', t => { + t.notThrows(() => { + t.pass(); + }, true); + });`, + + `${header} + test('some test', t => { + t.notThrows(() => { + t.pass(); + }, 'some string'); + });`, + + `${header} + test('some test', t => { + t.notThrows(() => { + t.pass(); + }, {firstName:'some', lastName: 'object'}); + });`, + + `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }); + });`, + + `${header} + test(t => { + t.notThrowsAsync(() => { + t.pass(); + }); + });`, + + `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, {firstName:'some', lastName: 'object'}); + });`, + + `${header} + test('some test', t => { + notThrows(foo); + });`, + + `${header} + test('some test', t => { + myCustomNotThrows.notThrows(foo); + });`, + + `${header} + t.notThrows(() => { + t.pass(); + }, void 0);`, + + // Shouldn't be triggered since it's not a test file + `test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, TypeError); + });` + ], + invalid: [ + { + code: `${header} + test(t => { + t.notThrows(() => { + t.pass(); + }, TypeError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrows(() => { + t.pass(); + }, TypeError); + });`, + errors + }, + { + code: `${header} + test(t => { + t.notThrowsAsync(() => { + t.pass(); + }, TypeError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, TypeError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, Error); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, SyntaxError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, AssertionError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, ReferenceError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, RangeError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, SystemError); + });`, + errors + }, + { + code: `${header} + test('some test', t => { + t.notThrowsAsync(() => { + t.pass(); + }, $DOMError); + });`, + errors + } + ] +});