From 4589db506d83b42ef44b5ffe81c578d6197562ba Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 31 Aug 2018 15:52:26 -0700 Subject: [PATCH 1/3] check for expando initializers in resolveEntityName when resolving type parameters in a prototype property assignment declaration. For example, this already works: ```js /** @template T */ function f(x) { this.x = x } /** @returns {T} */ f.protototype.m = function () { return this.x } ``` This now works too: ```js /** @template T */ var f = function (x) { this.x = x } /** @returns {T} */ f.prototype.m = function () { return this.x } ``` Fixes #26826 --- src/compiler/checker.ts | 10 +- .../reference/jsdocTemplateTag4.symbols | 113 ++++++++++++++ .../reference/jsdocTemplateTag4.types | 141 ++++++++++++++++++ .../conformance/jsdoc/jsdocTemplateTag4.ts | 63 ++++++++ 4 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateTag4.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTag4.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTag4.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da1af15dfa458..ebe8d1194b579 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2140,7 +2140,7 @@ namespace ts { } } - function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined { + function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Node | undefined { const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); if (typeAlias) { return; @@ -2150,7 +2150,13 @@ namespace ts { isBinaryExpression(host.expression) && getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { const symbol = getSymbolOfNode(host.expression.left); - return symbol && symbol.parent!.valueDeclaration; + if (symbol) { + const decl = symbol.parent!.valueDeclaration; + const initializer = isAssignmentDeclaration(decl) ? getAssignedJavascriptInitializer(decl) : + hasOnlyExpressionInitializer(decl) ? getDeclaredJavascriptInitializer(decl) : + undefined; + return initializer || decl; + } } const sig = getHostSignatureFromJSDocHost(host); if (sig) { diff --git a/tests/baselines/reference/jsdocTemplateTag4.symbols b/tests/baselines/reference/jsdocTemplateTag4.symbols new file mode 100644 index 0000000000000..02ce7221fc2be --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag4.symbols @@ -0,0 +1,113 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * Should work for function declarations + * @constructor + * @template {string} K + * @template V + */ +function Multimap() { +>Multimap : Symbol(Multimap, Decl(a.js, 0, 0)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21)) +>this : Symbol(Multimap, Decl(a.js, 0, 0)) +>_map : Symbol(Multimap._map, Decl(a.js, 6, 21)) + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap.prototype.get = function (key) { +>Multimap.prototype : Symbol(Multimap.get, Decl(a.js, 9, 2)) +>Multimap : Symbol(Multimap, Decl(a.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>get : Symbol(Multimap.get, Decl(a.js, 9, 2)) +>key : Symbol(key, Decl(a.js, 15, 35)) + + return this._map[key + '']; +>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21)) +>this : Symbol(Multimap, Decl(a.js, 0, 0)) +>_map : Symbol(Multimap._map, Decl(a.js, 6, 21)) +>key : Symbol(key, Decl(a.js, 15, 35)) +} + +/** + * Should work for initialisers too + * @constructor + * @template {string} K + * @template V + */ +var Multimap2 = function() { +>Multimap2 : Symbol(Multimap2, Decl(a.js, 25, 3)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap2._map, Decl(a.js, 25, 28)) +>this : Symbol(Multimap2, Decl(a.js, 25, 15)) +>_map : Symbol(Multimap2._map, Decl(a.js, 25, 28)) + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap2.prototype.get = function (key) { +>Multimap2.prototype : Symbol(Multimap2.get, Decl(a.js, 28, 2)) +>Multimap2 : Symbol(Multimap2, Decl(a.js, 25, 3)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>get : Symbol(Multimap2.get, Decl(a.js, 28, 2)) +>key : Symbol(key, Decl(a.js, 34, 36)) + + return this._map[key + '']; +>this._map : Symbol(Multimap2._map, Decl(a.js, 25, 28)) +>this : Symbol(Multimap2, Decl(a.js, 25, 15)) +>_map : Symbol(Multimap2._map, Decl(a.js, 25, 28)) +>key : Symbol(key, Decl(a.js, 34, 36)) +} + +var Ns = {}; +>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12)) + +/** + * Should work for expando-namespaced initialisers too + * @constructor + * @template {string} K + * @template V + */ +Ns.Multimap3 = function() { +>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12)) +>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12)) +>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap3._map, Decl(a.js, 45, 27)) +>this : Symbol(Multimap3, Decl(a.js, 45, 14)) +>_map : Symbol(Multimap3._map, Decl(a.js, 45, 27)) + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Ns.Multimap3.prototype.get = function (key) { +>Ns.Multimap3.prototype : Symbol(Ns.Multimap3.get, Decl(a.js, 48, 2)) +>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12)) +>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12)) +>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>get : Symbol(Ns.Multimap3.get, Decl(a.js, 48, 2)) +>key : Symbol(key, Decl(a.js, 54, 39)) + + return this._map[key + '']; +>this._map : Symbol(Multimap3._map, Decl(a.js, 45, 27)) +>this : Symbol(Multimap3, Decl(a.js, 45, 14)) +>_map : Symbol(Multimap3._map, Decl(a.js, 45, 27)) +>key : Symbol(key, Decl(a.js, 54, 39)) +} + diff --git a/tests/baselines/reference/jsdocTemplateTag4.types b/tests/baselines/reference/jsdocTemplateTag4.types new file mode 100644 index 0000000000000..6df18d4b98a35 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag4.types @@ -0,0 +1,141 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * Should work for function declarations + * @constructor + * @template {string} K + * @template V + */ +function Multimap() { +>Multimap : typeof Multimap + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap +>_map : { [x: string]: V; } +>{} : {} + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap.prototype.get = function (key) { +>Multimap.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V +>Multimap.prototype.get : any +>Multimap.prototype : any +>Multimap : typeof Multimap +>prototype : any +>get : any +>function (key) { return this._map[key + ''];} : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap +>_map : { [x: string]: V; } +>key + '' : string +>key : K +>'' : "" +} + +/** + * Should work for initialisers too + * @constructor + * @template {string} K + * @template V + */ +var Multimap2 = function() { +>Multimap2 : typeof Multimap2 +>function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap2 + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap2 +>_map : { [x: string]: V; } +>{} : {} + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap2.prototype.get = function (key) { +>Multimap2.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V +>Multimap2.prototype.get : any +>Multimap2.prototype : any +>Multimap2 : typeof Multimap2 +>prototype : any +>get : any +>function (key) { return this._map[key + ''];} : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap2 +>_map : { [x: string]: V; } +>key + '' : string +>key : K +>'' : "" +} + +var Ns = {}; +>Ns : typeof Ns +>{} : {} + +/** + * Should work for expando-namespaced initialisers too + * @constructor + * @template {string} K + * @template V + */ +Ns.Multimap3 = function() { +>Ns.Multimap3 = function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3 +>Ns.Multimap3 : typeof Multimap3 +>Ns : typeof Ns +>Multimap3 : typeof Multimap3 +>function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3 + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap3 +>_map : { [x: string]: V; } +>{} : {} + +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Ns.Multimap3.prototype.get = function (key) { +>Ns.Multimap3.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V +>Ns.Multimap3.prototype.get : any +>Ns.Multimap3.prototype : any +>Ns.Multimap3 : typeof Multimap3 +>Ns : typeof Ns +>Multimap3 : typeof Multimap3 +>prototype : any +>get : any +>function (key) { return this._map[key + ''];} : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : V +>this._map : { [x: string]: V; } +>this : Multimap3 +>_map : { [x: string]: V; } +>key + '' : string +>key : K +>'' : "" +} + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag4.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag4.ts new file mode 100644 index 0000000000000..adbf6bfbaca0c --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag4.ts @@ -0,0 +1,63 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @strict: true +// @Filename: a.js + +/** + * 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 = {}; +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap.prototype.get = function (key) { + return this._map[key + '']; +} + +/** + * 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 = {}; +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Multimap2.prototype.get = function (key) { + return this._map[key + '']; +} + +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 = {}; +}; + +/** + * @param {K} key the key ok + * @returns {V} the value ok + */ +Ns.Multimap3.prototype.get = function (key) { + return this._map[key + '']; +} From 9c6cf79d000b06b877841cbba8ddf8b9fee83384 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 31 Aug 2018 16:14:53 -0700 Subject: [PATCH 2/3] Lookup type parameters on prototype-assignment methods In the same way that they're looked up on prototype-property methods. That is, this previously worked: ```js /** @template T */ function f() { } /** @param {T} p */ f.prototype.m = function () { } ``` And this now works too: ```js /** @template T */ function f() { } f.prototype = { /** @param {T} p */ m() { } } ``` Note that the baselines still have errors; I'll file a followup bug for them. --- src/compiler/checker.ts | 24 ++- .../reference/jsdocTemplateTag5.errors.txt | 77 +++++++++ .../reference/jsdocTemplateTag5.symbols | 114 ++++++++++++++ .../reference/jsdocTemplateTag5.types | 148 ++++++++++++++++++ .../conformance/jsdoc/jsdocTemplateTag5.ts | 70 +++++++++ 5 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateTag5.errors.txt create mode 100644 tests/baselines/reference/jsdocTemplateTag5.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTag5.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ebe8d1194b579..e15141706931e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2149,13 +2149,19 @@ namespace ts { if (isExpressionStatement(host) && isBinaryExpression(host.expression) && getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration const symbol = getSymbolOfNode(host.expression.left); if (symbol) { - const decl = symbol.parent!.valueDeclaration; - const initializer = isAssignmentDeclaration(decl) ? getAssignedJavascriptInitializer(decl) : - hasOnlyExpressionInitializer(decl) ? getDeclaredJavascriptInitializer(decl) : - undefined; - return initializer || decl; + return getDeclarationOfJSPrototypeContainer(symbol); + } + } + if (isObjectLiteralMethod(host) && + isBinaryExpression(host.parent.parent) && + getSpecialPropertyAssignmentKind(host.parent.parent) === SpecialPropertyAssignmentKind.Prototype) { + // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } } const sig = getHostSignatureFromJSDocHost(host); @@ -2165,6 +2171,14 @@ namespace ts { } } + function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { + const decl = symbol.parent!.valueDeclaration; + const initializer = isAssignmentDeclaration(decl) ? getAssignedJavascriptInitializer(decl) : + hasOnlyExpressionInitializer(decl) ? getDeclaredJavascriptInitializer(decl) : + undefined; + return initializer || decl; + } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol | undefined { return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0); } diff --git a/tests/baselines/reference/jsdocTemplateTag5.errors.txt b/tests/baselines/reference/jsdocTemplateTag5.errors.txt new file mode 100644 index 0000000000000..d2392a1709b3c --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag5.errors.txt @@ -0,0 +1,77 @@ +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(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 new file mode 100644 index 0000000000000..e6a513d090127 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag5.symbols @@ -0,0 +1,114 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * Should work for function declarations + * @constructor + * @template {string} K + * @template V + */ +function Multimap() { +>Multimap : Symbol(Multimap, Decl(a.js, 0, 0), Decl(a.js, 9, 2)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21)) +>_map : Symbol(Multimap._map, Decl(a.js, 6, 21)) + +}; + +Multimap.prototype = { +>Multimap.prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2)) +>Multimap : Symbol(Multimap, Decl(a.js, 0, 0), Decl(a.js, 9, 2)) +>prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2)) + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : Symbol(get, Decl(a.js, 11, 22)) +>key : Symbol(key, Decl(a.js, 16, 8)) + + return this._map[key + '']; +>this : Symbol(__object, Decl(a.js, 11, 20)) +>key : Symbol(key, Decl(a.js, 16, 8)) + } +} + +/** + * Should work for initialisers too + * @constructor + * @template {string} K + * @template V + */ +var Multimap2 = function() { +>Multimap2 : Symbol(Multimap2, Decl(a.js, 27, 3), Decl(a.js, 30, 2)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap2._map, Decl(a.js, 27, 28)) +>_map : Symbol(Multimap2._map, Decl(a.js, 27, 28)) + +}; + +Multimap2.prototype = { +>Multimap2.prototype : Symbol(Multimap2.prototype, Decl(a.js, 30, 2)) +>Multimap2 : Symbol(Multimap2, Decl(a.js, 27, 3), Decl(a.js, 30, 2)) +>prototype : Symbol(Multimap2.prototype, Decl(a.js, 30, 2)) + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : Symbol(get, Decl(a.js, 32, 23)) +>key : Symbol(key, Decl(a.js, 37, 8)) + + return this._map[key + '']; +>this : Symbol(__object, Decl(a.js, 32, 21)) +>key : Symbol(key, Decl(a.js, 37, 8)) + } +} + +var Ns = {}; +>Ns : Symbol(Ns, Decl(a.js, 42, 3), Decl(a.js, 42, 12), Decl(a.js, 52, 2)) + +/** + * Should work for expando-namespaced initialisers too + * @constructor + * @template {string} K + * @template V + */ +Ns.Multimap3 = function() { +>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 42, 12), Decl(a.js, 54, 3)) +>Ns : Symbol(Ns, Decl(a.js, 42, 3), Decl(a.js, 42, 12), Decl(a.js, 52, 2)) +>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 42, 12), Decl(a.js, 54, 3)) + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map : Symbol(Multimap3._map, Decl(a.js, 49, 27)) +>_map : Symbol(Multimap3._map, Decl(a.js, 49, 27)) + +}; + +Ns.Multimap3.prototype = { +>Ns.Multimap3.prototype : Symbol(Ns.Multimap3.prototype, Decl(a.js, 52, 2)) +>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 42, 12), Decl(a.js, 54, 3)) +>Ns : Symbol(Ns, Decl(a.js, 42, 3), Decl(a.js, 42, 12), Decl(a.js, 52, 2)) +>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 42, 12), Decl(a.js, 54, 3)) +>prototype : Symbol(Ns.Multimap3.prototype, Decl(a.js, 52, 2)) + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : Symbol(get, Decl(a.js, 54, 26)) +>key : Symbol(key, Decl(a.js, 59, 8)) + + return this._map[key + '']; +>this : Symbol(__object, Decl(a.js, 54, 24)) +>key : Symbol(key, Decl(a.js, 59, 8)) + } +} + + diff --git a/tests/baselines/reference/jsdocTemplateTag5.types b/tests/baselines/reference/jsdocTemplateTag5.types new file mode 100644 index 0000000000000..36b3d17ec6c08 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag5.types @@ -0,0 +1,148 @@ +=== tests/cases/conformance/jsdoc/a.js === +/** + * Should work for function declarations + * @constructor + * @template {string} K + * @template V + */ +function Multimap() { +>Multimap : typeof Multimap + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap & { get(key: K): V; } +>_map : { [x: string]: V; } +>{} : {} + +}; + +Multimap.prototype = { +>Multimap.prototype = { /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } +>Multimap.prototype : { get(key: K): V; } +>Multimap : typeof Multimap +>prototype : { get(key: K): V; } +>{ /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : any +>this._map : any +>this : { get(key: K): V; } +>_map : any +>key + '' : string +>key : K +>'' : "" + } +} + +/** + * Should work for initialisers too + * @constructor + * @template {string} K + * @template V + */ +var Multimap2 = function() { +>Multimap2 : typeof Multimap2 +>function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap2 + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap2 & { get(key: K): V; } +>_map : { [x: string]: V; } +>{} : {} + +}; + +Multimap2.prototype = { +>Multimap2.prototype = { /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } +>Multimap2.prototype : { get(key: K): V; } +>Multimap2 : typeof Multimap2 +>prototype : { get(key: K): V; } +>{ /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : any +>this._map : any +>this : { get(key: K): V; } +>_map : any +>key + '' : string +>key : K +>'' : "" + } +} + +var Ns = {}; +>Ns : typeof Ns +>{} : {} + +/** + * Should work for expando-namespaced initialisers too + * @constructor + * @template {string} K + * @template V + */ +Ns.Multimap3 = function() { +>Ns.Multimap3 = function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3 +>Ns.Multimap3 : typeof Multimap3 +>Ns : typeof Ns +>Multimap3 : typeof Multimap3 +>function() { /** @type {Object} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3 + + /** @type {Object} TODO: Remove the prototype from the fresh object */ + this._map = {}; +>this._map = {} : {} +>this._map : { [x: string]: V; } +>this : Multimap3 & { get(key: K): V; } +>_map : { [x: string]: V; } +>{} : {} + +}; + +Ns.Multimap3.prototype = { +>Ns.Multimap3.prototype = { /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } +>Ns.Multimap3.prototype : { get(key: K): V; } +>Ns.Multimap3 : typeof Multimap3 +>Ns : typeof Ns +>Multimap3 : typeof Multimap3 +>prototype : { get(key: K): V; } +>{ /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } + + /** + * @param {K} key the key ok + * @returns {V} the value ok + */ + get(key) { +>get : (key: K) => V +>key : K + + return this._map[key + '']; +>this._map[key + ''] : any +>this._map : any +>this : { get(key: K): V; } +>_map : any +>key + '' : string +>key : K +>'' : "" + } +} + + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts new file mode 100644 index 0000000000000..7f25c0b9b2c83 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts @@ -0,0 +1,70 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @strict: true +// @Filename: a.js + +/** + * 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 + '']; + } +} + +/** + * 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(key) { + return this._map[key + '']; + } +} + +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 + '']; + } +} + From e68e3c2e6620a85454ffa1c0c3727b30a1bce17b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 4 Sep 2018 13:31:10 -0700 Subject: [PATCH 3/3] Look up types on property assignments too --- src/compiler/checker.ts | 2 +- .../reference/jsdocTemplateTag5.errors.txt | 6 +++--- .../baselines/reference/jsdocTemplateTag5.symbols | 6 +++--- tests/baselines/reference/jsdocTemplateTag5.types | 15 ++++++++------- .../cases/conformance/jsdoc/jsdocTemplateTag5.ts | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e15141706931e..57de68ab02b39 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2155,7 +2155,7 @@ namespace ts { return getDeclarationOfJSPrototypeContainer(symbol); } } - if (isObjectLiteralMethod(host) && + if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) && isBinaryExpression(host.parent.parent) && getSpecialPropertyAssignmentKind(host.parent.parent) === SpecialPropertyAssignmentKind.Prototype) { // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration diff --git a/tests/baselines/reference/jsdocTemplateTag5.errors.txt b/tests/baselines/reference/jsdocTemplateTag5.errors.txt index d2392a1709b3c..e24cd0926b6d8 100644 --- a/tests/baselines/reference/jsdocTemplateTag5.errors.txt +++ b/tests/baselines/reference/jsdocTemplateTag5.errors.txt @@ -1,5 +1,5 @@ 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(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; }'. @@ -43,10 +43,10 @@ tests/cases/conformance/jsdoc/a.js(61,21): error TS2339: Property '_map' does no * @param {K} key the key ok * @returns {V} the value ok */ - get(key) { + get: function(key) { return this._map[key + '']; ~~~~ -!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'. +!!! error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'. } } diff --git a/tests/baselines/reference/jsdocTemplateTag5.symbols b/tests/baselines/reference/jsdocTemplateTag5.symbols index e6a513d090127..249e92ab257f6 100644 --- a/tests/baselines/reference/jsdocTemplateTag5.symbols +++ b/tests/baselines/reference/jsdocTemplateTag5.symbols @@ -59,13 +59,13 @@ Multimap2.prototype = { * @param {K} key the key ok * @returns {V} the value ok */ - get(key) { + get: function(key) { >get : Symbol(get, Decl(a.js, 32, 23)) ->key : Symbol(key, Decl(a.js, 37, 8)) +>key : Symbol(key, Decl(a.js, 37, 18)) return this._map[key + '']; >this : Symbol(__object, Decl(a.js, 32, 21)) ->key : Symbol(key, Decl(a.js, 37, 8)) +>key : Symbol(key, Decl(a.js, 37, 18)) } } diff --git a/tests/baselines/reference/jsdocTemplateTag5.types b/tests/baselines/reference/jsdocTemplateTag5.types index 36b3d17ec6c08..c5b8752d2b62f 100644 --- a/tests/baselines/reference/jsdocTemplateTag5.types +++ b/tests/baselines/reference/jsdocTemplateTag5.types @@ -58,31 +58,32 @@ var Multimap2 = function() { this._map = {}; >this._map = {} : {} >this._map : { [x: string]: V; } ->this : Multimap2 & { get(key: K): V; } +>this : Multimap2 & { get: (key: K) => V; } >_map : { [x: string]: V; } >{} : {} }; Multimap2.prototype = { ->Multimap2.prototype = { /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } ->Multimap2.prototype : { get(key: K): V; } +>Multimap2.prototype = { /** * @param {K} key the key ok * @returns {V} the value ok */ get: function(key) { return this._map[key + '']; }} : { get: (key: K) => V; } +>Multimap2.prototype : { get: (key: K) => V; } >Multimap2 : typeof Multimap2 ->prototype : { get(key: K): V; } ->{ /** * @param {K} key the key ok * @returns {V} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: K): V; } +>prototype : { get: (key: K) => V; } +>{ /** * @param {K} key the key ok * @returns {V} the value ok */ get: function(key) { return this._map[key + '']; }} : { get: (key: K) => V; } /** * @param {K} key the key ok * @returns {V} the value ok */ - get(key) { + get: function(key) { >get : (key: K) => V +>function(key) { return this._map[key + '']; } : (key: K) => V >key : K return this._map[key + '']; >this._map[key + ''] : any >this._map : any ->this : { get(key: K): V; } +>this : { get: (key: K) => V; } >_map : any >key + '' : string >key : K diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts index 7f25c0b9b2c83..0bfb6b7ca373a 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag5.ts @@ -41,7 +41,7 @@ Multimap2.prototype = { * @param {K} key the key ok * @returns {V} the value ok */ - get(key) { + get: function(key) { return this._map[key + '']; } }