diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 3c3234b174817..1ba5572278ffc 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -450,20 +450,84 @@ namespace ts { return visitEachChild(node, visitor, context); } + function wrapPrivateNameForDestructuringAssignment(node: PrivateNamedPropertyAccessExpression): Expression { + const parameterName = createIdentifier("x"); + const parameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + parameterName, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); + const setterName = createIdentifier("value"); + return createPropertyAccess( + createObjectLiteral([ + createSetAccessor( + /*decorators*/ undefined, + /*modifiers*/ undefined, + setterName, + [parameter], + createBlock([createReturn(createAssignment(node, parameterName))]) + ) + ]), + setterName + ); + } + /** * Visits a BinaryExpression that contains a destructuring assignment. * * @param node A BinaryExpression node. */ function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression { - if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRest) { - return flattenDestructuringAssignment( - node, - visitor, - context, - FlattenLevel.ObjectRest, - !noDestructuringValue - ); + if (isDestructuringAssignment(node)) { + // Check for private name assignments. + let leftExpr = node.left; + if (isArrayLiteralExpression(leftExpr)) { + const hasPrivateNames = forEach(leftExpr.elements, isPrivateNamedPropertyAccessExpression); + if (hasPrivateNames) { + leftExpr = updateArrayLiteral( + leftExpr, + leftExpr.elements.map( + element => isPrivateNamedPropertyAccessExpression(element) ? + wrapPrivateNameForDestructuringAssignment(element) : + element + ) + ); + } + } + else { + const hasPrivateNames = forEach( + leftExpr.properties, + prop => isPropertyAssignment(prop) && isPrivateNamedPropertyAccessExpression(prop.initializer) + ); + if (hasPrivateNames) { + leftExpr = updateObjectLiteral( + leftExpr, + leftExpr.properties.map( + prop => isPropertyAssignment(prop) && isPrivateNamedPropertyAccessExpression(prop.initializer) ? + updatePropertyAssignment(prop, prop.name, wrapPrivateNameForDestructuringAssignment(prop.initializer)) : + prop + ) + ); + } + } + if (leftExpr !== node.left || (node.left.transformFlags & TransformFlags.ContainsObjectRest)) { + return flattenDestructuringAssignment( + updateBinary( + node, + leftExpr, + node.right, + node.operatorToken + ) as DestructuringAssignment, + visitor, + context, + (node.left.transformFlags & TransformFlags.ContainsObjectRest) ? FlattenLevel.ObjectRest : FlattenLevel.All, + !noDestructuringValue + ); + } } else if (node.operatorToken.kind === SyntaxKind.CommaToken) { return updateBinary( diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a86badd2c56a4..70ef6de6ade07 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1676,6 +1676,10 @@ namespace ts { name: Identifier | PrivateName; } + export interface PrivateNamedPropertyAccessExpression extends PropertyAccessExpression { + name: PrivateName; + } + export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 74c1e6086514f..dbb4bb6f73a55 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6095,6 +6095,10 @@ namespace ts { return node && isPropertyDeclaration(node) && isPrivateName(node.name); } + export function isPrivateNamedPropertyAccessExpression(node: Node): node is PrivateNamedPropertyAccessExpression { + return node && isPropertyAccessExpression(node) && isPrivateName(node.name); + } + // Type members export function isTypeElement(node: Node): node is TypeElement { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 0918ac03f824c..e2d17dd94e33c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1056,6 +1056,9 @@ declare namespace ts { expression: LeftHandSideExpression; name: Identifier | PrivateName; } + interface PrivateNamedPropertyAccessExpression extends PropertyAccessExpression { + name: PrivateName; + } interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -3462,6 +3465,7 @@ declare namespace ts { function isClassLike(node: Node): node is ClassLikeDeclaration; function isAccessor(node: Node): node is AccessorDeclaration; function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration; + function isPrivateNamedPropertyAccessExpression(node: Node): node is PrivateNamedPropertyAccessExpression; function isTypeElement(node: Node): node is TypeElement; function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement; function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index acafe6f5d59a1..08cedb79fca13 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1056,6 +1056,9 @@ declare namespace ts { expression: LeftHandSideExpression; name: Identifier | PrivateName; } + interface PrivateNamedPropertyAccessExpression extends PropertyAccessExpression { + name: PrivateName; + } interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -3462,6 +3465,7 @@ declare namespace ts { function isClassLike(node: Node): node is ClassLikeDeclaration; function isAccessor(node: Node): node is AccessorDeclaration; function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration; + function isPrivateNamedPropertyAccessExpression(node: Node): node is PrivateNamedPropertyAccessExpression; function isTypeElement(node: Node): node is TypeElement; function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement; function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; diff --git a/tests/baselines/reference/privateNameDestructuringAssignment.js b/tests/baselines/reference/privateNameDestructuringAssignment.js new file mode 100644 index 0000000000000..6124f94908d73 --- /dev/null +++ b/tests/baselines/reference/privateNameDestructuringAssignment.js @@ -0,0 +1,26 @@ +//// [privateNameDestructuringAssignment.ts] +class Test { + #myPropDefault = 23; + #myProp: number; + constructor() { + ({ value: this.#myProp } = { value: 10 }); + ({ something: this.#myProp } = { something: this.#myPropDefault }); + } +} + + +//// [privateNameDestructuringAssignment.js] +var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; }; +var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); }; +var Test = /** @class */ (function () { + function Test() { + _myPropDefault.set(this, void 0); + _myProp.set(this, void 0); + _classPrivateFieldSet(this, _myPropDefault, 23); + ({ set value(x) { return _classPrivateFieldSet(this, _myProp, x); } }.value = { value: 10 }.value); + ({ set value(x) { return _classPrivateFieldSet(this, _myProp, x); } }.value = { something: _classPrivateFieldGet(this, _myPropDefault) }.something); + } + return Test; +}()); +var _myPropDefault = new WeakMap; +var _myProp = new WeakMap; diff --git a/tests/baselines/reference/privateNameDestructuringAssignment.symbols b/tests/baselines/reference/privateNameDestructuringAssignment.symbols new file mode 100644 index 0000000000000..90ad3851230e3 --- /dev/null +++ b/tests/baselines/reference/privateNameDestructuringAssignment.symbols @@ -0,0 +1,27 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameDestructuringAssignment.ts === +class Test { +>Test : Symbol(Test, Decl(privateNameDestructuringAssignment.ts, 0, 0)) + + #myPropDefault = 23; +>#myPropDefault : Symbol(Test[#myPropDefault], Decl(privateNameDestructuringAssignment.ts, 0, 12)) + + #myProp: number; +>#myProp : Symbol(Test[#myProp], Decl(privateNameDestructuringAssignment.ts, 1, 24)) + + constructor() { + ({ value: this.#myProp } = { value: 10 }); +>value : Symbol(value, Decl(privateNameDestructuringAssignment.ts, 4, 10)) +>this.#myProp : Symbol(Test[#myProp], Decl(privateNameDestructuringAssignment.ts, 1, 24)) +>this : Symbol(Test, Decl(privateNameDestructuringAssignment.ts, 0, 0)) +>value : Symbol(value, Decl(privateNameDestructuringAssignment.ts, 4, 36)) + + ({ something: this.#myProp } = { something: this.#myPropDefault }); +>something : Symbol(something, Decl(privateNameDestructuringAssignment.ts, 5, 10)) +>this.#myProp : Symbol(Test[#myProp], Decl(privateNameDestructuringAssignment.ts, 1, 24)) +>this : Symbol(Test, Decl(privateNameDestructuringAssignment.ts, 0, 0)) +>something : Symbol(something, Decl(privateNameDestructuringAssignment.ts, 5, 40)) +>this.#myPropDefault : Symbol(Test[#myPropDefault], Decl(privateNameDestructuringAssignment.ts, 0, 12)) +>this : Symbol(Test, Decl(privateNameDestructuringAssignment.ts, 0, 0)) + } +} + diff --git a/tests/baselines/reference/privateNameDestructuringAssignment.types b/tests/baselines/reference/privateNameDestructuringAssignment.types new file mode 100644 index 0000000000000..fa572327a6dcb --- /dev/null +++ b/tests/baselines/reference/privateNameDestructuringAssignment.types @@ -0,0 +1,37 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameDestructuringAssignment.ts === +class Test { +>Test : Test + + #myPropDefault = 23; +>#myPropDefault : number +>23 : 23 + + #myProp: number; +>#myProp : number + + constructor() { + ({ value: this.#myProp } = { value: 10 }); +>({ value: this.#myProp } = { value: 10 }) : { value: number; } +>{ value: this.#myProp } = { value: 10 } : { value: number; } +>{ value: this.#myProp } : { value: number; } +>value : number +>this.#myProp : number +>this : this +>{ value: 10 } : { value: number; } +>value : number +>10 : 10 + + ({ something: this.#myProp } = { something: this.#myPropDefault }); +>({ something: this.#myProp } = { something: this.#myPropDefault }) : { something: number; } +>{ something: this.#myProp } = { something: this.#myPropDefault } : { something: number; } +>{ something: this.#myProp } : { something: number; } +>something : number +>this.#myProp : number +>this : this +>{ something: this.#myPropDefault } : { something: number; } +>something : number +>this.#myPropDefault : number +>this : this + } +} + diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameDestructuringAssignment.ts b/tests/cases/conformance/classes/members/privateNames/privateNameDestructuringAssignment.ts new file mode 100644 index 0000000000000..e7229fcce60c1 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameDestructuringAssignment.ts @@ -0,0 +1,8 @@ +class Test { + #myPropDefault = 23; + #myProp: number; + constructor() { + ({ value: this.#myProp } = { value: 10 }); + ({ something: this.#myProp } = { something: this.#myPropDefault }); + } +}