From 1d209d50e34fa402ee81e19061ea7ad585d0348e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 1 May 2017 10:41:44 -0700 Subject: [PATCH 1/5] Update version --- package.json | 2 +- src/compiler/core.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 170317743b3..4e13a1f4320 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "http://typescriptlang.org/", - "version": "2.3.2", + "version": "2.3.3", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 906be748e59..8d4ed6a214e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -3,7 +3,7 @@ namespace ts { /** The version of the TypeScript compiler release */ - export const version = "2.3.2"; + export const version = "2.3.3"; } /* @internal */ From 69623146bd4cef69fd018440c776bed1175c088e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 24 Apr 2017 14:46:58 -0700 Subject: [PATCH 2/5] ES2015 transforms accessors with captured `this` Previously, it did not, meaning that the emit for lexically captured `this` was incorrect. --- src/compiler/transformers/es2015.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 3ac5dc5d878..0dc6ed4a964 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3180,7 +3180,20 @@ namespace ts { const savedConvertedLoopState = convertedLoopState; convertedLoopState = undefined; const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); - const updated = visitEachChild(node, visitor, context); + let updated: AccessorDeclaration; + if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis) { + const parameters = visitParameterList(node.parameters, visitor, context); + const body = transformFunctionBody(node); + if (node.kind === SyntaxKind.GetAccessor) { + updated = updateGetAccessor(node, node.decorators, node.modifiers, node.name, parameters, node.type, body); + } + else { + updated = updateSetAccessor(node, node.decorators, node.modifiers, node.name, parameters, body); + } + } + else { + updated = visitEachChild(node, visitor, context); + } exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updated; From f3737dd97a75ae05f5f2e4b585922b8ae86b589f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 24 Apr 2017 14:48:25 -0700 Subject: [PATCH 3/5] Test ES2015 transform of accessors w/captured this --- .../emitThisInObjectLiteralGetter.js | 15 +++++++++++++++ .../emitThisInObjectLiteralGetter.symbols | 13 +++++++++++++ .../emitThisInObjectLiteralGetter.types | 19 +++++++++++++++++++ .../compiler/emitThisInObjectLiteralGetter.ts | 6 ++++++ 4 files changed, 53 insertions(+) create mode 100644 tests/baselines/reference/emitThisInObjectLiteralGetter.js create mode 100644 tests/baselines/reference/emitThisInObjectLiteralGetter.symbols create mode 100644 tests/baselines/reference/emitThisInObjectLiteralGetter.types create mode 100644 tests/cases/compiler/emitThisInObjectLiteralGetter.ts diff --git a/tests/baselines/reference/emitThisInObjectLiteralGetter.js b/tests/baselines/reference/emitThisInObjectLiteralGetter.js new file mode 100644 index 00000000000..8c64f9e6045 --- /dev/null +++ b/tests/baselines/reference/emitThisInObjectLiteralGetter.js @@ -0,0 +1,15 @@ +//// [emitThisInObjectLiteralGetter.ts] +const example = { + get foo() { + return item => this.bar(item); + } +}; + + +//// [emitThisInObjectLiteralGetter.js] +var example = { + get foo() { + var _this = this; + return function (item) { return _this.bar(item); }; + } +}; diff --git a/tests/baselines/reference/emitThisInObjectLiteralGetter.symbols b/tests/baselines/reference/emitThisInObjectLiteralGetter.symbols new file mode 100644 index 00000000000..0de4890e8d1 --- /dev/null +++ b/tests/baselines/reference/emitThisInObjectLiteralGetter.symbols @@ -0,0 +1,13 @@ +=== tests/cases/compiler/emitThisInObjectLiteralGetter.ts === +const example = { +>example : Symbol(example, Decl(emitThisInObjectLiteralGetter.ts, 0, 5)) + + get foo() { +>foo : Symbol(foo, Decl(emitThisInObjectLiteralGetter.ts, 0, 17)) + + return item => this.bar(item); +>item : Symbol(item, Decl(emitThisInObjectLiteralGetter.ts, 2, 14)) +>item : Symbol(item, Decl(emitThisInObjectLiteralGetter.ts, 2, 14)) + } +}; + diff --git a/tests/baselines/reference/emitThisInObjectLiteralGetter.types b/tests/baselines/reference/emitThisInObjectLiteralGetter.types new file mode 100644 index 00000000000..3d8712b2afd --- /dev/null +++ b/tests/baselines/reference/emitThisInObjectLiteralGetter.types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/emitThisInObjectLiteralGetter.ts === +const example = { +>example : { readonly foo: (item: any) => any; } +>{ get foo() { return item => this.bar(item); }} : { readonly foo: (item: any) => any; } + + get foo() { +>foo : (item: any) => any + + return item => this.bar(item); +>item => this.bar(item) : (item: any) => any +>item : any +>this.bar(item) : any +>this.bar : any +>this : any +>bar : any +>item : any + } +}; + diff --git a/tests/cases/compiler/emitThisInObjectLiteralGetter.ts b/tests/cases/compiler/emitThisInObjectLiteralGetter.ts new file mode 100644 index 00000000000..8927891bd98 --- /dev/null +++ b/tests/cases/compiler/emitThisInObjectLiteralGetter.ts @@ -0,0 +1,6 @@ +// @target: es5 +const example = { + get foo() { + return item => this.bar(item); + } +}; From c1103424b8a40bad1e154d728110876a6ebae98f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 25 Apr 2017 12:44:02 -0700 Subject: [PATCH 4/5] Fix class name emit in ES5 --- src/compiler/factory.ts | 22 +++++++++++++++++++ src/compiler/transformers/es2015.ts | 13 ++++++----- src/compiler/types.ts | 17 +++++++------- .../classDeclarationBlockScoping1.js | 4 ++-- .../classDeclarationBlockScoping2.js | 4 ++-- tests/baselines/reference/localTypes1.js | 4 ++-- 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 5c7486debe3..05c26916167 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2937,6 +2937,28 @@ namespace ts { ); } + /** + * Gets the internal name of a declaration. This is primarily used for declarations that can be + * referred to by name in the body of an ES5 class function body. An internal name will *never* + * be prefixed with an module or namespace export modifier like "exports." when emitted as an + * expression. An internal name will also *never* be renamed due to a collision with a block + * scoped variable. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); + } + + /** + * Gets whether an identifier should only be referred to by its internal name. + */ + export function isInternalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; + } + /** * Gets the local name of a declaration. This is primarily used for declarations that can be * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 0dc6ed4a964..a430476958e 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -813,7 +813,7 @@ namespace ts { // Create a synthetic text range for the return statement. const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken); - const localName = getLocalName(node); + const localName = getInternalName(node); // The following partially-emitted expression exists purely to align our sourcemap // emit with the original emitter. @@ -870,7 +870,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*asteriskToken*/ undefined, - getDeclarationName(node), + getInternalName(node), /*typeParameters*/ undefined, transformConstructorParameters(constructor, hasSynthesizedSuper), /*type*/ undefined, @@ -3725,7 +3725,7 @@ namespace ts { function substituteIdentifier(node: Identifier) { // Only substitute the identifier if we have enabled substitutions for block-scoped // bindings. - if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { + if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) { const original = getParseTreeNode(node, isIdentifier); if (original && isNameOfDeclarationWithCollidingName(original)) { return setTextRange(getGeneratedNameForNode(original), node); @@ -3778,7 +3778,7 @@ namespace ts { * @param node An Identifier node. */ function substituteExpressionIdentifier(node: Identifier): Identifier { - if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings) { + if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) { const declaration = resolver.getReferencedDeclarationWithCollidingName(node); if (declaration) { return setTextRange(getGeneratedNameForNode(declaration.name), node); @@ -3802,8 +3802,9 @@ namespace ts { } function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) { - const expression = getLocalName(node); - return hasModifier(member, ModifierFlags.Static) ? expression : createPropertyAccess(expression, "prototype"); + return hasModifier(member, ModifierFlags.Static) + ? getLocalName(node) + : createPropertyAccess(getInternalName(node), "prototype"); } function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration, hasExtendsClause: boolean) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2389ba80753..57cf17f7100 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3940,14 +3940,15 @@ namespace ts { HelperName = 1 << 12, ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration. - Indented = 1 << 15, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). - NoIndentation = 1 << 16, // Do not indent the node. - AsyncFunctionBody = 1 << 17, - ReuseTempVariableScope = 1 << 18, // Reuse the existing temp variable scope during emit. - CustomPrologue = 1 << 19, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). - NoHoisting = 1 << 20, // Do not hoist this declaration in --module system - HasEndOfDeclarationMarker = 1 << 21, // Declaration has an associated NotEmittedStatement to mark the end of the declaration - Iterator = 1 << 22, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. + InternalName = 1 << 15, // The name is internal to an ES5 class body function. + Indented = 1 << 16, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). + NoIndentation = 1 << 17, // Do not indent the node. + AsyncFunctionBody = 1 << 18, + ReuseTempVariableScope = 1 << 19, // Reuse the existing temp variable scope during emit. + CustomPrologue = 1 << 20, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). + NoHoisting = 1 << 21, // Do not hoist this declaration in --module system + HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration + Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable. } export interface EmitHelper { diff --git a/tests/baselines/reference/classDeclarationBlockScoping1.js b/tests/baselines/reference/classDeclarationBlockScoping1.js index ac3195af696..03c6254d9ea 100644 --- a/tests/baselines/reference/classDeclarationBlockScoping1.js +++ b/tests/baselines/reference/classDeclarationBlockScoping1.js @@ -15,8 +15,8 @@ var C = (function () { }()); { var C_1 = (function () { - function C_1() { + function C() { } - return C_1; + return C; }()); } diff --git a/tests/baselines/reference/classDeclarationBlockScoping2.js b/tests/baselines/reference/classDeclarationBlockScoping2.js index c3e3a89677a..7cc8a064a0c 100644 --- a/tests/baselines/reference/classDeclarationBlockScoping2.js +++ b/tests/baselines/reference/classDeclarationBlockScoping2.js @@ -19,9 +19,9 @@ function f() { var c1 = C; { var C_1 = (function () { - function C_1() { + function C() { } - return C_1; + return C; }()); var c2 = C_1; } diff --git a/tests/baselines/reference/localTypes1.js b/tests/baselines/reference/localTypes1.js index ab12be6d9fc..0932790dcfd 100644 --- a/tests/baselines/reference/localTypes1.js +++ b/tests/baselines/reference/localTypes1.js @@ -206,9 +206,9 @@ function f3(b) { } else { var A_1 = (function () { - function A_1() { + function A() { } - return A_1; + return A; }()); var c = [new A_1()]; c[0].x = E.B; From 8531b7ae9f09117e4230e1d68ef7097809cc67be Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 25 Apr 2017 13:30:07 -0700 Subject: [PATCH 5/5] Do not rename references to class inside of the class body function --- src/compiler/transformers/es2015.ts | 30 +++++++- .../baselines/reference/classBlockScoping.js | 71 ++++++++++++++++++ .../reference/classBlockScoping.symbols | 64 ++++++++++++++++ .../reference/classBlockScoping.types | 74 +++++++++++++++++++ tests/cases/compiler/classBlockScoping.ts | 33 +++++++++ 5 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/classBlockScoping.js create mode 100644 tests/baselines/reference/classBlockScoping.symbols create mode 100644 tests/baselines/reference/classBlockScoping.types create mode 100644 tests/cases/compiler/classBlockScoping.ts diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index a430476958e..734fbcaeb16 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3780,7 +3780,7 @@ namespace ts { function substituteExpressionIdentifier(node: Identifier): Identifier { if (enabledSubstitutions & ES2015SubstitutionFlags.BlockScopedBindings && !isInternalName(node)) { const declaration = resolver.getReferencedDeclarationWithCollidingName(node); - if (declaration) { + if (declaration && !(isClassLike(declaration) && isPartOfClassBody(declaration, node))) { return setTextRange(getGeneratedNameForNode(declaration.name), node); } } @@ -3788,6 +3788,32 @@ namespace ts { return node; } + function isPartOfClassBody(declaration: ClassLikeDeclaration, node: Identifier) { + let currentNode = getParseTreeNode(node); + if (!currentNode || currentNode === declaration || currentNode.end <= declaration.pos || currentNode.pos >= declaration.end) { + // if the node has no correlation to a parse tree node, its definitely not + // part of the body. + // if the node is outside of the document range of the declaration, its + // definitely not part of the body. + return false; + } + const blockScope = getEnclosingBlockScopeContainer(declaration); + while (currentNode) { + if (currentNode === blockScope || currentNode === declaration) { + // if we are in the enclosing block scope of the declaration, we are definitely + // not inside the class body. + return false; + } + if (isClassElement(currentNode) && currentNode.parent === declaration) { + // we are in the class body, but we treat static fields as outside of the class body + return currentNode.kind !== SyntaxKind.PropertyDeclaration + || (getModifierFlags(currentNode) & ModifierFlags.Static) === 0; + } + currentNode = currentNode.parent; + } + return false; + } + /** * Substitutes `this` when contained within an arrow function. * @@ -3803,7 +3829,7 @@ namespace ts { function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) { return hasModifier(member, ModifierFlags.Static) - ? getLocalName(node) + ? getInternalName(node) : createPropertyAccess(getInternalName(node), "prototype"); } diff --git a/tests/baselines/reference/classBlockScoping.js b/tests/baselines/reference/classBlockScoping.js new file mode 100644 index 00000000000..76d0dd892de --- /dev/null +++ b/tests/baselines/reference/classBlockScoping.js @@ -0,0 +1,71 @@ +//// [classBlockScoping.ts] +function f(b: boolean) { + let Foo: any; + if (b) { + Foo = class Foo { + static y = new Foo(); + + static x() { + new Foo(); + } + + m() { + new Foo(); + } + }; + + new Foo(); + } + else { + class Foo { + static y = new Foo(); + + static x() { + new Foo(); + } + + m() { + new Foo(); + } + } + + new Foo(); + } +} + +//// [classBlockScoping.js] +function f(b) { + var Foo; + if (b) { + Foo = (_a = (function () { + function Foo() { + } + Foo.x = function () { + new Foo(); + }; + Foo.prototype.m = function () { + new Foo(); + }; + return Foo; + }()), + _a.y = new _a(), + _a); + new Foo(); + } + else { + var Foo_1 = (function () { + function Foo() { + } + Foo.x = function () { + new Foo(); + }; + Foo.prototype.m = function () { + new Foo(); + }; + return Foo; + }()); + Foo_1.y = new Foo_1(); + new Foo_1(); + } + var _a; +} diff --git a/tests/baselines/reference/classBlockScoping.symbols b/tests/baselines/reference/classBlockScoping.symbols new file mode 100644 index 00000000000..571d54f2d8e --- /dev/null +++ b/tests/baselines/reference/classBlockScoping.symbols @@ -0,0 +1,64 @@ +=== tests/cases/compiler/classBlockScoping.ts === +function f(b: boolean) { +>f : Symbol(f, Decl(classBlockScoping.ts, 0, 0)) +>b : Symbol(b, Decl(classBlockScoping.ts, 0, 11)) + + let Foo: any; +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5)) + + if (b) { +>b : Symbol(b, Decl(classBlockScoping.ts, 0, 11)) + + Foo = class Foo { +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5)) +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9)) + + static y = new Foo(); +>y : Symbol(Foo.y, Decl(classBlockScoping.ts, 3, 21)) +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9)) + + static x() { +>x : Symbol(Foo.x, Decl(classBlockScoping.ts, 4, 27)) + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9)) + } + + m() { +>m : Symbol(Foo.m, Decl(classBlockScoping.ts, 8, 7)) + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 3, 9)) + } + }; + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 1, 5)) + } + else { + class Foo { +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8)) + + static y = new Foo(); +>y : Symbol(Foo.y, Decl(classBlockScoping.ts, 18, 15)) +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8)) + + static x() { +>x : Symbol(Foo.x, Decl(classBlockScoping.ts, 19, 27)) + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8)) + } + + m() { +>m : Symbol(Foo.m, Decl(classBlockScoping.ts, 23, 7)) + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8)) + } + } + + new Foo(); +>Foo : Symbol(Foo, Decl(classBlockScoping.ts, 17, 8)) + } +} diff --git a/tests/baselines/reference/classBlockScoping.types b/tests/baselines/reference/classBlockScoping.types new file mode 100644 index 00000000000..a54499d7400 --- /dev/null +++ b/tests/baselines/reference/classBlockScoping.types @@ -0,0 +1,74 @@ +=== tests/cases/compiler/classBlockScoping.ts === +function f(b: boolean) { +>f : (b: boolean) => void +>b : boolean + + let Foo: any; +>Foo : any + + if (b) { +>b : boolean + + Foo = class Foo { +>Foo = class Foo { static y = new Foo(); static x() { new Foo(); } m() { new Foo(); } } : typeof Foo +>Foo : any +>class Foo { static y = new Foo(); static x() { new Foo(); } m() { new Foo(); } } : typeof Foo +>Foo : typeof Foo + + static y = new Foo(); +>y : Foo +>new Foo() : Foo +>Foo : typeof Foo + + static x() { +>x : () => void + + new Foo(); +>new Foo() : Foo +>Foo : typeof Foo + } + + m() { +>m : () => void + + new Foo(); +>new Foo() : Foo +>Foo : typeof Foo + } + }; + + new Foo(); +>new Foo() : any +>Foo : any + } + else { + class Foo { +>Foo : Foo + + static y = new Foo(); +>y : Foo +>new Foo() : Foo +>Foo : typeof Foo + + static x() { +>x : () => void + + new Foo(); +>new Foo() : Foo +>Foo : typeof Foo + } + + m() { +>m : () => void + + new Foo(); +>new Foo() : Foo +>Foo : typeof Foo + } + } + + new Foo(); +>new Foo() : Foo +>Foo : typeof Foo + } +} diff --git a/tests/cases/compiler/classBlockScoping.ts b/tests/cases/compiler/classBlockScoping.ts new file mode 100644 index 00000000000..d4620fc89c8 --- /dev/null +++ b/tests/cases/compiler/classBlockScoping.ts @@ -0,0 +1,33 @@ +function f(b: boolean) { + let Foo: any; + if (b) { + Foo = class Foo { + static y = new Foo(); + + static x() { + new Foo(); + } + + m() { + new Foo(); + } + }; + + new Foo(); + } + else { + class Foo { + static y = new Foo(); + + static x() { + new Foo(); + } + + m() { + new Foo(); + } + } + + new Foo(); + } +} \ No newline at end of file