Skip to content

Commit 05b66d0

Browse files
snitin315fisker
andauthored
feat: add sourceCode.isGlobalReference(node) method (#19695)
* feat: add `sourceCode.isGlobalReference(node)` method * fix: update tests & docs * chore: fix lint & remove unwanted chnges * refactor: don't use findVariable * fix: cover more cases * refactor: update lib/languages/js/source-code/source-code.js Co-authored-by: fisker Cheung <lionkay@gmail.com> * fix: cache isGlobalReference results * chore: fix test cases * chore: update types * docs: update custom-rules.md * chore: fix formatting * chore: revert unwanted changes --------- Co-authored-by: fisker Cheung <lionkay@gmail.com>
1 parent 53c3235 commit 05b66d0

File tree

7 files changed

+730
-102
lines changed

7 files changed

+730
-102
lines changed

docs/src/extend/custom-rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ Once you have an instance of `SourceCode`, you can use the following methods on
543543
- `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)).
544544
- `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)).
545545
- `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node.
546+
- `isGlobalReference(node)`: Returns true if the identifier references a global variable configured via `languageOptions.globals`, `/* global */` comments, or `ecmaVersion`, and not declared by a local binding.
546547
- `getFirstToken(node, skipOptions)`: Returns the first token representing the given node.
547548
- `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node.
548549
- `getLastToken(node, skipOptions)`: Returns the last token representing the given node.

lib/languages/js/source-code/source-code.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ class SourceCode extends TokenStore {
422422
["scopes", new WeakMap()],
423423
["vars", new Map()],
424424
["configNodes", void 0],
425+
["isGlobalReference", new WeakMap()],
425426
]);
426427

427428
/**
@@ -902,6 +903,41 @@ class SourceCode extends TokenStore {
902903
return ancestorsStartingAtParent.reverse();
903904
}
904905

906+
/**
907+
* Determines whether the given identifier node is a reference to a global variable.
908+
* @param {ASTNode} node `Identifier` node to check.
909+
* @returns {boolean} True if the identifier is a reference to a global variable.
910+
*/
911+
isGlobalReference(node) {
912+
if (!node) {
913+
throw new TypeError("Missing required argument: node.");
914+
}
915+
916+
const cache = this[caches].get("isGlobalReference");
917+
918+
if (cache.has(node)) {
919+
return cache.get(node);
920+
}
921+
922+
if (node.type !== "Identifier") {
923+
cache.set(node, false);
924+
return false;
925+
}
926+
927+
const variable = this.scopeManager.scopes[0].set.get(node.name);
928+
929+
if (!variable || variable.defs.length > 0) {
930+
cache.set(node, false);
931+
return false;
932+
}
933+
934+
const result = variable.references.some(
935+
({ identifier }) => identifier === node,
936+
);
937+
cache.set(node, result);
938+
return result;
939+
}
940+
905941
/**
906942
* Returns the location of the given node or token.
907943
* @param {ASTNode|Token} nodeOrToken The node or token to get the location of.

lib/rules/no-promise-executor-return.js

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
// Requirements
1010
//------------------------------------------------------------------------------
1111

12-
const { findVariable } = require("@eslint-community/eslint-utils");
1312
const astUtils = require("./utils/ast-utils");
1413

1514
//------------------------------------------------------------------------------
@@ -21,51 +20,21 @@ const functionTypesToCheck = new Set([
2120
"FunctionExpression",
2221
]);
2322

24-
/**
25-
* Determines whether the given identifier node is a reference to a global variable.
26-
* @param {ASTNode} node `Identifier` node to check.
27-
* @param {Scope} scope Scope to which the node belongs.
28-
* @returns {boolean} True if the identifier is a reference to a global variable.
29-
*/
30-
function isGlobalReference(node, scope) {
31-
const variable = findVariable(scope, node);
32-
33-
return (
34-
variable !== null &&
35-
variable.scope.type === "global" &&
36-
variable.defs.length === 0
37-
);
38-
}
39-
40-
/**
41-
* Finds function's outer scope.
42-
* @param {Scope} scope Function's own scope.
43-
* @returns {Scope} Function's outer scope.
44-
*/
45-
function getOuterScope(scope) {
46-
const upper = scope.upper;
47-
48-
if (upper.type === "function-expression-name") {
49-
return upper.upper;
50-
}
51-
return upper;
52-
}
53-
5423
/**
5524
* Determines whether the given function node is used as a Promise executor.
5625
* @param {ASTNode} node The node to check.
57-
* @param {Scope} scope Function's own scope.
26+
* @param {SourceCode} sourceCode Source code to which the node belongs.
5827
* @returns {boolean} `true` if the node is a Promise executor.
5928
*/
60-
function isPromiseExecutor(node, scope) {
29+
function isPromiseExecutor(node, sourceCode) {
6130
const parent = node.parent;
6231

6332
return (
6433
parent.type === "NewExpression" &&
6534
parent.arguments[0] === node &&
6635
parent.callee.type === "Identifier" &&
6736
parent.callee.name === "Promise" &&
68-
isGlobalReference(parent.callee, getOuterScope(scope))
37+
sourceCode.isGlobalReference(parent.callee)
6938
);
7039
}
7140

@@ -203,7 +172,7 @@ module.exports = {
203172
upper: funcInfo,
204173
shouldCheck:
205174
functionTypesToCheck.has(node.type) &&
206-
isPromiseExecutor(node, sourceCode.getScope(node)),
175+
isPromiseExecutor(node, sourceCode),
207176
};
208177

209178
if (

lib/rules/no-setter-return.js

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,24 @@
1010
//------------------------------------------------------------------------------
1111

1212
const astUtils = require("./utils/ast-utils");
13-
const { findVariable } = require("@eslint-community/eslint-utils");
1413

1514
//------------------------------------------------------------------------------
1615
// Helpers
1716
//------------------------------------------------------------------------------
1817

19-
/**
20-
* Determines whether the given identifier node is a reference to a global variable.
21-
* @param {ASTNode} node `Identifier` node to check.
22-
* @param {Scope} scope Scope to which the node belongs.
23-
* @returns {boolean} True if the identifier is a reference to a global variable.
24-
*/
25-
function isGlobalReference(node, scope) {
26-
const variable = findVariable(scope, node);
27-
28-
return (
29-
variable !== null &&
30-
variable.scope.type === "global" &&
31-
variable.defs.length === 0
32-
);
33-
}
34-
3518
/**
3619
* Determines whether the given node is an argument of the specified global method call, at the given `index` position.
3720
* E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable.
3821
* @param {ASTNode} node The node to check.
39-
* @param {Scope} scope Scope to which the node belongs.
22+
* @param {SourceCode} sourceCode Source code to which the node belongs.
4023
* @param {string} objectName Name of the global object.
4124
* @param {string} methodName Name of the method.
4225
* @param {number} index The given position.
4326
* @returns {boolean} `true` if the node is argument at the given position.
4427
*/
4528
function isArgumentOfGlobalMethodCall(
4629
node,
47-
scope,
30+
sourceCode,
4831
objectName,
4932
methodName,
5033
index,
@@ -59,31 +42,30 @@ function isArgumentOfGlobalMethodCall(
5942
objectName,
6043
methodName,
6144
) &&
62-
isGlobalReference(
45+
sourceCode.isGlobalReference(
6346
astUtils.skipChainExpression(callNode.callee).object,
64-
scope,
6547
)
6648
);
6749
}
6850

6951
/**
7052
* Determines whether the given node is used as a property descriptor.
7153
* @param {ASTNode} node The node to check.
72-
* @param {Scope} scope Scope to which the node belongs.
54+
* @param {SourceCode} sourceCode Source code to which the node belongs.
7355
* @returns {boolean} `true` if the node is a property descriptor.
7456
*/
75-
function isPropertyDescriptor(node, scope) {
57+
function isPropertyDescriptor(node, sourceCode) {
7658
if (
7759
isArgumentOfGlobalMethodCall(
7860
node,
79-
scope,
61+
sourceCode,
8062
"Object",
8163
"defineProperty",
8264
2,
8365
) ||
8466
isArgumentOfGlobalMethodCall(
8567
node,
86-
scope,
68+
sourceCode,
8769
"Reflect",
8870
"defineProperty",
8971
2,
@@ -101,14 +83,14 @@ function isPropertyDescriptor(node, scope) {
10183
grandparent.type === "ObjectExpression" &&
10284
(isArgumentOfGlobalMethodCall(
10385
grandparent,
104-
scope,
86+
sourceCode,
10587
"Object",
10688
"create",
10789
1,
10890
) ||
10991
isArgumentOfGlobalMethodCall(
11092
grandparent,
111-
scope,
93+
sourceCode,
11294
"Object",
11395
"defineProperties",
11496
1,
@@ -124,10 +106,10 @@ function isPropertyDescriptor(node, scope) {
124106
/**
125107
* Determines whether the given function node is used as a setter function.
126108
* @param {ASTNode} node The node to check.
127-
* @param {Scope} scope Scope to which the node belongs.
109+
* @param {SourceCode} sourceCode Source code to which the node belongs.
128110
* @returns {boolean} `true` if the node is a setter.
129111
*/
130-
function isSetter(node, scope) {
112+
function isSetter(node, sourceCode) {
131113
const parent = node.parent;
132114

133115
if (
@@ -144,7 +126,7 @@ function isSetter(node, scope) {
144126
parent.value === node &&
145127
astUtils.getStaticPropertyName(parent) === "set" &&
146128
parent.parent.type === "ObjectExpression" &&
147-
isPropertyDescriptor(parent.parent, scope)
129+
isPropertyDescriptor(parent.parent, sourceCode)
148130
) {
149131
// Setter in a property descriptor
150132
return true;
@@ -153,21 +135,6 @@ function isSetter(node, scope) {
153135
return false;
154136
}
155137

156-
/**
157-
* Finds function's outer scope.
158-
* @param {Scope} scope Function's own scope.
159-
* @returns {Scope} Function's outer scope.
160-
*/
161-
function getOuterScope(scope) {
162-
const upper = scope.upper;
163-
164-
if (upper.type === "function-expression-name") {
165-
return upper.upper;
166-
}
167-
168-
return upper;
169-
}
170-
171138
//------------------------------------------------------------------------------
172139
// Rule Definition
173140
//------------------------------------------------------------------------------
@@ -200,11 +167,9 @@ module.exports = {
200167
* @returns {void}
201168
*/
202169
function enterFunction(node) {
203-
const outerScope = getOuterScope(sourceCode.getScope(node));
204-
205170
funcInfo = {
206171
upper: funcInfo,
207-
isSetter: isSetter(node, outerScope),
172+
isSetter: isSetter(node, sourceCode),
208173
};
209174
}
210175

lib/rules/prefer-regex-literals.js

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const {
1414
CALL,
1515
CONSTRUCT,
1616
ReferenceTracker,
17-
findVariable,
1817
} = require("@eslint-community/eslint-utils");
1918
const {
2019
RegExpValidator,
@@ -167,22 +166,6 @@ module.exports = {
167166
const [{ disallowRedundantWrapping }] = context.options;
168167
const sourceCode = context.sourceCode;
169168

170-
/**
171-
* Determines whether the given identifier node is a reference to a global variable.
172-
* @param {ASTNode} node `Identifier` node to check.
173-
* @returns {boolean} True if the identifier is a reference to a global variable.
174-
*/
175-
function isGlobalReference(node) {
176-
const scope = sourceCode.getScope(node);
177-
const variable = findVariable(scope, node);
178-
179-
return (
180-
variable !== null &&
181-
variable.scope.type === "global" &&
182-
variable.defs.length === 0
183-
);
184-
}
185-
186169
/**
187170
* Determines whether the given node is a String.raw`` tagged template expression
188171
* with a static template literal.
@@ -193,7 +176,7 @@ module.exports = {
193176
return (
194177
node.type === "TaggedTemplateExpression" &&
195178
astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
196-
isGlobalReference(
179+
sourceCode.isGlobalReference(
197180
astUtils.skipChainExpression(node.tag).object,
198181
) &&
199182
astUtils.isStaticTemplateLiteral(node.quasi)

lib/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ export class SourceCode
289289
second: ESTree.Node | AST.Token,
290290
): boolean;
291291

292+
isGlobalReference(node: ESTree.Identifier): boolean;
293+
292294
markVariableAsUsed(name: string, refNode?: ESTree.Node): boolean;
293295

294296
traverse(): Iterable<TraversalStep>;

0 commit comments

Comments
 (0)