Skip to content

Commit

Permalink
Update: support globalThis (refs #12670) (#12774)
Browse files Browse the repository at this point in the history
* support globalThis in no-alert (ref #12670)

* support globalThis in no-eval (ref #12670)

* bump eslint-utils to ^2.0.0 (ref #12670)

* add globalThis test cases in require-unicode-regexp (ref #12670)

* add globalThis test cases in prefer-regex-literals (ref #12670)

* add globalThis test cases in prefer-object-spread (ref #12670)

* add globalThis test cases in prefer-named-capture-group (ref #12670)

* add globalThis test cases in prefer-exponentiation-operator (ref #12670)

* add globalThis test cases in no-misleading-character-class (ref #12670)

* edit test cases in no-eval

* support globalThis in no-obj-calls (ref #12670)

* add globalThis test cases in no-redeclare (#12670)

* change to use getPropertyName

* fix tpo

* add messageID - unexpectedRefCall
  • Loading branch information
yeonjuan committed Mar 17, 2020
1 parent af7af9d commit 183e300
Show file tree
Hide file tree
Showing 15 changed files with 419 additions and 16 deletions.
3 changes: 2 additions & 1 deletion conf/environments.js
Expand Up @@ -40,7 +40,8 @@ const newGlobals2017 = {
const newGlobals2020 = {
BigInt: false,
BigInt64Array: false,
BigUint64Array: false
BigUint64Array: false,
globalThis: false
};

//------------------------------------------------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions lib/rules/no-alert.js
Expand Up @@ -8,7 +8,10 @@
// Requirements
//------------------------------------------------------------------------------

const getPropertyName = require("./utils/ast-utils").getStaticPropertyName;
const {
getStaticPropertyName: getPropertyName,
getVariableByName
} = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -61,7 +64,7 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
if (scope.type === "global" && node.type === "ThisExpression") {
return true;
}
if (node.name === "window") {
if (node.name === "window" || (node.name === "globalThis" && getVariableByName(scope, "globalThis"))) {
return !isShadowed(scope, node);
}

Expand Down Expand Up @@ -119,7 +122,6 @@ module.exports = {
});
}
}

}
};

Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-eval.js
Expand Up @@ -17,7 +17,8 @@ const astUtils = require("./utils/ast-utils");

const candidatesOfGlobalObject = Object.freeze([
"global",
"window"
"window",
"globalThis"
]);

/**
Expand Down
24 changes: 21 additions & 3 deletions lib/rules/no-obj-calls.js
Expand Up @@ -10,13 +10,26 @@
//------------------------------------------------------------------------------

const { CALL, ReferenceTracker } = require("eslint-utils");
const getPropertyName = require("./utils/ast-utils").getStaticPropertyName;

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

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

/**
* Returns the name of the node to report
* @param {ASTNode} node A node to report
* @returns {string} name to report
*/
function getReportNodeName(node) {
if (node.callee.type === "MemberExpression") {
return getPropertyName(node.callee);
}
return node.callee.name;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand All @@ -35,7 +48,8 @@ module.exports = {
schema: [],

messages: {
unexpectedCall: "'{{name}}' is not a function."
unexpectedCall: "'{{name}}' is not a function.",
unexpectedRefCall: "'{{name}}' is reference to '{{ref}}', which is not a function."
}
},

Expand All @@ -53,8 +67,12 @@ module.exports = {
};
}

for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
context.report({ node, messageId: "unexpectedCall", data: { name: node.callee.name } });
for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
const name = getReportNodeName(node);
const ref = path[0];
const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";

context.report({ node, messageId, data: { name, ref } });
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -53,7 +53,7 @@
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^1.4.3",
"eslint-utils": "^2.0.0",
"eslint-visitor-keys": "^1.1.0",
"espree": "^6.2.1",
"esquery": "^1.0.1",
Expand Down
22 changes: 21 additions & 1 deletion tests/lib/rules/no-alert.js
Expand Up @@ -34,7 +34,12 @@ ruleTester.run("no-alert", rule, {
"function prompt() {} prompt();",
"window[alert]();",
"function foo() { this.alert(); }",
"function foo() { var window = bar; window.alert(); }"
"function foo() { var window = bar; window.alert(); }",
"globalThis.alert();",
{ code: "globalThis['alert']();", env: { es6: true } },
{ code: "globalThis.alert();", env: { es2017: true } },
{ code: "var globalThis = foo; globalThis.alert();", env: { es2020: true } },
{ code: "function foo() { var globalThis = foo; globalThis.alert(); }", env: { es2020: true } }
],
invalid: [
{
Expand Down Expand Up @@ -104,6 +109,21 @@ ruleTester.run("no-alert", rule, {
{
code: "function foo() { var window = bar; window.alert(); }\nwindow.alert();",
errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 2, column: 1 }]
},
{
code: "globalThis['alert'](foo)",
env: { es2020: true },
errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 1, column: 1 }]
},
{
code: "globalThis.alert();",
env: { es2020: true },
errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 1, column: 1 }]
},
{
code: "function foo() { var globalThis = bar; globalThis.alert(); }\nglobalThis.alert();",
env: { es2020: true },
errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 2, column: 1 }]
}
]
});
20 changes: 18 additions & 2 deletions tests/lib/rules/no-eval.js
Expand Up @@ -35,6 +35,11 @@ ruleTester.run("no-eval", rule, {
{ code: "global.eval('foo')", env: { browser: true } },
{ code: "global.noeval('foo')", env: { node: true } },
{ code: "function foo() { var eval = 'foo'; global[eval]('foo') }", env: { node: true } },
"globalThis.eval('foo')",
{ code: "globalThis.eval('foo')", env: { es2017: true } },
{ code: "globalThis.eval('foo')", env: { browser: true } },
{ code: "globalThis.noneval('foo')", env: { es2020: true } },
{ code: "function foo() { var eval = 'foo'; globalThis[eval]('foo') }", env: { es2020: true } },
"this.noeval('foo');",
"function foo() { 'use strict'; this.eval('foo'); }",
{ code: "function foo() { this.eval('foo'); }", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
Expand All @@ -57,7 +62,12 @@ ruleTester.run("no-eval", rule, {
{ code: "global.eval('foo')", options: [{ allowIndirect: true }], env: { node: true } },
{ code: "global.global.eval('foo')", options: [{ allowIndirect: true }], env: { node: true } },
{ code: "this.eval('foo')", options: [{ allowIndirect: true }] },
{ code: "function foo() { this.eval('foo') }", options: [{ allowIndirect: true }] }
{ code: "function foo() { this.eval('foo') }", options: [{ allowIndirect: true }] },
{ code: "(0, globalThis.eval)('foo')", options: [{ allowIndirect: true }], env: { es2020: true } },
{ code: "(0, globalThis['eval'])('foo')", options: [{ allowIndirect: true }], env: { es2020: true } },
{ code: "var EVAL = globalThis.eval; EVAL('foo')", options: [{ allowIndirect: true }] },
{ code: "function foo() { globalThis.eval('foo') }", options: [{ allowIndirect: true }], env: { es2020: true } },
{ code: "globalThis.globalThis.eval('foo');", options: [{ allowIndirect: true }], env: { es2020: true } }
],

invalid: [
Expand All @@ -84,6 +94,12 @@ ruleTester.run("no-eval", rule, {
{ code: "global.global.eval('foo')", env: { node: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 15, endColumn: 19 }] },
{ code: "global.global[`eval`]('foo')", parserOptions: { ecmaVersion: 6 }, env: { node: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 15, endColumn: 21 }] },
{ code: "this.eval('foo')", errors: [{ messageId: "unexpected", type: "CallExpression", column: 6, endColumn: 10 }] },
{ code: "function foo() { this.eval('foo') }", errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 27 }] }
{ code: "function foo() { this.eval('foo') }", errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 27 }] },
{ code: "var EVAL = globalThis.eval; EVAL('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 23, endColumn: 27 }] },
{ code: "globalThis.eval('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 12, endColumn: 16 }] },
{ code: "globalThis.globalThis.eval('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 27 }] },
{ code: "globalThis.globalThis['eval']('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 29 }] },
{ code: "(0, globalThis.eval)('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 20 }] },
{ code: "(0, globalThis['eval'])('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 22 }] }
]
});
24 changes: 23 additions & 1 deletion tests/lib/rules/no-misleading-character-class.js
Expand Up @@ -67,7 +67,9 @@ ruleTester.run("no-misleading-character-class", rule, {

// don't report and don't crash on invalid regex
"var r = new RegExp('[Á] [ ');",
"var r = RegExp('{ [Á]', 'u');"
"var r = RegExp('{ [Á]', 'u');",
{ code: "var r = new globalThis.RegExp('[Á] [ ');", env: { es2020: true } },
{ code: "var r = globalThis.RegExp('{ [Á]', 'u');", env: { es2020: true } }
],
invalid: [

Expand Down Expand Up @@ -271,6 +273,26 @@ ruleTester.run("no-misleading-character-class", rule, {
{
code: String.raw`var r = new RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`,
errors: [{ messageId: "zwj" }]
},
{
code: String.raw`var r = new globalThis.RegExp("[❇️]", "")`,
env: { es2020: true },
errors: [{ messageId: "combiningClass" }]
},
{
code: String.raw`var r = new globalThis.RegExp("[👶🏻]", "u")`,
env: { es2020: true },
errors: [{ messageId: "emojiModifier" }]
},
{
code: String.raw`var r = new globalThis.RegExp("[🇯🇵]", "")`,
env: { es2020: true },
errors: [{ messageId: "surrogatePairWithoutUFlag" }]
},
{
code: String.raw`var r = new globalThis.RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`,
env: { es2020: true },
errors: [{ messageId: "zwj" }]
}
]
});
75 changes: 75 additions & 0 deletions tests/lib/rules/no-obj-calls.js
Expand Up @@ -28,6 +28,19 @@ ruleTester.run("no-obj-calls", rule, {
"Reflect.get(foo, 'x')",
"Atomics.load(foo, 0)",

{ code: "globalThis.Math();", env: { es6: true } },
{ code: "var x = globalThis.Math();", env: { es6: true } },
{ code: "f(globalThis.Math());", env: { es6: true } },
{ code: "globalThis.Math().foo;", env: { es6: true } },
{ code: "var x = globalThis.JSON();", env: { es6: true } },
{ code: "x = globalThis.JSON(str);", env: { es6: true } },
{ code: "globalThis.Math( globalThis.JSON() );", env: { es6: true } },
{ code: "var x = globalThis.Reflect();", env: { es6: true } },
{ code: "var x = globalThis.Reflect();", env: { es2017: true } },
{ code: "/*globals Reflect: true*/ globalThis.Reflect();", env: { es2017: true } },
{ code: "var x = globalThis.Atomics();", env: { es2017: true } },
{ code: "var x = globalThis.Atomics();", globals: { Atomics: false }, env: { es2017: true } },

// non-existing variables
"/*globals Math: off*/ Math();",
{
Expand Down Expand Up @@ -129,6 +142,68 @@ ruleTester.run("no-obj-calls", rule, {
code: "var x = Atomics();",
globals: { Atomics: false },
errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }]
},
{
code: "var x = globalThis.Math();",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression" }]
},
{
code: "f(globalThis.Math());",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 3, endColumn: 20 }]
},
{
code: "globalThis.Math().foo;",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 18 }]
},
{
code: "var x = globalThis.JSON();",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }]
},
{
code: "x = globalThis.JSON(str);",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression" }]
},
{
code: "globalThis.Math( globalThis.JSON() );",
env: { es2020: true },
errors: [
{ messageId: "unexpectedCall", data: { name: "Math" }, type: "CallExpression", column: 1, endColumn: 37 },
{ messageId: "unexpectedCall", data: { name: "JSON" }, type: "CallExpression", column: 18, endColumn: 35 }
]
},
{
code: "var x = globalThis.Reflect();",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
},
{
code: "/*globals Reflect: true*/ Reflect();",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
},
{
code: "var x = globalThis.Atomics();",
env: { es2020: true },
errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }]
},
{
code: "var foo = bar ? baz: JSON; foo();",
errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }]
},
{
code: "var foo = bar ? baz: globalThis.JSON; foo();",
env: { es2020: true },
errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }]
},
{
code: "var foo = window.Atomics; foo();",
env: { es2020: true, browser: true },
errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "CallExpression" }]
}
]
});
18 changes: 18 additions & 0 deletions tests/lib/rules/no-redeclare.js
Expand Up @@ -41,6 +41,8 @@ ruleTester.run("no-redeclare", rule, {
options: [{ builtinGlobals: true }],
env: { browser: false }
},
{ code: "var glovalThis = foo", options: [{ builtinGlobals: true }], env: { es6: true } },
{ code: "var glovalThis = foo", options: [{ builtinGlobals: true }], env: { es2017: true } },

// Comments and built-ins.
{
Expand Down Expand Up @@ -124,6 +126,22 @@ ruleTester.run("no-redeclare", rule, {
{ message: "'a' is already defined.", type: "Identifier" }
]
},
{
code: "var globalThis = 0;",
options: [{ builtinGlobals: true }],
env: { es2020: true },
errors: [{ message: "'globalThis' is already defined as a built-in global variable.", type: "Identifier" }]
},
{
code: "var a; var {a = 0, b: globalThis = 0} = {};",
options: [{ builtinGlobals: true }],
parserOptions: { ecmaVersion: 6 },
env: { es2020: true },
errors: [
{ message: "'a' is already defined.", type: "Identifier" },
{ message: "'globalThis' is already defined as a built-in global variable.", type: "Identifier" }
]
},
{
code: "/*global b:false*/ var b = 1;",
options: [{ builtinGlobals: true }],
Expand Down

0 comments on commit 183e300

Please sign in to comment.