From 116a8a8cffa773643fc232c082113adb5a1a186f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 20 Feb 2018 12:23:00 -0800 Subject: [PATCH] Support nested prototype declarations And add a test for them --- src/compiler/binder.ts | 35 +++++++++------- .../typeFromPropertyAssignment12.symbols | 34 +++++++++++++++ .../typeFromPropertyAssignment12.types | 42 +++++++++++++++++++ .../salsa/typeFromPropertyAssignment12.ts | 13 ++++++ 4 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 tests/baselines/reference/typeFromPropertyAssignment12.symbols create mode 100644 tests/baselines/reference/typeFromPropertyAssignment12.types create mode 100644 tests/cases/conformance/salsa/typeFromPropertyAssignment12.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index fb58352d2ad..54d4c366739 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2040,7 +2040,7 @@ namespace ts { bindModuleExportsAssignment(node as BinaryExpression); break; case SpecialPropertyAssignmentKind.PrototypeProperty: - bindPrototypePropertyAssignment(node as BinaryExpression); + bindPrototypePropertyAssignment((node as BinaryExpression).left as PropertyAccessEntityNameExpression, node); break; case SpecialPropertyAssignmentKind.ThisProperty: bindThisPropertyAssignment(node as BinaryExpression); @@ -2347,31 +2347,37 @@ namespace ts { } function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) { - Debug.assert(isInJavaScriptFile(node)); if (node.expression.kind === SyntaxKind.ThisKeyword) { bindThisPropertyAssignment(node); } - else if ((node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression) && - node.parent.parent.kind === SyntaxKind.SourceFile) { + else if (isEntityNameExpression(node) && + isPropertyAccessExpression(node.expression) && + node.expression.name.escapedText === "prototype" && + node.parent.parent.kind === SyntaxKind.SourceFile) { + bindPrototypePropertyAssignment(node as PropertyAccessEntityNameExpression, node.parent); + } + else if (isEntityNameExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { bindStaticPropertyAssignment(node as PropertyAccessEntityNameExpression); } } - function bindPrototypePropertyAssignment(node: BinaryExpression) { - // We saw a node of the form 'x.prototype.y = z'. Declare a 'member' y on x if x is a function or class, or not declared. - + /** + * For 'x.prototype.y = z', declare a 'member' y on x if x is a function or class, or not declared. + * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. + */ + function bindPrototypePropertyAssignment(lhs: PropertyAccessEntityNameExpression, parent: Node) { // Look up the function in the local scope, since prototype assignments should // follow the function declaration - const leftSideOfAssignment = node.left as PropertyAccessEntityNameExpression; - const classPrototype = leftSideOfAssignment.expression as PropertyAccessEntityNameExpression; + // TODO: This cast is now insufficient for original case+nested case + const classPrototype = lhs.expression as PropertyAccessEntityNameExpression; const constructorFunction = classPrototype.expression as Identifier; // Fix up parent pointers since we're going to use these nodes before we bind into them - leftSideOfAssignment.parent = node; + lhs.parent = parent; constructorFunction.parent = classPrototype; - classPrototype.parent = leftSideOfAssignment; + classPrototype.parent = lhs; - bindPropertyAssignment(constructorFunction, leftSideOfAssignment, /*isPrototypeProperty*/ true); + bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true); } @@ -2448,10 +2454,7 @@ namespace ts { } else { const s = getJSInitializerSymbol(forEachIdentifierInEntityName(e.expression, action)); - if (!s || !s.exports) { - // Not a valid nested special assignment - return undefined; - } + Debug.assert(!!s && !!s.exports); return action(e.name, s.exports.get(e.name.escapedText)); } } diff --git a/tests/baselines/reference/typeFromPropertyAssignment12.symbols b/tests/baselines/reference/typeFromPropertyAssignment12.symbols new file mode 100644 index 00000000000..139c9c1b4d6 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment12.symbols @@ -0,0 +1,34 @@ +=== tests/cases/conformance/salsa/module.js === +var Outer = function(element, config) {}; +>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41)) +>element : Symbol(element, Decl(module.js, 0, 21)) +>config : Symbol(config, Decl(module.js, 0, 29)) + +/** @constructor */ +Outer.Pos = function (line, ch) {}; +>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) +>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41)) +>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) +>line : Symbol(line, Decl(module.js, 2, 22)) +>ch : Symbol(ch, Decl(module.js, 2, 27)) + +/** @type {number} */ +Outer.Pos.prototype.line; +>Outer.Pos.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) +>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41)) +>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) + +var pos = new Outer.Pos(1, 'x'); +>pos : Symbol(pos, Decl(module.js, 5, 3)) +>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) +>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41)) +>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41)) + +pos.line; +>pos.line : Symbol((Anonymous function).line, Decl(module.js, 2, 35)) +>pos : Symbol(pos, Decl(module.js, 5, 3)) +>line : Symbol((Anonymous function).line, Decl(module.js, 2, 35)) + + diff --git a/tests/baselines/reference/typeFromPropertyAssignment12.types b/tests/baselines/reference/typeFromPropertyAssignment12.types new file mode 100644 index 00000000000..16bcd17e38f --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment12.types @@ -0,0 +1,42 @@ +=== tests/cases/conformance/salsa/module.js === +var Outer = function(element, config) {}; +>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; } +>function(element, config) {} : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; } +>element : any +>config : any + +/** @constructor */ +Outer.Pos = function (line, ch) {}; +>Outer.Pos = function (line, ch) {} : (line: any, ch: any) => void +>Outer.Pos : (line: any, ch: any) => void +>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; } +>Pos : (line: any, ch: any) => void +>function (line, ch) {} : (line: any, ch: any) => void +>line : any +>ch : any + +/** @type {number} */ +Outer.Pos.prototype.line; +>Outer.Pos.prototype.line : any +>Outer.Pos.prototype : any +>Outer.Pos : (line: any, ch: any) => void +>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; } +>Pos : (line: any, ch: any) => void +>prototype : any +>line : any + +var pos = new Outer.Pos(1, 'x'); +>pos : { line: number; } +>new Outer.Pos(1, 'x') : { line: number; } +>Outer.Pos : (line: any, ch: any) => void +>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; } +>Pos : (line: any, ch: any) => void +>1 : 1 +>'x' : "x" + +pos.line; +>pos.line : number +>pos : { line: number; } +>line : number + + diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment12.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment12.ts new file mode 100644 index 00000000000..f8833ced4e8 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment12.ts @@ -0,0 +1,13 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @target: es6 +// @Filename: module.js +var Outer = function(element, config) {}; +/** @constructor */ +Outer.Pos = function (line, ch) {}; +/** @type {number} */ +Outer.Pos.prototype.line; +var pos = new Outer.Pos(1, 'x'); +pos.line; +