Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit 42ef7a9

Browse files
authored
fix!: class extends is evaluated in the class scope (#116)
* fix!: class `extends` is evaluated in the class scope Fixes #59 * add tests with nested scopes
1 parent ed67857 commit 42ef7a9

File tree

3 files changed

+202
-14
lines changed

3 files changed

+202
-14
lines changed

lib/referencer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,6 @@ class Referencer extends esrecurse.Visitor {
272272
));
273273
}
274274

275-
this.visit(node.superClass);
276-
277275
this.scopeManager.__nestClassScope(node);
278276

279277
if (node.id) {
@@ -284,6 +282,8 @@ class Referencer extends esrecurse.Visitor {
284282
node
285283
));
286284
}
285+
286+
this.visit(node.superClass);
287287
this.visit(node.body);
288288

289289
this.close(node);

tests/es6-class.js

Lines changed: 197 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@ describe("ES6 class", () => {
4848
expect(scope.isStrict).to.be.false;
4949
expect(scope.variables).to.have.length(1);
5050
expect(scope.variables[0].name).to.be.equal("Derived");
51-
expect(scope.references).to.have.length(2);
52-
expect(scope.references[0].identifier.name).to.be.equal("Base");
53-
expect(scope.references[1].identifier.name).to.be.equal("Derived");
51+
expect(scope.references).to.have.length(1);
52+
expect(scope.references[0].identifier.name).to.be.equal("Derived");
5453

5554
scope = scopeManager.scopes[1];
5655
expect(scope.type).to.be.equal("class");
5756
expect(scope.block.type).to.be.equal("ClassDeclaration");
5857
expect(scope.isStrict).to.be.true;
5958
expect(scope.variables).to.have.length(1);
6059
expect(scope.variables[0].name).to.be.equal("Derived");
61-
expect(scope.references).to.have.length(0);
60+
expect(scope.references).to.have.length(1);
61+
expect(scope.references[0].identifier.name).to.be.equal("Base");
6262

6363
scope = scopeManager.scopes[2];
6464
expect(scope.type).to.be.equal("function");
@@ -87,16 +87,16 @@ describe("ES6 class", () => {
8787
expect(scope.block.type).to.be.equal("Program");
8888
expect(scope.isStrict).to.be.false;
8989
expect(scope.variables).to.have.length(0);
90-
expect(scope.references).to.have.length(1);
91-
expect(scope.references[0].identifier.name).to.be.equal("Base");
90+
expect(scope.references).to.have.length(0);
9291

9392
scope = scopeManager.scopes[1];
9493
expect(scope.type).to.be.equal("class");
9594
expect(scope.block.type).to.be.equal("ClassExpression");
9695
expect(scope.isStrict).to.be.true;
9796
expect(scope.variables).to.have.length(1);
9897
expect(scope.variables[0].name).to.be.equal("Derived");
99-
expect(scope.references).to.have.length(0);
98+
expect(scope.references).to.have.length(1);
99+
expect(scope.references[0].identifier.name).to.be.equal("Base");
100100

101101
scope = scopeManager.scopes[2];
102102
expect(scope.type).to.be.equal("function");
@@ -121,12 +121,13 @@ describe("ES6 class", () => {
121121
expect(scope.block.type).to.be.equal("Program");
122122
expect(scope.isStrict).to.be.false;
123123
expect(scope.variables).to.have.length(0);
124-
expect(scope.references).to.have.length(1);
125-
expect(scope.references[0].identifier.name).to.be.equal("Base");
124+
expect(scope.references).to.have.length(0);
126125

127126
scope = scopeManager.scopes[1];
128127
expect(scope.type).to.be.equal("class");
129128
expect(scope.block.type).to.be.equal("ClassExpression");
129+
expect(scope.references).to.have.length(1);
130+
expect(scope.references[0].identifier.name).to.be.equal("Base");
130131

131132
scope = scopeManager.scopes[2];
132133
expect(scope.type).to.be.equal("function");
@@ -177,6 +178,193 @@ describe("ES6 class", () => {
177178
expect(scope.references[1].identifier.name).to.be.equal("yuyushiki");
178179
});
179180

181+
// https://github.com/eslint/eslint-scope/issues/59
182+
it("class heritage may refer class name in class expressions #1", () => {
183+
const ast = espree(`
184+
const A = class A extends A {}
185+
`);
186+
187+
const scopeManager = analyze(ast, { ecmaVersion: 6 });
188+
189+
expect(scopeManager.scopes).to.have.length(2);
190+
191+
let scope = scopeManager.scopes[0];
192+
193+
expect(scope.type).to.be.equal("global");
194+
expect(scope.block.type).to.be.equal("Program");
195+
expect(scope.isStrict).to.be.false;
196+
expect(scope.variables).to.have.length(1);
197+
expect(scope.variables[0].name).to.be.equal("A"); // variable `A` defined by `const A`
198+
expect(scope.variables[0].references).to.have.length(1); // init reference `A` in `const A`
199+
expect(scope.variables[0].references[0].init).to.be.true;
200+
expect(scope.references).to.have.length(1);
201+
expect(scope.references[0]).to.be.equal(scope.variables[0].references[0]);
202+
203+
scope = scopeManager.scopes[1];
204+
expect(scope.type).to.be.equal("class");
205+
expect(scope.block.type).to.be.equal("ClassExpression");
206+
expect(scope.isStrict).to.be.true;
207+
expect(scope.variables).to.have.length(1);
208+
expect(scope.variables[0].name).to.be.equal("A"); // variable `A` defined by `class A`
209+
expect(scope.variables[0].references).to.have.length(1); // reference `A` in `extends A`
210+
expect(scope.references).to.have.length(1);
211+
expect(scope.references[0].resolved).to.be.equal(scope.variables[0]);
212+
expect(scope.references[0]).to.be.equal(scope.variables[0].references[0]);
213+
});
214+
215+
it("class heritage may refer class name in class expressions #2", () => {
216+
const ast = espree(`
217+
let foo;
218+
(class C extends (foo = C, class {}) {});
219+
`);
220+
221+
const scopeManager = analyze(ast, { ecmaVersion: 6 });
222+
223+
expect(scopeManager.scopes).to.have.length(3);
224+
225+
let scope = scopeManager.scopes[0];
226+
227+
expect(scope.type).to.be.equal("global");
228+
expect(scope.block.type).to.be.equal("Program");
229+
expect(scope.isStrict).to.be.false;
230+
expect(scope.variables).to.have.length(1);
231+
expect(scope.variables[0].name).to.be.equal("foo");
232+
expect(scope.references).to.have.length(0);
233+
234+
scope = scopeManager.scopes[1];
235+
expect(scope.type).to.be.equal("class");
236+
expect(scope.block.type).to.be.equal("ClassExpression");
237+
expect(scope.isStrict).to.be.true;
238+
expect(scope.variables).to.have.length(1);
239+
expect(scope.variables[0].name).to.be.equal("C");
240+
expect(scope.variables[0].references).to.have.length(1);
241+
expect(scope.references).to.have.length(2);
242+
expect(scope.references[0].identifier.name).to.be.equal("foo");
243+
expect(scope.references[1].identifier.name).to.be.equal("C");
244+
245+
// `C` in `foo = C` is a reference to variable `C` defined by `class C`
246+
expect(scope.references[1].resolved).to.be.equal(scope.variables[0]);
247+
expect(scope.references[1]).to.be.equal(scope.variables[0].references[0]);
248+
249+
scope = scopeManager.scopes[2];
250+
expect(scope.type).to.be.equal("class");
251+
expect(scope.block.type).to.be.equal("ClassExpression");
252+
expect(scope.isStrict).to.be.true;
253+
expect(scope.variables).to.have.length(0);
254+
expect(scope.references).to.have.length(0);
255+
});
256+
257+
it("class heritage may refer class name in class declarations", () => {
258+
const ast = espree(`
259+
let foo;
260+
class C extends (foo = C, class {}) {}
261+
new C();
262+
`);
263+
264+
const scopeManager = analyze(ast, { ecmaVersion: 6 });
265+
266+
expect(scopeManager.scopes).to.have.length(3);
267+
268+
let scope = scopeManager.scopes[0];
269+
270+
expect(scope.type).to.be.equal("global");
271+
expect(scope.block.type).to.be.equal("Program");
272+
expect(scope.isStrict).to.be.false;
273+
expect(scope.variables).to.have.length(2);
274+
expect(scope.variables[0].name).to.be.equal("foo");
275+
expect(scope.variables[0].references).to.have.length(1);
276+
expect(scope.variables[1].name).to.be.equal("C");
277+
expect(scope.variables[1].references).to.have.length(1);
278+
expect(scope.references).to.have.length(1);
279+
expect(scope.references[0].identifier.name).to.be.equal("C"); // `C` in `new C()`
280+
expect(scope.references[0].resolved).to.be.equal(scope.variables[1]);
281+
expect(scope.references[0]).to.be.equal(scope.variables[1].references[0]);
282+
283+
scope = scopeManager.scopes[1];
284+
expect(scope.type).to.be.equal("class");
285+
expect(scope.block.type).to.be.equal("ClassDeclaration");
286+
expect(scope.isStrict).to.be.true;
287+
expect(scope.variables).to.have.length(1);
288+
expect(scope.variables[0].name).to.be.equal("C");
289+
expect(scope.variables[0].references).to.have.length(1);
290+
expect(scope.references).to.have.length(2);
291+
expect(scope.references[0].identifier.name).to.be.equal("foo");
292+
expect(scope.references[1].identifier.name).to.be.equal("C"); // `C` in `foo = C`
293+
294+
/*
295+
* `class C` creates two variables `C`: one in the scope where the class
296+
* is declared, another in the class scope. References inside the class
297+
* should be to the variable in the class scope.
298+
*/
299+
expect(scope.references[1].resolved).to.be.equal(scope.variables[0]);
300+
expect(scope.references[1]).to.be.equal(scope.variables[0].references[0]);
301+
302+
scope = scopeManager.scopes[2];
303+
expect(scope.type).to.be.equal("class");
304+
expect(scope.block.type).to.be.equal("ClassExpression");
305+
expect(scope.isStrict).to.be.true;
306+
expect(scope.variables).to.have.length(0);
307+
expect(scope.references).to.have.length(0);
308+
});
309+
310+
it("inner scopes in the class heritage of a class expression are nested in the class scope", () => {
311+
const ast = espree(`
312+
(class extends function () {} {})
313+
`);
314+
315+
const scopeManager = analyze(ast, { ecmaVersion: 6 });
316+
317+
expect(scopeManager.scopes).to.have.length(3);
318+
319+
let scope = scopeManager.scopes[0];
320+
321+
expect(scope.type).to.be.equal("global");
322+
expect(scope.block.type).to.be.equal("Program");
323+
expect(scope.isStrict).to.be.false;
324+
325+
scope = scopeManager.scopes[1];
326+
expect(scope.type).to.be.equal("class");
327+
expect(scope.block.type).to.be.equal("ClassExpression");
328+
expect(scope.isStrict).to.be.true;
329+
330+
scope = scopeManager.scopes[2];
331+
expect(scope.type).to.be.equal("function");
332+
expect(scope.block.type).to.be.equal("FunctionExpression");
333+
expect(scope.isStrict).to.be.true;
334+
expect(scope.upper).to.be.equal(scopeManager.scopes[1]);
335+
expect(scopeManager.scopes[1].childScopes).to.have.length(1);
336+
expect(scopeManager.scopes[1].childScopes[0]).to.be.equal(scope);
337+
});
338+
339+
it("inner scopes in the class heritage of a class declaration are nested in the class scope", () => {
340+
const ast = espree(`
341+
class C extends function () {} {}
342+
`);
343+
344+
const scopeManager = analyze(ast, { ecmaVersion: 6 });
345+
346+
expect(scopeManager.scopes).to.have.length(3);
347+
348+
let scope = scopeManager.scopes[0];
349+
350+
expect(scope.type).to.be.equal("global");
351+
expect(scope.block.type).to.be.equal("Program");
352+
expect(scope.isStrict).to.be.false;
353+
354+
scope = scopeManager.scopes[1];
355+
expect(scope.type).to.be.equal("class");
356+
expect(scope.block.type).to.be.equal("ClassDeclaration");
357+
expect(scope.isStrict).to.be.true;
358+
359+
scope = scopeManager.scopes[2];
360+
expect(scope.type).to.be.equal("function");
361+
expect(scope.block.type).to.be.equal("FunctionExpression");
362+
expect(scope.isStrict).to.be.true;
363+
expect(scope.upper).to.be.equal(scopeManager.scopes[1]);
364+
expect(scopeManager.scopes[1].childScopes).to.have.length(1);
365+
expect(scopeManager.scopes[1].childScopes[0]).to.be.equal(scope);
366+
});
367+
180368
it("regression #49", () => {
181369
const ast = espree(`
182370
class Shoe {

tests/es6-super.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ describe("ES6 super", () => {
4848
expect(scope.type).to.be.equal("global");
4949
expect(scope.variables).to.have.length(1);
5050
expect(scope.variables[0].name).to.be.equal("Foo");
51-
expect(scope.references).to.have.length(1);
52-
expect(scope.references[0].identifier.name).to.be.equal("Bar");
51+
expect(scope.references).to.have.length(0);
5352

5453
scope = scopeManager.scopes[1];
5554
expect(scope.type).to.be.equal("class");
5655
expect(scope.variables).to.have.length(1);
5756
expect(scope.variables[0].name).to.be.equal("Foo");
58-
expect(scope.references).to.have.length(0);
57+
expect(scope.references).to.have.length(1);
58+
expect(scope.references[0].identifier.name).to.be.equal("Bar");
5959

6060
scope = scopeManager.scopes[2];
6161
expect(scope.type).to.be.equal("function");

0 commit comments

Comments
 (0)