Skip to content

Commit 05402b8

Browse files
authored
Merge pull request #18027 from Microsoft/fix16924
Switch to arrow for ts class wrapper IIFE
2 parents 4c68b6d + ccd0158 commit 05402b8

19 files changed

+119
-60
lines changed

src/compiler/factory.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2372,6 +2372,24 @@ namespace ts {
23722372
);
23732373
}
23742374

2375+
export function createImmediatelyInvokedArrowFunction(statements: Statement[]): CallExpression;
2376+
export function createImmediatelyInvokedArrowFunction(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression;
2377+
export function createImmediatelyInvokedArrowFunction(statements: Statement[], param?: ParameterDeclaration, paramValue?: Expression) {
2378+
return createCall(
2379+
createArrowFunction(
2380+
/*modifiers*/ undefined,
2381+
/*typeParameters*/ undefined,
2382+
/*parameters*/ param ? [param] : [],
2383+
/*type*/ undefined,
2384+
/*equalsGreaterThanToken*/ undefined,
2385+
createBlock(statements, /*multiLine*/ true)
2386+
),
2387+
/*typeArguments*/ undefined,
2388+
/*argumentsArray*/ paramValue ? [paramValue] : []
2389+
);
2390+
}
2391+
2392+
23752393
export function createComma(left: Expression, right: Expression) {
23762394
return <Expression>createBinary(left, SyntaxKind.CommaToken, right);
23772395
}
@@ -4036,8 +4054,31 @@ namespace ts {
40364054
}
40374055
}
40384056

4057+
/**
4058+
* Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions.
4059+
*
4060+
* A parenthesized expression can be ignored when all of the following are true:
4061+
*
4062+
* - It's `pos` and `end` are not -1
4063+
* - It does not have a custom source map range
4064+
* - It does not have a custom comment range
4065+
* - It does not have synthetic leading or trailing comments
4066+
*
4067+
* If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around
4068+
* the expression to maintain precedence, a new parenthesized expression should be created automatically when
4069+
* the containing expression is created/updated.
4070+
*/
4071+
function isIgnorableParen(node: Expression) {
4072+
return node.kind === SyntaxKind.ParenthesizedExpression
4073+
&& nodeIsSynthesized(node)
4074+
&& nodeIsSynthesized(getSourceMapRange(node))
4075+
&& nodeIsSynthesized(getCommentRange(node))
4076+
&& !some(getSyntheticLeadingComments(node))
4077+
&& !some(getSyntheticTrailingComments(node));
4078+
}
4079+
40394080
export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression {
4040-
if (outerExpression && isOuterExpression(outerExpression, kinds)) {
4081+
if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) {
40414082
return updateOuterExpression(
40424083
outerExpression,
40434084
recreateOuterExpressions(outerExpression.expression, innerExpression)

src/compiler/transformers/es2015.ts

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -339,64 +339,12 @@ namespace ts {
339339
&& !(<ReturnStatement>node).expression;
340340
}
341341

342-
function isClassLikeVariableStatement(node: Node) {
343-
if (!isVariableStatement(node)) return false;
344-
const variable = singleOrUndefined((<VariableStatement>node).declarationList.declarations);
345-
return variable
346-
&& variable.initializer
347-
&& isIdentifier(variable.name)
348-
&& (isClassLike(variable.initializer)
349-
|| (isAssignmentExpression(variable.initializer)
350-
&& isIdentifier(variable.initializer.left)
351-
&& isClassLike(variable.initializer.right)));
352-
}
353-
354-
function isTypeScriptClassWrapper(node: Node) {
355-
const call = tryCast(node, isCallExpression);
356-
if (!call || isParseTreeNode(call) ||
357-
some(call.typeArguments) ||
358-
some(call.arguments)) {
359-
return false;
360-
}
361-
362-
const func = tryCast(skipOuterExpressions(call.expression), isFunctionExpression);
363-
if (!func || isParseTreeNode(func) ||
364-
some(func.typeParameters) ||
365-
some(func.parameters) ||
366-
func.type ||
367-
!func.body) {
368-
return false;
369-
}
370-
371-
const statements = func.body.statements;
372-
if (statements.length < 2) {
373-
return false;
374-
}
375-
376-
const firstStatement = statements[0];
377-
if (isParseTreeNode(firstStatement) ||
378-
!isClassLike(firstStatement) &&
379-
!isClassLikeVariableStatement(firstStatement)) {
380-
return false;
381-
}
382-
383-
const lastStatement = elementAt(statements, -1);
384-
const returnStatement = tryCast(isVariableStatement(lastStatement) ? elementAt(statements, -2) : lastStatement, isReturnStatement);
385-
if (!returnStatement ||
386-
!returnStatement.expression ||
387-
!isIdentifier(skipOuterExpressions(returnStatement.expression))) {
388-
return false;
389-
}
390-
391-
return true;
392-
}
393-
394342
function shouldVisitNode(node: Node): boolean {
395343
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
396344
|| convertedLoopState !== undefined
397345
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && (isStatement(node) || (node.kind === SyntaxKind.Block)))
398346
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node))
399-
|| isTypeScriptClassWrapper(node);
347+
|| (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) !== 0;
400348
}
401349

402350
function visitor(node: Node): VisitResult<Node> {
@@ -3308,13 +3256,14 @@ namespace ts {
33083256
* @param node a CallExpression.
33093257
*/
33103258
function visitCallExpression(node: CallExpression) {
3311-
if (isTypeScriptClassWrapper(node)) {
3259+
if (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) {
33123260
return visitTypeScriptClassWrapper(node);
33133261
}
33143262

33153263
if (node.transformFlags & TransformFlags.ES2015) {
33163264
return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true);
33173265
}
3266+
33183267
return updateCall(
33193268
node,
33203269
visitNode(node.expression, callExpressionVisitor, isExpression),
@@ -3357,7 +3306,7 @@ namespace ts {
33573306

33583307
// We skip any outer expressions in a number of places to get to the innermost
33593308
// expression, but we will restore them later to preserve comments and source maps.
3360-
const body = cast(skipOuterExpressions(node.expression), isFunctionExpression).body;
3309+
const body = cast(cast(skipOuterExpressions(node.expression), isArrowFunction).body, isBlock);
33613310

33623311
// The class statements are the statements generated by visiting the first statement of the
33633312
// body (1), while all other statements are added to remainingStatements (2)

src/compiler/transformers/ts.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,13 +613,16 @@ namespace ts {
613613

614614
addRange(statements, context.endLexicalEnvironment());
615615

616+
const iife = createImmediatelyInvokedArrowFunction(statements);
617+
setEmitFlags(iife, EmitFlags.TypeScriptClassWrapper);
618+
616619
const varStatement = createVariableStatement(
617620
/*modifiers*/ undefined,
618621
createVariableDeclarationList([
619622
createVariableDeclaration(
620623
getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ false),
621624
/*type*/ undefined,
622-
createImmediatelyInvokedFunctionExpression(statements)
625+
iife
623626
)
624627
])
625628
);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4185,6 +4185,7 @@ namespace ts {
41854185
HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
41864186
Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
41874187
NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
4188+
/*@internal*/ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform.
41884189
}
41894190

41904191
export interface EmitHelper {

src/compiler/utilities.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,9 +2068,9 @@ namespace ts {
20682068
|| kind === SyntaxKind.SourceFile;
20692069
}
20702070

2071-
export function nodeIsSynthesized(node: TextRange): boolean {
2072-
return positionIsSynthesized(node.pos)
2073-
|| positionIsSynthesized(node.end);
2071+
export function nodeIsSynthesized(range: TextRange): boolean {
2072+
return positionIsSynthesized(range.pos)
2073+
|| positionIsSynthesized(range.end);
20742074
}
20752075

20762076
export function getOriginalSourceFile(sourceFile: SourceFile) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [asyncArrowInClassES5.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/16924
3+
// Should capture `this`
4+
5+
class Test {
6+
static member = async (x: string) => { };
7+
}
8+
9+
10+
//// [asyncArrowInClassES5.js]
11+
// https://github.com/Microsoft/TypeScript/issues/16924
12+
// Should capture `this`
13+
var _this = this;
14+
var Test = /** @class */ (function () {
15+
function Test() {
16+
}
17+
Test.member = function (x) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
18+
return [2 /*return*/];
19+
}); }); };
20+
return Test;
21+
}());
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/compiler/asyncArrowInClassES5.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/16924
3+
// Should capture `this`
4+
5+
class Test {
6+
>Test : Symbol(Test, Decl(asyncArrowInClassES5.ts, 0, 0))
7+
8+
static member = async (x: string) => { };
9+
>member : Symbol(Test.member, Decl(asyncArrowInClassES5.ts, 3, 12))
10+
>x : Symbol(x, Decl(asyncArrowInClassES5.ts, 4, 27))
11+
}
12+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== tests/cases/compiler/asyncArrowInClassES5.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/16924
3+
// Should capture `this`
4+
5+
class Test {
6+
>Test : Test
7+
8+
static member = async (x: string) => { };
9+
>member : (x: string) => Promise<void>
10+
>async (x: string) => { } : (x: string) => Promise<void>
11+
>x : string
12+
}
13+

tests/baselines/reference/decoratorOnClassMethod11.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
1717
};
1818
var M;
1919
(function (M) {
20+
var _this = this;
2021
var C = /** @class */ (function () {
2122
function C() {
2223
}

tests/baselines/reference/decoratorOnClassMethod12.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
2828
};
2929
var M;
3030
(function (M) {
31+
var _this = this;
3132
var S = /** @class */ (function () {
3233
function S() {
3334
}

tests/baselines/reference/inferringClassMembersFromAssignments.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ var stringOrNumberOrUndefined = C.inStaticNestedArrowFunction;
124124

125125

126126
//// [output.js]
127+
var _this = this;
127128
var C = /** @class */ (function () {
128129
function C() {
129130
var _this = this;

tests/baselines/reference/superAccess2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var __extends = (this && this.__extends) || (function () {
3535
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
3636
};
3737
})();
38+
var _this = this;
3839
var P = /** @class */ (function () {
3940
function P() {
4041
}

tests/baselines/reference/thisInArrowFunctionInStaticInitializer1.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Vector {
99
}
1010

1111
//// [thisInArrowFunctionInStaticInitializer1.js]
12+
var _this = this;
1213
function log(a) { }
1314
var Vector = /** @class */ (function () {
1415
function Vector() {

tests/baselines/reference/thisInConstructorParameter2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class P {
1010
}
1111

1212
//// [thisInConstructorParameter2.js]
13+
var _this = this;
1314
var P = /** @class */ (function () {
1415
function P(z, zz) {
1516
if (z === void 0) { z = this; }

tests/baselines/reference/thisInInvalidContexts.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ var __extends = (this && this.__extends) || (function () {
5959
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
6060
};
6161
})();
62+
var _this = this;
6263
//'this' in static member initializer
6364
var ErrClass1 = /** @class */ (function () {
6465
function ErrClass1() {

tests/baselines/reference/thisInInvalidContextsExternalModule.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ var __extends = (this && this.__extends) || (function () {
6060
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
6161
};
6262
})();
63+
var _this = this;
6364
//'this' in static member initializer
6465
var ErrClass1 = /** @class */ (function () {
6566
function ErrClass1() {

tests/baselines/reference/thisInOuterClassBody.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Foo {
2121
}
2222

2323
//// [thisInOuterClassBody.js]
24+
var _this = this;
2425
var Foo = /** @class */ (function () {
2526
function Foo() {
2627
this.x = this;

tests/baselines/reference/typeOfThisInStaticMembers2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class C2<T> {
88
}
99

1010
//// [typeOfThisInStaticMembers2.js]
11+
var _this = this;
1112
var C = /** @class */ (function () {
1213
function C() {
1314
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @noEmitHelpers: true
2+
// @lib: es2015
3+
// @target: es5
4+
// https://github.com/Microsoft/TypeScript/issues/16924
5+
// Should capture `this`
6+
7+
class Test {
8+
static member = async (x: string) => { };
9+
}

0 commit comments

Comments
 (0)