Skip to content

Commit 55cc028

Browse files
committed
Add es2020 transformation
1 parent a78342a commit 55cc028

12 files changed

+388
-356
lines changed

src/compiler/binder.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3350,7 +3350,7 @@ namespace ts {
33503350
const expression = node.expression;
33513351

33523352
if (node.flags & NodeFlags.OptionalChain) {
3353-
transformFlags |= TransformFlags.ContainsESNext;
3353+
transformFlags |= TransformFlags.ContainsES2020;
33543354
}
33553355

33563356
if (node.typeArguments) {
@@ -3394,7 +3394,7 @@ namespace ts {
33943394
const leftKind = node.left.kind;
33953395

33963396
if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) {
3397-
transformFlags |= TransformFlags.AssertESNext;
3397+
transformFlags |= TransformFlags.AssertES2020;
33983398
}
33993399
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
34003400
// Destructuring object assignments with are ES2015 syntax
@@ -3753,7 +3753,7 @@ namespace ts {
37533753
let transformFlags = subtreeFlags;
37543754

37553755
if (node.flags & NodeFlags.OptionalChain) {
3756-
transformFlags |= TransformFlags.ContainsESNext;
3756+
transformFlags |= TransformFlags.ContainsES2020;
37573757
}
37583758

37593759
// If a PropertyAccessExpression starts with a super keyword, then it is
@@ -3772,7 +3772,7 @@ namespace ts {
37723772
let transformFlags = subtreeFlags;
37733773

37743774
if (node.flags & NodeFlags.OptionalChain) {
3775-
transformFlags |= TransformFlags.ContainsESNext;
3775+
transformFlags |= TransformFlags.ContainsES2020;
37763776
}
37773777

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

src/compiler/transformer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ namespace ts {
5454
transformers.push(transformESNext);
5555
}
5656

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

src/compiler/transformers/es2020.ts

Lines changed: 207 additions & 0 deletions
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)