Skip to content

Commit

Permalink
Parse await using declarations (#15520)
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed May 17, 2023
1 parent c5f68eb commit 500e298
Show file tree
Hide file tree
Showing 90 changed files with 3,148 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -184,7 +184,7 @@ jobs:
run: make -j prepublish-prepare-dts lint-ci
- name: Ensure cwd does not contain uncommitted changes
run: |
node ./scripts/assert-dir-git-clean.js code-quality
node ./scripts/assert-dir-git-clean.js "prepublish-prepare-dts lint-ci"
test:
name: Test on Node.js # GitHub will add ${{ matrix.node-version }} to this title
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-generator/src/generators/statements.ts
Expand Up @@ -261,7 +261,7 @@ export function VariableDeclaration(
}

const { kind } = node;
this.word(kind, kind === "using");
this.word(kind, kind === "using" || kind === "await using");
this.space();

let hasInits = false;
Expand Down
@@ -0,0 +1,3 @@
async function f() {
/*0*/await/*1*/using/*2*/b/*3*/=/*4*/f()/*5*/;
}
@@ -0,0 +1,3 @@
async function f() {
/*0*/await using /*2*/b /*3*/ = /*4*/f() /*5*/; /*1*/
}
@@ -0,0 +1,3 @@
async function f() {
/*0*/for/*1*/(/*2*/await/*3*/using/*4*/b/*5*/=/*6*/x/*7*/;/*8*/;/*9*/)/*10*/;
}
@@ -0,0 +1,3 @@
async function f() {
/*0*/for /*1*/ /*8*/ /*9*/ ( /*2*/await using /*4*/b /*5*/ = /*6*/x /*3*/ /*7*/;;) /*10*/;
}
@@ -0,0 +1,8 @@
async function f() {
{
/*0*/for/*1*/(/*2*/await/*3*/using/*4*/b/*5*/of/*6*/x/*7*/)/*8*/;
}
{
/*0*/for/*1*/await/*2*/(/*3*/await/*4*/using/*5*/b/*6*/of/*7*/x/*8*/)/*9*/;
}
}
@@ -0,0 +1,8 @@
async function f() {
{
/*0*/for /*1*/ ( /*2*/await using /*4*/b /*3*/ /*5*/ of /*6*/x /*7*/) /*8*/;
}
{
/*0*/for /*1*/ /*2*/ await ( /*3*/await using /*5*/b /*4*/ /*6*/ of /*7*/x /*8*/) /*9*/;
}
}
@@ -0,0 +1,3 @@
{
"plugins": ["explicitResourceManagement"]
}
1 change: 1 addition & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -126,6 +126,7 @@
},
{
"enum": [
"asyncDoExpressions",
"asyncGenerators",
"bigInt",
"classPrivateMethods",
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -33,6 +33,8 @@ export default {
"Can not use 'await' as identifier inside a static block.",
AwaitExpressionFormalParameter:
"'await' is not allowed in async function parameters.",
AwaitUsingNotInAsyncContext:
"'await using' is only allowed within async functions and at the top levels of modules.",
AwaitNotInAsyncContext:
"'await' is only allowed within async functions and at the top levels of modules.",
AwaitNotInAsyncFunction: "'await' is only allowed within async functions.",
Expand Down
108 changes: 76 additions & 32 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -10,7 +10,6 @@ import {
import ExpressionParser from "./expression";
import { Errors } from "../parse-error";
import { isIdentifierChar, isIdentifierStart } from "../util/identifier";
import { lineBreak } from "../util/whitespace";
import * as charCodes from "charcodes";
import {
BIND_CLASS,
Expand Down Expand Up @@ -349,6 +348,19 @@ export default abstract class StatementParser extends ExpressionParser {
}
}

startsAwaitUsing(): boolean {
let next = this.nextTokenInLineStart();
if (this.isUnparsedContextual(next, "using")) {
next = this.nextTokenInLineStartSince(next + 5);
const nextCh = this.codePointAtPos(next);
if (this.chStartsBindingIdentifier(nextCh, next)) {
this.expectPlugin("explicitResourceManagement");
return true;
}
}
return false;
}

// https://tc39.es/ecma262/#prod-ModuleItem
parseModuleItem(this: Parser) {
return this.parseStatementLike(
Expand Down Expand Up @@ -483,6 +495,23 @@ export default abstract class StatementParser extends ExpressionParser {
case tt._try:
return this.parseTryStatement(node as Undone<N.TryStatement>);

case tt._await:
// [+Await] await [no LineTerminator here] using [no LineTerminator here] BindingList[+Using]
if (!this.state.containsEsc && this.startsAwaitUsing()) {
if (!this.isAwaitAllowed()) {
this.raise(Errors.AwaitUsingNotInAsyncContext, { at: node });
} else if (!allowDeclaration) {
this.raise(Errors.UnexpectedLexicalDeclaration, {
at: node,
});
}
this.next(); // eat 'await'
return this.parseVarStatement(
node as Undone<N.VariableDeclaration>,
"await using",
);
}
break;
case tt._using:
// using [no LineTerminator here] BindingList[+Using]
if (
Expand Down Expand Up @@ -913,31 +942,49 @@ export default abstract class StatementParser extends ExpressionParser {
}

const startsWithLet = this.isContextual(tt._let);
const startsWithUsing = this.isContextual(tt._using);
const isLetOrUsing =
(startsWithLet && this.hasFollowingBindingAtom()) ||
(startsWithUsing && this.startsUsingForOf());
if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) {
const initNode = this.startNode<N.VariableDeclaration>();
const kind = this.state.value;
this.next();
this.parseVar(initNode, true, kind);
const init = this.finishNode(initNode, "VariableDeclaration");
{
const startsWithAwaitUsing =
this.isContextual(tt._await) && this.startsAwaitUsing();
const starsWithUsingDeclaration =
startsWithAwaitUsing ||
(this.isContextual(tt._using) && this.startsUsingForOf());
const isLetOrUsing =
(startsWithLet && this.hasFollowingBindingAtom()) ||
starsWithUsingDeclaration;

if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) {
const initNode = this.startNode<N.VariableDeclaration>();
let kind;
if (startsWithAwaitUsing) {
kind = "await using";
if (!this.isAwaitAllowed()) {
this.raise(Errors.AwaitUsingNotInAsyncContext, {
at: this.state.startLoc,
});
}
this.next(); // eat 'await'
} else {
kind = this.state.value;
}
this.next();
this.parseVar(initNode, true, kind);
const init = this.finishNode(initNode, "VariableDeclaration");

const isForIn = this.match(tt._in);
if (isForIn && startsWithUsing) {
this.raise(Errors.ForInUsing, { at: init });
}
if (
(isForIn || this.isContextual(tt._of)) &&
init.declarations.length === 1
) {
return this.parseForIn(node as Undone<N.ForInOf>, init, awaitAt);
}
if (awaitAt !== null) {
this.unexpected(awaitAt);
const isForIn = this.match(tt._in);
if (isForIn && starsWithUsingDeclaration) {
this.raise(Errors.ForInUsing, { at: init });
}
if (
(isForIn || this.isContextual(tt._of)) &&
init.declarations.length === 1
) {
return this.parseForIn(node as Undone<N.ForInOf>, init, awaitAt);
}
if (awaitAt !== null) {
this.unexpected(awaitAt);
}
return this.parseFor(node as Undone<N.ForStatement>, init);
}
return this.parseFor(node as Undone<N.ForStatement>, init);
}

// Check whether the first token is possibly a contextual keyword, so that
Expand Down Expand Up @@ -1159,7 +1206,7 @@ export default abstract class StatementParser extends ExpressionParser {
parseVarStatement(
this: Parser,
node: Undone<N.VariableDeclaration>,
kind: "var" | "let" | "const" | "using",
kind: "var" | "let" | "const" | "using" | "await using",
allowMissingInitializer: boolean = false,
): N.VariableDeclaration {
this.next();
Expand Down Expand Up @@ -1492,7 +1539,7 @@ export default abstract class StatementParser extends ExpressionParser {
this: Parser,
node: Undone<N.VariableDeclaration>,
isFor: boolean,
kind: "var" | "let" | "const" | "using",
kind: "var" | "let" | "const" | "using" | "await using",
allowMissingInitializer: boolean = false,
): Undone<N.VariableDeclaration> {
const declarations: N.VariableDeclarator[] = (node.declarations = []);
Expand Down Expand Up @@ -1534,7 +1581,7 @@ export default abstract class StatementParser extends ExpressionParser {
parseVarId(
this: Parser,
decl: Undone<N.VariableDeclarator>,
kind: "var" | "let" | "const" | "using",
kind: "var" | "let" | "const" | "using" | "await using",
): void {
const id = this.parseBindingAtom();
this.checkLVal(id, {
Expand Down Expand Up @@ -2458,11 +2505,8 @@ export default abstract class StatementParser extends ExpressionParser {

isAsyncFunction(): boolean {
if (!this.isContextual(tt._async)) return false;
const next = this.nextTokenStart();
return (
!lineBreak.test(this.input.slice(this.state.pos, next)) &&
this.isUnparsedContextual(next, "function")
);
const next = this.nextTokenInLineStart();
return this.isUnparsedContextual(next, "function");
}

parseExportDefaultExpression(this: Parser): N.Expression | N.Declaration {
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/types.d.ts
Expand Up @@ -355,7 +355,7 @@ export interface FunctionDeclaration extends OptFunctionDeclaration {
export interface VariableDeclaration extends DeclarationBase, HasDecorators {
type: "VariableDeclaration";
declarations: VariableDeclarator[];
kind: "var" | "let" | "const" | "using";
kind: "var" | "let" | "const" | "using" | "await using";
}

export interface VariableDeclarator extends NodeBase {
Expand Down
@@ -0,0 +1,3 @@
async function f() {
await using in foo;
}
@@ -0,0 +1,55 @@
{
"type": "File",
"start":0,"end":44,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":44}},
"program": {
"type": "Program",
"start":0,"end":44,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":44}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":44,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":44}},
"id": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"f"},
"name": "f"
},
"generator": false,
"async": true,
"params": [],
"body": {
"type": "BlockStatement",
"start":19,"end":44,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":3,"column":1,"index":44}},
"body": [
{
"type": "ExpressionStatement",
"start":23,"end":42,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":21,"index":42}},
"expression": {
"type": "BinaryExpression",
"start":23,"end":41,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":20,"index":41}},
"left": {
"type": "AwaitExpression",
"start":23,"end":34,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":13,"index":34}},
"argument": {
"type": "Identifier",
"start":29,"end":34,"loc":{"start":{"line":2,"column":8,"index":29},"end":{"line":2,"column":13,"index":34},"identifierName":"using"},
"name": "using"
}
},
"operator": "in",
"right": {
"type": "Identifier",
"start":38,"end":41,"loc":{"start":{"line":2,"column":17,"index":38},"end":{"line":2,"column":20,"index":41},"identifierName":"foo"},
"name": "foo"
}
}
}
],
"directives": []
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
async function f() {
await using instanceof foo;
}
@@ -0,0 +1,55 @@
{
"type": "File",
"start":0,"end":52,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":52}},
"program": {
"type": "Program",
"start":0,"end":52,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":52}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":52,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":52}},
"id": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"f"},
"name": "f"
},
"generator": false,
"async": true,
"params": [],
"body": {
"type": "BlockStatement",
"start":19,"end":52,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":3,"column":1,"index":52}},
"body": [
{
"type": "ExpressionStatement",
"start":23,"end":50,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":29,"index":50}},
"expression": {
"type": "BinaryExpression",
"start":23,"end":49,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":28,"index":49}},
"left": {
"type": "AwaitExpression",
"start":23,"end":34,"loc":{"start":{"line":2,"column":2,"index":23},"end":{"line":2,"column":13,"index":34}},
"argument": {
"type": "Identifier",
"start":29,"end":34,"loc":{"start":{"line":2,"column":8,"index":29},"end":{"line":2,"column":13,"index":34},"identifierName":"using"},
"name": "using"
}
},
"operator": "instanceof",
"right": {
"type": "Identifier",
"start":46,"end":49,"loc":{"start":{"line":2,"column":25,"index":46},"end":{"line":2,"column":28,"index":49},"identifierName":"foo"},
"name": "foo"
}
}
}
],
"directives": []
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
async function f() {
await using reader = getReader();
}
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: \"explicitResourceManagement\". (2:2)"
}
@@ -0,0 +1,9 @@
async function f() {
{
await using f, f = foo();
}
{
await using g = foo();
await using g = foo();
}
}

0 comments on commit 500e298

Please sign in to comment.