Skip to content

Transform private name bindings in destructuring assignments #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 93 additions & 8 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,20 +758,105 @@ namespace ts {
return visitEachChild(node, visitor, context);
}

function wrapPrivateNameForDestructuringTarget(node: PrivateNamedPropertyAccess) {
return createPropertyAccess(
createObjectLiteral([
createSetAccessor(
/*decorators*/ undefined,
/*modifiers*/ undefined,
"value",
[createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined, "x",
/*questionToken*/ undefined,
/*type*/ undefined,
/*initializer*/ undefined
)],
createBlock(
[createExpressionStatement(
createAssignment(
visitNode(node, visitor),
createIdentifier("x")
)
)]
)
)
]),
"value"
);
}

function transformDestructuringAssignmentTarget(node: ArrayLiteralExpression | ObjectLiteralExpression) {
const hasPrivateNames = isArrayLiteralExpression(node) ?
forEach(node.elements, isPrivateNamedPropertyAccess) :
forEach(node.properties, property => isPropertyAssignment(property) && isPrivateNamedPropertyAccess(property.initializer));
if (!hasPrivateNames) {
return node;
}
if (isArrayLiteralExpression(node)) {
// Transforms private names in destructuring assignment array bindings.
//
// Source:
// ([ this.#myProp ] = [ "hello" ]);
//
// Transformation:
// [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ];
return updateArrayLiteral(
node,
node.elements.map(
expr => isPrivateNamedPropertyAccess(expr) ?
wrapPrivateNameForDestructuringTarget(expr) :
expr
)
);
}
else {
// Transforms private names in destructuring assignment object bindings.
//
// Source:
// ({ stringProperty: this.#myProp } = { stringProperty: "hello" });
//
// Transformation:
// ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" };
return updateObjectLiteral(
node,
node.properties.map(
prop => isPropertyAssignment(prop) && isPrivateNamedPropertyAccess(prop.initializer) ?
updatePropertyAssignment(
prop,
prop.name,
wrapPrivateNameForDestructuringTarget(prop.initializer)
) :
prop
)
);
}
}

/**
* 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.ContainsObjectRestOrSpread) {
return flattenDestructuringAssignment(
node,
visitor,
context,
FlattenLevel.ObjectRest,
!noDestructuringValue
);
if (isDestructuringAssignment(node)) {
const left = transformDestructuringAssignmentTarget(node.left);
if (left !== node.left || node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
return flattenDestructuringAssignment(
left === node.left ? node : updateBinary(
node,
left,
node.right,
node.operatorToken.kind
) as DestructuringAssignment,
visitor,
context,
node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread
? FlattenLevel.ObjectRest : FlattenLevel.All,
!noDestructuringValue
);
}
}
else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
return updateBinary(
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,10 @@ namespace ts {
name: Identifier | PrivateName;
}

export interface PrivateNamedPropertyAccess extends PropertyAccessExpression {
name: PrivateName;
}

export interface SuperPropertyAccessExpression extends PropertyAccessExpression {
expression: SuperExpression;
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6076,6 +6076,10 @@ namespace ts {
return isPropertyDeclaration(node) && isPrivateName(node.name);
}

export function isPrivateNamedPropertyAccess(node: Node): node is PrivateNamedPropertyAccess {
return isPropertyAccessExpression(node) && isPrivateName(node.name);
}

export function isBindingName(node: Node): node is BindingName {
const kind = node.kind;
return kind === SyntaxKind.Identifier
Expand Down
37 changes: 37 additions & 0 deletions tests/baselines/reference/privateNameFieldDestructuredBinding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//// [privateNameFieldDestructuredBinding.ts]
class A {
#field = 1;
testObject() {
return { x: 10, y: 6 };
}
testArray() {
return [10, 11];
}
constructor() {
let y: number;
({ x: this.#field, y } = this.testObject());
([this.#field, y] = this.testArray());
}
}


//// [privateNameFieldDestructuredBinding.js]
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 _field;
var A = /** @class */ (function () {
function A() {
_field.set(this, 1);
var _a, _b;
var y;
(_a = this.testObject(), { set value(x) { _classPrivateFieldGet(this, _field) = x; } }.value = _a.x, y = _a.y);
(_b = this.testArray(), { set value(x) { _classPrivateFieldGet(this, _field) = x; } }.value = _b[0], y = _b[1]);
}
A.prototype.testObject = function () {
return { x: 10, y: 6 };
};
A.prototype.testArray = function () {
return [10, 11];
};
return A;
}());
_field = new WeakMap();
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldDestructuredBinding.ts ===
class A {
>A : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))

#field = 1;
>#field : Symbol(A[#field], Decl(privateNameFieldDestructuredBinding.ts, 0, 9))

testObject() {
>testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))

return { x: 10, y: 6 };
>x : Symbol(x, Decl(privateNameFieldDestructuredBinding.ts, 3, 16))
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 3, 23))
}
testArray() {
>testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))

return [10, 11];
}
constructor() {
let y: number;
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 9, 11))

({ x: this.#field, y } = this.testObject());
>x : Symbol(x, Decl(privateNameFieldDestructuredBinding.ts, 10, 10))
>this.#field : Symbol(A[#field], Decl(privateNameFieldDestructuredBinding.ts, 0, 9))
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 10, 26))
>this.testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
>testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))

([this.#field, y] = this.testArray());
>this.#field : Symbol(A[#field], Decl(privateNameFieldDestructuredBinding.ts, 0, 9))
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 9, 11))
>this.testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
>testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldDestructuredBinding.ts ===
class A {
>A : A

#field = 1;
>#field : number
>1 : 1

testObject() {
>testObject : () => { x: number; y: number; }

return { x: 10, y: 6 };
>{ x: 10, y: 6 } : { x: number; y: number; }
>x : number
>10 : 10
>y : number
>6 : 6
}
testArray() {
>testArray : () => number[]

return [10, 11];
>[10, 11] : number[]
>10 : 10
>11 : 11
}
constructor() {
let y: number;
>y : number

({ x: this.#field, y } = this.testObject());
>({ x: this.#field, y } = this.testObject()) : { x: number; y: number; }
>{ x: this.#field, y } = this.testObject() : { x: number; y: number; }
>{ x: this.#field, y } : { x: number; y: number; }
>x : number
>this.#field : number
>this : this
>y : number
>this.testObject() : { x: number; y: number; }
>this.testObject : () => { x: number; y: number; }
>this : this
>testObject : () => { x: number; y: number; }

([this.#field, y] = this.testArray());
>([this.#field, y] = this.testArray()) : number[]
>[this.#field, y] = this.testArray() : number[]
>[this.#field, y] : [number, number]
>this.#field : number
>this : this
>y : number
>this.testArray() : number[]
>this.testArray : () => number[]
>this : this
>testArray : () => number[]
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class A {
#field = 1;
testObject() {
return { x: 10, y: 6 };
}
testArray() {
return [10, 11];
}
constructor() {
let y: number;
({ x: this.#field, y } = this.testObject());
([this.#field, y] = this.testArray());
}
}