diff --git a/docs/rules/no-obj-calls.md b/docs/rules/no-obj-calls.md index 9484eb73688..cfd5576eae5 100644 --- a/docs/rules/no-obj-calls.md +++ b/docs/rules/no-obj-calls.md @@ -6,13 +6,17 @@ The [ECMAScript 5 specification](https://es5.github.io/#x15.8) makes it clear th > The Math object does not have a `[[Call]]` internal property; it is not possible to invoke the Math object as a function. -And the [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked: +The [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect-object) makes it clear that `Reflect` cannot be invoked: > The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function. +And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: + +> The Atomics object does not have a [[Call]] internal method; it is not possible to invoke the Atomics object as a function. + ## Rule Details -This rule disallows calling the `Math`, `JSON` and `Reflect` objects as functions. +This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions. Examples of **incorrect** code for this rule: @@ -22,6 +26,7 @@ Examples of **incorrect** code for this rule: var math = Math(); var json = JSON(); var reflect = Reflect(); +var atomics = Atomics(); ``` Examples of **correct** code for this rule: @@ -34,6 +39,7 @@ function area(r) { } var object = JSON.parse("{}"); var value = Reflect.get({ x: 1, y: 2 }, "x"); +var first = Atomics.load(foo, 0); ``` ## Further Reading diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index 92492b7a26e..5102d559494 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -5,6 +5,18 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { CALL, ReferenceTracker } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"]; + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -20,23 +32,31 @@ module.exports = { url: "https://eslint.org/docs/rules/no-obj-calls" }, - schema: [] + schema: [], + + messages: { + unexpectedCall: "'{{name}}' is not a function." + } }, create(context) { return { - CallExpression(node) { - - if (node.callee.type === "Identifier") { - const name = node.callee.name; + Program() { + const scope = context.getScope(); + const tracker = new ReferenceTracker(scope); + const traceMap = {}; + + for (const global of nonCallableGlobals) { + traceMap[global] = { + [CALL]: true + }; + } - if (name === "Math" || name === "JSON" || name === "Reflect") { - context.report({ node, message: "'{{name}}' is not a function.", data: { name } }); - } + for (const { node } of tracker.iterateGlobalReferences(traceMap)) { + context.report({ node, messageId: "unexpectedCall", data: { name: node.callee.name } }); } } }; - } }; diff --git a/tests/lib/rules/no-obj-calls.js b/tests/lib/rules/no-obj-calls.js index 6f390ab4575..d5bcf063818 100644 --- a/tests/lib/rules/no-obj-calls.js +++ b/tests/lib/rules/no-obj-calls.js @@ -20,11 +20,112 @@ const ruleTester = new RuleTester(); ruleTester.run("no-obj-calls", rule, { valid: [ - "var x = Math.random();" + "var x = Math;", + "var x = Math.random();", + "var x = Math.PI;", + "var x = foo.Math();", + "JSON.parse(foo)", + "Reflect.get(foo, 'x')", + "Atomics.load(foo, 0)", + + // non-existing variables + "/*globals Math: off*/ Math();", + { + code: "JSON();", + globals: { JSON: "off" } + }, + "Reflect();", + "Atomics();", + { + code: "Atomics();", + env: { es6: true } + }, + + // shadowed variables + "var Math; Math();", + { + code: "let JSON; JSON();", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "if (foo) { const Reflect = 1; Reflect(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { es6: true } + }, + "function foo(Math) { Math(); }", + { + code: "function foo(Atomics) { Atomics(); }", + env: { es2017: true } + }, + "function foo() { var JSON; JSON(); }", + { + code: "function foo() { var Atomics = bar(); var baz = Atomics(5); }", + globals: { Atomics: false } + } ], invalid: [ - { code: "var x = Math();", errors: [{ message: "'Math' is not a function.", type: "CallExpression" }] }, - { code: "var x = JSON();", errors: [{ message: "'JSON' is not a function.", type: "CallExpression" }] }, - { code: "var x = Reflect();", errors: [{ message: "'Reflect' is not a function.", type: "CallExpression" }] } + + // test full message + { + code: "Math();", + errors: [{ message: "'Math' is not a function.", type: "CallExpression" }] + }, + + { + code: "var x = Math();", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }] + }, + { + code: "f(Math());", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 3, endColumn: 9 }] + }, + { + code: "Math().foo;", + errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 7 }] + }, + { + code: "var x = JSON();", + errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }] + }, + { + code: "x = JSON(str);", + errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }] + }, + { + code: "Math( JSON() );", + errors: [ + { messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 15 }, + { messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression", column: 7, endColumn: 13 } + ] + }, + { + code: "var x = Reflect();", + env: { es6: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] + }, + { + code: "var x = Reflect();", + env: { es2017: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] + }, + { + code: "/*globals Reflect: true*/ Reflect();", + errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }] + }, + { + code: "var x = Atomics();", + env: { es2017: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] + }, + { + code: "var x = Atomics();", + env: { es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] + }, + { + code: "var x = Atomics();", + globals: { Atomics: false }, + errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] + } ] });