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 00000000..75202b0c --- /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 60669d41..3b834cb6 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/package.json b/package.json index ecab1c50..a0ff5e95 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "ava": { "files": [ "!rules", - "test/*.js" + "test/*js" ] }, "xo": { diff --git a/readme.md b/readme.md index 4a1e809e..61cc0cda 100644 --- a/readme.md +++ b/readme.md @@ -44,6 +44,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", @@ -84,6 +85,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) - No specifying error type 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 00000000..00b9d7d6 --- /dev/null +++ b/rules/no-error-ctor-with-notthrows.js @@ -0,0 +1,42 @@ +'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') { + if (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 00000000..17453140 --- /dev/null +++ b/test/no-error-ctor-with-notthrows.js @@ -0,0 +1,210 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import rule from '../rules/no-error-ctor-with-notthrows'; + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + }, + parserOptions: { + ecmaVersion: 2019 + } +}); + +const errors = [{ruleId: 'no-error-ctor-with-notthrows'}]; + +const header = `const test = require('ava');\n`; // eslint-disable-line quotes + +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 + } + ] +});