From 4e02187cd27d6e511b74f3afec980bf4d473abf3 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 6 Aug 2023 23:46:43 +0200 Subject: [PATCH] New rule `no-extra-new` --- README.md | 2 + lib/rules/no-extra-new.js | 61 ++++++++++++++++++ lib/utils.js | 2 +- rule-docs/no-extra-new.md | 52 +++++++++++++++ test/lib/rules/no-extra-new.js | 114 +++++++++++++++++++++++++++++++++ 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 lib/rules/no-extra-new.js create mode 100644 rule-docs/no-extra-new.md create mode 100644 test/lib/rules/no-extra-new.js diff --git a/README.md b/README.md index 2d63311..3649a6c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Then configure the rules defined by this plugin under the `"rules"` section. "@origin-1/bracket-layout": "error", "@origin-1/indent": "error", "@origin-1/nice-space-before-function-paren": "error", + "@origin-1/no-extra-new": "error", "@origin-1/no-spaces-in-call-expression": "error", "@origin-1/no-spaces-in-tagged-template": "error", "@origin-1/property-colon-spacing": "error" @@ -44,6 +45,7 @@ Then configure the rules defined by this plugin under the `"rules"` section. * [`bracket-layout`](rule-docs/bracket-layout.md) * [`indent`](rule-docs/indent.md) * [`nice-space-before-function-paren`](rule-docs/nice-space-before-function-paren.md) +* [`no-extra-new`](rule-docs/no-extra-new.md) * [`no-spaces-in-call-expression`](rule-docs/no-spaces-in-call-expression.md) * [`no-spaces-in-tagged-template`](rule-docs/no-spaces-in-tagged-template.md) * [`property-colon-spacing`](rule-docs/property-colon-spacing.md) diff --git a/lib/rules/no-extra-new.js b/lib/rules/no-extra-new.js new file mode 100644 index 0000000..f52ecca --- /dev/null +++ b/lib/rules/no-extra-new.js @@ -0,0 +1,61 @@ +'use strict'; + +const { makeRuleDocsURL } = require('../utils'); + +const CONSTRUCTOR_NAMES = +[ + 'AggregateError', + 'Array', + 'Error', + 'EvalError', + 'Function', + 'Object', + 'RangeError', + 'ReferenceError', + 'RegExp', + 'SyntaxError', + 'TypeError', + 'URIError', +]; + +const meta = +{ + type: 'suggestion', + docs: + { + description: 'Disallow unnecessary usages of the new syntax', + url: makeRuleDocsURL('no-extra-new'), + }, + schema: [], + messages: { unexpected: 'Unnecessary usage of the new syntax.' }, +}; + +function create(context) +{ + const { sourceCode } = context; + const ruleListeners = + { + 'Program:exit': + node => + { + const globalScope = sourceCode.getScope(node); + for (const constructorName of CONSTRUCTOR_NAMES) + { + const variable = globalScope.set.get(constructorName); + if (variable && variable.defs.length === 0) + { + for (const ref of variable.references) + { + const idNode = ref.identifier; + const { parent } = idNode; + if (parent && parent.type === 'NewExpression' && parent.callee === idNode) + context.report({ node: parent, messageId: 'unexpected' }); + } + } + } + }, + }; + return ruleListeners; +} + +module.exports = { meta, create }; diff --git a/lib/utils.js b/lib/utils.js index 910e8a9..8bc795d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -67,7 +67,7 @@ function isBracketToken(token) /** * Determines whether a specified token is a closing parenthesis token. - * @param {Token} token - The token to check. + * @param {Token} token The token to check. * @returns {boolean} Whether or not the token is a closing parenthesis token. */ function isClosingParenToken(token) diff --git a/rule-docs/no-extra-new.md b/rule-docs/no-extra-new.md new file mode 100644 index 0000000..b388ed1 --- /dev/null +++ b/rule-docs/no-extra-new.md @@ -0,0 +1,52 @@ +# `no-extra-new` + +The rule `no-extra-new` disallows unnecessary usages of the the `new` syntax. + +The following built-in constructors can be called equally with or without `new` syntax: +* `AggregateError` +* `Array` +* `Error` +* `EvalError` +* `Function` +* `Object` +* `RangeError` +* `ReferenceError` +* `RegExp` +* `SyntaxError` +* `TypeError` +* `URIError` + +## Examples + +### Examples of **incorrect** code for this rule + +```js +/* eslint @origin-1/no-extra-new: "error" */ + +var array = new Array(42); +var fn = new Function; +var obj = new Object('foo'); +var regExp = new RegExp('\\d', 'g'); +throw new TypeError(); +``` + +### Examples of **correct** code for this rule + +```js +/* eslint @origin-1/no-extra-new: "error" */ + +var array = Array(42); +var fn = Function(); +var obj = Object('foo'); +var regExp = RegExp('\\d', 'g'); +throw TypeError(); + +function throwNew(Error) +{ + throw new Error(); // New expressions with a variable as callee are allowed. +} +``` + +## Superseded core ESLint rules + +* [`no-new-object`](https://eslint.org/docs/latest/rules/no-new-object) diff --git a/test/lib/rules/no-extra-new.js b/test/lib/rules/no-extra-new.js new file mode 100644 index 0000000..e05d22d --- /dev/null +++ b/test/lib/rules/no-extra-new.js @@ -0,0 +1,114 @@ +'use strict'; + +const rule = require('../../../lib/rules/no-extra-new'); +const { RuleTester } = require('eslint'); + +const ruleTester = new RuleTester(); +const tests = +{ + valid: + [ + 'var myObject = {};', + 'var myObject = new CustomObject();', + 'var foo = new foo.Object();', + ` + var Object = function Object() {}; + new Object(); + `, + ` + var x = something ? MyClass : Object; + var y = new x();`, + { + code: + ` + class Object + { + constructor() { } + } + new Object(); + `, + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + ` + import { Object } from './'; + new Object(); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + ` + var Error = CustomError; + function foo() + { + throw new Error(); + } + `, + { + code: + ` + function foo() + { + throw new Error(); + } + const Error = CustomError; + `, + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'throw new AggregateError(errors);', + env: { es2020: true }, + }, + ], + invalid: + [ + { + code: 'var foo = new Object();', + errors: [{ messageId: 'unexpected', type: 'NewExpression' }], + }, + { + code: 'new Object();', + errors: [{ messageId: 'unexpected', type: 'NewExpression' }], + }, + { + code: 'const a = new Object();', + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: 'unexpected', type: 'NewExpression' }], + }, + { + code: + ` + new Array; + new Error; + new EvalError; + new Function; + new Object; + new RangeError; + new ReferenceError; + new RegExp; + new SyntaxError; + new TypeError; + new URIError; + `, + errors: Array(11).fill({ messageId: 'unexpected', type: 'NewExpression' }), + }, + { + code: 'throw new AggregateError(errors);', + env: { es2021: true }, + errors: [{ messageId: 'unexpected', type: 'NewExpression' }], + }, + { + code: + ` + function foo(Error) + { + throw new Error; + } + throw new Error; + `, + errors: [{ messageId: 'unexpected', type: 'NewExpression' }], + }, + ], +}; + +ruleTester.run('no-extra-new', rule, tests);