diff --git a/docs/rules/throw-new-error.md b/docs/rules/throw-new-error.md index 96f8f5a1ee..902226b8fd 100644 --- a/docs/rules/throw-new-error.md +++ b/docs/rules/throw-new-error.md @@ -4,18 +4,30 @@ While it's possible to create a new error without using the `new` keyword, it's This rule is fixable. - ## Fail ```js throw Error(); +``` + +```js throw TypeError('unicorn'); ``` +```js +throw lib.TypeError(); +``` ## Pass ```js throw new Error(); +``` + +```js throw new TypeError('unicorn'); ``` + +```js +throw new lib.TypeError(); +``` diff --git a/rules/throw-new-error.js b/rules/throw-new-error.js index 4684ef2abc..47437911b8 100644 --- a/rules/throw-new-error.js +++ b/rules/throw-new-error.js @@ -1,24 +1,38 @@ 'use strict'; const getDocumentationUrl = require('./utils/get-documentation-url'); +const messageId = 'throw-new-error'; +const customError = /^(?:[A-Z][\da-z]*)*Error$/; + const selector = [ 'ThrowStatement', '>', - 'CallExpression', - '[callee.type="Identifier"]' + 'CallExpression.argument', + `:matches(${ + [ + // `throw FooError()` + [ + '[callee.type="Identifier"]', + `[callee.name=/${customError.source}/]` + ], + // `throw lib.FooError()` + [ + '[callee.type="MemberExpression"]', + '[callee.computed=false]', + '[callee.property.type="Identifier"]', + `[callee.property.name=/${customError.source}/]` + ] + ].map(selector => selector.join('')).join(', ') + })` ].join(''); -const customError = /^(?:[A-Z][\da-z]*)*Error$/; -const message = 'Use `new` when throwing an error.'; const create = context => ({ [selector]: node => { - if (customError.test(node.callee.name)) { - context.report({ - node, - message, - fix: fixer => fixer.insertTextBefore(node, 'new ') - }); - } + context.report({ + node, + messageId, + fix: fixer => fixer.insertTextBefore(node, 'new ') + }); } }); @@ -29,6 +43,9 @@ module.exports = { docs: { url: getDocumentationUrl(__filename) }, + messages: { + [messageId]: 'Use `new` when throwing an error.' + }, fixable: 'code' } }; diff --git a/test/throw-new-error.js b/test/throw-new-error.js index 728d5bd3a1..6addf91f45 100644 --- a/test/throw-new-error.js +++ b/test/throw-new-error.js @@ -2,13 +2,15 @@ import test from 'ava'; import avaRuleTester from 'eslint-ava-rule-tester'; import rule from '../rules/throw-new-error'; +const messageId = 'throw-new-error'; + const ruleTester = avaRuleTester(test, { - env: { - es6: true + parserOptions: { + ecmaVersion: 2020 } }); -const errors = [{ruleId: 'throw-new-error'}]; +const errors = [{messageId}]; ruleTester.run('new-error', rule, { valid: [ @@ -28,8 +30,14 @@ ruleTester.run('new-error', rule, { 'throw getError()', // Not `CallExpression` 'throw CustomError', - // Not `Identifier` - 'throw lib.Error()' + // Not `Identifier` / `MemberExpression` + 'throw getErrorConstructor()()', + // `MemberExpression.computed` + 'throw lib[Error]()', + // `MemberExpression.property` not `Identifier` + 'throw lib["Error"]()', + // Not `FooError` like + 'throw lib.getError()' ], invalid: [ { @@ -37,6 +45,31 @@ ruleTester.run('new-error', rule, { output: 'throw new Error()', errors }, + { + code: 'throw (Error)()', + output: 'throw new (Error)()', + errors + }, + { + code: 'throw lib.Error()', + output: 'throw new lib.Error()', + errors + }, + { + code: 'throw lib.mod.Error()', + output: 'throw new lib.mod.Error()', + errors + }, + { + code: 'throw lib[mod].Error()', + output: 'throw new lib[mod].Error()', + errors + }, + { + code: 'throw (lib.mod).Error()', + output: 'throw new (lib.mod).Error()', + errors + }, { code: 'throw Error(\'foo\')', output: 'throw new Error(\'foo\')',