Skip to content
This repository was archived by the owner on Aug 18, 2021. It is now read-only.

Commit 8fc39e9

Browse files
committed
Fixes flow type scoping issues
This adds a special escope Scope when function, class, and typealias definitions include typeParameters which includes those parameters for reference, but is write-through to it's parent when encountering new variables. This is a little weird, but seems as good as we can while monkey-patching escope. This also fixes some tests which had invalid flow but were written to expect no errors. Fixes #123
1 parent f2800d0 commit 8fc39e9

File tree

2 files changed

+101
-48
lines changed

2 files changed

+101
-48
lines changed

index.js

+34-18
Original file line numberDiff line numberDiff line change
@@ -162,37 +162,49 @@ function monkeypatch() {
162162
if (node.typeAnnotation) {
163163
visitTypeAnnotation.call(this, node.typeAnnotation);
164164
} else if (node.type === "Identifier") {
165-
// exception for polymorphic types: <T>, <A>, etc
166-
if (node.name.length === 1 && node.name === node.name.toUpperCase()) {
167-
return;
168-
}
169165
this.visit(node);
170166
} else {
171167
visitTypeAnnotation.call(this, node);
172168
}
173169
}
174170

171+
function nestTypeParamScope(manager, node) {
172+
var parentScope = manager.__currentScope;
173+
var scope = new escope.Scope(manager, "type-parameters", parentScope, node, false);
174+
manager.__nestScope(scope);
175+
for (var j = 0; j < node.typeParameters.params.length; j++) {
176+
var name = node.typeParameters.params[j];
177+
scope.__define(name, new Definition("TypeParameter", name, name));
178+
}
179+
scope.__define = function() {
180+
return parentScope.__define.apply(parentScope, arguments);
181+
}
182+
return scope;
183+
}
184+
175185
// visit decorators that are in: ClassDeclaration / ClassExpression
176186
var visitClass = referencer.prototype.visitClass;
177187
referencer.prototype.visitClass = function(node) {
178188
visitDecorators.call(this, node);
189+
var typeParamScope;
190+
if (node.typeParameters) {
191+
typeParamScope = nestTypeParamScope(this.scopeManager, node);
192+
}
179193
// visit flow type: ClassImplements
180194
if (node.implements) {
181195
for (var i = 0; i < node.implements.length; i++) {
182196
checkIdentifierOrVisit.call(this, node.implements[i]);
183197
}
184198
}
185-
if (node.typeParameters) {
186-
for (var j = 0; j < node.typeParameters.params.length; j++) {
187-
checkIdentifierOrVisit.call(this, node.typeParameters.params[j]);
188-
}
189-
}
190199
if (node.superTypeParameters) {
191200
for (var k = 0; k < node.superTypeParameters.params.length; k++) {
192201
checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]);
193202
}
194203
}
195204
visitClass.call(this, node);
205+
if (typeParamScope) {
206+
this.close(node);
207+
}
196208
};
197209
// visit decorators that are in: Property / MethodDefinition
198210
var visitProperty = referencer.prototype.visitProperty;
@@ -207,6 +219,10 @@ function monkeypatch() {
207219
// visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression
208220
var visitFunction = referencer.prototype.visitFunction;
209221
referencer.prototype.visitFunction = function(node) {
222+
var typeParamScope;
223+
if (node.typeParameters) {
224+
typeParamScope = nestTypeParamScope(this.scopeManager, node);
225+
}
210226
if (node.returnType) {
211227
checkIdentifierOrVisit.call(this, node.returnType);
212228
}
@@ -218,12 +234,10 @@ function monkeypatch() {
218234
}
219235
}
220236
}
221-
if (node.typeParameters) {
222-
for (var j = 0; j < node.typeParameters.params.length; j++) {
223-
checkIdentifierOrVisit.call(this, node.typeParameters.params[j]);
224-
}
225-
}
226237
visitFunction.call(this, node);
238+
if (typeParamScope) {
239+
this.close(node);
240+
}
227241
};
228242

229243
// visit flow type in VariableDeclaration
@@ -261,13 +275,15 @@ function monkeypatch() {
261275

262276
referencer.prototype.TypeAlias = function(node) {
263277
createScopeVariable.call(this, node, node.id);
278+
var typeParamScope;
279+
if (node.typeParameters) {
280+
typeParamScope = nestTypeParamScope(this.scopeManager, node);
281+
}
264282
if (node.right) {
265283
visitTypeAnnotation.call(this, node.right);
266284
}
267-
if (node.typeParameters) {
268-
for (var i = 0; i < node.typeParameters.params.length; i++) {
269-
checkIdentifierOrVisit.call(this, node.typeParameters.params[i]);
270-
}
285+
if (typeParamScope) {
286+
this.close(node);
271287
}
272288
};
273289

test/non-regression.js

+67-30
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ describe("verify", function () {
169169
verifyAndAssertMessages([
170170
"import type Foo from 'foo';",
171171
"import type Foo2 from 'foo';",
172-
"function log<Foo, Foo2>() {}",
173-
"log();"
172+
"function log<T1, T2>(a: T1, b: T2) { return a + b; }",
173+
"log<Foo, Foo2>(1, 2);"
174174
].join("\n"),
175175
{ "no-unused-vars": 1, "no-undef": 1 },
176176
[]
@@ -276,9 +276,8 @@ describe("verify", function () {
276276
it("type alias with type parameters", function () {
277277
verifyAndAssertMessages([
278278
"import type Bar from 'foo';",
279-
"import type Foo2 from 'foo';",
280279
"import type Foo3 from 'foo';",
281-
"type Foo<Foo2> = Bar<Foo3>",
280+
"type Foo<T> = Bar<T, Foo3>",
282281
"var x : Foo = 1; x;"
283282
].join("\n"),
284283
{ "no-unused-vars": 1, "no-undef": 1 },
@@ -316,6 +315,62 @@ describe("verify", function () {
316315
);
317316
});
318317

318+
it("polymorphpic/generic types for class #123", function () {
319+
verifyAndAssertMessages([
320+
"class Box<T> {",
321+
"value: T;",
322+
"}",
323+
"var box = new Box();",
324+
"console.log(box.value);"
325+
].join("\n"),
326+
{ "no-unused-vars": 1, "no-undef": 1 },
327+
[]
328+
);
329+
});
330+
331+
it("polymorphpic/generic types for function #123", function () {
332+
verifyAndAssertMessages([
333+
"export function identity<T>(value) {",
334+
"var a: T = value; a;",
335+
"}"
336+
].join("\n"),
337+
{ "no-unused-vars": 1, "no-undef": 1 },
338+
[]
339+
);
340+
});
341+
342+
it("polymorphpic/generic types for type alias #123", function () {
343+
verifyAndAssertMessages([
344+
"import Bar from './Bar';",
345+
"type Foo<T> = Bar<T>; var x: Foo = 1; x++"
346+
].join("\n"),
347+
{ "no-unused-vars": 1, "no-undef": 1 },
348+
[]
349+
);
350+
});
351+
352+
it("polymorphpic/generic types - outside of fn scope #123", function () {
353+
verifyAndAssertMessages([
354+
"export function foo<T>(value) {",
355+
"};",
356+
"var b: T = 1; b;"
357+
].join("\n"),
358+
{ "no-unused-vars": 1, "no-undef": 1 },
359+
[ '1:20 T is defined but never used no-unused-vars',
360+
'3:7 "T" is not defined. no-undef' ]
361+
);
362+
});
363+
364+
it("polymorphpic/generic types - extending unknown #123", function () {
365+
verifyAndAssertMessages([
366+
"import Bar from 'bar';",
367+
"export class Foo extends Bar<T> {}",
368+
].join("\n"),
369+
{ "no-unused-vars": 1, "no-undef": 1 },
370+
[ '2:29 "T" is not defined. no-undef' ]
371+
);
372+
});
373+
319374
it("1", function () {
320375
verifyAndAssertMessages(
321376
[
@@ -413,9 +468,7 @@ describe("verify", function () {
413468
it("9", function () {
414469
verifyAndAssertMessages(
415470
[
416-
"import type Foo from 'foo';",
417-
"import type Foo2 from 'foo';",
418-
"export default function <Foo, Foo2>() {}"
471+
"export default function <T1, T2>(a: T1, b: T2) {}"
419472
].join("\n"),
420473
{ "no-unused-vars": 1, "no-undef": 1 },
421474
[]
@@ -425,9 +478,7 @@ describe("verify", function () {
425478
it("10", function () {
426479
verifyAndAssertMessages(
427480
[
428-
"import type Foo from 'foo';",
429-
"import type Foo2 from 'foo';",
430-
"var a=function<Foo,Foo2>() {}; a;"
481+
"var a=function<T1,T2>(a: T1, b: T2) {return a + b;}; a;"
431482
].join("\n"),
432483
{ "no-unused-vars": 1, "no-undef": 1 },
433484
[]
@@ -437,10 +488,7 @@ describe("verify", function () {
437488
it("11", function () {
438489
verifyAndAssertMessages(
439490
[
440-
"import type Foo from 'foo';",
441-
"import type Foo2 from 'foo';",
442-
"import type Foo3 from 'foo';",
443-
"var a={*id<Foo>(x: Foo2): Foo3 { x; }}; a;"
491+
"var a={*id<T>(x: T): T { x; }}; a;"
444492
].join("\n"),
445493
{ "no-unused-vars": 1, "no-undef": 1 },
446494
[]
@@ -450,10 +498,7 @@ describe("verify", function () {
450498
it("12", function () {
451499
verifyAndAssertMessages(
452500
[
453-
"import type Foo from 'foo';",
454-
"import type Foo2 from 'foo';",
455-
"import type Foo3 from 'foo';",
456-
"var a={async id<Foo>(x: Foo2): Foo3 { x; }}; a;"
501+
"var a={async id<T>(x: T): T { x; }}; a;"
457502
].join("\n"),
458503
{ "no-unused-vars": 1, "no-undef": 1 },
459504
[]
@@ -463,10 +508,7 @@ describe("verify", function () {
463508
it("13", function () {
464509
verifyAndAssertMessages(
465510
[
466-
"import type Foo from 'foo';",
467-
"import type Foo2 from 'foo';",
468-
"import type Foo3 from 'foo';",
469-
"var a={123<Foo>(x: Foo2): Foo3 { x; }}; a;"
511+
"var a={123<T>(x: T): T { x; }}; a;"
470512
].join("\n"),
471513
{ "no-unused-vars": 1, "no-undef": 1 },
472514
[]
@@ -597,10 +639,8 @@ describe("verify", function () {
597639
it("24", function () {
598640
verifyAndAssertMessages(
599641
[
600-
"import type Foo from 'foo';",
601-
"import type Foo2 from 'foo';",
602-
"import Baz from 'foo';",
603-
"export default class Bar<Foo> extends Baz<Foo2> { };"
642+
"import type Baz from 'baz';",
643+
"export default class Bar<T> extends Baz<T> { };"
604644
].join("\n"),
605645
{ "no-unused-vars": 1, "no-undef": 1 },
606646
[]
@@ -610,10 +650,7 @@ describe("verify", function () {
610650
it("25", function () {
611651
verifyAndAssertMessages(
612652
[
613-
"import type Foo from 'foo';",
614-
"import type Foo2 from 'foo';",
615-
"import type Foo3 from 'foo';",
616-
"export default class Bar<Foo> { bar<Foo2>():Foo3 { return 42; }}"
653+
"export default class Bar<T> { bar(): T { return 42; }}"
617654
].join("\n"),
618655
{ "no-unused-vars": 1, "no-undef": 1 },
619656
[]

0 commit comments

Comments
 (0)