diff --git a/Jakefile.js b/Jakefile.js index 1929a3a475f..26bfe76c048 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -239,6 +239,7 @@ function concatenateFiles(destinationFile, sourceFiles) { var useDebugMode = true; var useTransforms = process.env.USE_TRANSFORMS || false; +var useTransformCompat = false; var host = (process.env.host || process.env.TYPESCRIPT_HOST || "node"); var compilerFilename = "tsc.js"; var LKGCompiler = path.join(LKGDirectory, compilerFilename); @@ -301,6 +302,9 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, noOu if (useBuiltCompiler && useTransforms) { options += " --experimentalTransforms" } + else if (useBuiltCompiler && useTransformCompat) { + options += " --transformCompatibleEmit" + } var cmd = host + " " + compilerPath + " " + options + " "; cmd = cmd + sources.join(" "); @@ -429,6 +433,10 @@ task("setTransforms", function() { useTransforms = true; }); +task("setTransformCompat", function() { + useTransformCompat = true; +}); + task("configure-nightly", [configureNightlyJs], function() { var cmd = host + " " + configureNightlyJs + " " + packageJson + " " + programTs; console.log(cmd); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index c65466f00de..307bb5d5403 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -322,9 +322,18 @@ namespace ts { description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking }, { + // this option will be removed when this is merged with master and exists solely + // to enable the tree transforming emitter side-by-side with the existing emitter. name: "experimentalTransforms", type: "boolean", experimental: true + }, + { + // this option will be removed when this is merged with master and exists solely + // to enable the tree transforming emitter side-by-side with the existing emitter. + name: "transformCompatibleEmit", + type: "boolean", + experimental: true } ]; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 3edbc2c6cce..47547ca94ac 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1919,6 +1919,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (multiLine) { decreaseIndent(); + if (!compilerOptions.transformCompatibleEmit) { + writeLine(); + } } write(")"); @@ -4335,7 +4338,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge writeLine(); emitStart(restParam); emitNodeWithCommentsAndWithoutSourcemap(restParam.name); - write(restIndex > 0 + write(restIndex > 0 || !compilerOptions.transformCompatibleEmit ? `[${tempName} - ${restIndex}] = arguments[${tempName}];` : `[${tempName}] = arguments[${tempName}];`); emitEnd(restParam); @@ -5357,7 +5360,7 @@ const _super = (function (geti, seti) { const isClassExpressionWithStaticProperties = staticProperties.length > 0 && node.kind === SyntaxKind.ClassExpression; let tempVariable: Identifier; - if (isClassExpressionWithStaticProperties) { + if (isClassExpressionWithStaticProperties && compilerOptions.transformCompatibleEmit) { tempVariable = createAndRecordTempVariable(TempFlags.Auto); write("("); increaseIndent(); @@ -5394,6 +5397,11 @@ const _super = (function (geti, seti) { writeLine(); emitConstructor(node, baseTypeNode); emitMemberFunctionsForES5AndLower(node); + if (!compilerOptions.transformCompatibleEmit) { + emitPropertyDeclarations(node, staticProperties); + writeLine(); + emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); + } writeLine(); emitToken(SyntaxKind.CloseBraceToken, node.members.end, () => { write("return "); @@ -5420,11 +5428,13 @@ const _super = (function (geti, seti) { write("))"); if (node.kind === SyntaxKind.ClassDeclaration) { write(";"); - emitPropertyDeclarations(node, staticProperties); - writeLine(); - emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); + if (compilerOptions.transformCompatibleEmit) { + emitPropertyDeclarations(node, staticProperties); + writeLine(); + emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined); + } } - else if (isClassExpressionWithStaticProperties) { + else if (isClassExpressionWithStaticProperties && compilerOptions.transformCompatibleEmit) { for (const property of staticProperties) { write(","); writeLine(); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 1ea66f9273b..0720906830f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -250,7 +250,7 @@ namespace ts { node.decorators = undefined; node.modifiers = undefined; node.typeParameters = undefined; - node.parameters = createSynthesizedNodeArray(parameters); + node.parameters = createNodeArray(parameters); node.type = undefined; node.body = body; return node; @@ -286,7 +286,7 @@ namespace ts { node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; node.type = undefined; - node.initializer = initializer; + node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -294,7 +294,7 @@ namespace ts { export function createArrayLiteral(elements?: Expression[]) { const node = createNode(SyntaxKind.ArrayLiteralExpression); - node.elements = createNodeArray(elements); + node.elements = parenthesizeListElements(createNodeArray(elements)); return node; } @@ -322,14 +322,16 @@ namespace ts { export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { const node = createNode(SyntaxKind.CallExpression, location); node.expression = parenthesizeForAccess(expression); - node.arguments = createNodeArray(argumentsArray); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); 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; + node.arguments = argumentsArray + ? parenthesizeListElements(createNodeArray(argumentsArray)) + : undefined; return node; } @@ -358,7 +360,7 @@ namespace ts { node.parameters = createNodeArray(parameters); node.type = undefined; node.equalsGreaterThanToken = createNode(SyntaxKind.EqualsGreaterThanToken); - node.body = body; + node.body = parenthesizeConciseBody(body); return node; } @@ -414,7 +416,7 @@ namespace ts { export function createSpread(expression: Expression) { const node = createNode(SyntaxKind.SpreadElementExpression); - node.expression = expression; + node.expression = parenthesizeExpressionForList(expression); return node; } @@ -424,8 +426,8 @@ namespace ts { node.modifiers = undefined; node.name = name; node.typeParameters = undefined; - node.heritageClauses = createSynthesizedNodeArray(heritageClauses); - node.members = createSynthesizedNodeArray(members); + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); return node; } @@ -469,7 +471,7 @@ namespace ts { export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { const node = createNode(SyntaxKind.VariableDeclaration, location); node.name = typeof name === "string" ? createIdentifier(name) : name; - node.initializer = initializer; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -479,7 +481,7 @@ namespace ts { export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { const node = createNode(SyntaxKind.ExpressionStatement, location); - node.expression = expression; + node.expression = parenthesizeExpressionForExpressionStatement(expression); return node; } @@ -562,8 +564,8 @@ namespace ts { setModifiers(node, modifiers); node.name = name; node.typeParameters = undefined; - node.heritageClauses = createSynthesizedNodeArray(heritageClauses); - node.members = createSynthesizedNodeArray(members); + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); return node; } @@ -599,7 +601,14 @@ namespace ts { export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { const node = createNode(SyntaxKind.HeritageClause, location); node.token = token; - node.types = createSynthesizedNodeArray(types); + node.types = createNodeArray(types); + return node; + } + + export function createCaseClause(expression: Expression, statements: Statement[], location?: TextRange) { + const node = createNode(SyntaxKind.CaseClause, location); + node.expression = parenthesizeExpressionForList(expression); + node.statements = createNodeArray(statements); return node; } @@ -609,7 +618,7 @@ namespace ts { const node = createNode(SyntaxKind.PropertyAssignment, location); node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; - node.initializer = initializer; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -1206,6 +1215,26 @@ namespace ts { : createParen(operand, /*location*/ operand); } + function parenthesizeListElements(elements: NodeArray) { + let result: Expression[]; + for (let i = 0; i < elements.length; i++) { + const element = parenthesizeExpressionForList(elements[i]); + if (result !== undefined || element !== elements[i]) { + if (result === undefined) { + result = elements.slice(0, i); + } + + result.push(element); + } + } + + if (result !== undefined) { + return createNodeArray(result, elements, elements.hasTrailingComma); + } + + return elements; + } + export function parenthesizeExpressionForList(expression: Expression) { const expressionPrecedence = getExpressionPrecedence(expression); const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); @@ -1231,6 +1260,14 @@ namespace ts { return expression; } + export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { + if (body.kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(body, /*location*/ body); + } + + return body; + } + function getLeftmostExpression(node: Expression): Expression { while (true) { switch (node.kind) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f10c327faa7..87e8775d1f0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,6 +1,7 @@ /// /// /// +/// namespace ts { /* @internal */ export let programTime = 0; diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 3866e4f9566..967fa0c715f 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -67,6 +67,11 @@ namespace ts { // Source map data let sourceMapData: SourceMapData; + // This keeps track of the number of times `disable` has been called without a + // corresponding call to `enable`. As long as this value is non-zero, mappings will not + // be recorded. + // This is primarily used to provide a better experience when debugging binding + // patterns and destructuring assignments for simple expressions. let disableDepth: number; return { @@ -160,12 +165,18 @@ namespace ts { disableDepth = 0; } + /** + * Re-enables the recording of mappings. + */ function enable() { if (disableDepth > 0) { disableDepth--; } } + /** + * Disables the recording of mappings. + */ function disable() { disableDepth++; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 7d91fc7e474..913fd95d55d 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -1,15 +1,47 @@ /// +/// +/// +/// +/// +/// +/// +/// /* @internal */ namespace ts { + const moduleTransformerMap: Map = { + [ModuleKind.ES6]: transformES6Module, + [ModuleKind.System]: transformSystemModule, + [ModuleKind.AMD]: transformModule, + [ModuleKind.CommonJS]: transformModule, + [ModuleKind.UMD]: transformModule, + [ModuleKind.None]: transformModule, + }; + const enum SyntaxKindFeatureFlags { ExpressionSubstitution = 1 << 0, EmitNotifications = 1 << 1, } export function getTransformers(compilerOptions: CompilerOptions) { + const jsx = compilerOptions.jsx; + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); const transformers: Transformer[] = []; - // TODO(rbuckton): Add transformers + + transformers.push(transformTypeScript); + transformers.push(moduleTransformerMap[moduleKind]); + + if (jsx === JsxEmit.React) { + transformers.push(transformJsx); + } + + transformers.push(transformES7); + + if (languageVersion < ScriptTarget.ES6) { + transformers.push(transformES6); + } + return transformers; } @@ -75,9 +107,7 @@ namespace ts { } currentSourceFile = sourceFile; - const visited = transformation(sourceFile); - currentSourceFile = undefined; - return visited; + return transformation(sourceFile); } function enableExpressionSubstitution(kind: SyntaxKind) { diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 05b2350226b..442201a1f7d 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -18,22 +18,29 @@ namespace ts { recordTempVariable: (node: Identifier) => void, visitor?: (node: Node) => Node) { - let location: TextRange = node; - let value = node.right; if (isEmptyObjectLiteralOrArrayLiteral(node.left)) { - return value; + return node.right; } + let location: TextRange = node; + let value = node.right; const expressions: Expression[] = []; if (needsValue) { - // Temporary assignment needed to emit root should highlight whole binary expression - value = ensureIdentifier(node.right, /*reuseIdentifierExpressions*/ true, node, emitTempVariableAssignment); + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignmen) is part of a larger expression), + // then we need to cache the right-hand value. + // + // The source map location for the assignment should point to the entire binary + // expression. + value = ensureIdentifier(node.right, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); } else if (nodeIsSynthesized(node)) { - // Source map node for root.left = root.right is root - // but if root is synthetic, which could be in below case, use the target which is { a } - // for ({a} of {a: string}) { - // } + // Generally, the source map location for a destructuring assignment is the root + // expression. + // + // However, if the root expression is synthesized (as in the case + // of the initializer when transforming a ForOfStatement), then the source map + // location should point to the right-hand value of the expression. location = node.right; } @@ -190,7 +197,7 @@ namespace ts { } if (isBinaryExpression(root)) { - emitDestructuringAssignment(root.left, value, location) + emitDestructuringAssignment(root.left, value, location); } else { emitBindingElement(root, value); @@ -255,21 +262,22 @@ namespace ts { function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) { const elements = target.elements; - if (elements.length !== 1) { + const numElements = elements.length; + if (numElements !== 1) { // For anything but a single element destructuring we need to generate a temporary // to ensure value is evaluated exactly once. // When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); } - for (let i = 0; i < elements.length; i++) { + for (let i = 0; i < numElements; i++) { const e = elements[i]; if (e.kind !== SyntaxKind.OmittedExpression) { // Assignment for target = value.propName should highligh whole property, hence use e as source map node if (e.kind !== SyntaxKind.SpreadElementExpression) { emitDestructuringAssignment(e, createElementAccess(value, createLiteral(i)), e); } - else if (i === elements.length - 1) { + else if (i === numElements - 1) { emitDestructuringAssignment((e).expression, createArraySlice(value, i), e); } } @@ -299,11 +307,11 @@ namespace ts { // so in that case, we'll intentionally create that temporary. value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target, emitTempVariableAssignment); } - for (let i = 0; i < elements.length; i++) { - let element = elements[i]; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; if (name.kind === SyntaxKind.ObjectBindingPattern) { // Rewrite element to a declaration with an initializer that fetches property - let propName = element.propertyName || element.name; + const propName = element.propertyName || element.name; emitBindingElement(element, createDestructuringPropertyAccess(value, propName)); } else if (element.kind !== SyntaxKind.OmittedExpression) { @@ -311,7 +319,7 @@ namespace ts { // Rewrite element to a declaration that accesses array element at index i emitBindingElement(element, createElementAccess(value, i)); } - else if (i === elements.length - 1) { + else if (i === numElements - 1) { emitBindingElement(element, createArraySlice(value, i)); } } @@ -332,16 +340,23 @@ namespace ts { ); } - function createDestructuringPropertyAccess(object: Expression, propertyName: PropertyName): LeftHandSideExpression { + /** + * Creates either a PropertyAccessExpression or an ElementAccessExpression for the + * right-hand side of a transformed destructuring assignment. + * + * @param expression The right-hand expression that is the source of the property. + * @param propertyName The destructuring property name. + */ + function createDestructuringPropertyAccess(expression: Expression, propertyName: PropertyName): LeftHandSideExpression { if (isComputedPropertyName(propertyName)) { return createElementAccess( - object, - ensureIdentifier(propertyName.expression, /*reuseIdentifierExpressions*/ false, propertyName, emitTempVariableAssignment) + expression, + ensureIdentifier(propertyName.expression, /*reuseIdentifierExpressions*/ false, /*location*/ propertyName, emitTempVariableAssignment) ); } else if (isIdentifier(propertyName)) { return createPropertyAccess( - object, + expression, propertyName.text ); } @@ -349,7 +364,7 @@ namespace ts { // We create a synthetic copy of the identifier in order to avoid the rewriting that might // otherwise occur when the identifier is emitted. return createElementAccess( - object, + expression, cloneNode(propertyName) ); } diff --git a/src/compiler/transformers/es7.ts b/src/compiler/transformers/es7.ts index e4d1e564cb8..c2079405081 100644 --- a/src/compiler/transformers/es7.ts +++ b/src/compiler/transformers/es7.ts @@ -5,8 +5,6 @@ namespace ts { // TODO(rbuckton): ES7->ES6 transformer export function transformES7(context: TransformationContext) { - const { hoistVariableDeclaration } = context; - return transformSourceFile; function transformSourceFile(node: SourceFile) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 8618e71f921..d791dd1eb4c 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -6,10 +6,8 @@ namespace ts { type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration; - // TODO(rbuckton): TS->ES7 transformer export function transformTypeScript(context: TransformationContext) { const { - nodeHasGeneratedName, getGeneratedNameForNode, makeUniqueName, setNodeEmitFlags, @@ -21,24 +19,46 @@ namespace ts { const resolver = context.getEmitResolver(); const compilerOptions = context.getCompilerOptions(); const languageVersion = getEmitScriptTarget(compilerOptions); - const decoratedClassAliases: Map = {}; - const currentDecoratedClassAliases: Map = {}; + + // Save the previous transformation hooks. const previousExpressionSubstitution = context.expressionSubstitution; const previousOnBeforeEmitNode = context.onBeforeEmitNode; const previousOnAfterEmitNode = context.onAfterEmitNode; - context.enableExpressionSubstitution(SyntaxKind.Identifier); + + // Set new transformation hooks. context.expressionSubstitution = substituteExpression; context.onBeforeEmitNode = onBeforeEmitNode; context.onAfterEmitNode = onAfterEmitNode; - let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; + // These variables contain state that changes as we descend into the tree. let currentSourceFile: SourceFile; let currentNamespace: ModuleDeclaration; let currentNamespaceLocalName: Identifier; let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentParent: Node; let currentNode: Node; - let isRightmostExpression: boolean; + + // These variables keep track of whether expression substitution has been enabled for + // specific edge cases. They are persisted between each SourceFile transformation and + // should not be reset. + let hasEnabledExpressionSubstitutionForDecoratedClasses = false; + let hasEnabledExpressionSubstitutionForNamespaceExports = false; + let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; + + // This map keeps track of aliases created for classes with decorators to avoid issues + // with the double-binding behavior of classes. + let decoratedClassAliases: Map; + + // This map keeps track of currently active aliases defined in `decoratedClassAliases` + // when just-in-time substitution occurs while printing an expression identifier. + let currentDecoratedClassAliases: Map; + + // This value keeps track of how deeply nested we are within any containing namespaces + // when performing just-in-time substitution while printing an expression identifier. + let namespaceNestLevel: number; + + // This array keeps track of containers where `super` is valid, for use with + // just-in-time substitution for `super` expressions inside of async methods. let superContainerStack: SuperContainer[]; return transformSourceFile; @@ -47,7 +67,6 @@ namespace ts { currentSourceFile = node; node = visitEachChild(node, visitor, context); setNodeEmitFlags(node, NodeEmitFlags.EmitEmitHelpers); - currentSourceFile = undefined; return node; } @@ -57,18 +76,23 @@ namespace ts { * @param node The node to visit. */ function visitWithStack(node: Node, visitor: (node: Node) => Node): Node { + // Save state const savedCurrentNamespace = currentNamespace; const savedCurrentScope = currentScope; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; - const savedIsRightmostExpression = isRightmostExpression; + + // Handle state changes before visiting a node. onBeforeVisitNode(node); + node = visitor(node); + + // Restore state currentNamespace = savedCurrentNamespace; currentScope = savedCurrentScope; currentParent = savedCurrentParent; currentNode = savedCurrentNode; - isRightmostExpression = savedIsRightmostExpression; + return node; } @@ -151,6 +175,11 @@ namespace ts { return visitorWorker(node); } + /** + * Branching visitor, visits a TypeScript syntax node. + * + * @param node The node to visit. + */ function visitTypeScript(node: Node): Node { // TypeScript ambient declarations are elided. if (node.flags & NodeFlags.Ambient) { @@ -207,7 +236,7 @@ namespace ts { case SyntaxKind.Constructor: // TypeScript constructors are elided. The constructor of a class will be - // reordered to the start of the member list in `transformClassDeclaration`. + // transformed as part of `transformClassDeclaration`. return undefined; case SyntaxKind.ClassDeclaration: @@ -334,11 +363,6 @@ namespace ts { currentScope = node; break; } - - // Keep track of whether this is the right-most expression of an expression tree. - // This is used to determine whether to parenthesize an `await` expression transformed - // into a `yield` expression. - isRightmostExpression = currentParent && isRightmost(currentParent, currentNode, isRightmostExpression); } /** @@ -471,11 +495,13 @@ namespace ts { // Record an alias to avoid class double-binding. if (resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.ClassWithBodyScopedClassBinding) { + enableExpressionSubstitutionForDecoratedClasses(); decoratedClassAlias = makeUniqueName(node.name ? node.name.text : "default"); decoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAlias; // We emit the class alias as a `let` declaration here so that it has the same // TDZ as the class. + // let ${decoratedClassAlias}; addNode(statements, createVariableStatement( @@ -538,7 +564,7 @@ namespace ts { // Write any decorators of the node. addNodes(statements, generateClassElementDecorationStatements(node, /*isStatic*/ false)); addNodes(statements, generateClassElementDecorationStatements(node, /*isStatic*/ true)); - addNode(statements, generateConstructorDecorationStatement(node, decoratedClassAlias)) + addNode(statements, generateConstructorDecorationStatement(node, decoratedClassAlias)); // If the class is exported as part of a TypeScript namespace, emit the namespace export. // Otherwise, if the class was exported at the top level and was decorated, emit an export @@ -567,7 +593,7 @@ namespace ts { * * @param node The node to transform. */ - function visitClassExpression(node: ClassExpression): LeftHandSideExpression { + function visitClassExpression(node: ClassExpression): Expression { const staticProperties = getInitializedProperties(node, /*isStatic*/ true); const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); const members = transformClassMembers(node, heritageClauses !== undefined); @@ -598,7 +624,7 @@ namespace ts { addNode(expressions, createAssignment(temp, classExpression)); addNodes(expressions, generateInitializedPropertyExpressions(node, staticProperties, temp)); addNode(expressions, temp); - return createParen(inlineExpressions(expressions)); + return inlineExpressions(expressions); } return classExpression; @@ -1070,7 +1096,7 @@ namespace ts { * Transforms all of the decorators for a declaration into an array of expressions. * * @param node The declaration node. - * @param allDecorators The AllDecorators object for the node. + * @param allDecorators An object containing all of the decorators for the declaration. */ function transformAllDecoratorsOfDeclaration(node: Declaration, allDecorators: AllDecorators) { if (!allDecorators) { @@ -1267,13 +1293,13 @@ namespace ts { */ function addTypeMetadata(node: Declaration, decoratorExpressions: Expression[]) { if (compilerOptions.emitDecoratorMetadata) { - if (shouldAppendTypeMetadata(node)) { + if (shouldAddTypeMetadata(node)) { decoratorExpressions.push(createMetadataHelper("design:type", serializeTypeOfNode(node), /*defer*/ true)); } - if (shouldAppendParamTypesMetadata(node)) { + if (shouldAddParamTypesMetadata(node)) { decoratorExpressions.push(createMetadataHelper("design:paramtypes", serializeParameterTypesOfNode(node), /*defer*/ true)); } - if (shouldAppendReturnTypeMetadata(node)) { + if (shouldAddReturnTypeMetadata(node)) { decoratorExpressions.push(createMetadataHelper("design:returntype", serializeReturnTypeOfNode(node), /*defer*/ true)); } } @@ -1281,12 +1307,12 @@ namespace ts { /** * Determines whether to emit the "design:type" metadata based on the node's kind. - * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata - * compiler option is set. + * The caller should have already tested whether the node has decorators and whether the + * emitDecoratorMetadata compiler option is set. * * @param node The node to test. */ - function shouldAppendTypeMetadata(node: Declaration): boolean { + function shouldAddTypeMetadata(node: Declaration): boolean { const kind = node.kind; return kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor @@ -1296,23 +1322,23 @@ namespace ts { /** * Determines whether to emit the "design:returntype" metadata based on the node's kind. - * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata - * compiler option is set. + * The caller should have already tested whether the node has decorators and whether the + * emitDecoratorMetadata compiler option is set. * * @param node The node to test. */ - function shouldAppendReturnTypeMetadata(node: Declaration): boolean { + function shouldAddReturnTypeMetadata(node: Declaration): boolean { return node.kind === SyntaxKind.MethodDeclaration; } /** * Determines whether to emit the "design:paramtypes" metadata based on the node's kind. - * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata - * compiler option is set. + * The caller should have already tested whether the node has decorators and whether the + * emitDecoratorMetadata compiler option is set. * * @param node The node to test. */ - function shouldAppendParamTypesMetadata(node: Declaration): boolean { + function shouldAddParamTypesMetadata(node: Declaration): boolean { const kind = node.kind; return kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.ClassExpression @@ -1442,7 +1468,7 @@ namespace ts { case SyntaxKind.TypePredicate: case SyntaxKind.BooleanKeyword: - return createIdentifier("Boolean") + return createIdentifier("Boolean"); case SyntaxKind.StringKeyword: case SyntaxKind.StringLiteral: @@ -1568,7 +1594,7 @@ namespace ts { * qualified name at runtime. */ function serializeQualifiedNameAsExpression(node: QualifiedName, useFallback: boolean): Expression { - let left: Expression + let left: Expression; if (node.left.kind === SyntaxKind.Identifier) { left = serializeEntityNameAsExpression(node.left, useFallback); } @@ -1974,7 +2000,7 @@ namespace ts { ) ); - const block = createBlock(statements); + const block = createBlock(statements, /*location*/ node.body); // Minor optimization, emit `_super` helper to capture `super` access in an arrow. // This step isn't needed if we eventually transform this to ES5. @@ -2000,25 +2026,6 @@ namespace ts { } } - function enableExpressionSubstitutionForAsyncMethodsWithSuper() { - if (!hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { - hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = true; - - // We need to enable substitutions for call, property access, and element access - // if we need to rewrite super calls. - context.enableExpressionSubstitution(SyntaxKind.CallExpression); - context.enableExpressionSubstitution(SyntaxKind.PropertyAccessExpression); - context.enableExpressionSubstitution(SyntaxKind.ElementAccessExpression); - - // We need to be notified when entering and exiting declarations that bind super. - context.enableEmitNotification(SyntaxKind.ClassDeclaration); - context.enableEmitNotification(SyntaxKind.MethodDeclaration); - context.enableEmitNotification(SyntaxKind.GetAccessor); - context.enableEmitNotification(SyntaxKind.SetAccessor); - context.enableEmitNotification(SyntaxKind.Constructor); - } - } - /** * Visits a parameter declaration node. * @@ -2198,7 +2205,7 @@ namespace ts { * @param member The enum member node. */ function transformEnumMemberDeclarationValue(member: EnumMember): Expression { - let value = resolver.getConstantValue(member); + const value = resolver.getConstantValue(member); if (value !== undefined) { return createLiteral(value); } @@ -2229,12 +2236,10 @@ namespace ts { * @param node The await expression node. */ function visitAwaitExpression(node: AwaitExpression): Expression { - const expression = createYield( + return createYield( visitNode(node.expression, visitor, isExpression), node ); - - return isRightmostExpression ? expression : createParen(expression); } /** @@ -2286,6 +2291,8 @@ namespace ts { Debug.assert(isIdentifier(node.name)); + enableExpressionSubstitutionForNamespaceExports(); + const savedCurrentNamespaceLocalName = currentNamespaceLocalName; const modifiers = visitNodes(node.modifiers, visitor, isModifier); const statements: Statement[] = []; @@ -2483,19 +2490,6 @@ namespace ts { return createStatement(expression, /*location*/ undefined); } - function createExportStatement(node: ClassExpression | ClassDeclaration | FunctionDeclaration): Statement { - const name = getDeclarationName(node); - if (currentNamespace) { - return createNamespaceExport(name, name); - } - else if (node.flags & NodeFlags.Default) { - return createExportDefault(name); - } - else { - return createModuleExport(name); - } - } - function createNamespaceExport(exportName: Identifier, exportValue: Expression, location?: TextRange) { return createStatement( createAssignment( @@ -2518,7 +2512,7 @@ namespace ts { name = getSynthesizedNode(name); return currentNamespaceLocalName ? createPropertyAccess(currentNamespaceLocalName, name) - : name + : name; } function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration) { @@ -2535,8 +2529,65 @@ namespace ts { : getClassPrototype(node); } + function onBeforeEmitNode(node: Node): void { + previousOnBeforeEmitNode(node); + + const kind = node.kind; + if (hasEnabledExpressionSubstitutionForDecoratedClasses + && kind === SyntaxKind.ClassDeclaration && node.decorators) { + currentDecoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAliases[getOriginalNodeId(node)]; + } + + if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + && (kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor)) { + + if (!superContainerStack) { + superContainerStack = []; + } + + superContainerStack.push(node); + } + + if (hasEnabledExpressionSubstitutionForNamespaceExports + && kind === SyntaxKind.ModuleDeclaration) { + namespaceNestLevel++; + } + } + + function onAfterEmitNode(node: Node): void { + previousOnAfterEmitNode(node); + + const kind = node.kind; + if (hasEnabledExpressionSubstitutionForDecoratedClasses + && kind === SyntaxKind.ClassDeclaration && node.decorators) { + currentDecoratedClassAliases[getOriginalNodeId(node)] = undefined; + } + + if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + && (kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor)) { + + if (superContainerStack) { + superContainerStack.pop(); + } + } + + if (hasEnabledExpressionSubstitutionForNamespaceExports + && kind === SyntaxKind.ModuleDeclaration) { + namespaceNestLevel--; + } + } + function substituteExpression(node: Expression): Expression { - node = previousExpressionSubstitution ? previousExpressionSubstitution(node) : node; + node = previousExpressionSubstitution(node); + switch (node.kind) { case SyntaxKind.Identifier: return substituteExpressionIdentifier(node); @@ -2556,8 +2607,10 @@ namespace ts { return node; } - function substituteExpressionIdentifier(node: Identifier) { - if (!nodeIsSynthesized(node) && resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { + function substituteExpressionIdentifier(node: Identifier): Expression { + if (hasEnabledExpressionSubstitutionForDecoratedClasses + && !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. @@ -2571,10 +2624,23 @@ namespace ts { } } + if (hasEnabledExpressionSubstitutionForNamespaceExports + && namespaceNestLevel > 0) { + // If we are nested within a namespace declaration, we may need to qualifiy + // an identifier that is exported from a merged namespace. + const original = getOriginalNode(node); + if (isIdentifier(original) && original.parent) { + const container = resolver.getReferencedExportContainer(original); + if (container && container.kind === SyntaxKind.ModuleDeclaration) { + return createPropertyAccess(getGeneratedNameForNode(container), node, /*location*/ node); + } + } + } + return node; } - function substituteCallExpression(node: CallExpression) { + function substituteCallExpression(node: CallExpression): Expression { const expression = node.expression; if (isSuperPropertyOrElementAccess(expression)) { const flags = getSuperContainerAsyncMethodFlags(); @@ -2624,6 +2690,54 @@ namespace ts { return node; } + function enableExpressionSubstitutionForAsyncMethodsWithSuper() { + if (!hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { + hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = true; + + // We need to enable substitutions for call, property access, and element access + // if we need to rewrite super calls. + context.enableExpressionSubstitution(SyntaxKind.CallExpression); + context.enableExpressionSubstitution(SyntaxKind.PropertyAccessExpression); + context.enableExpressionSubstitution(SyntaxKind.ElementAccessExpression); + + // We need to be notified when entering and exiting declarations that bind super. + context.enableEmitNotification(SyntaxKind.ClassDeclaration); + context.enableEmitNotification(SyntaxKind.MethodDeclaration); + context.enableEmitNotification(SyntaxKind.GetAccessor); + context.enableEmitNotification(SyntaxKind.SetAccessor); + context.enableEmitNotification(SyntaxKind.Constructor); + } + } + + function enableExpressionSubstitutionForDecoratedClasses() { + if (!hasEnabledExpressionSubstitutionForDecoratedClasses) { + hasEnabledExpressionSubstitutionForDecoratedClasses = true; + + // We need to enable substitutions for identifiers. This allows us to + // substitute class names inside of a class declaration. + context.enableExpressionSubstitution(SyntaxKind.Identifier); + + // Keep track of class aliases. + decoratedClassAliases = {}; + currentDecoratedClassAliases = {}; + } + } + + function enableExpressionSubstitutionForNamespaceExports() { + if (!hasEnabledExpressionSubstitutionForNamespaceExports) { + hasEnabledExpressionSubstitutionForNamespaceExports = true; + + // We need to enable substitutions for identifiers. This allows us to + // substitute the names of exported members of a namespace. + context.enableExpressionSubstitution(SyntaxKind.Identifier); + + // We need to be notified when entering and exiting namespaces. + context.enableEmitNotification(SyntaxKind.ModuleDeclaration); + + // Keep track of namespace nesting depth + namespaceNestLevel = 0; + } + } function createSuperAccessInAsyncMethod(argumentExpression: Expression, flags: NodeCheckFlags, location: TextRange): LeftHandSideExpression { if (flags & NodeCheckFlags.AsyncMethodWithSuperBinding) { @@ -2650,88 +2764,5 @@ namespace ts { return container !== undefined && resolver.getNodeCheckFlags(getOriginalNode(container)) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); } - - function onBeforeEmitNode(node: Node): void { - const kind = node.kind; - if (kind === SyntaxKind.ClassDeclaration && node.decorators) { - currentDecoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAliases[getOriginalNodeId(node)]; - } - - if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper - && (kind === SyntaxKind.ClassDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor)) { - - if (!superContainerStack) { - superContainerStack = []; - } - - superContainerStack.push(node); - } - } - - function onAfterEmitNode(node: Node): void { - const kind = node.kind; - if (kind === SyntaxKind.ClassDeclaration && node.decorators) { - currentDecoratedClassAliases[getOriginalNodeId(node)] = undefined; - } - - if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper - && (kind === SyntaxKind.ClassDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor)) { - - if (superContainerStack) { - superContainerStack.pop(); - } - } - } - - function isRightmost(parentNode: Node, node: Node, wasRightmost: boolean) { - switch (parentNode.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ReturnStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseClause: - case SyntaxKind.WithStatement: - case SyntaxKind.Decorator: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.SpreadElementExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.EnumMember: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.JsxExpression: - return true; - case SyntaxKind.TemplateSpan: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return node !== (parentNode).expression; - case SyntaxKind.BinaryExpression: - return wasRightmost && node === (parentNode).right; - case SyntaxKind.ConditionalExpression: - return wasRightmost && node === (parentNode).whenTrue; - default: - return wasRightmost; - } - } } } \ No newline at end of file diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2348c4d2b03..6a55153f631 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2470,6 +2470,7 @@ namespace ts { allowJs?: boolean; /* @internal */ stripInternal?: boolean; /* @internal */ experimentalTransforms?: boolean; + /* @internal */ transformCompatibleEmit?: boolean; // Skip checking lib.d.ts to help speed up tests. /* @internal */ skipDefaultLibCheck?: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 446bddbff10..fa990528072 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2792,7 +2792,7 @@ namespace ts { else if (kind === SyntaxKind.ConditionalExpression) { return isSimpleExpressionWorker((node).condition, depth + 1) && isSimpleExpressionWorker((node).whenTrue, depth + 1) - && isSimpleExpressionWorker((node).whenFalse, depth + 1) + && isSimpleExpressionWorker((node).whenFalse, depth + 1); } else if (kind === SyntaxKind.VoidExpression || kind === SyntaxKind.TypeOfExpression diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 838f00cd1b1..05f8e775586 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1,4 +1,5 @@ /// +/// /* @internal */ namespace ts { @@ -163,7 +164,7 @@ namespace ts { { name: "typeParameters", test: isTypeParameter }, { name: "parameters", test: isParameter }, { name: "type", test: isTypeNode, optional: true }, - { name: "body", test: isConciseBody, lift: liftToBlock }, + { name: "body", test: isConciseBody, lift: liftToBlock, parenthesize: parenthesizeConciseBody }, ], [SyntaxKind.DeleteExpression]: [ { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, @@ -408,7 +409,7 @@ namespace ts { { name: "expression", test: isExpression, optional: true }, ], [SyntaxKind.CaseClause]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForList }, { name: "statements", test: isStatement }, ], [SyntaxKind.DefaultClause]: [ @@ -478,25 +479,46 @@ namespace ts { * @param lift An optional callback to execute to lift a NodeArrayNode into a valid Node. */ export function visitNode(node: T, visitor: (node: Node) => Node, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { + return visitNodeWorker(node, visitor, test, optional, lift, /*parenthesize*/ undefined, /*parentNode*/ undefined); + } + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param optional A value indicating whether the Node is itself optional. + * @param lift A callback to execute to lift a NodeArrayNode into a valid Node. + * @param parenthesize A callback used to parenthesize the node if needed. + * @param parentNode A parentNode for the node. + */ + function visitNodeWorker(node: Node, visitor: (node: Node) => Node, test: (node: Node) => boolean, optional: boolean, lift: (node: NodeArray) => Node, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node): Node { if (node === undefined) { return undefined; } - const visited = visitor(node); + let visited = visitor(node); if (visited === node) { return node; } - const lifted = liftNode(visited, lift); - if (lifted === undefined) { + if (visited !== undefined && isNodeArrayNode(visited)) { + visited = (lift || extractSingleNode)((>visited).nodes); + } + + if (parenthesize !== undefined && visited !== undefined) { + visited = parenthesize(visited, parentNode); + } + + if (visited === undefined) { Debug.assert(optional, "Node not optional."); return undefined; } Debug.assert(test === undefined || test(visited), "Wrong node type after visit."); aggregateTransformFlags(visited); - visited.original = node; - return visited; + return visited; } /** @@ -509,11 +531,24 @@ namespace ts { * @param count An optional value indicating the maximum number of nodes to visit. */ export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test: (node: Node) => boolean, start?: number, count?: number): TArray { + return visitNodesWorker(nodes, visitor, test, /*parenthesize*/ undefined, /*parentNode*/ undefined, start, count); + } + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + function visitNodesWorker(nodes: NodeArray, visitor: (node: Node) => Node, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, start: number, count: number): NodeArray { if (nodes === undefined) { return undefined; } - let updated: T[]; + let updated: Node[]; // Ensure start and count have valid values const length = nodes.length; @@ -533,25 +568,21 @@ namespace ts { // Visit each original node. for (let i = 0; i < count; i++) { const node = nodes[i + start]; - const visited = node && >visitor(node); + const visited = node !== undefined ? visitor(node) : undefined; if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { // Ensure we have a copy of `nodes`, up to the current index. updated = nodes.slice(0, i); } - if (visited !== node) { - aggregateTransformFlags(visited); - } - - addNodeWorker(updated, visited, /*addOnNewLine*/ undefined, test); + addNodeWorker(updated, visited, /*addOnNewLine*/ undefined, test, parenthesize, parentNode, /*isVisiting*/ visited !== node); } } if (updated !== undefined) { - return (isModifiersArray(nodes) + return isModifiersArray(nodes) ? createModifiersArray(updated, nodes) - : createNodeArray(updated, nodes, nodes.hasTrailingComma)); + : createNodeArray(updated, nodes, nodes.hasTrailingComma); } return nodes; @@ -584,9 +615,17 @@ namespace ts { 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; + let visited: Node | NodeArray; + if (isArray(value)) { + const visitedArray = visitNodesWorker(value, visitor, edge.test, edge.parenthesize, node, 0, value.length); + if (isModifiersArray(visitedArray)) { + modifiers = visitedArray.flags; + } + + visited = visitedArray; + } + else { + visited = visitNodeWorker(value, visitor, edge.test, edge.optional, edge.lift, edge.parenthesize, node); } if (updated !== undefined || visited !== value) { @@ -601,7 +640,7 @@ namespace ts { } if (visited !== value) { - setEdgeValue(updated, edge, visited); + updated[edge.name] = visited; } } } @@ -627,39 +666,6 @@ namespace ts { return updated; } - /** - * Visits a node edge. - * - * @param edge The edge of the Node. - * @param value The Node or NodeArray value for the edge. - * @param visitor A callback used to visit the node. - */ - 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); - } - - /** - * 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; - } - - /** - * 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; - } - /** * Appends a node to an array. * @@ -668,7 +674,7 @@ namespace ts { * @param test The node test used to validate each node. */ export function addNode(to: T[], from: OneOrMany, startOnNewLine?: boolean) { - addNodeWorker(to, from, startOnNewLine, /*test*/ undefined); + addNodeWorker(to, from, startOnNewLine, /*test*/ undefined, /*parenthesize*/ undefined, /*parentNode*/ undefined, /*isVisiting*/ false); } /** @@ -679,29 +685,38 @@ namespace ts { * @param test The node test used to validate each node. */ export function addNodes(to: T[], from: OneOrMany[], startOnNewLine?: boolean) { - addNodesWorker(to, from, startOnNewLine, /*test*/ undefined); + addNodesWorker(to, from, startOnNewLine, /*test*/ undefined, /*parenthesize*/ undefined, /*parentNode*/ undefined, /*isVisiting*/ false); } - function addNodeWorker(to: T[], from: OneOrMany, startOnNewLine: boolean, test: (node: Node) => boolean) { + function addNodeWorker(to: Node[], from: OneOrMany, startOnNewLine: boolean, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, isVisiting: boolean) { if (to && from) { if (isNodeArrayNode(from)) { - addNodesWorker(to, from.nodes, startOnNewLine, test); + addNodesWorker(to, from.nodes, startOnNewLine, test, parenthesize, parentNode, isVisiting); + return; } - else { - Debug.assert(test === undefined || test(from), "Wrong node type after visit."); - if (startOnNewLine) { - from.startsOnNewLine = true; - } - to.push(from); + if (parenthesize !== undefined) { + from = parenthesize(from, parentNode); } + + Debug.assert(test === undefined || test(from), "Wrong node type after visit."); + + if (startOnNewLine) { + from.startsOnNewLine = true; + } + + if (isVisiting) { + aggregateTransformFlags(from); + } + + to.push(from); } } - function addNodesWorker(to: T[], from: OneOrMany[], startOnNewLine: boolean, test: (node: Node) => boolean) { + function addNodesWorker(to: Node[], from: OneOrMany[], startOnNewLine: boolean, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, isVisiting: boolean) { if (to && from) { for (const node of from) { - addNodeWorker(to, node, startOnNewLine, test); + addNodeWorker(to, node, startOnNewLine, test, parenthesize, parentNode, isVisiting); } } } @@ -841,26 +856,6 @@ namespace ts { return createNodeArray(concatenate(statements, declarations), /*location*/ statements); } - /** - * Tries to lift a NodeArrayNode to a Node. This is primarily used to - * lift multiple statements into a single Block. - * - * @param node The visited Node. - * @param options Options used to control lift behavior. - */ - function liftNode(node: Node, lifter: (nodes: NodeArray) => Node): Node { - if (node === undefined) { - return undefined; - } - else if (isNodeArrayNode(node)) { - const lift = lifter || extractSingleNode; - return lift(node.nodes); - } - else { - return node; - } - } - /** * Lifts a NodeArray containing only Statement nodes to a block. * diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 248d81cb072..e56e2805c3e 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -204,7 +204,7 @@ namespace ts.formatting { // - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually // - parent and child are not on the same line let useActualIndentation = - (isDeclaration(current) || isStatement(current)) && + (isDeclaration(current) || isStatementButNotDeclaration(current)) && (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); if (!useActualIndentation) {