diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6bd0c0409d435..e6a794e866ac6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15809,30 +15809,27 @@ namespace ts { } function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { + const isInJS = isInJSFile(node); if (isFunctionLike(container) && (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. - // If this is a function in a JS file, it might be a class method. - // Check if it's the RHS of a x.prototype.y = function [name]() { .... } - if (container.kind === SyntaxKind.FunctionExpression && - container.parent.kind === SyntaxKind.BinaryExpression && - getAssignmentDeclarationKind(container.parent as BinaryExpression) === AssignmentDeclarationKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container') - const className = (((container.parent as BinaryExpression) // x.prototype.y = f - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x + const className = getClassNameFromPrototypeMethod(container); + if (isInJS && className) { const classSymbol = checkExpression(className).symbol; if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - return getFlowTypeOfReference(node, getInferredClassType(classSymbol)); + const classType = getJavascriptClassType(classSymbol); + if (classType) { + return getFlowTypeOfReference(node, classType); + } } } // Check if it's a constructor definition, can be either a variable decl or function decl // i.e. // * /** @constructor */ function [name]() { ... } // * /** @constructor */ var x = function() { ... } - else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && + else if (isInJS && + (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && getJSDocClassTag(container)) { const classType = getJavascriptClassType(container.symbol); if (classType) { @@ -15852,7 +15849,7 @@ namespace ts { return getFlowTypeOfReference(node, type); } - if (isInJSFile(node)) { + if (isInJS) { const type = getTypeForThisExpressionFromJSDoc(container); if (type && type !== errorType) { return getFlowTypeOfReference(node, type); @@ -15860,6 +15857,34 @@ namespace ts { } } + function getClassNameFromPrototypeMethod(container: Node) { + // Check if it's the RHS of a x.prototype.y = function [name]() { .... } + if (container.kind === SyntaxKind.FunctionExpression && + isBinaryExpression(container.parent) && + getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = container' + return ((container.parent // x.prototype.y = container + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + } + // x.prototype = { method() { } } + else if (container.kind === SyntaxKind.MethodDeclaration && + container.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.left as PropertyAccessExpression).expression; + } + // x.prototype = { method: function() { } } + else if (container.kind === SyntaxKind.FunctionExpression && + container.parent.kind === SyntaxKind.PropertyAssignment && + container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + } + } + function getTypeForThisExpressionFromJSDoc(node: Node) { const jsdocType = getJSDocType(node); if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { diff --git a/tests/baselines/reference/jsdocTemplateTag5.errors.txt b/tests/baselines/reference/jsdocTemplateTag5.errors.txt deleted file mode 100644 index e24cd0926b6d8..0000000000000 --- a/tests/baselines/reference/jsdocTemplateTag5.errors.txt +++ /dev/null @@ -1,77 +0,0 @@ -tests/cases/conformance/jsdoc/a.js(18,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'. -tests/cases/conformance/jsdoc/a.js(39,21): error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'. -tests/cases/conformance/jsdoc/a.js(61,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'. - - -==== tests/cases/conformance/jsdoc/a.js (3 errors) ==== - /** - * Should work for function declarations - * @constructor - * @template {string} K - * @template V - */ - function Multimap() { - /** @type {Object} TODO: Remove the prototype from the fresh object */ - this._map = {}; - }; - - Multimap.prototype = { - /** - * @param {K} key the key ok - * @returns {V} the value ok - */ - get(key) { - return this._map[key + '']; - ~~~~ -!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'. - } - } - - /** - * Should work for initialisers too - * @constructor - * @template {string} K - * @template V - */ - var Multimap2 = function() { - /** @type {Object} TODO: Remove the prototype from the fresh object */ - this._map = {}; - }; - - Multimap2.prototype = { - /** - * @param {K} key the key ok - * @returns {V} the value ok - */ - get: function(key) { - return this._map[key + '']; - ~~~~ -!!! error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'. - } - } - - var Ns = {}; - /** - * Should work for expando-namespaced initialisers too - * @constructor - * @template {string} K - * @template V - */ - Ns.Multimap3 = function() { - /** @type {Object} TODO: Remove the prototype from the fresh object */ - this._map = {}; - }; - - Ns.Multimap3.prototype = { - /** - * @param {K} key the key ok - * @returns {V} the value ok - */ - get(key) { - return this._map[key + '']; - ~~~~ -!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'. - } - } - - \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTemplateTag5.symbols b/tests/baselines/reference/jsdocTemplateTag5.symbols index 249e92ab257f6..aa1b023f339cc 100644 --- a/tests/baselines/reference/jsdocTemplateTag5.symbols +++ b/tests/baselines/reference/jsdocTemplateTag5.symbols @@ -29,7 +29,8 @@ Multimap.prototype = { >key : Symbol(key, Decl(a.js, 16, 8)) return this._map[key + '']; ->this : Symbol(__object, Decl(a.js, 11, 20)) +>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21)) +>_map : Symbol(Multimap._map, Decl(a.js, 6, 21)) >key : Symbol(key, Decl(a.js, 16, 8)) } } @@ -64,7 +65,8 @@ Multimap2.prototype = { >key : Symbol(key, Decl(a.js, 37, 18)) return this._map[key + '']; ->this : Symbol(__object, Decl(a.js, 32, 21)) +>this._map : Symbol(Multimap2._map, Decl(a.js, 27, 28)) +>_map : Symbol(Multimap2._map, Decl(a.js, 27, 28)) >key : Symbol(key, Decl(a.js, 37, 18)) } } @@ -106,7 +108,8 @@ Ns.Multimap3.prototype = { >key : Symbol(key, Decl(a.js, 59, 8)) return this._map[key + '']; ->this : Symbol(__object, Decl(a.js, 54, 24)) +>this._map : Symbol(Multimap3._map, Decl(a.js, 49, 27)) +>_map : Symbol(Multimap3._map, Decl(a.js, 49, 27)) >key : Symbol(key, Decl(a.js, 59, 8)) } } diff --git a/tests/baselines/reference/jsdocTemplateTag5.types b/tests/baselines/reference/jsdocTemplateTag5.types index c5b8752d2b62f..1195b372e18c3 100644 --- a/tests/baselines/reference/jsdocTemplateTag5.types +++ b/tests/baselines/reference/jsdocTemplateTag5.types @@ -34,10 +34,10 @@ Multimap.prototype = { >key : K return this._map[key + '']; ->this._map[key + ''] : any ->this._map : any ->this : { get(key: K): V; } ->_map : any +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap & { get(key: K): V; } +>_map : { [x: string]: V; } >key + '' : string >key : K >'' : "" @@ -81,10 +81,10 @@ Multimap2.prototype = { >key : K return this._map[key + '']; ->this._map[key + ''] : any ->this._map : any ->this : { get: (key: K) => V; } ->_map : any +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap2 & { get: (key: K) => V; } +>_map : { [x: string]: V; } >key + '' : string >key : K >'' : "" @@ -136,10 +136,10 @@ Ns.Multimap3.prototype = { >key : K return this._map[key + '']; ->this._map[key + ''] : any ->this._map : any ->this : { get(key: K): V; } ->_map : any +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap3 & { get(key: K): V; } +>_map : { [x: string]: V; } >key + '' : string >key : K >'' : "" diff --git a/tests/baselines/reference/typeFromPrototypeAssignment.errors.txt b/tests/baselines/reference/typeFromPrototypeAssignment.errors.txt new file mode 100644 index 0000000000000..edd3435c445c3 --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment.errors.txt @@ -0,0 +1,45 @@ +tests/cases/conformance/salsa/a.js(27,20): error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'. + + +==== tests/cases/conformance/salsa/a.js (1 errors) ==== + // all references to _map, set, get, addon should be ok + + /** @constructor */ + var Multimap = function() { + this._map = {}; + this._map + this.set + this.get + this.addon + }; + + Multimap.prototype = { + set: function() { + this._map + this.set + this.get + this.addon + }, + get() { + this._map + this.set + this.get + this.addon + } + } + + Multimap.prototype.addon = function () { + ~~~~~ +!!! error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'. + this._map + this.set + this.get + this.addon + } + + var mm = new Multimap(); + mm._map + mm.set + mm.get + mm.addon + \ No newline at end of file diff --git a/tests/baselines/reference/typeFromPrototypeAssignment.symbols b/tests/baselines/reference/typeFromPrototypeAssignment.symbols new file mode 100644 index 0000000000000..d51382a639fca --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment.symbols @@ -0,0 +1,122 @@ +=== tests/cases/conformance/salsa/a.js === +// all references to _map, set, get, addon should be ok + +/** @constructor */ +var Multimap = function() { +>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2)) + + this._map = {}; +>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + + this.set +>this.set : Symbol(set, Decl(a.js, 11, 22)) +>set : Symbol(set, Decl(a.js, 11, 22)) + + this.get +>this.get : Symbol(get, Decl(a.js, 17, 6)) +>get : Symbol(get, Decl(a.js, 17, 6)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) + +}; + +Multimap.prototype = { +>Multimap.prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2)) +>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2)) +>prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2)) + + set: function() { +>set : Symbol(set, Decl(a.js, 11, 22)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + + this.set +>this.set : Symbol(set, Decl(a.js, 11, 22)) +>set : Symbol(set, Decl(a.js, 11, 22)) + + this.get +>this.get : Symbol(get, Decl(a.js, 17, 6)) +>get : Symbol(get, Decl(a.js, 17, 6)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) + + }, + get() { +>get : Symbol(get, Decl(a.js, 17, 6)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + + this.set +>this.set : Symbol(set, Decl(a.js, 11, 22)) +>set : Symbol(set, Decl(a.js, 11, 22)) + + this.get +>this.get : Symbol(get, Decl(a.js, 17, 6)) +>get : Symbol(get, Decl(a.js, 17, 6)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) + } +} + +Multimap.prototype.addon = function () { +>Multimap.prototype : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2)) +>prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + + this.set +>this.set : Symbol(set, Decl(a.js, 11, 22)) +>set : Symbol(set, Decl(a.js, 11, 22)) + + this.get +>this.get : Symbol(get, Decl(a.js, 17, 6)) +>get : Symbol(get, Decl(a.js, 17, 6)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +} + +var mm = new Multimap(); +>mm : Symbol(mm, Decl(a.js, 33, 3)) +>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2)) + +mm._map +>mm._map : Symbol(Multimap._map, Decl(a.js, 3, 27)) +>mm : Symbol(mm, Decl(a.js, 33, 3)) +>_map : Symbol(Multimap._map, Decl(a.js, 3, 27)) + +mm.set +>mm.set : Symbol(set, Decl(a.js, 11, 22)) +>mm : Symbol(mm, Decl(a.js, 33, 3)) +>set : Symbol(set, Decl(a.js, 11, 22)) + +mm.get +>mm.get : Symbol(get, Decl(a.js, 17, 6)) +>mm : Symbol(mm, Decl(a.js, 33, 3)) +>get : Symbol(get, Decl(a.js, 17, 6)) + +mm.addon +>mm.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) +>mm : Symbol(mm, Decl(a.js, 33, 3)) +>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1)) + diff --git a/tests/baselines/reference/typeFromPrototypeAssignment.types b/tests/baselines/reference/typeFromPrototypeAssignment.types new file mode 100644 index 0000000000000..87e4be5e0b96f --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment.types @@ -0,0 +1,149 @@ +=== tests/cases/conformance/salsa/a.js === +// all references to _map, set, get, addon should be ok + +/** @constructor */ +var Multimap = function() { +>Multimap : typeof Multimap +>function() { this._map = {}; this._map this.set this.get this.addon} : typeof Multimap + + this._map = {}; +>this._map = {} : {} +>this._map : {} +>this : Multimap & { set: () => void; get(): void; } +>_map : {} +>{} : {} + + this._map +>this._map : {} +>this : Multimap & { set: () => void; get(): void; } +>_map : {} + + this.set +>this.set : () => void +>this : Multimap & { set: () => void; get(): void; } +>set : () => void + + this.get +>this.get : () => void +>this : Multimap & { set: () => void; get(): void; } +>get : () => void + + this.addon +>this.addon : () => void +>this : Multimap & { set: () => void; get(): void; } +>addon : () => void + +}; + +Multimap.prototype = { +>Multimap.prototype = { set: function() { this._map this.set this.get this.addon }, get() { this._map this.set this.get this.addon }} : { set: () => void; get(): void; } +>Multimap.prototype : { set: () => void; get(): void; } +>Multimap : typeof Multimap +>prototype : { set: () => void; get(): void; } +>{ set: function() { this._map this.set this.get this.addon }, get() { this._map this.set this.get this.addon }} : { set: () => void; get(): void; } + + set: function() { +>set : () => void +>function() { this._map this.set this.get this.addon } : () => void + + this._map +>this._map : {} +>this : Multimap & { set: () => void; get(): void; } +>_map : {} + + this.set +>this.set : () => void +>this : Multimap & { set: () => void; get(): void; } +>set : () => void + + this.get +>this.get : () => void +>this : Multimap & { set: () => void; get(): void; } +>get : () => void + + this.addon +>this.addon : () => void +>this : Multimap & { set: () => void; get(): void; } +>addon : () => void + + }, + get() { +>get : () => void + + this._map +>this._map : {} +>this : Multimap & { set: () => void; get(): void; } +>_map : {} + + this.set +>this.set : () => void +>this : Multimap & { set: () => void; get(): void; } +>set : () => void + + this.get +>this.get : () => void +>this : Multimap & { set: () => void; get(): void; } +>get : () => void + + this.addon +>this.addon : () => void +>this : Multimap & { set: () => void; get(): void; } +>addon : () => void + } +} + +Multimap.prototype.addon = function () { +>Multimap.prototype.addon = function () { this._map this.set this.get this.addon} : () => void +>Multimap.prototype.addon : any +>Multimap.prototype : { set: () => void; get(): void; } +>Multimap : typeof Multimap +>prototype : { set: () => void; get(): void; } +>addon : any +>function () { this._map this.set this.get this.addon} : () => void + + this._map +>this._map : {} +>this : Multimap & { set: () => void; get(): void; } +>_map : {} + + this.set +>this.set : () => void +>this : Multimap & { set: () => void; get(): void; } +>set : () => void + + this.get +>this.get : () => void +>this : Multimap & { set: () => void; get(): void; } +>get : () => void + + this.addon +>this.addon : () => void +>this : Multimap & { set: () => void; get(): void; } +>addon : () => void +} + +var mm = new Multimap(); +>mm : Multimap & { set: () => void; get(): void; } +>new Multimap() : Multimap & { set: () => void; get(): void; } +>Multimap : typeof Multimap + +mm._map +>mm._map : {} +>mm : Multimap & { set: () => void; get(): void; } +>_map : {} + +mm.set +>mm.set : () => void +>mm : Multimap & { set: () => void; get(): void; } +>set : () => void + +mm.get +>mm.get : () => void +>mm : Multimap & { set: () => void; get(): void; } +>get : () => void + +mm.addon +>mm.addon : () => void +>mm : Multimap & { set: () => void; get(): void; } +>addon : () => void + diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment.ts new file mode 100644 index 0000000000000..373ccab0394bf --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment.ts @@ -0,0 +1,44 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: a.js +// @strict: true + +// all references to _map, set, get, addon should be ok + +/** @constructor */ +var Multimap = function() { + this._map = {}; + this._map + this.set + this.get + this.addon +}; + +Multimap.prototype = { + set: function() { + this._map + this.set + this.get + this.addon + }, + get() { + this._map + this.set + this.get + this.addon + } +} + +Multimap.prototype.addon = function () { + this._map + this.set + this.get + this.addon +} + +var mm = new Multimap(); +mm._map +mm.set +mm.get +mm.addon