From a1370abed72e1fb93e601816d981fa6e46204afb Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 24 Mar 2020 20:37:22 +0100 Subject: [PATCH] Update: Report constructor calls in no-obj-calls (#12909) * Update: Report constructor calls in no-obj-calls * Add tests for globalThis and unexpectedRefCall * Remove Math and JSON from no-new-wrappers --- docs/rules/no-obj-calls.md | 18 +++++ lib/rules/no-new-wrappers.js | 2 +- lib/rules/no-obj-calls.js | 5 +- tests/lib/rules/no-new-wrappers.js | 20 ----- tests/lib/rules/no-obj-calls.js | 119 ++++++++++++++++++++++++++++- 5 files changed, 137 insertions(+), 27 deletions(-) diff --git a/docs/rules/no-obj-calls.md b/docs/rules/no-obj-calls.md index 7cd5ac4d4fa..7c8f715cedc 100644 --- a/docs/rules/no-obj-calls.md +++ b/docs/rules/no-obj-calls.md @@ -18,27 +18,45 @@ And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma- This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions. +This rule also disallows using these objects as constructors with the `new` operator. + Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ +/*eslint-env es2017*/ var math = Math(); + +var newMath = new Math(); + var json = JSON(); + +var newJSON = new JSON(); + var reflect = Reflect(); + +var newReflect = new Reflect(); + var atomics = Atomics(); + +var newAtomics = new Atomics(); ``` Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ +/*eslint-env es2017*/ function area(r) { return Math.PI * r * r; } + var object = JSON.parse("{}"); + var value = Reflect.get({ x: 1, y: 2 }, "x"); + var first = Atomics.load(foo, 0); ``` diff --git a/lib/rules/no-new-wrappers.js b/lib/rules/no-new-wrappers.js index 0a2861fa5f7..d276c48d203 100644 --- a/lib/rules/no-new-wrappers.js +++ b/lib/rules/no-new-wrappers.js @@ -32,7 +32,7 @@ module.exports = { return { NewExpression(node) { - const wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"]; + const wrapperObjects = ["String", "Number", "Boolean"]; if (wrapperObjects.indexOf(node.callee.name) > -1) { context.report({ diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index 336451656d2..6139ba2c182 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; //------------------------------------------------------------------------------ @@ -63,7 +63,8 @@ module.exports = { for (const g of nonCallableGlobals) { traceMap[g] = { - [CALL]: true + [CALL]: true, + [CONSTRUCT]: true }; } diff --git a/tests/lib/rules/no-new-wrappers.js b/tests/lib/rules/no-new-wrappers.js index fa53d3b465a..57dba4e1285 100644 --- a/tests/lib/rules/no-new-wrappers.js +++ b/tests/lib/rules/no-new-wrappers.js @@ -53,26 +53,6 @@ ruleTester.run("no-new-wrappers", rule, { }, type: "NewExpression" }] - }, - { - code: "var a = new Math();", - errors: [{ - messageId: "noConstructor", - data: { - fn: "Math" - }, - type: "NewExpression" - }] - }, - { - code: "var a = new JSON({ myProp: 10 });", - errors: [{ - messageId: "noConstructor", - data: { - fn: "JSON" - }, - type: "NewExpression" - }] } ] }); diff --git a/tests/lib/rules/no-obj-calls.js b/tests/lib/rules/no-obj-calls.js index d0aea3cb2db..4c21d9be4c8 100644 --- a/tests/lib/rules/no-obj-calls.js +++ b/tests/lib/rules/no-obj-calls.js @@ -24,9 +24,27 @@ ruleTester.run("no-obj-calls", rule, { "var x = Math.random();", "var x = Math.PI;", "var x = foo.Math();", + "var x = new foo.Math();", + "var x = new Math.foo;", + "var x = new Math.foo();", "JSON.parse(foo)", - "Reflect.get(foo, 'x')", - "Atomics.load(foo, 0)", + "new JSON.parse", + { + code: "Reflect.get(foo, 'x')", + env: { es6: true } + }, + { + code: "new Reflect.foo(a, b)", + env: { es6: true } + }, + { + code: "Atomics.load(foo, 0)", + env: { es2017: true } + }, + { + code: "new Atomics.foo()", + env: { es2017: true } + }, { code: "globalThis.Math();", env: { es6: true } }, { code: "var x = globalThis.Math();", env: { es6: true } }, @@ -43,12 +61,19 @@ ruleTester.run("no-obj-calls", rule, { // non-existing variables "/*globals Math: off*/ Math();", + "/*globals Math: off*/ new Math();", { code: "JSON();", globals: { JSON: "off" } }, + { + code: "new JSON();", + globals: { JSON: "off" } + }, "Reflect();", "Atomics();", + "new Reflect();", + "new Atomics();", { code: "Atomics();", env: { es6: true } @@ -56,20 +81,36 @@ ruleTester.run("no-obj-calls", rule, { // shadowed variables "var Math; Math();", + "var Math; new Math();", { code: "let JSON; JSON();", parserOptions: { ecmaVersion: 2015 } }, + { + code: "let JSON; new JSON();", + parserOptions: { ecmaVersion: 2015 } + }, { code: "if (foo) { const Reflect = 1; Reflect(); }", parserOptions: { ecmaVersion: 2015 }, env: { es6: true } }, + { + code: "if (foo) { const Reflect = 1; new Reflect(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { es6: true } + }, "function foo(Math) { Math(); }", + "function foo(JSON) { new JSON(); }", { code: "function foo(Atomics) { Atomics(); }", env: { es2017: true } }, + { + code: "function foo() { if (bar) { let Atomics; if (baz) { new Atomics(); } } }", + parserOptions: { ecmaVersion: 2015 }, + env: { es2017: true } + }, "function foo() { var JSON; JSON(); }", { code: "function foo() { var Atomics = bar(); var baz = Atomics(5); }", @@ -81,12 +122,10 @@ ruleTester.run("no-obj-calls", rule, { } ], invalid: [ - { code: "Math();", errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }] }, - { code: "var x = Math();", errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }] @@ -99,6 +138,26 @@ ruleTester.run("no-obj-calls", rule, { code: "Math().foo;", errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 7 }] }, + { + code: "new Math;", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, + { + code: "new Math();", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, + { + code: "new Math(foo);", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, + { + code: "new Math().foo;", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, + { + code: "(new Math).foo();", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, { code: "var x = JSON();", errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }] @@ -107,6 +166,10 @@ ruleTester.run("no-obj-calls", rule, { code: "x = JSON(str);", errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }] }, + { + code: "var x = new JSON();", + errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "NewExpression" }] + }, { code: "Math( JSON() );", errors: [ @@ -119,6 +182,11 @@ ruleTester.run("no-obj-calls", rule, { env: { es6: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] }, + { + code: "var x = new Reflect();", + env: { es6: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "NewExpression" }] + }, { code: "var x = Reflect();", env: { es2017: true }, @@ -128,11 +196,20 @@ ruleTester.run("no-obj-calls", rule, { code: "/*globals Reflect: true*/ Reflect();", errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] }, + { + code: "/*globals Reflect: true*/ new Reflect();", + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "NewExpression" }] + }, { code: "var x = Atomics();", env: { es2017: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] }, + { + code: "var x = new Atomics();", + env: { es2017: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "NewExpression" }] + }, { code: "var x = Atomics();", env: { es2020: true }, @@ -143,11 +220,21 @@ ruleTester.run("no-obj-calls", rule, { globals: { Atomics: false }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] }, + { + code: "var x = new Atomics();", + globals: { Atomics: "writable" }, + errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "NewExpression" }] + }, { code: "var x = globalThis.Math();", env: { es2020: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }] }, + { + code: "var x = new globalThis.Math();", + env: { es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression" }] + }, { code: "f(globalThis.Math());", env: { es2020: true }, @@ -158,6 +245,11 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 18 }] }, + { + code: "new globalThis.Math().foo;", + env: { es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "NewExpression", column: 1, endColumn: 22 }] + }, { code: "var x = globalThis.JSON();", env: { es2020: true }, @@ -181,6 +273,11 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] }, + { + code: "var x = new globalThis.Reflect;", + env: { es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "NewExpression" }] + }, { code: "/*globals Reflect: true*/ Reflect();", env: { es2020: true }, @@ -195,15 +292,29 @@ ruleTester.run("no-obj-calls", rule, { code: "var foo = bar ? baz: JSON; foo();", errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }] }, + { + code: "var foo = bar ? baz: JSON; new foo();", + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "NewExpression" }] + }, { code: "var foo = bar ? baz: globalThis.JSON; foo();", env: { es2020: true }, errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }] }, + { + code: "var foo = bar ? baz: globalThis.JSON; new foo();", + env: { es2020: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "NewExpression" }] + }, { code: "var foo = window.Atomics; foo();", env: { es2020: true, browser: true }, errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "CallExpression" }] + }, + { + code: "var foo = window.Atomics; new foo;", + env: { es2020: true, browser: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "NewExpression" }] } ] });