diff --git a/index.js b/index.js index 81475eb7..e78025c0 100644 --- a/index.js +++ b/index.js @@ -162,37 +162,49 @@ function monkeypatch() { if (node.typeAnnotation) { visitTypeAnnotation.call(this, node.typeAnnotation); } else if (node.type === "Identifier") { - // exception for polymorphic types: , , etc - if (node.name.length === 1 && node.name === node.name.toUpperCase()) { - return; - } this.visit(node); } else { 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 var visitClass = referencer.prototype.visitClass; referencer.prototype.visitClass = function(node) { visitDecorators.call(this, node); + var typeParamScope; + if (node.typeParameters) { + typeParamScope = nestTypeParamScope(this.scopeManager, node); + } // visit flow type: ClassImplements if (node.implements) { for (var i = 0; i < node.implements.length; 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) { for (var k = 0; k < node.superTypeParameters.params.length; k++) { checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]); } } visitClass.call(this, node); + if (typeParamScope) { + this.close(node); + } }; // visit decorators that are in: Property / MethodDefinition var visitProperty = referencer.prototype.visitProperty; @@ -207,6 +219,10 @@ function monkeypatch() { // visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression var visitFunction = referencer.prototype.visitFunction; referencer.prototype.visitFunction = function(node) { + var typeParamScope; + if (node.typeParameters) { + typeParamScope = nestTypeParamScope(this.scopeManager, node); + } if (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); + if (typeParamScope) { + this.close(node); + } }; // visit flow type in VariableDeclaration @@ -261,13 +275,15 @@ function monkeypatch() { referencer.prototype.TypeAlias = function(node) { createScopeVariable.call(this, node, node.id); + var typeParamScope; + if (node.typeParameters) { + typeParamScope = nestTypeParamScope(this.scopeManager, node); + } if (node.right) { visitTypeAnnotation.call(this, node.right); } - if (node.typeParameters) { - for (var i = 0; i < node.typeParameters.params.length; i++) { - checkIdentifierOrVisit.call(this, node.typeParameters.params[i]); - } + if (typeParamScope) { + this.close(node); } }; diff --git a/test/non-regression.js b/test/non-regression.js index a0b149c2..de0bfbf6 100644 --- a/test/non-regression.js +++ b/test/non-regression.js @@ -169,8 +169,8 @@ describe("verify", function () { verifyAndAssertMessages([ "import type Foo from 'foo';", "import type Foo2 from 'foo';", - "function log() {}", - "log();" + "function log(a: T1, b: T2) { return a + b; }", + "log(1, 2);" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -276,9 +276,8 @@ describe("verify", function () { it("type alias with type parameters", function () { verifyAndAssertMessages([ "import type Bar from 'foo';", - "import type Foo2 from 'foo';", "import type Foo3 from 'foo';", - "type Foo = Bar", + "type Foo = Bar", "var x : Foo = 1; x;" ].join("\n"), { "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 {", + "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(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 = Bar; 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(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 {}", + ].join("\n"), + { "no-unused-vars": 1, "no-undef": 1 }, + [ '2:29 "T" is not defined. no-undef' ] + ); + }); + it("1", function () { verifyAndAssertMessages( [ @@ -413,9 +468,7 @@ describe("verify", function () { it("9", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "export default function () {}" + "export default function (a: T1, b: T2) {}" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -425,9 +478,7 @@ describe("verify", function () { it("10", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "var a=function() {}; a;" + "var a=function(a: T1, b: T2) {return a + b;}; a;" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -437,10 +488,7 @@ describe("verify", function () { it("11", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "import type Foo3 from 'foo';", - "var a={*id(x: Foo2): Foo3 { x; }}; a;" + "var a={*id(x: T): T { x; }}; a;" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -450,10 +498,7 @@ describe("verify", function () { it("12", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "import type Foo3 from 'foo';", - "var a={async id(x: Foo2): Foo3 { x; }}; a;" + "var a={async id(x: T): T { x; }}; a;" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -463,10 +508,7 @@ describe("verify", function () { it("13", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "import type Foo3 from 'foo';", - "var a={123(x: Foo2): Foo3 { x; }}; a;" + "var a={123(x: T): T { x; }}; a;" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -597,10 +639,8 @@ describe("verify", function () { it("24", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "import Baz from 'foo';", - "export default class Bar extends Baz { };" + "import type Baz from 'baz';", + "export default class Bar extends Baz { };" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, [] @@ -610,10 +650,7 @@ describe("verify", function () { it("25", function () { verifyAndAssertMessages( [ - "import type Foo from 'foo';", - "import type Foo2 from 'foo';", - "import type Foo3 from 'foo';", - "export default class Bar { bar():Foo3 { return 42; }}" + "export default class Bar { bar(): T { return 42; }}" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, []