Merge pull request babel/babel-eslint#126 from leebyron/fix-flow-type

Fixes flow type scoping issues - fixes babel/babel-eslint#123
This commit is contained in:
Henry Zhu 2015-06-09 15:00:38 -04:00
parent d83aa7f6ce
commit 1c25c8eed3
2 changed files with 101 additions and 48 deletions

View File

@ -162,37 +162,49 @@ function monkeypatch() {
if (node.typeAnnotation) { if (node.typeAnnotation) {
visitTypeAnnotation.call(this, node.typeAnnotation); visitTypeAnnotation.call(this, node.typeAnnotation);
} else if (node.type === "Identifier") { } else if (node.type === "Identifier") {
// exception for polymorphic types: <T>, <A>, etc
if (node.name.length === 1 && node.name === node.name.toUpperCase()) {
return;
}
this.visit(node); this.visit(node);
} else { } else {
visitTypeAnnotation.call(this, node); visitTypeAnnotation.call(this, node);
} }
} }
function nestTypeParamScope(manager, node) {
var parentScope = manager.__currentScope;
var scope = new escope.Scope(manager, "type-parameters", parentScope, node, false);
manager.__nestScope(scope);
for (var j = 0; j < node.typeParameters.params.length; j++) {
var name = node.typeParameters.params[j];
scope.__define(name, new Definition("TypeParameter", name, name));
}
scope.__define = function() {
return parentScope.__define.apply(parentScope, arguments);
}
return scope;
}
// visit decorators that are in: ClassDeclaration / ClassExpression // visit decorators that are in: ClassDeclaration / ClassExpression
var visitClass = referencer.prototype.visitClass; var visitClass = referencer.prototype.visitClass;
referencer.prototype.visitClass = function(node) { referencer.prototype.visitClass = function(node) {
visitDecorators.call(this, node); visitDecorators.call(this, node);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
// visit flow type: ClassImplements // visit flow type: ClassImplements
if (node.implements) { if (node.implements) {
for (var i = 0; i < node.implements.length; i++) { for (var i = 0; i < node.implements.length; i++) {
checkIdentifierOrVisit.call(this, node.implements[i]); checkIdentifierOrVisit.call(this, node.implements[i]);
} }
} }
if (node.typeParameters) {
for (var j = 0; j < node.typeParameters.params.length; j++) {
checkIdentifierOrVisit.call(this, node.typeParameters.params[j]);
}
}
if (node.superTypeParameters) { if (node.superTypeParameters) {
for (var k = 0; k < node.superTypeParameters.params.length; k++) { for (var k = 0; k < node.superTypeParameters.params.length; k++) {
checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]); checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]);
} }
} }
visitClass.call(this, node); visitClass.call(this, node);
if (typeParamScope) {
this.close(node);
}
}; };
// visit decorators that are in: Property / MethodDefinition // visit decorators that are in: Property / MethodDefinition
var visitProperty = referencer.prototype.visitProperty; var visitProperty = referencer.prototype.visitProperty;
@ -207,6 +219,10 @@ function monkeypatch() {
// visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression // visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression
var visitFunction = referencer.prototype.visitFunction; var visitFunction = referencer.prototype.visitFunction;
referencer.prototype.visitFunction = function(node) { referencer.prototype.visitFunction = function(node) {
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
if (node.returnType) { if (node.returnType) {
checkIdentifierOrVisit.call(this, node.returnType); checkIdentifierOrVisit.call(this, node.returnType);
} }
@ -218,12 +234,10 @@ function monkeypatch() {
} }
} }
} }
if (node.typeParameters) {
for (var j = 0; j < node.typeParameters.params.length; j++) {
checkIdentifierOrVisit.call(this, node.typeParameters.params[j]);
}
}
visitFunction.call(this, node); visitFunction.call(this, node);
if (typeParamScope) {
this.close(node);
}
}; };
// visit flow type in VariableDeclaration // visit flow type in VariableDeclaration
@ -261,13 +275,15 @@ function monkeypatch() {
referencer.prototype.TypeAlias = function(node) { referencer.prototype.TypeAlias = function(node) {
createScopeVariable.call(this, node, node.id); createScopeVariable.call(this, node, node.id);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
if (node.right) { if (node.right) {
visitTypeAnnotation.call(this, node.right); visitTypeAnnotation.call(this, node.right);
} }
if (node.typeParameters) { if (typeParamScope) {
for (var i = 0; i < node.typeParameters.params.length; i++) { this.close(node);
checkIdentifierOrVisit.call(this, node.typeParameters.params[i]);
}
} }
}; };

View File

@ -169,8 +169,8 @@ describe("verify", function () {
verifyAndAssertMessages([ verifyAndAssertMessages([
"import type Foo from 'foo';", "import type Foo from 'foo';",
"import type Foo2 from 'foo';", "import type Foo2 from 'foo';",
"function log<Foo, Foo2>() {}", "function log<T1, T2>(a: T1, b: T2) { return a + b; }",
"log();" "log<Foo, Foo2>(1, 2);"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -276,9 +276,8 @@ describe("verify", function () {
it("type alias with type parameters", function () { it("type alias with type parameters", function () {
verifyAndAssertMessages([ verifyAndAssertMessages([
"import type Bar from 'foo';", "import type Bar from 'foo';",
"import type Foo2 from 'foo';",
"import type Foo3 from 'foo';", "import type Foo3 from 'foo';",
"type Foo<Foo2> = Bar<Foo3>", "type Foo<T> = Bar<T, Foo3>",
"var x : Foo = 1; x;" "var x : Foo = 1; x;"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
@ -316,6 +315,62 @@ describe("verify", function () {
); );
}); });
it("polymorphpic/generic types for class #123", function () {
verifyAndAssertMessages([
"class Box<T> {",
"value: T;",
"}",
"var box = new Box();",
"console.log(box.value);"
].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 },
[]
);
});
it("polymorphpic/generic types for function #123", function () {
verifyAndAssertMessages([
"export function identity<T>(value) {",
"var a: T = value; a;",
"}"
].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 },
[]
);
});
it("polymorphpic/generic types for type alias #123", function () {
verifyAndAssertMessages([
"import Bar from './Bar';",
"type Foo<T> = Bar<T>; var x: Foo = 1; x++"
].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 },
[]
);
});
it("polymorphpic/generic types - outside of fn scope #123", function () {
verifyAndAssertMessages([
"export function foo<T>(value) {",
"};",
"var b: T = 1; b;"
].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 },
[ '1:20 T is defined but never used no-unused-vars',
'3:7 "T" is not defined. no-undef' ]
);
});
it("polymorphpic/generic types - extending unknown #123", function () {
verifyAndAssertMessages([
"import Bar from 'bar';",
"export class Foo extends Bar<T> {}",
].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 },
[ '2:29 "T" is not defined. no-undef' ]
);
});
it("1", function () { it("1", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
@ -413,9 +468,7 @@ describe("verify", function () {
it("9", function () { it("9", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "export default function <T1, T2>(a: T1, b: T2) {}"
"import type Foo2 from 'foo';",
"export default function <Foo, Foo2>() {}"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -425,9 +478,7 @@ describe("verify", function () {
it("10", function () { it("10", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "var a=function<T1,T2>(a: T1, b: T2) {return a + b;}; a;"
"import type Foo2 from 'foo';",
"var a=function<Foo,Foo2>() {}; a;"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -437,10 +488,7 @@ describe("verify", function () {
it("11", function () { it("11", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "var a={*id<T>(x: T): T { x; }}; a;"
"import type Foo2 from 'foo';",
"import type Foo3 from 'foo';",
"var a={*id<Foo>(x: Foo2): Foo3 { x; }}; a;"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -450,10 +498,7 @@ describe("verify", function () {
it("12", function () { it("12", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "var a={async id<T>(x: T): T { x; }}; a;"
"import type Foo2 from 'foo';",
"import type Foo3 from 'foo';",
"var a={async id<Foo>(x: Foo2): Foo3 { x; }}; a;"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -463,10 +508,7 @@ describe("verify", function () {
it("13", function () { it("13", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "var a={123<T>(x: T): T { x; }}; a;"
"import type Foo2 from 'foo';",
"import type Foo3 from 'foo';",
"var a={123<Foo>(x: Foo2): Foo3 { x; }}; a;"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -597,10 +639,8 @@ describe("verify", function () {
it("24", function () { it("24", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "import type Baz from 'baz';",
"import type Foo2 from 'foo';", "export default class Bar<T> extends Baz<T> { };"
"import Baz from 'foo';",
"export default class Bar<Foo> extends Baz<Foo2> { };"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []
@ -610,10 +650,7 @@ describe("verify", function () {
it("25", function () { it("25", function () {
verifyAndAssertMessages( verifyAndAssertMessages(
[ [
"import type Foo from 'foo';", "export default class Bar<T> { bar(): T { return 42; }}"
"import type Foo2 from 'foo';",
"import type Foo3 from 'foo';",
"export default class Bar<Foo> { bar<Foo2>():Foo3 { return 42; }}"
].join("\n"), ].join("\n"),
{ "no-unused-vars": 1, "no-undef": 1 }, { "no-unused-vars": 1, "no-undef": 1 },
[] []