From c759b633d6e33754fb0cd4473759062ce920c4a2 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 19 Feb 2016 17:01:02 -0800 Subject: [PATCH] Adds ES6 transformer --- Jakefile.js | 9 + src/compiler/binder.ts | 36 +- src/compiler/checker.ts | 10 +- src/compiler/commandLineParser.ts | 5 + src/compiler/comments.ts | 142 +- src/compiler/core.ts | 16 +- src/compiler/declarationEmitter.ts | 8 +- src/compiler/emitter.ts | 66 +- src/compiler/factory.ts | 347 ++++- src/compiler/parser.ts | 4 + src/compiler/printer.ts | 237 +-- src/compiler/program.ts | 2 +- src/compiler/scanner.ts | 5 +- src/compiler/sourcemap.ts | 4 +- src/compiler/transformer.ts | 7 +- src/compiler/transformers/destructuring.ts | 10 +- src/compiler/transformers/es6.ts | 1515 +++++++++++++++++++- src/compiler/transformers/es7.ts | 4 +- src/compiler/transformers/jsx.ts | 8 +- src/compiler/transformers/ts.ts | 147 +- src/compiler/types.ts | 36 +- src/compiler/utilities.ts | 186 ++- src/compiler/visitor.ts | 162 ++- 23 files changed, 2537 insertions(+), 429 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index 69510545ea3..1929a3a475f 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -238,6 +238,7 @@ function concatenateFiles(destinationFile, sourceFiles) { } var useDebugMode = true; +var useTransforms = process.env.USE_TRANSFORMS || false; var host = (process.env.host || process.env.TYPESCRIPT_HOST || "node"); var compilerFilename = "tsc.js"; var LKGCompiler = path.join(LKGDirectory, compilerFilename); @@ -297,6 +298,10 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, noOu options += " --stripInternal" } + if (useBuiltCompiler && useTransforms) { + options += " --experimentalTransforms" + } + var cmd = host + " " + compilerPath + " " + options + " "; cmd = cmd + sources.join(" "); console.log(cmd + "\n"); @@ -420,6 +425,10 @@ task("setDebugMode", function() { useDebugMode = true; }); +task("setTransforms", function() { + useTransforms = true; +}); + task("configure-nightly", [configureNightlyJs], function() { var cmd = host + " " + configureNightlyJs + " " + packageJson + " " + programTs; console.log(cmd); diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b0ae5804b5f..6f1de3677e7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1971,20 +1971,6 @@ namespace ts { break; - case SyntaxKind.ExpressionStatement: - if (nodeIsSynthesized(node)) { - const expression = (node).expression; - if (nodeIsSynthesized(expression) - && isCallExpression(expression) - && expression.expression.kind === SyntaxKind.SuperKeyword) { - // A synthesized call to `super` should be transformed to a cleaner emit - // when transpiling to ES5/3. - transformFlags |= TransformFlags.AssertES6; - } - } - - break; - case SyntaxKind.BinaryExpression: if (isDestructuringAssignment(node)) { // Destructuring assignments are ES6 syntax. @@ -2093,7 +2079,7 @@ namespace ts { case SyntaxKind.VariableDeclarationList: // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. if (node.flags & NodeFlags.BlockScoped) { - transformFlags |= TransformFlags.AssertES6; + transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsBlockScopedBinding; } break; @@ -2106,6 +2092,26 @@ namespace ts { break; + case SyntaxKind.LabeledStatement: + // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding + && isIterationStatement(this, /*lookInLabeledStatements*/ true)) { + transformFlags |= TransformFlags.AssertES6; + } + + break; + + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + // A loop containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding) { + transformFlags |= TransformFlags.AssertES6; + } + + break; + case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: // A ClassDeclarations or ClassExpression is ES6 syntax. diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3cb9031fd99..7dfd679e72c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16672,11 +16672,13 @@ namespace ts { } // Modifiers are never allowed on properties except for 'async' on a method declaration - forEach(prop.modifiers, mod => { - if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + if (prop.modifiers) { + for (const mod of prop.modifiers) { + if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } } - }); + } // ECMA-262 11.1.5 Object Initialiser // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d5bf95a6405..c65466f00de 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -320,6 +320,11 @@ namespace ts { name: "allowSyntheticDefaultImports", type: "boolean", description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking + }, + { + name: "experimentalTransforms", + type: "boolean", + experimental: true } ]; diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 2b51e937fad..a680d01e102 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -5,11 +5,15 @@ namespace ts { export interface CommentWriter { reset(): void; setSourceFile(sourceFile: SourceFile): void; - getLeadingCommentsToEmit(node: TextRange): CommentRange[]; - getTrailingCommentsToEmit(node: TextRange): CommentRange[]; - emitDetachedComments(node: TextRange): void; - emitLeadingComments(node: TextRange, comments?: CommentRange[]): void; - emitTrailingComments(node: TextRange, comments?: CommentRange[]): void; + getLeadingComments(range: Node, getAdditionalRange?: (range: Node) => Node): CommentRange[]; + getLeadingComments(range: TextRange): CommentRange[]; + getLeadingCommentsOfPosition(pos: number): CommentRange[]; + getTrailingComments(range: Node, getAdditionalRange?: (range: Node) => Node): CommentRange[]; + getTrailingComments(range: TextRange): CommentRange[]; + getTrailingCommentsOfPosition(pos: number): CommentRange[]; + emitLeadingComments(range: TextRange, comments?: CommentRange[]): void; + emitTrailingComments(range: TextRange, comments?: CommentRange[]): void; + emitDetachedComments(range: TextRange): void; } export function createCommentWriter(host: EmitHost, writer: EmitTextWriter, sourceMap: SourceMapWriter): CommentWriter { @@ -25,8 +29,8 @@ namespace ts { // This maps start->end for a comment range. See `hasConsumedCommentRange` and // `consumeCommentRange` for usage. let consumedCommentRanges: number[]; - let leadingCommentRangeNodeStarts: boolean[]; - let trailingCommentRangeNodeEnds: boolean[]; + let leadingCommentRangePositions: boolean[]; + let trailingCommentRangePositions: boolean[]; return compilerOptions.removeComments ? createCommentRemovingWriter() @@ -36,11 +40,13 @@ namespace ts { return { reset, setSourceFile, - getLeadingCommentsToEmit(node: TextRange): CommentRange[] { return undefined; }, - getTrailingCommentsToEmit(node: TextRange): CommentRange[] { return undefined; }, + getLeadingComments(range: TextRange, getAdditionalRange?: (range: TextRange) => TextRange): CommentRange[] { return undefined; }, + getLeadingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, + getTrailingComments(range: TextRange, getAdditionalRange?: (range: TextRange) => TextRange): CommentRange[] { return undefined; }, + getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, + emitLeadingComments(range: TextRange, comments?: CommentRange[]): void { }, + emitTrailingComments(range: TextRange, comments?: CommentRange[]): void { }, emitDetachedComments, - emitLeadingComments(node: TextRange, comments?: CommentRange[]): void { }, - emitTrailingComments(node: TextRange, comments?: CommentRange[]): void { }, }; function emitDetachedComments(node: TextRange): void { @@ -53,41 +59,85 @@ namespace ts { return { reset, setSourceFile, - getLeadingCommentsToEmit, - getTrailingCommentsToEmit, - emitDetachedComments, + getLeadingComments, + getLeadingCommentsOfPosition, + getTrailingComments, + getTrailingCommentsOfPosition, emitLeadingComments, emitTrailingComments, + emitDetachedComments, }; - function getLeadingCommentsToEmit(node: TextRange) { - if (nodeIsSynthesized(node)) { - return; + function getLeadingComments(range: TextRange | Node, getAdditionalRange?: (range: Node) => Node) { + let comments = getLeadingCommentsOfPosition(range.pos); + if (getAdditionalRange) { + let additionalRange = getAdditionalRange(range); + while (additionalRange) { + comments = concatenate( + getLeadingCommentsOfPosition(additionalRange.pos), + comments + ); + + additionalRange = getAdditionalRange(additionalRange); + } } - if (!leadingCommentRangeNodeStarts[node.pos]) { - leadingCommentRangeNodeStarts[node.pos] = true; - const comments = hasDetachedComments(node.pos) - ? getLeadingCommentsWithoutDetachedComments() - : getLeadingCommentRanges(currentText, node.pos); - return consumeCommentRanges(comments); - } - - return noComments; + return comments; } - function getTrailingCommentsToEmit(node: TextRange) { - if (nodeIsSynthesized(node)) { - return; + function getTrailingComments(range: TextRange | Node, getAdditionalRange?: (range: Node) => Node) { + let comments = getTrailingCommentsOfPosition(range.end); + if (getAdditionalRange) { + let additionalRange = getAdditionalRange(range); + while (additionalRange) { + comments = concatenate( + comments, + getTrailingCommentsOfPosition(additionalRange.end) + ); + + additionalRange = getAdditionalRange(additionalRange); + } } - if (!trailingCommentRangeNodeEnds[node.end]) { - trailingCommentRangeNodeEnds[node.end] = true; - const comments = getTrailingCommentRanges(currentText, node.end); - return consumeCommentRanges(comments); + return comments; + } + + function getLeadingCommentsOfPosition(pos: number) { + if (positionIsSynthesized(pos) || leadingCommentRangePositions[pos]) { + return undefined; } - return noComments; + leadingCommentRangePositions[pos] = true; + const comments = hasDetachedComments(pos) + ? getLeadingCommentsWithoutDetachedComments() + : getLeadingCommentRanges(currentText, pos); + return consumeCommentRanges(comments); + } + + function getTrailingCommentsOfPosition(pos: number) { + if (positionIsSynthesized(pos) || trailingCommentRangePositions[pos]) { + return undefined; + } + + trailingCommentRangePositions[pos] = true; + const comments = getTrailingCommentRanges(currentText, pos); + return consumeCommentRanges(comments); + } + + function emitLeadingComments(range: TextRange, comments = getLeadingComments(range)) { + emitNewLineBeforeLeadingComments(currentLineMap, writer, range, comments); + + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); + } + + function emitTrailingComments(range: TextRange, comments = getTrailingComments(range)) { + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ + emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); + } + + function emitDetachedComments(range: TextRange) { + emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); } function hasConsumedCommentRange(comment: CommentRange) { @@ -136,22 +186,6 @@ namespace ts { return noComments; } - - function emitLeadingComments(range: TextRange, leadingComments: CommentRange[] = getLeadingCommentsToEmit(range)) { - emitNewLineBeforeLeadingComments(currentLineMap, writer, range, leadingComments); - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); - } - - function emitTrailingComments(range: TextRange, trailingComments = getTrailingCommentsToEmit(range)) { - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ false, newLine, writeComment); - } - - function emitDetachedComments(range: TextRange) { - emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); - } } function reset() { @@ -160,8 +194,8 @@ namespace ts { currentLineMap = undefined; detachedCommentsInfo = undefined; consumedCommentRanges = undefined; - trailingCommentRangeNodeEnds = undefined; - leadingCommentRangeNodeStarts = undefined; + trailingCommentRangePositions = undefined; + leadingCommentRangePositions = undefined; } function setSourceFile(sourceFile: SourceFile) { @@ -170,8 +204,8 @@ namespace ts { currentLineMap = getLineStarts(sourceFile); detachedCommentsInfo = undefined; consumedCommentRanges = []; - leadingCommentRangeNodeStarts = []; - trailingCommentRangeNodeEnds = []; + leadingCommentRangePositions = []; + trailingCommentRangePositions = []; } function hasDetachedComments(pos: number) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 19726482cb3..09446cf668d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -292,9 +292,9 @@ namespace ts { return ~low; } - export function reduceLeft(array: T[], f: (memo: U, value: T) => U, initial: U): U; - export function reduceLeft(array: T[], f: (memo: T, value: T) => T): T; - export function reduceLeft(array: T[], f: (memo: T, value: T) => T, initial?: T): T { + export function reduceLeft(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { if (array) { const count = array.length; if (count > 0) { @@ -308,7 +308,7 @@ namespace ts { result = initial; } while (pos < count) { - result = f(result, array[pos]); + result = f(result, array[pos], pos); pos++; } return result; @@ -317,9 +317,9 @@ namespace ts { return initial; } - export function reduceRight(array: T[], f: (memo: U, value: T) => U, initial: U): U; - export function reduceRight(array: T[], f: (memo: T, value: T) => T): T; - export function reduceRight(array: T[], f: (memo: T, value: T) => T, initial?: T): T { + export function reduceRight(array: T[], f: (memo: U, value: T, i: number) => U, initial: U): U; + export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T): T; + export function reduceRight(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T): T { if (array) { let pos = array.length - 1; if (pos >= 0) { @@ -332,7 +332,7 @@ namespace ts { result = initial; } while (pos >= 0) { - result = f(result, array[pos]); + result = f(result, array[pos], pos); pos--; } return result; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index ab0b16947fc..c89b7579d52 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -91,7 +91,7 @@ namespace ts { // Emit reference in dts, if the file reference was not already emitted if (referencedFile && !contains(emittedReferencedFiles, referencedFile)) { // Add a reference to generated dts file, - // global file reference is added only + // global file reference is added only // - if it is not bundled emit (because otherwise it would be self reference) // - and it is not already added if (writeReferencePath(referencedFile, !isBundledEmit && !addedGlobalFileReference)) { @@ -144,7 +144,7 @@ namespace ts { if (!isBundledEmit && isExternalModule(sourceFile) && sourceFile.moduleAugmentations.length && !resultHasExternalModuleIndicator) { // if file was external module with augmentations - this fact should be preserved in .d.ts as well. - // in case if we didn't write any external module specifiers in .d.ts we need to emit something + // in case if we didn't write any external module specifiers in .d.ts we need to emit something // that will force compiler to think that this file is an external module - 'export {}' is a reasonable choice here. write("export {};"); writeLine(); @@ -349,7 +349,7 @@ namespace ts { const jsDocComments = getJsDocCommentsFromText(declaration, currentText); emitNewLineBeforeLeadingComments(currentLineMap, writer, declaration, jsDocComments); // jsDoc comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, jsDocComments, /*trailingSeparator*/ true, newLine, writeCommentRange); + emitComments(currentText, currentLineMap, writer, jsDocComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeCommentRange); } } @@ -736,7 +736,7 @@ namespace ts { function emitExternalModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration) { // emitExternalModuleSpecifier is usually called when we emit something in the.d.ts file that will make it an external module (i.e. import/export declarations). - // the only case when it is not true is when we call it to emit correct name for module augmentation - d.ts files with just module augmentations are not considered + // the only case when it is not true is when we call it to emit correct name for module augmentation - d.ts files with just module augmentations are not considered // external modules since they are indistingushable from script files with ambient modules. To fix this in such d.ts files we'll emit top level 'export {}' // so compiler will treat them as external modules. resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || parent.kind !== SyntaxKind.ModuleDeclaration; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index ebac2996765..3919e68aba5 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -287,12 +287,8 @@ namespace ts { _i = 0x10000000, // Use/preference flag for '_i' } - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { - return printFiles(resolver, host, targetSourceFile); - } - // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function legacyEmitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { + export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { // emit output for the __extends helper function const extendsHelper = ` var __extends = (this && this.__extends) || function (d, b) { @@ -1919,7 +1915,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (multiLine) { decreaseIndent(); - writeLine(); } write(")"); @@ -2246,6 +2241,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return node; } + function skipAssertions(node: Expression): Expression { + while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; + } + return node; + } + function emitCallTarget(node: Expression): Expression { if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { emit(node); @@ -2695,7 +2697,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function synthesizedNodeStartsOnNewLine(node: Node) { - return nodeIsSynthesized(node) && (node).startsOnNewLine; + return nodeIsSynthesized(node) && node.startsOnNewLine; } function emitConditionalExpression(node: ConditionalExpression) { @@ -3312,8 +3314,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // we can't reuse 'arr' because it might be modified within the body of the loop. const counter = createTempVariable(TempFlags._i); const rhsReference = createSynthesizedNode(SyntaxKind.Identifier) as Identifier; - rhsReference.text = node.expression.kind === SyntaxKind.Identifier ? - makeUniqueName((node.expression).text) : + const expressionWithoutAssertions = skipAssertions(node.expression); + rhsReference.text = expressionWithoutAssertions.kind === SyntaxKind.Identifier ? + makeUniqueName((expressionWithoutAssertions).text) : makeTempVariableName(TempFlags.Auto); // This is the let keyword for the counter and rhsReference. The let keyword for @@ -4328,7 +4331,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge writeLine(); emitStart(restParam); emitNodeWithCommentsAndWithoutSourcemap(restParam.name); - write("[" + tempName + " - " + restIndex + "] = arguments[" + tempName + "];"); + write(restIndex > 0 + ? `[${tempName} - ${restIndex}] = arguments[${tempName}];` + : `[${tempName}] = arguments[${tempName}];`); emitEnd(restParam); decreaseIndent(); writeLine(); @@ -5344,6 +5349,18 @@ const _super = (function (geti, seti) { write(" = "); } + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const isClassExpressionWithStaticProperties = staticProperties.length > 0 && node.kind === SyntaxKind.ClassExpression; + let tempVariable: Identifier; + + if (isClassExpressionWithStaticProperties) { + tempVariable = createAndRecordTempVariable(TempFlags.Auto); + write("("); + increaseIndent(); + emit(tempVariable); + write(" = "); + } + write("(function ("); const baseTypeNode = getClassExtendsHeritageClauseElement(node); if (baseTypeNode) { @@ -5373,9 +5390,6 @@ const _super = (function (geti, seti) { writeLine(); emitConstructor(node, baseTypeNode); emitMemberFunctionsForES5AndLower(node); - emitPropertyDeclarations(node, getInitializedProperties(node, /*isStatic*/ true)); - writeLine(); - emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); writeLine(); emitToken(SyntaxKind.CloseBraceToken, node.members.end, () => { write("return "); @@ -5402,7 +5416,23 @@ const _super = (function (geti, seti) { write("))"); if (node.kind === SyntaxKind.ClassDeclaration) { write(";"); + emitPropertyDeclarations(node, staticProperties); + writeLine(); + emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); } + else if (isClassExpressionWithStaticProperties) { + for (const property of staticProperties) { + write(","); + writeLine(); + emitPropertyDeclaration(node, property, /*receiver*/ tempVariable, /*isExpression*/ true); + } + write(","); + writeLine(); + emit(tempVariable); + decreaseIndent(); + write(")"); + } + emitEnd(node); if (node.kind === SyntaxKind.ClassDeclaration) { @@ -7941,7 +7971,7 @@ const _super = (function (geti, seti) { emitNewLineBeforeLeadingComments(currentLineMap, writer, node, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitTrailingComments(node: Node) { @@ -7953,7 +7983,7 @@ const _super = (function (geti, seti) { const trailingComments = getTrailingCommentsToEmit(node); // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ false, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); } /** @@ -7968,8 +7998,8 @@ const _super = (function (geti, seti) { const trailingComments = getTrailingCommentRanges(currentText, pos); - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment); + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitLeadingCommentsOfPositionWorker(pos: number) { @@ -7990,7 +8020,7 @@ const _super = (function (geti, seti) { emitNewLineBeforeLeadingComments(currentLineMap, writer, { pos: pos, end: pos }, leadingComments); // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 0b0505bb50f..5fdffc32d46 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -92,7 +92,7 @@ namespace ts { } export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { - const node = createNode(kind, /*location*/ undefined); + const node = createNode(kind, /*location*/ undefined); node.startsOnNewLine = startsOnNewLine; return node; } @@ -153,20 +153,20 @@ namespace ts { // Literals - export function createLiteral(value: string): StringLiteral; - export function createLiteral(value: number): LiteralExpression; - export function createLiteral(value: string | number | boolean): PrimaryExpression; - export function createLiteral(value: string | number | boolean): PrimaryExpression { + export function createLiteral(value: string, location?: TextRange): StringLiteral; + export function createLiteral(value: number, location?: TextRange): LiteralExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression { if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral); + const node = createNode(SyntaxKind.NumericLiteral, location); node.text = value.toString(); return node; } else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location); } else { - const node = createNode(SyntaxKind.StringLiteral); + const node = createNode(SyntaxKind.StringLiteral, location); node.text = String(value); return node; } @@ -203,8 +203,8 @@ namespace ts { return node; } - export function createThis() { - const node = createNode(SyntaxKind.ThisKeyword); + export function createThis(location?: TextRange) { + const node = createNode(SyntaxKind.ThisKeyword, location); return node; } @@ -267,8 +267,8 @@ namespace ts { return node; } - export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression) { - const node = createNode(SyntaxKind.Parameter); + export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.Parameter, location); node.decorators = undefined; node.modifiers = undefined; node.dotDotDotToken = undefined; @@ -288,8 +288,8 @@ namespace ts { return node; } - export function createObjectLiteral(properties?: ObjectLiteralElement[]) { - const node = createNode(SyntaxKind.ObjectLiteralExpression); + export function createObjectLiteral(properties?: ObjectLiteralElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ObjectLiteralExpression, location); node.properties = createNodeArray(properties); return node; } @@ -316,6 +316,13 @@ namespace ts { return node; } + export function createNew(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.NewExpression, location); + node.expression = parenthesizeForAccess(expression); + node.arguments = argumentsArray ? createNodeArray(argumentsArray) : undefined; + return node; + } + export function createParen(expression: Expression, location?: TextRange) { const node = createNode(SyntaxKind.ParenthesizedExpression, location); node.expression = expression; @@ -347,13 +354,20 @@ namespace ts { export function createTypeOf(expression: Expression) { const node = createNode(SyntaxKind.TypeOfExpression); - node.expression = parenthesizeForUnary(expression); + node.expression = parenthesizePrefixOperand(expression); return node; } export function createVoid(expression: Expression) { const node = createNode(SyntaxKind.VoidExpression); - node.expression = parenthesizeForUnary(expression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function createPostfix(operand: Expression, operator: SyntaxKind, location?: TextRange) { + const node = createNode(SyntaxKind.PostfixUnaryExpression, location); + node.operand = parenthesizePostfixOperand(operand); + node.operator = operator; return node; } @@ -442,14 +456,64 @@ namespace ts { return node; } + export function createEmptyStatement(location: TextRange) { + return createNode(SyntaxKind.EmptyStatement, location); + } + export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { const node = createNode(SyntaxKind.ExpressionStatement, location); node.expression = expression; return node; } - export function createReturn(expression?: Expression): ReturnStatement { - const node = createSynthesizedNode(SyntaxKind.ReturnStatement); + export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.IfStatement, location); + node.expression = expression; + node.thenStatement = thenStatement; + node.elseStatement = elseStatement; + return node; + } + + export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForStatement, location); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = statement; + return node; + } + + export function createLabel(label: string | Identifier, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.LabeledStatement, location); + node.label = typeof label === "string" ? createIdentifier(label) : label; + node.statement = statement; + return node; + } + + export function createDo(expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.DoStatement, location); + node.expression = expression; + node.statement = statement; + return node; + } + + export function createWhile(statement: Statement, expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.WhileStatement, location); + node.statement = statement; + node.expression = expression; + return node; + } + + export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForInStatement, location); + node.initializer = initializer; + node.expression = expression; + node.statement = statement; + return node; + } + + export function createReturn(expression?: Expression, location?: TextRange): ReturnStatement { + const node = createNode(SyntaxKind.ReturnStatement, location); node.expression = expression; return node; } @@ -516,9 +580,9 @@ namespace ts { // Property assignments - export function createPropertyAssignment(name: PropertyName, initializer: Expression) { - const node = createNode(SyntaxKind.PropertyAssignment); - node.name = name; + export function createPropertyAssignment(name: string | PropertyName, initializer: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAssignment, location); + node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; node.initializer = initializer; return node; @@ -526,18 +590,18 @@ namespace ts { // Compound nodes + export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); + } + + export function createLessThan(left: Expression, right: Expression, location?: TextRange) { + return createBinary(left, SyntaxKind.LessThanToken, right, location); + } + export function createAssignment(left: Expression, right: Expression, location?: TextRange) { return createBinary(left, SyntaxKind.EqualsToken, right, location); } - export function createLogicalAnd(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); - } - - export function createLogicalOr(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.BarBarToken, right); - } - export function createStrictEquality(left: Expression, right: Expression) { return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); } @@ -546,8 +610,24 @@ namespace ts { return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); } - export function createComma(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.CommaToken, right); + export function createAdd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.PlusToken, right); + } + + export function createSubtract(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.MinusToken, right); + } + + export function createPostfixIncrement(operand: Expression, location?: TextRange) { + return createPostfix(operand, SyntaxKind.PlusPlusToken, location); + } + + export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); + } + + export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); } export function createVoidZero() { @@ -566,6 +646,28 @@ namespace ts { return node; } + export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: Expression[], location?: TextRange) { + return createCall( + createPropertyAccess(func, "call"), + [ + thisArg, + ...argumentsList + ], + location + ); + } + + export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { + return createCall( + createPropertyAccess(func, "apply"), + [ + thisArg, + argumentsExpression + ], + location + ); + } + export function createArraySlice(array: Expression, start?: number | Expression) { const argumentsList: Expression[] = []; if (start !== undefined) { @@ -575,6 +677,13 @@ namespace ts { return createCall(createPropertyAccess(array, "slice"), argumentsList); } + export function createArrayConcat(array: Expression, values: Expression[]) { + return createCall( + createPropertyAccess(array, "concat"), + values + ); + } + export function createMathPow(left: Expression, right: Expression, location?: TextRange) { return createCall( createPropertyAccess(createIdentifier("Math"), "pow"), @@ -618,6 +727,16 @@ namespace ts { // Helpers + export function createExtendsHelper(name: Identifier) { + return createCall( + createIdentifier("__extends"), + [ + name, + createIdentifier("_super") + ] + ); + } + export function createParamHelper(expression: Expression, parameterOffset: number) { return createCall( createIdentifier("__param"), @@ -671,6 +790,56 @@ namespace ts { ); } + function createPropertyDescriptor({ get, set, value, enumerable, configurable, writable }: PropertyDescriptorOptions, preferNewLine?: boolean, location?: TextRange) { + const properties: ObjectLiteralElement[] = []; + addPropertyAssignment(properties, "get", get, preferNewLine); + addPropertyAssignment(properties, "set", set, preferNewLine); + addPropertyAssignment(properties, "value", value, preferNewLine); + addPropertyAssignment(properties, "enumerable", enumerable, preferNewLine); + addPropertyAssignment(properties, "configurable", configurable, preferNewLine); + addPropertyAssignment(properties, "writable", writable, preferNewLine); + return createObjectLiteral(properties, location) + } + + function addPropertyAssignment(properties: ObjectLiteralElement[], name: string, value: boolean | Expression, preferNewLine: boolean) { + if (value !== undefined) { + const property = createPropertyAssignment( + name, + typeof value === "boolean" ? createLiteral(value) : value + ); + + if (preferNewLine) { + startOnNewLine(property); + } + + addNode(properties, property); + } + } + + export interface PropertyDescriptorOptions { + get?: Expression; + set?: Expression; + value?: Expression; + enumerable?: boolean | Expression; + configurable?: boolean | Expression; + writable?: boolean | Expression; + } + + export function createObjectDefineProperty(target: Expression, memberName: Expression, descriptor: PropertyDescriptorOptions, preferNewLine?: boolean, location?: TextRange) { + return createCall( + createPropertyAccess( + createIdentifier("Object"), + "defineProperty" + ), + [ + target, + memberName, + createPropertyDescriptor(descriptor, preferNewLine) + ], + location + ); + } + function createObjectCreate(prototype: Expression) { return createCall( createPropertyAccess(createIdentifier("Object"), "create"), @@ -839,6 +1008,12 @@ namespace ts { : cloneNode(node); } + export function createExpressionForPropertyName(memberName: PropertyName, location?: TextRange): Expression { + return isIdentifier(memberName) ? createLiteral(memberName.text, location) + : isComputedPropertyName(memberName) ? cloneNode(memberName.expression, location) + : cloneNode(memberName, location); + } + // Utilities /** @@ -850,11 +1025,7 @@ namespace ts { * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the * BinaryExpression. */ - function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { - // When diagnosing whether the expression needs parentheses, the decision should be based - // on the innermost expression in a chain of nested type assertions. - operand = skipAssertions(operand); - + export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { // If the resulting expression is already parenthesized, we do not need to do any further processing. if (operand.kind === SyntaxKind.ParenthesizedExpression) { return operand; @@ -968,11 +1139,7 @@ namespace ts { * * @param expr The expression node. */ - function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { - // When diagnosing whether the expression needs parentheses, the decision should be based - // on the innermost expression in a chain of nested type assertions. - expr = skipAssertions(expr); - + export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { // isLeftHandSideExpression is almost the correct criterion for when it is not necessary // to parenthesize the expression before a dot. The known exceptions are: // @@ -981,21 +1148,86 @@ namespace ts { // NumericLiteral // 1.x -> not the same as (1).x // - if (isLeftHandSideExpression(expr) && - expr.kind !== SyntaxKind.NewExpression && - expr.kind !== SyntaxKind.NumericLiteral) { - return expr; + if (isLeftHandSideExpression(expression) && + expression.kind !== SyntaxKind.NewExpression && + expression.kind !== SyntaxKind.NumericLiteral) { + return expression; } - return createParen(expr); + return createParen(expression, /*location*/ expression); } - function parenthesizeForUnary(operand: Expression) { - if (isUnaryExpression(operand)) { - return operand; + export function parenthesizePostfixOperand(operand: Expression) { + return isLeftHandSideExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizePrefixOperand(operand: Expression) { + return isUnaryExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizeExpressionForList(expression: Expression) { + const expressionPrecedence = getExpressionPrecedence(expression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + return expressionPrecedence > commaPrecedence + ? expression + : createParen(expression, /*location*/ expression); + } + + export function parenthesizeExpressionForExpressionStatement(expression: Expression) { + if (isCallExpression(expression)) { + const callee = expression.expression; + if (callee.kind === SyntaxKind.FunctionExpression + || callee.kind === SyntaxKind.ArrowFunction) { + const clone = cloneNode(expression, expression, expression.flags, expression.parent, expression); + clone.expression = createParen(callee, /*location*/ callee); + return clone; + } + } + else if (getLeftmostExpression(expression).kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(expression, /*location*/ expression); } - return createParen(operand); + return expression; + } + + function getLeftmostExpression(node: Expression): Expression { + while (true) { + switch (node.kind) { + case SyntaxKind.PostfixUnaryExpression: + node = (node).operand; + continue; + + case SyntaxKind.BinaryExpression: + node = (node).left; + continue; + + case SyntaxKind.ConditionalExpression: + node = (node).condition; + continue; + + case SyntaxKind.CallExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + node = (node).expression; + continue; + } + + return node; + } + } + + export function skipParentheses(node: Expression): Expression { + while (node.kind === SyntaxKind.ParenthesizedExpression + || node.kind === SyntaxKind.TypeAssertionExpression + || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; + } + + return node; } /** @@ -1004,7 +1236,8 @@ namespace ts { * @param node The expression node. */ function skipAssertions(node: Expression) { - while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { + while (node.kind === SyntaxKind.TypeAssertionExpression + || node.kind === SyntaxKind.AsExpression) { node = (node).expression; } @@ -1012,7 +1245,7 @@ namespace ts { } export function startOnNewLine(node: T): T { - (node).startsOnNewLine = true; + node.startsOnNewLine = true; return node; } @@ -1034,6 +1267,16 @@ namespace ts { return node; } + export function setMultiLine(node: T, multiLine: boolean): T { + node.multiLine = multiLine; + return node; + } + + export function setHasTrailingComma(nodes: NodeArray, hasTrailingComma: boolean): NodeArray { + nodes.hasTrailingComma = hasTrailingComma; + return nodes; + } + export function getSynthesizedNode(node: T): T { return nodeIsSynthesized(node) ? node : cloneNode(node, /*location*/ undefined, node.flags, /*parent*/ undefined, /*original*/ node); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b21f9919f45..c4813ac9932 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4069,6 +4069,10 @@ namespace ts { function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { const node = createNode(SyntaxKind.Block); if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 8a0725f5236..8b3a69b104a 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -119,13 +119,31 @@ const _super = (function (geti, seti) { const transformers = getTransformers(compilerOptions).concat(initializePrinter); const writer = createTextWriter(newLine); - const { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer; + const { + write, + writeTextOfNode, + writeLine, + increaseIndent, + decreaseIndent + } = writer; const sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter(); - const { emitStart, emitEnd, emitPos } = sourceMap; + const { + emitStart, + emitEnd, + emitPos + } = sourceMap; const comments = createCommentWriter(host, writer, sourceMap); - const { emitDetachedComments, emitLeadingComments, emitTrailingComments, getLeadingCommentsToEmit, getTrailingCommentsToEmit } = comments; + const { + getLeadingComments, + getLeadingCommentsOfPosition, + getTrailingComments, + getTrailingCommentsOfPosition, + emitLeadingComments, + emitTrailingComments, + emitDetachedComments + } = comments; let context: TransformationContext; let startLexicalEnvironment: () => void; @@ -233,14 +251,22 @@ const _super = (function (geti, seti) { } function emit(node: Node) { + emitWithWorker(node, emitWorker); + } + + function emitExpression(node: Expression) { + emitWithWorker(node, emitExpressionWorker); + } + + function emitWithWorker(node: Node, emitWorker: (node: Node) => void) { if (node) { const adviseOnEmit = isEmitNotificationEnabled(node); if (adviseOnEmit && onBeforeEmitNode) { onBeforeEmitNode(node); } - const leadingComments = getLeadingCommentsToEmit(node); - const trailingComments = getTrailingCommentsToEmit(node); + const leadingComments = getLeadingComments(node, getNotEmittedParent); + const trailingComments = getTrailingComments(node, getNotEmittedParent); emitLeadingComments(node, leadingComments); emitStart(node); emitWorker(node); @@ -253,7 +279,18 @@ const _super = (function (geti, seti) { } } - function emitWorker(node: Node) { + function getNotEmittedParent(node: Node): Node { + if (getNodeEmitFlags(node) & NodeEmitFlags.EmitCommentsOfNotEmittedParent) { + const parent = getOriginalNode(node).parent; + if (getNodeEmitFlags(parent) & NodeEmitFlags.IsNotEmittedNode) { + return parent; + } + } + + return undefined; + } + + function emitWorker(node: Node): void { const kind = node.kind; switch (kind) { // Pseudo-literals @@ -358,7 +395,7 @@ const _super = (function (geti, seti) { case SyntaxKind.ExpressionWithTypeArguments: return emitExpressionWithTypeArguments(node); case SyntaxKind.ThisType: - return write("this"); + return emitThisType(node); case SyntaxKind.StringLiteralType: return emitLiteral(node); @@ -374,7 +411,7 @@ const _super = (function (geti, seti) { case SyntaxKind.TemplateSpan: return emitTemplateSpan(node); case SyntaxKind.SemicolonClassElement: - return write(";"); + return emitSemicolonClassElement(node); // Statements case SyntaxKind.Block: @@ -382,7 +419,7 @@ const _super = (function (geti, seti) { case SyntaxKind.VariableStatement: return emitVariableStatement(node); case SyntaxKind.EmptyStatement: - return write(";"); + return emitEmptyStatement(node); case SyntaxKind.ExpressionStatement: return emitExpressionStatement(node); case SyntaxKind.IfStatement: @@ -501,23 +538,11 @@ const _super = (function (geti, seti) { // JSDoc nodes (ignored) } - if (isExpressionKind(kind)) { + if (isExpression(node)) { return emitExpressionWorker(node); } } - function emitExpression(node: Expression) { - if (node) { - const leadingComments = getLeadingCommentsToEmit(node); - const trailingComments = getTrailingCommentsToEmit(node); - emitLeadingComments(node, leadingComments); - emitStart(node); - emitExpressionWorker(node); - emitEnd(node); - emitTrailingComments(node, trailingComments); - } - } - function emitExpressionWorker(node: Node) { const kind = node.kind; if (isExpressionSubstitutionEnabled(node) && tryEmitSubstitute(node, expressionSubstitution)) { @@ -641,7 +666,7 @@ const _super = (function (geti, seti) { const text = temporaryVariables[nodeId] || (temporaryVariables[nodeId] = makeTempVariableName(tempKindToFlags(node.tempKind))); write(text); } - else if (nodeIsSynthesized(node)) { + else if (nodeIsSynthesized(node) || !node.parent) { if (getNodeEmitFlags(node) & NodeEmitFlags.UMDDefine) { writeLines(umdHelper); } @@ -741,13 +766,13 @@ const _super = (function (geti, seti) { emitModifiers(node, node.modifiers); writeIfPresent(node.asteriskToken, "*"); emit(node.name); - emitSignatureAndBody(node); + emitSignatureAndBody(node, emitSignatureHead); } function emitConstructor(node: ConstructorDeclaration) { emitModifiers(node, node.modifiers); write("constructor"); - emitSignatureAndBody(node); + emitSignatureAndBody(node, emitSignatureHead); } function emitAccessorDeclaration(node: AccessorDeclaration) { @@ -755,7 +780,7 @@ const _super = (function (geti, seti) { emitModifiers(node, node.modifiers); write(node.kind === SyntaxKind.GetAccessor ? "get " : "set "); emit(node.name); - emitSignatureAndBody(node); + emitSignatureAndBody(node, emitSignatureHead); } function emitCallSignature(node: CallSignatureDeclaration) { @@ -785,6 +810,10 @@ const _super = (function (geti, seti) { write(";"); } + function emitSemicolonClassElement(node: SemicolonClassElement) { + write(";"); + } + // // Types // @@ -851,6 +880,10 @@ const _super = (function (geti, seti) { write(")"); } + function emitThisType(node: ThisTypeNode) { + write("this"); + } + // // Binding patterns // @@ -896,7 +929,7 @@ const _super = (function (geti, seti) { write("[]"); } else { - const preferNewLine = getNodeEmitFlags(node) & NodeEmitFlags.MultiLine ? ListFormat.PreferNewLine : ListFormat.None; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine); } } @@ -907,7 +940,7 @@ const _super = (function (geti, seti) { write("{}"); } else { - const preferNewLine = getNodeEmitFlags(node) & NodeEmitFlags.MultiLine ? ListFormat.PreferNewLine : ListFormat.None; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; const allowTrailingComma = languageVersion >= ScriptTarget.ES5 ? ListFormat.AllowTrailingComma : ListFormat.None; emitList(node, properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); } @@ -998,27 +1031,8 @@ const _super = (function (geti, seti) { function emitArrowFunction(node: ArrowFunction) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - const body = node.body; - if (isBlock(body)) { - const savedTempFlags = tempFlags; - tempFlags = 0; - startLexicalEnvironment(); - emitArrowFunctionHead(node); - write(" {"); + emitSignatureAndBody(node, emitArrowFunctionHead); - const startingLine = writer.getLine(); - emitBlockFunctionBody(node, body); - - const endingLine = writer.getLine(); - emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine); - tempFlags = savedTempFlags; - write("}"); - } - else { - emitArrowFunctionHead(node); - write(" "); - emitExpression(body); - } } function emitArrowFunctionHead(node: ArrowFunction) { @@ -1186,6 +1200,10 @@ const _super = (function (geti, seti) { write(";"); } + function emitEmptyStatement(node: EmptyStatement) { + write(";"); + } + function emitExpressionStatement(node: ExpressionStatement) { emitExpression(node.expression); write(";"); @@ -1353,25 +1371,27 @@ const _super = (function (geti, seti) { emitModifiers(node, node.modifiers); write(node.asteriskToken ? "function* " : "function "); emit(node.name); - emitSignatureAndBody(node); + emitSignatureAndBody(node, emitSignatureHead); } - function emitSignatureAndBody(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { const body = node.body; if (body) { - const savedTempFlags = tempFlags; - tempFlags = 0; - startLexicalEnvironment(); - emitSignatureHead(node); - write(" {"); - - const startingLine = writer.getLine(); - emitBlockFunctionBody(node, body); - - const endingLine = writer.getLine(); - emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine); - write("}"); - tempFlags = savedTempFlags; + if (isBlock(body)) { + const savedTempFlags = tempFlags; + tempFlags = 0; + startLexicalEnvironment(); + emitSignatureHead(node); + write(" {"); + emitBlockFunctionBody(node, body); + write("}"); + tempFlags = savedTempFlags; + } + else { + emitSignatureHead(node); + write(" "); + emitExpression(body); + } } else { emitSignatureHead(node); @@ -1388,37 +1408,49 @@ const _super = (function (geti, seti) { function shouldEmitBlockFunctionBodyOnSingleLine(parentNode: Node, body: Block) { const originalNode = getOriginalNode(parentNode); - if (isFunctionLike(originalNode) && !nodeIsSynthesized(originalNode) && rangeEndIsOnSameLineAsRangeStart(originalNode.body, originalNode.body)) { - for (const statement of body.statements) { - if (synthesizedNodeStartsOnNewLine(statement)) { - return false; + if (isFunctionLike(originalNode) && !nodeIsSynthesized(originalNode)) { + const body = originalNode.body; + if (isBlock(body)) { + if (rangeEndIsOnSameLineAsRangeStart(body, body)) { + for (const statement of body.statements) { + if (synthesizedNodeStartsOnNewLine(statement)) { + return false; + } + } + + return true; } } - - if (originalNode.kind === SyntaxKind.ArrowFunction && !rangeEndIsOnSameLineAsRangeStart((originalNode).equalsGreaterThanToken, originalNode.body)) { - return false; + else { + return rangeEndIsOnSameLineAsRangeStart((originalNode).equalsGreaterThanToken, originalNode.body); } - - return true; } return false; } function emitBlockFunctionBody(parentNode: Node, body: Block) { - // Emit all the prologue directives (like "use strict"). + const startingLine = writer.getLine(); increaseIndent(); - const statements = body.statements; - const statementOffset = emitPrologueDirectives(statements, /*startWithNewLine*/ true); + emitDetachedComments(body.statements); + + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); const helpersEmitted = emitHelpers(body); - decreaseIndent(); if (statementOffset === 0 && !helpersEmitted && shouldEmitBlockFunctionBodyOnSingleLine(parentNode, body)) { - emitList(body, statements, ListFormat.SingleLineFunctionBodyStatements); + decreaseIndent(); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); } else { - emitList(body, statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); } + + const endingLine = writer.getLine(); + emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine); + emitLeadingComments(collapseTextRange(body.statements, TextRangeCollapse.CollapseToEnd)); + decreaseIndent(); } function emitClassDeclaration(node: ClassDeclaration) { @@ -1688,6 +1720,8 @@ const _super = (function (geti, seti) { write("case "); emitExpression(node.expression); write(":"); + + debugger; emitCaseOrDefaultClauseStatements(node, node.statements); } @@ -2051,8 +2085,10 @@ const _super = (function (geti, seti) { } else { // Write the opening line terminator or leading whitespace. + let shouldEmitInterveningComments = true; if (shouldWriteLeadingLineTerminator(parentNode, children, format)) { writeLine(); + shouldEmitInterveningComments = false; } else if (format & ListFormat.SpaceBetweenBraces) { write(" "); @@ -2076,12 +2112,20 @@ const _super = (function (geti, seti) { // Write either a line terminator or whitespace to separate the elements. if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) { writeLine(); + shouldEmitInterveningComments = false; } else if (previousSibling) { write(" "); } } + if (shouldEmitInterveningComments) { + emitLeadingComments(child, getTrailingCommentsOfPosition(child.pos)); + } + else { + shouldEmitInterveningComments = true; + } + // Emit this child. emit(child); @@ -2175,7 +2219,7 @@ const _super = (function (geti, seti) { return true; } else if (format & ListFormat.PreserveLines) { - if (getNodeEmitFlags(parentNode) & NodeEmitFlags.MultiLine) { + if (format & ListFormat.PreferNewLine) { return true; } @@ -2217,10 +2261,10 @@ const _super = (function (geti, seti) { function shouldWriteClosingLineTerminator(parentNode: Node, children: NodeArray, format: ListFormat) { if (format & ListFormat.MultiLine) { - return true; + return (format & ListFormat.NoTrailingNewLine) === 0; } else if (format & ListFormat.PreserveLines) { - if (getNodeEmitFlags(parentNode) & NodeEmitFlags.MultiLine) { + if (format & ListFormat.PreferNewLine) { return true; } @@ -2242,7 +2286,7 @@ const _super = (function (geti, seti) { function synthesizedNodeStartsOnNewLine(node: Node, format?: ListFormat) { if (nodeIsSynthesized(node)) { - const startsOnNewLine = (node).startsOnNewLine; + const startsOnNewLine = node.startsOnNewLine; if (startsOnNewLine === undefined) { return (format & ListFormat.PreferNewLine) !== 0; } @@ -2274,8 +2318,12 @@ const _super = (function (geti, seti) { } function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); + // Always use a newline for synthesized code if the synthesizer desires it. - if (synthesizedNodeStartsOnNewLine(node2)) { + if (node2.startsOnNewLine) { return true; } @@ -2285,6 +2333,14 @@ const _super = (function (geti, seti) { && !rangeEndIsOnSameLineAsRangeStart(node1, node2); } + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node).expression; + } + + return node; + } + function getTextOfNode(node: Node, includeTrivia?: boolean) { if (nodeIsSynthesized(node) && (isLiteralExpression(node) || isIdentifier(node))) { return node.text; @@ -2304,9 +2360,9 @@ const _super = (function (geti, seti) { } function isSingleLineEmptyBlock(block: Block) { - return (getNodeEmitFlags(block) & NodeEmitFlags.MultiLine) === 0 && - block.statements.length === 0 && - rangeEndIsOnSameLineAsRangeStart(block, block); + return !block.multiLine + && block.statements.length === 0 + && rangeEndIsOnSameLineAsRangeStart(block, block); } function tempKindToFlags(kind: TempVariableKind) { @@ -2361,7 +2417,7 @@ const _super = (function (geti, seti) { function createBracketsMap() { const brackets: string[][] = []; brackets[ListFormat.Braces] = ["{", "}"]; - brackets[ListFormat.Parenthesis] = ["(", ")"]; + brackets[ListFormat.Parenthesis] = ["(",")"]; brackets[ListFormat.AngleBrackets] = ["<", ">"]; brackets[ListFormat.SquareBrackets] = ["[", "]"]; return brackets; @@ -2407,6 +2463,7 @@ const _super = (function (geti, seti) { // Other PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes. + NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list. // Precomputed Formats TypeLiteralMembers = MultiLine | Indented, @@ -2424,7 +2481,7 @@ const _super = (function (geti, seti) { MultiLineBlockStatements = Indented | MultiLine, VariableDeclarationList = CommaDelimited | SingleLine, SingleLineFunctionBodyStatements = SingleLine | SpaceBetweenBraces, - MultiLineFunctionBodyStatements = MultiLine | Indented, + MultiLineFunctionBodyStatements = MultiLine, ClassHeritageClauses = SingleLine, ClassMembers = Indented | MultiLine, InterfaceMembers = Indented | MultiLine, @@ -2433,9 +2490,9 @@ const _super = (function (geti, seti) { NamedImportsOrExportsElements = CommaDelimited | AllowTrailingComma | SingleLine | SpaceBetweenBraces, JsxElementChildren = SingleLine, JsxElementAttributes = SingleLine, - CaseOrDefaultClauseStatements = Indented | MultiLine, + CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty, HeritageClauseTypes = CommaDelimited | SingleLine, - SourceFileStatements = MultiLine, + SourceFileStatements = MultiLine | NoTrailingNewLine, Decorators = MultiLine | Optional, TypeArguments = CommaDelimited | SingleLine | Indented | AngleBrackets | Optional, TypeParameters = CommaDelimited | SingleLine | Indented | AngleBrackets | Optional, diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4a3d0cbc164..089f53cf7c8 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -968,7 +968,7 @@ namespace ts { const start = new Date().getTime(); - const emitResult = emitFiles( + const emitResult = (options.experimentalTransforms ? printFiles : emitFiles)( emitResolver, getEmitHost(writeFileCallback), sourceFile); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 68c8f5bc794..2ce54b9b9ed 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -433,9 +433,7 @@ namespace ts { /* @internal */ export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean): number { - // Using ! with a greater than test is a fast way of testing the following conditions: - // pos === undefined || pos === null || isNaN(pos) || pos < 0; - if (!(pos >= 0)) { + if (positionIsSynthesized(pos)) { return pos; } @@ -642,6 +640,7 @@ namespace ts { pos++; } } + if (collecting) { if (!result) { result = []; diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 5e453b1b101..3866e4f9566 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -310,13 +310,13 @@ namespace ts { function emitStart(range: TextRange) { emitPos(getStartPos(range)); - if ((range).disableSourceMap) { + if (range.disableSourceMap) { disable(); } } function emitEnd(range: TextRange, stopOverridingEnd?: boolean) { - if ((range).disableSourceMap) { + if (range.disableSourceMap) { enable(); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index b2c1007b716..be220f07ed8 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -84,8 +84,12 @@ namespace ts { hoistFunctionDeclaration, startLexicalEnvironment, endLexicalEnvironment, + identifierSubstitution: node => node, + expressionSubstitution: node => node, enableExpressionSubstitution, isExpressionSubstitutionEnabled, + onBeforeEmitNode: node => { }, + onAfterEmitNode: node => { }, enableEmitNotification, isEmitNotificationEnabled, }; @@ -148,8 +152,9 @@ namespace ts { /** * Sets flags that control emit behavior of a node. */ - function setNodeEmitFlags(node: Node, flags: NodeEmitFlags) { + function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { nodeEmitFlags[getNodeId(node)] = flags; + return node; } /** diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index c21d81ac4d5..05b2350226b 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -50,7 +50,7 @@ namespace ts { function emitAssignment(name: Identifier, value: Expression, location: TextRange) { const expression = createAssignment(name, value, location); if (isSimpleExpression(value)) { - (expression).disableSourceMap = true; + expression.disableSourceMap = true; } aggregateTransformFlags(expression); @@ -82,7 +82,7 @@ namespace ts { function emitAssignment(name: Identifier, value: Expression, location: TextRange) { const declaration = createVariableDeclaration(name, value, location); if (isSimpleExpression(value)) { - (declaration).disableSourceMap = true; + declaration.disableSourceMap = true; } aggregateTransformFlags(declaration); @@ -117,7 +117,7 @@ namespace ts { } if (isSimpleExpression(value)) { - (declaration).disableSourceMap = true; + declaration.disableSourceMap = true; } declaration.original = original; @@ -169,7 +169,7 @@ namespace ts { function emitPendingAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { const expression = createAssignment(name, value, location); if (isSimpleExpression(value)) { - (expression).disableSourceMap = true; + expression.disableSourceMap = true; } expression.original = original; @@ -199,7 +199,7 @@ namespace ts { function emitDestructuringAssignment(bindingTarget: Expression | ShorthandPropertyAssignment, value: Expression, location: TextRange) { // When emitting target = value use source map node to highlight, including any temporary assignments needed for this let target: Expression; - if (isShortHandPropertyAssignment(bindingTarget)) { + if (isShorthandPropertyAssignment(bindingTarget)) { const initializer = visitor ? visitNode(bindingTarget.objectAssignmentInitializer, visitor, isExpression) : bindingTarget.objectAssignmentInitializer; diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index d5c5f9566db..613ee5031f3 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -3,17 +3,74 @@ /*@internal*/ namespace ts { - // TODO(rbuckton): ES6->ES5 transformer export function transformES6(context: TransformationContext) { + const { + getGeneratedNameForNode, + makeUniqueName, + startLexicalEnvironment, + endLexicalEnvironment, + hoistVariableDeclaration, + setNodeEmitFlags, + enableExpressionSubstitution, + enableEmitNotification, + } = context; + + const resolver = context.getEmitResolver(); + const previousIdentifierSubstitution = context.identifierSubstitution; + const previousExpressionSubstitution = context.expressionSubstitution; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; + context.enableExpressionSubstitution(SyntaxKind.Identifier); + context.identifierSubstitution = substituteIdentifier; + context.expressionSubstitution = substituteExpression; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; + + let currentSourceFile: SourceFile; + let currentParent: Node; + let currentNode: Node; + let enclosingBlockScopeContainer: Node; + let enclosingBlockScopeContainerParent: Node; + let containingFunction: FunctionLikeDeclaration; + let containingNonArrowFunction: FunctionLikeDeclaration; + let combinedNodeFlags: NodeFlags; + + // This stack is is used to support substitutions when printing nodes. + let hasEnabledExpressionSubstitutionForCapturedThis = false; + let containingFunctionStack: FunctionLikeDeclaration[]; + return transformSourceFile; function transformSourceFile(node: SourceFile) { + currentSourceFile = node; return visitEachChild(node, visitor, context); } function visitor(node: Node): Node { + const savedContainingFunction = containingFunction; + const savedContainingNonArrowFunction = containingNonArrowFunction; + const savedCurrentParent = currentParent; + const savedCurrentNode = currentNode; + const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer; + const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent; + const savedCombinedNodeFlags = combinedNodeFlags; + + onBeforeVisitNode(node); + node = visitorWorker(node); + + containingFunction = savedContainingFunction; + containingNonArrowFunction = savedContainingNonArrowFunction; + currentParent = savedCurrentParent; + currentNode = savedCurrentNode; + enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer; + enclosingBlockScopeContainerParent = savedEnclosingBlockScopeContainerParent; + combinedNodeFlags = savedCombinedNodeFlags; + return node; + } + + function visitorWorker(node: Node): Node { if (node.transformFlags & TransformFlags.ES6) { - return visitorWorker(node); + return visitJavaScript(node); } else if (node.transformFlags & TransformFlags.ContainsES6) { return visitEachChild(node, visitor, context); @@ -23,8 +80,1460 @@ namespace ts { } } - function visitorWorker(node: Node): Node { + function visitJavaScript(node: Node): Node { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + return visitClassDeclaration(node); + + case SyntaxKind.ClassExpression: + return visitClassExpression(node); + + case SyntaxKind.Parameter: + return visitParameter(node); + + case SyntaxKind.FunctionDeclaration: + return visitFunctionDeclaration(node); + + case SyntaxKind.ArrowFunction: + return visitArrowFunction(node); + + case SyntaxKind.FunctionExpression: + return visitFunctionExpression(node); + + case SyntaxKind.VariableDeclaration: + return visitVariableDeclaration(node); + + case SyntaxKind.VariableDeclarationList: + return visitVariableDeclarationList(node); + + case SyntaxKind.LabeledStatement: + return visitLabeledStatement(node); + + case SyntaxKind.DoStatement: + return visitDoStatement(node); + + case SyntaxKind.WhileStatement: + return visitWhileStatement(node); + + case SyntaxKind.ForStatement: + return visitForStatement(node); + + case SyntaxKind.ForInStatement: + return visitForInStatement(node); + + case SyntaxKind.ForOfStatement: + return visitForOfStatement(node); + + case SyntaxKind.ObjectLiteralExpression: + return visitObjectLiteralExpression(node); + + case SyntaxKind.ShorthandPropertyAssignment: + return visitShorthandPropertyAssignment(node); + + case SyntaxKind.ArrayLiteralExpression: + return visitArrayLiteralExpression(node); + + case SyntaxKind.CallExpression: + return visitCallExpression(node); + + case SyntaxKind.NewExpression: + return visitNewExpression(node); + + case SyntaxKind.BinaryExpression: + return visitBinaryExpression(node); + + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return visitTemplateLiteral(node); + + case SyntaxKind.TaggedTemplateExpression: + return visitTaggedTemplateExpression(node); + + case SyntaxKind.TemplateExpression: + return visitTemplateExpression(node); + + case SyntaxKind.SuperKeyword: + return visitSuperKeyword(node); + + case SyntaxKind.MethodDeclaration: + return visitMethodDeclaration(node); + + case SyntaxKind.SourceFile: + return visitSourceFileNode(node); + } + + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); + } + + function onBeforeVisitNode(node: Node) { + const currentGrandparent = currentParent; + currentParent = currentNode; + currentNode = node; + + combinedNodeFlags = combineNodeFlags(currentNode, currentParent, combinedNodeFlags); + + if (currentParent) { + if (isBlockScope(currentParent, currentGrandparent)) { + enclosingBlockScopeContainer = currentParent; + enclosingBlockScopeContainerParent = currentGrandparent; + } + + switch (currentParent.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + containingNonArrowFunction = currentParent; + containingFunction = currentParent; + break; + + case SyntaxKind.ArrowFunction: + containingFunction = currentParent; + break; + } + } + } + + function visitClassDeclaration(node: ClassDeclaration): Statement { + return startOnNewLine( + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + getDeclarationName(node), + transformClassLikeDeclarationToExpression(node) + ) + ]), + node + ) + ); + } + + function visitClassExpression(node: ClassExpression): Expression { + return transformClassLikeDeclarationToExpression(node); + } + + function transformClassLikeDeclarationToExpression(node: ClassExpression | ClassDeclaration): Expression { + const baseTypeNode = getClassExtendsHeritageClauseElement(node); + return createParen( + createCall( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + baseTypeNode ? [createParameter("_super")] : [], + transformClassBody(node, baseTypeNode !== undefined) + ), + baseTypeNode ? [visitNode(baseTypeNode.expression, visitor, isExpression)] : [] + ) + ); + } + + function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): Block { + const statements: Statement[] = []; + startLexicalEnvironment(); + addExtendsHelperIfNeeded(statements, node, hasExtendsClause); + addConstructor(statements, node, hasExtendsClause); + addClassMembers(statements, node); + addLine(statements, createReturn(getDeclarationName(node))); + addLines(statements, endLexicalEnvironment()); + return createBlock(statements); + } + + function addExtendsHelperIfNeeded(classStatements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { + if (hasExtendsClause) { + addLine(classStatements, + createStatement( + createExtendsHelper(getDeclarationName(node)) + ) + ); + } + } + + function addConstructor(classStatements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { + const constructor = getFirstConstructorWithBody(node); + const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, hasExtendsClause); + addLine(classStatements, + createFunctionDeclaration( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + getDeclarationName(node), + transformConstructorParameters(constructor, hasSynthesizedSuper), + transformConstructorBody(constructor, hasExtendsClause, hasSynthesizedSuper), + /*location*/ constructor + ) + ); + } + + function transformConstructorParameters(constructor: ConstructorDeclaration, hasSynthesizedSuper: boolean): ParameterDeclaration[] { + if (constructor && !hasSynthesizedSuper) { + return visitNodes(constructor.parameters, visitor, isParameter); + } + + return []; + } + + function transformConstructorBody(constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { + const statements: Statement[] = []; + startLexicalEnvironment(); + if (constructor) { + addCaptureThisForNodeIfNeeded(statements, constructor); + addDefaultValueAssignments(statements, constructor); + addRestParameter(statements, constructor, hasSynthesizedSuper); + } + + addDefaultSuperCall(statements, constructor, hasExtendsClause, hasSynthesizedSuper); + + if (constructor) { + addNodes(statements, visitNodes(constructor.body.statements, visitor, isStatement, hasSynthesizedSuper ? 1 : 0)); + } + + addLines(statements, endLexicalEnvironment()); + return createBlock(statements, /*location*/ constructor && constructor.body); + } + + function addDefaultSuperCall(statements: Statement[], constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { + if (constructor ? hasSynthesizedSuper : hasExtendsClause) { + addLine(statements, + createStatement( + createFunctionApply( + createIdentifier("_super"), + createThis(), + createIdentifier("arguments") + ) + ) + ); + } + } + + function visitParameter(node: ParameterDeclaration): ParameterDeclaration { + if (isBindingPattern(node.name)) { + // Binding patterns are converted into a generated name and are + // evaluated inside the function body. + return createParameter( + getGeneratedNameForNode(node), + /*initializer*/ undefined, + /*location*/ node + ); + } + else if (node.initializer) { + // Initializers are elided + return createParameter( + node.name, + /*initializer*/ undefined, + /*location*/ node + ); + } + else if (node.dotDotDotToken) { + // rest parameters are elided + return undefined; + } + else { + return node; + } + } + + function shouldAddDefaultValueAssignments(node: FunctionLikeDeclaration): boolean { + return (node.transformFlags & TransformFlags.ContainsDefaultValueAssignments) !== 0; + } + + function addDefaultValueAssignments(statements: Statement[], node: FunctionLikeDeclaration): void { + if (!shouldAddDefaultValueAssignments(node)) { + return; + } + + for (const parameter of node.parameters) { + const { name, initializer, dotDotDotToken } = parameter; + + // A rest parameter cannot have a binding pattern or an initializer, + // so let's just ignore it. + if (dotDotDotToken) { + continue; + } + + if (isBindingPattern(name)) { + addDefaultValueAssignmentForBindingPattern(statements, parameter, name, initializer); + } + else if (initializer) { + addDefaultValueAssignmentForInitializer(statements, parameter, name, initializer); + } + } + } + + function addDefaultValueAssignmentForBindingPattern(statements: Statement[], parameter: ParameterDeclaration, name: BindingPattern, initializer: Expression): void { + const temp = getGeneratedNameForNode(parameter); + + // In cases where a binding pattern is simply '[]' or '{}', + // we usually don't want to emit a var declaration; however, in the presence + // of an initializer, we must emit that expression to preserve side effects. + if (name.elements.length > 0) { + addLine(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList( + transformParameterBindingElements(parameter, temp) + ) + ) + ); + } + else if (initializer) { + addLine(statements, + createStatement( + createAssignment( + temp, + visitNode(initializer, visitor, isExpression) + ) + ) + ); + } + } + + function transformParameterBindingElements(parameter: ParameterDeclaration, name: Identifier) { + return flattenParameterDestructuring(parameter, name, visitor); + } + + function addDefaultValueAssignmentForInitializer(statements: Statement[], parameter: ParameterDeclaration, name: Identifier, initializer: Expression): void { + addLine(statements, + createIf( + createStrictEquality( + getSynthesizedNode(name), + createVoidZero() + ), + setNodeEmitFlags( + createBlock([ + createStatement( + createAssignment( + getSynthesizedNode(name), + visitNode(initializer, visitor, isExpression) + ) + ) + ]), + NodeEmitFlags.SingleLine + ) + ) + ); + } + + function shouldAddRestParameter(node: ParameterDeclaration) { + return node && node.dotDotDotToken; + } + + function addRestParameter(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper?: boolean): void { + if (inConstructorWithSynthesizedSuper) { + return; + } + + const parameter = lastOrUndefined(node.parameters); + if (!shouldAddRestParameter(parameter)) { + return; + } + + const name = getSynthesizedNode(parameter.name); + const restIndex = node.parameters.length - 1; + const temp = createLoopVariable(); + + // var param = []; + addLine(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + name, + createArrayLiteral([]) + ) + ]) + ) + ); + + // for (var _i = restIndex; _i < arguments.length; _i++) { + // param[_i - restIndex] = arguments[_i]; + // } + addLine(statements, + createFor( + createVariableDeclarationList([ + createVariableDeclaration(temp, createLiteral(restIndex)) + ]), + createLessThan( + temp, + createPropertyAccess(createIdentifier("arguments"), "length") + ), + createPostfixIncrement(temp), + createBlock([ + startOnNewLine( + createStatement( + createAssignment( + createElementAccess( + name, + restIndex === 0 ? temp : createSubtract(temp, createLiteral(restIndex)) + ), + createElementAccess(createIdentifier("arguments"), temp) + ) + ) + ) + ]) + ) + ); + } + + function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): void { + if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) { + enableExpressionSubstitutionForCapturedThis(); + + addLine(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + "_this", + createThis() + ) + ]) + ) + ); + } + } + + function addClassMembers(classStatements: Statement[], node: ClassExpression | ClassDeclaration): void { + for (const member of node.members) { + switch (member.kind) { + case SyntaxKind.SemicolonClassElement: + addLine(classStatements, transformSemicolonClassElementToStatement(member)); + break; + + case SyntaxKind.MethodDeclaration: + addLine(classStatements, transformClassMethodDeclarationToStatement(node, member)); + break; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const accessors = getAllAccessorDeclarations(node.members, member); + if (member === accessors.firstAccessor) { + const receiver = getClassMemberPrefix(node, member); + addLine(classStatements, transformAccessorsToStatement(receiver, accessors)); + } + + break; + + case SyntaxKind.Constructor: + // Constructors are handled in visitClassExpression/visitClassDeclaration + break; + + default: + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); + break; + } + } + } + + function transformSemicolonClassElementToStatement(member: SemicolonClassElement) { + return createEmptyStatement(member); + } + + function transformClassMethodDeclarationToStatement(node: ClassExpression | ClassDeclaration, member: MethodDeclaration) { + const savedContainingFunction = containingFunction; + const savedContainingNonArrowFunction = containingNonArrowFunction; + containingFunction = containingNonArrowFunction = member; + const statement = createStatement( + createAssignment( + createMemberAccessForPropertyName( + getClassMemberPrefix(node, member), + visitNode(member.name, visitor, isPropertyName) + ), + transformFunctionLikeToExpression(member) + ), + /*location*/ member + ); + + containingFunction = savedContainingFunction; + containingNonArrowFunction = savedContainingNonArrowFunction; + return statement; + } + + function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations): Statement { + const savedContainingFunction = containingFunction; + const savedContainingNonArrowFunction = containingNonArrowFunction; + containingFunction = containingNonArrowFunction = accessors.firstAccessor; + const statement = createStatement( + transformAccessorsToExpression(receiver, accessors) + ); + containingFunction = savedContainingFunction; + containingNonArrowFunction = savedContainingNonArrowFunction; + return statement; + } + + function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations): Expression { + return createObjectDefineProperty( + receiver, + createExpressionForPropertyName( + visitNode(firstAccessor.name, visitor, isPropertyName), + /*location*/ firstAccessor.name + ), + { + get: getAccessor && transformFunctionLikeToExpression(getAccessor, /*location*/ getAccessor), + set: setAccessor && transformFunctionLikeToExpression(setAccessor, /*location*/ setAccessor), + enumerable: true, + configurable: true + }, + /*preferNewLine*/ true, + /*location*/ firstAccessor + ); + } + + function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location?: TextRange, name?: Identifier): FunctionExpression { + return setOriginalNode( + createFunctionExpression( + /*asteriskToken*/ undefined, + name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + location + ), + node + ); + } + + function visitArrowFunction(node: ArrowFunction) { + if (node.transformFlags & TransformFlags.ContainsLexicalThis) { + enableExpressionSubstitutionForCapturedThis(); + } + + return transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined); + } + + function visitFunctionExpression(node: FunctionExpression): Expression { + return transformFunctionLikeToExpression(node, /*location*/ node, node.name); + } + + function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { + return setOriginalNode( + createFunctionDeclaration( + /*modifiers*/ undefined, + node.asteriskToken, // TODO(rbuckton): downlevel support for generators + node.name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + /*location*/ node + ), + node + ); + } + + function transformFunctionBody(node: FunctionLikeDeclaration) { + const statements: Statement[] = []; + startLexicalEnvironment(); + addCaptureThisForNodeIfNeeded(statements, node); + addDefaultValueAssignments(statements, node); + addRestParameter(statements, node); + + const body = node.body; + if (isBlock(body)) { + addNodes(statements, visitNodes(body.statements, visitor, isStatement)); + } + else { + const expression = visitNode(body, visitor, isExpression); + if (expression) { + addNode(statements, createReturn(expression, /*location*/ body)); + } + } + + addLines(statements, endLexicalEnvironment()); + return createBlock(statements, node.body); + } + + function visitBinaryExpression(node: BinaryExpression): Expression { + // If we are here it is because this is a destructuring assignment. + // TODO(rbuckton): Determine whether we need to save the value. + return flattenDestructuringAssignment(node, /*needsValue*/ true, hoistVariableDeclaration, visitor); + } + + function visitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList { + return setOriginalNode( + createVariableDeclarationList( + flattenNodes(map(node.declarations, visitVariableDeclaration)), + /*location*/ node + ), + node + ); + } + + function visitVariableDeclaration(node: VariableDeclaration): OneOrMore { + const name = node.name; + if (isBindingPattern(name)) { + return createNodeArrayNode( + flattenVariableDestructuring(node, /*value*/ undefined, visitor) + ); + } + else { + let initializer = node.initializer; + // For binding pattern names that lack initializer there is no point to emit + // explicit initializer since downlevel codegen for destructuring will fail + // in the absence of initializer so all binding elements will say uninitialized + if (!initializer) { + const original = getOriginalNode(node); + if (isVariableDeclaration(original)) { + // Nested let bindings might need to be initialized explicitly to preserve + // ES6 semantic: + // + // { let x = 1; } + // { let x; } // x here should be undefined. not 1 + // + // Top level bindings never collide with anything and thus don't require + // explicit initialization. As for nested let bindings there are two cases: + // + // - Nested let bindings that were not renamed definitely should be + // initialized explicitly: + // + // { let x = 1; } + // { let x; if (some-condition) { x = 1}; if (x) { /*1*/ } } + // + // Without explicit initialization code in /*1*/ can be executed even if + // some-condition is evaluated to false. + // + // - Renaming introduces fresh name that should not collide with any + // existing names, however renamed bindings sometimes also should be + // explicitly initialized. One particular case: non-captured binding + // declared inside loop body (but not in loop initializer): + // + // let x; + // for (;;) { + // let x; + // } + // + // In downlevel codegen inner 'x' will be renamed so it won't collide + // with outer 'x' however it will should be reset on every iteration as + // if it was declared anew. + // + // * Why non-captured binding? + // - Because if loop contains block scoped binding captured in some + // function then loop body will be rewritten to have a fresh scope + // on every iteration so everything will just work. + // + // * Why loop initializer is excluded? + // - Since we've introduced a fresh name it already will be undefined. + + const flags = resolver.getNodeCheckFlags(original); + const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; + const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; + + const emittedAsTopLevel = + isBlockScopedContainerTopLevel(enclosingBlockScopeContainer) + || (isCapturedInFunction + && isDeclaredInLoop + && isBlock(enclosingBlockScopeContainer) + && isIterationStatement(enclosingBlockScopeContainerParent, /*lookInLabeledStatements*/ false)); + + const emittedAsNestedLetDeclaration = combinedNodeFlags & NodeFlags.Let && !emittedAsTopLevel; + + const emitExplicitInitializer = + emittedAsNestedLetDeclaration + && enclosingBlockScopeContainer.kind !== SyntaxKind.ForInStatement + && enclosingBlockScopeContainer.kind !== SyntaxKind.ForOfStatement + && (!resolver.isDeclarationWithCollidingName(original) + || (isDeclaredInLoop + && !isCapturedInFunction + && !isIterationStatement(enclosingBlockScopeContainer, /*lookInLabeledStatements*/ false))); + + if (emitExplicitInitializer) { + initializer = createVoidZero(); + } + } + } + + return setOriginalNode( + createVariableDeclaration( + name, + visitNode(initializer, visitor, isExpression, /*optional*/ true), + /*location*/ node + ), + node + ); + } + } + + function visitLabeledStatement(node: LabeledStatement) { + // TODO: Convert loop body for block scoped bindings. + return visitEachChild(node, visitor, context); + } + + function visitDoStatement(node: DoStatement) { + // TODO: Convert loop body for block scoped bindings. + return visitEachChild(node, visitor, context); + } + + function visitWhileStatement(node: WhileStatement) { + // TODO: Convert loop body for block scoped bindings. + return visitEachChild(node, visitor, context); + } + + function visitForStatement(node: ForStatement) { + // TODO: Convert loop body for block scoped bindings. + return visitEachChild(node, visitor, context); + } + + + function visitForInStatement(node: ForInStatement) { + // TODO: Convert loop body for block scoped bindings. + return visitEachChild(node, visitor, context); + } + + function visitForOfStatement(node: ForOfStatement): Statement { + // TODO: Convert loop body for block scoped bindings. + + // The following ES6 code: + // + // for (let v of expr) { } + // + // should be emitted as + // + // for (var _i = 0, _a = expr; _i < _a.length; _i++) { + // var v = _a[_i]; + // } + // + // where _a and _i are temps emitted to capture the RHS and the counter, + // respectively. + // When the left hand side is an expression instead of a let declaration, + // the "let v" is not emitted. + // When the left hand side is a let/const, the v is renamed if there is + // another v in scope. + // Note that all assignments to the LHS are emitted in the body, including + // all destructuring. + // Note also that because an extra statement is needed to assign to the LHS, + // for-of bodies are always emitted as blocks. + + const expression = visitNode(node.expression, visitor, isExpression); + const rhsIsIdentifier = expression.kind === SyntaxKind.Identifier; + const initializer = node.initializer; + const loopDeclarations: VariableDeclaration[] = []; + const loopBodyStatements: Statement[] = []; + + // In the case where the user wrote an identifier as the RHS, like this: + // + // for (let v of arr) { } + // + // we don't want to emit a temporary variable for the RHS, just use it directly. + const counter = createLoopVariable(); + const rhsReference = expression.kind === SyntaxKind.Identifier + ? makeUniqueName((expression).text) + : createTempVariable(); + + // Initialize LHS + // var v = _a[_i]; + if (isVariableDeclarationList(initializer)) { + const declarations: VariableDeclaration[] = []; + const firstDeclaration = firstOrUndefined(initializer.declarations); + if (firstDeclaration && isBindingPattern(firstDeclaration.name)) { + // This works whether the declaration is a var, let, or const. + // It will use rhsIterationValue _a[_i] as the initializer. + addLine(loopBodyStatements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList( + flattenVariableDestructuring( + firstDeclaration, + createElementAccess(rhsReference, counter), + visitor + ) + ), + /*location*/ initializer + ) + ); + } + else { + // The following call does not include the initializer, so we have + // to emit it separately. + addLine(loopBodyStatements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + firstDeclaration ? firstDeclaration.name : createTempVariable(), + createElementAccess(rhsReference, counter) + ) + ]), + /*location*/ initializer + ) + ); + } + } + else { + // Initializer is an expression. Emit the expression in the body, so that it's + // evaluated on every iteration. + const assignment = createAssignment(initializer, createElementAccess(rhsReference, counter)); + if (isDestructuringAssignment(assignment)) { + // This is a destructuring pattern, so we flatten the destructuring instead. + addLine(loopBodyStatements, + createStatement( + flattenDestructuringAssignment( + assignment, + /*needsValue*/ false, + hoistVariableDeclaration, + visitor + ) + ) + ); + } + else { + addLine(loopBodyStatements, createStatement(assignment, /*location*/ node.initializer)); + } + } + + const statement = visitNode(node.statement, visitor, isStatement); + if (isBlock(statement)) { + addNodes(loopBodyStatements, statement.statements); + } + else { + addNode(loopBodyStatements, statement); + } + + return createFor( + createVariableDeclarationList( + [ + createVariableDeclaration(counter, createLiteral(0), /*location*/ node.expression), + createVariableDeclaration(rhsReference, expression, /*location*/ node.expression) + ], + /*location*/ node.expression + ), + createLessThan( + counter, + createPropertyAccess(rhsReference, "length"), + /*location*/ initializer + ), + createPostfixIncrement(counter, /*location*/ initializer), + createBlock( + loopBodyStatements + ), + /*location*/ node + ); + } + + function shouldConvertLoopBody(node: IterationStatement): boolean { + return (resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithCapturedBlockScopedBinding) !== 0; + } + + function visitObjectLiteralExpression(node: ObjectLiteralExpression): LeftHandSideExpression { + // We are here because a ComputedPropertyName was used somewhere in the expression. + const properties = node.properties; + const numProperties = properties.length; + + // Find the first computed property. + // Everything until that point can be emitted as part of the initial object literal. + let numInitialNonComputedProperties = numProperties; + for (let i = 0, n = properties.length; i < n; i++) { + if (properties[i].name.kind === SyntaxKind.ComputedPropertyName) { + numInitialNonComputedProperties = i; + break; + } + } + + Debug.assert(numInitialNonComputedProperties !== numProperties); + + // For computed properties, we need to create a unique handle to the object + // literal so we can modify it without risking internal assignments tainting the object. + const temp = createTempVariable(); + hoistVariableDeclaration(temp); + + // Write out the first non-computed properties, then emit the rest through indexing on the temp variable. + let initialProperties = visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialNonComputedProperties); + + const expressions: Expression[] = []; + addNode(expressions, + createAssignment( + temp, + setMultiLine( + createObjectLiteral( + visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialNonComputedProperties) + ), + node.multiLine + ) + ), + node.multiLine + ); + + addObjectLiteralMembers(expressions, node, temp, numInitialNonComputedProperties); + + // We need to clone the temporary identifier so that we can write it on a + // new line + addNode(expressions, cloneNode(temp), node.multiLine); + return createParen(inlineExpressions(expressions)); + } + + function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, numInitialNonComputedProperties: number) { + const properties = node.properties; + for (let i = numInitialNonComputedProperties, len = properties.length; i < len; i++) { + const property = properties[i]; + switch (property.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const accessors = getAllAccessorDeclarations(node.properties, property); + if (property === accessors.firstAccessor) { + addNode(expressions, transformAccessorsToExpression(receiver, accessors), node.multiLine); + } + + break; + + case SyntaxKind.PropertyAssignment: + addNode(expressions, transformPropertyAssignmentToExpression(node, property, receiver), node.multiLine); + break; + + case SyntaxKind.ShorthandPropertyAssignment: + addNode(expressions, transformShorthandPropertyAssignmentToExpression(node, property, receiver), node.multiLine); + break; + + case SyntaxKind.MethodDeclaration: + addNode(expressions, transformObjectLiteralMethodDeclarationToExpression(node, property, receiver), node.multiLine); + break; + + default: + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); + break; + } + } + } + + function transformPropertyAssignmentToExpression(node: ObjectLiteralExpression, property: PropertyAssignment, receiver: Expression) { + return createAssignment( + createMemberAccessForPropertyName( + receiver, + visitNode(property.name, visitor, isPropertyName) + ), + visitNode(property.initializer, visitor, isExpression), + /*location*/ property + ); + } + + function transformShorthandPropertyAssignmentToExpression(node: ObjectLiteralExpression, property: ShorthandPropertyAssignment, receiver: Expression) { + return createAssignment( + createMemberAccessForPropertyName( + receiver, + visitNode(property.name, visitor, isPropertyName) + ), + getSynthesizedNode(property.name), + /*location*/ property + ); + } + + function transformObjectLiteralMethodDeclarationToExpression(node: ObjectLiteralExpression, method: MethodDeclaration, receiver: Expression) { + return createAssignment( + createMemberAccessForPropertyName( + receiver, + visitNode(method.name, visitor, isPropertyName) + ), + transformFunctionLikeToExpression(method, /*location*/ method), + /*location*/ method + ); + } + + function visitMethodDeclaration(node: MethodDeclaration): ObjectLiteralElement { + // We should only get here for methods on an object literal with regular identifier names. + // Methods on classes are handled in visitClassDeclaration/visitClassExpression. + // Methods with computed property names are handled in visitObjectLiteralExpression. + Debug.assert(isIdentifier(node.name), `Unexpected node kind: ${formatSyntaxKind(node.name.kind)}.`); + return createPropertyAssignment( + node.name, + transformFunctionLikeToExpression(node, /*location*/ node), + /*location*/ node + ); + } + + function visitShorthandPropertyAssignment(node: ShorthandPropertyAssignment): ObjectLiteralElement { + return createPropertyAssignment( + node.name, + getSynthesizedNode(node.name), + /*location*/ node + ); + } + + function visitArrayLiteralExpression(node: ArrayLiteralExpression): Expression { + // We are here either because SuperKeyword was used somewhere in the expression, or + // because we contain a SpreadElementExpression. + if (node.transformFlags & TransformFlags.ContainsSpreadElementExpression) { + return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, node.multiLine); + } + else { + // We don't handle SuperKeyword here, so fall back. + return visitEachChild(node, visitor, context); + } + } + + function visitCallExpression(node: CallExpression): LeftHandSideExpression { + // We are here either because SuperKeyword was used somewhere in the expression, or + // because we contain a SpreadElementExpression. + const { target, thisArg } = transformCallTarget(node.expression); + if (node.transformFlags & TransformFlags.ContainsSpreadElementExpression) { + return createFunctionApply( + target, + thisArg, + transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false) + ) + } + else { + Debug.assert(isSuperCall(node)); + return createFunctionCall( + target, + thisArg, + visitNodes(node.arguments, visitor, isExpression), + /*location*/ node + ); + } + } + + function visitNewExpression(node: NewExpression): LeftHandSideExpression { + // We are here either because we contain a SpreadElementExpression. + Debug.assert((node.transformFlags & TransformFlags.ContainsSpreadElementExpression) !== 0); + + // Transforms `new C(...a)` into `new ((_a = C).bind.apply(_a, [void 0].concat(a)))()`. + // Transforms `new x.C(...a)` into `new ((_a = x.C).bind.apply(_a, [void 0].concat(a)))()`. + const { target, thisArg } = transformCallTarget(createPropertyAccess(node.expression, "bind")); + return createNew( + createParen( + createFunctionApply( + target, + thisArg, + transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, createVoidZero()) + ) + ), + [] + ); + } + + interface CallTarget { + target: Expression; + thisArg: Expression; + } + + function transformCallTarget(expression: Expression): CallTarget { + const callee = skipParentheses(expression); + switch (callee.kind) { + case SyntaxKind.PropertyAccessExpression: + return transformPropertyAccessCallTarget(callee); + + case SyntaxKind.ElementAccessExpression: + return transformElementAccessCallTarget(callee); + + case SyntaxKind.SuperKeyword: + return transformSuperCallTarget(callee); + + default: + const thisArg = createVoidZero(); + const target = visitNode(expression, visitor, isExpression); + return { target, thisArg }; + } + } + + function transformPropertyAccessCallTarget(node: PropertyAccessExpression): CallTarget { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + // For `super.b()`, target is either `_super.b` (for static members) or + // `_super.prototype.b` (for instance members), and thisArg is `this`. + const thisArg = createThis(/*location*/ node.expression); + const target = createPropertyAccess( + visitSuperKeyword(node.expression), + node.name + ); + + return { target, thisArg }; + } + else { + // For `a.b()`, target is `(_a = a).b` and thisArg is `_a`. + const thisArg = createTempVariable(); + const target = createPropertyAccess( + createAssignment( + thisArg, + visitNode(node.expression, visitor, isExpression) + ), + node.name + ); + + return { target, thisArg }; + } + } + + function transformElementAccessCallTarget(node: ElementAccessExpression): CallTarget { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + // For `super[b]()`, target is either `_super[b]` (for static members) or + // `_super.prototype[b]` (for instance members), and thisArg is `this`. + const thisArg = createThis(/*location*/ node.expression); + const target = createElementAccess( + visitSuperKeyword(node.expression), + visitNode(node.argumentExpression, visitor, isExpression) + ); + + return { target, thisArg }; + } + else { + // For `a[b]()`, expression is `(_a = a)[b]` and thisArg is `_a`. + const thisArg = createTempVariable(); + const target = createElementAccess( + createAssignment( + thisArg, + visitNode(node.expression, visitor, isExpression) + ), + visitNode(node.argumentExpression, visitor, isExpression) + ); + + return { target, thisArg }; + } + } + + function transformSuperCallTarget(node: PrimaryExpression): CallTarget { + // For `super()`, expression is `_super` and thisArg is `this`. + const thisArg = createThis(/*location*/ node); + const target = createIdentifier("_super"); + return { target, thisArg }; + } + + function transformAndSpreadElements(elements: NodeArray, needsUniqueCopy: boolean, multiLine: boolean, leadingExpression?: Expression): Expression { + const segments: Expression[] = []; + addNode(segments, leadingExpression); + + const length = elements.length; + let start = 0; + for (let i = 0; i < length; i++) { + const element = elements[i]; + if (isSpreadElementExpression(element)) { + if (i > start) { + addNode(segments, + setMultiLine( + createArrayLiteral( + visitNodes(elements, visitor, isExpression, start, i) + ), + multiLine + ) + ); + } + + addNode(segments, visitNode(element.expression, visitor, isExpression)); + start = i + 1; + } + } + + if (start < length) { + addNode(segments, + setMultiLine( + createArrayLiteral( + visitNodes(elements, visitor, isExpression, start, length) + ), + multiLine + ) + ); + } + + if (segments.length === 1) { + if (!leadingExpression && needsUniqueCopy && isSpreadElementExpression(elements[0])) { + return createArraySlice(segments[0]); + } + + return segments[0]; + } + + // Rewrite using the pattern .concat(, , ...) + return createArrayConcat(segments.shift(), segments); + } + + function visitTemplateLiteral(node: LiteralExpression): LeftHandSideExpression { + return createLiteral(node.text); + } + + function visitTaggedTemplateExpression(node: TaggedTemplateExpression): LeftHandSideExpression { + // Visit the tag expression + const tag = visitNode(node.tag, visitor, isExpression); + + // Allocate storage for the template site object + const temp = createTempVariable(); + hoistVariableDeclaration(temp); + + // Build up the template arguments and the raw and cooked strings for the template. + const templateArguments: Expression[] = [temp]; + const cookedStrings: Expression[] = []; + const rawStrings: Expression[] = []; + const template = node.template; + if (isNoSubstitutionTemplateLiteral(template)) { + addNode(cookedStrings, createLiteral(template.text)); + addNode(rawStrings, getRawLiteral(template)); + } + else { + addNode(cookedStrings, createLiteral(template.head.text)); + addNode(rawStrings, getRawLiteral(template.head)); + for (const templateSpan of template.templateSpans) { + addNode(cookedStrings, createLiteral(templateSpan.literal.text)); + addNode(rawStrings, getRawLiteral(templateSpan.literal)); + addNode(templateArguments, visitNode(templateSpan.expression, visitor, isExpression)); + } + } + + return createParen( + inlineExpressions([ + createAssignment(temp, createArrayLiteral(cookedStrings)), + createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)), + createCall( + tag, + templateArguments + ) + ]) + ); + } + + function getRawLiteral(node: LiteralLikeNode) { + // Find original source text, since we need to emit the raw strings of the tagged template. + // The raw strings contain the (escaped) strings of what the user wrote. + // Examples: `\n` is converted to "\\n", a template string with a newline to "\n". + let text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node); + + // text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"), + // thus we need to remove those characters. + // First template piece starts with "`", others with "}" + // Last template piece ends with "`", others with "${" + let isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail; + text = text.substring(1, text.length - (isLast ? 1 : 2)); + + // Newline normalization: + // ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's + // and LineTerminatorSequences are normalized to for both TV and TRV. + text = text.replace(/\r\n?/g, "\n"); + text = escapeString(text); + return createLiteral(text); + } + + function visitTemplateExpression(node: TemplateExpression): Expression { + const expressions: Expression[] = []; + addTemplateHead(expressions, node); + addTemplateSpans(expressions, node.templateSpans); + + // createAdd will check if each expression binds less closely than binary '+'. + // If it does, it wraps the expression in parentheses. Otherwise, something like + // `abc${ 1 << 2 }` + // becomes + // "abc" + 1 << 2 + "" + // which is really + // ("abc" + 1) << (2 + "") + // rather than + // "abc" + (1 << 2) + "" + const expression = reduceLeft(expressions, createAdd); + if (nodeIsSynthesized(expression)) { + setTextRange(expression, node); + } + + return expression; + } + + function shouldAddTemplateHead(node: TemplateExpression) { + // If this expression has an empty head literal and the first template span has a non-empty + // literal, then emitting the empty head literal is not necessary. + // `${ foo } and ${ bar }` + // can be emitted as + // foo + " and " + bar + // This is because it is only required that one of the first two operands in the emit + // output must be a string literal, so that the other operand and all following operands + // are forced into strings. + // + // If the first template span has an empty literal, then the head must still be emitted. + // `${ foo }${ bar }` + // must still be emitted as + // "" + foo + bar + + // There is always atleast one templateSpan in this code path, since + // NoSubstitutionTemplateLiterals are directly emitted via emitLiteral() + Debug.assert(node.templateSpans.length !== 0); + + return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0; + } + + function addTemplateHead(expressions: Expression[], node: TemplateExpression): void { + if (!shouldAddTemplateHead(node)) { + return; + } + + addNode(expressions, createLiteral(node.head.text)); + } + + function addTemplateSpans(expressions: Expression[], nodes: TemplateSpan[]): void { + for (const node of nodes) { + addNode(expressions, visitNode(node.expression, visitor, isExpression)); + + // Only emit if the literal is non-empty. + // The binary '+' operator is left-associative, so the first string concatenation + // with the head will force the result up to this point to be a string. + // Emitting a '+ ""' has no semantic effect for middles and tails. + if (node.literal.text.length !== 0) { + addNode(expressions, createLiteral(node.literal.text)); + } + } + } + + function visitSuperKeyword(node: PrimaryExpression): LeftHandSideExpression { + const expression = createIdentifier("_super"); + return containingNonArrowFunction + && isClassElement(containingNonArrowFunction) + && (containingNonArrowFunction.flags & NodeFlags.Static) === 0 + ? createPropertyAccess(createIdentifier("_super"), "prototype") + : createIdentifier("_super"); + } + + function visitSourceFileNode(node: SourceFile): SourceFile { + const clone = cloneNode(node, node, node.flags, /*parent*/ undefined, node); + const statements: Statement[] = []; + startLexicalEnvironment(); + let statementOffset = addPrologueDirectives(statements, node.statements); + addCaptureThisForNodeIfNeeded(statements, node); + addNodes(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + addNodes(statements, endLexicalEnvironment()); + clone.statements = createNodeArray(statements, node.statements); + return clone; + } + + function addPrologueDirectives(to: Statement[], from: NodeArray): number { + for (let i = 0; i < from.length; ++i) { + if (isPrologueDirective(from[i])) { + addNode(to, from[i]); + } + else { + return i; + } + } + + return from.length; + } + + var inEmit: boolean; + + function onBeforeEmitNode(node: Node) { + previousOnBeforeEmitNode(node); + + if (containingFunctionStack && isFunctionLike(node)) { + containingFunctionStack.push(node); + } + } + + function onAfterEmitNode(node: Node) { + previousOnAfterEmitNode(node); + + if (containingFunctionStack && isFunctionLike(node)) { + containingFunctionStack.pop(); + } + } + + function enableExpressionSubstitutionForCapturedThis() { + if (!hasEnabledExpressionSubstitutionForCapturedThis) { + hasEnabledExpressionSubstitutionForCapturedThis = true; + enableExpressionSubstitution(SyntaxKind.ThisKeyword); + enableEmitNotification(SyntaxKind.Constructor); + enableEmitNotification(SyntaxKind.MethodDeclaration); + enableEmitNotification(SyntaxKind.GetAccessor); + enableEmitNotification(SyntaxKind.SetAccessor); + enableEmitNotification(SyntaxKind.ArrowFunction); + enableEmitNotification(SyntaxKind.FunctionExpression); + enableEmitNotification(SyntaxKind.FunctionDeclaration); + containingFunctionStack = []; + } + } + + function substituteIdentifier(node: Identifier) { + node = previousIdentifierSubstitution(node); + + const original = getOriginalNode(node); + if (isIdentifier(original) && isNameOfDeclarationWithCollidingName(original)) { + return getGeneratedNameForNode(original); + } + return node; } + + function isNameOfDeclarationWithCollidingName(node: Identifier) { + const parent = node.parent; + if (parent) { + switch (parent.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableDeclaration: + return (parent).name === node + && resolver.isDeclarationWithCollidingName(parent); + } + } + + return false; + } + + + function substituteExpression(node: Expression): Expression { + node = previousExpressionSubstitution(node); + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node); + + case SyntaxKind.ThisKeyword: + return substituteThisKeyword(node); + } + + return node; + } + + function substituteExpressionIdentifier(node: Identifier): Identifier { + const original = getOriginalNode(node); + if (isIdentifier(original)) { + const declaration = resolver.getReferencedDeclarationWithCollidingName(original); + if (declaration) { + return getGeneratedNameForNode(declaration.name); + } + } + + return node; + } + + function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { + if (containingFunctionStack) { + const containingFunction = lastOrUndefined(containingFunctionStack); + if (containingFunction && getOriginalNode(containingFunction).kind === SyntaxKind.ArrowFunction) { + return createIdentifier("_this"); + } + } + + return node; + } + + function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration) { + return node.name ? getSynthesizedNode(node.name) : getGeneratedNameForNode(node); + } + + function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) { + const expression = getDeclarationName(node); + return member.flags & NodeFlags.Static ? expression : createPropertyAccess(expression, "prototype"); + } + + function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration, hasExtendsClause: boolean) { + if (!constructor || !hasExtendsClause) { + return false; + } + + const parameter = singleOrUndefined(constructor.parameters); + if (!parameter || !nodeIsSynthesized(parameter) || !parameter.dotDotDotToken) { + return false; + } + + const statement = firstOrUndefined(constructor.body.statements); + if (!statement || !nodeIsSynthesized(statement) || statement.kind !== SyntaxKind.ExpressionStatement) { + return false; + } + + const statementExpression = (statement).expression; + if (!nodeIsSynthesized(statementExpression) || statementExpression.kind !== SyntaxKind.CallExpression) { + return false; + } + + const callTarget = (statementExpression).expression; + if (!nodeIsSynthesized(callTarget) || callTarget.kind !== SyntaxKind.SuperKeyword) { + return false; + } + + const callArgument = singleOrUndefined((statementExpression).arguments); + if (!callArgument || !nodeIsSynthesized(callArgument) || callArgument.kind !== SyntaxKind.SpreadElementExpression) { + return false; + } + + const expression = (callArgument).expression; + return isIdentifier(expression) && expression === parameter.name; + } } } \ No newline at end of file diff --git a/src/compiler/transformers/es7.ts b/src/compiler/transformers/es7.ts index 9dca322c175..7be97566c51 100644 --- a/src/compiler/transformers/es7.ts +++ b/src/compiler/transformers/es7.ts @@ -30,7 +30,7 @@ namespace ts { return visitBinaryExpression(node); } - Debug.fail("Unexpected node kind."); + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); } function visitBinaryExpression(node: BinaryExpression): Expression { @@ -90,7 +90,7 @@ namespace ts { return createMathPow(left, right, /*location*/ node); } else { - Debug.fail("Unexpected node kind."); + Debug.fail(`Unexpected operator kind: ${formatSyntaxKind(node.operatorToken.kind)}.`); } } } diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 8ad85f153c9..fd8c568ac0e 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -34,7 +34,7 @@ namespace ts { return visitJsxSelfClosingElement(node); } - Debug.fail("Unexpected node kind."); + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); } function transformJsxChildToExpression(node: JsxChild): Expression { @@ -52,7 +52,7 @@ namespace ts { return visitJsxSelfClosingElement(node); } - Debug.fail("Unexpected node kind."); + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); } function visitJsxElement(node: JsxElement) { @@ -85,14 +85,14 @@ namespace ts { properties = undefined; } - addNode(segments, transformJsxSpreadAttributeToExpression(attr), isExpression); + addNode(segments, transformJsxSpreadAttributeToExpression(attr)); } else { if (!properties) { properties = []; } - addNode(properties, transformJsxAttributeToObjectLiteralElement(attr), isObjectLiteralElement); + addNode(properties, transformJsxAttributeToObjectLiteralElement(attr)); } } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 616457e8b32..f7e7e128746 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -31,14 +31,17 @@ namespace ts { context.onBeforeEmitNode = onBeforeEmitNode; context.onAfterEmitNode = onAfterEmitNode; - let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; let currentSourceFile: SourceFile; let currentNamespace: ModuleDeclaration; let currentNamespaceLocalName: Identifier; let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentParent: Node; let currentNode: Node; + let combinedNodeFlags: NodeFlags; let isRightmostExpression: boolean; + + // This stack is is used to support substitutions when printing nodes. + let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; let superContainerStack: SuperContainer[]; return transformSourceFile; @@ -61,6 +64,7 @@ namespace ts { const savedCurrentScope = currentScope; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; + const savedCombinedNodeFlags = combinedNodeFlags; const savedIsRightmostExpression = isRightmostExpression; onBeforeVisitNode(node); node = visitor(node); @@ -68,6 +72,7 @@ namespace ts { currentScope = savedCurrentScope; currentParent = savedCurrentParent; currentNode = savedCurrentNode; + combinedNodeFlags = savedCombinedNodeFlags; isRightmostExpression = savedIsRightmostExpression; return node; } @@ -290,7 +295,7 @@ namespace ts { case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: // TypeScript type assertions are removed, but their subtrees are preserved. - return visitNode((node).expression, visitor, isExpression); + return visitAssertionExpression(node); case SyntaxKind.EnumDeclaration: // TypeScript enum declarations do not exist in ES6 and must be rewritten. @@ -313,7 +318,7 @@ namespace ts { return visitImportEqualsDeclaration(node); default: - Debug.fail("Unexpected node."); + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); break; } } @@ -326,6 +331,9 @@ namespace ts { function onBeforeVisitNode(node: Node) { currentParent = currentNode; currentNode = node; + + combinedNodeFlags = combineNodeFlags(currentNode, currentParent, combinedNodeFlags); + switch (node.kind) { case SyntaxKind.SourceFile: case SyntaxKind.CaseBlock: @@ -357,7 +365,7 @@ namespace ts { const statements: Statement[] = []; const modifiers = visitNodes(node.modifiers, visitor, isModifier); const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, heritageClauses !== undefined); + const members = transformClassMembers(node, firstOrUndefined(heritageClauses) !== undefined); let decoratedClassAlias: Identifier; // emit name if @@ -744,7 +752,10 @@ namespace ts { // End the lexical environment. addNodes(statements, endLexicalEnvironment()); - return createBlock(statements); + return setMultiLine( + createBlock(statements, constructor ? constructor.body : undefined), + true + ); } /** @@ -855,7 +866,17 @@ namespace ts { * @param receiver The receiver on which each property should be assigned. */ function generateInitializedPropertyStatements(node: ClassExpression | ClassDeclaration, properties: PropertyDeclaration[], receiver: LeftHandSideExpression) { - return map(generateInitializedPropertyExpressions(node, properties, receiver), expressionToStatement); + const statements: Statement[] = []; + for (const property of properties) { + statements.push( + createStatement( + transformInitializedProperty(node, property, receiver), + /*location*/ property + ) + ); + } + + return statements; } /** @@ -868,8 +889,9 @@ namespace ts { function generateInitializedPropertyExpressions(node: ClassExpression | ClassDeclaration, properties: PropertyDeclaration[], receiver: LeftHandSideExpression) { const expressions: Expression[] = []; for (const property of properties) { - expressions.push(transformInitializedProperty(node, property, receiver)); + expressions.push(transformInitializedProperty(node, property, receiver, /*location*/ property)); } + return expressions; } @@ -880,12 +902,13 @@ namespace ts { * @param property The property declaration. * @param receiver The object receiving the property assignment. */ - function transformInitializedProperty(node: ClassExpression | ClassDeclaration, property: PropertyDeclaration, receiver: LeftHandSideExpression) { + function transformInitializedProperty(node: ClassExpression | ClassDeclaration, property: PropertyDeclaration, receiver: LeftHandSideExpression, location?: TextRange) { const propertyName = visitPropertyNameOfClassElement(property); const initializer = visitNode(property.initializer, visitor, isExpression); return createAssignment( createMemberAccessForPropertyName(receiver, propertyName), - initializer + initializer, + location ); } @@ -1467,7 +1490,7 @@ namespace ts { break; default: - Debug.fail("Cannot serialize unexpected type node."); + Debug.fail(`Unexpected node kind: ${formatSyntaxKind(node.kind)}.`); break; } @@ -2053,7 +2076,8 @@ namespace ts { return createStatement( inlineExpressions( map(variables, transformInitializedVariable) - ) + ), + /*location*/ node ); } @@ -2113,7 +2137,10 @@ namespace ts { location = undefined; } - const namespaceMemberName = getNamespaceMemberName(node.name); + const name = isNamespaceExport(node) + ? getNamespaceMemberName(node.name) + : getSynthesizedNode(node.name); + currentNamespaceLocalName = getGeneratedNameForNode(node); addNode(statements, createStatement( @@ -2127,9 +2154,9 @@ namespace ts { ) ), [createLogicalOr( - namespaceMemberName, + name, createAssignment( - namespaceMemberName, + name, createObjectLiteral() ) )] @@ -2143,7 +2170,7 @@ namespace ts { createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ - createVariableDeclaration(node.name, namespaceMemberName) + createVariableDeclaration(node.name, name) ]), location ) @@ -2229,12 +2256,20 @@ namespace ts { * @param node The await expression node. */ function visitAwaitExpression(node: AwaitExpression): Expression { - const expression = createYield( - visitNode(node.expression, visitor, isExpression), + const expression = setOriginalNode( + createYield( + visitNode(node.expression, visitor, isExpression), + node + ), node ); - return isRightmostExpression ? expression : createParen(expression); + return isRightmostExpression + ? expression + : setOriginalNode( + createParen(expression, /*location*/ node), + node + ); } /** @@ -2265,11 +2300,29 @@ namespace ts { !(expression.kind === SyntaxKind.CallExpression && currentParent.kind === SyntaxKind.NewExpression) && !(expression.kind === SyntaxKind.FunctionExpression && currentParent.kind === SyntaxKind.CallExpression) && !(expression.kind === SyntaxKind.NumericLiteral && currentParent.kind === SyntaxKind.PropertyAccessExpression)) { - return expression; + return trackChildOfNotEmittedNode(node, expression, node.expression); } } - return createParen(expression, node); + return setOriginalNode( + createParen(expression, node), + node + ); + } + + function visitAssertionExpression(node: AssertionExpression): Expression { + const expression = visitNode((node).expression, visitor, isExpression); + return trackChildOfNotEmittedNode(node, expression, node.expression); + } + + function trackChildOfNotEmittedNode(parent: Node, child: T, original: T) { + if (!child.parent && !child.original) { + child = cloneNode(child, child, child.flags, child.parent, original); + } + + setNodeEmitFlags(parent, NodeEmitFlags.IsNotEmittedNode); + setNodeEmitFlags(child, NodeEmitFlags.EmitCommentsOfNotEmittedParent); + return child; } /** @@ -2305,10 +2358,14 @@ namespace ts { location = undefined; } + const name = isNamespaceExport(node) + ? getNamespaceMemberName(node.name) + : getSynthesizedNode(node.name); + let moduleParam: Expression = createLogicalOr( - getNamespaceMemberName(node.name), + name, createAssignment( - getNamespaceMemberName(node.name), + name, createObjectLiteral([]) ) ); @@ -2515,13 +2572,10 @@ namespace ts { } function getNamespaceMemberName(name: Identifier): Expression { - name = getSynthesizedNode(name); - return currentNamespaceLocalName - ? createPropertyAccess(currentNamespaceLocalName, name) - : name + return createPropertyAccess(currentNamespaceLocalName, getSynthesizedNode(name)); } - function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration) { + function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration | EnumDeclaration) { return node.name ? getSynthesizedNode(node.name) : getGeneratedNameForNode(node); } @@ -2536,7 +2590,7 @@ namespace ts { } function substituteExpression(node: Expression): Expression { - node = previousExpressionSubstitution ? previousExpressionSubstitution(node) : node; + node = previousExpressionSubstitution(node); switch (node.kind) { case SyntaxKind.Identifier: return substituteExpressionIdentifier(node); @@ -2556,19 +2610,30 @@ namespace ts { return node; } - function substituteExpressionIdentifier(node: Identifier) { - if (!nodeIsSynthesized(node) && resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { - // Due to the emit for class decorators, any reference to the class from inside of the class body - // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind - // behavior of class names in ES6. - const original = getOriginalNode(node); - const declaration = resolver.getReferencedValueDeclaration(isIdentifier(original) ? original : node); - if (declaration) { - const classAlias = currentDecoratedClassAliases[getNodeId(declaration)]; - if (classAlias) { - return cloneNode(classAlias); + function substituteExpressionIdentifier(node: Identifier): Expression { + const original = getOriginalNode(node); + if (isIdentifier(original)) { + if (resolver.getNodeCheckFlags(original) & NodeCheckFlags.BodyScopedClassBinding) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + const declaration = resolver.getReferencedValueDeclaration(original); + if (declaration) { + const classAlias = currentDecoratedClassAliases[getNodeId(declaration)]; + if (classAlias) { + return cloneNode(classAlias); + } } } + + const container = resolver.getReferencedExportContainer(original); + if (container && container.kind === SyntaxKind.ModuleDeclaration) { + return createPropertyAccess( + getGeneratedNameForNode(container), + cloneNode(node), + /*location*/ node + ); + } } return node; @@ -2652,6 +2717,8 @@ namespace ts { } function onBeforeEmitNode(node: Node): void { + previousOnAfterEmitNode(node); + const kind = node.kind; if (kind === SyntaxKind.ClassDeclaration && node.decorators) { currentDecoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAliases[getOriginalNodeId(node)]; @@ -2673,6 +2740,8 @@ namespace ts { } function onAfterEmitNode(node: Node): void { + previousOnAfterEmitNode(node); + const kind = node.kind; if (kind === SyntaxKind.ClassDeclaration && node.decorators) { currentDecoratedClassAliases[getOriginalNodeId(node)] = undefined; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 847b00a1c97..47908d322a2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -20,6 +20,7 @@ namespace ts { export interface TextRange { pos: number; end: number; + /* @internal */ disableSourceMap?: boolean; // Whether a synthesized text range disables source maps for its contents (used by transforms). } // token > SyntaxKind.Identifer => token is a keyword @@ -449,6 +450,7 @@ namespace ts { /* @internal */ id?: number; // Unique id (used to look up NodeLinks) parent?: Node; // Parent node (initialized by binding) /* @internal */ original?: Node; // The original node if this is an updated node. + /* @internal */ startsOnNewLine?: boolean; // Whether a synthesized node should start on a new line (used by transforms). /* @internal */ jsDocComment?: JSDocComment; // JSDoc for the node, if it has any. Only for .js files. /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding) /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) @@ -1124,6 +1126,7 @@ namespace ts { // @kind(SyntaxKind.Block) export interface Block extends Statement { statements: NodeArray; + multiLine?: boolean; } // @kind(SyntaxKind.VariableStatement) @@ -2466,6 +2469,7 @@ namespace ts { allowSyntheticDefaultImports?: boolean; allowJs?: boolean; /* @internal */ stripInternal?: boolean; + /* @internal */ experimentalTransforms?: boolean; // Skip checking lib.d.ts to help speed up tests. /* @internal */ skipDefaultLibCheck?: boolean; @@ -2767,6 +2771,7 @@ namespace ts { ContainsParameterPropertyAssignments = 1 << 13, ContainsSpreadElementExpression = 1 << 14, ContainsComputedPropertyName = 1 << 15, + ContainsBlockScopedBinding = 1 << 16, // Assertions // - Bitmasks that are used to assert facts about the syntax of a node and its subtree. @@ -2779,12 +2784,12 @@ namespace ts { // - Bitmasks that exclude flags from propagating out of a specific context // into the subtree flags of their container. NodeExcludes = TypeScript | Jsx | ES7 | ES6, - ArrowFunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments, - FunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments, - ConstructorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsParameterPropertyAssignments, - MethodOrAccessorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis, + ArrowFunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, + FunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, + ConstructorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, + MethodOrAccessorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, ClassExcludes = ContainsDecorators | ContainsPropertyInitializer | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsParameterPropertyAssignments, - ModuleExcludes = ContainsDecorators | ContainsLexicalThis | ContainsCapturedLexicalThis, + ModuleExcludes = ContainsDecorators | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, TypeExcludes = ~ContainsTypeScript, ObjectLiteralExcludes = ContainsDecorators | ContainsComputedPropertyName, ArrayLiteralOrCallOrNewExcludes = ContainsSpreadElementExpression, @@ -2792,15 +2797,16 @@ namespace ts { /* @internal */ export const enum NodeEmitFlags { - EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node. - EmitExportStar = 1 << 1, // The export * helper should be written to this node. - EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. - EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. - UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. - NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node, this is primarily used when printing a SystemJS module. - SingleLine = 1 << 6, // The contents of this node should be emit on a single line. - MultiLine = 1 << 7, // The contents of this node should be emit on multiple lines. - AdviseOnEmitNode = 1 << 8, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. + EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node. + EmitExportStar = 1 << 1, // The export * helper should be written to this node. + EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. + EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. + UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. + NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node, this is primarily used when printing a SystemJS module. + SingleLine = 1 << 6, // The contents of this node should be emit on a single line. + AdviseOnEmitNode = 1 << 7, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. + IsNotEmittedNode = 1 << 8, // Is a node that is not emitted but whose comments should be preserved if possible. + EmitCommentsOfNotEmittedParent = 1 << 8, // Emits comments of missing parent nodes. } /** Additional context provided to `visitEachChild` */ @@ -2817,7 +2823,7 @@ namespace ts { getCompilerOptions(): CompilerOptions; getEmitResolver(): EmitResolver; getNodeEmitFlags(node: Node): NodeEmitFlags; - setNodeEmitFlags(node: Node, flags: NodeEmitFlags): void; + setNodeEmitFlags(node: T, flags: NodeEmitFlags): T; hoistFunctionDeclaration(node: FunctionDeclaration): void; hoistVariableDeclaration(node: Identifier): void; isUniqueName(name: string): boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b84feea4153..aa7d0affab6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -8,13 +8,6 @@ namespace ts { isNoDefaultLib?: boolean; } - export interface SynthesizedNode extends Node { - leadingCommentRanges?: CommentRange[]; - trailingCommentRanges?: CommentRange[]; - startsOnNewLine?: boolean; - disableSourceMap?: boolean; - } - export function getDeclarationOfKind(symbol: Symbol, kind: SyntaxKind): Declaration { const declarations = symbol.declarations; if (declarations) { @@ -330,8 +323,7 @@ namespace ts { export function isBlockScopedContainerTopLevel(node: Node): boolean { return node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.ModuleDeclaration || - isFunctionLike(node) || - isFunctionBlock(node); + isFunctionLike(node); } export function isGlobalScopeAugmentation(module: ModuleDeclaration): boolean { @@ -354,30 +346,41 @@ namespace ts { return false; } + export function isBlockScope(node: Node, parentNode: Node) { + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.CaseBlock: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + + case SyntaxKind.Block: + // function block is not considered block-scope container + // see comment in binder.ts: bind(...), case for SyntaxKind.Block + return parentNode && !isFunctionLike(parentNode); + } + + return false; + } + // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. export function getEnclosingBlockScopeContainer(node: Node): Node { let current = node.parent; while (current) { - if (isFunctionLike(current)) { + if (isBlockScope(current, current.parent)) { return current; } - switch (current.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.CaseBlock: - case SyntaxKind.CatchClause: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return current; - case SyntaxKind.Block: - // function block is not considered block-scope container - // see comment in binder.ts: bind(...), case for SyntaxKind.Block - if (!isFunctionLike(current.parent)) { - return current; - } - } current = current.parent; } @@ -488,6 +491,19 @@ namespace ts { return node; } + /** + * Combines the flags of a node with the combined flags of its parent if they can be combined. + */ + export function combineNodeFlags(node: Node, parentNode: Node, previousNodeFlags: NodeFlags) { + if ((node.kind === SyntaxKind.VariableDeclarationList && parentNode.kind === SyntaxKind.VariableStatement) || + (node.kind === SyntaxKind.VariableDeclaration && parentNode.kind === SyntaxKind.VariableDeclarationList) || + (node.kind === SyntaxKind.BindingElement)) { + return node.flags | previousNodeFlags; + } + + return node.flags; + } + // Returns the node flags for this node and all relevant parent nodes. This is done so that // nodes like variable declarations and binding elements can returned a view of their flags // that includes the modifiers from their container. i.e. flags like export/declare aren't @@ -1733,7 +1749,7 @@ namespace ts { return getOperatorPrecedence(expression.kind, operator, hasArguments); } - function getOperator(expression: Expression) { + export function getOperator(expression: Expression) { if (expression.kind === SyntaxKind.BinaryExpression) { return (expression).operatorToken.kind; } @@ -2318,26 +2334,33 @@ namespace ts { } } - export function emitComments(text: string, lineMap: number[], writer: EmitTextWriter, comments: CommentRange[], trailingSeparator: boolean, newLine: string, + export function emitComments(text: string, lineMap: number[], writer: EmitTextWriter, comments: CommentRange[], leadingSeparator: boolean, trailingSeparator: boolean, newLine: string, writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) => void) { - let emitLeadingSpace = !trailingSeparator; - forEach(comments, comment => { - if (emitLeadingSpace) { - writer.write(" "); - emitLeadingSpace = false; - } - writeComment(text, lineMap, writer, comment, newLine); - if (comment.hasTrailingNewLine) { - writer.writeLine(); - } - else if (trailingSeparator) { + if (comments && comments.length > 0) { + if (leadingSeparator) { writer.write(" "); } - else { - // Emit leading space to separate comment during next comment emit - emitLeadingSpace = true; + + let emitInterveningSeperator = false; + for (const comment of comments) { + if (emitInterveningSeperator) { + writer.write(" "); + emitInterveningSeperator = false; + } + + writeComment(text, lineMap, writer, comment, newLine); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + else { + emitInterveningSeperator = true; + } } - }); + + if (emitInterveningSeperator && trailingSeparator) { + writer.write(" "); + } + } } /** @@ -2394,7 +2417,7 @@ namespace ts { if (nodeLine >= lastCommentLine + 2) { // Valid detachedComments emitNewLineBeforeLeadingComments(lineMap, writer, node, leadingComments); - emitComments(text, lineMap, writer, detachedComments, /*trailingSeparator*/ true, newLine, writeComment); + emitComments(text, lineMap, writer, detachedComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); currentDetachedCommentInfo = { nodePos: node.pos, detachedCommentEndPos: lastOrUndefined(detachedComments).end }; } } @@ -2795,6 +2818,35 @@ namespace ts { return false; } + export function formatSyntaxKind(kind: SyntaxKind): string { + const syntaxKindEnum = (ts).SyntaxKind; + if (syntaxKindEnum) { + for (const name in syntaxKindEnum) { + if (syntaxKindEnum[name] === kind) { + return kind.toString() + " (" + name + ")"; + } + } + } + else { + return kind.toString(); + } + } + + export const enum TextRangeCollapse { + CollapseToStart, + CollapseToEnd, + } + + export function collapseTextRange(range: TextRange, collapse: TextRangeCollapse) { + if (range.pos === range.end) { + return range; + } + + return collapse === TextRangeCollapse.CollapseToStart + ? { pos: range.pos, end: range.end } + : { pos: range.end, end: range.end }; + } + // Node tests // // All node tests in the following list should *not* reference parent pointers so that @@ -2812,6 +2864,10 @@ namespace ts { // Literals + export function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression { + return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; + } + export function isLiteralKind(kind: SyntaxKind): boolean { return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; } @@ -2905,6 +2961,10 @@ namespace ts { // Type members + export function isMethodDeclaration(node: Node): node is MethodDeclaration { + return node.kind === SyntaxKind.MethodDeclaration; + } + export function isClassElement(node: Node): node is ClassElement { const kind = node.kind; return kind === SyntaxKind.Constructor @@ -2991,11 +3051,16 @@ namespace ts { || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } + export function isSpreadElementExpression(node: Node): node is SpreadElementExpression { + return node.kind === SyntaxKind.SpreadElementExpression; + } + export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { return node.kind === SyntaxKind.ExpressionWithTypeArguments; } - function isLeftHandSideExpressionKind(kind: SyntaxKind) { + export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { + const kind = node.kind; return kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression || kind === SyntaxKind.NewExpression @@ -3021,11 +3086,8 @@ namespace ts { || kind === SyntaxKind.SuperKeyword; } - export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { - return isLeftHandSideExpressionKind(node.kind); - } - - function isUnaryExpressionKind(kind: SyntaxKind): boolean { + export function isUnaryExpression(node: Node): node is UnaryExpression { + const kind = node.kind; return kind === SyntaxKind.PrefixUnaryExpression || kind === SyntaxKind.PostfixUnaryExpression || kind === SyntaxKind.DeleteExpression @@ -3033,14 +3095,11 @@ namespace ts { || kind === SyntaxKind.VoidExpression || kind === SyntaxKind.AwaitExpression || kind === SyntaxKind.TypeAssertionExpression - || isLeftHandSideExpressionKind(kind); + || isLeftHandSideExpression(node); } - export function isUnaryExpression(node: Node): node is UnaryExpression { - return isUnaryExpressionKind(node.kind); - } - - export function isExpressionKind(kind: SyntaxKind): boolean { + export function isExpression(node: Node): node is Expression { + const kind = node.kind; return kind === SyntaxKind.ConditionalExpression || kind === SyntaxKind.YieldExpression || kind === SyntaxKind.ArrowFunction @@ -3048,11 +3107,7 @@ namespace ts { || kind === SyntaxKind.SpreadElementExpression || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression - || isUnaryExpressionKind(kind); - } - - export function isExpression(node: Node): node is Expression { - return isExpressionKind(node.kind); + || isUnaryExpression(node); } // Misc @@ -3264,9 +3319,12 @@ namespace ts { // Property assignments - export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - const kind = node.kind; - return kind === SyntaxKind.ShorthandPropertyAssignment; + export function isPropertyAssignment(node: Node): node is PropertyAssignment { + return node.kind === SyntaxKind.PropertyAssignment; + } + + export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + return node.kind === SyntaxKind.ShorthandPropertyAssignment; } // Enum diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 0ddcc770a9f..3292881e014 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -19,6 +19,8 @@ namespace ts { /** A callback used to lift a NodeArrayNode into a valid node. */ lift?: (nodes: NodeArray) => Node; + + parenthesize?: (value: Node, parentNode: Node) => Node; }; /** @@ -52,7 +54,7 @@ namespace ts { { name: "modifiers", test: isModifier }, { name: "name", test: isBindingName }, { name: "type", test: isTypeNode, optional: true }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.Decorator]: [ { name: "expression", test: isLeftHandSideExpression }, @@ -108,34 +110,34 @@ namespace ts { [SyntaxKind.BindingElement]: [ { name: "propertyName", test: isPropertyName, optional: true }, { name: "name", test: isBindingName }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ArrayLiteralExpression]: [ - { name: "elements", test: isExpression }, + { name: "elements", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ObjectLiteralExpression]: [ { name: "properties", test: isObjectLiteralElement }, ], [SyntaxKind.PropertyAccessExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "name", test: isIdentifier }, ], [SyntaxKind.ElementAccessExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "argumentExpression", test: isExpression }, ], [SyntaxKind.CallExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, { name: "arguments", test: isExpression }, ], [SyntaxKind.NewExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, { name: "arguments", test: isExpression }, ], [SyntaxKind.TaggedTemplateExpression]: [ - { name: "tag", test: isLeftHandSideExpression }, + { name: "tag", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "template", test: isTemplate }, ], [SyntaxKind.TypeAssertionExpression]: [ @@ -163,26 +165,26 @@ namespace ts { { name: "body", test: isConciseBody, lift: liftToBlock }, ], [SyntaxKind.DeleteExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.TypeOfExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.VoidExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.AwaitExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.PrefixUnaryExpression]: [ - { name: "operand", test: isUnaryExpression }, + { name: "operand", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.PostfixUnaryExpression]: [ - { name: "operand", test: isLeftHandSideExpression }, + { name: "operand", test: isLeftHandSideExpression, parenthesize: parenthesizePostfixOperand }, ], [SyntaxKind.BinaryExpression]: [ - { name: "left", test: isExpression }, - { name: "right", test: isExpression }, + { name: "left", test: isExpression, parenthesize: (node: Expression, parent: BinaryExpression) => parenthesizeBinaryOperand(getOperator(parent), node, true) }, + { name: "right", test: isExpression, parenthesize: (node: Expression, parent: BinaryExpression) => parenthesizeBinaryOperand(getOperator(parent), node, false) }, ], [SyntaxKind.ConditionalExpression]: [ { name: "condition", test: isExpression }, @@ -197,7 +199,7 @@ namespace ts { { name: "expression", test: isExpression, optional: true }, ], [SyntaxKind.SpreadElementExpression]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ClassExpression]: [ { name: "decorators", test: isDecorator }, @@ -208,7 +210,7 @@ namespace ts { { name: "members", test: isClassElement }, ], [SyntaxKind.ExpressionWithTypeArguments]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, ], [SyntaxKind.AsExpression]: [ @@ -228,7 +230,7 @@ namespace ts { { name: "declarationList", test: isVariableDeclarationList }, ], [SyntaxKind.ExpressionStatement]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForExpressionStatement }, ], [SyntaxKind.IfStatement]: [ { name: "expression", test: isExpression }, @@ -291,7 +293,7 @@ namespace ts { [SyntaxKind.VariableDeclaration]: [ { name: "name", test: isBindingName }, { name: "type", test: isTypeNode, optional: true }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.VariableDeclarationList]: [ { name: "declarations", test: isVariableDeclaration }, @@ -420,7 +422,7 @@ namespace ts { ], [SyntaxKind.PropertyAssignment]: [ { name: "name", test: isPropertyName }, - { name: "initializer", test: isExpression }, + { name: "initializer", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ShorthandPropertyAssignment]: [ { name: "name", test: isIdentifier }, @@ -428,7 +430,7 @@ namespace ts { ], [SyntaxKind.EnumMember]: [ { name: "name", test: isPropertyName }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.SourceFile]: [ { name: "statements", test: isStatement }, @@ -492,7 +494,6 @@ namespace ts { Debug.assert(test === undefined || test(visited), "Wrong node type after visit."); aggregateTransformFlags(visited); - visited.original = node; return visited; } @@ -541,14 +542,14 @@ namespace ts { aggregateTransformFlags(visited); } - addNode(updated, visited, test); + addNodeWorker(updated, visited, /*addOnNewLine*/ undefined, test); } } if (updated !== undefined) { return (isModifiersArray(nodes) ? createModifiersArray(updated, nodes) - : createNodeArray(updated, nodes)); + : setHasTrailingComma(createNodeArray(updated, nodes), nodes.hasTrailingComma)); } return nodes; @@ -577,21 +578,27 @@ namespace ts { const edgeTraversalPath = nodeEdgeTraversalMap[node.kind]; if (edgeTraversalPath) { + let modifiers: NodeFlags; for (const edge of edgeTraversalPath) { const value = >node[edge.name]; if (value !== undefined) { const visited = visitEdge(edge, value, visitor); + if (visited && isArray(visited) && isModifiersArray(visited)) { + modifiers = visited.flags; + } + if (updated !== undefined || visited !== value) { if (updated === undefined) { updated = cloneNode(node, /*location*/ node, node.flags & ~NodeFlags.Modifier, /*parent*/ undefined, /*original*/ node); } - if (visited && isArray(visited) && isModifiersArray(visited)) { - updated[edge.name] = visited; - updated.flags |= visited.flags; + if (modifiers) { + updated.flags |= modifiers; + modifiers = undefined; } - else { - updated[edge.name] = visited; + + if (visited !== value) { + setEdgeValue(updated, edge, visited); } } } @@ -617,6 +624,17 @@ namespace ts { return updated; } + /** + * Sets the value of an edge, adjusting the value as necessary for cases such as expression precedence. + */ + function setEdgeValue(parentNode: Node & Map, edge: NodeEdge, value: Node | NodeArray) { + if (value && edge.parenthesize && !isArray(value)) { + value = parenthesizeEdge(value, parentNode, edge.parenthesize, edge.test); + } + + parentNode[edge.name] = value; + } + /** * Visits a node edge. * @@ -627,7 +645,31 @@ namespace ts { function visitEdge(edge: NodeEdge, value: Node | NodeArray, visitor: (node: Node) => Node) { return isArray(value) ? visitNodes(>value, visitor, edge.test, /*start*/ undefined, /*count*/ undefined) - : visitNode(value, visitor, edge.test, edge.optional, edge.lift); + : visitNode(value, visitor, !edge.parenthesize ? edge.test : undefined, edge.optional, edge.lift); + } + + /** + * Applies parentheses to a node to ensure the correct precedence. + */ + function parenthesizeEdge(node: Node, parentNode: Node, parenthesize: (node: Node, parentNode: Node) => Node, test: (node: Node) => boolean) { + node = parenthesize(node, parentNode); + Debug.assert(test === undefined || test(node), "Unexpected node kind after visit."); + return node; + } + + /** + * Flattens an array of nodes that could contain NodeArrayNodes. + */ + export function flattenNodes(nodes: OneOrMore[]): T[] { + let result: T[]; + if (nodes) { + result = []; + for (const node of nodes) { + addNode(result, node); + } + } + + return result; } /** @@ -635,18 +677,9 @@ namespace ts { * * @param to The destination array. * @param from The source Node or NodeArrayNode. - * @param test The node test used to validate each node. */ - export function addNode(to: T[], from: OneOrMore, test?: (node: Node) => boolean) { - if (to && from) { - if (isNodeArrayNode(from)) { - addNodes(to, from.nodes, test); - } - else { - Debug.assert(test === undefined || test(from), "Wrong node type after visit."); - to.push(from); - } - } + export function addNode(to: T[], from: OneOrMore, startOnNewLine?: boolean) { + addNodeWorker(to, from, startOnNewLine, /*test*/ undefined) } /** @@ -654,12 +687,51 @@ namespace ts { * * @param to The destination NodeArray. * @param from The source array of Node or NodeArrayNode. - * @param test The node test used to validate each node. */ - export function addNodes(to: T[], from: OneOrMore[], test?: (node: Node) => boolean) { + export function addNodes(to: T[], from: OneOrMore[], startOnNewLine?: boolean) { + addNodesWorker(to, from, startOnNewLine, /*test*/ undefined); + } + + /** + * Appends a node to an array on a new line. + * + * @param to The destination array. + * @param from The source Node or NodeArrayNode. + */ + export function addLine(to: T[], from: OneOrMore) { + addNodeWorker(to, from, /*addOnNewLine*/ true, /*test*/ undefined); + } + + /** + * Appends an array of nodes to an array on new lines. + * + * @param to The destination NodeArray. + * @param from The source array of Node or NodeArrayNode. + */ + export function addLines(to: T[], from: OneOrMore[]) { + addNodesWorker(to, from, /*addOnNewLine*/ true, /*test*/ undefined); + } + + function addNodeWorker(to: T[], from: OneOrMore, addOnNewLine: boolean, test: (node: Node) => boolean) { + if (to && from) { + if (isNodeArrayNode(from)) { + addNodesWorker(to, from.nodes, addOnNewLine, test); + } + else { + Debug.assert(test === undefined || test(from), "Wrong node type after visit."); + if (addOnNewLine) { + startOnNewLine(from); + } + + to.push(from); + } + } + } + + function addNodesWorker(to: T[], from: OneOrMore[], addOnNewLine: boolean, test: (node: Node) => boolean) { if (to && from) { for (const node of from) { - addNode(to, node, test); + addNodeWorker(to, node, addOnNewLine, test); } } }