Skip to content

Commit f9cf821

Browse files
authored
Support using and await using declarations (#54505)
1 parent d90eb0d commit f9cf821

File tree

674 files changed

+29610
-2826
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

674 files changed

+29610
-2826
lines changed

src/compiler/_namespaces/ts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export * from "../visitorPublic";
3333
export * from "../sourcemap";
3434
export * from "../transformers/utilities";
3535
export * from "../transformers/destructuring";
36+
export * from "../transformers/classThis";
37+
export * from "../transformers/namedEvaluation";
3638
export * from "../transformers/taggedTemplate";
3739
export * from "../transformers/ts";
3840
export * from "../transformers/classFields";

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3727,7 +3727,7 @@ function isExecutableStatement(s: Statement): boolean {
37273727
// Don't remove statements that can validly be used before they appear.
37283728
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) &&
37293729
// `var x;` may declare a variable used above
3730-
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer));
3730+
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer));
37313731
}
37323732

37333733
function isPurelyTypeDeclaration(s: Statement): boolean {

src/compiler/checker.ts

Lines changed: 200 additions & 71 deletions
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ const libEntries: [string, string][] = [
218218
["esnext.symbol", "lib.es2019.symbol.d.ts"],
219219
["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"],
220220
["esnext.intl", "lib.esnext.intl.d.ts"],
221+
["esnext.disposable", "lib.esnext.disposable.d.ts"],
221222
["esnext.bigint", "lib.es2020.bigint.d.ts"],
222223
["esnext.string", "lib.es2022.string.d.ts"],
223224
["esnext.promise", "lib.es2021.promise.d.ts"],

src/compiler/diagnosticMessages.json

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -451,18 +451,14 @@
451451
"category": "Error",
452452
"code": 1149
453453
},
454-
"'const' declarations must be initialized.": {
454+
"'{0}' declarations must be initialized.": {
455455
"category": "Error",
456456
"code": 1155
457457
},
458-
"'const' declarations can only be declared inside a block.": {
458+
"'{0}' declarations can only be declared inside a block.": {
459459
"category": "Error",
460460
"code": 1156
461461
},
462-
"'let' declarations can only be declared inside a block.": {
463-
"category": "Error",
464-
"code": 1157
465-
},
466462
"Unterminated template literal.": {
467463
"category": "Error",
468464
"code": 1160
@@ -1601,6 +1597,26 @@
16011597
"category": "Error",
16021598
"code": 1490
16031599
},
1600+
"'{0}' modifier cannot appear on a 'using' declaration.": {
1601+
"category": "Error",
1602+
"code": 1491
1603+
},
1604+
"'{0}' declarations may not have binding patterns.": {
1605+
"category": "Error",
1606+
"code": 1492
1607+
},
1608+
"The left-hand side of a 'for...in' statement cannot be a 'using' declaration.": {
1609+
"category": "Error",
1610+
"code": 1493
1611+
},
1612+
"The left-hand side of a 'for...in' statement cannot be an 'await using' declaration.": {
1613+
"category": "Error",
1614+
"code": 1494
1615+
},
1616+
"'{0}' modifier cannot appear on an 'await using' declaration.": {
1617+
"category": "Error",
1618+
"code": 1495
1619+
},
16041620

16051621
"The types of '{0}' are incompatible between these types.": {
16061622
"category": "Error",
@@ -3623,6 +3639,26 @@
36233639
"category": "Error",
36243640
"code": 2849
36253641
},
3642+
"The initializer of a 'using' declaration must be either an object with a '[Symbol.dispose]()' method, or be 'null' or 'undefined'.": {
3643+
"category": "Error",
3644+
"code": 2850
3645+
},
3646+
"The initializer of an 'await using' declaration must be either an object with a '[Symbol.asyncDispose]()' or '[Symbol.dispose]()' method, or be 'null' or 'undefined'.": {
3647+
"category": "Error",
3648+
"code": 2851
3649+
},
3650+
"'await using' statements are only allowed within async functions and at the top levels of modules.": {
3651+
"category": "Error",
3652+
"code": 2852
3653+
},
3654+
"'await using' statements are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.": {
3655+
"category": "Error",
3656+
"code": 2853
3657+
},
3658+
"Top-level 'await using' statements are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
3659+
"category": "Error",
3660+
"code": 2854
3661+
},
36263662

36273663
"Import declaration '{0}' is using private name '{1}'.": {
36283664
"category": "Error",
@@ -7757,11 +7793,11 @@
77577793
"category": "Error",
77587794
"code": 18036
77597795
},
7760-
"Await expression cannot be used inside a class static block.": {
7796+
"'await' expression cannot be used inside a class static block.": {
77617797
"category": "Error",
77627798
"code": 18037
77637799
},
7764-
"'For await' loops cannot be used inside a class static block.": {
7800+
"'for await' loops cannot be used inside a class static block.": {
77657801
"category": "Error",
77667802
"code": 18038
77677803
},
@@ -7820,5 +7856,9 @@
78207856
"Its type '{0}' is not a valid JSX element type.": {
78217857
"category": "Error",
78227858
"code": 18053
7859+
},
7860+
"'await using' statements cannot be used inside a class static block.": {
7861+
"category": "Error",
7862+
"code": 18054
78237863
}
78247864
}

src/compiler/emitter.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ import {
254254
isUnparsedNode,
255255
isUnparsedPrepend,
256256
isUnparsedSource,
257+
isVarAwaitUsing,
257258
isVarConst,
258259
isVariableStatement,
260+
isVarUsing,
259261
JSDoc,
260262
JSDocAugmentsTag,
261263
JSDocCallbackTag,
@@ -3685,7 +3687,18 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
36853687
}
36863688

36873689
function emitVariableDeclarationList(node: VariableDeclarationList) {
3688-
writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var");
3690+
if (isVarAwaitUsing(node)) {
3691+
writeKeyword("await");
3692+
writeSpace();
3693+
writeKeyword("using");
3694+
}
3695+
else {
3696+
const head = isLet(node) ? "let" :
3697+
isVarConst(node) ? "const" :
3698+
isVarUsing(node) ? "using" :
3699+
"var";
3700+
writeKeyword(head);
3701+
}
36893702
writeSpace();
36903703
emitList(node, node.declarations, ListFormat.VariableDeclarationList);
36913704
}

src/compiler/factory/emitHelpers.ts

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ export interface EmitHelperFactory {
136136
createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
137137
createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
138138
createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression;
139+
// 'using' helpers
140+
createAddDisposableResourceHelper(envBinding: Expression, value: Expression, async: boolean): Expression;
141+
createDisposeResourcesHelper(envBinding: Expression): Expression;
139142
}
140143

141144
/** @internal */
@@ -183,7 +186,10 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
183186
// Class Fields Helpers
184187
createClassPrivateFieldGetHelper,
185188
createClassPrivateFieldSetHelper,
186-
createClassPrivateFieldInHelper
189+
createClassPrivateFieldInHelper,
190+
// 'using' helpers
191+
createAddDisposableResourceHelper,
192+
createDisposeResourcesHelper,
187193
};
188194

189195
/**
@@ -666,6 +672,20 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
666672
context.requestEmitHelper(classPrivateFieldInHelper);
667673
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /*typeArguments*/ undefined, [state, receiver]);
668674
}
675+
676+
function createAddDisposableResourceHelper(envBinding: Expression, value: Expression, async: boolean): Expression {
677+
context.requestEmitHelper(addDisposableResourceHelper);
678+
return factory.createCallExpression(
679+
getUnscopedHelperName("__addDisposableResource"),
680+
/*typeArguments*/ undefined,
681+
[envBinding, value, async ? factory.createTrue() : factory.createFalse()]
682+
);
683+
}
684+
685+
function createDisposeResourcesHelper(envBinding: Expression) {
686+
context.requestEmitHelper(disposeResourcesHelper);
687+
return factory.createCallExpression(getUnscopedHelperName("__disposeResources"), /*typeArguments*/ undefined, [envBinding]);
688+
}
669689
}
670690

671691
/** @internal */
@@ -1367,6 +1387,71 @@ export const classPrivateFieldInHelper: UnscopedEmitHelper = {
13671387
};`
13681388
};
13691389

1390+
/**
1391+
* @internal
1392+
*/
1393+
export const addDisposableResourceHelper: UnscopedEmitHelper = {
1394+
name: "typescript:addDisposableResource",
1395+
importName: "__addDisposableResource",
1396+
scoped: false,
1397+
text: `
1398+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
1399+
if (value !== null && value !== void 0) {
1400+
if (typeof value !== "object") throw new TypeError("Object expected.");
1401+
var dispose;
1402+
if (async) {
1403+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
1404+
dispose = value[Symbol.asyncDispose];
1405+
}
1406+
if (dispose === void 0) {
1407+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
1408+
dispose = value[Symbol.dispose];
1409+
}
1410+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
1411+
env.stack.push({ value: value, dispose: dispose, async: async });
1412+
}
1413+
else if (async) {
1414+
env.stack.push({ async: true });
1415+
}
1416+
return value;
1417+
};`
1418+
};
1419+
1420+
/**
1421+
* @internal
1422+
*/
1423+
export const disposeResourcesHelper: UnscopedEmitHelper = {
1424+
name: "typescript:disposeResources",
1425+
importName: "__disposeResources",
1426+
scoped: false,
1427+
text: `
1428+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
1429+
return function (env) {
1430+
function fail(e) {
1431+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
1432+
env.hasError = true;
1433+
}
1434+
function next() {
1435+
while (env.stack.length) {
1436+
var rec = env.stack.pop();
1437+
try {
1438+
var result = rec.dispose && rec.dispose.call(rec.value);
1439+
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
1440+
}
1441+
catch (e) {
1442+
fail(e);
1443+
}
1444+
}
1445+
if (env.hasError) throw env.error;
1446+
}
1447+
return next();
1448+
};
1449+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1450+
var e = new Error(message);
1451+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1452+
});`
1453+
};
1454+
13701455
let allUnscopedEmitHelpers: ReadonlyMap<string, UnscopedEmitHelper> | undefined;
13711456

13721457
/** @internal */
@@ -1399,7 +1484,9 @@ export function getAllUnscopedEmitHelpers() {
13991484
classPrivateFieldSetHelper,
14001485
classPrivateFieldInHelper,
14011486
createBindingHelper,
1402-
setModuleDefaultHelper
1487+
setModuleDefaultHelper,
1488+
addDisposableResourceHelper,
1489+
disposeResourcesHelper,
14031490
], helper => helper.name));
14041491
}
14051492

src/compiler/factory/nodeConverters.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
BindingOrAssignmentPattern,
77
Block,
88
cast,
9+
ClassDeclaration,
910
ConciseBody,
1011
Debug,
1112
Expression,
@@ -17,6 +18,8 @@ import {
1718
isBindingElement,
1819
isBindingPattern,
1920
isBlock,
21+
isDefaultModifier,
22+
isExportModifier,
2023
isExpression,
2124
isIdentifier,
2225
isObjectBindingPattern,
@@ -39,6 +42,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
3942
return {
4043
convertToFunctionBlock,
4144
convertToFunctionExpression,
45+
convertToClassExpression,
4246
convertToArrayAssignmentElement,
4347
convertToObjectAssignmentElement,
4448
convertToAssignmentPattern,
@@ -59,7 +63,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
5963
function convertToFunctionExpression(node: FunctionDeclaration) {
6064
if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`);
6165
const updated = factory.createFunctionExpression(
62-
getModifiers(node),
66+
getModifiers(node)?.filter(modifier => !isExportModifier(modifier) && !isDefaultModifier(modifier)),
6367
node.asteriskToken,
6468
node.name,
6569
node.typeParameters,
@@ -75,6 +79,22 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
7579
return updated;
7680
}
7781

82+
function convertToClassExpression(node: ClassDeclaration) {
83+
const updated = factory.createClassExpression(
84+
node.modifiers?.filter(modifier => !isExportModifier(modifier) && !isDefaultModifier(modifier)),
85+
node.name,
86+
node.typeParameters,
87+
node.heritageClauses,
88+
node.members
89+
);
90+
setOriginalNode(updated, node);
91+
setTextRange(updated, node);
92+
if (getStartsOnNewLine(node)) {
93+
setStartsOnNewLine(updated, /*newLine*/ true);
94+
}
95+
return updated;
96+
}
97+
7898
function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) {
7999
if (isBindingElement(element)) {
80100
if (element.dotDotDotToken) {
@@ -163,6 +183,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
163183
export const nullNodeConverters: NodeConverters = {
164184
convertToFunctionBlock: notImplemented,
165185
convertToFunctionExpression: notImplemented,
186+
convertToClassExpression: notImplemented,
166187
convertToArrayAssignmentElement: notImplemented,
167188
convertToObjectAssignmentElement: notImplemented,
168189
convertToAssignmentPattern: notImplemented,

0 commit comments

Comments
 (0)