diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05c20ffb11a..1ab76688271 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12693,12 +12693,6 @@ namespace ts { if (typeNode) { return getTypeFromTypeNode(typeNode); } - if (isInJavaScriptFile(declaration)) { - const jsDocType = getTypeForDeclarationFromJSDocComment(declaration); - if (jsDocType) { - return jsDocType; - } - } if (declaration.kind === SyntaxKind.Parameter) { const type = getContextuallyTypedParameterType(declaration); if (type) { @@ -13306,6 +13300,7 @@ namespace ts { let patternWithComputedProperties = false; let hasComputedStringProperty = false; let hasComputedNumberProperty = false; + const isInJSFile = isInJavaScriptFile(node); let offset = 0; for (let i = 0; i < node.properties.length; i++) { @@ -13314,6 +13309,11 @@ namespace ts { if (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || isObjectLiteralMethod(memberDecl)) { + let jsdocType: Type; + if (isInJSFile) { + jsdocType = getTypeForDeclarationFromJSDocComment(memberDecl); + } + let type: Type; if (memberDecl.kind === SyntaxKind.PropertyAssignment) { type = checkPropertyAssignment(memberDecl, checkMode); @@ -13326,6 +13326,11 @@ namespace ts { type = checkExpressionForMutableLocation((memberDecl).name, checkMode); } + if (jsdocType) { + checkTypeAssignableTo(type, jsdocType, memberDecl); + type = jsdocType; + } + typeFlags |= type.flags; const prop = createSymbol(SymbolFlags.Property | member.flags, member.name); if (inDestructuringPattern) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c8827801e5e..77d1a0c6496 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2664,11 +2664,11 @@ namespace ts { * Gets the effective type annotation of a variable, parameter, or property. If the node was * parsed in a JavaScript file, gets the type annotation from JSDoc. */ - export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode { + export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode | undefined { if (node.type) { return node.type; } - if (node.flags & NodeFlags.JavaScriptFile) { + if (isInJavaScriptFile(node)) { return getJSDocType(node); } } @@ -2677,11 +2677,11 @@ namespace ts { * Gets the effective return type annotation of a signature. If the node was parsed in a * JavaScript file, gets the return type annotation from JSDoc. */ - export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode { + export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode | undefined { if (node.type) { return node.type; } - if (node.flags & NodeFlags.JavaScriptFile) { + if (isInJavaScriptFile(node)) { return getJSDocReturnType(node); } } diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.js b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.js new file mode 100644 index 00000000000..26f19815855 --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.js @@ -0,0 +1,53 @@ +//// [0.js] +// @ts-check +var lol = "hello Lol" +const obj = { + /** @type {string|undefined} */ + foo: undefined, + /** @type {string|undefined} */ + bar: "42", + /** @type {function(number): number} */ + method1(n1) { + return n1 + 42; + }, + /** @type {string} */ + lol, + /** @type {number} */ + ['b' + 'ar1']: 42, + /** @type {function(number): number} */ + arrowFunc: (num) => num + 42 +} +obj.foo = 'string' +obj.lol +obj.bar = undefined; +var k = obj.method1(0); +obj.bar1 = "42"; +obj.arrowFunc(0); + +//// [0.js] +// @ts-check +var lol = "hello Lol"; +var obj = (_a = { + /** @type {string|undefined} */ + foo: undefined, + /** @type {string|undefined} */ + bar: "42", + /** @type {function(number): number} */ + method1: function (n1) { + return n1 + 42; + }, + /** @type {string} */ + lol: lol + }, + /** @type {number} */ + _a['b' + 'ar1'] = 42, + /** @type {function(number): number} */ + _a.arrowFunc = function (num) { return num + 42; }, + _a); +obj.foo = 'string'; +obj.lol; +obj.bar = undefined; +var k = obj.method1(0); +obj.bar1 = "42"; +obj.arrowFunc(0); +var _a; diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.symbols b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.symbols new file mode 100644 index 00000000000..27f70798c71 --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.symbols @@ -0,0 +1,68 @@ +=== tests/cases/conformance/jsdoc/0.js === +// @ts-check +var lol = "hello Lol" +>lol : Symbol(lol, Decl(0.js, 1, 3)) + +const obj = { +>obj : Symbol(obj, Decl(0.js, 2, 5)) + + /** @type {string|undefined} */ + foo: undefined, +>foo : Symbol(foo, Decl(0.js, 2, 13)) +>undefined : Symbol(undefined) + + /** @type {string|undefined} */ + bar: "42", +>bar : Symbol(bar, Decl(0.js, 4, 17)) + + /** @type {function(number): number} */ + method1(n1) { +>method1 : Symbol(method1, Decl(0.js, 6, 12)) +>n1 : Symbol(n1, Decl(0.js, 8, 10)) + + return n1 + 42; +>n1 : Symbol(n1, Decl(0.js, 8, 10)) + + }, + /** @type {string} */ + lol, +>lol : Symbol(lol, Decl(0.js, 10, 4)) + + /** @type {number} */ + ['b' + 'ar1']: 42, + /** @type {function(number): number} */ + arrowFunc: (num) => num + 42 +>arrowFunc : Symbol(arrowFunc, Decl(0.js, 14, 20)) +>num : Symbol(num, Decl(0.js, 16, 14)) +>num : Symbol(num, Decl(0.js, 16, 14)) +} +obj.foo = 'string' +>obj.foo : Symbol(foo, Decl(0.js, 2, 13)) +>obj : Symbol(obj, Decl(0.js, 2, 5)) +>foo : Symbol(foo, Decl(0.js, 2, 13)) + +obj.lol +>obj.lol : Symbol(lol, Decl(0.js, 10, 4)) +>obj : Symbol(obj, Decl(0.js, 2, 5)) +>lol : Symbol(lol, Decl(0.js, 10, 4)) + +obj.bar = undefined; +>obj.bar : Symbol(bar, Decl(0.js, 4, 17)) +>obj : Symbol(obj, Decl(0.js, 2, 5)) +>bar : Symbol(bar, Decl(0.js, 4, 17)) +>undefined : Symbol(undefined) + +var k = obj.method1(0); +>k : Symbol(k, Decl(0.js, 21, 3)) +>obj.method1 : Symbol(method1, Decl(0.js, 6, 12)) +>obj : Symbol(obj, Decl(0.js, 2, 5)) +>method1 : Symbol(method1, Decl(0.js, 6, 12)) + +obj.bar1 = "42"; +>obj : Symbol(obj, Decl(0.js, 2, 5)) + +obj.arrowFunc(0); +>obj.arrowFunc : Symbol(arrowFunc, Decl(0.js, 14, 20)) +>obj : Symbol(obj, Decl(0.js, 2, 5)) +>arrowFunc : Symbol(arrowFunc, Decl(0.js, 14, 20)) + diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types new file mode 100644 index 00000000000..72c5c55228e --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty1.types @@ -0,0 +1,92 @@ +=== tests/cases/conformance/jsdoc/0.js === +// @ts-check +var lol = "hello Lol" +>lol : string +>"hello Lol" : "hello Lol" + +const obj = { +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>{ /** @type {string|undefined} */ foo: undefined, /** @type {string|undefined} */ bar: "42", /** @type {function(number): number} */ method1(n1) { return n1 + 42; }, /** @type {string} */ lol, /** @type {number} */ ['b' + 'ar1']: 42, /** @type {function(number): number} */ arrowFunc: (num) => num + 42} : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } + + /** @type {string|undefined} */ + foo: undefined, +>foo : string | undefined +>undefined : undefined + + /** @type {string|undefined} */ + bar: "42", +>bar : string | undefined +>"42" : "42" + + /** @type {function(number): number} */ + method1(n1) { +>method1 : (n1: any) => any +>n1 : any + + return n1 + 42; +>n1 + 42 : any +>n1 : any +>42 : 42 + + }, + /** @type {string} */ + lol, +>lol : string + + /** @type {number} */ + ['b' + 'ar1']: 42, +>'b' + 'ar1' : string +>'b' : "b" +>'ar1' : "ar1" +>42 : 42 + + /** @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 +>42 : 42 +} +obj.foo = 'string' +>obj.foo = 'string' : "string" +>obj.foo : string | undefined +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>foo : string | undefined +>'string' : "string" + +obj.lol +>obj.lol : string +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>lol : string + +obj.bar = undefined; +>obj.bar = undefined : undefined +>obj.bar : string | undefined +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>bar : string | undefined +>undefined : undefined + +var k = obj.method1(0); +>k : number +>obj.method1(0) : number +>obj.method1 : (arg0: number) => number +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>method1 : (arg0: number) => number +>0 : 0 + +obj.bar1 = "42"; +>obj.bar1 = "42" : "42" +>obj.bar1 : any +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>bar1 : any +>"42" : "42" + +obj.arrowFunc(0); +>obj.arrowFunc(0) : number +>obj.arrowFunc : (arg0: number) => number +>obj : { [x: string]: any; foo: string | undefined; bar: string | undefined; method1(arg0: number): number; lol: string; arrowFunc: (arg0: number) => number; } +>arrowFunc : (arg0: number) => number +>0 : 0 + diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt new file mode 100644 index 00000000000..33c83e61172 --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.errors.txt @@ -0,0 +1,54 @@ +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'. + 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'. + 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(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'. + + +==== tests/cases/conformance/jsdoc/0.js (7 errors) ==== + // @ts-check + var lol; + const obj = { + /** @type {string|undefined} */ + bar: 42, + ~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string | undefined'. + /** @type {function(number): number} */ + method1(n1) { + ~~~~~~~ +!!! error TS2322: Type '(n1: any) => 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 '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'. + /** @type {string} */ + lol + ~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + } + lol = "string" + /** @type {string} */ + var s = obj.method1(0); + ~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + + /** @type {string} */ + var s1 = obj.method2("0"); + ~~~ +!!! error TS2345: Argument of type '"0"' is not assignable to parameter of type 'number'. \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.js b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.js new file mode 100644 index 00000000000..ab574fddced --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypeTagOnObjectProperty2.js @@ -0,0 +1,49 @@ +//// [0.js] +// @ts-check +var lol; +const obj = { + /** @type {string|undefined} */ + bar: 42, + /** @type {function(number): number} */ + method1(n1) { + return "42"; + }, + /** @type {function(number): number} */ + method2: (n1) => "lol", + /** @type {function(number): number} */ + arrowFunc: (num="0") => num + 42, + /** @type {string} */ + lol +} +lol = "string" +/** @type {string} */ +var s = obj.method1(0); + +/** @type {string} */ +var s1 = obj.method2("0"); + +//// [0.js] +// @ts-check +var lol; +var obj = { + /** @type {string|undefined} */ + bar: 42, + /** @type {function(number): number} */ + method1: function (n1) { + return "42"; + }, + /** @type {function(number): number} */ + method2: function (n1) { return "lol"; }, + /** @type {function(number): number} */ + arrowFunc: function (num) { + if (num === void 0) { num = "0"; } + return num + 42; + }, + /** @type {string} */ + lol: lol +}; +lol = "string"; +/** @type {string} */ +var s = obj.method1(0); +/** @type {string} */ +var s1 = obj.method2("0"); diff --git a/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty1.ts b/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty1.ts new file mode 100644 index 00000000000..a98b0a70a2a --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty1.ts @@ -0,0 +1,29 @@ +// @allowJS: true +// @suppressOutputPathCheck: true +// @strictNullChecks: true + +// @filename: 0.js +// @ts-check +var lol = "hello Lol" +const obj = { + /** @type {string|undefined} */ + foo: undefined, + /** @type {string|undefined} */ + bar: "42", + /** @type {function(number): number} */ + method1(n1) { + return n1 + 42; + }, + /** @type {string} */ + lol, + /** @type {number} */ + ['b' + 'ar1']: 42, + /** @type {function(number): number} */ + arrowFunc: (num) => num + 42 +} +obj.foo = 'string' +obj.lol +obj.bar = undefined; +var k = obj.method1(0); +obj.bar1 = "42"; +obj.arrowFunc(0); \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty2.ts b/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty2.ts new file mode 100644 index 00000000000..9bab96dee9b --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocTypeTagOnObjectProperty2.ts @@ -0,0 +1,27 @@ +// @allowJS: true +// @suppressOutputPathCheck: true +// @strictNullChecks: true + +// @filename: 0.js +// @ts-check +var lol; +const obj = { + /** @type {string|undefined} */ + bar: 42, + /** @type {function(number): number} */ + method1(n1) { + return "42"; + }, + /** @type {function(number): number} */ + method2: (n1) => "lol", + /** @type {function(number): number} */ + arrowFunc: (num="0") => num + 42, + /** @type {string} */ + lol +} +lol = "string" +/** @type {string} */ +var s = obj.method1(0); + +/** @type {string} */ +var s1 = obj.method2("0"); \ No newline at end of file