diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 941aa0eadc1..4919a373d66 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17915,15 +17915,34 @@ namespace ts { function getAssignedClassType(symbol: Symbol) { const decl = symbol.valueDeclaration; - const assignmentSymbol = decl && decl.parent && isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left); + const assignmentSymbol = decl && decl.parent && + (isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || + isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); if (assignmentSymbol) { - const prototype = forEach(assignmentSymbol.declarations, d => getAssignedJavascriptPrototype(d.parent)); + const prototype = forEach(assignmentSymbol.declarations, getAssignedJavascriptPrototype); if (prototype) { return checkExpression(prototype); } } } + function getAssignedJavascriptPrototype(node: Node) { + if (!node.parent) { + return false; + } + let parent: Node = node.parent; + while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + parent = parent.parent; + } + return parent && isBinaryExpression(parent) && + isPropertyAccessExpression(parent.left) && + parent.left.name.escapedText === "prototype" && + parent.operatorToken.kind === SyntaxKind.EqualsToken && + isObjectLiteralExpression(parent.right) && + parent.right; + } + + function getInferredClassType(symbol: Symbol) { const links = getSymbolLinks(symbol); if (!links.inferredClassType) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 51c3b6ae22c..420ce4bdf09 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1488,18 +1488,10 @@ namespace ts { return symbol; } const declaration = symbol.valueDeclaration; - const e = getDeclaredJavascriptInitializer(declaration) || getAssignedJavascriptInitializer(declaration) || getAssignedJavascriptPrototype(declaration); + const e = getDeclaredJavascriptInitializer(declaration) || getAssignedJavascriptInitializer(declaration); return e ? e.symbol : symbol; } - export function getAssignedJavascriptPrototype(node: Node) { - return isPropertyAccessExpression(node) && - node.parent && isPropertyAccessExpression(node.parent) && node.parent.name.escapedText === "prototype" && - node.parent.parent && isBinaryExpression(node.parent.parent) && node.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken && - node.parent.parent.right.kind === SyntaxKind.ObjectLiteralExpression && - node.parent.parent.right; - } - export function getDeclaredJavascriptInitializer(node: Node) { if (node && isVariableDeclaration(node) && node.initializer) { return getJavascriptInitializer(node.initializer) || diff --git a/tests/baselines/reference/typeFromPropertyAssignment11.symbols b/tests/baselines/reference/typeFromPropertyAssignment11.symbols new file mode 100644 index 00000000000..93d79e2de1c --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment11.symbols @@ -0,0 +1,52 @@ +=== tests/cases/conformance/salsa/module.js === +var Inner = function() {} +>Inner : Symbol(Inner, Decl(module.js, 0, 3), Decl(module.js, 0, 25)) + +Inner.prototype = { +>Inner.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>Inner : Symbol(Inner, Decl(module.js, 0, 3), Decl(module.js, 0, 25)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) + + m() { }, +>m : Symbol(m, Decl(module.js, 1, 19)) + + i: 1 +>i : Symbol(i, Decl(module.js, 2, 12)) +} +// incremental assignments still work +Inner.prototype.j = 2 +>Inner.prototype : Symbol(Inner.j, Decl(module.js, 4, 1)) +>Inner : Symbol(Inner, Decl(module.js, 0, 3), Decl(module.js, 0, 25)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>j : Symbol(Inner.j, Decl(module.js, 4, 1)) + +/** @type {string} */ +Inner.prototype.k; +>Inner.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>Inner : Symbol(Inner, Decl(module.js, 0, 3), Decl(module.js, 0, 25)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) + +var inner = new Inner() +>inner : Symbol(inner, Decl(module.js, 9, 3)) +>Inner : Symbol(Inner, Decl(module.js, 0, 3), Decl(module.js, 0, 25)) + +inner.m() +>inner.m : Symbol(m, Decl(module.js, 1, 19)) +>inner : Symbol(inner, Decl(module.js, 9, 3)) +>m : Symbol(m, Decl(module.js, 1, 19)) + +inner.i +>inner.i : Symbol(i, Decl(module.js, 2, 12)) +>inner : Symbol(inner, Decl(module.js, 9, 3)) +>i : Symbol(i, Decl(module.js, 2, 12)) + +inner.j +>inner.j : Symbol(Inner.j, Decl(module.js, 4, 1)) +>inner : Symbol(inner, Decl(module.js, 9, 3)) +>j : Symbol(Inner.j, Decl(module.js, 4, 1)) + +inner.k +>inner.k : Symbol(Inner.k, Decl(module.js, 6, 21)) +>inner : Symbol(inner, Decl(module.js, 9, 3)) +>k : Symbol(Inner.k, Decl(module.js, 6, 21)) + diff --git a/tests/baselines/reference/typeFromPropertyAssignment11.types b/tests/baselines/reference/typeFromPropertyAssignment11.types new file mode 100644 index 00000000000..c0ad8c49ca0 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment11.types @@ -0,0 +1,63 @@ +=== tests/cases/conformance/salsa/module.js === +var Inner = function() {} +>Inner : () => void +>function() {} : () => void + +Inner.prototype = { +>Inner.prototype = { m() { }, i: 1} : { [x: string]: any; m(): void; i: number; } +>Inner.prototype : any +>Inner : () => void +>prototype : any +>{ m() { }, i: 1} : { [x: string]: any; m(): void; i: number; } + + m() { }, +>m : () => void + + i: 1 +>i : number +>1 : 1 +} +// incremental assignments still work +Inner.prototype.j = 2 +>Inner.prototype.j = 2 : 2 +>Inner.prototype.j : any +>Inner.prototype : any +>Inner : () => void +>prototype : any +>j : any +>2 : 2 + +/** @type {string} */ +Inner.prototype.k; +>Inner.prototype.k : any +>Inner.prototype : any +>Inner : () => void +>prototype : any +>k : any + +var inner = new Inner() +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>new Inner() : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>Inner : () => void + +inner.m() +>inner.m() : void +>inner.m : () => void +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>m : () => void + +inner.i +>inner.i : number +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>i : number + +inner.j +>inner.j : number +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>j : number + +inner.k +>inner.k : string +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>k : string + diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment11.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment11.ts new file mode 100644 index 00000000000..308da40f3c4 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment11.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @target: es6 +// @Filename: module.js +var Inner = function() {} +Inner.prototype = { + m() { }, + i: 1 +} +// incremental assignments still work +Inner.prototype.j = 2 +/** @type {string} */ +Inner.prototype.k; +var inner = new Inner() +inner.m() +inner.i +inner.j +inner.k