Skip to content

Commit

Permalink
Update: Report global Atomics calls in no-obj-calls (fixes #12234) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mdjermanovic authored and ilyavolodin committed Sep 14, 2019
1 parent 985c9e5 commit 37c0fde
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 15 deletions.
10 changes: 8 additions & 2 deletions docs/rules/no-obj-calls.md
Expand Up @@ -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:

Expand All @@ -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:
Expand All @@ -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
Expand Down
38 changes: 29 additions & 9 deletions lib/rules/no-obj-calls.js
Expand Up @@ -5,6 +5,18 @@

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const { CALL, ReferenceTracker } = require("eslint-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand All @@ -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 } });
}
}
};

}
};
109 changes: 105 additions & 4 deletions tests/lib/rules/no-obj-calls.js
Expand Up @@ -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" }]
}
]
});

0 comments on commit 37c0fde

Please sign in to comment.