diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 0f3893f14f2..7a49dbe3e1e 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -950,13 +950,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "no-new-object", - "description": "Disallow `Object` constructors", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, { "name": "no-new-wrappers", "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", @@ -971,6 +964,13 @@ "fixable": false, "hasSuggestions": true }, + { + "name": "no-object-constructor", + "description": "Disallow calls to the `Object` constructor without an argument", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, { "name": "no-octal", "description": "Disallow octal literals", @@ -1956,6 +1956,12 @@ "no-unsafe-negation" ] }, + { + "name": "no-new-object", + "replacedBy": [ + "no-object-constructor" + ] + }, { "name": "no-new-require", "replacedBy": [] diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 7113de13bdb..862f013d3fa 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1364,7 +1364,11 @@ "description": "Disallow `Object` constructors", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-new-object" - } + }, + "deprecated": true, + "replacedBy": [ + "no-object-constructor" + ] }, "no-new-require": { "deprecated": true, @@ -1409,6 +1413,15 @@ "url": "https://eslint.org/docs/latest/rules/no-obj-calls" } }, + "no-object-constructor": { + "type": "suggestion", + "docs": { + "description": "Disallow calls to the `Object` constructor without an argument", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-object-constructor" + }, + "hasSuggestions": true + }, "no-octal": { "type": "suggestion", "docs": { diff --git a/docs/src/rules/no-array-constructor.md b/docs/src/rules/no-array-constructor.md index 7a22df0e68a..aa7c079baa2 100644 --- a/docs/src/rules/no-array-constructor.md +++ b/docs/src/rules/no-array-constructor.md @@ -2,8 +2,8 @@ title: no-array-constructor rule_type: suggestion related_rules: -- no-new-object - no-new-wrappers +- no-object-constructor --- diff --git a/docs/src/rules/no-new-object.md b/docs/src/rules/no-new-object.md index 41b02f87406..bdd57741198 100644 --- a/docs/src/rules/no-new-object.md +++ b/docs/src/rules/no-new-object.md @@ -6,6 +6,7 @@ related_rules: - no-new-wrappers --- +This rule was **deprecated** in ESLint v8.50.0 and replaced by the [no-object-constructor](no-object-constructor) rule. The new rule flags more situations where object literal syntax can be used, and it does not report a problem when the `Object` constructor is invoked with an argument. The `Object` constructor is used to create new generic objects in JavaScript, such as: @@ -25,7 +26,7 @@ While there are no performance differences between the two approaches, the byte ## Rule Details -This rule disallows `Object` constructors. +This rule disallows calling the `Object` constructor with `new`. Examples of **incorrect** code for this rule: @@ -37,6 +38,8 @@ Examples of **incorrect** code for this rule: var myObject = new Object(); new Object(); + +var foo = new Object("foo"); ``` ::: @@ -54,10 +57,12 @@ var myObject = {}; var Object = function Object() {}; new Object(); + +var foo = Object("foo"); ``` ::: ## When Not To Use It -If you wish to allow the use of the `Object` constructor, you can safely turn this rule off. +If you wish to allow the use of the `Object` constructor with `new`, you can safely turn this rule off. diff --git a/docs/src/rules/no-new-wrappers.md b/docs/src/rules/no-new-wrappers.md index da0a860ca15..fe11e3af7ee 100644 --- a/docs/src/rules/no-new-wrappers.md +++ b/docs/src/rules/no-new-wrappers.md @@ -3,7 +3,7 @@ title: no-new-wrappers rule_type: suggestion related_rules: - no-array-constructor -- no-new-object +- no-object-constructor further_reading: - https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects --- diff --git a/docs/src/rules/no-object-constructor.md b/docs/src/rules/no-object-constructor.md new file mode 100644 index 00000000000..e2f7015cdab --- /dev/null +++ b/docs/src/rules/no-object-constructor.md @@ -0,0 +1,50 @@ +--- +title: no-object-constructor +rule_type: suggestion +related_rules: +- no-array-constructor +- no-new-wrappers +--- + +Use of the `Object` constructor to construct a new empty object is generally discouraged in favor of object literal notation because of conciseness and because the `Object` global may be redefined. +The exception is when the `Object` constructor is used to intentionally wrap a specified value which is passed as an argument. + +## Rule Details + +This rule disallows calling the `Object` constructor without an argument. + +Examples of **incorrect** code for this rule: + +:::incorrect + +```js +/*eslint no-object-constructor: "error"*/ + +Object(); + +new Object(); +``` + +::: + +Examples of **correct** code for this rule: + +:::correct + +```js +/*eslint no-object-constructor: "error"*/ + +Object("foo"); + +const obj = { a: 1, b: 2 }; + +const isObject = value => value === Object(value); + +const createObject = Object => new Object(); +``` + +::: + +## When Not To Use It + +If you wish to allow the use of the `Object` constructor, you can safely turn this rule off. diff --git a/lib/rules/index.js b/lib/rules/index.js index e42639656f7..840abe73b0f 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -175,6 +175,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-new-wrappers": () => require("./no-new-wrappers"), "no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"), "no-obj-calls": () => require("./no-obj-calls"), + "no-object-constructor": () => require("./no-object-constructor"), "no-octal": () => require("./no-octal"), "no-octal-escape": () => require("./no-octal-escape"), "no-param-reassign": () => require("./no-param-reassign"), diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index 08a482be715..06275f47125 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to disallow calls to the Object constructor * @author Matt DuVall + * @deprecated in ESLint v8.50.0 */ "use strict"; @@ -26,6 +27,12 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-new-object" }, + deprecated: true, + + replacedBy: [ + "no-object-constructor" + ], + schema: [], messages: { diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js new file mode 100644 index 00000000000..1299779f7ec --- /dev/null +++ b/lib/rules/no-object-constructor.js @@ -0,0 +1,118 @@ +/** + * @fileoverview Rule to disallow calls to the `Object` constructor without an argument + * @author Francesco Trotta + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getVariableByName, isArrowToken } = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Tests if a node appears at the beginning of an ancestor ExpressionStatement node. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node. + */ +function isStartOfExpressionStatement(node) { + const start = node.range[0]; + let ancestor = node; + + while ((ancestor = ancestor.parent) && ancestor.range[0] === start) { + if (ancestor.type === "ExpressionStatement") { + return true; + } + } + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Disallow calls to the `Object` constructor without an argument", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-object-constructor" + }, + + hasSuggestions: true, + + schema: [], + + messages: { + preferLiteral: "The object literal notation {} is preferable.", + useLiteral: "Replace with '{{replacement}}'." + } + }, + + create(context) { + + const sourceCode = context.sourceCode; + + /** + * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses. + * @param {ASTNode} node The node to be replaced. + * @returns {boolean} Whether or not parentheses around the object literal are required. + */ + function needsParentheses(node) { + if (isStartOfExpressionStatement(node)) { + return true; + } + + const prevToken = sourceCode.getTokenBefore(node); + + if (prevToken && isArrowToken(prevToken)) { + return true; + } + + return false; + } + + /** + * Reports on nodes where the `Object` constructor is called without arguments. + * @param {ASTNode} node The node to evaluate. + * @returns {void} + */ + function check(node) { + if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) { + return; + } + + const variable = getVariableByName(sourceCode.getScope(node), "Object"); + + if (variable && variable.identifiers.length === 0) { + const replacement = needsParentheses(node) ? "({})" : "{}"; + + context.report({ + node, + messageId: "preferLiteral", + suggest: [ + { + messageId: "useLiteral", + data: { replacement }, + fix: fixer => fixer.replaceText(node, replacement) + } + ] + }); + } + } + + return { + CallExpression: check, + NewExpression: check + }; + + } +}; diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index 177ff92f316..52f580035a8 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -152,11 +152,11 @@ module.exports = Object.freeze({ "no-new": "error", "no-new-func": "error", "no-new-native-nonconstructor": "error", - "no-new-object": "error", "no-new-symbol": "error", "no-new-wrappers": "error", "no-nonoctal-decimal-escape": "error", "no-obj-calls": "error", + "no-object-constructor": "error", "no-octal": "error", "no-octal-escape": "error", "no-param-reassign": "error", diff --git a/tests/lib/rules/no-object-constructor.js b/tests/lib/rules/no-object-constructor.js new file mode 100644 index 00000000000..789b9e84cb7 --- /dev/null +++ b/tests/lib/rules/no-object-constructor.js @@ -0,0 +1,109 @@ +/** + * @fileoverview Tests for the no-object-constructor rule + * @author Francesco Trotta + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-object-constructor"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: "latest" } }); + +ruleTester.run("no-object-constructor", rule, { + valid: [ + "new Object(x)", + "Object(x)", + "new globalThis.Object", + "const createObject = Object => new Object()", + "var Object; new Object;", + { + code: "new Object()", + globals: { + Object: "off" + } + } + ], + invalid: [ + { + code: "new Object", + errors: [{ + messageId: "preferLiteral", + type: "NewExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({})" + }] + }] + }, + { + code: "Object()", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({})" + }] + }] + }, + { + code: "const fn = () => Object();", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "const fn = () => ({});" + }] + }] + }, + { + code: "Object() instanceof Object;", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({}) instanceof Object;" + }] + }] + }, + { + code: "const obj = Object?.();", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '{}'.", + messageId: "useLiteral", + output: "const obj = {};" + }] + }] + }, + { + code: "(new Object() instanceof Object);", + errors: [{ + messageId: "preferLiteral", + type: "NewExpression", + suggestions: [{ + desc: "Replace with '{}'.", + messageId: "useLiteral", + output: "({} instanceof Object);" + }] + }] + } + ] +}); diff --git a/tools/rule-types.json b/tools/rule-types.json index f3fe8f80cd1..35895f3838a 100644 --- a/tools/rule-types.json +++ b/tools/rule-types.json @@ -162,6 +162,7 @@ "no-new-wrappers": "suggestion", "no-nonoctal-decimal-escape": "suggestion", "no-obj-calls": "problem", + "no-object-constructor": "suggestion", "no-octal": "suggestion", "no-octal-escape": "suggestion", "no-param-reassign": "suggestion",