Skip to content

Commit e07820e

Browse files
authored
feat: add global object access detection to no-restricted-globals (#19939)
* feat: add global object access detection to no-restricted-globals * fix ci * add tests * add self to default global objects * refactor
1 parent fa20b9d commit e07820e

File tree

4 files changed

+1085
-29
lines changed

4 files changed

+1085
-29
lines changed

docs/src/rules/no-restricted-globals.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ This rule allows you to specify global variable names that you don't want to use
2020

2121
## Options
2222

23-
This rule takes a list of strings, where each string is a global to be restricted:
23+
This rule has both string and object options to specify the global variables to restrict.
24+
25+
Using the string option, you can specify the name of a global variable that you want to restrict as a value in the rule options array:
2426

2527
```json
2628
{
@@ -107,6 +109,88 @@ function onClick() {
107109

108110
:::
109111

112+
### globals
113+
114+
An object option whose value is an array containing the names of the globals you want to restrict.
115+
116+
Examples of **incorrect** code for `"event"` and `"fdescribe"` global variable names:
117+
118+
::: incorrect
119+
120+
```js
121+
/*global event, fdescribe*/
122+
/*eslint no-restricted-globals: ["error", { globals: ["event", "fdescribe"] }]*/
123+
124+
function onClick() {
125+
console.log(event);
126+
}
127+
128+
fdescribe("foo", function() {
129+
});
130+
```
131+
132+
:::
133+
134+
Custom messages for a particular global can also be specified in `globals` array using objects with `name` and `message`:
135+
136+
Examples of **incorrect** code for an `"event"` global variable name, along with a custom error message:
137+
138+
::: incorrect
139+
140+
```js
141+
/*global event*/
142+
/* eslint no-restricted-globals: ["error", { globals: [{ name: "event", message: "Use local parameter instead." }] }] */
143+
144+
function onClick() {
145+
console.log(event);
146+
}
147+
```
148+
149+
:::
150+
151+
### checkGlobalObject
152+
153+
A boolean option that enables detection of restricted globals accessed via global objects. Default is `false`.
154+
155+
Examples of **incorrect** code for `checkGlobalObject: true` option:
156+
157+
::: incorrect
158+
159+
```js
160+
/*global globalThis, self, window*/
161+
/*eslint no-restricted-globals: ["error", { globals: ["Promise"], checkGlobalObject: true }]*/
162+
163+
globalThis.Promise
164+
self.Promise
165+
window.Promise
166+
```
167+
168+
:::
169+
170+
### globalObjects
171+
172+
An array option that specifies additional global object names to check when `checkGlobalObject` is enabled. By default, the rule checks these global objects: `globalThis`, `self`, and `window`.
173+
174+
Examples of **incorrect** code for `globalObjects` option:
175+
176+
::: incorrect
177+
178+
```js
179+
/*global globalThis, self, window, myGlobal*/
180+
/*eslint no-restricted-globals: ["error", {
181+
globals: ["Promise"],
182+
checkGlobalObject: true,
183+
globalObjects: ["myGlobal"]
184+
}]*/
185+
186+
globalThis.Promise
187+
self.Promise
188+
window.Promise
189+
myGlobal.Promise;
190+
```
191+
192+
:::
193+
110194
Restricted globals used in TypeScript type annotations—such as type references, interface inheritance, or class implementations—are ignored by this rule.
111195

112196
Examples of **correct** TypeScript code for "Promise", "Event", and "Window" global variable names:

lib/rules/no-restricted-globals.js

Lines changed: 131 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
*/
55
"use strict";
66

7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const astUtils = require("./utils/ast-utils");
12+
713
//------------------------------------------------------------------------------
814
// Helpers
915
//------------------------------------------------------------------------------
@@ -16,10 +22,34 @@ const TYPE_NODES = new Set([
1622
"TSQualifiedName",
1723
]);
1824

25+
const GLOBAL_OBJECTS = new Set(["globalThis", "self", "window"]);
26+
1927
//------------------------------------------------------------------------------
2028
// Rule Definition
2129
//------------------------------------------------------------------------------
2230

31+
const arrayOfGlobals = {
32+
type: "array",
33+
items: {
34+
oneOf: [
35+
{
36+
type: "string",
37+
},
38+
{
39+
type: "object",
40+
properties: {
41+
name: { type: "string" },
42+
message: { type: "string" },
43+
},
44+
required: ["name"],
45+
additionalProperties: false,
46+
},
47+
],
48+
},
49+
uniqueItems: true,
50+
minItems: 0,
51+
};
52+
2353
/** @type {import('../types').Rule.RuleModule} */
2454
module.exports = {
2555
meta: {
@@ -34,25 +64,33 @@ module.exports = {
3464
},
3565

3666
schema: {
37-
type: "array",
38-
items: {
39-
oneOf: [
40-
{
41-
type: "string",
42-
},
43-
{
44-
type: "object",
45-
properties: {
46-
name: { type: "string" },
47-
message: { type: "string" },
67+
anyOf: [
68+
arrayOfGlobals,
69+
{
70+
type: "array",
71+
items: [
72+
{
73+
type: "object",
74+
properties: {
75+
globals: arrayOfGlobals,
76+
checkGlobalObject: {
77+
type: "boolean",
78+
},
79+
globalObjects: {
80+
type: "array",
81+
items: {
82+
type: "string",
83+
},
84+
uniqueItems: true,
85+
},
86+
},
87+
required: ["globals"],
88+
additionalProperties: false,
4889
},
49-
required: ["name"],
50-
additionalProperties: false,
51-
},
52-
],
53-
},
54-
uniqueItems: true,
55-
minItems: 0,
90+
],
91+
additionalItems: false,
92+
},
93+
],
5694
},
5795

5896
messages: {
@@ -63,14 +101,33 @@ module.exports = {
63101
},
64102

65103
create(context) {
66-
const sourceCode = context.sourceCode;
104+
const { sourceCode, options } = context;
105+
106+
const isGlobalsObject =
107+
typeof options[0] === "object" &&
108+
Object.hasOwn(options[0], "globals");
109+
110+
const restrictedGlobals = isGlobalsObject
111+
? options[0].globals
112+
: options;
113+
const checkGlobalObject = isGlobalsObject
114+
? options[0].checkGlobalObject
115+
: false;
116+
const userGlobalObjects = isGlobalsObject
117+
? options[0].globalObjects || []
118+
: [];
119+
120+
const globalObjects = new Set([
121+
...GLOBAL_OBJECTS,
122+
...userGlobalObjects,
123+
]);
67124

68125
// If no globals are restricted, we don't need to do anything
69-
if (context.options.length === 0) {
126+
if (restrictedGlobals.length === 0) {
70127
return {};
71128
}
72129

73-
const restrictedGlobalMessages = context.options.reduce(
130+
const restrictedGlobalMessages = restrictedGlobals.reduce(
74131
(memo, option) => {
75132
if (typeof option === "string") {
76133
memo[option] = null;
@@ -151,6 +208,59 @@ module.exports = {
151208
}
152209
});
153210
},
211+
212+
"Program:exit"(node) {
213+
if (!checkGlobalObject) {
214+
return;
215+
}
216+
217+
const globalScope = sourceCode.getScope(node);
218+
globalObjects.forEach(globalObjectName => {
219+
const variable = astUtils.getVariableByName(
220+
globalScope,
221+
globalObjectName,
222+
);
223+
224+
if (!variable) {
225+
return;
226+
}
227+
228+
variable.references.forEach(reference => {
229+
const identifier = reference.identifier;
230+
let parent = identifier.parent;
231+
232+
// To detect code like `window.window.Promise`.
233+
while (
234+
astUtils.isSpecificMemberAccess(
235+
parent,
236+
null,
237+
globalObjectName,
238+
)
239+
) {
240+
parent = parent.parent;
241+
}
242+
243+
const propertyName =
244+
astUtils.getStaticPropertyName(parent);
245+
if (propertyName && isRestricted(propertyName)) {
246+
const customMessage =
247+
restrictedGlobalMessages[propertyName];
248+
const messageId = customMessage
249+
? "customMessage"
250+
: "defaultMessage";
251+
252+
context.report({
253+
node: parent.property,
254+
messageId,
255+
data: {
256+
name: propertyName,
257+
customMessage,
258+
},
259+
});
260+
}
261+
});
262+
});
263+
},
154264
};
155265
},
156266
};

lib/types/rules.d.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3403,13 +3403,26 @@ export interface ESLintRules extends Linter.RulesRecord {
34033403
*/
34043404
"no-restricted-globals": Linter.RuleEntry<
34053405
[
3406-
...Array<
3407-
| string
3408-
| {
3409-
name: string;
3410-
message?: string | undefined;
3411-
}
3412-
>,
3406+
...(
3407+
| Array<
3408+
| string
3409+
| {
3410+
name: string;
3411+
message?: string | undefined;
3412+
}
3413+
>
3414+
| Array<{
3415+
globals: Array<
3416+
| string
3417+
| {
3418+
name: string;
3419+
message?: string | undefined;
3420+
}
3421+
>;
3422+
checkGlobalObject?: boolean;
3423+
globalObjects?: string[];
3424+
}>
3425+
),
34133426
]
34143427
>;
34153428

0 commit comments

Comments
 (0)