From 86091d7217162bab577e3ab182712fe3c7b3849f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 25 Oct 2016 17:25:28 -0700 Subject: [PATCH] Clean up and consolidate destructuring transform --- src/compiler/binder.ts | 4 +- src/compiler/emitter.ts | 4 +- src/compiler/factory.ts | 6 +- src/compiler/transformers/destructuring.ts | 781 ++++++++++-------- src/compiler/transformers/es2015.ts | 55 +- src/compiler/transformers/module/module.ts | 10 +- src/compiler/transformers/module/system.ts | 11 +- src/compiler/transformers/ts.ts | 7 +- src/compiler/types.ts | 22 +- src/compiler/utilities.ts | 18 +- src/compiler/visitor.ts | 8 +- .../reference/assignmentTypeNarrowing.js | 10 +- .../reference/asyncMethodWithSuper_es5.js | 8 +- .../computedPropertiesInDestructuring1.js | 16 +- .../reference/declarationsAndAssignments.js | 6 +- .../destructuringAssignmentWithDefault.js | 2 +- .../emptyAssignmentPatterns02_ES5.js | 4 +- .../emptyAssignmentPatterns04_ES5.js | 5 +- .../initializePropertiesWithRenamedLet.js | 5 +- .../reference/missingAndExcessProperties.js | 16 +- ...thandPropertyAssignmentsInDestructuring.js | 40 +- 21 files changed, 592 insertions(+), 446 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f5f4b231ad1..26463ca939a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1007,7 +1007,7 @@ namespace ts { currentFlow = finishFlowLabel(preFinallyLabel); bind(node.finallyBlock); // if flow after finally is unreachable - keep it - // otherwise check if flows after try and after catch are unreachable + // otherwise check if flows after try and after catch are unreachable // if yes - convert current flow to unreachable // i.e. // try { return "1" } finally { console.log(1); } @@ -3077,7 +3077,7 @@ namespace ts { case SyntaxKind.SpreadElementExpression: // This node is ES6 syntax, but is handled by a containing node. - transformFlags |= TransformFlags.ContainsSpreadElementExpression; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsSpreadElementExpression; break; case SyntaxKind.SuperKeyword: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index ed16ec06159..5be5ae9a71d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2025,9 +2025,7 @@ namespace ts { for (let i = 0; i < lines.length; i++) { const line = indentation ? lines[i].slice(indentation) : lines[i]; if (line.length) { - if (i > 0) { - writeLine(); - } + writeLine(); write(line); } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 4400e8f2a83..7791923be3f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1699,6 +1699,8 @@ namespace ts { } } + // Utilities + export interface CallBinding { target: LeftHandSideExpression; thisArg: Expression; @@ -2733,7 +2735,9 @@ namespace ts { */ export function addEmitHelper(node: T, helper: EmitHelper): T { const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); + if (!contains(emitNode.helpers, helper)) { + emitNode.helpers = append(emitNode.helpers, helper); + } return node; } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index ab5c4629e95..837c6c57bb7 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -3,137 +3,146 @@ /*@internal*/ namespace ts { + type EffectiveBindingElement + = VariableDeclaration + | ParameterDeclaration + | BindingElement + | ObjectLiteralElementLike + | Expression + ; + + type EffectiveObjectBindingPattern + = ObjectBindingPattern + | ObjectLiteralExpression + ; + + type EffectiveArrayBindingPattern + = ArrayBindingPattern + | ArrayLiteralExpression + ; + + type EffectiveBindingPattern + = EffectiveObjectBindingPattern + | EffectiveArrayBindingPattern + ; + + type EffectiveBindingTarget + = EffectiveBindingPattern + | Expression + ; + /** - * Flattens a destructuring assignment expression. + * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. * - * @param root The destructuring assignment expression. + * @param node The node to flatten. * @param needsValue Indicates whether the value from the right-hand-side of the - * destructuring assignment is needed as part of a larger expression. + * destructuring assignment is needed as part of a larger expression. + * @param createAssignmentCallback A callback used to create an assignment expression. * @param recordTempVariable A callback used to record new temporary variables. - * @param visitor An optional visitor to use to visit expressions. + * @param visitor An optional visitor used to visit default value initializers of binding patterns. */ - export function flattenDestructuringAssignment( - context: TransformationContext, - node: BinaryExpression, + export function flattenDestructuringToExpression( + node: VariableDeclaration | DestructuringAssignment, needsValue: boolean, + createAssignmentCallback: (target: Expression, value: Expression, location?: TextRange) => Expression, recordTempVariable: (node: Identifier) => void, visitor?: (node: Node) => VisitResult): Expression { - if (isEmptyObjectLiteralOrArrayLiteral(node.left)) { - const right = node.right; - if (isDestructuringAssignment(right)) { - return flattenDestructuringAssignment(context, right, needsValue, recordTempVariable, visitor); - } - else { - return node.right; - } - } - let location: TextRange = node; - let value = node.right; + let value: Expression; + if (isDestructuringAssignment(node)) { + value = node.right; + while (isEmptyObjectLiteralOrArrayLiteral(node.left)) { + if (isDestructuringAssignment(value)) { + location = node = value; + value = node.right; + } + else { + return value; + } + } + } + const expressions: Expression[] = []; - if (needsValue) { - // 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(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment, visitor); - } - else if (nodeIsSynthesized(node)) { - // 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 = value; + + if (value) { + value = visitNode(value, visitor, isExpression); + if (needsValue) { + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignment 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(value, /*reuseIdentifierExpressions*/ true, emitTempVariableAssignment, location); + } + else if (nodeIsSynthesized(node)) { + // 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 = value; + } } - flattenDestructuring(node, value, location, emitAssignment, emitTempVariableAssignment, visitor); + flattenEffectiveBindingElement(node, value, isDestructuringAssignment(node), emitAssignment, emitTempVariableAssignment, visitor, location); - if (needsValue) { + if (value && needsValue) { expressions.push(value); } - const expression = inlineExpressions(expressions); - aggregateTransformFlags(expression); - return expression; - - function emitAssignment(name: Identifier, value: Expression, location: TextRange) { - const expression = createAssignment(name, value, location); + return aggregateTransformFlags(inlineExpressions(expressions)); + function emitAssignment(target: Expression, value: Expression, location: TextRange, original: Node) { + const expression = createAssignmentCallback(target, value, location); + expression.original = original; // NOTE: this completely disables source maps, but aligns with the behavior of // `emitAssignment` in the old emitter. setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); - aggregateTransformFlags(expression); expressions.push(expression); } function emitTempVariableAssignment(value: Expression, location: TextRange) { const name = createTempVariable(recordTempVariable); - emitAssignment(name, value, location); - return name; - } - } - - /** - * Flattens binding patterns in a parameter declaration. - * - * @param node The ParameterDeclaration to flatten. - * @param value The rhs value for the binding pattern. - * @param visitor An optional visitor to use to visit expressions. - */ - export function flattenParameterDestructuring( - node: ParameterDeclaration, - value: Expression, - visitor?: (node: Node) => VisitResult) { - const declarations: VariableDeclaration[] = []; - - flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); - - return declarations; - - function emitAssignment(name: Identifier, value: Expression, location: TextRange) { - const declaration = createVariableDeclaration(name, /*type*/ undefined, value, location); - + const expression = createAssignment(name, value, location); // NOTE: this completely disables source maps, but aligns with the behavior of // `emitAssignment` in the old emitter. - setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps); - - aggregateTransformFlags(declaration); - declarations.push(declaration); - } - - function emitTempVariableAssignment(value: Expression, location: TextRange) { - const name = createTempVariable(/*recordTempVariable*/ undefined); - emitAssignment(name, value, location); + setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); + aggregateTransformFlags(expression); + expressions.push(expression); return name; } } /** - * Flattens binding patterns in a variable declaration. + * Flattens binding patterns in a VariableDeclaration or a ParameterDeclaration to a VariableDeclaration list. * - * @param node The VariableDeclaration to flatten. - * @param value An optional rhs value for the binding pattern. + * @param node The node to flatten. + * @param boundValue An optional rhs value for the binding pattern. This value is *not* visited during flattening. * @param visitor An optional visitor to use to visit expressions. + * @param recordTempVariable An optional callback used to hoist temporary variables rather than + * declaring them inline. */ - export function flattenVariableDestructuring( - node: VariableDeclaration, - value?: Expression, - visitor?: (node: Node) => VisitResult, - recordTempVariable?: (node: Identifier) => void) { - const declarations: VariableDeclaration[] = []; + export function flattenDestructuring( + node: VariableDeclaration | ParameterDeclaration, + boundValue?: Expression, + recordTempVariable?: (node: Identifier) => void, + visitor?: (node: Node) => VisitResult) { let pendingAssignments: Expression[]; - flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); - + const declarations: VariableDeclaration[] = []; + flattenEffectiveBindingElement(node, boundValue, /*skipInitializer*/ false, emitAssignment, emitTempVariableAssignment, visitor, /*location*/ node); return declarations; - function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { + function emitAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { + if (!isIdentifier(name)) { + Debug.failBadSyntaxKind(name, "Identifier expected."); + return; + } + if (pendingAssignments) { pendingAssignments.push(value); value = inlineExpressions(pendingAssignments); @@ -145,22 +154,13 @@ namespace ts { // NOTE: this completely disables source maps, but aligns with the behavior of // `emitAssignment` in the old emitter. - setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps); - - declarations.push(declaration); - aggregateTransformFlags(declaration); + declarations.push(aggregateTransformFlags(setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps))); } function emitTempVariableAssignment(value: Expression, location: TextRange) { const name = createTempVariable(recordTempVariable); if (recordTempVariable) { - const assignment = createAssignment(name, value, location); - if (pendingAssignments) { - pendingAssignments.push(assignment); - } - else { - pendingAssignments = [assignment]; - } + pendingAssignments = append(pendingAssignments, createAssignment(name, value, location)); } else { emitAssignment(name, value, location, /*original*/ undefined); @@ -169,244 +169,365 @@ namespace ts { } } + function flattenEffectiveBindingElement( + bindingElement: EffectiveBindingElement, + boundValue: Expression | undefined, + skipInitializer: boolean, + emitAssignment: (target: Expression, value: Expression, location: TextRange, original: Node) => void, + emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, + visitor: ((node: Node) => VisitResult) | undefined, + location: TextRange) { + + if (!skipInitializer) { + const initializer = visitNode(getInitializerOfEffectiveBindingElement(bindingElement), visitor, isExpression); + if (initializer) { + // Combine value and initializer + boundValue = boundValue ? createDefaultValueCheck(boundValue, initializer, location, emitTempVariableAssignment) : initializer; + } + else if (!boundValue) { + // Use 'void 0' in absence of value and initializer + boundValue = createVoidZero(); + } + } + + const bindingTarget = getTargetOfEffectiveBindingElement(bindingElement); + if (isEffectiveBindingPattern(bindingTarget)) { + const elements = getElementsOfEffectiveBindingPattern(bindingTarget); + const numElements = elements.length; + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + const reuseIdentifierExpressions = !isDeclarationBindingElement(bindingElement) || numElements !== 0; + boundValue = ensureIdentifier(boundValue, reuseIdentifierExpressions, emitTempVariableAssignment, location); + } + + if (isEffectiveObjectBindingPattern(bindingTarget)) { + for (const element of elements) { + // Rewrite element to a declaration with an initializer that fetches property + flattenEffectiveBindingElement( + element, + createDestructuringPropertyAccess( + boundValue, + getEffectivePropertyNameOfEffectiveBindingElement(element), + emitTempVariableAssignment), + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + visitor, + /*location*/ element); + } + } + else { + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (!isOmittedExpression(element)) { + if (!isEffectiveBindingElementWithRest(element)) { + // Rewrite element to a declaration that accesses array element at index i + flattenEffectiveBindingElement( + element, + createElementAccess(boundValue, i), + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + visitor, + /*location*/ element); + } + else if (i === numElements - 1) { + flattenEffectiveBindingElement( + element, + createArraySlice(boundValue, i), + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + visitor, + /*location*/ element); + } + } + } + } + } + else { + emitAssignment(bindingTarget, boundValue, location, /*original*/ bindingElement); + } + } + /** - * Flattens binding patterns in a variable declaration and transforms them into an expression. - * - * @param node The VariableDeclaration to flatten. - * @param recordTempVariable A callback used to record new temporary variables. - * @param createAssignmentCallback An optional callback used to create assignment expressions - * for non-temporary variables. - * @param visitor An optional visitor to use to visit expressions. + * Determines whether the BindingElement-like is a declaration */ - export function flattenVariableDestructuringToExpression( - node: VariableDeclaration, - recordTempVariable: (name: Identifier) => void, - createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression, - visitor?: (node: Node) => VisitResult) { - - const pendingAssignments: Expression[] = []; - - flattenDestructuring(node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment, visitor); - - const expression = inlineExpressions(pendingAssignments); - aggregateTransformFlags(expression); - return expression; - - function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { - const expression = createAssignmentCallback - ? createAssignmentCallback(name, value, location) - : createAssignment(name, value, location); - - emitPendingAssignment(expression, original); + function isDeclarationBindingElement(bindingElement: EffectiveBindingElement): bindingElement is VariableDeclaration | ParameterDeclaration | BindingElement { + switch (bindingElement.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + return true; } - function emitTempVariableAssignment(value: Expression, location: TextRange) { - const name = createTempVariable(recordTempVariable); - emitPendingAssignment(createAssignment(name, value, location), /*original*/ undefined); - return name; + return false; + } + + /** + * Gets the initializer of a BindingElement-like element + */ + function getInitializerOfEffectiveBindingElement(bindingElement: EffectiveBindingElement): Expression | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `1` in `let { a = 1 } = ...` + // `1` in `let { a: b = 1 } = ...` + // `1` in `let { a: {b} = 1 } = ...` + // `1` in `let { a: [b] = 1 } = ...` + // `1` in `let [a = 1] = ...` + // `1` in `let [{a} = 1] = ...` + // `1` in `let [[a] = 1] = ...` + return bindingElement.initializer; } - function emitPendingAssignment(expression: Expression, original: Node) { - expression.original = original; + if (isPropertyAssignment(bindingElement)) { + // `1` in `({ a: b = 1 } = ...)` + // `1` in `({ a: {b} = 1 } = ...)` + // `1` in `({ a: [b] = 1 } = ...)` + return isAssignmentExpression(bindingElement.initializer, /*excludeCompoundAssignment*/ true) + ? bindingElement.initializer.right + : undefined; + } - // NOTE: this completely disables source maps, but aligns with the behavior of - // `emitAssignment` in the old emitter. - setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); + if (isShorthandPropertyAssignment(bindingElement)) { + // `1` in `({ a = 1 } = ...)` + return bindingElement.objectAssignmentInitializer; + } - pendingAssignments.push(expression); + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `1` in `[a = 1] = ...` + // `1` in `[{a} = 1] = ...` + // `1` in `[[a] = 1] = ...` + return bindingElement.right; + } + + if (isSpreadElementExpression(bindingElement) || isPartiallyEmittedExpression(bindingElement)) { + // Recovery consistent with existing emit. + return getInitializerOfEffectiveBindingElement(bindingElement.expression); } } - function flattenDestructuring( - root: VariableDeclaration | ParameterDeclaration | BindingElement | BinaryExpression, - value: Expression, - location: TextRange, - emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void, - emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, - visitor?: (node: Node) => VisitResult) { - if (value && visitor) { - value = visitNode(value, visitor, isExpression); + /** + * Gets the name of a BindingElement-like element + */ + function getTargetOfEffectiveBindingElement(bindingElement: EffectiveBindingElement): EffectiveBindingTarget { + if (isDeclarationBindingElement(bindingElement)) { + // `a` in `let { a } = ...` + // `a` in `let { a = 1 } = ...` + // `b` in `let { a: b } = ...` + // `b` in `let { a: b = 1 } = ...` + // `{b}` in `let { a: {b} } = ...` + // `{b}` in `let { a: {b} = 1 } = ...` + // `[b]` in `let { a: [b] } = ...` + // `[b]` in `let { a: [b] = 1 } = ...` + // `a` in `let [a] = ...` + // `a` in `let [a = 1] = ...` + // `a` in `let [...a] = ...` + // `{a}` in `let [{a}] = ...` + // `{a}` in `let [{a} = 1] = ...` + // `[a]` in `let [[a]] = ...` + // `[a]` in `let [[a] = 1] = ...` + return bindingElement.name; } - if (isBinaryExpression(root)) { - emitDestructuringAssignment(root.left, value, location); + if (isObjectLiteralElementLike(bindingElement)) { + switch (bindingElement.kind) { + case SyntaxKind.PropertyAssignment: + // `b` in `({ a: b } = ...)` + // `b` in `({ a: b = 1 } = ...)` + // `{b}` in `({ a: {b} } = ...)` + // `{b}` in `({ a: {b} = 1 } = ...)` + // `[b]` in `({ a: [b] } = ...)` + // `[b]` in `({ a: [b] = 1 } = ...)` + // `b.c` in `({ a: b.c } = ...)` + // `b.c` in `({ a: b.c = 1 } = ...)` + // `b[0]` in `({ a: b[0] } = ...)` + // `b[0]` in `({ a: b[0] = 1 } = ...)` + return getTargetOfEffectiveBindingElement(bindingElement.initializer); + + case SyntaxKind.ShorthandPropertyAssignment: + // `a` in `({ a } = ...)` + // `a` in `({ a = 1 } = ...)` + return bindingElement.name; + } + + // no target + return undefined; + } + + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `a` in `[a = 1] = ...` + // `{a}` in `[{a} = 1] = ...` + // `[a]` in `[[a] = 1] = ...` + // `a.b` in `[a.b = 1] = ...` + // `a[0]` in `[a[0] = 1] = ...` + return getTargetOfEffectiveBindingElement(bindingElement.left); + } + + if (isSpreadElementExpression(bindingElement) || isPartiallyEmittedExpression(bindingElement)) { + // `a` in `[...a] = ...` + return getTargetOfEffectiveBindingElement(bindingElement.expression); + } + + // `a` in `[a] = ...` + // `{a}` in `[{a}] = ...` + // `[a]` in `[[a]] = ...` + // `a.b` in `[a.b] = ...` + // `a[0]` in `[a[0]] = ...` + return bindingElement; + } + + /** + * Determines whether a BindingElement-like element is a rest element. + */ + function isEffectiveBindingElementWithRest(bindingElement: EffectiveBindingElement) { + switch (bindingElement.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + // `...` in `let [...a] = ...` + return (bindingElement).dotDotDotToken !== undefined; + + case SyntaxKind.SpreadElementExpression: + // `...` in `[...a] = ...` + return true; + } + + return false; + } + + /** + * Gets the property name of a BindingElement-like element + */ + function getEffectivePropertyNameOfEffectiveBindingElement(bindingElement: EffectiveBindingElement) { + switch (bindingElement.kind) { + case SyntaxKind.BindingElement: + // `a` in `let { a: b } = ...` + // `[a]` in `let { [a]: b } = ...` + // `"a"` in `let { "a": b } = ...` + // `1` in `let { 1: b } = ...` + if ((bindingElement).propertyName) { + return (bindingElement).propertyName; + } + + break; + + case SyntaxKind.PropertyAssignment: + // `a` in `({ a: b } = ...)` + // `[a]` in `({ [a]: b } = ...)` + // `"a"` in `({ "a": b } = ...)` + // `1` in `({ 1: b } = ...)` + if ((bindingElement).name) { + return (bindingElement).name; + } + + break; + } + + const target = getTargetOfEffectiveBindingElement(bindingElement); + if (target && isPropertyName(target)) { + return target; + } + + Debug.fail("Invalid property name for binding element."); + } + + /** + * Determines whether a node is BindingPattern-like + */ + function isEffectiveBindingPattern(node: EffectiveBindingTarget): node is EffectiveBindingPattern { + return isEffectiveObjectBindingPattern(node) + || isEffectiveArrayBindingPattern(node); + } + + /** + * Determines whether a node is ObjectBindingPattern-like + */ + function isEffectiveObjectBindingPattern(node: EffectiveBindingTarget): node is EffectiveObjectBindingPattern { + switch (node.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return true; + } + + return false; + } + + /** + * Determines whether a node is ArrayBindingPattern-like + */ + function isEffectiveArrayBindingPattern(node: EffectiveBindingTarget): node is EffectiveArrayBindingPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return true; + } + + return false; + } + + /** + * Gets the elements of a BindingPattern-like name + */ + function getElementsOfEffectiveBindingPattern(name: EffectiveBindingPattern): EffectiveBindingElement[] { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + // `a` in `{a}` + // `a` in `[a]` + return name.elements; + + case SyntaxKind.ObjectLiteralExpression: + // `a` in `{a}` + return name.properties; + + case SyntaxKind.ArrayLiteralExpression: + // `a` in `[a]` + return name.elements; + } + } + + /** + * Creates an expression used to provide a default value if a value is `undefined` at runtime. + */ + function createDefaultValueCheck(value: Expression, defaultValue: Expression, location: TextRange, emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier): Expression { + value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, emitTempVariableAssignment, location); + return createConditional( + createStrictEquality(value, createVoidZero()), + createToken(SyntaxKind.QuestionToken), + defaultValue, + createToken(SyntaxKind.ColonToken), + value + ); + } + + /** + * 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. + * @param emitTempVariableAssignment A callback used to emit a temporary variable. + */ + function createDestructuringPropertyAccess(expression: Expression, propertyName: PropertyName, emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier): LeftHandSideExpression { + if (isComputedPropertyName(propertyName)) { + const argumentExpression = ensureIdentifier(propertyName.expression, /*reuseIdentifierExpressions*/ false, emitTempVariableAssignment, /*location*/ propertyName); + return createElementAccess(expression, argumentExpression); + } + else if (isLiteralExpression(propertyName)) { + const argumentExpression = getSynthesizedClone(propertyName); + argumentExpression.text = unescapeIdentifier(argumentExpression.text); + return createElementAccess(expression, argumentExpression); + } + else if (isGeneratedIdentifier(propertyName)) { + const name = getSynthesizedClone(propertyName); + name.text = unescapeIdentifier(name.text); + return createPropertyAccess(expression, name); } else { - emitBindingElement(root, value); - } - - 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)) { - const initializer = visitor - ? visitNode(bindingTarget.objectAssignmentInitializer, visitor, isExpression) - : bindingTarget.objectAssignmentInitializer; - - if (initializer) { - value = createDefaultValueCheck(value, initializer, location); - } - - target = bindingTarget.name; - } - else if (isBinaryExpression(bindingTarget) && bindingTarget.operatorToken.kind === SyntaxKind.EqualsToken) { - const initializer = visitor - ? visitNode(bindingTarget.right, visitor, isExpression) - : bindingTarget.right; - - value = createDefaultValueCheck(value, initializer, location); - target = bindingTarget.left; - } - else { - target = bindingTarget; - } - - if (target.kind === SyntaxKind.ObjectLiteralExpression) { - emitObjectLiteralAssignment(target, value, location); - } - else if (target.kind === SyntaxKind.ArrayLiteralExpression) { - emitArrayLiteralAssignment(target, value, location); - } - else { - const name = getMutableClone(target); - setSourceMapRange(name, target); - setCommentRange(name, target); - emitAssignment(name, value, location, /*original*/ undefined); - } - } - - function emitObjectLiteralAssignment(target: ObjectLiteralExpression, value: Expression, location: TextRange) { - const properties = target.properties; - if (properties.length !== 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 (const p of properties) { - if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) { - const propName = (p).name; - const target = p.kind === SyntaxKind.ShorthandPropertyAssignment ? p : (p).initializer || propName; - // Assignment for target = value.propName should highligh whole property, hence use p as source map node - emitDestructuringAssignment(target, createDestructuringPropertyAccess(value, propName), p); - } - } - } - - function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, location: TextRange) { - const elements = target.elements; - 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 < 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 === numElements - 1) { - emitDestructuringAssignment((e).expression, createArraySlice(value, i), e); - } - } - } - } - - function emitBindingElement(target: VariableDeclaration | ParameterDeclaration | BindingElement, value: Expression) { - // Any temporary assignments needed to emit target = value should point to target - const initializer = visitor ? visitNode(target.initializer, visitor, isExpression) : target.initializer; - if (initializer) { - // Combine value and initializer - value = value ? createDefaultValueCheck(value, initializer, target) : initializer; - } - else if (!value) { - // Use 'void 0' in absence of value and initializer - value = createVoidZero(); - } - - const name = target.name; - if (isBindingPattern(name)) { - const elements = name.elements; - const numElements = elements.length; - if (numElements !== 1) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target, emitTempVariableAssignment); - } - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (isOmittedExpression(element)) { - continue; - } - else if (name.kind === SyntaxKind.ObjectBindingPattern) { - // Rewrite element to a declaration with an initializer that fetches property - const propName = element.propertyName || element.name; - emitBindingElement(element, createDestructuringPropertyAccess(value, propName)); - } - else { - if (!element.dotDotDotToken) { - // Rewrite element to a declaration that accesses array element at index i - emitBindingElement(element, createElementAccess(value, i)); - } - else if (i === numElements - 1) { - emitBindingElement(element, createArraySlice(value, i)); - } - } - } - } - else { - emitAssignment(name, value, target, target); - } - } - - function createDefaultValueCheck(value: Expression, defaultValue: Expression, location: TextRange): Expression { - value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); - return createConditional( - createStrictEquality(value, createVoidZero()), - createToken(SyntaxKind.QuestionToken), - defaultValue, - createToken(SyntaxKind.ColonToken), - value - ); - } - - /** - * 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( - expression, - ensureIdentifier(propertyName.expression, /*reuseIdentifierExpressions*/ false, /*location*/ propertyName, emitTempVariableAssignment) - ); - } - else if (isLiteralExpression(propertyName)) { - const clone = getSynthesizedClone(propertyName); - clone.text = unescapeIdentifier(clone.text); - return createElementAccess(expression, clone); - } - else { - if (isGeneratedIdentifier(propertyName)) { - const clone = getSynthesizedClone(propertyName); - clone.text = unescapeIdentifier(clone.text); - return createPropertyAccess(expression, clone); - } - else { - return createPropertyAccess(expression, createIdentifier(unescapeIdentifier(propertyName.text))); - } - } + const name = createIdentifier(unescapeIdentifier(propertyName.text)); + return createPropertyAccess(expression, name); } } @@ -417,26 +538,20 @@ namespace ts { * * @param value the expression whose value needs to be bound. * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; - * false if it is necessary to always emit an identifier. - * @param location The location to use for source maps and comments. + * false if it is necessary to always emit an identifier. * @param emitTempVariableAssignment A callback used to emit a temporary variable. - * @param visitor An optional callback used to visit the value. + * @param location The location to use for source maps and comments. */ function ensureIdentifier( value: Expression, reuseIdentifierExpressions: boolean, - location: TextRange, emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, - visitor?: (node: Node) => VisitResult) { + location: TextRange) { if (isIdentifier(value) && reuseIdentifierExpressions) { return value; } else { - if (visitor) { - value = visitNode(value, visitor, isExpression); - } - return emitTempVariableAssignment(value, location); } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 608a2466964..1e8ab4f971e 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1,5 +1,6 @@ /// /// +/// /*@internal*/ namespace ts { @@ -404,6 +405,9 @@ namespace ts { case SyntaxKind.YieldExpression: return visitYieldExpression(node); + case SyntaxKind.SpreadElementExpression: + return visitSpreadElementExpression(node); + case SyntaxKind.SuperKeyword: return visitSuperKeyword(); @@ -1129,7 +1133,7 @@ namespace ts { createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList( - flattenParameterDestructuring(parameter, temp, visitor) + flattenDestructuring(parameter, temp, /*recordTempVariable*/ undefined, visitor) ) ), EmitFlags.CustomPrologue @@ -1660,18 +1664,21 @@ namespace ts { */ function visitParenthesizedExpression(node: ParenthesizedExpression, needsDestructuringValue: boolean): ParenthesizedExpression { // If we are here it is most likely because our expression is a destructuring assignment. - if (needsDestructuringValue) { + if (!needsDestructuringValue) { + // By default we always emit the RHS at the end of a flattened destructuring + // expression. If we are in a state where we do not need the destructuring value, + // we pass that information along to the children that care about it. switch (node.expression.kind) { case SyntaxKind.ParenthesizedExpression: - return createParen( - visitParenthesizedExpression(node.expression, /*needsDestructuringValue*/ true), - /*location*/ node + return updateParen( + node, + visitParenthesizedExpression(node.expression, /*needsDestructuringValue*/ false) ); case SyntaxKind.BinaryExpression: - return createParen( - visitBinaryExpression(node.expression, /*needsDestructuringValue*/ true), - /*location*/ node + return updateParen( + node, + visitBinaryExpression(node.expression, /*needsDestructuringValue*/ false) ); } } @@ -1689,7 +1696,13 @@ namespace ts { function visitBinaryExpression(node: BinaryExpression, needsDestructuringValue: boolean): Expression { // If we are here it is because this is a destructuring assignment. Debug.assert(isDestructuringAssignment(node)); - return flattenDestructuringAssignment(context, node, needsDestructuringValue, hoistVariableDeclaration, visitor); + return flattenDestructuringToExpression( + node, + needsDestructuringValue, + createAssignment, + hoistVariableDeclaration, + visitor + ); } function visitVariableStatement(node: VariableStatement): Statement { @@ -1701,15 +1714,17 @@ namespace ts { if (decl.initializer) { let assignment: Expression; if (isBindingPattern(decl.name)) { - assignment = flattenVariableDestructuringToExpression(decl, hoistVariableDeclaration, /*createAssignmentCallback*/ undefined, visitor); + assignment = flattenDestructuringToExpression(decl, /*needsValue*/ false, createAssignment, hoistVariableDeclaration, visitor); } else { assignment = createBinary(decl.name, SyntaxKind.EqualsToken, visitNode(decl.initializer, visitor, isExpression)); } - (assignments || (assignments = [])).push(assignment); + + assignments = append(assignments, assignment); } } if (assignments) { + // TODO(rbuckton): use inlineExpressions. return createStatement(reduceLeft(assignments, (acc, v) => createBinary(v, SyntaxKind.CommaToken, acc)), node); } else { @@ -1854,8 +1869,11 @@ namespace ts { if (isBindingPattern(node.name)) { const recordTempVariablesInLine = !enclosingVariableStatement || !hasModifier(enclosingVariableStatement, ModifierFlags.Export); - return flattenVariableDestructuring(node, /*value*/ undefined, visitor, - recordTempVariablesInLine ? undefined : hoistVariableDeclaration); + return flattenDestructuring( + node, + /*value*/ undefined, + recordTempVariablesInLine ? undefined : hoistVariableDeclaration, + visitor); } return visitEachChild(node, visitor, context); @@ -1956,9 +1974,10 @@ namespace ts { if (firstOriginalDeclaration && isBindingPattern(firstOriginalDeclaration.name)) { // This works whether the declaration is a var, let, or const. // It will use rhsIterationValue _a[_i] as the initializer. - const declarations = flattenVariableDestructuring( + const declarations = flattenDestructuring( firstOriginalDeclaration, createElementAccess(rhsReference, counter), + /*recordTempVariable*/ undefined, visitor ); @@ -2007,10 +2026,10 @@ namespace ts { // This is a destructuring pattern, so we flatten the destructuring instead. statements.push( createStatement( - flattenDestructuringAssignment( - context, + flattenDestructuringToExpression( assignment, /*needsValue*/ false, + createAssignment, hoistVariableDeclaration, visitor ) @@ -2840,7 +2859,7 @@ namespace ts { } function visitSpanOfSpreadElements(chunk: Expression[]): VisitResult { - return map(chunk, visitExpressionOfSpreadElement); + return map(chunk, visitSpreadElementExpression); } function visitSpanOfNonSpreadElements(chunk: Expression[], multiLine: boolean, hasTrailingComma: boolean): VisitResult { @@ -2856,7 +2875,7 @@ namespace ts { * * @param node A SpreadElementExpression node. */ - function visitExpressionOfSpreadElement(node: SpreadElementExpression) { + function visitSpreadElementExpression(node: SpreadElementExpression) { return visitNode(node.expression, visitor, isExpression); } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index b946d93f32d..b3c2178e99c 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -1,5 +1,6 @@ /// /// +/// /*@internal*/ namespace ts { @@ -45,6 +46,7 @@ namespace ts { let currentSourceFile: SourceFile; // The current file. let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file. let noSubstitution: Map; // Set of nodes for which substitution rules should be ignored. + let helperState: EmitHelperState; return transformSourceFile; @@ -62,6 +64,7 @@ namespace ts { currentSourceFile = node; currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); + helperState = { currentSourceFile, compilerOptions }; // Perform the transformation. const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; @@ -69,6 +72,7 @@ namespace ts { currentSourceFile = undefined; currentModuleInfo = undefined; + helperState = undefined; return aggregateTransformFlags(updated); } @@ -759,10 +763,12 @@ namespace ts { */ function transformInitializedVariable(node: VariableDeclaration): Expression { if (isBindingPattern(node.name)) { - return flattenVariableDestructuringToExpression( + return flattenDestructuringToExpression( node, + /*needsValue*/ false, + createExportExpression, hoistVariableDeclaration, - createExportExpression + /*visitor*/ undefined ); } else { diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 4b24d5c1641..f6d051d25ef 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -1,5 +1,6 @@ /// /// +/// /*@internal*/ namespace ts { @@ -40,6 +41,7 @@ namespace ts { let hoistedStatements: Statement[]; let enclosingBlockScopedContainer: Node; let noSubstitution: Map; // Set of nodes for which substitution rules should be ignored. + let helperState: EmitHelperState; return transformSourceFile; @@ -58,6 +60,7 @@ namespace ts { const id = getOriginalNodeId(node); currentSourceFile = node; enclosingBlockScopedContainer = node; + helperState = { currentSourceFile, compilerOptions }; // System modules have the following shape: // @@ -131,6 +134,7 @@ namespace ts { contextObject = undefined; hoistedStatements = undefined; enclosingBlockScopedContainer = undefined; + helperState = undefined; return aggregateTransformFlags(updated); } @@ -818,7 +822,7 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression { const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment; return isBindingPattern(node.name) - ? flattenVariableDestructuringToExpression(node, hoistVariableDeclaration, createAssignment, destructuringVisitor) + ? flattenDestructuringToExpression(node, /*needsValue*/ false, createAssignment, hoistVariableDeclaration, destructuringVisitor) : createAssignment(node.name, visitNode(node.initializer, destructuringVisitor, isExpression)); } @@ -1469,9 +1473,8 @@ namespace ts { */ function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult { if (hasExportedReferenceInDestructuringTarget(node.left)) { - return flattenDestructuringAssignment(context, node, /*needsValue*/ true, hoistVariableDeclaration, destructuringVisitor); + return flattenDestructuringToExpression(node, /*needsValue*/ true, createAssignment, hoistVariableDeclaration, destructuringVisitor); } - return visitEachChild(node, destructuringVisitor, context); } @@ -1481,7 +1484,7 @@ namespace ts { * @param node The destructuring target. */ function hasExportedReferenceInDestructuringTarget(node: Expression | ObjectLiteralElementLike): boolean { - if (isAssignmentExpression(node)) { + if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) { return hasExportedReferenceInDestructuringTarget(node.left); } else if (isSpreadElementExpression(node)) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 02c7b107b25..bf0a7a4b27e 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2312,12 +2312,7 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration): Expression { const name = node.name; if (isBindingPattern(name)) { - return flattenVariableDestructuringToExpression( - node, - hoistVariableDeclaration, - createNamespaceExportExpression, - visitor - ); + return flattenDestructuringToExpression(node, /*needsValue*/ false, createNamespaceExportExpression, hoistVariableDeclaration, visitor); } else { return createAssignment( diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 80c5d475dde..562e6cef26a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -635,9 +635,9 @@ namespace ts { export interface ParameterDeclaration extends Declaration { kind: SyntaxKind.Parameter; - dotDotDotToken?: DotDotDotToken; // Present on rest parameter + dotDotDotToken?: DotDotDotToken; // Present on rest parameter name: BindingName; // Declared parameter name - questionToken?: QuestionToken; // Present on optional parameter + questionToken?: QuestionToken; // Present on optional parameter type?: TypeNode; // Optional type annotation initializer?: Expression; // Optional initializer } @@ -645,7 +645,7 @@ namespace ts { export interface BindingElement extends Declaration { kind: SyntaxKind.BindingElement; propertyName?: PropertyName; // Binding property name (in object binding pattern) - dotDotDotToken?: DotDotDotToken; // Present on rest binding element + dotDotDotToken?: DotDotDotToken; // Present on rest binding element name: BindingName; // Declared binding element name initializer?: Expression; // Optional initializer } @@ -671,7 +671,12 @@ namespace ts { name?: PropertyName; } - export type ObjectLiteralElementLike = PropertyAssignment | ShorthandPropertyAssignment | MethodDeclaration | AccessorDeclaration; + export type ObjectLiteralElementLike + = PropertyAssignment + | ShorthandPropertyAssignment + | MethodDeclaration + | AccessorDeclaration + ; export interface PropertyAssignment extends ObjectLiteralElement { kind: SyntaxKind.PropertyAssignment; @@ -712,6 +717,7 @@ namespace ts { } export interface BindingPattern extends Node { + kind: SyntaxKind.ObjectBindingPattern | SyntaxKind.ArrayBindingPattern; elements: NodeArray; } @@ -1157,16 +1163,16 @@ namespace ts { right: Expression; } - export interface AssignmentExpression extends BinaryExpression { + export interface AssignmentExpression extends BinaryExpression { left: LeftHandSideExpression; - operatorToken: Token; + operatorToken: Token; } - export interface ObjectDestructuringAssignment extends AssignmentExpression { + export interface ObjectDestructuringAssignment extends AssignmentExpression { left: ObjectLiteralExpression; } - export interface ArrayDestructuringAssignment extends AssignmentExpression { + export interface ArrayDestructuringAssignment extends AssignmentExpression { left: ArrayLiteralExpression; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e06913e1c0a..46b91c1d656 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3084,19 +3084,21 @@ namespace ts { } } - export function isAssignmentExpression(node: Node): node is AssignmentExpression { + export function isAssignmentExpression(node: Node, excludeCompoundAssignment: true): node is AssignmentExpression; + export function isAssignmentExpression(node: Node, excludeCompoundAssignment?: false): node is AssignmentExpression; + export function isAssignmentExpression(node: Node, excludeCompoundAssignment?: boolean): node is AssignmentExpression { return isBinaryExpression(node) - && isAssignmentOperator(node.operatorToken.kind) + && (excludeCompoundAssignment + ? node.operatorToken.kind === SyntaxKind.EqualsToken + : isAssignmentOperator(node.operatorToken.kind)) && isLeftHandSideExpression(node.left); } export function isDestructuringAssignment(node: Node): node is DestructuringAssignment { - if (isBinaryExpression(node)) { - if (node.operatorToken.kind === SyntaxKind.EqualsToken) { - const kind = node.left.kind; - return kind === SyntaxKind.ObjectLiteralExpression - || kind === SyntaxKind.ArrayLiteralExpression; - } + if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) { + const kind = node.left.kind; + return kind === SyntaxKind.ObjectLiteralExpression + || kind === SyntaxKind.ArrayLiteralExpression; } return false; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 609031a8221..e30132b05f5 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -549,11 +549,11 @@ namespace ts { export function visitNode(node: T, visitor: (node: Node) => VisitResult, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T; export function visitNode(node: T, visitor: (node: Node) => VisitResult, test: (node: Node) => boolean, optional: boolean, lift: (node: NodeArray) => T, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node): T; export function visitNode(node: Node, visitor: (node: Node) => VisitResult, test: (node: Node) => boolean, optional?: boolean, lift?: (node: Node[]) => Node, parenthesize?: (node: Node, parentNode: Node) => Node, parentNode?: Node): Node { - if (node === undefined) { - return undefined; + if (node === undefined || visitor === undefined) { + return node; } - const visited = visitor(node); + const visited = visitor(aggregateTransformFlags(node)); if (visited === node) { return node; } @@ -621,7 +621,7 @@ namespace ts { // Visit each original node. for (let i = 0; i < count; i++) { const node = nodes[i + start]; - const visited = node !== undefined ? visitor(node) : undefined; + const visited = node !== undefined ? visitor(aggregateTransformFlags(node)) : undefined; if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { // Ensure we have a copy of `nodes`, up to the current index. diff --git a/tests/baselines/reference/assignmentTypeNarrowing.js b/tests/baselines/reference/assignmentTypeNarrowing.js index 7c10dde65cc..92fd49d9941 100644 --- a/tests/baselines/reference/assignmentTypeNarrowing.js +++ b/tests/baselines/reference/assignmentTypeNarrowing.js @@ -37,17 +37,17 @@ x = [true][0]; x; // boolean _a = [1][0], x = _a === void 0 ? "" : _a; x; // string | number -(_b = { x: true }, x = _b.x, _b); +(x = { x: true }.x); x; // boolean -(_c = { y: 1 }, x = _c.y, _c); +(x = { y: 1 }.y); x; // number -(_d = { x: true }, _e = _d.x, x = _e === void 0 ? "" : _e, _d); +(_b = { x: true }.x, x = _b === void 0 ? "" : _b); x; // string | boolean -(_f = { y: 1 }, _g = _f.y, x = _g === void 0 ? /a/ : _g, _f); +(_c = { y: 1 }.y, x = _c === void 0 ? /a/ : _c); x; // number | RegExp var a; for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { x = a_1[_i]; x; // string } -var _a, _b, _c, _d, _e, _f, _g; +var _a, _b, _c; diff --git a/tests/baselines/reference/asyncMethodWithSuper_es5.js b/tests/baselines/reference/asyncMethodWithSuper_es5.js index 9ceff6bab4e..c4c61fc8f9b 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es5.js +++ b/tests/baselines/reference/asyncMethodWithSuper_es5.js @@ -81,8 +81,8 @@ var B = (function (_super) { // async method with assignment/destructuring on 'super' requires a binding B.prototype.advanced = function () { return __awaiter(this, void 0, void 0, function () { - var f, a, b, _a, _b; - return __generator(this, function (_c) { + var f, a, b; + return __generator(this, function (_a) { f = function () { }; // call with property access _super.prototype.x.call(this); @@ -95,9 +95,9 @@ var B = (function (_super) { // element access (assign) _super.prototype["x"] = f; // destructuring assign with property access - (_a = { f: f }, super.x = _a.f, _a); + (super.x = { f: f }.f); // destructuring assign with element access - (_b = { f: f }, super["x"] = _b.f, _b); + (super["x"] = { f: f }.f); return [2 /*return*/]; }); }); diff --git a/tests/baselines/reference/computedPropertiesInDestructuring1.js b/tests/baselines/reference/computedPropertiesInDestructuring1.js index 0bc4286ed7b..b0dc33a4b4e 100644 --- a/tests/baselines/reference/computedPropertiesInDestructuring1.js +++ b/tests/baselines/reference/computedPropertiesInDestructuring1.js @@ -65,11 +65,11 @@ function f5(_a) { var _f = foo(), bar6 = [{ bar: "bar" }][0][_f]; var _g = foo.toExponential(), bar7 = [{ bar: "bar" }][0][_g]; // destructuring assignment -(_h = { bar: "bar" }, _j = foo, bar = _h[_j], _h); -(_k = { bar: "bar" }, _l = "bar", bar2 = _k[_l], _k); -(_m = { bar: "bar" }, _o = foo2(), bar3 = _m[_o], _m); -_p = foo, bar4 = [{ bar: "bar" }][0][_p]; -_q = foo2(), bar5 = [{ bar: "bar" }][0][_q]; -_r = foo(), bar4 = [{ bar: "bar" }][0][_r]; -_s = (1 + {}), bar4 = [{ bar: "bar" }][0][_s]; -var _h, _j, _k, _l, _m, _o, _p, _q, _r, _s; +(_h = foo, bar = { bar: "bar" }[_h]); +(_j = "bar", bar2 = { bar: "bar" }[_j]); +(_k = foo2(), bar3 = { bar: "bar" }[_k]); +_l = foo, bar4 = [{ bar: "bar" }][0][_l]; +_m = foo2(), bar5 = [{ bar: "bar" }][0][_m]; +_o = foo(), bar4 = [{ bar: "bar" }][0][_o]; +_p = (1 + {}), bar4 = [{ bar: "bar" }][0][_p]; +var _h, _j, _k, _l, _m, _o, _p; diff --git a/tests/baselines/reference/declarationsAndAssignments.js b/tests/baselines/reference/declarationsAndAssignments.js index 018aa5522ca..3a2f9ef3b4e 100644 --- a/tests/baselines/reference/declarationsAndAssignments.js +++ b/tests/baselines/reference/declarationsAndAssignments.js @@ -300,8 +300,8 @@ function f18() { var a; var b; var aa; - (_a = { a: a, b: b }, a = _a.a, b = _a.b, _a); - (_b = { b: b, a: a }, a = _b.a, b = _b.b, _b); + (_a = { a: a, b: b }, a = _a.a, b = _a.b); + (_b = { b: b, a: a }, a = _b.a, b = _b.b); _c = [a, b], aa[0] = _c[0], b = _c[1]; _d = [b, a], a = _d[0], b = _d[1]; // Error _e = [2, "def"], _f = _e[0], a = _f === void 0 ? 1 : _f, _g = _e[1], b = _g === void 0 ? "abc" : _g; @@ -311,7 +311,7 @@ function f19() { var a, b; _a = [1, 2], a = _a[0], b = _a[1]; _b = [b, a], a = _b[0], b = _b[1]; - (_c = { b: b, a: a }, a = _c.a, b = _c.b, _c); + (_c = { b: b, a: a }, a = _c.a, b = _c.b); _d = [[2, 3]][0], _e = _d === void 0 ? [1, 2] : _d, a = _e[0], b = _e[1]; var x = (_f = [1, 2], a = _f[0], b = _f[1], _f); var _a, _b, _c, _d, _e, _f; diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.js b/tests/baselines/reference/destructuringAssignmentWithDefault.js index ce3837e1162..fe3ccdcee53 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.js +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.js @@ -7,5 +7,5 @@ let x = 0; //// [destructuringAssignmentWithDefault.js] var a = {}; var x = 0; -(_a = a.x, x = _a === void 0 ? 1 : _a, a); +(_a = a.x, x = _a === void 0 ? 1 : _a); var _a; diff --git a/tests/baselines/reference/emptyAssignmentPatterns02_ES5.js b/tests/baselines/reference/emptyAssignmentPatterns02_ES5.js index 370e019104f..f5434d4a1c3 100644 --- a/tests/baselines/reference/emptyAssignmentPatterns02_ES5.js +++ b/tests/baselines/reference/emptyAssignmentPatterns02_ES5.js @@ -9,8 +9,8 @@ let x, y, z, a1, a2, a3; //// [emptyAssignmentPatterns02_ES5.js] var a; var x, y, z, a1, a2, a3; -(x = a.x, y = a.y, z = a.z, a); -(a1 = a[0], a2 = a[1], a3 = a[2], a); +(x = a.x, y = a.y, z = a.z); +(a1 = a[0], a2 = a[1], a3 = a[2]); //// [emptyAssignmentPatterns02_ES5.d.ts] diff --git a/tests/baselines/reference/emptyAssignmentPatterns04_ES5.js b/tests/baselines/reference/emptyAssignmentPatterns04_ES5.js index e6b3cc7e3f2..91559d18252 100644 --- a/tests/baselines/reference/emptyAssignmentPatterns04_ES5.js +++ b/tests/baselines/reference/emptyAssignmentPatterns04_ES5.js @@ -9,9 +9,8 @@ let x, y, z, a1, a2, a3; //// [emptyAssignmentPatterns04_ES5.js] var a; var x, y, z, a1, a2, a3; -(_a = a, x = _a.x, y = _a.y, z = _a.z, _a); -(_b = a, a1 = _b[0], a2 = _b[1], a3 = _b[2], _b); -var _a, _b; +(x = a.x, y = a.y, z = a.z); +(a1 = a[0], a2 = a[1], a3 = a[2]); //// [emptyAssignmentPatterns04_ES5.d.ts] diff --git a/tests/baselines/reference/initializePropertiesWithRenamedLet.js b/tests/baselines/reference/initializePropertiesWithRenamedLet.js index d53fed8c0f7..4abe488aa08 100644 --- a/tests/baselines/reference/initializePropertiesWithRenamedLet.js +++ b/tests/baselines/reference/initializePropertiesWithRenamedLet.js @@ -28,7 +28,6 @@ if (true) { var x_1 = { x: 0 }.x; var y_1 = { y: 0 }.y; var z_1; - (_a = { z: 0 }, z_1 = _a.z, _a); - (_b = { z: 0 }, z_1 = _b.z, _b); + (z_1 = { z: 0 }.z); + (z_1 = { z: 0 }.z); } -var _a, _b; diff --git a/tests/baselines/reference/missingAndExcessProperties.js b/tests/baselines/reference/missingAndExcessProperties.js index b9fcadd9519..daefe18eed7 100644 --- a/tests/baselines/reference/missingAndExcessProperties.js +++ b/tests/baselines/reference/missingAndExcessProperties.js @@ -45,10 +45,10 @@ function f1() { // Missing properties function f2() { var x, y; - (_a = {}, x = _a.x, y = _a.y, _a); - (_b = {}, _c = _b.x, x = _c === void 0 ? 1 : _c, y = _b.y, _b); - (_d = {}, x = _d.x, _e = _d.y, y = _e === void 0 ? 1 : _e, _d); - (_f = {}, _g = _f.x, x = _g === void 0 ? 1 : _g, _h = _f.y, y = _h === void 0 ? 1 : _h, _f); + (_a = {}, x = _a.x, y = _a.y); + (_b = {}, _c = _b.x, x = _c === void 0 ? 1 : _c, y = _b.y); + (_d = {}, x = _d.x, _e = _d.y, y = _e === void 0 ? 1 : _e); + (_f = {}, _g = _f.x, x = _g === void 0 ? 1 : _g, _h = _f.y, y = _h === void 0 ? 1 : _h); var _a, _b, _c, _d, _e, _f, _g, _h; } // Excess properties @@ -62,8 +62,8 @@ function f3() { function f4() { var x, y; ({ x: 0, y: 0 }); - (_a = { x: 0, y: 0 }, x = _a.x, _a); - (_b = { x: 0, y: 0 }, y = _b.y, _b); - (_c = { x: 0, y: 0 }, x = _c.x, y = _c.y, _c); - var _a, _b, _c; + (x = { x: 0, y: 0 }.x); + (y = { x: 0, y: 0 }.y); + (_a = { x: 0, y: 0 }, x = _a.x, y = _a.y); + var _a; } diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.js b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.js index 981ee08e20c..6be02246e39 100644 --- a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.js +++ b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.js @@ -176,63 +176,63 @@ function foo({a = 4, b = { x: 5 }}) { }); (function () { var y; - (_a = { y: 1 }, _b = _a.y, y = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = { y: 1 }.y, y = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y; - (_a = { y: 1 }, _b = _a.y, y = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = { y: 1 }.y, y = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y0; - (_a = { y0: 1 }, _b = _a.y0, y0 = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = { y0: 1 }.y0, y0 = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y0; - (_a = { y0: 1 }, _b = _a.y0, y0 = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = { y0: 1 }.y0, y0 = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y1; - (_a = {}, _b = _a.y1, y1 = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = {}.y1, y1 = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y1; - (_a = {}, _b = _a.y1, y1 = _b === void 0 ? 5 : _b, _a); - var _a, _b; + (_a = {}.y1, y1 = _a === void 0 ? 5 : _a); + var _a; }); (function () { var y2, y3; - (_a = {}, _b = _a.y2, y2 = _b === void 0 ? 5 : _b, _c = _a.y3, y3 = _c === void 0 ? { x: 1 } : _c, _a); + (_a = {}, _b = _a.y2, y2 = _b === void 0 ? 5 : _b, _c = _a.y3, y3 = _c === void 0 ? { x: 1 } : _c); var _a, _b, _c; }); (function () { var y2, y3; - (_a = {}, _b = _a.y2, y2 = _b === void 0 ? 5 : _b, _c = _a.y3, y3 = _c === void 0 ? { x: 1 } : _c, _a); + (_a = {}, _b = _a.y2, y2 = _b === void 0 ? 5 : _b, _c = _a.y3, y3 = _c === void 0 ? { x: 1 } : _c); var _a, _b, _c; }); (function () { var y4, y5; - (_a = {}, _b = _a.y4, y4 = _b === void 0 ? 5 : _b, _c = _a.y5, y5 = _c === void 0 ? { x: 1 } : _c, _a); + (_a = {}, _b = _a.y4, y4 = _b === void 0 ? 5 : _b, _c = _a.y5, y5 = _c === void 0 ? { x: 1 } : _c); var _a, _b, _c; }); (function () { var y4, y5; - (_a = {}, _b = _a.y4, y4 = _b === void 0 ? 5 : _b, _c = _a.y5, y5 = _c === void 0 ? { x: 1 } : _c, _a); + (_a = {}, _b = _a.y4, y4 = _b === void 0 ? 5 : _b, _c = _a.y5, y5 = _c === void 0 ? { x: 1 } : _c); var _a, _b, _c; }); (function () { var z; - (_a = { z: { x: 1 } }, _b = _a.z, z = _b === void 0 ? { x: 5 } : _b, _a); - var _a, _b; + (_a = { z: { x: 1 } }.z, z = _a === void 0 ? { x: 5 } : _a); + var _a; }); (function () { var z; - (_a = { z: { x: 1 } }, _b = _a.z, z = _b === void 0 ? { x: 5 } : _b, _a); - var _a, _b; + (_a = { z: { x: 1 } }.z, z = _a === void 0 ? { x: 5 } : _a); + var _a; }); (function () { var a = { s: s };