Skip to content

Commit 0f5ddd2

Browse files
Add es2020 transformation (microsoft#35518)
* Add es2020 transformation * Add missing comma Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 320adf5 commit 0f5ddd2

12 files changed

+389
-357
lines changed

src/compiler/binder.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3361,7 +3361,7 @@ namespace ts {
33613361
const expression = node.expression;
33623362

33633363
if (node.flags & NodeFlags.OptionalChain) {
3364-
transformFlags |= TransformFlags.ContainsESNext;
3364+
transformFlags |= TransformFlags.ContainsES2020;
33653365
}
33663366

33673367
if (node.typeArguments) {
@@ -3405,7 +3405,7 @@ namespace ts {
34053405
const leftKind = node.left.kind;
34063406

34073407
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
3408-
transformFlags |= TransformFlags.AssertESNext;
3408+
transformFlags |= TransformFlags.AssertES2020;
34093409
}
34103410
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
34113411
// Destructuring object assignments with are ES2015 syntax
@@ -3764,7 +3764,7 @@ namespace ts {
37643764
let transformFlags = subtreeFlags;
37653765

37663766
if (node.flags & NodeFlags.OptionalChain) {
3767-
transformFlags |= TransformFlags.ContainsESNext;
3767+
transformFlags |= TransformFlags.ContainsES2020;
37683768
}
37693769

37703770
// If a PropertyAccessExpression starts with a super keyword, then it is
@@ -3783,7 +3783,7 @@ namespace ts {
37833783
let transformFlags = subtreeFlags;
37843784

37853785
if (node.flags & NodeFlags.OptionalChain) {
3786-
transformFlags |= TransformFlags.ContainsESNext;
3786+
transformFlags |= TransformFlags.ContainsES2020;
37873787
}
37883788

37893789
// If an ElementAccessExpression starts with a super keyword, then it is

src/compiler/transformer.ts

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ namespace ts {
5555
transformers.push(transformESNext);
5656
}
5757

58+
if (languageVersion < ScriptTarget.ES2020) {
59+
transformers.push(transformES2020);
60+
}
61+
5862
if (languageVersion < ScriptTarget.ES2019) {
5963
transformers.push(transformES2019);
6064
}

src/compiler/transformers/es2020.ts

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*@internal*/
2+
namespace ts {
3+
export function transformES2020(context: TransformationContext) {
4+
const {
5+
hoistVariableDeclaration,
6+
} = context;
7+
8+
return chainBundle(transformSourceFile);
9+
10+
function transformSourceFile(node: SourceFile) {
11+
if (node.isDeclarationFile) {
12+
return node;
13+
}
14+
15+
return visitEachChild(node, visitor, context);
16+
}
17+
18+
function visitor(node: Node): VisitResult<Node> {
19+
if ((node.transformFlags & TransformFlags.ContainsES2020) === 0) {
20+
return node;
21+
}
22+
switch (node.kind) {
23+
case SyntaxKind.PropertyAccessExpression:
24+
case SyntaxKind.ElementAccessExpression:
25+
case SyntaxKind.CallExpression:
26+
if (node.flags & NodeFlags.OptionalChain) {
27+
const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false, /*isDelete*/ false);
28+
Debug.assertNotNode(updated, isSyntheticReference);
29+
return updated;
30+
}
31+
return visitEachChild(node, visitor, context);
32+
case SyntaxKind.BinaryExpression:
33+
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) {
34+
return transformNullishCoalescingExpression(<BinaryExpression>node);
35+
}
36+
return visitEachChild(node, visitor, context);
37+
case SyntaxKind.DeleteExpression:
38+
return visitDeleteExpression(node as DeleteExpression);
39+
default:
40+
return visitEachChild(node, visitor, context);
41+
}
42+
}
43+
44+
function flattenChain(chain: OptionalChain) {
45+
const links: OptionalChain[] = [chain];
46+
while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) {
47+
chain = cast(chain.expression, isOptionalChain);
48+
links.unshift(chain);
49+
}
50+
return { expression: chain.expression, chain: links };
51+
}
52+
53+
function visitNonOptionalParenthesizedExpression(node: ParenthesizedExpression, captureThisArg: boolean, isDelete: boolean): Expression {
54+
const expression = visitNonOptionalExpression(node.expression, captureThisArg, isDelete);
55+
if (isSyntheticReference(expression)) {
56+
// `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` }
57+
// `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` }
58+
return createSyntheticReferenceExpression(updateParen(node, expression.expression), expression.thisArg);
59+
}
60+
return updateParen(node, expression);
61+
}
62+
63+
function visitNonOptionalPropertyOrElementAccessExpression(node: AccessExpression, captureThisArg: boolean, isDelete: boolean): Expression {
64+
if (isOptionalChain(node)) {
65+
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
66+
return visitOptionalExpression(node, captureThisArg, isDelete);
67+
}
68+
69+
let expression: Expression = visitNode(node.expression, visitor, isExpression);
70+
Debug.assertNotNode(expression, isSyntheticReference);
71+
72+
let thisArg: Expression | undefined;
73+
if (captureThisArg) {
74+
if (shouldCaptureInTempVariable(expression)) {
75+
thisArg = createTempVariable(hoistVariableDeclaration);
76+
expression = createAssignment(thisArg, expression);
77+
}
78+
else {
79+
thisArg = expression;
80+
}
81+
}
82+
83+
expression = node.kind === SyntaxKind.PropertyAccessExpression
84+
? updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier))
85+
: updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression));
86+
return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression;
87+
}
88+
89+
function visitNonOptionalCallExpression(node: CallExpression, captureThisArg: boolean): Expression {
90+
if (isOptionalChain(node)) {
91+
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
92+
return visitOptionalExpression(node, captureThisArg, /*isDelete*/ false);
93+
}
94+
return visitEachChild(node, visitor, context);
95+
}
96+
97+
function visitNonOptionalExpression(node: Expression, captureThisArg: boolean, isDelete: boolean): Expression {
98+
switch (node.kind) {
99+
case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg, isDelete);
100+
case SyntaxKind.PropertyAccessExpression:
101+
case SyntaxKind.ElementAccessExpression: return visitNonOptionalPropertyOrElementAccessExpression(node as AccessExpression, captureThisArg, isDelete);
102+
case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg);
103+
default: return visitNode(node, visitor, isExpression);
104+
}
105+
}
106+
107+
function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean, isDelete: boolean): Expression {
108+
const { expression, chain } = flattenChain(node);
109+
const left = visitNonOptionalExpression(expression, isCallChain(chain[0]), /*isDelete*/ false);
110+
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
111+
let leftExpression = isSyntheticReference(left) ? left.expression : left;
112+
let capturedLeft: Expression = leftExpression;
113+
if (shouldCaptureInTempVariable(leftExpression)) {
114+
capturedLeft = createTempVariable(hoistVariableDeclaration);
115+
leftExpression = createAssignment(capturedLeft, leftExpression);
116+
}
117+
let rightExpression = capturedLeft;
118+
let thisArg: Expression | undefined;
119+
for (let i = 0; i < chain.length; i++) {
120+
const segment = chain[i];
121+
switch (segment.kind) {
122+
case SyntaxKind.PropertyAccessExpression:
123+
case SyntaxKind.ElementAccessExpression:
124+
if (i === chain.length - 1 && captureThisArg) {
125+
if (shouldCaptureInTempVariable(rightExpression)) {
126+
thisArg = createTempVariable(hoistVariableDeclaration);
127+
rightExpression = createAssignment(thisArg, rightExpression);
128+
}
129+
else {
130+
thisArg = rightExpression;
131+
}
132+
}
133+
rightExpression = segment.kind === SyntaxKind.PropertyAccessExpression
134+
? createPropertyAccess(rightExpression, visitNode(segment.name, visitor, isIdentifier))
135+
: createElementAccess(rightExpression, visitNode(segment.argumentExpression, visitor, isExpression));
136+
break;
137+
case SyntaxKind.CallExpression:
138+
if (i === 0 && leftThisArg) {
139+
rightExpression = createFunctionCall(
140+
rightExpression,
141+
leftThisArg.kind === SyntaxKind.SuperKeyword ? createThis() : leftThisArg,
142+
visitNodes(segment.arguments, visitor, isExpression)
143+
);
144+
}
145+
else {
146+
rightExpression = createCall(
147+
rightExpression,
148+
/*typeArguments*/ undefined,
149+
visitNodes(segment.arguments, visitor, isExpression)
150+
);
151+
}
152+
break;
153+
}
154+
setOriginalNode(rightExpression, segment);
155+
}
156+
157+
const target = isDelete
158+
? createConditional(createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true), createTrue(), createDelete(rightExpression))
159+
: createConditional(createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true), createVoidZero(), rightExpression);
160+
return thisArg ? createSyntheticReferenceExpression(target, thisArg) : target;
161+
}
162+
163+
function createNotNullCondition(left: Expression, right: Expression, invert?: boolean) {
164+
return createBinary(
165+
createBinary(
166+
left,
167+
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
168+
createNull()
169+
),
170+
createToken(invert ? SyntaxKind.BarBarToken : SyntaxKind.AmpersandAmpersandToken),
171+
createBinary(
172+
right,
173+
createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken),
174+
createVoidZero()
175+
)
176+
);
177+
}
178+
179+
function transformNullishCoalescingExpression(node: BinaryExpression) {
180+
let left = visitNode(node.left, visitor, isExpression);
181+
let right = left;
182+
if (shouldCaptureInTempVariable(left)) {
183+
right = createTempVariable(hoistVariableDeclaration);
184+
left = createAssignment(right, left);
185+
}
186+
return createConditional(
187+
createNotNullCondition(left, right),
188+
right,
189+
visitNode(node.right, visitor, isExpression),
190+
);
191+
}
192+
193+
function shouldCaptureInTempVariable(expression: Expression): boolean {
194+
// don't capture identifiers and `this` in a temporary variable
195+
// `super` cannot be captured as it's no real variable
196+
return !isIdentifier(expression) &&
197+
expression.kind !== SyntaxKind.ThisKeyword &&
198+
expression.kind !== SyntaxKind.SuperKeyword;
199+
}
200+
201+
function visitDeleteExpression(node: DeleteExpression) {
202+
return isOptionalChain(skipParentheses(node.expression))
203+
? setOriginalNode(visitNonOptionalExpression(node.expression, /*captureThisArg*/ false, /*isDelete*/ true), node)
204+
: updateDelete(node, visitNode(node.expression, visitor, isExpression));
205+
}
206+
}
207+
}

0 commit comments

Comments
 (0)