diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9a477f05211..52131a90c3a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1280,7 +1280,7 @@ namespace ts { function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { const name = !isOmittedExpression(node) ? node.name : undefined; if (isBindingPattern(name)) { - for (const child of name.elements) { + for (const child of name.elements) { bindInitializedVariableFlow(child); } } @@ -2629,7 +2629,7 @@ namespace ts { } // parameters with object rest destructuring are ES Next syntax - if (subtreeFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)) { + if (subtreeFlags & TransformFlags.ContainsObjectRest) { transformFlags |= TransformFlags.AssertESNext; } @@ -2867,7 +2867,7 @@ namespace ts { } // function declarations with object rest destructuring are ES Next syntax - if (subtreeFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)) { + if (subtreeFlags & TransformFlags.ContainsObjectRest) { transformFlags |= TransformFlags.AssertESNext; } @@ -2909,7 +2909,7 @@ namespace ts { } // function expressions with object rest destructuring are ES Next syntax - if (subtreeFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)) { + if (subtreeFlags & TransformFlags.ContainsObjectRest) { transformFlags |= TransformFlags.AssertESNext; } @@ -2952,7 +2952,7 @@ namespace ts { } // arrow functions with object rest destructuring are ES Next syntax - if (subtreeFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)) { + if (subtreeFlags & TransformFlags.ContainsObjectRest) { transformFlags |= TransformFlags.AssertESNext; } @@ -2982,16 +2982,11 @@ namespace ts { function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; - const nameKind = node.name.kind; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; - // A VariableDeclaration with an object binding pattern is ES2015 syntax - // and possibly ESNext syntax if it contains an object binding pattern - if (nameKind === SyntaxKind.ObjectBindingPattern) { - transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; - } - // A VariableDeclaration with an object binding pattern is ES2015 syntax. - else if (nameKind === SyntaxKind.ArrayBindingPattern) { - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; + // A VariableDeclaration containing ObjectRest is ESNext syntax + if (subtreeFlags & TransformFlags.ContainsObjectRest) { + transformFlags |= TransformFlags.AssertESNext; } // Type annotations are TypeScript syntax. @@ -3213,13 +3208,6 @@ namespace ts { transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsSpread | TransformFlags.ContainsObjectSpread; break; - case SyntaxKind.BindingElement: - transformFlags |= TransformFlags.AssertES2015; - if ((node).dotDotDotToken) { - transformFlags |= TransformFlags.ContainsRest; - } - break; - case SyntaxKind.SuperKeyword: // This node is ES6 syntax. transformFlags |= TransformFlags.AssertES2015; @@ -3232,7 +3220,7 @@ namespace ts { case SyntaxKind.ObjectBindingPattern: transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; - if (subtreeFlags & TransformFlags.ContainsSpread) { + if (subtreeFlags & TransformFlags.ContainsRest) { transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsObjectRest; } excludeFlags = TransformFlags.BindingPatternExcludes; @@ -3243,6 +3231,13 @@ namespace ts { excludeFlags = TransformFlags.BindingPatternExcludes; break; + case SyntaxKind.BindingElement: + transformFlags |= TransformFlags.AssertES2015; + if ((node).dotDotDotToken) { + transformFlags |= TransformFlags.ContainsRest; + } + break; + case SyntaxKind.Decorator: // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsDecorators; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ac3d5ed6e29..c107263b9d8 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1405,7 +1405,7 @@ namespace ts { return node; } - export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression) { + export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression) { if (node.name !== name || node.objectAssignmentInitializer !== objectAssignmentInitializer) { return updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer, node), node); } @@ -1419,7 +1419,7 @@ namespace ts { return node; } - // Top-level nodes + // Top-level nodes export function updateSourceFileNode(node: SourceFile, statements: Statement[]) { if (node.statements !== statements) { @@ -1994,6 +1994,8 @@ namespace ts { ); } + // Utilities + export interface CallBinding { target: LeftHandSideExpression; thisArg: Expression; @@ -2339,8 +2341,6 @@ namespace ts { return qualifiedName; } - // Utilities - export function convertToFunctionBody(node: ConciseBody) { if (isBlock(node)) { return node; @@ -2424,407 +2424,6 @@ namespace ts { return node; } - /** - * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended - * order of operations. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { - const skipped = skipPartiallyEmittedExpressions(operand); - - // If the resulting expression is already parenthesized, we do not need to do any further processing. - if (skipped.kind === SyntaxKind.ParenthesizedExpression) { - return operand; - } - - return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) - ? createParen(operand) - : operand; - } - - /** - * Determines whether the operand to a BinaryExpression needs to be parenthesized. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression) { - // If the operand has lower precedence, then it needs to be parenthesized to preserve the - // intent of the expression. For example, if the operand is `a + b` and the operator is - // `*`, then we need to parenthesize the operand to preserve the intended order of - // operations: `(a + b) * x`. - // - // If the operand has higher precedence, then it does not need to be parenthesized. For - // example, if the operand is `a * b` and the operator is `+`, then we do not need to - // parenthesize to preserve the intended order of operations: `a * b + x`. - // - // If the operand has the same precedence, then we need to check the associativity of - // the operator based on whether this is the left or right operand of the expression. - // - // For example, if `a / d` is on the right of operator `*`, we need to parenthesize - // to preserve the intended order of operations: `x * (a / d)` - // - // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve - // the intended order of operations: `(a ** b) ** c` - const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); - const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); - const emittedOperand = skipPartiallyEmittedExpressions(operand); - const operandPrecedence = getExpressionPrecedence(emittedOperand); - switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { - case Comparison.LessThan: - // If the operand is the right side of a right-associative binary operation - // and is a yield expression, then we do not need parentheses. - if (!isLeftSideOfBinary - && binaryOperatorAssociativity === Associativity.Right - && operand.kind === SyntaxKind.YieldExpression) { - return false; - } - - return true; - - case Comparison.GreaterThan: - return false; - - case Comparison.EqualTo: - if (isLeftSideOfBinary) { - // No need to parenthesize the left operand when the binary operator is - // left associative: - // (a*b)/x -> a*b/x - // (a**b)/x -> a**b/x - // - // Parentheses are needed for the left operand when the binary operator is - // right associative: - // (a/b)**x -> (a/b)**x - // (a**b)**x -> (a**b)**x - return binaryOperatorAssociativity === Associativity.Right; - } - else { - if (isBinaryExpression(emittedOperand) - && emittedOperand.operatorToken.kind === binaryOperator) { - // No need to parenthesize the right operand when the binary operator and - // operand are the same and one of the following: - // x*(a*b) => x*a*b - // x|(a|b) => x|a|b - // x&(a&b) => x&a&b - // x^(a^b) => x^a^b - if (operatorHasAssociativeProperty(binaryOperator)) { - return false; - } - - // No need to parenthesize the right operand when the binary operator - // is plus (+) if both the left and right operands consist solely of either - // literals of the same kind or binary plus (+) expressions for literals of - // the same kind (recursively). - // "a"+(1+2) => "a"+(1+2) - // "a"+("b"+"c") => "a"+"b"+"c" - if (binaryOperator === SyntaxKind.PlusToken) { - const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; - if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { - return false; - } - } - } - - // No need to parenthesize the right operand when the operand is right - // associative: - // x/(a**b) -> x/a**b - // x**(a**b) -> x**a**b - // - // Parentheses are needed for the right operand when the operand is left - // associative: - // x/(a*b) -> x/(a*b) - // x**(a/b) -> x**(a/b) - const operandAssociativity = getExpressionAssociativity(emittedOperand); - return operandAssociativity === Associativity.Left; - } - } - } - - /** - * Determines whether a binary operator is mathematically associative. - * - * @param binaryOperator The binary operator. - */ - function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { - // The following operators are associative in JavaScript: - // (a*b)*c -> a*(b*c) -> a*b*c - // (a|b)|c -> a|(b|c) -> a|b|c - // (a&b)&c -> a&(b&c) -> a&b&c - // (a^b)^c -> a^(b^c) -> a^b^c - // - // While addition is associative in mathematics, JavaScript's `+` is not - // guaranteed to be associative as it is overloaded with string concatenation. - return binaryOperator === SyntaxKind.AsteriskToken - || binaryOperator === SyntaxKind.BarToken - || binaryOperator === SyntaxKind.AmpersandToken - || binaryOperator === SyntaxKind.CaretToken; - } - - interface BinaryPlusExpression extends BinaryExpression { - cachedLiteralKind: SyntaxKind; - } - - /** - * This function determines whether an expression consists of a homogeneous set of - * literal expressions or binary plus expressions that all share the same literal kind. - * It is used to determine whether the right-hand operand of a binary plus expression can be - * emitted without parentheses. - */ - function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { - node = skipPartiallyEmittedExpressions(node); - - if (isLiteralKind(node.kind)) { - return node.kind; - } - - if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.PlusToken) { - if ((node).cachedLiteralKind !== undefined) { - return (node).cachedLiteralKind; - } - - const leftKind = getLiteralKindOfBinaryPlusOperand((node).left); - const literalKind = isLiteralKind(leftKind) - && leftKind === getLiteralKindOfBinaryPlusOperand((node).right) - ? leftKind - : SyntaxKind.Unknown; - - (node).cachedLiteralKind = literalKind; - return literalKind; - } - - return SyntaxKind.Unknown; - } - - /** - * Wraps an expression in parentheses if it is needed in order to use the expression - * as the expression of a NewExpression node. - * - * @param expression The Expression node. - */ - export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - switch (emittedExpression.kind) { - case SyntaxKind.CallExpression: - return createParen(expression); - - case SyntaxKind.NewExpression: - return (emittedExpression).arguments - ? expression - : createParen(expression); - } - - return parenthesizeForAccess(expression); - } - - /** - * Wraps an expression in parentheses if it is needed in order to use the expression for - * property or element access. - * - * @param expr The expression node. - */ - export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { - // isLeftHandSideExpression is almost the correct criterion for when it is not necessary - // to parenthesize the expression before a dot. The known exceptions are: - // - // NewExpression: - // new C.x -> not the same as (new C).x - // NumericLiteral - // 1.x -> not the same as (1).x - // - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isLeftHandSideExpression(emittedExpression) - && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression).arguments) - && emittedExpression.kind !== SyntaxKind.NumericLiteral) { - return expression; - } - - return createParen(expression, /*location*/ expression); - } - - export function parenthesizePostfixOperand(operand: Expression) { - return isLeftHandSideExpression(operand) - ? operand - : createParen(operand, /*location*/ operand); - } - - export function parenthesizePrefixOperand(operand: Expression) { - return isUnaryExpression(operand) - ? operand - : createParen(operand, /*location*/ operand); - } - - 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 emittedExpression = skipPartiallyEmittedExpressions(expression); - const expressionPrecedence = getExpressionPrecedence(emittedExpression); - const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); - return expressionPrecedence > commaPrecedence - ? expression - : createParen(expression, /*location*/ expression); - } - - export function parenthesizeExpressionForExpressionStatement(expression: Expression) { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isCallExpression(emittedExpression)) { - const callee = emittedExpression.expression; - const kind = skipPartiallyEmittedExpressions(callee).kind; - if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { - const mutableCall = getMutableClone(emittedExpression); - mutableCall.expression = createParen(callee, /*location*/ callee); - return recreatePartiallyEmittedExpressions(expression, mutableCall); - } - } - else { - const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind; - if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { - return createParen(expression, /*location*/ expression); - } - } - - return expression; - } - - /** - * Clones a series of not-emitted expressions with a new inner expression. - * - * @param originalOuterExpression The original outer expression. - * @param newInnerExpression The new inner expression. - */ - function recreatePartiallyEmittedExpressions(originalOuterExpression: Expression, newInnerExpression: Expression) { - if (isPartiallyEmittedExpression(originalOuterExpression)) { - const clone = getMutableClone(originalOuterExpression); - clone.expression = recreatePartiallyEmittedExpressions(clone.expression, newInnerExpression); - return clone; - } - - return newInnerExpression; - } - - function getLeftmostExpression(node: Expression): Expression { - while (true) { - switch (node.kind) { - case SyntaxKind.PostfixUnaryExpression: - node = (node).operand; - continue; - - case SyntaxKind.BinaryExpression: - node = (node).left; - continue; - - case SyntaxKind.ConditionalExpression: - node = (node).condition; - continue; - - case SyntaxKind.CallExpression: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - node = (node).expression; - continue; - - case SyntaxKind.PartiallyEmittedExpression: - node = (node).expression; - continue; - } - - return node; - } - } - - export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { - const emittedBody = skipPartiallyEmittedExpressions(body); - if (emittedBody.kind === SyntaxKind.ObjectLiteralExpression) { - return createParen(body, /*location*/ body); - } - - return body; - } - - export const enum OuterExpressionKinds { - Parentheses = 1 << 0, - Assertions = 1 << 1, - PartiallyEmittedExpressions = 1 << 2, - - All = Parentheses | Assertions | PartiallyEmittedExpressions - } - - export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; - export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; - export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { - let previousNode: Node; - do { - previousNode = node; - if (kinds & OuterExpressionKinds.Parentheses) { - node = skipParentheses(node); - } - - if (kinds & OuterExpressionKinds.Assertions) { - node = skipAssertions(node); - } - - if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { - node = skipPartiallyEmittedExpressions(node); - } - } - while (previousNode !== node); - - return node; - } - - export function skipParentheses(node: Expression): Expression; - export function skipParentheses(node: Node): Node; - export function skipParentheses(node: Node): Node { - while (node.kind === SyntaxKind.ParenthesizedExpression) { - node = (node).expression; - } - - return node; - } - - export function skipAssertions(node: Expression): Expression; - export function skipAssertions(node: Node): Node; - export function skipAssertions(node: Node): Node { - while (isAssertionExpression(node)) { - node = (node).expression; - } - - return node; - } - - export function skipPartiallyEmittedExpressions(node: Expression): Expression; - export function skipPartiallyEmittedExpressions(node: Node): Node; - export function skipPartiallyEmittedExpressions(node: Node) { - while (node.kind === SyntaxKind.PartiallyEmittedExpression) { - node = (node).expression; - } - - return node; - } - export function startOnNewLine(node: T): T { node.startsOnNewLine = true; return node; @@ -3295,7 +2894,7 @@ namespace ts { createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList( - flattenDestructuringToDeclarations( + flattenDestructuringBinding( context, parameter, temp, @@ -3454,9 +3053,6 @@ namespace ts { statements.push(forStatement); } - - - export function convertForOf(node: ForOfStatement, convertedLoopBodyStatements: Statement[], visitor: (node: Node) => VisitResult, enableSubstitutionsForBlockScopedBindings: () => void, @@ -3509,7 +3105,7 @@ 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 = flattenDestructuringToDeclarations( + const declarations = flattenDestructuringBinding( context, firstOriginalDeclaration, elementAccess, @@ -3566,7 +3162,7 @@ namespace ts { // This is a destructuring pattern, so we flatten the destructuring instead. statements.push( createStatement( - flattenDestructuringToExpression( + flattenDestructuringAssignment( context, assignment, /*needsValue*/ false, @@ -3651,4 +3247,680 @@ namespace ts { setEmitFlags(forStatement, EmitFlags.NoTokenTrailingSourceMaps); return forStatement; } + + /** + * Gets the initializer of an BindingOrAssignmentElement. + */ + export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): 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; + } + + 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; + } + + if (isShorthandPropertyAssignment(bindingElement)) { + // `1` in `({ a = 1 } = ...)` + return bindingElement.objectAssignmentInitializer; + } + + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `1` in `[a = 1] = ...` + // `1` in `[{a} = 1] = ...` + // `1` in `[[a] = 1] = ...` + return bindingElement.right; + } + + if (isSpreadExpression(bindingElement) && isBindingOrAssignmentElement(bindingElement.expression)) { + // Recovery consistent with existing emit. + return getInitializerOfBindingOrAssignmentElement(bindingElement.expression); + } + } + + /** + * Gets the name of an BindingOrAssignmentElement. + */ + export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget { + if (isDeclarationBindingElement(bindingElement)) { + // `a` in `let { a } = ...` + // `a` in `let { a = 1 } = ...` + // `b` in `let { a: b } = ...` + // `b` in `let { a: b = 1 } = ...` + // `a` in `let { ...a } = ...` + // `{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 (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 isBindingOrAssignmentElement(bindingElement.initializer) + ? getTargetOfBindingOrAssignmentElement(bindingElement.initializer) + : undefined; + + case SyntaxKind.ShorthandPropertyAssignment: + // `a` in `({ a } = ...)` + // `a` in `({ a = 1 } = ...)` + return bindingElement.name; + + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + return isBindingOrAssignmentElement(bindingElement.expression) + ? getTargetOfBindingOrAssignmentElement(bindingElement.expression) + : undefined; + } + + // 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 isBindingOrAssignmentElement(bindingElement.left) + ? getTargetOfBindingOrAssignmentElement(bindingElement.left) + : undefined; + } + + if (isSpreadExpression(bindingElement)) { + // `a` in `[...a] = ...` + return isBindingOrAssignmentElement(bindingElement.expression) + ? getTargetOfBindingOrAssignmentElement(bindingElement.expression) + : undefined; + } + + // `a` in `[a] = ...` + // `{a}` in `[{a}] = ...` + // `[a]` in `[[a]] = ...` + // `a.b` in `[a.b] = ...` + // `a[0]` in `[a[0]] = ...` + return bindingElement; + } + + /** + * Determines whether an BindingOrAssignmentElement is a rest element. + */ + export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator { + switch (bindingElement.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + // `...` in `let [...a] = ...` + return (bindingElement).dotDotDotToken; + + case SyntaxKind.SpreadElement: + case SyntaxKind.SpreadAssignment: + // `...` in `[...a] = ...` + return bindingElement; + } + + return undefined; + } + + /** + * Gets the property name of a BindingOrAssignmentElement + */ + export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement) { + 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; + + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + return (bindingElement).name; + } + + const target = getTargetOfBindingOrAssignmentElement(bindingElement); + if (target && isPropertyName(target)) { + return target; + } + + Debug.fail("Invalid property name for binding element."); + } + + /** + * Gets the elements of a BindingOrAssignmentPattern + */ + export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): BindingOrAssignmentElement[] { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + // `a` in `{a}` + // `a` in `[a]` + return map(name.elements, convertToBindingOrAssignmentElement); + + case SyntaxKind.ObjectLiteralExpression: + // `a` in `{a}` + return filter(name.properties, isBindingOrAssignmentElement); + } + } + + function convertToBindingOrAssignmentElement(node: Node) { + return isBindingOrAssignmentElement(node) ? node : createOmittedExpression(node); + } + + export function convertToArrayAssignmentElement(element: BindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(createSpread(element.name, element), element); + } + const expression = convertToAssignmentElementTarget(element.name); + return element.initializer ? setOriginalNode(createAssignment(expression, element.initializer, element), element) : expression; + } + Debug.assertNode(element, isExpression); + return element; + } + + export function convertToObjectAssignmentElement(element: BindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(createSpreadAssignment(element.name, element), element); + } + if (element.propertyName) { + const expression = convertToAssignmentElementTarget(element.name); + return setOriginalNode(createPropertyAssignment(element.propertyName, element.initializer ? createAssignment(expression, element.initializer) : expression, element), element); + } + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(createShorthandPropertyAssignment(element.name, element.initializer, element), element); + } + Debug.assertNode(element, isObjectLiteralElementLike); + return element; + } + + export function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return convertToArrayAssignmentPattern(node); + + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return convertToObjectAssignmentPattern(node); + } + } + + export function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { + if (isObjectBindingPattern(node)) { + return setOriginalNode(createObjectLiteral(map(node.elements, convertToObjectAssignmentElement), node), node); + } + Debug.assertNode(node, isObjectLiteralExpression); + return node; + } + + export function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { + if (isArrayBindingPattern(node)) { + return setOriginalNode(createArrayLiteral(map(node.elements, convertToArrayAssignmentElement), node), node); + } + Debug.assertNode(node, isArrayLiteralExpression); + return node; + } + + export function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { + if (isBindingPattern(node)) { + return convertToAssignmentPattern(node); + } + + Debug.assertNode(node, isExpression); + return node; + } + + // Parenthesizing + + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { + const skipped = skipPartiallyEmittedExpressions(operand); + + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (skipped.kind === SyntaxKind.ParenthesizedExpression) { + return operand; + } + + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) + ? createParen(operand) + : operand; + } + + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + const emittedOperand = skipPartiallyEmittedExpressions(operand); + const operandPrecedence = getExpressionPrecedence(emittedOperand); + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { + case Comparison.LessThan: + // If the operand is the right side of a right-associative binary operation + // and is a yield expression, then we do not need parentheses. + if (!isLeftSideOfBinary + && binaryOperatorAssociativity === Associativity.Right + && operand.kind === SyntaxKind.YieldExpression) { + return false; + } + + return true; + + case Comparison.GreaterThan: + return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + // + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + return binaryOperatorAssociativity === Associativity.Right; + } + else { + if (isBinaryExpression(emittedOperand) + && emittedOperand.operatorToken.kind === binaryOperator) { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (operatorHasAssociativeProperty(binaryOperator)) { + return false; + } + + // No need to parenthesize the right operand when the binary operator + // is plus (+) if both the left and right operands consist solely of either + // literals of the same kind or binary plus (+) expressions for literals of + // the same kind (recursively). + // "a"+(1+2) => "a"+(1+2) + // "a"+("b"+"c") => "a"+"b"+"c" + if (binaryOperator === SyntaxKind.PlusToken) { + const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; + if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { + return false; + } + } + } + + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + // + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(emittedOperand); + return operandAssociativity === Associativity.Left; + } + } + } + + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; + } + + interface BinaryPlusExpression extends BinaryExpression { + cachedLiteralKind: SyntaxKind; + } + + /** + * This function determines whether an expression consists of a homogeneous set of + * literal expressions or binary plus expressions that all share the same literal kind. + * It is used to determine whether the right-hand operand of a binary plus expression can be + * emitted without parentheses. + */ + function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { + node = skipPartiallyEmittedExpressions(node); + + if (isLiteralKind(node.kind)) { + return node.kind; + } + + if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.PlusToken) { + if ((node).cachedLiteralKind !== undefined) { + return (node).cachedLiteralKind; + } + + const leftKind = getLiteralKindOfBinaryPlusOperand((node).left); + const literalKind = isLiteralKind(leftKind) + && leftKind === getLiteralKindOfBinaryPlusOperand((node).right) + ? leftKind + : SyntaxKind.Unknown; + + (node).cachedLiteralKind = literalKind; + return literalKind; + } + + return SyntaxKind.Unknown; + } + + /** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a NewExpression node. + * + * @param expression The Expression node. + */ + export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + switch (emittedExpression.kind) { + case SyntaxKind.CallExpression: + return createParen(expression); + + case SyntaxKind.NewExpression: + return (emittedExpression).arguments + ? expression + : createParen(expression); + } + + return parenthesizeForAccess(expression); + } + + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + * + * @param expr The expression node. + */ + export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exceptions are: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // NumericLiteral + // 1.x -> not the same as (1).x + // + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isLeftHandSideExpression(emittedExpression) + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression).arguments) + && emittedExpression.kind !== SyntaxKind.NumericLiteral) { + return expression; + } + + return createParen(expression, /*location*/ expression); + } + + export function parenthesizePostfixOperand(operand: Expression) { + return isLeftHandSideExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizePrefixOperand(operand: Expression) { + return isUnaryExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + 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 emittedExpression = skipPartiallyEmittedExpressions(expression); + const expressionPrecedence = getExpressionPrecedence(emittedExpression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + return expressionPrecedence > commaPrecedence + ? expression + : createParen(expression, /*location*/ expression); + } + + export function parenthesizeExpressionForExpressionStatement(expression: Expression) { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isCallExpression(emittedExpression)) { + const callee = emittedExpression.expression; + const kind = skipPartiallyEmittedExpressions(callee).kind; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { + const mutableCall = getMutableClone(emittedExpression); + mutableCall.expression = createParen(callee, /*location*/ callee); + return recreatePartiallyEmittedExpressions(expression, mutableCall); + } + } + else { + const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + return createParen(expression, /*location*/ expression); + } + } + + return expression; + } + + /** + * Clones a series of not-emitted expressions with a new inner expression. + * + * @param originalOuterExpression The original outer expression. + * @param newInnerExpression The new inner expression. + */ + function recreatePartiallyEmittedExpressions(originalOuterExpression: Expression, newInnerExpression: Expression) { + if (isPartiallyEmittedExpression(originalOuterExpression)) { + const clone = getMutableClone(originalOuterExpression); + clone.expression = recreatePartiallyEmittedExpressions(clone.expression, newInnerExpression); + return clone; + } + + return newInnerExpression; + } + + function getLeftmostExpression(node: Expression): Expression { + while (true) { + switch (node.kind) { + case SyntaxKind.PostfixUnaryExpression: + node = (node).operand; + continue; + + case SyntaxKind.BinaryExpression: + node = (node).left; + continue; + + case SyntaxKind.ConditionalExpression: + node = (node).condition; + continue; + + case SyntaxKind.CallExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + node = (node).expression; + continue; + + case SyntaxKind.PartiallyEmittedExpression: + node = (node).expression; + continue; + } + + return node; + } + } + + export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { + const emittedBody = skipPartiallyEmittedExpressions(body); + if (emittedBody.kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(body, /*location*/ body); + } + + return body; + } + + export const enum OuterExpressionKinds { + Parentheses = 1 << 0, + Assertions = 1 << 1, + PartiallyEmittedExpressions = 1 << 2, + + All = Parentheses | Assertions | PartiallyEmittedExpressions + } + + export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; + export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; + export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { + let previousNode: Node; + do { + previousNode = node; + if (kinds & OuterExpressionKinds.Parentheses) { + node = skipParentheses(node); + } + + if (kinds & OuterExpressionKinds.Assertions) { + node = skipAssertions(node); + } + + if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { + node = skipPartiallyEmittedExpressions(node); + } + } + while (previousNode !== node); + + return node; + } + + export function skipParentheses(node: Expression): Expression; + export function skipParentheses(node: Node): Node; + export function skipParentheses(node: Node): Node { + while (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node).expression; + } + + return node; + } + + export function skipAssertions(node: Expression): Expression; + export function skipAssertions(node: Node): Node; + export function skipAssertions(node: Node): Node { + while (isAssertionExpression(node)) { + node = (node).expression; + } + + return node; + } + + export function skipPartiallyEmittedExpressions(node: Expression): Expression; + export function skipPartiallyEmittedExpressions(node: Node): Node; + export function skipPartiallyEmittedExpressions(node: Node) { + while (node.kind === SyntaxKind.PartiallyEmittedExpression) { + node = (node).expression; + } + + return node; + } } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index d4886457062..b8da29ae291 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -3,80 +3,21 @@ /*@internal*/ namespace ts { - type EffectiveBindingOrAssignmentElement = VariableDeclaration | ParameterDeclaration | BindingElement | ObjectLiteralElementLike | Expression; - type EffectiveObjectBindingOrAssignmentPattern = ObjectBindingPattern | ObjectLiteralExpression; - type EffectiveArrayBindingOrAssignmentPattern = ArrayBindingPattern | ArrayLiteralExpression; - type EffectiveBindingOrAssignmentPattern = EffectiveObjectBindingOrAssignmentPattern | EffectiveArrayBindingOrAssignmentPattern; - type EffectiveBindingOrAssignmentTarget = EffectiveBindingOrAssignmentPattern | Expression; - type EffectiveBindingOrAssignmentRestIndicator = DotDotDotToken | SpreadElement | SpreadAssignment; - interface FlattenHost { context: TransformationContext; level: FlattenLevel; recordTempVariablesInLine: boolean; emitExpression: (value: Expression) => void; - emitBindingOrAssignment: (target: EffectiveBindingOrAssignmentTarget, value: Expression, location: TextRange, original: Node) => void; - createArrayBindingOrAssignmentPattern: (elements: EffectiveBindingOrAssignmentElement[]) => EffectiveArrayBindingOrAssignmentPattern; - createObjectBindingOrAssignmentPattern: (elements: EffectiveBindingOrAssignmentElement[]) => EffectiveObjectBindingOrAssignmentPattern; - createArrayBindingOrAssignmentElement: (node: Identifier) => EffectiveBindingOrAssignmentElement; + emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) => void; + createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; + createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; + createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; visitor?: (node: Node) => VisitResult; } export const enum FlattenLevel { - ObjectRest, All, - } - - export const enum FlattenOutput { - Expression, - Declarations - } - - export const enum FlattenOptions { - NeedsValue = 1 << 0, - SkipInitializer = 1 << 1, - RecordTempVariablesInLine = 1 << 2, - } - - export function flattenDestructuring( - output: FlattenOutput.Expression, - level: FlattenLevel, - node: VariableDeclaration | DestructuringAssignment, - visitor: (node: Node) => VisitResult, - context: TransformationContext, - options: FlattenOptions, - createAssignment?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression; - export function flattenDestructuring( - output: FlattenOutput.Declarations, - level: FlattenLevel, - node: VariableDeclaration | ParameterDeclaration, - visitor: (node: Node) => VisitResult, - context: TransformationContext, - options: FlattenOptions, - boundValue?: Expression): VariableDeclaration[]; - export function flattenDestructuring( - _output: FlattenOutput, - _level: FlattenLevel, - _node: VariableDeclaration | ParameterDeclaration | DestructuringAssignment, - _visitor: (node: Node) => VisitResult, - _context: TransformationContext, - _options: FlattenOptions, - _valueOrCallback?: Expression | ((name: Identifier, value: Expression, location?: TextRange) => Expression)): Expression | VariableDeclaration[] { - // const flattenMode = flags & FlattenFlags.FlattenMask; - // const outputMode = flags & FlattenFlags.OutputMask; - // const options = flags & FlattenFlags.OptionsMask; - - - // if (outputMode === FlattenFlags.OutputExpressions) { - // return flattenDestructuringToExpression(context, node, !(options & FlattenFlags.NeedsValue), flattenMode, createAssignmentCallback, visitor); - // } - // else { - // return flattenDestructuringToDeclarations( - // context, - // node, - // boundValue) - // } - return; + ObjectRest, } /** @@ -90,7 +31,7 @@ namespace ts { * @param createAssignmentCallback A callback used to create the assignment expression. * @param visitor A visitor used to visit initializers. */ - export function flattenDestructuringToExpression( + export function flattenDestructuringAssignment( context: TransformationContext, node: VariableDeclaration | DestructuringAssignment, needsValue: boolean, @@ -120,9 +61,9 @@ namespace ts { recordTempVariablesInLine: false, emitExpression, emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: createEffectiveArrayAssignmentPattern, - createObjectBindingOrAssignmentPattern: createEffectiveObjectAssignmentPattern, - createArrayBindingOrAssignmentElement: createEffectiveAssignmentElement, + createArrayBindingOrAssignmentPattern: makeArrayAssignmentPattern, + createObjectBindingOrAssignmentPattern: makeObjectAssignmentPattern, + createArrayBindingOrAssignmentElement: makeAssignmentElement, visitor }; @@ -148,7 +89,7 @@ namespace ts { } } - flattenEffectiveBindingElement(host, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); + flattenBindingOrAssignmentElement(host, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); if (value && needsValue) { expressions.push(value); @@ -164,7 +105,7 @@ namespace ts { expressions.push(expression); } - function emitBindingOrAssignment(target: EffectiveBindingOrAssignmentTarget, value: Expression, location: TextRange, original: Node) { + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); const expression = createAssignmentCallback ? createAssignmentCallback(target, value, location) @@ -183,7 +124,7 @@ namespace ts { * @param recordTempVariablesInLine Indicates whether temporary variables should be recored in-line. * @param level Indicates the extent to which flattening should occur. */ - export function flattenDestructuringToDeclarations( + export function flattenDestructuringBinding( context: TransformationContext, node: VariableDeclaration | ParameterDeclaration, boundValue: Expression | undefined, @@ -201,13 +142,13 @@ namespace ts { recordTempVariablesInLine, emitExpression, emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: createEffectiveArrayBindingPattern, - createObjectBindingOrAssignmentPattern: createEffectiveObjectBindingPattern, - createArrayBindingOrAssignmentElement: createEffectiveBindingElement, + createArrayBindingOrAssignmentPattern: makeArrayBindingPattern, + createObjectBindingOrAssignmentPattern: makeObjectBindingPattern, + createArrayBindingOrAssignmentElement: makeBindingElement, visitor }; - flattenEffectiveBindingElement(host, node, boundValue, node, skipInitializer); + flattenBindingOrAssignmentElement(host, node, boundValue, node, skipInitializer); if (pendingExpressions) { const temp = createTempVariable(/*recordTempVariable*/ undefined); @@ -248,7 +189,7 @@ namespace ts { pendingExpressions = append(pendingExpressions, value); } - function emitBindingOrAssignment(target: EffectiveBindingOrAssignmentTarget, value: Expression, location: TextRange, original: Node) { + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { Debug.assertNode(target, isBindingName); if (pendingExpressions) { value = inlineExpressions(append(pendingExpressions, value)); @@ -258,14 +199,14 @@ namespace ts { } } - function flattenEffectiveBindingElement( + function flattenBindingOrAssignmentElement( host: FlattenHost, - bindingElement: EffectiveBindingOrAssignmentElement, + bindingElement: BindingOrAssignmentElement, boundValue: Expression | undefined, location: TextRange, skipInitializer?: boolean) { if (!skipInitializer) { - const initializer = visitNode(getInitializerOfEffectiveBindingElement(bindingElement), host.visitor, isExpression); + const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(bindingElement), host.visitor, isExpression); if (initializer) { // Combine value and initializer boundValue = boundValue ? createDefaultValueCheck(host, boundValue, initializer, location) : initializer; @@ -275,39 +216,36 @@ namespace ts { boundValue = createVoidZero(); } } - const bindingTarget = getTargetOfEffectiveBindingElement(bindingElement); - if (!isEffectiveBindingPattern(bindingTarget)) { - host.emitBindingOrAssignment(bindingTarget, boundValue, location, /*original*/ bindingElement); + const bindingTarget = getTargetOfBindingOrAssignmentElement(bindingElement); + if (isObjectBindingOrAssignmentPattern(bindingTarget)) { + flattenObjectBindingOrAssignmentPattern(host, bindingElement, bindingTarget, boundValue, location); + } + else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { + flattenArrayBindingOrAssignmentPattern(host, bindingElement, bindingTarget, boundValue, location); } else { - 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(host, boundValue, reuseIdentifierExpressions, location); - } - if (isEffectiveObjectBindingPattern(bindingTarget)) { - flattenEffectiveObjectBindingElements(host, bindingTarget, elements, boundValue, location); - } - else { - flattenEffectiveArrayBindingElements(host, bindingTarget, elements, boundValue, location); - } + host.emitBindingOrAssignment(bindingTarget, boundValue, location, /*original*/ bindingElement); } } - function flattenEffectiveObjectBindingElements(host: FlattenHost, bindingTarget: EffectiveObjectBindingOrAssignmentPattern, elements: EffectiveBindingOrAssignmentElement[], boundValue: Expression, location: TextRange) { - let bindingElements: EffectiveBindingOrAssignmentElement[]; + function flattenObjectBindingOrAssignmentPattern(host: FlattenHost, parentElement: BindingOrAssignmentElement, bindingTarget: ObjectBindingOrAssignmentPattern, boundValue: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(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(parentElement) || numElements !== 0; + boundValue = ensureIdentifier(host, boundValue, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[]; for (let i = 0; i < numElements; i++) { const element = elements[i]; - if (!getEffectiveRestIndicator(element)) { - if (host.level <= FlattenLevel.ObjectRest + if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + if (host.level >= FlattenLevel.ObjectRest && !(element.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)) - && !(getTargetOfEffectiveBindingElement(element).transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))) { + && !(getTargetOfBindingOrAssignmentElement(element).transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))) { bindingElements = append(bindingElements, element); } else { @@ -315,9 +253,9 @@ namespace ts { host.emitBindingOrAssignment(host.createObjectBindingOrAssignmentPattern(bindingElements), boundValue, location, bindingTarget); bindingElements = undefined; } - const propertyName = getEffectivePropertyNameOfEffectiveBindingElement(element); + const propertyName = getPropertyNameOfBindingOrAssignmentElement(element); const value = createDestructuringPropertyAccess(host, boundValue, propertyName); - flattenEffectiveBindingElement(host, element, value, /*location*/ element); + flattenBindingOrAssignmentElement(host, element, value, /*location*/ element); } } else if (i === numElements - 1) { @@ -326,7 +264,7 @@ namespace ts { bindingElements = undefined; } const value = createRestCall(boundValue, elements, bindingTarget); - flattenEffectiveBindingElement(host, element, value, element); + flattenBindingOrAssignmentElement(host, element, value, element); } } if (bindingElements) { @@ -334,16 +272,27 @@ namespace ts { } } - function flattenEffectiveArrayBindingElements(host: FlattenHost, bindingTarget: EffectiveArrayBindingOrAssignmentPattern, elements: EffectiveBindingOrAssignmentElement[], boundValue: Expression, location: TextRange) { - let bindingElements: EffectiveBindingOrAssignmentElement[]; - let spreadContainingElements: [Identifier, EffectiveBindingOrAssignmentElement][]; + function flattenArrayBindingOrAssignmentPattern(host: FlattenHost, parentElement: BindingOrAssignmentElement, bindingTarget: ArrayBindingOrAssignmentPattern, boundValue: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(bindingTarget); const numElements = elements.length; + if (numElements !== 1 && (host.level < FlattenLevel.ObjectRest || numElements === 0)) { + // 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(parentElement) || numElements !== 0; + boundValue = ensureIdentifier(host, boundValue, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[]; + let restContainingElements: [Identifier, BindingOrAssignmentElement][]; for (let i = 0; i < numElements; i++) { const element = elements[i]; - if (host.level <= FlattenLevel.ObjectRest) { - if (element.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest) && i < numElements - 1) { + if (host.level >= FlattenLevel.ObjectRest) { + // If an array pattern contains an ObjectRest, we must cache the result so that we + // can perform the ObjectRest destructuring in a different declaration + if (element.transformFlags & TransformFlags.ContainsObjectRest) { const temp = createTempVariable(/*recordTempVariable*/ undefined); - spreadContainingElements = append(spreadContainingElements, <[Identifier, EffectiveBindingOrAssignmentElement]>[temp, element]); + restContainingElements = append(restContainingElements, <[Identifier, BindingOrAssignmentElement]>[temp, element]); bindingElements = append(bindingElements, host.createArrayBindingOrAssignmentElement(temp)); } else { @@ -353,21 +302,21 @@ namespace ts { else if (isOmittedExpression(element)) { continue; } - else if (!getEffectiveRestIndicator(element)) { + else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { const value = createElementAccess(boundValue, i); - flattenEffectiveBindingElement(host, element, value, /*location*/ element); + flattenBindingOrAssignmentElement(host, element, value, /*location*/ element); } else if (i === numElements - 1) { const value = createArraySlice(boundValue, i); - flattenEffectiveBindingElement(host, element, value, /*location*/ element); + flattenBindingOrAssignmentElement(host, element, value, /*location*/ element); } } if (bindingElements) { host.emitBindingOrAssignment(host.createArrayBindingOrAssignmentPattern(bindingElements), boundValue, location, bindingTarget); } - if (spreadContainingElements) { - for (const [id, element] of spreadContainingElements) { - flattenEffectiveBindingElement(host, element, id, element); + if (restContainingElements) { + for (const [id, element] of restContainingElements) { + flattenBindingOrAssignmentElement(host, element, id, element); } } } @@ -448,321 +397,35 @@ namespace ts { } } - /** - * Determines whether the EffectiveBindingElement is a declaration - */ - function isDeclarationBindingElement(bindingElement: EffectiveBindingOrAssignmentElement): bindingElement is VariableDeclaration | ParameterDeclaration | BindingElement { - switch (bindingElement.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - return true; - } - - return false; - } - - /** - * Gets the initializer of an EffectiveBindingElement. - */ - function getInitializerOfEffectiveBindingElement(bindingElement: EffectiveBindingOrAssignmentElement): 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; - } - - 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; - } - - if (isShorthandPropertyAssignment(bindingElement)) { - // `1` in `({ a = 1 } = ...)` - return bindingElement.objectAssignmentInitializer; - } - - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `1` in `[a = 1] = ...` - // `1` in `[{a} = 1] = ...` - // `1` in `[[a] = 1] = ...` - return bindingElement.right; - } - - if (isSpreadExpression(bindingElement) || isPartiallyEmittedExpression(bindingElement)) { - // Recovery consistent with existing emit. - return getInitializerOfEffectiveBindingElement(bindingElement.expression); - } - } - - /** - * Gets the name of an EffectiveBindingElement. - */ - function getTargetOfEffectiveBindingElement(bindingElement: EffectiveBindingOrAssignmentElement): EffectiveBindingOrAssignmentTarget { - if (isDeclarationBindingElement(bindingElement)) { - // `a` in `let { a } = ...` - // `a` in `let { a = 1 } = ...` - // `b` in `let { a: b } = ...` - // `b` in `let { a: b = 1 } = ...` - // `a` in `let { ...a } = ...` - // `{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 (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; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - return getTargetOfEffectiveBindingElement(bindingElement.expression); - } - - // 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 (isSpreadExpression(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 an EffectiveBindingElement is a rest element. - */ - function getEffectiveRestIndicator(bindingElement: EffectiveBindingOrAssignmentElement): EffectiveBindingOrAssignmentRestIndicator { - switch (bindingElement.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - // `...` in `let [...a] = ...` - return (bindingElement).dotDotDotToken; - - case SyntaxKind.SpreadElement: - case SyntaxKind.SpreadAssignment: - // `...` in `[...a] = ...` - return bindingElement; - } - - return undefined; - } - - /** - * Gets the property name of a BindingElement-like element - */ - function getEffectivePropertyNameOfEffectiveBindingElement(bindingElement: EffectiveBindingOrAssignmentElement) { - 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; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - return (bindingElement).name; - } - - 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: EffectiveBindingOrAssignmentTarget): node is EffectiveBindingOrAssignmentPattern { - return isEffectiveObjectBindingPattern(node) - || isEffectiveArrayBindingPattern(node); - } - - /** - * Determines whether a node is ObjectBindingPattern-like - */ - function isEffectiveObjectBindingPattern(node: EffectiveBindingOrAssignmentTarget): node is EffectiveObjectBindingOrAssignmentPattern { - switch (node.kind) { - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ObjectLiteralExpression: - return true; - } - - return false; - } - - /** - * Determines whether a node is ArrayBindingPattern-like - */ - function isEffectiveArrayBindingPattern(node: EffectiveBindingOrAssignmentTarget): node is EffectiveArrayBindingOrAssignmentPattern { - switch (node.kind) { - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - return true; - } - - return false; - } - - /** - * Gets the elements of a BindingPattern-like name - */ - function getElementsOfEffectiveBindingPattern(name: EffectiveBindingOrAssignmentPattern): EffectiveBindingOrAssignmentElement[] { - switch (name.kind) { - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - // `a` in `{a}` - // `a` in `[a]` - return name.elements; - - case SyntaxKind.ObjectLiteralExpression: - // `a` in `{a}` - return name.properties; - } - } - - function createEffectiveArrayBindingPattern(elements: EffectiveBindingOrAssignmentElement[]) { + function makeArrayBindingPattern(elements: BindingOrAssignmentElement[]) { Debug.assertEachNode(elements, isArrayBindingElement); return createArrayBindingPattern(elements); } - function createEffectiveArrayAssignmentPattern(elements: EffectiveBindingOrAssignmentElement[]) { - return createArrayLiteral(map(elements, convertToArrayLiteralElement)); + function makeArrayAssignmentPattern(elements: BindingOrAssignmentElement[]) { + return createArrayLiteral(map(elements, convertToArrayAssignmentElement)); } - function createEffectiveObjectBindingPattern(elements: EffectiveBindingOrAssignmentElement[]) { + function makeObjectBindingPattern(elements: BindingOrAssignmentElement[]) { Debug.assertEachNode(elements, isBindingElement); return createObjectBindingPattern(elements); } - function createEffectiveObjectAssignmentPattern(elements: EffectiveBindingOrAssignmentElement[]) { - return createObjectLiteral(map(elements, convertToObjectLiteralElement)); + function makeObjectAssignmentPattern(elements: BindingOrAssignmentElement[]) { + return createObjectLiteral(map(elements, convertToObjectAssignmentElement)); } - function createEffectiveBindingElement(name: Identifier) { + function makeBindingElement(name: Identifier) { return createBindingElement(/*propertyName*/ undefined, /*dotDotDotToken*/ undefined, name); } - function createEffectiveAssignmentElement(name: Identifier) { + function makeAssignmentElement(name: Identifier) { return name; } - function convertToArrayLiteralElement(element: EffectiveBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(createSpread(element.name, element), element); - } - const expression = convertToExpressionTarget(element.name); - return element.initializer ? setOriginalNode(createAssignment(expression, element.initializer, element), element) : expression; - } - Debug.assertNode(element, isExpression); - return element; - } - - function convertToObjectLiteralElement(element: EffectiveBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(createSpreadAssignment(element.name, element), element); - } - if (element.propertyName) { - const expression = convertToExpressionTarget(element.name); - return setOriginalNode(createPropertyAssignment(element.propertyName, element.initializer ? createAssignment(expression, element.initializer) : expression, element), element); - } - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(createShorthandPropertyAssignment(element.name, element.initializer, element), element); - } - Debug.assertNode(element, isObjectLiteralElementLike); - return element; - } - - function convertToExpressionTarget(target: EffectiveBindingOrAssignmentTarget): Expression { - if (isBindingPattern(target)) { - switch (target.kind) { - case SyntaxKind.ArrayBindingPattern: - return setOriginalNode(createArrayLiteral(map(target.elements, convertToArrayLiteralElement), target), target); - case SyntaxKind.ObjectBindingPattern: - return setOriginalNode(createObjectLiteral(map(target.elements, convertToObjectLiteralElement), target), target); - } - return; - } - Debug.assertNode(target, isExpression); - return target; - } - /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);`*/ - function createRestCall(value: Expression, elements: EffectiveBindingOrAssignmentElement[], location: TextRange): Expression { + function createRestCall(value: Expression, elements: BindingOrAssignmentElement[], location: TextRange): Expression { const propertyNames: LiteralExpression[] = []; for (let i = 0; i < elements.length - 1; i++) { if (isOmittedExpression(elements[i])) { @@ -771,7 +434,7 @@ namespace ts { const str = createSynthesizedNode(SyntaxKind.StringLiteral); str.pos = location.pos; str.end = location.end; - str.text = getTextOfPropertyName(getEffectivePropertyNameOfEffectiveBindingElement(elements[i])); + str.text = getTextOfPropertyName(getPropertyNameOfBindingOrAssignmentElement(elements[i])); propertyNames.push(str); } const args = createSynthesizedNodeArray([value, createArrayLiteral(propertyNames, location)]); diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index dd7498c4149..29107e149f4 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1365,7 +1365,7 @@ 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 flattenDestructuringToExpression( + return flattenDestructuringAssignment( context, node, needsDestructuringValue, @@ -1383,7 +1383,7 @@ namespace ts { if (decl.initializer) { let assignment: Expression; if (isBindingPattern(decl.name)) { - assignment = flattenDestructuringToExpression( + assignment = flattenDestructuringAssignment( context, decl, /*needsValue*/ false, @@ -1543,8 +1543,7 @@ namespace ts { if (isBindingPattern(node.name)) { const recordTempVariablesInLine = !enclosingVariableStatement || !hasModifier(enclosingVariableStatement, ModifierFlags.Export); - debugger; - return flattenDestructuringToDeclarations( + return flattenDestructuringBinding( context, node, /*value*/ undefined, @@ -2181,7 +2180,7 @@ namespace ts { const temp = createTempVariable(undefined); const newVariableDeclaration = createVariableDeclaration(temp, undefined, undefined, node.variableDeclaration); - const vars = flattenDestructuringToDeclarations(context, node.variableDeclaration, temp, /*skipInitializer*/ false, /*recordTempVariablesInLine*/ true, FlattenLevel.All, visitor); + const vars = flattenDestructuringBinding(context, node.variableDeclaration, temp, /*skipInitializer*/ false, /*recordTempVariablesInLine*/ true, FlattenLevel.All, visitor); const list = createVariableDeclarationList(vars, /*location*/node.variableDeclaration, /*flags*/node.variableDeclaration.flags); const destructure = createVariableStatement(undefined, list); diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index a52ee9b1a24..fb854318e69 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -135,7 +135,7 @@ namespace ts { */ function visitBinaryExpression(node: BinaryExpression, needsDestructuringValue: boolean): Expression { if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsESNext) { - return flattenDestructuringToExpression( + return flattenDestructuringAssignment( context, node, needsDestructuringValue, @@ -156,7 +156,7 @@ namespace ts { function visitVariableDeclaration(node: VariableDeclaration): VisitResult { // If we are here it is because the name contains a binding pattern with a rest somewhere in it. if (isBindingPattern(node.name) && node.name.transformFlags & TransformFlags.AssertESNext) { - return flattenDestructuringToDeclarations(context, node, /*boundValue*/ undefined, /*skipInitializer*/ false, /*recordTempVariablesInLine*/ true, FlattenLevel.ObjectRest, visitor); + return flattenDestructuringBinding(context, node, /*boundValue*/ undefined, /*skipInitializer*/ false, /*recordTempVariablesInLine*/ true, FlattenLevel.ObjectRest, visitor); } return visitEachChild(node, visitor, context); } @@ -202,14 +202,14 @@ namespace ts { const declaration = firstOrUndefined(initializer.declarations); return declaration && declaration.name && declaration.name.kind === SyntaxKind.ObjectBindingPattern && - !!(declaration.name.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)); + !!(declaration.name.transformFlags & TransformFlags.ContainsObjectRest); } return false; } function isRestAssignment(initializer: ForInitializer) { return initializer.kind === SyntaxKind.ObjectLiteralExpression && - initializer.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest); + initializer.transformFlags & TransformFlags.ContainsObjectRest; } function visitParameter(node: ParameterDeclaration): ParameterDeclaration { @@ -238,7 +238,7 @@ namespace ts { function isObjectRestParameter(node: ParameterDeclaration) { return node.name && node.name.kind === SyntaxKind.ObjectBindingPattern && - !!(node.name.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest)); + !!(node.name.transformFlags & TransformFlags.ContainsObjectRest); } function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index bb17b99e5c6..8c65630aa5c 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -756,7 +756,7 @@ namespace ts { */ function transformInitializedVariable(node: VariableDeclaration): Expression { if (isBindingPattern(node.name)) { - return flattenDestructuringToExpression( + return flattenDestructuringAssignment( context, node, /*needsValue*/ false, diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 1ed1f82d506..3d89305f617 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -808,7 +808,7 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression { const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment; return isBindingPattern(node.name) - ? flattenDestructuringToExpression(context, node, /*needsValue*/ false, FlattenLevel.All, createAssignment, destructuringVisitor) + ? flattenDestructuringAssignment(context, node, /*needsValue*/ false, FlattenLevel.All, createAssignment, destructuringVisitor) : createAssignment(node.name, visitNode(node.initializer, destructuringVisitor, isExpression)); } @@ -1459,7 +1459,7 @@ namespace ts { */ function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult { if (hasExportedReferenceInDestructuringTarget(node.left)) { - return flattenDestructuringToExpression( + return flattenDestructuringAssignment( context, node, /*needsValue*/ true, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 118b170b6a4..4fc68566795 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2353,7 +2353,7 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration): Expression { const name = node.name; if (isBindingPattern(name)) { - return flattenDestructuringToExpression( + return flattenDestructuringAssignment( context, node, /*needsValue*/ false, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e0ba546fb96..0cda729f998 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -722,22 +722,20 @@ namespace ts { name: PropertyName; } - export interface BindingPattern extends Node { - elements: NodeArray; - } - - export interface ObjectBindingPattern extends BindingPattern { + export interface ObjectBindingPattern extends Node { kind: SyntaxKind.ObjectBindingPattern; elements: NodeArray; } - export type ArrayBindingElement = BindingElement | OmittedExpression; - - export interface ArrayBindingPattern extends BindingPattern { + export interface ArrayBindingPattern extends Node { kind: SyntaxKind.ArrayBindingPattern; elements: NodeArray; } + export type BindingPattern = (ObjectBindingPattern | ArrayBindingPattern) & { elements: NodeArray; }; + + export type ArrayBindingElement = BindingElement | OmittedExpression; + /** * Several node kinds share function-like features such as a signature, * a name, and a body. These nodes should extend FunctionLikeDeclaration. @@ -1191,7 +1189,49 @@ namespace ts { left: ArrayLiteralExpression; } - export type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; + export type DestructuringAssignment + = ObjectDestructuringAssignment + | ArrayDestructuringAssignment + ; + + export type BindingOrAssignmentElement + = VariableDeclaration + | ParameterDeclaration + | BindingElement + | PropertyAssignment // AssignmentProperty + | ShorthandPropertyAssignment // AssignmentProperty + | SpreadAssignment // AssignmentRestProperty + | OmittedExpression // Elision + | SpreadElement // AssignmentRestElement + | ArrayLiteralExpression // ArrayAssignmentPattern + | ObjectLiteralExpression // ObjectAssignmentPattern + | AssignmentExpression // AssignmentElement + | Identifier // DestructuringAssignmentTarget + | PropertyAccessExpression // DestructuringAssignmentTarget + | ElementAccessExpression // DestructuringAssignmentTarget + ; + + export type BindingOrAssignmentElementRestIndicator + = DotDotDotToken // from BindingElement + | SpreadElement // AssignmentRestElement + | SpreadAssignment // AssignmentRestProperty + ; + + export type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + + export type ObjectBindingOrAssignmentPattern + = ObjectBindingPattern + | ObjectLiteralExpression // ObjectAssignmentPattern + ; + + export type ArrayBindingOrAssignmentPattern + = ArrayBindingPattern + | ArrayLiteralExpression // ArrayAssignmentPattern + ; + + export type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression; + + export type BindingOrAssignmentPattern = ObjectBindingOrAssignmentPattern | ArrayBindingOrAssignmentPattern; export interface ConditionalExpression extends Expression { kind: SyntaxKind.ConditionalExpression; @@ -3554,10 +3594,10 @@ namespace ts { TypeExcludes = ~ContainsTypeScript, ObjectLiteralExcludes = NodeExcludes | ContainsDecorators | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName | ContainsObjectSpread, ArrayLiteralOrCallOrNewExcludes = NodeExcludes | ContainsSpread, - VariableDeclarationListExcludes = NodeExcludes | ContainsBindingPattern | ContainsObjectSpread, + VariableDeclarationListExcludes = NodeExcludes | ContainsBindingPattern | ContainsObjectRest, ParameterExcludes = NodeExcludes, - CatchClauseExcludes = NodeExcludes | ContainsObjectSpread, - BindingPatternExcludes = NodeExcludes | ContainsSpread, + CatchClauseExcludes = NodeExcludes | ContainsObjectRest, + BindingPatternExcludes = NodeExcludes | ContainsRest, // Masks // - Additional bitmasks diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f0a666e1531..669b0d542ac 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3948,6 +3948,78 @@ namespace ts { || kind === SyntaxKind.OmittedExpression; } + + /** + * Determines whether the BindingOrAssignmentElement is a BindingElement-like declaration + */ + export function isDeclarationBindingElement(bindingElement: BindingOrAssignmentElement): bindingElement is VariableDeclaration | ParameterDeclaration | BindingElement { + switch (bindingElement.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + return true; + } + + return false; + } + + /** + * Determines whether a node is a BindingOrAssignmentElement + */ + export function isBindingOrAssignmentElement(node: Node): node is BindingOrAssignmentElement { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: + case SyntaxKind.OmittedExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.Identifier: + case SyntaxKind.SpreadElement: + return true; + } + return isAssignmentExpression(node, /*excludeCompoundAssignment*/ true); + } + + /** + * Determines whether a node is a BindingOrAssignmentPattern + */ + export function isBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is BindingOrAssignmentPattern { + return isObjectBindingOrAssignmentPattern(node) + || isArrayBindingOrAssignmentPattern(node); + } + + /** + * Determines whether a node is an ObjectBindingOrAssignmentPattern + */ + export function isObjectBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is ObjectBindingOrAssignmentPattern { + switch (node.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return true; + } + + return false; + } + + /** + * Determines whether a node is an ArrayBindingOrAssignmentPattern + */ + export function isArrayBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is ArrayBindingOrAssignmentPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return true; + } + + return false; + } + // Expression export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression {