diff --git a/Jakefile.js b/Jakefile.js index 46f2a0fca96..9106c3c619d 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -717,7 +717,7 @@ function runTestsAndWriteOutput(file) { } var args = []; - args.push("-R", "TAP"); + args.push("-R", "tap"); args.push("--no-colors"); args.push("-t", testTimeout); if (tests) { @@ -1272,4 +1272,4 @@ function environmentVariableIsEnabled(name) { function environmentVariableIsDisabled(name) { return /^(no?|f(alse)?|off|disabled?|0|-)$/.test(process.env[name]); -} \ No newline at end of file +} diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1cff02ffc48..f92f6789d29 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1910,6 +1910,17 @@ namespace ts { // This is so that they can flow through PropertyName transforms unaffected. // Instead, we mark the container as ES6, so that it can properly handle the transform. transformFlags = TransformFlags.ContainsComputedPropertyName; + if (subtreeFlags & TransformFlags.ContainsLexicalThis) { + // A computed method name like `[this.getName()](x: string) { ... }` needs to + // distinguish itself from the normal case of a method body containing `this`: + // `this` inside a method doesn't need to be rewritten (the method provides `this`), + // whereas `this` inside a computed name *might* need to be rewritten if the class/object + // is inside an arrow function: + // `_this = this; () => class K { [_this.getName()]() { ... } }` + // To make this distinction, use ContainsLexicalThisInComputedPropertyName + // instead of ContainsLexicalThis for computed property names + transformFlags |= TransformFlags.ContainsLexicalThisInComputedPropertyName; + } break; case SyntaxKind.SpreadElementExpression: @@ -1945,6 +1956,11 @@ namespace ts { // is an ES6 node. transformFlags = TransformFlags.AssertES6; } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } break; case SyntaxKind.CallExpression: @@ -2256,6 +2272,11 @@ namespace ts { || hasModifier(node, ModifierFlags.Export)) { transformFlags |= TransformFlags.AssertTypeScript; } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); } @@ -2272,6 +2293,11 @@ namespace ts { | TransformFlags.ContainsDecorators)) { transformFlags |= TransformFlags.AssertTypeScript; } + if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { + // A computed property name containing `this` might need to be rewritten, + // so propagate the ContainsLexicalThis flag upward. + transformFlags |= TransformFlags.ContainsLexicalThis; + } return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index e35c5a08297..928ba76699e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1252,7 +1252,18 @@ namespace ts { return (node.expression as StringLiteral).text === "use strict"; } + /** + * Add any necessary prologue-directives into target statement-array. + * The function needs to be called during each transformation step. + * This function needs to be called whenever we transform the statement + * list of a source file, namespace, or function-like body. + * + * @param target: result statements array + * @param source: origin statements array + * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives + */ export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean): number { + Debug.assert(target.length === 0, "PrologueDirectives should be at the first statement in the target statements array"); let foundUseStrict = false; for (let i = 0; i < source.length; i++) { if (isPrologueDirective(source[i])) { diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 06e8a354d85..e1374c13ad3 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -370,16 +370,19 @@ namespace ts { ); } else if (isLiteralExpression(propertyName)) { - return createElementAccess( - expression, - getSynthesizedClone(propertyName) - ); + const clone = getSynthesizedClone(propertyName); + clone.text = unescapeIdentifier(clone.text); + return createElementAccess(expression, clone); } else { - return createPropertyAccess( - expression, - isGeneratedIdentifier(propertyName) ? getSynthesizedClone(propertyName) : createIdentifier(propertyName.text) - ); + if (isGeneratedIdentifier(propertyName)) { + const clone = getSynthesizedClone(propertyName); + clone.text = unescapeIdentifier(clone.text); + return createPropertyAccess(expression, clone); + } + else { + return createPropertyAccess(expression, createIdentifier(unescapeIdentifier(propertyName.text))); + } } } } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 796dd9529ad..7b769889dca 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -1211,7 +1211,15 @@ namespace ts { let statementsLocation: TextRange; const statements: Statement[] = []; + const body = node.body; + let statementOffset: number; + startLexicalEnvironment(); + if (isBlock(body)) { + // ensureUseStrict is false because no new prologue-directive should be added. + // addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array + statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false); + } addCaptureThisForNodeIfNeeded(statements, node); addDefaultValueAssignmentsIfNeeded(statements, node); addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); @@ -1221,10 +1229,9 @@ namespace ts { multiLine = true; } - const body = node.body; if (isBlock(body)) { statementsLocation = body.statements; - addRange(statements, visitNodes(body.statements, visitor, isStatement)); + addRange(statements, visitNodes(body.statements, visitor, isStatement, statementOffset)); // If the original body was a multi-line block, this must be a multi-line block. if (!multiLine && body.multiLine) { diff --git a/src/compiler/transformers/module/es6.ts b/src/compiler/transformers/module/es6.ts index 9355b476247..742f208d61c 100644 --- a/src/compiler/transformers/module/es6.ts +++ b/src/compiler/transformers/module/es6.ts @@ -34,12 +34,18 @@ namespace ts { return visitImportSpecifier(node); case SyntaxKind.ExportAssignment: return visitExportAssignment(node); + case SyntaxKind.ExportDeclaration: + return visitExportDeclaration(node); + case SyntaxKind.NamedExports: + return visitNamedExports(node); + case SyntaxKind.ExportSpecifier: + return visitExportSpecifier(node); } return node; } - function visitExportAssignment(node: ExportAssignment): ExportDeclaration { + function visitExportAssignment(node: ExportAssignment): ExportAssignment { if (node.isExportEquals) { return undefined; // do not emit export equals for ES6 } @@ -47,6 +53,34 @@ namespace ts { return nodeIsSynthesized(original) || resolver.isValueAliasDeclaration(original) ? node: undefined; } + function visitExportDeclaration(node: ExportDeclaration): ExportDeclaration { + if (!node.exportClause) { + return resolver.moduleExportsSomeValue(node.moduleSpecifier) ? node : undefined; + } + if (!resolver.isValueAliasDeclaration(node)) { + return undefined; + } + const newExportClause = visitNode(node.exportClause, visitor, isNamedExports, /*optional*/ true); + if (node.exportClause === newExportClause) { + return node; + } + return newExportClause + ? createExportDeclaration(newExportClause, node.moduleSpecifier) + : undefined; + } + + function visitNamedExports(node: NamedExports): NamedExports { + const newExports = visitNodes(node.elements, visitor, isExportSpecifier); + if (node.elements === newExports) { + return node; + } + return newExports.length ? createNamedExports(newExports) : undefined; + } + + function visitExportSpecifier(node: ExportSpecifier): ExportSpecifier { + return resolver.isValueAliasDeclaration(node) ? node : undefined; + } + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): ImportEqualsDeclaration { return !isExternalModuleImportEqualsDeclaration(node) || resolver.isReferencedAliasDeclaration(node) ? node : undefined; } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 5d789ed6280..14134a0e206 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -358,6 +358,9 @@ namespace ts { return undefined; } + // Set emitFlags on the name of the importEqualsDeclaration + // This is so the printer will not substitute the identifier + setNodeEmitFlags(node.name, NodeEmitFlags.NoSubstitution); const statements: Statement[] = []; if (moduleKind !== ModuleKind.AMD) { if (hasModifier(node, ModifierFlags.Export)) { @@ -672,6 +675,9 @@ namespace ts { function visitClassDeclaration(node: ClassDeclaration): VisitResult { const statements: Statement[] = []; const name = node.name || getGeneratedNameForNode(node); + // Set emitFlags on the name of the classDeclaration + // This is so that when printer will not substitute the identifier + setNodeEmitFlags(name, NodeEmitFlags.NoSubstitution); if (hasModifier(node, ModifierFlags.Export)) { statements.push( createClassDeclaration( @@ -804,11 +810,20 @@ namespace ts { else if (declaration.kind === SyntaxKind.ImportSpecifier) { const name = (declaration).propertyName || (declaration).name; - return createPropertyAccess( - getGeneratedNameForNode(declaration.parent.parent.parent), - getSynthesizedClone(name), - /*location*/ node - ); + if (name.originalKeywordKind === SyntaxKind.DefaultKeyword && languageVersion <= ScriptTarget.ES3) { + return createElementAccess( + getGeneratedNameForNode(declaration.parent.parent.parent), + createLiteral(name.text), + /*location*/ node + ); + } + else { + return createPropertyAccess( + getGeneratedNameForNode(declaration.parent.parent.parent), + getSynthesizedClone(name), + /*location*/ node + ); + } } } } @@ -840,7 +855,7 @@ namespace ts { function createExportAssignment(name: Identifier, value: Expression) { return createAssignment( - name.originalKeywordKind && languageVersion === ScriptTarget.ES3 + name.originalKeywordKind === SyntaxKind.DefaultKeyword && languageVersion === ScriptTarget.ES3 ? createElementAccess( createIdentifier("exports"), createLiteral(name.text) @@ -889,6 +904,9 @@ namespace ts { // Find the name of the module alias, if there is one const importAliasName = getLocalNameForExternalImport(importNode, currentSourceFile); if (includeNonAmdDependencies && importAliasName) { + // Set emitFlags on the name of the classDeclaration + // This is so that when printer will not substitute the identifier + setNodeEmitFlags(importAliasName, NodeEmitFlags.NoSubstitution); aliasedModuleNames.push(externalModuleName); importAliasNames.push(createParameter(importAliasName)); } diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 663917170ef..eee50781f32 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -193,7 +193,7 @@ namespace ts { startLexicalEnvironment(); // Add any prologue directives. - const statementOffset = addPrologueDirectives(statements, node.statements, !compilerOptions.noImplicitUseStrict); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict); // var __moduleName = context_1 && context_1.id; addNode(statements, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 001a08dca67..200050f189a 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2223,28 +2223,7 @@ namespace ts { || (isES6ExportedDeclaration(node) && isFirstDeclarationOfKind(node, node.kind)); } - /** - * Adds a leading VariableStatement for an enum or module declaration. - */ - function addVarForEnumDeclaration(statements: Statement[], node: EnumDeclaration) { - // Emit a variable statement for the enum. - statements.push( - setOriginalNode( - createVariableStatement( - isES6ExportedDeclaration(node) - ? visitNodes(node.modifiers, visitor, isModifier) - : undefined, - [createVariableDeclaration( - getDeclarationName(node) - )], - /*location*/ node - ), - /*original*/ node - ) - ); - } - - /** + /* * Adds a trailing VariableStatement for an enum or module declaration. */ function addVarForEnumExportedFromNamespace(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { @@ -2274,7 +2253,7 @@ namespace ts { const statements: Statement[] = []; if (shouldEmitVarForEnumDeclaration(node)) { - addVarForEnumDeclaration(statements, node); + addVarForEnumOrModuleDeclaration(statements, node); } const localName = getGeneratedNameForNode(node); @@ -2411,9 +2390,9 @@ namespace ts { } /** - * Adds a leading VariableStatement for a module declaration. + * Adds a leading VariableStatement for a enum or module declaration. */ - function addVarForModuleDeclaration(statements: Statement[], node: ModuleDeclaration) { + function addVarForEnumOrModuleDeclaration(statements: Statement[], node: ModuleDeclaration | EnumDeclaration) { // Emit a variable statement for the module. statements.push( setOriginalNode( @@ -2424,7 +2403,21 @@ namespace ts { [createVariableDeclaration( getDeclarationName(node) )], - /*location*/ node + // Trailing comments for module declaration should be emitted with function closure instead of variable statement + // So do not set the end position for the variable statement node + // /** Module comment*/ + // module m1 { + // function foo4Export() { + // } + // } // trailing comment module + // Should emit + // /** Module comment*/ + // var m1; + // (function (m1) { + // function foo4Export() { + // } + // })(m1 || (m1 = {})); // trailing comment module + /*location*/ { pos: node.pos, end: -1 } ), node ) @@ -2449,7 +2442,7 @@ namespace ts { const statements: Statement[] = []; if (shouldEmitVarForModuleDeclaration(node)) { - addVarForModuleDeclaration(statements, node); + addVarForEnumOrModuleDeclaration(statements, node); } const localName = getGeneratedNameForNode(node); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 627b33f0d34..4c99417950d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2830,11 +2830,12 @@ namespace ts { ContainsPropertyInitializer = 1 << 10, ContainsLexicalThis = 1 << 11, ContainsCapturedLexicalThis = 1 << 12, - ContainsDefaultValueAssignments = 1 << 13, - ContainsParameterPropertyAssignments = 1 << 14, - ContainsSpreadElementExpression = 1 << 15, - ContainsComputedPropertyName = 1 << 16, - ContainsBlockScopedBinding = 1 << 17, + ContainsLexicalThisInComputedPropertyName = 1 << 13, + ContainsDefaultValueAssignments = 1 << 14, + ContainsParameterPropertyAssignments = 1 << 15, + ContainsSpreadElementExpression = 1 << 16, + ContainsComputedPropertyName = 1 << 17, + ContainsBlockScopedBinding = 1 << 18, HasComputedFlags = 1 << 31, // Transform flags have been computed. @@ -2853,10 +2854,10 @@ namespace ts { FunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, ConstructorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, MethodOrAccessorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, - ClassExcludes = ContainsDecorators | ContainsPropertyInitializer | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsParameterPropertyAssignments, + ClassExcludes = ContainsDecorators | ContainsPropertyInitializer | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsParameterPropertyAssignments | ContainsLexicalThisInComputedPropertyName, ModuleExcludes = ContainsDecorators | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, TypeExcludes = ~ContainsTypeScript, - ObjectLiteralExcludes = ContainsDecorators | ContainsComputedPropertyName, + ObjectLiteralExcludes = ContainsDecorators | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName, ArrayLiteralOrCallOrNewExcludes = ContainsSpreadElementExpression, } diff --git a/tests/baselines/reference/computedPropertyNames29_ES5.js b/tests/baselines/reference/computedPropertyNames29_ES5.js index af6f93c8ad9..966081ae035 100644 --- a/tests/baselines/reference/computedPropertyNames29_ES5.js +++ b/tests/baselines/reference/computedPropertyNames29_ES5.js @@ -18,7 +18,8 @@ var C = (function () { var _this = this; (function () { var obj = (_a = {}, - _a[_this.bar()] = function () { }, + _a[_this.bar()] = function () { } // needs capture + , _a); var _a; }); diff --git a/tests/baselines/reference/computedPropertyNames31_ES5.js b/tests/baselines/reference/computedPropertyNames31_ES5.js index 86fb6655a27..2e38e2a131f 100644 --- a/tests/baselines/reference/computedPropertyNames31_ES5.js +++ b/tests/baselines/reference/computedPropertyNames31_ES5.js @@ -38,7 +38,8 @@ var C = (function (_super) { var _this = this; (function () { var obj = (_a = {}, - _a[_super.prototype.bar.call(_this)] = function () { }, + _a[_super.prototype.bar.call(_this)] = function () { } // needs capture + , _a); var _a; });