Skip to content

Commit

Permalink
Parse using declaration (explicit resource management) (#14968)
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed Oct 26, 2022
1 parent dfc4b61 commit 8300960
Show file tree
Hide file tree
Showing 90 changed files with 1,370 additions and 43 deletions.
21 changes: 20 additions & 1 deletion packages/babel-generator/src/generators/statements.ts
@@ -1,4 +1,5 @@
import type Printer from "../printer";
import type { PrintJoinOptions } from "../printer";
import {
isFor,
isForStatement,
Expand Down Expand Up @@ -102,6 +103,7 @@ function ForXStatement(this: Printer, node: t.ForXStatement) {
this.word("await");
this.space();
}
this.printInnerComments(node, false);
this.token("(");
this.print(node.left, node);
this.space();
Expand Down Expand Up @@ -259,7 +261,13 @@ export function VariableDeclaration(
this.space();
}

this.word(node.kind);
const { kind } = node;
this.word(kind);
const { _noLineTerminator } = this;
if (kind === "using") {
// ensure no line break after `using`
this._noLineTerminator = true;
}
this.space();

let hasInits = false;
Expand All @@ -285,13 +293,24 @@ export function VariableDeclaration(
// bar = "foo";
//

let iterator: PrintJoinOptions["iterator"] | undefined;
if (kind === "using") {
// Ensure no line break between `using` and the first declarator
iterator = (_, i: number) => {
if (i === 0) {
this._noLineTerminator = _noLineTerminator;
}
};
}

this.printList(node.declarations, node, {
separator: hasInits
? function (this: Printer) {
this.token(",");
this.newline();
}
: undefined,
iterator,
indent: node.declarations.length > 1 ? true : false,
});

Expand Down
6 changes: 2 additions & 4 deletions packages/babel-generator/src/printer.ts
Expand Up @@ -79,14 +79,12 @@ interface PrintSequenceOptions extends Partial<AddNewlinesOptions> {

interface PrintListOptions {
separator?: (this: Printer) => void;
iterator?: (node: t.Node, index: number) => void;
statement?: boolean;
indent?: boolean;
}

type PrintJoinOptions = PrintListOptions &
PrintSequenceOptions & {
iterator?: (node: t.Node, index: number) => void;
};
export type PrintJoinOptions = PrintListOptions & PrintSequenceOptions;
class Printer {
constructor(format: Format, map: SourceMap) {
this.format = format;
Expand Down
@@ -0,0 +1 @@
/*1*/ for /* 2 */ await /*3*/ ( /*4*/ using /*5*/ fo /*6*/ of /*7*/ of /*8*/) /*9*/;
@@ -0,0 +1,3 @@
{
"plugins": ["explicitResourceManagement"]
}
@@ -0,0 +1 @@
/*1*/for await /* 2 */ /*3*/( /*4*/using /*5*/fo /*6*/ of /*7*/of /*8*/) /*9*/;
@@ -0,0 +1,3 @@
{
using /* 1 */ a = foo(), /* 2 */ b = foo()
}
@@ -0,0 +1,4 @@
{
using /* 1 */a = foo(),
/* 2 */b = foo();
}
@@ -0,0 +1 @@
for(using /* 1 */ a = foo(), /* 2 */ b = foo();;);
@@ -0,0 +1 @@
for (using /* 1 */a = foo(), /* 2 */b = foo();;);
@@ -0,0 +1 @@
for(using /* 1 */ foo of bar());
@@ -0,0 +1 @@
for (using /* 1 */foo of bar());
@@ -0,0 +1,3 @@
{
"plugins": ["explicitResourceManagement"]
}
3 changes: 2 additions & 1 deletion packages/babel-parser/ast/spec.md
Expand Up @@ -572,7 +572,7 @@ A function declaration. Note that unlike in the parent interface `Function`, the
interface VariableDeclaration <: Declaration {
type: "VariableDeclaration";
declarations: [ VariableDeclarator ];
kind: "var" | "let" | "const";
kind: "var" | "let" | "const" | "using";
}
```

Expand Down Expand Up @@ -1469,3 +1469,4 @@ interface PipelineBareConstructorBody <: NodeBase {
type: "PipelineBareAwaitedFunctionBody";
callee: Expression;
}
```
5 changes: 5 additions & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -131,9 +131,12 @@
"decimal",
"decorators",
"decorators-legacy",
"decoratorAutoAccessors",
"destructuringPrivate",
"doExpressions",
"dynamicImport",
"estree",
"explicitResourceManagement",
"exportDefaultFrom",
"exportNamespaceFrom",
"flow",
Expand All @@ -145,6 +148,7 @@
"importReflection",
"jsx",
"logicalAssignment",
"moduleBlocks",
"moduleStringNames",
"nullishCoalescingOperator",
"numericSeparator",
Expand All @@ -155,6 +159,7 @@
"pipelineOperator",
"placeholders",
"privateIn",
"regexpUnicodeSets",
"throwExpressions",
"topLevelAwait",
"typescript",
Expand Down
5 changes: 5 additions & 0 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -90,6 +90,7 @@ export default {
`'${
type === "ForInStatement" ? "for-in" : "for-of"
}' loop variable declaration may not have an initializer.`,
ForInUsing: "For-in loop may not start with 'using' declaration.",

ForOfAsync: "The left-hand side of a for-of loop may not be 'async'.",
ForOfLet: "The left-hand side of a for-of loop may not start with 'let'.",
Expand Down Expand Up @@ -266,6 +267,8 @@ export default {
}`,
UnexpectedTokenUnaryExponentiation:
"Illegal expression. Wrap left hand side or entire exponentiation in parentheses.",
UnexpectedUsingDeclaration:
"Using declaration cannot appear in the top level when source type is `script`.",
UnsupportedBind: "Binding should be performed on object property.",
UnsupportedDecoratorExport:
"A decorated export must export a class declaration.",
Expand All @@ -291,6 +294,8 @@ export default {
UnterminatedRegExp: "Unterminated regular expression.",
UnterminatedString: "Unterminated string constant.",
UnterminatedTemplate: "Unterminated template.",
UsingDeclarationHasBindingPattern:
"Using declaration cannot have destructuring patterns.",
VarRedeclaration: ({ identifierName }: { identifierName: string }) =>
`Identifier '${identifierName}' has already been declared.`,
YieldBindingIdentifier:
Expand Down
80 changes: 59 additions & 21 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -281,7 +281,7 @@ export default abstract class StatementParser extends ExpressionParser {
if (!this.isContextual(tt._let)) {
return false;
}
return this.isLetKeyword(context);
return this.hasFollowingIdentifier(context);
}

/**
Expand All @@ -293,7 +293,7 @@ export default abstract class StatementParser extends ExpressionParser {
* @returns {boolean}
* @memberof StatementParser
*/
isLetKeyword(context?: string | null): boolean {
hasFollowingIdentifier(context?: string | null): boolean {
const next = this.nextTokenStart();
const nextCh = this.codePointAtPos(next);
// For ambiguous cases, determine if a LexicalDeclaration (or only a
Expand Down Expand Up @@ -326,6 +326,17 @@ export default abstract class StatementParser extends ExpressionParser {
return false;
}

startsUsingForOf(): boolean {
const lookahead = this.lookahead();
if (lookahead.type === tt._of && !lookahead.containsEsc) {
// `using of` must start a for-lhs-of statement
return false;
} else {
this.expectPlugin("explicitResourceManagement");
return true;
}
}

// Parse a single statement.
//
// If expecting a statement and finding a slash operator, parse a
Expand Down Expand Up @@ -354,14 +365,8 @@ export default abstract class StatementParser extends ExpressionParser {
topLevel?: boolean | null,
decorators?: N.Decorator[] | null,
): N.Statement {
let starttype = this.state.type;
const starttype = this.state.type;
const node = this.startNode();
let kind;

if (this.isLet(context)) {
starttype = tt._var;
kind = "let";
}

// Most types of statements are recognized by the keyword they
// start with. Many are trivial to parse, some require a bit of
Expand Down Expand Up @@ -414,9 +419,28 @@ export default abstract class StatementParser extends ExpressionParser {
case tt._try:
return this.parseTryStatement(node as Undone<N.TryStatement>);

case tt._using:
// using [no LineTerminator here] BindingList[+Using]
if (this.hasFollowingLineBreak()) {
break;
}
// fall through
case tt._let:
if (this.state.containsEsc || !this.hasFollowingIdentifier(context)) {
break;
}
// fall through
case tt._const:
case tt._var:
kind = kind || this.state.value;
case tt._var: {
const kind = this.state.value;
if (kind === "using") {
this.expectPlugin("explicitResourceManagement");
if (!this.scope.inModule && this.scope.inTopLevel) {
this.raise(Errors.UnexpectedUsingDeclaration, {
at: this.state.startLoc,
});
}
}
if (context && kind !== "var") {
this.raise(Errors.UnexpectedLexicalDeclaration, {
at: this.state.startLoc,
Expand All @@ -426,7 +450,7 @@ export default abstract class StatementParser extends ExpressionParser {
node as Undone<N.VariableDeclaration>,
kind,
);

}
case tt._while:
return this.parseWhileStatement(node as Undone<N.WhileStatement>);
case tt._with:
Expand Down Expand Up @@ -781,16 +805,26 @@ export default abstract class StatementParser extends ExpressionParser {
}

const startsWithLet = this.isContextual(tt._let);
const isLet = startsWithLet && this.isLetKeyword();
if (this.match(tt._var) || this.match(tt._const) || isLet) {
const startsWithUsing =
this.isContextual(tt._using) && !this.hasFollowingLineBreak();
const isLetOrUsing =
(startsWithLet && this.hasFollowingIdentifier()) ||
(startsWithUsing &&
this.hasFollowingIdentifier() &&
this.startsUsingForOf());
if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) {
const initNode = this.startNode<N.VariableDeclaration>();
const kind = isLet ? "let" : this.state.value;
const 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 (
(this.match(tt._in) || this.isContextual(tt._of)) &&
(isForIn || this.isContextual(tt._of)) &&
init.declarations.length === 1
) {
return this.parseForIn(node as Undone<N.ForInOf>, init, awaitAt);
Expand Down Expand Up @@ -1008,7 +1042,7 @@ export default abstract class StatementParser extends ExpressionParser {
parseVarStatement(
this: Parser,
node: Undone<N.VariableDeclaration>,
kind: "var" | "let" | "const",
kind: "var" | "let" | "const" | "using",
allowMissingInitializer: boolean = false,
): N.VariableDeclaration {
this.next();
Expand Down Expand Up @@ -1338,7 +1372,7 @@ export default abstract class StatementParser extends ExpressionParser {
this: Parser,
node: Undone<N.VariableDeclaration>,
isFor: boolean,
kind: "var" | "let" | "const",
kind: "var" | "let" | "const" | "using",
allowMissingInitializer: boolean = false,
): Undone<N.VariableDeclaration> {
const declarations: N.VariableDeclarator[] = (node.declarations = []);
Expand Down Expand Up @@ -1380,13 +1414,17 @@ export default abstract class StatementParser extends ExpressionParser {
parseVarId(
this: Parser,
decl: Undone<N.VariableDeclarator>,
kind: "var" | "let" | "const",
kind: "var" | "let" | "const" | "using",
): void {
decl.id = this.parseBindingAtom();
this.checkLVal(decl.id, {
const id = this.parseBindingAtom();
if (kind === "using" && id.type !== "Identifier") {
this.raise(Errors.UsingDeclarationHasBindingPattern, { at: id });
}
this.checkLVal(id, {
in: { type: "VariableDeclarator" },
binding: kind === "var" ? BIND_VAR : BIND_LEXICAL,
});
decl.id = id;
}

// Parse a function declaration or literal (depending on the
Expand Down
9 changes: 2 additions & 7 deletions packages/babel-parser/src/plugins/placeholders.ts
Expand Up @@ -152,16 +152,11 @@ export default (superClass: typeof Parser) =>
* parser/statement.js *
* ============================================================ */

isLet(context?: string | null): boolean {
if (super.isLet(context)) {
hasFollowingIdentifier(context?: string | null): boolean {
if (super.hasFollowingIdentifier(context)) {
return true;
}

// Replicate the original checks that lead to looking ahead for an
// identifier.
if (!this.isContextual(tt._let)) {
return false;
}
if (context) return false;

// Accept "let %%" as the start of "let %%placeholder%%", as though the
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-parser/src/tokenizer/state.ts
Expand Up @@ -184,4 +184,7 @@ export type LookaheadState = {
curPosition: () => Position;
/* Used only in readToken_mult_modulo */
inType: boolean;
// These boolean properties are not initialized in createLookaheadState()
// instead they will only be set by the tokenizer
containsEsc?: boolean;
};
1 change: 1 addition & 0 deletions packages/babel-parser/src/tokenizer/types.ts
Expand Up @@ -292,6 +292,7 @@ export const tt = {
_sent: createKeywordLike("sent", { startsExpr }),
_set: createKeywordLike("set", { startsExpr }),
_static: createKeywordLike("static", { startsExpr }),
_using: createKeywordLike("using", { startsExpr }),
_yield: createKeywordLike("yield", { startsExpr }),

// Flow and TypeScript Keywordlike
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";
kind: "var" | "let" | "const" | "using";
}

export interface VariableDeclarator extends NodeBase {
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/typings.d.ts
Expand Up @@ -12,6 +12,7 @@ export type Plugin =
| "destructuringPrivate"
| "doExpressions"
| "dynamicImport"
| "explicitResourceManagement"
| "exportDefaultFrom"
| "exportNamespaceFrom" // deprecated
| "flow"
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-parser/src/util/scope.ts
Expand Up @@ -48,6 +48,9 @@ export default class ScopeHandler<IScope extends Scope = Scope> {
this.inModule = inModule;
}

get inTopLevel() {
return (this.currentScope().flags & SCOPE_PROGRAM) > 0;
}
get inFunction() {
return (this.currentVarScopeFlags() & SCOPE_FUNCTION) > 0;
}
Expand Down
@@ -0,0 +1 @@
for (using reader of getReaders());

0 comments on commit 8300960

Please sign in to comment.