Skip to content

Commit 0945301

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

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
@@ -828,20 +828,105 @@ namespace ts {
828828
return visitEachChild(node, visitor, context);
829829
}
830830

831+
function wrapPrivateNameForDestructuringTarget(node: PrivateNamedPropertyAccessExpression) {
832+
return createPropertyAccess(
833+
createObjectLiteral([
834+
createSetAccessor(
835+
/*decorators*/ undefined,
836+
/*modifiers*/ undefined,
837+
"value",
838+
[createParameter(
839+
/*decorators*/ undefined,
840+
/*modifiers*/ undefined,
841+
/*dotDotDotToken*/ undefined, "x",
842+
/*questionToken*/ undefined,
843+
/*type*/ undefined,
844+
/*initializer*/ undefined
845+
)],
846+
createBlock(
847+
[createExpressionStatement(
848+
createAssignment(
849+
visitNode(node, visitor),
850+
createIdentifier("x")
851+
)
852+
)]
853+
)
854+
)
855+
]),
856+
"value"
857+
);
858+
}
859+
860+
function transformDestructuringAssignmentTarget(node: ArrayLiteralExpression | ObjectLiteralExpression) {
861+
const hasPrivateNames = isArrayLiteralExpression(node) ?
862+
forEach(node.elements, isPrivateNamedPropertyAccessExpression) :
863+
forEach(node.properties, property => isPropertyAssignment(property) && isPrivateNamedPropertyAccessExpression(property.initializer));
864+
if (!hasPrivateNames) {
865+
return node;
866+
}
867+
if (isArrayLiteralExpression(node)) {
868+
// Transforms private names in destructuring assignment array bindings.
869+
//
870+
// Source:
871+
// ([ this.#myProp ] = [ "hello" ]);
872+
//
873+
// Transformation:
874+
// [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ];
875+
return updateArrayLiteral(
876+
node,
877+
node.elements.map(
878+
expr => isPrivateNamedPropertyAccessExpression(expr) ?
879+
wrapPrivateNameForDestructuringTarget(expr) :
880+
expr
881+
)
882+
);
883+
}
884+
else {
885+
// Transforms private names in destructuring assignment object bindings.
886+
//
887+
// Source:
888+
// ({ stringProperty: this.#myProp } = { stringProperty: "hello" });
889+
//
890+
// Transformation:
891+
// ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" };
892+
return updateObjectLiteral(
893+
node,
894+
node.properties.map(
895+
prop => isPropertyAssignment(prop) && isPrivateNamedPropertyAccessExpression(prop.initializer) ?
896+
updatePropertyAssignment(
897+
prop,
898+
prop.name,
899+
wrapPrivateNameForDestructuringTarget(prop.initializer)
900+
) :
901+
prop
902+
)
903+
);
904+
}
905+
}
906+
831907
/**
832908
* Visits a BinaryExpression that contains a destructuring assignment.
833909
*
834910
* @param node A BinaryExpression node.
835911
*/
836912
function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression {
837-
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
838-
return flattenDestructuringAssignment(
839-
node,
840-
visitor,
841-
context,
842-
FlattenLevel.ObjectRest,
843-
!noDestructuringValue
844-
);
913+
if (isDestructuringAssignment(node)) {
914+
const left = transformDestructuringAssignmentTarget(node.left);
915+
if (left !== node.left || node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
916+
return flattenDestructuringAssignment(
917+
left === node.left ? node : updateBinary(
918+
node,
919+
left,
920+
node.right,
921+
node.operatorToken.kind
922+
) as DestructuringAssignment,
923+
visitor,
924+
context,
925+
node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread
926+
? FlattenLevel.ObjectRest : FlattenLevel.All,
927+
!noDestructuringValue
928+
);
929+
}
845930
}
846931
else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
847932
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)