From 0deff30fe8c308412322d419aa493a90e8341a0c Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 15 Sep 2025 20:06:02 +0200 Subject: [PATCH 1/6] feat!: Close `var` and `function` references on global scope --- packages/eslint-scope/lib/scope.js | 50 +++---------------- .../tests/implicit-global-reference.test.js | 48 ++++++++++++++++++ packages/eslint-scope/tests/label.test.js | 7 +-- .../eslint-scope/tests/references.test.js | 16 +++--- 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/packages/eslint-scope/lib/scope.js b/packages/eslint-scope/lib/scope.js index 46eeb771..21b37b63 100644 --- a/packages/eslint-scope/lib/scope.js +++ b/packages/eslint-scope/lib/scope.js @@ -122,18 +122,6 @@ function registerScope(scopeManager, scope) { } } -/** - * Should be statically - * @param {Object} def def - * @returns {boolean} should be statically - */ -function shouldBeStatically(def) { - return ( - (def.type === Variable.ClassName) || - (def.type === Variable.Variable && def.parent.kind !== "var") - ); -} - /** * @constructor Scope */ @@ -267,22 +255,7 @@ class Scope { } __shouldStaticallyClose(scopeManager) { - return (!this.dynamic || scopeManager.__isOptimistic()); - } - - __shouldStaticallyCloseForGlobal(ref) { - - // On global scope, let/const/class declarations should be resolved statically. - const name = ref.identifier.name; - - if (!this.set.has(name)) { - return false; - } - - const variable = this.set.get(name); - const defs = variable.defs; - - return defs.length > 0 && defs.every(shouldBeStatically); + return (!this.dynamic || scopeManager.__isOptimistic() || this.type === "global"); } __staticCloseRef(ref) { @@ -302,26 +275,13 @@ class Scope { } while (current); } - __globalCloseRef(ref) { - - // let/const/class declarations should be resolved statically. - // others should be resolved dynamically. - if (this.__shouldStaticallyCloseForGlobal(ref)) { - this.__staticCloseRef(ref); - } else { - this.__dynamicCloseRef(ref); - } - } - __close(scopeManager) { let closeRef; if (this.__shouldStaticallyClose(scopeManager)) { closeRef = this.__staticCloseRef; - } else if (this.type !== "global") { - closeRef = this.__dynamicCloseRef; } else { - closeRef = this.__globalCloseRef; + closeRef = this.__dynamicCloseRef; } // Try Resolving all references in this scope. @@ -560,9 +520,11 @@ class GlobalScope extends Scope { } - this.implicit.left = this.__left; + super.__close(scopeManager); + + this.implicit.left = [...this.through]; - return super.__close(scopeManager); + return null; } __defineImplicit(node, def) { diff --git a/packages/eslint-scope/tests/implicit-global-reference.test.js b/packages/eslint-scope/tests/implicit-global-reference.test.js index 4c869ff1..7809d0b8 100644 --- a/packages/eslint-scope/tests/implicit-global-reference.test.js +++ b/packages/eslint-scope/tests/implicit-global-reference.test.js @@ -44,6 +44,8 @@ describe("implicit global reference", () => { ); expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql([]); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql([]); }); it("assignments global scope without definition", () => { @@ -66,6 +68,18 @@ describe("implicit global reference", () => { "x" ] ); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql( + [ + "x", + "x" + ] + ); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql( + [ + "x", + "x" + ] + ); }); it("assignments global scope without definition eval", () => { @@ -120,6 +134,16 @@ describe("implicit global reference", () => { "x" ] ); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql( + [ + "x" + ] + ); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql( + [ + "x" + ] + ); }); it("assignment doesn't leak", () => { @@ -151,6 +175,8 @@ describe("implicit global reference", () => { ); expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql([]); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql([]); }); it("for-in-statement leaks", () => { @@ -177,6 +203,18 @@ describe("implicit global reference", () => { "x" ] ); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql( + [ + "x", + "y" + ] + ); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql( + [ + "x", + "y" + ] + ); }); it("for-in-statement doesn't leaks", () => { @@ -208,5 +246,15 @@ describe("implicit global reference", () => { ); expect(scopes[0].implicit.variables.map(variable => variable.name)).to.be.eql([]); + expect(scopes[0].implicit.left.map(reference => reference.identifier.name)).to.be.eql( + [ + "y" + ] + ); + expect(scopes[0].through.map(reference => reference.identifier.name)).to.be.eql( + [ + "y" + ] + ); }); }); diff --git a/packages/eslint-scope/tests/label.test.js b/packages/eslint-scope/tests/label.test.js index 3d0ff196..47869852 100644 --- a/packages/eslint-scope/tests/label.test.js +++ b/packages/eslint-scope/tests/label.test.js @@ -66,9 +66,10 @@ describe("label", () => { expect(globalScope.type).to.be.equal("global"); expect(globalScope.variables).to.have.length(1); expect(globalScope.variables[0].name).to.be.equal("foo"); - expect(globalScope.through.length).to.be.equal(3); - expect(globalScope.through[2].identifier.name).to.be.equal("foo"); - expect(globalScope.through[2].isRead()).to.be.true; + expect(globalScope.through.length).to.be.equal(1); + expect(globalScope.through[0].identifier.name).to.be.equal("console"); + expect(globalScope.variables[0].references.length).to.be.equal(2); + expect(globalScope.variables[0].references[1].isRead()).to.be.true; }); }); diff --git a/packages/eslint-scope/tests/references.test.js b/packages/eslint-scope/tests/references.test.js index a4776442..cbee1022 100644 --- a/packages/eslint-scope/tests/references.test.js +++ b/packages/eslint-scope/tests/references.test.js @@ -155,7 +155,7 @@ describe("References:", () => { }); describe("When there is a `var` declaration on global,", () => { - it("the reference on global should NOT be resolved.", () => { + it("the reference on global should be resolved.", () => { const ast = espree("var a = 0;"); const scopeManager = analyze(ast, { ecmaVersion: 6 }); @@ -171,13 +171,13 @@ describe("References:", () => { expect(reference.from).to.equal(scope); expect(reference.identifier.name).to.equal("a"); - expect(reference.resolved).to.be.null; + expect(reference.resolved).to.equal(scope.variables[0]); expect(reference.writeExpr).to.not.be.undefined; expect(reference.isWrite()).to.be.true; expect(reference.isRead()).to.be.false; }); - it("the reference in functions should NOT be resolved.", () => { + it("the reference in functions should be resolved.", () => { const ast = espree(` var a = 0; function foo() { @@ -198,7 +198,7 @@ describe("References:", () => { expect(reference.from).to.equal(scope); expect(reference.identifier.name).to.equal("a"); - expect(reference.resolved).to.be.null; + expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); expect(reference.writeExpr).to.be.undefined; expect(reference.isWrite()).to.be.false; expect(reference.isRead()).to.be.true; @@ -206,7 +206,7 @@ describe("References:", () => { }); describe("When there is a `function` declaration on global,", () => { - it("the reference on global should NOT be resolved.", () => { + it("the reference on global should be resolved.", () => { const ast = espree(` function a() {} a(); @@ -225,13 +225,13 @@ describe("References:", () => { expect(reference.from).to.equal(scope); expect(reference.identifier.name).to.equal("a"); - expect(reference.resolved).to.be.null; + expect(reference.resolved).to.equal(scope.variables[0]); expect(reference.writeExpr).to.be.undefined; expect(reference.isWrite()).to.be.false; expect(reference.isRead()).to.be.true; }); - it("the reference in functions should NOT be resolved.", () => { + it("the reference in functions should be resolved.", () => { const ast = espree(` function a() {} function foo() { @@ -252,7 +252,7 @@ describe("References:", () => { expect(reference.from).to.equal(scope); expect(reference.identifier.name).to.equal("a"); - expect(reference.resolved).to.be.null; + expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]); expect(reference.writeExpr).to.be.undefined; expect(reference.isWrite()).to.be.false; expect(reference.isRead()).to.be.true; From 1df040a628d29e4f5c266c2620e02027fb38fa3b Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 16 Sep 2025 19:05:29 +0200 Subject: [PATCH 2/6] add `ScopeManager#addGlobals` --- packages/eslint-scope/lib/scope-manager.js | 12 ++++ packages/eslint-scope/lib/scope.js | 45 +++++++++++++ packages/eslint-scope/tests/add-globals.js | 78 ++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 packages/eslint-scope/tests/add-globals.js diff --git a/packages/eslint-scope/lib/scope-manager.js b/packages/eslint-scope/lib/scope-manager.js index b012b646..af602d36 100644 --- a/packages/eslint-scope/lib/scope-manager.js +++ b/packages/eslint-scope/lib/scope-manager.js @@ -182,6 +182,18 @@ class ScopeManager { return null; } + /** + * Add global variables and resolve their references. + * @function ScopeManager#addGlobals + * @param {string[]} names Names of global variables to add. + * @returns {void} + */ + addGlobals(names) { + const globalScope = this.scopes[0]; + + globalScope.__addVariables(names); + } + attach() { } // eslint-disable-line class-methods-use-this -- Desired as instance method detach() { } // eslint-disable-line class-methods-use-this -- Desired as instance method diff --git a/packages/eslint-scope/lib/scope.js b/packages/eslint-scope/lib/scope.js index 21b37b63..0b7f5890 100644 --- a/packages/eslint-scope/lib/scope.js +++ b/packages/eslint-scope/lib/scope.js @@ -538,6 +538,51 @@ class GlobalScope extends Scope { ); } } + + __addVariables(names) { + for (const name of names) { + this.__defineGeneric( + name, + this.set, + this.variables, + null, + null + ); + } + + const namesSet = new Set(names); + + this.through = this.through.filter(reference => { + const name = reference.identifier.name; + + if (namesSet.has(name)) { + const variable = this.set.get(name); + + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); + + this.implicit.variables = this.implicit.variables.filter(variable => { + const name = variable.name; + + if (namesSet.has(name)) { + this.implicit.set.delete(name); + + return false; + } + + return true; + }); + + this.implicit.left = this.implicit.left.filter( + reference => !namesSet.has(reference.identifier.name) + ); + } } /** diff --git a/packages/eslint-scope/tests/add-globals.js b/packages/eslint-scope/tests/add-globals.js new file mode 100644 index 00000000..ad915ede --- /dev/null +++ b/packages/eslint-scope/tests/add-globals.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Tests for ScopeManager#addGlobals method. + * @author Milos Djermanovic + */ + +import { expect } from "chai"; +import espree from "./util/espree.js"; +import { analyze } from "../lib/index.js"; + +describe("ScopeManager#addGlobals", () => { + it("adds variables to the global scope and closes references from the global scope", () => { + const ast = espree(` + foo = bar + bar; + `); + + const scopeManager = analyze(ast); + + expect(scopeManager.scopes).to.have.length(1); + + const globalScope = scopeManager.scopes[0]; + + expect(globalScope.type).to.be.equal("global"); + + expect(globalScope.variables).to.have.length(0); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].resolved).to.be.null; + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].resolved).to.be.null; + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].resolved).to.be.null; + expect(globalScope.references[1]).to.not.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(3); + expect(globalScope.through[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.through[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.through[2]).to.be.equal(globalScope.references[2]); + expect(globalScope.implicit.variables).to.have.length(1); + expect(globalScope.implicit.variables[0].name).to.be.equal("foo"); + expect(globalScope.implicit.set.size).to.be.equal(1); + expect(globalScope.implicit.set.get("foo")).to.be.equal(globalScope.implicit.variables[0]); + expect(globalScope.implicit.left).to.have.length(3); + expect(globalScope.implicit.left[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.implicit.left[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.implicit.left[2]).to.be.equal(globalScope.references[2]); + + scopeManager.addGlobals(["foo", "bar"]); + + expect(globalScope.variables).to.have.length(2); + expect(globalScope.variables[0].name).to.be.equal("foo"); + expect(globalScope.variables[0].scope).to.be.equal(globalScope); + expect(globalScope.variables[0].defs).to.have.length(0); + expect(globalScope.variables[0].identifiers).to.have.length(0); + expect(globalScope.variables[1].name).to.be.equal("bar"); + expect(globalScope.variables[1].scope).to.be.equal(globalScope); + expect(globalScope.variables[1].defs).to.have.length(0); + expect(globalScope.variables[1].identifiers).to.have.length(0); + expect(globalScope.set.size).to.be.equal(2); + expect(globalScope.set.get("foo")).to.be.equal(globalScope.variables[0]); + expect(globalScope.set.get("bar")).to.be.equal(globalScope.variables[1]); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].resolved).to.be.equal(globalScope.variables[0]); + expect(globalScope.variables[0].references).to.have.length(1); + expect(globalScope.variables[0].references[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.variables[1].references).to.have.length(2); + expect(globalScope.variables[1].references[0]).to.be.equal(globalScope.references[1]); + expect(globalScope.variables[1].references[1]).to.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(0); + expect(globalScope.implicit.variables).to.have.length(0); + expect(globalScope.implicit.set.size).to.be.equal(0); + expect(globalScope.implicit.left).to.have.length(0); + }); + +}); From fadd0154c6f3006a61188c20e6440639d6f19c01 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 17 Sep 2025 08:01:03 +0200 Subject: [PATCH 3/6] refactor --- packages/eslint-scope/lib/scope-manager.js | 4 +--- packages/eslint-scope/tests/add-globals.js | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/eslint-scope/lib/scope-manager.js b/packages/eslint-scope/lib/scope-manager.js index af602d36..a9f5c943 100644 --- a/packages/eslint-scope/lib/scope-manager.js +++ b/packages/eslint-scope/lib/scope-manager.js @@ -189,9 +189,7 @@ class ScopeManager { * @returns {void} */ addGlobals(names) { - const globalScope = this.scopes[0]; - - globalScope.__addVariables(names); + this.globalScope.__addVariables(names); } attach() { } // eslint-disable-line class-methods-use-this -- Desired as instance method diff --git a/packages/eslint-scope/tests/add-globals.js b/packages/eslint-scope/tests/add-globals.js index ad915ede..d090bb8f 100644 --- a/packages/eslint-scope/tests/add-globals.js +++ b/packages/eslint-scope/tests/add-globals.js @@ -22,6 +22,7 @@ describe("ScopeManager#addGlobals", () => { expect(globalScope.type).to.be.equal("global"); expect(globalScope.variables).to.have.length(0); + expect(globalScope.set.size).to.be.equal(0); expect(globalScope.references).to.have.length(3); expect(globalScope.references[0].identifier.name).to.be.equal("foo"); expect(globalScope.references[0].resolved).to.be.null; From e08692c33ba27daabb68cfc9feb197f6e751fac6 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 17 Sep 2025 08:53:28 +0200 Subject: [PATCH 4/6] add more tests --- packages/eslint-scope/tests/add-globals.js | 274 ++++++++++++++++++++- 1 file changed, 273 insertions(+), 1 deletion(-) diff --git a/packages/eslint-scope/tests/add-globals.js b/packages/eslint-scope/tests/add-globals.js index d090bb8f..63fa41bc 100644 --- a/packages/eslint-scope/tests/add-globals.js +++ b/packages/eslint-scope/tests/add-globals.js @@ -8,7 +8,7 @@ import espree from "./util/espree.js"; import { analyze } from "../lib/index.js"; describe("ScopeManager#addGlobals", () => { - it("adds variables to the global scope and closes references from the global scope", () => { + it("adds variables to the global scope and resolves references from the global scope", () => { const ast = espree(` foo = bar + bar; `); @@ -25,10 +25,13 @@ describe("ScopeManager#addGlobals", () => { expect(globalScope.set.size).to.be.equal(0); expect(globalScope.references).to.have.length(3); expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); expect(globalScope.references[0].resolved).to.be.null; expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); expect(globalScope.references[1].resolved).to.be.null; expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); expect(globalScope.references[2].resolved).to.be.null; expect(globalScope.references[1]).to.not.be.equal(globalScope.references[2]); expect(globalScope.through).to.have.length(3); @@ -37,6 +40,9 @@ describe("ScopeManager#addGlobals", () => { expect(globalScope.through[2]).to.be.equal(globalScope.references[2]); expect(globalScope.implicit.variables).to.have.length(1); expect(globalScope.implicit.variables[0].name).to.be.equal("foo"); + expect(globalScope.implicit.variables[0].references).to.have.length(0); + expect(globalScope.implicit.variables[0].defs).to.have.length(1); + expect(globalScope.implicit.variables[0].identifiers).to.have.length(1); expect(globalScope.implicit.set.size).to.be.equal(1); expect(globalScope.implicit.set.get("foo")).to.be.equal(globalScope.implicit.variables[0]); expect(globalScope.implicit.left).to.have.length(3); @@ -60,12 +66,278 @@ describe("ScopeManager#addGlobals", () => { expect(globalScope.set.get("bar")).to.be.equal(globalScope.variables[1]); expect(globalScope.references).to.have.length(3); expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); expect(globalScope.references[0].resolved).to.be.equal(globalScope.variables[0]); expect(globalScope.variables[0].references).to.have.length(1); expect(globalScope.variables[0].references[0]).to.be.equal(globalScope.references[0]); expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); expect(globalScope.references[1].resolved).to.be.equal(globalScope.variables[1]); expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); + expect(globalScope.references[2].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.variables[1].references).to.have.length(2); + expect(globalScope.variables[1].references[0]).to.be.equal(globalScope.references[1]); + expect(globalScope.variables[1].references[1]).to.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(0); + expect(globalScope.implicit.variables).to.have.length(0); + expect(globalScope.implicit.set.size).to.be.equal(0); + expect(globalScope.implicit.left).to.have.length(0); + }); + + it("adds variables to the global scope and resolves references from inner scopes", () => { + const ast = espree(` + () => foo = bar + bar; + `); + + const scopeManager = analyze(ast); + + expect(scopeManager.scopes).to.have.length(2); + + const globalScope = scopeManager.scopes[0]; + + expect(globalScope.type).to.be.equal("global"); + + const functionScope = scopeManager.scopes[1]; + + expect(functionScope.type).to.be.equal("function"); + + expect(functionScope.variables).to.have.length(0); + expect(functionScope.set.size).to.be.equal(0); + expect(functionScope.references).to.have.length(3); + expect(functionScope.references[0].identifier.name).to.be.equal("foo"); + expect(functionScope.references[0].from).to.be.equal(functionScope); + expect(functionScope.references[0].resolved).to.be.null; + expect(functionScope.references[1].identifier.name).to.be.equal("bar"); + expect(functionScope.references[1].from).to.be.equal(functionScope); + expect(functionScope.references[1].resolved).to.be.null; + expect(functionScope.references[2].identifier.name).to.be.equal("bar"); + expect(functionScope.references[2].from).to.be.equal(functionScope); + expect(functionScope.references[2].resolved).to.be.null; + expect(functionScope.references[1]).to.not.be.equal(functionScope.references[2]); + expect(functionScope.through).to.have.length(3); + expect(functionScope.through[0]).to.be.equal(functionScope.references[0]); + expect(functionScope.through[1]).to.be.equal(functionScope.references[1]); + expect(functionScope.through[2]).to.be.equal(functionScope.references[2]); + expect(globalScope.variables).to.have.length(0); + expect(globalScope.set.size).to.be.equal(0); + expect(globalScope.references).to.have.length(0); + expect(globalScope.through).to.have.length(3); + expect(globalScope.through[0]).to.be.equal(functionScope.references[0]); + expect(globalScope.through[1]).to.be.equal(functionScope.references[1]); + expect(globalScope.through[2]).to.be.equal(functionScope.references[2]); + expect(globalScope.implicit.variables).to.have.length(1); + expect(globalScope.implicit.variables[0].name).to.be.equal("foo"); + expect(globalScope.implicit.variables[0].references).to.have.length(0); + expect(globalScope.implicit.variables[0].defs).to.have.length(1); + expect(globalScope.implicit.variables[0].identifiers).to.have.length(1); + expect(globalScope.implicit.set.size).to.be.equal(1); + expect(globalScope.implicit.set.get("foo")).to.be.equal(globalScope.implicit.variables[0]); + expect(globalScope.implicit.left).to.have.length(3); + expect(globalScope.implicit.left[0]).to.be.equal(functionScope.references[0]); + expect(globalScope.implicit.left[1]).to.be.equal(functionScope.references[1]); + expect(globalScope.implicit.left[2]).to.be.equal(functionScope.references[2]); + + scopeManager.addGlobals(["foo", "bar"]); + + expect(globalScope.variables).to.have.length(2); + expect(globalScope.variables[0].name).to.be.equal("foo"); + expect(globalScope.variables[0].scope).to.be.equal(globalScope); + expect(globalScope.variables[0].defs).to.have.length(0); + expect(globalScope.variables[0].identifiers).to.have.length(0); + expect(globalScope.variables[1].name).to.be.equal("bar"); + expect(globalScope.variables[1].scope).to.be.equal(globalScope); + expect(globalScope.variables[1].defs).to.have.length(0); + expect(globalScope.variables[1].identifiers).to.have.length(0); + expect(globalScope.set.size).to.be.equal(2); + expect(globalScope.set.get("foo")).to.be.equal(globalScope.variables[0]); + expect(globalScope.set.get("bar")).to.be.equal(globalScope.variables[1]); + expect(functionScope.variables).to.have.length(0); + expect(functionScope.set.size).to.be.equal(0); + expect(functionScope.references).to.have.length(3); + expect(functionScope.references[0].identifier.name).to.be.equal("foo"); + expect(functionScope.references[0].from).to.be.equal(functionScope); + expect(functionScope.references[0].resolved).to.be.equal(globalScope.variables[0]); + expect(globalScope.variables[0].references).to.have.length(1); + expect(globalScope.variables[0].references[0]).to.be.equal(functionScope.references[0]); + expect(functionScope.references[1].identifier.name).to.be.equal("bar"); + expect(functionScope.references[1].from).to.be.equal(functionScope); + expect(functionScope.references[1].resolved).to.be.equal(globalScope.variables[1]); + expect(functionScope.references[2].identifier.name).to.be.equal("bar"); + expect(functionScope.references[2].from).to.be.equal(functionScope); + expect(functionScope.references[2].resolved).to.be.equal(globalScope.variables[1]); + expect(functionScope.references[1]).to.not.be.equal(functionScope.references[2]); + expect(globalScope.variables[1].references).to.have.length(2); + expect(globalScope.variables[1].references[0]).to.be.equal(functionScope.references[1]); + expect(globalScope.variables[1].references[1]).to.be.equal(functionScope.references[2]); + expect(functionScope.through).to.have.length(3); + expect(functionScope.through[0]).to.be.equal(functionScope.references[0]); + expect(functionScope.through[1]).to.be.equal(functionScope.references[1]); + expect(functionScope.through[2]).to.be.equal(functionScope.references[2]); + expect(globalScope.references).to.have.length(0); + expect(globalScope.through).to.have.length(0); + expect(globalScope.implicit.variables).to.have.length(0); + expect(globalScope.implicit.set.size).to.be.equal(0); + expect(globalScope.implicit.left).to.have.length(0); + }); + + it("adds variables to the global scope and doesn't affect unrelated references", () => { + const ast = espree(` + foo = bar + bar; + `); + + const scopeManager = analyze(ast); + + expect(scopeManager.scopes).to.have.length(1); + + const globalScope = scopeManager.scopes[0]; + + expect(globalScope.type).to.be.equal("global"); + + expect(globalScope.variables).to.have.length(0); + expect(globalScope.set.size).to.be.equal(0); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); + expect(globalScope.references[0].resolved).to.be.null; + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); + expect(globalScope.references[1].resolved).to.be.null; + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); + expect(globalScope.references[2].resolved).to.be.null; + expect(globalScope.references[1]).to.not.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(3); + expect(globalScope.through[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.through[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.through[2]).to.be.equal(globalScope.references[2]); + expect(globalScope.implicit.variables).to.have.length(1); + expect(globalScope.implicit.variables[0].name).to.be.equal("foo"); + expect(globalScope.implicit.variables[0].references).to.have.length(0); + expect(globalScope.implicit.variables[0].defs).to.have.length(1); + expect(globalScope.implicit.variables[0].identifiers).to.have.length(1); + expect(globalScope.implicit.set.size).to.be.equal(1); + expect(globalScope.implicit.set.get("foo")).to.be.equal(globalScope.implicit.variables[0]); + expect(globalScope.implicit.left).to.have.length(3); + expect(globalScope.implicit.left[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.implicit.left[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.implicit.left[2]).to.be.equal(globalScope.references[2]); + + scopeManager.addGlobals(["baz", "qux"]); + + expect(globalScope.variables).to.have.length(2); + expect(globalScope.variables[0].name).to.be.equal("baz"); + expect(globalScope.variables[0].scope).to.be.equal(globalScope); + expect(globalScope.variables[0].defs).to.have.length(0); + expect(globalScope.variables[0].identifiers).to.have.length(0); + expect(globalScope.variables[0].references).to.have.length(0); + expect(globalScope.variables[1].name).to.be.equal("qux"); + expect(globalScope.variables[1].scope).to.be.equal(globalScope); + expect(globalScope.variables[1].defs).to.have.length(0); + expect(globalScope.variables[1].identifiers).to.have.length(0); + expect(globalScope.variables[1].references).to.have.length(0); + expect(globalScope.set.size).to.be.equal(2); + expect(globalScope.set.get("baz")).to.be.equal(globalScope.variables[0]); + expect(globalScope.set.get("qux")).to.be.equal(globalScope.variables[1]); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); + expect(globalScope.references[0].resolved).to.be.null; + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); + expect(globalScope.references[1].resolved).to.be.null; + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); + expect(globalScope.references[2].resolved).to.be.null; + expect(globalScope.references[1]).to.not.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(3); + expect(globalScope.through[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.through[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.through[2]).to.be.equal(globalScope.references[2]); + expect(globalScope.implicit.variables).to.have.length(1); + expect(globalScope.implicit.variables[0].name).to.be.equal("foo"); + expect(globalScope.implicit.variables[0].references).to.have.length(0); + expect(globalScope.implicit.variables[0].defs).to.have.length(1); + expect(globalScope.implicit.variables[0].identifiers).to.have.length(1); + expect(globalScope.implicit.set.size).to.be.equal(1); + expect(globalScope.implicit.set.get("foo")).to.be.equal(globalScope.implicit.variables[0]); + expect(globalScope.implicit.left).to.have.length(3); + expect(globalScope.implicit.left[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.implicit.left[1]).to.be.equal(globalScope.references[1]); + expect(globalScope.implicit.left[2]).to.be.equal(globalScope.references[2]); + }); + + it("doesn't affect already declared global variables", () => { + const ast = espree(` + let foo = bar + bar; + var bar; + `); + + const scopeManager = analyze(ast); + + expect(scopeManager.scopes).to.have.length(1); + + const globalScope = scopeManager.scopes[0]; + + expect(globalScope.type).to.be.equal("global"); + + expect(globalScope.variables).to.have.length(2); + expect(globalScope.variables[0].name).to.be.equal("foo"); + expect(globalScope.variables[0].scope).to.be.equal(globalScope); + expect(globalScope.variables[0].defs).to.have.length(1); + expect(globalScope.variables[0].identifiers).to.have.length(1); + expect(globalScope.variables[1].name).to.be.equal("bar"); + expect(globalScope.variables[1].scope).to.be.equal(globalScope); + expect(globalScope.variables[1].defs).to.have.length(1); + expect(globalScope.variables[1].identifiers).to.have.length(1); + expect(globalScope.set.size).to.be.equal(2); + expect(globalScope.set.get("foo")).to.be.equal(globalScope.variables[0]); + expect(globalScope.set.get("bar")).to.be.equal(globalScope.variables[1]); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); + expect(globalScope.references[0].resolved).to.be.equal(globalScope.variables[0]); + expect(globalScope.variables[0].references).to.have.length(1); + expect(globalScope.variables[0].references[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); + expect(globalScope.references[1].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); + expect(globalScope.references[2].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.variables[1].references).to.have.length(2); + expect(globalScope.variables[1].references[0]).to.be.equal(globalScope.references[1]); + expect(globalScope.variables[1].references[1]).to.be.equal(globalScope.references[2]); + expect(globalScope.through).to.have.length(0); + expect(globalScope.implicit.variables).to.have.length(0); + expect(globalScope.implicit.set.size).to.be.equal(0); + expect(globalScope.implicit.left).to.have.length(0); + + scopeManager.addGlobals(["foo", "bar"]); + + expect(globalScope.variables).to.have.length(2); + expect(globalScope.variables[0].name).to.be.equal("foo"); + expect(globalScope.variables[0].scope).to.be.equal(globalScope); + expect(globalScope.variables[0].defs).to.have.length(1); + expect(globalScope.variables[0].identifiers).to.have.length(1); + expect(globalScope.variables[1].name).to.be.equal("bar"); + expect(globalScope.variables[1].scope).to.be.equal(globalScope); + expect(globalScope.variables[1].defs).to.have.length(1); + expect(globalScope.variables[1].identifiers).to.have.length(1); + expect(globalScope.set.size).to.be.equal(2); + expect(globalScope.set.get("foo")).to.be.equal(globalScope.variables[0]); + expect(globalScope.set.get("bar")).to.be.equal(globalScope.variables[1]); + expect(globalScope.references).to.have.length(3); + expect(globalScope.references[0].identifier.name).to.be.equal("foo"); + expect(globalScope.references[0].from).to.be.equal(globalScope); + expect(globalScope.references[0].resolved).to.be.equal(globalScope.variables[0]); + expect(globalScope.variables[0].references).to.have.length(1); + expect(globalScope.variables[0].references[0]).to.be.equal(globalScope.references[0]); + expect(globalScope.references[1].identifier.name).to.be.equal("bar"); + expect(globalScope.references[1].from).to.be.equal(globalScope); + expect(globalScope.references[1].resolved).to.be.equal(globalScope.variables[1]); + expect(globalScope.references[2].identifier.name).to.be.equal("bar"); + expect(globalScope.references[2].from).to.be.equal(globalScope); expect(globalScope.references[2].resolved).to.be.equal(globalScope.variables[1]); expect(globalScope.variables[1].references).to.have.length(2); expect(globalScope.variables[1].references[0]).to.be.equal(globalScope.references[1]); From 8ef248d7f444a9cd5c2c7ac0ae6a88de207f07c2 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 23 Sep 2025 13:32:55 +0200 Subject: [PATCH 5/6] rename add-globals.js to add-globals.test.js --- .../eslint-scope/tests/{add-globals.js => add-globals.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/eslint-scope/tests/{add-globals.js => add-globals.test.js} (100%) diff --git a/packages/eslint-scope/tests/add-globals.js b/packages/eslint-scope/tests/add-globals.test.js similarity index 100% rename from packages/eslint-scope/tests/add-globals.js rename to packages/eslint-scope/tests/add-globals.test.js From d59bf47620c68fda789077004203a4f50240938c Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 23 Sep 2025 13:40:26 +0200 Subject: [PATCH 6/6] update docs --- packages/eslint-scope/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/eslint-scope/README.md b/packages/eslint-scope/README.md index f9e90ac0..810a1085 100644 --- a/packages/eslint-scope/README.md +++ b/packages/eslint-scope/README.md @@ -89,6 +89,11 @@ The `ScopeManager` class is at the core of eslint-scope and is returned when you #### Methods +- **`addGlobals(names)`** + Adds variables to the global scope and resolves references to them. + - `names` - An array of strings, the names of variables to add to the global scope. + - Returns: `undefined`. + - **`acquire(node, inner)`** Acquires the appropriate scope for a given node. - `node` - The AST node to acquire the scope from.