diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d34efcbe1bf..039944bd6f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11164,10 +11164,10 @@ namespace ts { return links.resolvedType; } - function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, properties: Symbol[], kind: IndexKind): IndexInfo { + function getObjectLiteralIndexInfo(propertyNodes: NodeArray, offset: number, properties: Symbol[], kind: IndexKind): IndexInfo { const propTypes: Type[] = []; for (let i = 0; i < properties.length; i++) { - if (kind === IndexKind.String || isNumericName(node.properties[i].name)) { + if (kind === IndexKind.String || isNumericName(propertyNodes[i + offset].name)) { propTypes.push(getTypeOfSymbol(properties[i])); } } @@ -11193,7 +11193,10 @@ namespace ts { let hasComputedStringProperty = false; let hasComputedNumberProperty = false; - for (const memberDecl of node.properties) { + let i = 0; + let offset = 0; + for (i = 0; i < node.properties.length; i++) { + const memberDecl = node.properties[i]; let member = memberDecl.symbol; if (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || @@ -11262,6 +11265,7 @@ namespace ts { return unknownType; } spread = getSpreadType(spread, type, /*isFromObjectLiteral*/ false); + offset = i + 1; continue; } else { @@ -11315,8 +11319,8 @@ namespace ts { return createObjectLiteralType(); function createObjectLiteralType() { - const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.String) : undefined; - const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.Number) : undefined; + const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node.properties, offset, propertiesArray, IndexKind.String) : undefined; + const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node.properties, offset, propertiesArray, IndexKind.Number) : undefined; const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral; result.flags |= TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index a18146bdaa1..84d5462914c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -45,7 +45,7 @@ var __assign = (this && this.__assign) || Object.assign || function(t) { const restHelper = ` var __rest = (this && this.__rest) || function (s, e) { var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && !e.indexOf(p)) + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) === -1) t[p] = s[p]; return t; };`; diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index a8c00776741..692d021eb5e 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -304,17 +304,19 @@ namespace ts { 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 + // When doing so we want to highlight the passed in source map node since that's the one needing this temp assignment value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment); } let bindingElements: ObjectLiteralElementLike[] = []; + let computedTempVariables: Expression[]; for (let i = 0; i < properties.length; i++) { const p = properties[i]; if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) { if (!transformRest || p.transformFlags & TransformFlags.ContainsSpreadExpression || - (p.kind === SyntaxKind.PropertyAssignment && p.initializer.transformFlags & TransformFlags.ContainsSpreadExpression)) { + (p.kind === SyntaxKind.PropertyAssignment && p.initializer.transformFlags & TransformFlags.ContainsSpreadExpression) || + isComputedPropertyName(p.name)) { if (bindingElements.length) { emitRestAssignment(bindingElements, value, location, target); bindingElements = []; @@ -322,7 +324,11 @@ namespace ts { const propName = (p).name; const bindingTarget = p.kind === SyntaxKind.ShorthandPropertyAssignment ? p : (p).initializer || propName; // Assignment for bindingTarget = value.propName should highlight whole property, hence use p as source map node - emitDestructuringAssignment(bindingTarget, createDestructuringPropertyAccess(value, propName), p); + const propAccess = createDestructuringPropertyAccess(value, propName); + if (isComputedPropertyName(propName)) { + (computedTempVariables = computedTempVariables || []).push((propAccess as ElementAccessExpression).argumentExpression); + } + emitDestructuringAssignment(bindingTarget, propAccess, p); } else { bindingElements.push(p); @@ -336,7 +342,7 @@ namespace ts { bindingElements = []; } const propName = (p as SpreadAssignment).expression as Identifier; - const restCall = createRestCall(value, target.properties, p => p.name, target); + const restCall = createRestCall(value, target.properties, p => p.name, target, computedTempVariables); emitDestructuringAssignment(propName, restCall, p); } } @@ -413,17 +419,28 @@ namespace ts { /** 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: T[], getPropertyName: (element: T) => PropertyName, location: TextRange): Expression { - const propertyNames: LiteralExpression[] = []; + function createRestCall(value: Expression, elements: T[], getPropertyName: (element: T) => PropertyName, location: TextRange, computedTempVariables: Expression[]): Expression { + const propertyNames: Expression[] = []; for (let i = 0; i < elements.length - 1; i++) { - if (isOmittedExpression(elements[i])) { + const element = elements[i]; + if (isOmittedExpression(element)) { continue; } - const str = createSynthesizedNode(SyntaxKind.StringLiteral); - str.pos = location.pos; - str.end = location.end; - str.text = getTextOfPropertyName(getPropertyName(elements[i])); - propertyNames.push(str); + if (isComputedPropertyName(getPropertyName(element))) { + // get the temp name and put that in there instead, like `_tmp + ""` + const stringifiedTemp = createSynthesizedNode(SyntaxKind.StringLiteral); + stringifiedTemp.pos = location.pos; + stringifiedTemp.end = location.end; + stringifiedTemp.text = ""; + propertyNames.push(createBinary(computedTempVariables.shift(), SyntaxKind.PlusToken, stringifiedTemp)); + } + else { + const str = createSynthesizedNode(SyntaxKind.StringLiteral); + str.pos = location.pos; + str.end = location.end; + str.text = getTextOfPropertyName(getPropertyName(element)); + propertyNames.push(str); + } } const args = createSynthesizedNodeArray([value, createArrayLiteral(propertyNames, location)]); return createCall(createIdentifier("__rest"), undefined, args); @@ -522,6 +539,7 @@ namespace ts { const elements = name.elements; const numElements = elements.length; let bindingElements: BindingElement[] = []; + let computedTempVariables: Expression[]; for (let i = 0; i < numElements; i++) { const element = elements[i]; if (isOmittedExpression(element)) { @@ -533,12 +551,15 @@ namespace ts { bindingElements = []; } const restCall = createRestCall(value, - name.elements, + elements, // name.elements, element => (element as BindingElement).propertyName || (element as BindingElement).name, - name); + name, + computedTempVariables); emitBindingElement(element, restCall); } - else if (transformRest && !(element.transformFlags & TransformFlags.ContainsSpreadExpression)) { + else if (transformRest && + !(element.transformFlags & TransformFlags.ContainsSpreadExpression) && + !isComputedPropertyName(element.propertyName || element.name)) { // do not emit until we have a complete bundle of ES2015 syntax bindingElements.push(element); } @@ -548,8 +569,13 @@ namespace ts { bindingElements = []; } // Rewrite element to a declaration with an initializer that fetches property + // TODO: Probably save the result of createDestructuringPropertyAccess if propName is a computed property const propName = element.propertyName || element.name; - emitBindingElement(element, createDestructuringPropertyAccess(value, propName)); + const propAccess = createDestructuringPropertyAccess(value, propName); + if (isComputedPropertyName(propName)) { + (computedTempVariables = computedTempVariables || []).push((propAccess as ElementAccessExpression).argumentExpression); + } + emitBindingElement(element, propAccess); } } if (bindingElements.length) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a9f2429d376..ed15264ba16 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1262,6 +1262,7 @@ namespace ts { case SyntaxKind.Decorator: case SyntaxKind.JsxExpression: case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.SpreadAssignment: return true; case SyntaxKind.ExpressionWithTypeArguments: return (parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);