Skip to content

Commit 7bf2171

Browse files
author
Joseph Watts
committed
Transform private name bindings in destructuring assignments
Signed-off-by: Joseph Watts <[email protected]>
1 parent 229f3ae commit 7bf2171

File tree

5 files changed

+243
-8
lines changed

5 files changed

+243
-8
lines changed

src/compiler/transformers/esnext.ts

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -835,20 +835,105 @@ namespace ts {
835835
return visitEachChild(node, visitor, context);
836836
}
837837

838+
function wrapPrivateNameForDestructuringTarget(node: PrivateNamedPropertyAccessExpression) {
839+
return createPropertyAccess(
840+
createObjectLiteral([
841+
createSetAccessor(
842+
/*decorators*/ undefined,
843+
/*modifiers*/ undefined,
844+
"value",
845+
[createParameter(
846+
/*decorators*/ undefined,
847+
/*modifiers*/ undefined,
848+
/*dotDotDotToken*/ undefined, "x",
849+
/*questionToken*/ undefined,
850+
/*type*/ undefined,
851+
/*initializer*/ undefined
852+
)],
853+
createBlock(
854+
[createExpressionStatement(
855+
createAssignment(
856+
visitNode(node, visitor),
857+
createIdentifier("x")
858+
)
859+
)]
860+
)
861+
)
862+
]),
863+
"value"
864+
);
865+
}
866+
867+
function transformDestructuringAssignmentTarget(node: ArrayLiteralExpression | ObjectLiteralExpression) {
868+
const hasPrivateNames = isArrayLiteralExpression(node) ?
869+
forEach(node.elements, isPrivateNamedPropertyAccessExpression) :
870+
forEach(node.properties, property => isPropertyAssignment(property) && isPrivateNamedPropertyAccessExpression(property.initializer));
871+
if (!hasPrivateNames) {
872+
return node;
873+
}
874+
if (isArrayLiteralExpression(node)) {
875+
// Transforms private names in destructuring assignment array bindings.
876+
//
877+
// Source:
878+
// ([ this.#myProp ] = [ "hello" ]);
879+
//
880+
// Transformation:
881+
// [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ];
882+
return updateArrayLiteral(
883+
node,
884+
node.elements.map(
885+
expr => isPrivateNamedPropertyAccessExpression(expr) ?
886+
wrapPrivateNameForDestructuringTarget(expr) :
887+
expr
888+
)
889+
);
890+
}
891+
else {
892+
// Transforms private names in destructuring assignment object bindings.
893+
//
894+
// Source:
895+
// ({ stringProperty: this.#myProp } = { stringProperty: "hello" });
896+
//
897+
// Transformation:
898+
// ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" };
899+
return updateObjectLiteral(
900+
node,
901+
node.properties.map(
902+
prop => isPropertyAssignment(prop) && isPrivateNamedPropertyAccessExpression(prop.initializer) ?
903+
updatePropertyAssignment(
904+
prop,
905+
prop.name,
906+
wrapPrivateNameForDestructuringTarget(prop.initializer)
907+
) :
908+
prop
909+
)
910+
);
911+
}
912+
}
913+
838914
/**
839915
* Visits a BinaryExpression that contains a destructuring assignment.
840916
*
841917
* @param node A BinaryExpression node.
842918
*/
843919
function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression {
844-
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
845-
return flattenDestructuringAssignment(
846-
node,
847-
visitor,
848-
context,
849-
FlattenLevel.ObjectRest,
850-
!noDestructuringValue
851-
);
920+
if (isDestructuringAssignment(node)) {
921+
const left = transformDestructuringAssignmentTarget(node.left);
922+
if (left !== node.left || node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
923+
return flattenDestructuringAssignment(
924+
left === node.left ? node : updateBinary(
925+
node,
926+
left,
927+
node.right,
928+
node.operatorToken.kind
929+
) as DestructuringAssignment,
930+
visitor,
931+
context,
932+
node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread
933+
? FlattenLevel.ObjectRest : FlattenLevel.All,
934+
!noDestructuringValue
935+
);
936+
}
852937
}
853938
else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
854939
return updateBinary(
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [privateNameFieldDestructuredBinding.ts]
2+
class A {
3+
#field = 1;
4+
testObject() {
5+
return { x: 10, y: 6 };
6+
}
7+
testArray() {
8+
return [10, 11];
9+
}
10+
constructor() {
11+
let y: number;
12+
({ x: this.#field, y } = this.testObject());
13+
([this.#field, y] = this.testArray());
14+
}
15+
}
16+
17+
18+
//// [privateNameFieldDestructuredBinding.js]
19+
var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };
20+
var _field;
21+
var A = /** @class */ (function () {
22+
function A() {
23+
_field.set(this, 1);
24+
var _a, _b;
25+
var y;
26+
(_a = this.testObject(), { set value(x) { _classPrivateFieldGet(this, _field) = x; } }.value = _a.x, y = _a.y);
27+
(_b = this.testArray(), { set value(x) { _classPrivateFieldGet(this, _field) = x; } }.value = _b[0], y = _b[1]);
28+
}
29+
A.prototype.testObject = function () {
30+
return { x: 10, y: 6 };
31+
};
32+
A.prototype.testArray = function () {
33+
return [10, 11];
34+
};
35+
return A;
36+
}());
37+
_field = new WeakMap();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldDestructuredBinding.ts ===
2+
class A {
3+
>A : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
4+
5+
#field = 1;
6+
>#field : Symbol(A.#field, Decl(privateNameFieldDestructuredBinding.ts, 0, 9))
7+
8+
testObject() {
9+
>testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))
10+
11+
return { x: 10, y: 6 };
12+
>x : Symbol(x, Decl(privateNameFieldDestructuredBinding.ts, 3, 16))
13+
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 3, 23))
14+
}
15+
testArray() {
16+
>testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))
17+
18+
return [10, 11];
19+
}
20+
constructor() {
21+
let y: number;
22+
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 9, 11))
23+
24+
({ x: this.#field, y } = this.testObject());
25+
>x : Symbol(x, Decl(privateNameFieldDestructuredBinding.ts, 10, 10))
26+
>this.#field : Symbol(A.#field, Decl(privateNameFieldDestructuredBinding.ts, 0, 9))
27+
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
28+
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 10, 26))
29+
>this.testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))
30+
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
31+
>testObject : Symbol(A.testObject, Decl(privateNameFieldDestructuredBinding.ts, 1, 15))
32+
33+
([this.#field, y] = this.testArray());
34+
>this.#field : Symbol(A.#field, Decl(privateNameFieldDestructuredBinding.ts, 0, 9))
35+
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
36+
>y : Symbol(y, Decl(privateNameFieldDestructuredBinding.ts, 9, 11))
37+
>this.testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))
38+
>this : Symbol(A, Decl(privateNameFieldDestructuredBinding.ts, 0, 0))
39+
>testArray : Symbol(A.testArray, Decl(privateNameFieldDestructuredBinding.ts, 4, 5))
40+
}
41+
}
42+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldDestructuredBinding.ts ===
2+
class A {
3+
>A : A
4+
5+
#field = 1;
6+
>#field : number
7+
>1 : 1
8+
9+
testObject() {
10+
>testObject : () => { x: number; y: number; }
11+
12+
return { x: 10, y: 6 };
13+
>{ x: 10, y: 6 } : { x: number; y: number; }
14+
>x : number
15+
>10 : 10
16+
>y : number
17+
>6 : 6
18+
}
19+
testArray() {
20+
>testArray : () => number[]
21+
22+
return [10, 11];
23+
>[10, 11] : number[]
24+
>10 : 10
25+
>11 : 11
26+
}
27+
constructor() {
28+
let y: number;
29+
>y : number
30+
31+
({ x: this.#field, y } = this.testObject());
32+
>({ x: this.#field, y } = this.testObject()) : { x: number; y: number; }
33+
>{ x: this.#field, y } = this.testObject() : { x: number; y: number; }
34+
>{ x: this.#field, y } : { x: number; y: number; }
35+
>x : number
36+
>this.#field : number
37+
>this : this
38+
>y : number
39+
>this.testObject() : { x: number; y: number; }
40+
>this.testObject : () => { x: number; y: number; }
41+
>this : this
42+
>testObject : () => { x: number; y: number; }
43+
44+
([this.#field, y] = this.testArray());
45+
>([this.#field, y] = this.testArray()) : number[]
46+
>[this.#field, y] = this.testArray() : number[]
47+
>[this.#field, y] : [number, number]
48+
>this.#field : number
49+
>this : this
50+
>y : number
51+
>this.testArray() : number[]
52+
>this.testArray : () => number[]
53+
>this : this
54+
>testArray : () => number[]
55+
}
56+
}
57+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class A {
2+
#field = 1;
3+
testObject() {
4+
return { x: 10, y: 6 };
5+
}
6+
testArray() {
7+
return [10, 11];
8+
}
9+
constructor() {
10+
let y: number;
11+
({ x: this.#field, y } = this.testObject());
12+
([this.#field, y] = this.testArray());
13+
}
14+
}

0 commit comments

Comments
 (0)