Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse using declaration (explicit resource management) #14968

Merged
merged 14 commits into from Oct 26, 2022
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we also unset _noLineTerminator after printing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean unset _noLineTerminator after printing the using keyword? The variable _noLineTerminator here stores the value of this.noLineTerminator before we print using, and then we restore this._noLineTerminator after the first declarator is printed.

}
};
}

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 @@ -144,6 +147,7 @@
"importMeta",
"jsx",
"logicalAssignment",
"moduleBlocks",
"moduleStringNames",
"nullishCoalescingOperator",
"numericSeparator",
Expand All @@ -154,6 +158,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 @@ -263,6 +264,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 @@ -288,6 +291,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 All @@ -351,14 +362,8 @@ export default abstract class StatementParser extends ExpressionParser {
context?: string | null,
topLevel?: boolean | 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 @@ -405,9 +410,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 @@ -417,7 +441,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 @@ -762,16 +786,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 @@ -989,7 +1023,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 @@ -1317,7 +1351,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 @@ -1359,13 +1393,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 @@ -189,4 +189,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 @@ -294,6 +294,7 @@ export const tt: InternalTokenTypes = {
_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());