From aeaf83c54f228d9af798810f7bf4f9635ce79a83 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 09:42:21 -0700 Subject: [PATCH 1/7] Fix type when annotated with a JSDoc function type Previously, 1. A variable annotated with a JSDoc function type would not require all its parameters to be provided. This should only apply to functions without a type annotation. 2. A parameter in a function with a JSDoc function type annotation would still have the type 'any'. 3. Two `var` declarations in a Typescript and Javascript file, respectively, would error even when they had identical function types. --- src/compiler/checker.ts | 8 ++++++- src/compiler/utilities.ts | 9 +++++++ tests/baselines/reference/jsdocTypeTag.js | 7 ++++++ .../baselines/reference/jsdocTypeTag.symbols | 8 +++++++ tests/baselines/reference/jsdocTypeTag.types | 8 +++++++ .../jsdocTypeTagParameterType.errors.txt | 18 ++++++++++++++ .../jsdocTypeTagParameterType.symbols | 19 +++++++++++++++ .../reference/jsdocTypeTagParameterType.types | 24 +++++++++++++++++++ .../jsdocTypeTagRequiredParameters.errors.txt | 19 +++++++++++++++ .../jsdocTypeTagRequiredParameters.symbols | 19 +++++++++++++++ .../jsdocTypeTagRequiredParameters.types | 22 +++++++++++++++++ tests/cases/conformance/jsdoc/jsdocTypeTag.ts | 4 ++++ .../jsdoc/jsdocTypeTagParameterType.ts | 15 ++++++++++++ .../jsdoc/jsdocTypeTagRequiredParameters.ts | 16 +++++++++++++ 14 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/jsdocTypeTagParameterType.errors.txt create mode 100644 tests/baselines/reference/jsdocTypeTagParameterType.symbols create mode 100644 tests/baselines/reference/jsdocTypeTagParameterType.types create mode 100644 tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt create mode 100644 tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols create mode 100644 tests/baselines/reference/jsdocTypeTagRequiredParameters.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTypeTagParameterType.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 35b28e0452011..ef63afaa5e088 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6699,7 +6699,13 @@ namespace ts { let hasThisParameter: boolean; const iife = getImmediatelyInvokedFunctionExpression(declaration); const isJSConstructSignature = isJSDocConstructSignature(declaration); - const isUntypedSignatureInJSFile = !iife && !isJSConstructSignature && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration); + const isUntypedSignatureInJSFile = !iife && + !isJSConstructSignature && + isInJavaScriptFile(declaration) && + declaration.kind !== SyntaxKind.FunctionType && + declaration.kind !== SyntaxKind.JSDocFunctionType && + !hasJSDocParameterTags(declaration) && + !getJSDocType(declaration); // If this is a JSDoc construct signature, then skip the first parameter in the // parameter list. The first parameter represents the return type of the construct diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 235b10e4cf19d..3e39cf392586f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4510,6 +4510,15 @@ namespace ts { let tag: JSDocTypeTag | JSDocParameterTag | undefined = getFirstJSDocTag(node, isJSDocTypeTag); if (!tag && isParameter(node)) { tag = find(getJSDocParameterTags(node), tag => !!tag.typeExpression); + if (!tag && isFunctionLike(node.parent)) { + const typeTag = getJSDocTypeTag(node.parent); + if (typeTag && typeTag.typeExpression && isFunctionLike(typeTag.typeExpression.type)) { + const i = node.parent.parameters.indexOf(node); + if (i > -1) { + return typeTag.typeExpression.type.parameters[i].type; + } + } + } } return tag && tag.typeExpression && tag.typeExpression.type; diff --git a/tests/baselines/reference/jsdocTypeTag.js b/tests/baselines/reference/jsdocTypeTag.js index 4789b1d26afce..90142bccd049e 100644 --- a/tests/baselines/reference/jsdocTypeTag.js +++ b/tests/baselines/reference/jsdocTypeTag.js @@ -60,6 +60,9 @@ var obj; /** @type {Function} */ var Func; + +/** @type {(s: string) => number} */ +var f; //// [b.ts] var S: string; @@ -82,6 +85,7 @@ var nullable: number | null; var Obj: any; var obj: any; var Func: Function; +var f: (s: string) => number; //// [a.js] @@ -125,6 +129,8 @@ var Obj; var obj; /** @type {Function} */ var Func; +/** @type {(s: string) => number} */ +var f; //// [b.js] var S; var s; @@ -146,3 +152,4 @@ var nullable; var Obj; var obj; var Func; +var f; diff --git a/tests/baselines/reference/jsdocTypeTag.symbols b/tests/baselines/reference/jsdocTypeTag.symbols index 77df5b119f470..0587d7d8458a4 100644 --- a/tests/baselines/reference/jsdocTypeTag.symbols +++ b/tests/baselines/reference/jsdocTypeTag.symbols @@ -79,6 +79,10 @@ var obj; var Func; >Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3)) +/** @type {(s: string) => number} */ +var f; +>f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3)) + === tests/cases/conformance/jsdoc/b.ts === var S: string; >S : Symbol(S, Decl(a.js, 1, 3), Decl(b.ts, 0, 3)) @@ -143,3 +147,7 @@ var Func: Function; >Func : Symbol(Func, Decl(a.js, 58, 3), Decl(b.ts, 19, 3)) >Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +var f: (s: string) => number; +>f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3)) +>s : Symbol(s, Decl(b.ts, 20, 8)) + diff --git a/tests/baselines/reference/jsdocTypeTag.types b/tests/baselines/reference/jsdocTypeTag.types index 9813a186a00d9..fee36b6b4ef9e 100644 --- a/tests/baselines/reference/jsdocTypeTag.types +++ b/tests/baselines/reference/jsdocTypeTag.types @@ -79,6 +79,10 @@ var obj; var Func; >Func : Function +/** @type {(s: string) => number} */ +var f; +>f : (s: string) => number + === tests/cases/conformance/jsdoc/b.ts === var S: string; >S : string @@ -146,3 +150,7 @@ var Func: Function; >Func : Function >Function : Function +var f: (s: string) => number; +>f : (s: string) => number +>s : string + diff --git a/tests/baselines/reference/jsdocTypeTagParameterType.errors.txt b/tests/baselines/reference/jsdocTypeTagParameterType.errors.txt new file mode 100644 index 0000000000000..d03e8211a18ff --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagParameterType.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/jsdoc/a.js(3,5): error TS2322: Type '1' is not assignable to type 'string'. +tests/cases/conformance/jsdoc/a.js(7,5): error TS2322: Type '1' is not assignable to type 'string'. + + +==== tests/cases/conformance/jsdoc/a.js (2 errors) ==== + /** @type {function(string): void} */ + const f = (value) => { + value = 1 // should error + ~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'string'. + }; + /** @type {(s: string) => void} */ + function g(s) { + s = 1 // Should error + ~ +!!! error TS2322: Type '1' is not assignable to type 'string'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypeTagParameterType.symbols b/tests/baselines/reference/jsdocTypeTagParameterType.symbols new file mode 100644 index 0000000000000..e9d098bda4b64 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagParameterType.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @type {function(string): void} */ +const f = (value) => { +>f : Symbol(f, Decl(a.js, 1, 5)) +>value : Symbol(value, Decl(a.js, 1, 11)) + + value = 1 // should error +>value : Symbol(value, Decl(a.js, 1, 11)) + +}; +/** @type {(s: string) => void} */ +function g(s) { +>g : Symbol(g, Decl(a.js, 3, 2)) +>s : Symbol(s, Decl(a.js, 5, 11)) + + s = 1 // Should error +>s : Symbol(s, Decl(a.js, 5, 11)) +} + diff --git a/tests/baselines/reference/jsdocTypeTagParameterType.types b/tests/baselines/reference/jsdocTypeTagParameterType.types new file mode 100644 index 0000000000000..e750b6c6be6d0 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagParameterType.types @@ -0,0 +1,24 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @type {function(string): void} */ +const f = (value) => { +>f : (arg0: string) => void +>(value) => { value = 1 // should error} : (value: string) => void +>value : string + + value = 1 // should error +>value = 1 : 1 +>value : string +>1 : 1 + +}; +/** @type {(s: string) => void} */ +function g(s) { +>g : (s: string) => void +>s : string + + s = 1 // Should error +>s = 1 : 1 +>s : string +>1 : 1 +} + diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt new file mode 100644 index 0000000000000..6aff6cb1201df --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt @@ -0,0 +1,19 @@ +tests/cases/conformance/jsdoc/a.js(8,1): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/jsdoc/a.js(9,1): error TS2554: Expected 1 arguments, but got 0. + + +==== tests/cases/conformance/jsdoc/a.js (2 errors) ==== + /** @type {function(string): void} */ + const f = (value) => { + }; + /** @type {(s: string) => void} */ + function g(s) { + } + + f() // should error + ~~~ +!!! error TS2554: Expected 1 arguments, but got 0. + g() // should error + ~~~ +!!! error TS2554: Expected 1 arguments, but got 0. + \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols b/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols new file mode 100644 index 0000000000000..8fe8052a1cf95 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @type {function(string): void} */ +const f = (value) => { +>f : Symbol(f, Decl(a.js, 1, 5)) +>value : Symbol(value, Decl(a.js, 1, 11)) + +}; +/** @type {(s: string) => void} */ +function g(s) { +>g : Symbol(g, Decl(a.js, 2, 2)) +>s : Symbol(s, Decl(a.js, 4, 11)) +} + +f() // should error +>f : Symbol(f, Decl(a.js, 1, 5)) + +g() // should error +>g : Symbol(g, Decl(a.js, 2, 2)) + diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.types b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types new file mode 100644 index 0000000000000..dea1861b83384 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types @@ -0,0 +1,22 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** @type {function(string): void} */ +const f = (value) => { +>f : (arg0: string) => void +>(value) => {} : (value: string) => void +>value : string + +}; +/** @type {(s: string) => void} */ +function g(s) { +>g : (s: string) => void +>s : string +} + +f() // should error +>f() : void +>f : (arg0: string) => void + +g() // should error +>g() : void +>g : (s: string) => void + diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts index 1c19771a3cd38..3fd5a3d4a4654 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts @@ -63,6 +63,9 @@ var obj; /** @type {Function} */ var Func; +/** @type {(s: string) => number} */ +var f; + // @filename: b.ts var S: string; var s: string; @@ -84,3 +87,4 @@ var nullable: number | null; var Obj: any; var obj: any; var Func: Function; +var f: (s: string) => number; diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTagParameterType.ts b/tests/cases/conformance/jsdoc/jsdocTypeTagParameterType.ts new file mode 100644 index 0000000000000..598555d1bb38b --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeTagParameterType.ts @@ -0,0 +1,15 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @strict: true +// @noImplicitAny: true +// @Filename: a.js + +/** @type {function(string): void} */ +const f = (value) => { + value = 1 // should error +}; +/** @type {(s: string) => void} */ +function g(s) { + s = 1 // Should error +} diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts b/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts new file mode 100644 index 0000000000000..0710eff28147f --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts @@ -0,0 +1,16 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @strict: true +// @noImplicitAny: true +// @Filename: a.js + +/** @type {function(string): void} */ +const f = (value) => { +}; +/** @type {(s: string) => void} */ +function g(s) { +} + +f() // should error +g() // should error From d79b4dff6a244e5a91d26916df870bf9608799fe Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 10:32:22 -0700 Subject: [PATCH 2/7] Update baselines and add constructor test --- .../checkJsdocTypeTagOnObjectProperty1.types | 16 ++++++++-------- ...eckJsdocTypeTagOnObjectProperty2.errors.txt | 18 +++++++----------- .../checkJsdocTypeTagOnObjectProperty2.types | 16 ++++++++-------- tests/baselines/reference/jsdocTypeTag.js | 7 +++++++ tests/baselines/reference/jsdocTypeTag.symbols | 9 +++++++++ tests/baselines/reference/jsdocTypeTag.types | 9 +++++++++ tests/cases/conformance/jsdoc/jsdocTypeTag.ts | 4 ++++ .../fourslash/annotateWithTypeFromJSDoc16.ts | 1 + 8 files changed, 53 insertions(+), 27 deletions(-) diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types index 41cf9fff7393a..2601309b504b2 100644 --- a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types @@ -20,12 +20,12 @@ const obj = { /** @type {function(number): number} */ method1(n1) { ->method1 : (n1: any) => any ->n1 : any +>method1 : (n1: number) => number +>n1 : number return n1 + 42; ->n1 + 42 : any ->n1 : any +>n1 + 42 : number +>n1 : number >42 : 42 }, @@ -44,10 +44,10 @@ const obj = { /** @type {function(number): number} */ arrowFunc: (num) => num + 42 >arrowFunc : (arg0: number) => number ->(num) => num + 42 : (num: any) => any ->num : any ->num + 42 : any ->num : any +>(num) => num + 42 : (num: number) => number +>num : number +>num + 42 : number +>num : number >42 : 42 } obj.foo = 'string' diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt index 33c83e6117236..48559d79cb189 100644 --- a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt @@ -1,11 +1,9 @@ tests/cases/conformance/jsdoc/0.js(5,3): error TS2322: Type 'number' is not assignable to type 'string | undefined'. -tests/cases/conformance/jsdoc/0.js(7,3): error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'. +tests/cases/conformance/jsdoc/0.js(7,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'. Type 'string' is not assignable to type 'number'. -tests/cases/conformance/jsdoc/0.js(11,3): error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'. +tests/cases/conformance/jsdoc/0.js(11,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'. Type 'string' is not assignable to type 'number'. -tests/cases/conformance/jsdoc/0.js(13,3): error TS2322: Type '(num?: string) => string' is not assignable to type '(arg0: number) => number'. - Types of parameters 'num' and 'arg0' are incompatible. - Type 'number' is not assignable to type 'string | undefined'. +tests/cases/conformance/jsdoc/0.js(13,15): error TS2322: Type '"0"' is not assignable to type 'number'. tests/cases/conformance/jsdoc/0.js(15,3): error TS2322: Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/jsdoc/0.js(19,5): error TS2322: Type 'number' is not assignable to type 'string'. tests/cases/conformance/jsdoc/0.js(22,22): error TS2345: Argument of type '"0"' is not assignable to parameter of type 'number'. @@ -22,21 +20,19 @@ tests/cases/conformance/jsdoc/0.js(22,22): error TS2345: Argument of type '"0"' /** @type {function(number): number} */ method1(n1) { ~~~~~~~ -!!! error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'. +!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'. !!! error TS2322: Type 'string' is not assignable to type 'number'. return "42"; }, /** @type {function(number): number} */ method2: (n1) => "lol", ~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2322: Type '(n1: any) => string' is not assignable to type '(arg0: number) => number'. +!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'. !!! error TS2322: Type 'string' is not assignable to type 'number'. /** @type {function(number): number} */ arrowFunc: (num="0") => num + 42, - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2322: Type '(num?: string) => string' is not assignable to type '(arg0: number) => number'. -!!! error TS2322: Types of parameters 'num' and 'arg0' are incompatible. -!!! error TS2322: Type 'number' is not assignable to type 'string | undefined'. + ~~~~~~~ +!!! error TS2322: Type '"0"' is not assignable to type 'number'. /** @type {string} */ lol ~~~ diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.types b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.types index 443826c7aeb58..c7f2b7bbeb007 100644 --- a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.types +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.types @@ -14,8 +14,8 @@ const obj = { /** @type {function(number): number} */ method1(n1) { ->method1 : (n1: any) => string ->n1 : any +>method1 : (n1: number) => string +>n1 : number return "42"; >"42" : "42" @@ -24,18 +24,18 @@ const obj = { /** @type {function(number): number} */ method2: (n1) => "lol", >method2 : (arg0: number) => number ->(n1) => "lol" : (n1: any) => string ->n1 : any +>(n1) => "lol" : (n1: number) => string +>n1 : number >"lol" : "lol" /** @type {function(number): number} */ arrowFunc: (num="0") => num + 42, >arrowFunc : (arg0: number) => number ->(num="0") => num + 42 : (num?: string) => string ->num : string +>(num="0") => num + 42 : (num?: number) => number +>num : number >"0" : "0" ->num + 42 : string ->num : string +>num + 42 : number +>num : number >42 : 42 /** @type {string} */ diff --git a/tests/baselines/reference/jsdocTypeTag.js b/tests/baselines/reference/jsdocTypeTag.js index 90142bccd049e..394fb5a53da01 100644 --- a/tests/baselines/reference/jsdocTypeTag.js +++ b/tests/baselines/reference/jsdocTypeTag.js @@ -63,6 +63,9 @@ var Func; /** @type {(s: string) => number} */ var f; + +/** @type {new (s: string) => { s: string }} */ +var ctor; //// [b.ts] var S: string; @@ -86,6 +89,7 @@ var Obj: any; var obj: any; var Func: Function; var f: (s: string) => number; +var ctor: new (s: string) => { s: string }; //// [a.js] @@ -131,6 +135,8 @@ var obj; var Func; /** @type {(s: string) => number} */ var f; +/** @type {new (s: string) => { s: string }} */ +var ctor; //// [b.js] var S; var s; @@ -153,3 +159,4 @@ var Obj; var obj; var Func; var f; +var ctor; diff --git a/tests/baselines/reference/jsdocTypeTag.symbols b/tests/baselines/reference/jsdocTypeTag.symbols index 0587d7d8458a4..db299c10d2b16 100644 --- a/tests/baselines/reference/jsdocTypeTag.symbols +++ b/tests/baselines/reference/jsdocTypeTag.symbols @@ -83,6 +83,10 @@ var Func; var f; >f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3)) +/** @type {new (s: string) => { s: string }} */ +var ctor; +>ctor : Symbol(ctor, Decl(a.js, 64, 3), Decl(b.ts, 21, 3)) + === tests/cases/conformance/jsdoc/b.ts === var S: string; >S : Symbol(S, Decl(a.js, 1, 3), Decl(b.ts, 0, 3)) @@ -151,3 +155,8 @@ var f: (s: string) => number; >f : Symbol(f, Decl(a.js, 61, 3), Decl(b.ts, 20, 3)) >s : Symbol(s, Decl(b.ts, 20, 8)) +var ctor: new (s: string) => { s: string }; +>ctor : Symbol(ctor, Decl(a.js, 64, 3), Decl(b.ts, 21, 3)) +>s : Symbol(s, Decl(b.ts, 21, 15)) +>s : Symbol(s, Decl(b.ts, 21, 30)) + diff --git a/tests/baselines/reference/jsdocTypeTag.types b/tests/baselines/reference/jsdocTypeTag.types index fee36b6b4ef9e..754a4b725cdc0 100644 --- a/tests/baselines/reference/jsdocTypeTag.types +++ b/tests/baselines/reference/jsdocTypeTag.types @@ -83,6 +83,10 @@ var Func; var f; >f : (s: string) => number +/** @type {new (s: string) => { s: string }} */ +var ctor; +>ctor : new (s: string) => { s: string; } + === tests/cases/conformance/jsdoc/b.ts === var S: string; >S : string @@ -154,3 +158,8 @@ var f: (s: string) => number; >f : (s: string) => number >s : string +var ctor: new (s: string) => { s: string }; +>ctor : new (s: string) => { s: string; } +>s : string +>s : string + diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts index 3fd5a3d4a4654..b5fdd397fa765 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeTag.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeTag.ts @@ -66,6 +66,9 @@ var Func; /** @type {(s: string) => number} */ var f; +/** @type {new (s: string) => { s: string }} */ +var ctor; + // @filename: b.ts var S: string; var s: string; @@ -88,3 +91,4 @@ var Obj: any; var obj: any; var Func: Function; var f: (s: string) => number; +var ctor: new (s: string) => { s: string }; diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts index ab57b32aaacc2..49d7133d9c460 100644 --- a/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc16.ts @@ -5,6 +5,7 @@ verify.codeFix({ description: "Annotate with type from JSDoc", + index: 0, newFileContent: `/** @type {function(*, ...number, ...boolean): void} */ var x: (arg0: any, arg1: number[], ...rest: boolean[]) => void = (x, ys, ...zs) => { };`, From 34c977eed52f88a0f4118713e08c1ac06a9abe34 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 10:32:38 -0700 Subject: [PATCH 3/7] Handle ConstructorType too --- src/compiler/checker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ef63afaa5e088..5c39399e5364a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6703,6 +6703,7 @@ namespace ts { !isJSConstructSignature && isInJavaScriptFile(declaration) && declaration.kind !== SyntaxKind.FunctionType && + declaration.kind !== SyntaxKind.ConstructorType && declaration.kind !== SyntaxKind.JSDocFunctionType && !hasJSDocParameterTags(declaration) && !getJSDocType(declaration); From dfbae12a9ba86777fcf91efb4d9473b599dd1562 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 11:00:17 -0700 Subject: [PATCH 4/7] Add test:method sig inside literal type --- .../jsdocTypeTagRequiredParameters.errors.txt | 16 +++++++++++++--- .../jsdocTypeTagRequiredParameters.symbols | 8 ++++++++ .../jsdocTypeTagRequiredParameters.types | 9 +++++++++ .../jsdoc/jsdocTypeTagRequiredParameters.ts | 4 ++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt index 6aff6cb1201df..4538fa9e58f60 100644 --- a/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt @@ -1,14 +1,21 @@ -tests/cases/conformance/jsdoc/a.js(8,1): error TS2554: Expected 1 arguments, but got 0. -tests/cases/conformance/jsdoc/a.js(9,1): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/jsdoc/a.js(8,12): error TS7006: Parameter 's' implicitly has an 'any' type. +tests/cases/conformance/jsdoc/a.js(11,1): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/jsdoc/a.js(12,1): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/jsdoc/a.js(13,1): error TS2554: Expected 1 arguments, but got 0. -==== tests/cases/conformance/jsdoc/a.js (2 errors) ==== +==== tests/cases/conformance/jsdoc/a.js (4 errors) ==== /** @type {function(string): void} */ const f = (value) => { }; /** @type {(s: string) => void} */ function g(s) { } + /** @type {{(s: string): void}} */ + function h(s) { + ~ +!!! error TS7006: Parameter 's' implicitly has an 'any' type. + } f() // should error ~~~ @@ -16,4 +23,7 @@ tests/cases/conformance/jsdoc/a.js(9,1): error TS2554: Expected 1 arguments, but g() // should error ~~~ !!! error TS2554: Expected 1 arguments, but got 0. + h() + ~~~ +!!! error TS2554: Expected 1 arguments, but got 0. \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols b/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols index 8fe8052a1cf95..1c740c4f49190 100644 --- a/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.symbols @@ -10,6 +10,11 @@ function g(s) { >g : Symbol(g, Decl(a.js, 2, 2)) >s : Symbol(s, Decl(a.js, 4, 11)) } +/** @type {{(s: string): void}} */ +function h(s) { +>h : Symbol(h, Decl(a.js, 5, 1)) +>s : Symbol(s, Decl(a.js, 7, 11)) +} f() // should error >f : Symbol(f, Decl(a.js, 1, 5)) @@ -17,3 +22,6 @@ f() // should error g() // should error >g : Symbol(g, Decl(a.js, 2, 2)) +h() +>h : Symbol(h, Decl(a.js, 5, 1)) + diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.types b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types index dea1861b83384..6c2a586178bb9 100644 --- a/tests/baselines/reference/jsdocTypeTagRequiredParameters.types +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types @@ -11,6 +11,11 @@ function g(s) { >g : (s: string) => void >s : string } +/** @type {{(s: string): void}} */ +function h(s) { +>h : (s: any) => void +>s : any +} f() // should error >f() : void @@ -20,3 +25,7 @@ g() // should error >g() : void >g : (s: string) => void +h() +>h() : void +>h : (s: any) => void + diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts b/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts index 0710eff28147f..6991b3e83867f 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeTagRequiredParameters.ts @@ -11,6 +11,10 @@ const f = (value) => { /** @type {(s: string) => void} */ function g(s) { } +/** @type {{(s: string): void}} */ +function h(s) { +} f() // should error g() // should error +h() From 1f6106be008adfd345f48a26500bd99a2be3ad44 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 13:21:07 -0700 Subject: [PATCH 5/7] Contextually type parameters by parent sig's JSDoc Instead of a syntactic check in getJSDocTag --- src/compiler/checker.ts | 90 ++++++++++--------- src/compiler/utilities.ts | 9 -- .../jsdocTypeTagRequiredParameters.errors.txt | 5 +- .../jsdocTypeTagRequiredParameters.types | 6 +- 4 files changed, 54 insertions(+), 56 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5c39399e5364a..4347aa9e8ba3f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9130,7 +9130,8 @@ namespace ts { } function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { - return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func); + return (isInJavaScriptFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && + isContextSensitiveFunctionLikeDeclaration(func); } function getTypeWithoutSignatures(type: Type): Type { @@ -14198,49 +14199,49 @@ namespace ts { // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { const func = parameter.parent; - if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife && iife.arguments) { - const indexOfParameter = func.parameters.indexOf(parameter); - if (parameter.dotDotDotToken) { - const restTypes: Type[] = []; - for (let i = indexOfParameter; i < iife.arguments.length; i++) { - restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i]))); - } - return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined; - } - const links = getNodeLinks(iife); - const cached = links.resolvedSignature; - links.resolvedSignature = anySignature; - const type = indexOfParameter < iife.arguments.length ? - getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) : - parameter.initializer ? undefined : undefinedWideningType; - links.resolvedSignature = cached; - return type; + if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + return undefined; + } + const iife = getImmediatelyInvokedFunctionExpression(func); + if (iife && iife.arguments) { + const indexOfParameter = func.parameters.indexOf(parameter); + if (parameter.dotDotDotToken) { + const restTypes: Type[] = []; + for (let i = indexOfParameter; i < iife.arguments.length; i++) { + restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i]))); + } + return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined; + } + const links = getNodeLinks(iife); + const cached = links.resolvedSignature; + links.resolvedSignature = anySignature; + const type = indexOfParameter < iife.arguments.length ? + getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) : + parameter.initializer ? undefined : undefinedWideningType; + links.resolvedSignature = cached; + return type; + } + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const funcHasRestParameters = hasRestParameter(func); + const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); + let indexOfParameter = func.parameters.indexOf(parameter); + if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) { + Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`. + indexOfParameter -= 1; } - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - const funcHasRestParameters = hasRestParameter(func); - const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); - let indexOfParameter = func.parameters.indexOf(parameter); - if (getThisParameter(func) !== undefined && !contextualSignature.thisParameter) { - Debug.assert(indexOfParameter !== 0); // Otherwise we should not have called `getContextuallyTypedParameterType`. - indexOfParameter -= 1; - } - if (indexOfParameter < len) { - return getTypeAtPosition(contextualSignature, indexOfParameter); - } + if (indexOfParameter < len) { + return getTypeAtPosition(contextualSignature, indexOfParameter); + } - // If last parameter is contextually rest parameter get its type - if (funcHasRestParameters && - indexOfParameter === (func.parameters.length - 1) && - isRestParameterIndex(contextualSignature, func.parameters.length - 1)) { - return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters)); - } + // If last parameter is contextually rest parameter get its type + if (funcHasRestParameters && + indexOfParameter === (func.parameters.length - 1) && + isRestParameterIndex(contextualSignature, func.parameters.length - 1)) { + return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters)); } } - return undefined; } // In a variable, parameter or property declaration with a type annotation, @@ -14803,7 +14804,16 @@ namespace ts { // union type of return types from these signatures function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const type = getContextualTypeForFunctionLikeDeclaration(node); + let type: Type; + if (isInJavaScriptFile(node)) { + const jsdoc = getJSDocType(node); + if (jsdoc) { + type = getTypeFromTypeNode(jsdoc); + } + } + if (!type) { + type = getContextualTypeForFunctionLikeDeclaration(node); + } if (!type) { return undefined; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3e39cf392586f..235b10e4cf19d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4510,15 +4510,6 @@ namespace ts { let tag: JSDocTypeTag | JSDocParameterTag | undefined = getFirstJSDocTag(node, isJSDocTypeTag); if (!tag && isParameter(node)) { tag = find(getJSDocParameterTags(node), tag => !!tag.typeExpression); - if (!tag && isFunctionLike(node.parent)) { - const typeTag = getJSDocTypeTag(node.parent); - if (typeTag && typeTag.typeExpression && isFunctionLike(typeTag.typeExpression.type)) { - const i = node.parent.parameters.indexOf(node); - if (i > -1) { - return typeTag.typeExpression.type.parameters[i].type; - } - } - } } return tag && tag.typeExpression && tag.typeExpression.type; diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt index 4538fa9e58f60..4afde4e5037db 100644 --- a/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.errors.txt @@ -1,10 +1,9 @@ -tests/cases/conformance/jsdoc/a.js(8,12): error TS7006: Parameter 's' implicitly has an 'any' type. tests/cases/conformance/jsdoc/a.js(11,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/jsdoc/a.js(12,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/jsdoc/a.js(13,1): error TS2554: Expected 1 arguments, but got 0. -==== tests/cases/conformance/jsdoc/a.js (4 errors) ==== +==== tests/cases/conformance/jsdoc/a.js (3 errors) ==== /** @type {function(string): void} */ const f = (value) => { }; @@ -13,8 +12,6 @@ tests/cases/conformance/jsdoc/a.js(13,1): error TS2554: Expected 1 arguments, bu } /** @type {{(s: string): void}} */ function h(s) { - ~ -!!! error TS7006: Parameter 's' implicitly has an 'any' type. } f() // should error diff --git a/tests/baselines/reference/jsdocTypeTagRequiredParameters.types b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types index 6c2a586178bb9..daca778b07185 100644 --- a/tests/baselines/reference/jsdocTypeTagRequiredParameters.types +++ b/tests/baselines/reference/jsdocTypeTagRequiredParameters.types @@ -13,8 +13,8 @@ function g(s) { } /** @type {{(s: string): void}} */ function h(s) { ->h : (s: any) => void ->s : any +>h : (s: string) => void +>s : string } f() // should error @@ -27,5 +27,5 @@ g() // should error h() >h() : void ->h : (s: any) => void +>h : (s: string) => void From e09bd63e932e2de730f662d79643ad279677ccb1 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 13:29:45 -0700 Subject: [PATCH 6/7] Remove redundant check:isUntypedSignatureInJSFile --- src/compiler/checker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4347aa9e8ba3f..0573a01d03a63 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6700,7 +6700,6 @@ namespace ts { const iife = getImmediatelyInvokedFunctionExpression(declaration); const isJSConstructSignature = isJSDocConstructSignature(declaration); const isUntypedSignatureInJSFile = !iife && - !isJSConstructSignature && isInJavaScriptFile(declaration) && declaration.kind !== SyntaxKind.FunctionType && declaration.kind !== SyntaxKind.ConstructorType && From deba8e282459954e2dad7ceba35ce1b604e3c44c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 19 Mar 2018 15:15:05 -0700 Subject: [PATCH 7/7] Positive check for value signatures Instead of excluding type signatures piecemeal. --- src/compiler/checker.ts | 4 +--- src/compiler/utilities.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0573a01d03a63..464c31134d0f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6701,9 +6701,7 @@ namespace ts { const isJSConstructSignature = isJSDocConstructSignature(declaration); const isUntypedSignatureInJSFile = !iife && isInJavaScriptFile(declaration) && - declaration.kind !== SyntaxKind.FunctionType && - declaration.kind !== SyntaxKind.ConstructorType && - declaration.kind !== SyntaxKind.JSDocFunctionType && + isValueSignatureDeclaration(declaration) && !hasJSDocParameterTags(declaration) && !getJSDocType(declaration); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 235b10e4cf19d..a5ba7c30cd2d3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1958,6 +1958,18 @@ namespace ts { return false; } + export type ValueSignatureDeclaration = + | FunctionDeclaration + | MethodDeclaration + | ConstructorDeclaration + | AccessorDeclaration + | FunctionExpression + | ArrowFunction; + + export function isValueSignatureDeclaration(node: Node): node is ValueSignatureDeclaration { + return isFunctionExpression(node) || isArrowFunction(node) || isMethodOrAccessor(node) || isFunctionDeclaration(node) || isConstructorDeclaration(node); + } + function walkUp(node: Node, kind: SyntaxKind) { while (node && node.kind === kind) { node = node.parent;