Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion docs/src/rules/no-restricted-globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ This rule allows you to specify global variable names that you don't want to use

## Options

This rule takes a list of strings, where each string is a global to be restricted:
This rule has both string and object options to specify the global variables to restrict.

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:

```json
{
Expand Down Expand Up @@ -107,6 +109,88 @@ function onClick() {

:::

### globals

An object option whose value is an array containing the names of the globals you want to restrict.

Examples of **incorrect** code for `"event"` and `"fdescribe"` global variable names:

::: incorrect

```js
/*global event, fdescribe*/
/*eslint no-restricted-globals: ["error", { globals: ["event", "fdescribe"] }]*/

function onClick() {
console.log(event);
}

fdescribe("foo", function() {
});
```

:::

Custom messages for a particular global can also be specified in `globals` array using objects with `name` and `message`:

Examples of **incorrect** code for an `"event"` global variable name, along with a custom error message:

::: incorrect

```js
/*global event*/
/* eslint no-restricted-globals: ["error", { globals: [{ name: "event", message: "Use local parameter instead." }] }] */

function onClick() {
console.log(event);
}
```

:::

### checkGlobalObject

A boolean option that enables detection of restricted globals accessed via global objects. Default is `false`.

Examples of **incorrect** code for `checkGlobalObject: true` option:

::: incorrect

```js
/*global globalThis, self, window*/
/*eslint no-restricted-globals: ["error", { globals: ["Promise"], checkGlobalObject: true }]*/

globalThis.Promise
self.Promise
window.Promise
```

:::

### globalObjects

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`.

Examples of **incorrect** code for `globalObjects` option:

::: incorrect

```js
/*global globalThis, self, window, myGlobal*/
/*eslint no-restricted-globals: ["error", {
globals: ["Promise"],
checkGlobalObject: true,
globalObjects: ["myGlobal"]
}]*/

globalThis.Promise
self.Promise
window.Promise
myGlobal.Promise;
```

:::

Restricted globals used in TypeScript type annotations—such as type references, interface inheritance, or class implementations—are ignored by this rule.

Examples of **correct** TypeScript code for "Promise", "Event", and "Window" global variable names:
Expand Down
152 changes: 131 additions & 21 deletions lib/rules/no-restricted-globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
*/
"use strict";

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

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
Expand All @@ -16,10 +22,34 @@ const TYPE_NODES = new Set([
"TSQualifiedName",
]);

const GLOBAL_OBJECTS = new Set(["globalThis", "self", "window"]);

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const arrayOfGlobals = {
type: "array",
items: {
oneOf: [
{
type: "string",
},
{
type: "object",
properties: {
name: { type: "string" },
message: { type: "string" },
},
required: ["name"],
additionalProperties: false,
},
],
},
uniqueItems: true,
minItems: 0,
};

/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
Expand All @@ -34,25 +64,33 @@ module.exports = {
},

schema: {
type: "array",
items: {
oneOf: [
{
type: "string",
},
{
type: "object",
properties: {
name: { type: "string" },
message: { type: "string" },
anyOf: [
arrayOfGlobals,
{
type: "array",
items: [
{
type: "object",
properties: {
globals: arrayOfGlobals,
checkGlobalObject: {
type: "boolean",
},
globalObjects: {
type: "array",
items: {
type: "string",
},
uniqueItems: true,
},
},
required: ["globals"],
additionalProperties: false,
},
required: ["name"],
additionalProperties: false,
},
],
},
uniqueItems: true,
minItems: 0,
],
additionalItems: false,
},
],
},

messages: {
Expand All @@ -63,14 +101,33 @@ module.exports = {
},

create(context) {
const sourceCode = context.sourceCode;
const { sourceCode, options } = context;

const isGlobalsObject =
typeof options[0] === "object" &&
Object.hasOwn(options[0], "globals");

const restrictedGlobals = isGlobalsObject
? options[0].globals
: options;
const checkGlobalObject = isGlobalsObject
? options[0].checkGlobalObject
: false;
const userGlobalObjects = isGlobalsObject
? options[0].globalObjects || []
: [];

const globalObjects = new Set([
...GLOBAL_OBJECTS,
...userGlobalObjects,
]);

// If no globals are restricted, we don't need to do anything
if (context.options.length === 0) {
if (restrictedGlobals.length === 0) {
return {};
}

const restrictedGlobalMessages = context.options.reduce(
const restrictedGlobalMessages = restrictedGlobals.reduce(
(memo, option) => {
if (typeof option === "string") {
memo[option] = null;
Expand Down Expand Up @@ -151,6 +208,59 @@ module.exports = {
}
});
},

"Program:exit"(node) {
if (!checkGlobalObject) {
return;
}

const globalScope = sourceCode.getScope(node);
globalObjects.forEach(globalObjectName => {
const variable = astUtils.getVariableByName(
globalScope,
globalObjectName,
);

if (!variable) {
return;
}

variable.references.forEach(reference => {
const identifier = reference.identifier;
let parent = identifier.parent;

// To detect code like `window.window.Promise`.
while (
astUtils.isSpecificMemberAccess(
parent,
null,
globalObjectName,
)
) {
parent = parent.parent;
}

const propertyName =
astUtils.getStaticPropertyName(parent);
if (propertyName && isRestricted(propertyName)) {
const customMessage =
restrictedGlobalMessages[propertyName];
const messageId = customMessage
? "customMessage"
: "defaultMessage";

context.report({
node: parent.property,
messageId,
data: {
name: propertyName,
customMessage,
},
});
}
});
});
},
};
},
};
27 changes: 20 additions & 7 deletions lib/types/rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3391,13 +3391,26 @@ export interface ESLintRules extends Linter.RulesRecord {
*/
"no-restricted-globals": Linter.RuleEntry<
[
...Array<
| string
| {
name: string;
message?: string | undefined;
}
>,
...(
| Array<
| string
| {
name: string;
message?: string | undefined;
}
>
| Array<{
globals: Array<
| string
| {
name: string;
message?: string | undefined;
}
>;
checkGlobalObject?: boolean;
globalObjects?: string[];
}>
),
]
>;

Expand Down
Loading
Loading