/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-parser/src/parser/statement.ts
3 115 строк
92 KB
liuxingbaoyu
fix: Define child node types correctly (#18159)
07 авг 2026, 12:30
Не верифицирован
07 авг 2026, 12:30
d8b1262
Код
Авторство
О чём код?
import type * as N from "../types.ts"; import { tokenIsIdentifier, tokenIsKeywordOrIdentifier, tokenIsLoop, tt, type TokenType, getExportedToken, } from "../tokenizer/types.ts"; import ExpressionParser from "./expression.ts"; import { Errors } from "../parse-error.ts"; import { isIdentifierChar, isIdentifierStart } from "../util/identifier.ts"; import * as charCodes from "charcodes"; import { ScopeFlag, ClassElementType, BindingFlag, } from "../util/scopeflags.ts"; import { ExpressionErrors } from "./util.ts"; import { ParamKind, functionFlags } from "../util/production-parameter.ts"; import { newExpressionScope, newParameterDeclarationScope, } from "../util/expression-scope.ts"; import { OptionFlags } from "../options.ts"; import type { Token } from "../tokenizer/index.ts"; import type { Position } from "../util/location.ts"; import { createPositionWithColumnOffset } from "../util/location.ts"; import type { Undone } from "./node.ts"; import type Parser from "./index.ts"; import { ParseBindingListFlags } from "./lval.ts"; import { LoopLabelKind } from "../tokenizer/state.ts"; import type { ExportedToken } from "../types.ts"; const loopLabel = { kind: LoopLabelKind.Loop } as const, switchLabel = { kind: LoopLabelKind.Switch } as const; export const enum ParseFunctionFlag { Expression = 0b0000, Declaration = 0b0001, HangingDeclaration = 0b0010, NullableId = 0b0100, Async = 0b1000, } export const enum ParseStatementFlag { StatementOnly = 0b0000, AllowImportExport = 0b0001, AllowDeclaration = 0b0010, AllowFunctionDeclaration = 0b0100, AllowLabeledFunction = 0b1000, } const loneSurrogate = /[\uD800-\uDFFF]/u; const keywordRelationalOperator = /in(?:stanceof)?/y; /** * Prepare exported tokens. * The internal token type will be replaced by the exported one with more metadata. * For performance reasons this routine mutates `tokens`, it is okay * here since we execute `parseTopLevel` once for every file. */ export function createExportedTokens(tokens: (Token | N.Comment)[]) { for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; const { type } = token; if (typeof type === "number") { // we manipulate `token` for performance reasons (token as unknown as ExportedToken).type = getExportedToken(type); } } return tokens as unknown as (ExportedToken | N.Comment)[]; } export default abstract class StatementParser extends ExpressionParser { // ### Statement parsing // Parse a program. Initializes the parser, reads any number of // statements, and wraps them in a Program node. Optionally takes a // `program` argument. If present, the statements will be appended // to its body instead of creating a new node. parseTopLevel( this: Parser, file: Undone<N.File>, program: Undone<N.Program>, ): N.File { file.program = this.parseProgram( program, tt.eof, this.options.sourceType === "module" ? "module" : "script", ); file.comments = this.comments; if (this.optionFlags & OptionFlags.Tokens) { file.tokens = createExportedTokens(this.tokens); } return this.finishNode(file, "File"); } parseProgram( this: Parser, program: Undone<N.Program>, end: TokenType, sourceType: N.Program["sourceType"], ): N.Program { program.sourceType = sourceType; program.interpreter = this.parseInterpreterDirective(); this.parseBlockBody(program, true, true, end); if (this.inModule) { if ( !(this.optionFlags & OptionFlags.AllowUndeclaredExports) && this.scope.undefinedExports.size > 0 ) { for (const [localName, at] of Array.from(this.scope.undefinedExports)) { this.raise(Errors.ModuleExportUndefined, at, { localName }); } } this.addExtra(program, "topLevelAwait", this.state.hasTopLevelAwait); } let finishedProgram: N.Program; if (end === tt.eof) { // finish at eof for top level program finishedProgram = this.finishNode(program, "Program"); } else { // finish immediately before the end token finishedProgram = this.finishNodeAt( program, "Program", createPositionWithColumnOffset(this.state.startLoc, -1), ); } return finishedProgram; } /** * cast a Statement to a Directive. This method mutates input statement. */ stmtToDirective(stmt: N.ExpressionStatement): N.Directive { const directive = this.castNodeTo(stmt, "Directive"); const directiveLiteral = this.castNodeTo( stmt.expression, "DirectiveLiteral", ); const expressionValue = directiveLiteral.value; const raw = this.input.slice( this.offsetToSourcePos(directiveLiteral.start!), this.offsetToSourcePos(directiveLiteral.end!), ); const val = (directiveLiteral.value = raw.slice(1, -1)); // remove quotes this.addExtra(directiveLiteral, "raw", raw); this.addExtra(directiveLiteral, "rawValue", val); this.addExtra(directiveLiteral, "expressionValue", expressionValue); directive.value = directiveLiteral; // @ts-expect-error delete non-optional properties delete stmt.expression; return directive; } parseInterpreterDirective(): N.InterpreterDirective | null { if (!this.match(tt.interpreterDirective)) { return null; } const node = this.startNode<N.InterpreterDirective>(); node.value = this.state.value; this.next(); return this.finishNode(node, "InterpreterDirective"); } isLet(): boolean { if (!this.isContextual(tt._let)) { return false; } return this.hasFollowingBindingAtom(); } isUsing(): boolean { if (!this.isContextual(tt._using)) { return false; } return this.nextTokenIsIdentifierOnSameLine(); } isForUsing(): boolean { if (!this.isContextual(tt._using)) { return false; } const next = this.nextTokenInLineStart(); const nextCh = this.codePointAtPos(next); if (this.isUnparsedContextual(next, "of")) { const nextCharAfterOf = this.lookaheadCharCodeSince(next + 2); // `for( using of` must start either a for-lhs-of statement // or a for lexical declaration if ( nextCharAfterOf !== charCodes.equalsTo && nextCharAfterOf !== charCodes.colon && // recover from `for(using of;...);` nextCharAfterOf !== charCodes.semicolon ) { return false; } } if ( this.chStartsBindingIdentifier(nextCh, next) || this.isUnparsedContextual(next, "void") ) { return true; } return false; } nextTokenIsIdentifierOnSameLine(): boolean { const next = this.nextTokenInLineStart(); const nextCh = this.codePointAtPos(next); return this.chStartsBindingIdentifier(nextCh, next); } isAwaitUsing(): boolean { if (!this.isContextual(tt._await)) { return false; } let next = this.nextTokenInLineStart(); if (this.isUnparsedContextual(next, "using")) { next = this.nextTokenInLineStartSince(next + 5); const nextCh = this.codePointAtPos(next); if (this.chStartsBindingIdentifier(nextCh, next)) { return true; } } return false; } chStartsBindingIdentifier(ch: number, pos: number) { if (isIdentifierStart(ch)) { keywordRelationalOperator.lastIndex = pos; if (keywordRelationalOperator.test(this.input)) { // We have seen `in` or `instanceof` so far, now check if the identifier // ends here const endCh = this.codePointAtPos(keywordRelationalOperator.lastIndex); if (!isIdentifierChar(endCh) && endCh !== charCodes.backslash) { return false; } } return true; } else if (ch === charCodes.backslash) { return true; } else { return false; } } chStartsBindingPattern(ch: number) { return ( ch === charCodes.leftSquareBracket || ch === charCodes.leftCurlyBrace ); } /** * Assuming we have seen a contextual `let` and declaration is allowed, check if it * starts a variable declaration so that it should be interpreted as a keyword. */ hasFollowingBindingAtom(): boolean { const next = this.nextTokenStart(); const nextCh = this.codePointAtPos(next); return ( this.chStartsBindingPattern(nextCh) || this.chStartsBindingIdentifier(nextCh, next) ); } /** * Assuming we have seen a contextual `using` and declaration is allowed, check if it * starts a variable declaration in the same line so that it should be interpreted as * a keyword. */ hasInLineFollowingBindingIdentifierOrBrace(): boolean { const next = this.nextTokenInLineStart(); const nextCh = this.codePointAtPos(next); return ( nextCh === charCodes.leftCurlyBrace || this.chStartsBindingIdentifier(nextCh, next) ); } allowsUsing(): boolean { return ( (this.scope.inModule || !this.scope.inTopLevel) && !this.scope.inBareCaseStatement ); } // https://tc39.es/ecma262/#prod-ModuleItem parseModuleItem(this: Parser) { return this.parseStatementLike( ParseStatementFlag.AllowImportExport | ParseStatementFlag.AllowDeclaration | ParseStatementFlag.AllowFunctionDeclaration | // This function is actually also used to parse StatementItems, // which with Annex B enabled allows labeled functions. ParseStatementFlag.AllowLabeledFunction, ); } // https://tc39.es/ecma262/#prod-StatementListItem parseStatementListItem(this: Parser) { return this.parseStatementLike( ParseStatementFlag.AllowDeclaration | ParseStatementFlag.AllowFunctionDeclaration | (!this.options.annexB || this.state.strict ? 0 : ParseStatementFlag.AllowLabeledFunction), ); } parseStatementOrSloppyAnnexBFunctionDeclaration( this: Parser, allowLabeledFunction: boolean = false, ) { let flags: ParseStatementFlag = ParseStatementFlag.StatementOnly; if (this.options.annexB && !this.state.strict) { flags |= ParseStatementFlag.AllowFunctionDeclaration; if (allowLabeledFunction) { flags |= ParseStatementFlag.AllowLabeledFunction; } } return this.parseStatementLike(flags); } // Parse a single statement. // // If expecting a statement and finding a slash operator, parse a // regular expression literal. This is to handle cases like // `if (foo) /blah/.exec(foo)`, where looking at the previous token // does not help. // https://tc39.es/ecma262/#prod-Statement parseStatement(this: Parser) { return this.parseStatementLike(ParseStatementFlag.StatementOnly); } // ImportDeclaration and ExportDeclaration are also handled here so we can throw recoverable errors // when they are not at the top level parseStatementLike( this: Parser, flags: ParseStatementFlag, ): | N.Statement | N.Declaration | N.ImportDeclaration | N.ExportDefaultDeclaration | N.ExportNamedDeclaration | N.ExportAllDeclaration { let decorators: N.Decorator[] | null = null; if (this.match(tt.at)) { decorators = this.parseDecorators(true); } return this.parseStatementContent(flags, decorators); } parseStatementContent( this: Parser, flags: ParseStatementFlag, decorators?: N.Decorator[] | null, ): N.Statement { const startType = this.state.type; const node = this.startNode(); const allowDeclaration = !!(flags & ParseStatementFlag.AllowDeclaration); const allowFunctionDeclaration = !!( flags & ParseStatementFlag.AllowFunctionDeclaration ); const topLevel = flags & ParseStatementFlag.AllowImportExport; // Most types of statements are recognized by the keyword they // start with. Many are trivial to parse, some require a bit of // complexity. switch (startType) { case tt._break: return this.parseBreakContinueStatement(node, /* isBreak */ true); case tt._continue: return this.parseBreakContinueStatement(node, /* isBreak */ false); case tt._debugger: return this.parseDebuggerStatement(node); case tt._do: return this.parseDoWhileStatement(node); case tt._for: return this.parseForStatement(node); case tt._function: if (this.lookaheadCharCode() === charCodes.dot) break; if (!allowFunctionDeclaration) { this.raise( this.state.strict ? Errors.StrictFunction : this.options.annexB ? Errors.SloppyFunctionAnnexB : Errors.SloppyFunction, this.state.startLoc, ); } return this.parseFunctionStatement( node, false, !allowDeclaration && allowFunctionDeclaration, ); case tt._class: if (!allowDeclaration) this.unexpected(); return this.parseClass( this.maybeTakeDecorators( decorators, node as Undone<N.ClassDeclaration>, ), true, ); case tt._if: return this.parseIfStatement(node); case tt._return: return this.parseReturnStatement(node); case tt._switch: return this.parseSwitchStatement(node); case tt._throw: return this.parseThrowStatement(node); case tt._try: return this.parseTryStatement(node); case tt._await: // [+Await] await [no LineTerminator here] using [no LineTerminator here] BindingList[+Using] if (this.isAwaitUsing()) { if (!this.allowsUsing()) { this.raise(Errors.UnexpectedUsingDeclaration, node); } else if (!allowDeclaration) { this.raise(Errors.UnexpectedLexicalDeclaration, node); } else if (!this.recordAwaitIfAllowed()) { this.raise(Errors.AwaitUsingNotInAsyncContext, node); } this.next(); // eat 'await' return this.parseVarStatement(node, "await using"); } break; case tt._using: // using [no LineTerminator here] BindingList[+Using] if ( this.state.containsEsc || !this.hasInLineFollowingBindingIdentifierOrBrace() ) { break; } if (!this.allowsUsing()) { this.raise(Errors.UnexpectedUsingDeclaration, this.state.startLoc); } else if (!allowDeclaration) { this.raise(Errors.UnexpectedLexicalDeclaration, this.state.startLoc); } return this.parseVarStatement(node, "using"); case tt._let: { if (this.state.containsEsc) { break; } // `let [` is an explicit negative lookahead for // ExpressionStatement, so special-case it first. const next = this.nextTokenStart(); const nextCh = this.codePointAtPos(next); if (nextCh !== charCodes.leftSquareBracket) { if (!allowDeclaration && this.hasFollowingLineBreak()) break; if ( !this.chStartsBindingIdentifier(nextCh, next) && nextCh !== charCodes.leftCurlyBrace ) { break; } } } // fall through case tt._const: { if (!allowDeclaration) { this.raise(Errors.UnexpectedLexicalDeclaration, this.state.startLoc); } } // fall through case tt._var: { const kind = this.state.value; return this.parseVarStatement(node, kind); } case tt._while: return this.parseWhileStatement(node); case tt._with: return this.parseWithStatement(node); case tt.braceL: return this.parseBlock(); case tt.semi: return this.parseEmptyStatement(node); case tt._import: { const nextTokenCharCode = this.lookaheadCharCode(); if ( nextTokenCharCode === charCodes.leftParenthesis || // import() nextTokenCharCode === charCodes.dot // import.meta ) { break; } } // fall through case tt._export: { if ( !(this.optionFlags & OptionFlags.AllowImportExportEverywhere) && !topLevel ) { this.raise(Errors.UnexpectedImportExport, this.state.startLoc); } this.next(); // eat `import`/`export` let result; if (startType === tt._import) { result = this.parseImport(node); } else { result = this.parseExport(node, decorators); } this.assertModuleNodeAllowed(result); return result; } default: { if (this.isAsyncFunction()) { if (!allowDeclaration) { this.raise( Errors.AsyncFunctionInSingleStatementContext, this.state.startLoc, ); } this.next(); // eat 'async' return this.parseFunctionStatement( node, true, !allowDeclaration && allowFunctionDeclaration, ); } } } // If the statement does not start with a statement keyword or a // brace, it's an ExpressionStatement or LabeledStatement. We // simply start parsing an expression, and afterwards, if the // next token is a colon and the expression was a simple // Identifier node, we switch to interpreting it as a label. const maybeName = this.state.value; const expr = this.parseExpression(); if ( tokenIsIdentifier(startType) && expr.type === "Identifier" && this.eat(tt.colon) ) { return this.parseLabeledStatement(node, maybeName, expr, flags); } else { return this.parseExpressionStatement(node, expr, decorators); } } assertModuleNodeAllowed(node: N.Node): void { if ( !(this.optionFlags & OptionFlags.AllowImportExportEverywhere) && !this.inModule ) { this.raise(Errors.ImportOutsideModule, node); } } // Attach the decorators to the given class. // NOTE: This method changes the .start location of the class, and thus // can affect comment attachment. Calling it before or after finalizing // the class node (and thus finalizing its comments) changes how comments // before the `class` keyword or before the final .start location of the // class are attached. maybeTakeDecorators<T extends Undone<N.Class>>( maybeDecorators: N.Decorator[] | null | undefined, classNode: T, exportNode?: Undone<N.ExportDefaultDeclaration | N.ExportNamedDeclaration>, ): T { if (maybeDecorators) { if (classNode.decorators?.length) { // Note: decorators attachment is only attempted multiple times // when the class is part of an export declaration. this.raise(Errors.DecoratorsBeforeAfterExport, classNode.decorators[0]); classNode.decorators.unshift(...maybeDecorators); } else { classNode.decorators = maybeDecorators; } this.resetStartLocationFromNode(classNode, maybeDecorators[0]); if (exportNode) this.resetStartLocationFromNode(exportNode, classNode); } return classNode; } canHaveLeadingDecorator(): boolean { return this.match(tt._class); } parseDecorators(this: Parser, allowExport?: boolean): N.Decorator[] { const decorators = []; do { decorators.push(this.parseDecorator()); } while (this.match(tt.at)); if (this.match(tt._export)) { if (!allowExport) { this.unexpected(); } } else if (!this.canHaveLeadingDecorator()) { throw this.raise(Errors.UnexpectedLeadingDecorator, this.state.startLoc); } return decorators; } parseDecorator(this: Parser): N.Decorator { this.expectOnePlugin(["decorators", "decorators-legacy"]); const node = this.startNode<N.Decorator>(); this.next(); if (this.hasPlugin("decorators")) { const startLoc = this.state.startLoc; let expr: N.Expression; if (this.match(tt.parenL)) { const startLoc = this.state.startLoc; this.next(); // eat '(' expr = this.parseExpression(); this.expect(tt.parenR); expr = this.wrapParenthesis(startLoc, expr); const paramsStartLoc = this.state.startLoc; node.expression = this.parseMaybeDecoratorArguments(expr, startLoc); if (node.expression !== expr) { this.raise( Errors.DecoratorArgumentsOutsideParentheses, paramsStartLoc, ); } } else { expr = this.parseIdentifier(false); while (this.eat(tt.dot)) { const node = this.startNodeAt<N.MemberExpression>(startLoc); node.object = expr; if (this.match(tt.privateName)) { this.classScope.usePrivateName( this.state.value, this.state.startLoc, ); node.property = this.parsePrivateName(); } else { node.property = this.parseIdentifier(true); } node.computed = false; expr = this.finishNode(node, "MemberExpression"); } node.expression = this.parseMaybeDecoratorArguments(expr, startLoc); } } else { this.state.canStartArrow = false; node.expression = this.parseExprSubscripts(); } return this.finishNode(node, "Decorator"); } parseMaybeDecoratorArguments( this: Parser, expr: N.Expression, startLoc: Position, ): N.Expression { if (this.eat(tt.parenL)) { const node = this.startNodeAt<N.CallExpression>(startLoc); node.callee = expr; node.arguments = this.parseCallExpressionArguments(); this.toReferencedList(node.arguments); return this.finishNode(node, "CallExpression"); } return expr; } parseBreakContinueStatement( node: Undone<N.Node>, isBreak: true, ): N.BreakStatement; parseBreakContinueStatement( node: Undone<N.Node>, isBreak: false, ): N.ContinueStatement; parseBreakContinueStatement( node: Undone<N.BreakStatement | N.ContinueStatement>, isBreak: boolean, ): N.BreakStatement | N.ContinueStatement { this.next(); if (this.isLineTerminator()) { node.label = null; } else { node.label = this.parseIdentifier(); this.semicolon(); } this.verifyBreakContinue(node, isBreak); return this.finishNode( node, isBreak ? "BreakStatement" : "ContinueStatement", ); } verifyBreakContinue( node: Undone<N.BreakStatement | N.ContinueStatement>, isBreak: boolean, ) { let i; for (i = 0; i < this.state.labels.length; ++i) { const lab = this.state.labels[i]; if (node.label == null || lab.name === node.label.name) { if (lab.kind != null && (isBreak || lab.kind === LoopLabelKind.Loop)) { break; } if (node.label && isBreak) break; } } if (i === this.state.labels.length) { const type = isBreak ? "BreakStatement" : "ContinueStatement"; this.raise(Errors.IllegalBreakContinue, node, { type }); } } parseDebuggerStatement( node: Undone<N.DebuggerStatement>, ): N.DebuggerStatement { this.next(); this.semicolon(); return this.finishNode(node, "DebuggerStatement"); } parseHeaderExpression(this: Parser): N.Expression { this.expect(tt.parenL); const val = this.parseExpression(); this.expect(tt.parenR); return val; } // https://tc39.es/ecma262/#prod-DoWhileStatement parseDoWhileStatement( this: Parser, node: Undone<N.DoWhileStatement>, ): N.DoWhileStatement { this.next(); this.state.labels.push(loopLabel); // Parse the loop body's body. node.body = this.parseStatement(); this.state.labels.pop(); this.expect(tt._while); node.test = this.parseHeaderExpression(); this.eat(tt.semi); return this.finishNode(node, "DoWhileStatement"); } // Disambiguating between a `for` and a `for`/`in` or `for`/`of` // loop is non-trivial. Basically, we have to parse the init `var` // statement or expression, disallowing the `in` operator (see // the second parameter to `parseExpression`), and then check // whether the next token is `in` or `of`. When there is no init // part (semicolon immediately after the opening parenthesis), it // is a regular `for` loop. parseForStatement(this: Parser, node: Undone<N.For>): N.For { this.next(); this.state.labels.push(loopLabel); let awaitAt = null; if (this.isContextual(tt._await) && this.recordAwaitIfAllowed()) { awaitAt = this.state.startLoc; this.next(); } this.scope.enter(ScopeFlag.OTHER); this.expect(tt.parenL); if (this.match(tt.semi)) { if (awaitAt !== null) { this.unexpected(awaitAt); } return this.parseFor(node, null); } const startsWithLet = this.isContextual(tt._let); { const startsWithAwaitUsing = this.isAwaitUsing(); const starsWithUsingDeclaration = startsWithAwaitUsing || this.isForUsing(); 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.recordAwaitIfAllowed()) { this.raise(Errors.AwaitUsingNotInAsyncContext, 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 && starsWithUsingDeclaration) { this.raise(Errors.ForInUsing, init); } if ( (isForIn || this.isContextual(tt._of)) && init.declarations.length === 1 ) { return this.parseForIn( node as Undone<N.ForXStatement>, init, awaitAt, ); } if (awaitAt !== null) { this.unexpected(awaitAt); } return this.parseFor(node, init); } } // Check whether the first token is possibly a contextual keyword, so that // we can forbid `for (async of` if this turns out to be a for-of loop. const startsWithAsync = this.isContextual(tt._async); const refExpressionErrors = new ExpressionErrors(); const init = this.parseExpression(true, refExpressionErrors); const isForOf = this.isContextual(tt._of); if (isForOf) { // Check for leading tokens that are forbidden in for-of loops: if (startsWithLet) { this.raise(Errors.ForOfLet, init); } if ( // `for await (async of []);` is allowed. awaitAt === null && startsWithAsync && init.type === "Identifier" ) { // This catches the case where the `async` in `for (async of` was // parsed as an identifier. If it was parsed as the start of an async // arrow function (e.g. `for (async of => {} of []);`), the LVal check // further down will raise a more appropriate error. this.raise(Errors.ForOfAsync, init); } } if (isForOf || this.match(tt._in)) { this.checkDestructuringPrivate(refExpressionErrors); this.toAssignable(init, /* isLHS */ true); const type = isForOf ? "ForOfStatement" : "ForInStatement"; this.checkLVal(init, { type }); return this.parseForIn( node as Undone<N.ForInStatement | N.ForOfStatement>, // @ts-expect-error init has been transformed to an assignable init, awaitAt, ); } else { this.checkExpressionErrors(refExpressionErrors, true); } if (awaitAt !== null) { this.unexpected(awaitAt); } return this.parseFor(node, init); } // https://tc39.es/ecma262/#prod-HoistableDeclaration parseFunctionStatement( this: Parser, node: Undone<N.FunctionDeclaration>, isAsync: boolean, isHangingDeclaration: boolean, ): N.FunctionDeclaration { this.next(); // eat 'function' return this.parseFunction( node, ParseFunctionFlag.Declaration | (isHangingDeclaration ? ParseFunctionFlag.HangingDeclaration : 0) | (isAsync ? ParseFunctionFlag.Async : 0), ); } // https://tc39.es/ecma262/#prod-IfStatement parseIfStatement(this: Parser, node: Undone<N.IfStatement>) { this.next(); node.test = this.parseHeaderExpression(); // Annex B.3.3 // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses node.consequent = this.parseStatementOrSloppyAnnexBFunctionDeclaration(); node.alternate = this.eat(tt._else) ? this.parseStatementOrSloppyAnnexBFunctionDeclaration() : null; return this.finishNode(node, "IfStatement"); } parseReturnStatement(this: Parser, node: Undone<N.ReturnStatement>) { if (!this.prodParam.hasReturn) { this.raise(Errors.IllegalReturn, this.state.startLoc); } this.next(); // In `return` (and `break`/`continue`), the keywords with // optional arguments, we eagerly look for a semicolon or the // possibility to insert one. if (this.isLineTerminator()) { node.argument = null; } else { node.argument = this.parseExpression(); this.semicolon(); } return this.finishNode(node, "ReturnStatement"); } // https://tc39.es/ecma262/#prod-SwitchStatement parseSwitchStatement(this: Parser, node: Undone<N.SwitchStatement>) { this.next(); node.discriminant = this.parseHeaderExpression(); const cases: N.SwitchStatement["cases"] = (node.cases = []); this.expect(tt.braceL); this.state.labels.push(switchLabel); this.scope.enter(ScopeFlag.SWITCH); // Statements under must be grouped (by label) in SwitchCase // nodes. `cur` is used to keep the node that we are currently // adding statements to. let cur; for (let sawDefault; !this.match(tt.braceR);) { if (this.match(tt._case) || this.match(tt._default)) { const isCase = this.match(tt._case); if (cur) this.finishNode(cur, "SwitchCase"); // @ts-expect-error Fixme cases.push((cur = this.startNode<N.SwitchCase>())); cur.consequent = []; this.next(); if (isCase) { cur.test = this.parseExpression(); } else { if (sawDefault) { this.raise( Errors.MultipleDefaultsInSwitch, this.state.lastTokStartLoc!, ); } sawDefault = true; cur.test = null; } this.expect(tt.colon); } else { if (cur) { cur.consequent.push(this.parseStatementListItem()); } else { this.unexpected(); } } } this.scope.exit(); if (cur) this.finishNode(cur, "SwitchCase"); this.next(); // Closing brace this.state.labels.pop(); return this.finishNode(node, "SwitchStatement"); } parseThrowStatement(this: Parser, node: Undone<N.ThrowStatement>) { this.next(); if (this.hasPrecedingLineBreak()) { this.raise(Errors.NewlineAfterThrow, this.state.lastTokEndLoc!); } node.argument = this.parseExpression(); this.semicolon(); return this.finishNode(node, "ThrowStatement"); } parseCatchClauseParam(this: Parser) { const param = this.parseBindingAtom(); this.scope.enter( this.options.annexB && param.type === "Identifier" ? ScopeFlag.SIMPLE_CATCH : ScopeFlag.OTHER, ); this.checkLVal( param, { type: "CatchClause" }, BindingFlag.TYPE_CATCH_PARAM, ); // checkLVal ensures that VoidPattern can not be used as a catch clause parameter. return param as Exclude< ReturnType<typeof this.parseBindingAtom>, N.VoidPattern >; } parseTryStatement( this: Parser, node: Undone<N.TryStatement>, ): N.TryStatement { this.next(); node.block = this.parseBlock(); node.handler = null; if (this.match(tt._catch)) { const clause = this.startNode<N.CatchClause>(); this.next(); if (this.match(tt.parenL)) { this.expect(tt.parenL); clause.param = this.parseCatchClauseParam(); this.expect(tt.parenR); } else { clause.param = null; this.scope.enter(ScopeFlag.OTHER); } // Parse the catch clause's body. clause.body = this.parseBlock(false, false); this.scope.exit(); node.handler = this.finishNode(clause, "CatchClause"); } node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null; if (!node.handler && !node.finalizer) { this.raise(Errors.NoCatchOrFinally, node); } return this.finishNode(node, "TryStatement"); } // https://tc39.es/ecma262/#prod-VariableStatement // https://tc39.es/ecma262/#prod-LexicalDeclaration parseVarStatement( this: Parser, node: Undone<N.VariableDeclaration>, kind: "var" | "let" | "const" | "using" | "await using", allowMissingInitializer: boolean = false, ): N.VariableDeclaration { this.next(); this.parseVar(node, false, kind, allowMissingInitializer); this.semicolon(); return this.finishNode(node, "VariableDeclaration"); } // https://tc39.es/ecma262/#prod-WhileStatement parseWhileStatement( this: Parser, node: Undone<N.WhileStatement>, ): N.WhileStatement { this.next(); node.test = this.parseHeaderExpression(); this.state.labels.push(loopLabel); // Parse the loop body. node.body = this.parseStatement(); this.state.labels.pop(); return this.finishNode(node, "WhileStatement"); } parseWithStatement( this: Parser, node: Undone<N.WithStatement>, ): N.WithStatement { if (this.state.strict) { this.raise(Errors.StrictWith, this.state.startLoc); } this.next(); node.object = this.parseHeaderExpression(); // Parse the statement body. node.body = this.parseStatement(); return this.finishNode(node, "WithStatement"); } parseEmptyStatement(node: Undone<N.EmptyStatement>): N.EmptyStatement { this.next(); return this.finishNode(node, "EmptyStatement"); } // https://tc39.es/ecma262/#prod-LabelledStatement parseLabeledStatement( this: Parser, node: Undone<N.LabeledStatement>, maybeName: string, expr: N.Identifier, flags: ParseStatementFlag, ): N.LabeledStatement { for (const label of this.state.labels) { if (label.name === maybeName) { this.raise(Errors.LabelRedeclaration, expr, { labelName: maybeName, }); } } const kind = tokenIsLoop(this.state.type) ? LoopLabelKind.Loop : this.match(tt._switch) ? LoopLabelKind.Switch : null; for (let i = this.state.labels.length - 1; i >= 0; i--) { const label = this.state.labels[i]; if (label.statementStart === node.start) { label.statementStart = this.sourceToOffsetPos(this.state.start); label.kind = kind; } else { break; } } this.state.labels.push({ name: maybeName, kind: kind, statementStart: this.sourceToOffsetPos(this.state.start), }); // https://tc39.es/ecma262/#prod-LabelledItem node.body = flags & ParseStatementFlag.AllowLabeledFunction ? this.parseStatementOrSloppyAnnexBFunctionDeclaration(true) : this.parseStatement(); this.state.labels.pop(); node.label = expr; return this.finishNode(node, "LabeledStatement"); } parseExpressionStatement( node: Undone<N.ExpressionStatement>, expr: N.Expression, /* eslint-disable-next-line @typescript-eslint/no-unused-vars -- used in TypeScript parser */ decorators: N.Decorator[] | null | undefined, ) { node.expression = expr; this.semicolon(); return this.finishNode(node, "ExpressionStatement"); } // Parse a semicolon-enclosed block of statements, handling `"use // strict"` declarations when `allowDirectives` is true (used for // function bodies). parseBlock( this: Parser, allowDirectives: boolean = false, createNewLexicalScope: boolean = true, afterBlockParse?: (hasStrictModeDirective: boolean) => void, ): N.BlockStatement { const node = this.startNode<N.BlockStatement>(); if (allowDirectives) { this.state.strictErrors.clear(); } this.expect(tt.braceL); if (createNewLexicalScope) { this.scope.enter(ScopeFlag.OTHER); } this.parseBlockBody( node, allowDirectives, false, tt.braceR, afterBlockParse, ); if (createNewLexicalScope) { this.scope.exit(); } return this.finishNode(node, "BlockStatement"); } isValidDirective(stmt: N.Statement): stmt is N.ExpressionStatement { return ( stmt.type === "ExpressionStatement" && stmt.expression.type === "StringLiteral" && !stmt.expression.extra!.parenthesized ); } parseBlockBody( this: Parser, node: Undone<N.BlockStatementLike>, allowDirectives: boolean | undefined | null, topLevel: boolean, end: TokenType, afterBlockParse?: (hasStrictModeDirective: boolean) => void, ): void { const body: N.BlockStatementLike["body"] = (node.body = []); const directives: N.BlockStatementLike["directives"] = (node.directives = []); this.parseBlockOrModuleBlockBody( body, allowDirectives ? directives : undefined, topLevel, end, afterBlockParse, ); } // Undefined directives means that directives are not allowed. // https://tc39.es/ecma262/#prod-Block // https://tc39.es/ecma262/#prod-ModuleBody parseBlockOrModuleBlockBody( this: Parser, body: N.Statement[], directives: N.Directive[] | undefined | null, topLevel: boolean, end: TokenType, afterBlockParse?: (hasStrictModeDirective: boolean) => void, ): void { const oldStrict = this.state.strict; let hasStrictModeDirective = false; let parsedNonDirective = false; while (!this.match(end)) { const stmt = topLevel ? this.parseModuleItem() : this.parseStatementListItem(); if (directives && !parsedNonDirective) { if (this.isValidDirective(stmt)) { const directive = this.stmtToDirective(stmt); directives.push(directive); if ( !hasStrictModeDirective && directive.value.value === "use strict" ) { hasStrictModeDirective = true; this.setStrict(true); } continue; } parsedNonDirective = true; // clear strict errors since the strict mode will not change within the block this.state.strictErrors.clear(); } body.push(stmt); } afterBlockParse?.call(this, hasStrictModeDirective); if (!oldStrict) { this.setStrict(false); } this.next(); } // Parse a regular `for` loop. The disambiguation code in // `parseStatement` will already have parsed the init statement or // expression. parseFor( this: Parser, node: Undone<N.ForStatement>, init?: N.VariableDeclaration | N.Expression | null, ): N.ForStatement { node.init = init; this.semicolon(/* allowAsi */ false); node.test = this.match(tt.semi) ? null : this.parseExpression(); this.semicolon(/* allowAsi */ false); node.update = this.match(tt.parenR) ? null : this.parseExpression(); this.expect(tt.parenR); // Parse the loop body. node.body = this.parseStatement(); this.scope.exit(); this.state.labels.pop(); return this.finishNode(node, "ForStatement"); } // Parse a `for`/`in` and `for`/`of` loop, which are almost // same from parser's perspective. parseForIn( this: Parser, node: Undone<N.ForXStatement>, init: N.VariableDeclaration | N.AssignmentPattern, awaitAt?: Position | null, ): N.ForXStatement { const isForIn = this.match(tt._in); this.next(); if (isForIn) { if (awaitAt !== null) this.unexpected(awaitAt); } else { (node as Undone<N.ForOfStatement>).await = awaitAt !== null; } if ( init.type === "VariableDeclaration" && init.declarations[0].init != null && (!isForIn || !this.options.annexB || this.state.strict || init.kind !== "var" || init.declarations[0].id.type !== "Identifier") ) { this.raise(Errors.ForInOfLoopInitializer, init, { type: isForIn ? "ForInStatement" : "ForOfStatement", }); } if (init.type === "AssignmentPattern") { this.raise(Errors.InvalidLhs, init, { ancestor: { type: "ForStatement" }, }); } node.left = init as N.VariableDeclaration; node.right = isForIn ? this.parseExpression() : this.parseMaybeAssignAllowIn(); this.expect(tt.parenR); // Parse the loop body. node.body = this.parseStatement(); this.scope.exit(); this.state.labels.pop(); return this.finishNode(node, isForIn ? "ForInStatement" : "ForOfStatement"); } // Parse a list of variable declarations. parseVar( this: Parser, node: Undone<N.VariableDeclaration>, isFor: boolean, kind: "var" | "let" | "const" | "using" | "await using", allowMissingInitializer: boolean = false, ): Undone<N.VariableDeclaration> { const declarations: N.VariableDeclarator[] = (node.declarations = []); node.kind = kind; for (;;) { const decl = this.startNode<N.VariableDeclarator>(); this.parseVarId(decl, kind); decl.init = !this.eat(tt.eq) ? null : isFor ? this.parseMaybeAssignDisallowIn() : this.parseMaybeAssignAllowIn(); if (decl.init === null && !allowMissingInitializer) { if ( decl.id.type !== "Identifier" && !(isFor && (this.match(tt._in) || this.isContextual(tt._of))) ) { this.raise( Errors.DeclarationMissingInitializer, this.state.lastTokEndLoc!, { kind: "destructuring", }, ); } else if ( (kind === "const" || kind === "using" || kind === "await using") && !(this.match(tt._in) || this.isContextual(tt._of)) ) { this.raise( Errors.DeclarationMissingInitializer, this.state.lastTokEndLoc!, { kind }, ); } } declarations.push(this.finishNode(decl, "VariableDeclarator")); if (!this.eat(tt.comma)) break; } return node; } parseVarId( this: Parser, decl: Undone<N.VariableDeclarator>, kind: "var" | "let" | "const" | "using" | "await using", ): void { const id = this.parseBindingAtom(); if (kind === "using" || kind === "await using") { if (id.type === "ArrayPattern" || id.type === "ObjectPattern") { this.raise(Errors.UsingDeclarationHasBindingPattern, id); } } else { if (id.type === "VoidPattern") { this.raise(Errors.UnexpectedVoidPattern, id); } } this.checkLVal( id, { type: "VariableDeclarator" }, kind === "var" ? BindingFlag.TYPE_VAR : BindingFlag.TYPE_LEXICAL, ); decl.id = id; } // https://tc39.es/ecma262/#prod-AsyncFunctionExpression parseAsyncFunctionExpression( this: Parser, node: Undone<N.FunctionExpression>, ): N.FunctionExpression { return this.parseFunction(node, ParseFunctionFlag.Async); } // Parse a function declaration or expression (depending on the // ParseFunctionFlag.Declaration flag). parseFunction<T extends N.NormalFunction>( this: Parser, node: Undone<T>, flags: ParseFunctionFlag = ParseFunctionFlag.Expression, ): T { const hangingDeclaration = flags & ParseFunctionFlag.HangingDeclaration; const isDeclaration = !!(flags & ParseFunctionFlag.Declaration); const requireId = isDeclaration && !(flags & ParseFunctionFlag.NullableId); const isAsync = !!(flags & ParseFunctionFlag.Async); this.initFunction(node, isAsync); if (this.match(tt.star)) { if (hangingDeclaration) { this.raise( Errors.GeneratorInSingleStatementContext, this.state.startLoc, ); } this.next(); // eat * node.generator = true; } if (isDeclaration) { node.id = this.parseFunctionId(requireId); } this.scope.enter(ScopeFlag.FUNCTION); this.prodParam.enter(functionFlags(isAsync, node.generator)); if (!isDeclaration) { node.id = this.parseFunctionId(); } this.parseFunctionParams(node, /* isConstructor */ false); this.parseFunctionBodyAndFinish( node, isDeclaration ? "FunctionDeclaration" : "FunctionExpression", ); this.prodParam.exit(); this.scope.exit(); if (isDeclaration && !hangingDeclaration) { // We need to register this _after_ parsing the function body // because of TypeScript body-less function declarations, // which shouldn't be added to the scope. this.registerFunctionStatementId(node as T); } return node as T; } parseFunctionId(requireId?: boolean): N.Identifier | undefined | null { return requireId || tokenIsIdentifier(this.state.type) ? this.parseIdentifier() : null; } parseFunctionParams( this: Parser, node: Undone<N.Function>, isConstructor?: boolean, ): void { this.expect(tt.parenL); this.expressionScope.enter(newParameterDeclarationScope()); node.params = this.parseBindingList( tt.parenR, charCodes.rightParenthesis, ParseBindingListFlags.IS_FUNCTION_PARAMS | (isConstructor ? ParseBindingListFlags.IS_CONSTRUCTOR_PARAMS : 0), ) as N.Pattern[]; this.expressionScope.exit(); } registerFunctionStatementId(node: N.Function): void { // @ts-expect-error Fixme: id is not defined in ArrowFunctionExpression if (!node.id) return; // If it is a regular function declaration in sloppy mode, then it is // subject to Annex B semantics (BindingFlag.TYPE_FUNCTION). Otherwise, the binding // mode depends on properties of the current scope (see // treatFunctionsAsVar). this.scope.declareName( // @ts-expect-error Fixme: id is not defined in ArrowFunctionExpression node.id.name, !this.options.annexB || this.state.strict || node.generator || node.async ? this.scope.treatFunctionsAsVar ? BindingFlag.TYPE_VAR : BindingFlag.TYPE_LEXICAL : BindingFlag.TYPE_FUNCTION, // @ts-expect-error Fixme: id is not defined in ArrowFunctionExpression node.id.start, ); } // Parse a class declaration or literal (depending on the // `isStatement` parameter). parseClass<T extends N.Class>( this: Parser, node: Undone<T>, isStatement: /* T === ClassDeclaration */ boolean, optionalId?: boolean, ): T { this.next(); // 'class' // A class definition is always strict mode code. const oldStrict = this.state.strict; this.state.strict = true; this.parseClassId(node, isStatement, optionalId); this.parseClassSuper(node); // this.state.strict is restored in parseClassBody node.body = this.parseClassBody(!!node.superClass, oldStrict); return this.finishNode( node, isStatement ? "ClassDeclaration" : "ClassExpression", ); } isClassProperty(): boolean { return this.match(tt.eq) || this.match(tt.semi) || this.match(tt.braceR); } isClassMethod(): boolean { return this.match(tt.parenL); } nameIsConstructor(key: N.Expression | N.PrivateName): boolean { return ( (key.type === "Identifier" && key.name === "constructor") || (key.type === "StringLiteral" && key.value === "constructor") ); } isNonstaticConstructor( method: Undone<N.ClassMethod | N.ClassProperty>, ): boolean { return ( !method.computed && !method.static && this.nameIsConstructor(method.key) ); } // https://tc39.es/ecma262/#prod-ClassBody parseClassBody( this: Parser, hadSuperClass: boolean, oldStrict: boolean, ): N.ClassBody { this.classScope.enter(); const state: N.ParseClassMemberState = { hadConstructor: false, hadSuperClass, }; let decorators: N.Decorator[] = []; const classBody = this.startNode<N.ClassBody>(); classBody.body = []; this.expect(tt.braceL); // Parse the contents within the braces. while (!this.match(tt.braceR)) { if (this.eat(tt.semi)) { if (decorators.length > 0) { throw this.raise( Errors.DecoratorSemicolon, this.state.lastTokEndLoc!, ); } continue; } if (this.match(tt.at)) { decorators.push(this.parseDecorator()); continue; } const member = this.startNode<N.ClassMember>(); // steal the decorators if there are any if (decorators.length) { member.decorators = decorators; this.resetStartLocationFromNode(member, decorators[0]); decorators = []; } this.parseClassMember(classBody, member, state); } this.state.strict = oldStrict; this.next(); // eat `}` if (decorators.length) { throw this.raise(Errors.TrailingDecorator, this.state.startLoc); } this.classScope.exit(); return this.finishNode(classBody, "ClassBody"); } // returns true if the current identifier is a method/field name, // false if it is a modifier parseClassMemberFromModifier( this: Parser, classBody: Undone<N.ClassBody>, member: Undone<N.ClassMember>, ): boolean { const key = this.parseIdentifier(true); // eats the modifier if (this.isClassMethod()) { const method: N.ClassMethod = member as any; // a method named like the modifier method.kind = "method"; method.computed = false; method.key = key; method.static = false; this.pushClassMethod( classBody, method, false, false, /* isConstructor */ false, false, ); return true; } else if (this.isClassProperty()) { const prop: N.ClassProperty = member as any; // a property named like the modifier prop.computed = false; prop.key = key; prop.static = false; classBody.body.push(this.parseClassProperty(prop)); return true; } this.resetPreviousNodeTrailingComments(key); return false; } parseClassMember( this: Parser, classBody: Undone<N.ClassBody>, member: Undone<N.ClassMember>, state: N.ParseClassMemberState, ): void { const isStatic = this.isContextual(tt._static); if (isStatic) { if (this.parseClassMemberFromModifier(classBody, member)) { // a class element named 'static' return; } if (this.eat(tt.braceL)) { this.parseClassStaticBlock(classBody, member as any as N.StaticBlock); return; } } this.parseClassMemberWithIsStatic(classBody, member, state, isStatic); } parseClassMemberWithIsStatic( this: Parser, classBody: Undone<N.ClassBody>, member: Undone<N.ClassMember>, state: N.ParseClassMemberState, isStatic: boolean, ) { const publicMethod = member as Undone<N.ClassMethod>; const privateMethod = member as Undone<N.ClassPrivateMethod>; const publicProp = member as Undone<N.ClassProperty>; const privateProp = member as Undone<N.ClassPrivateProperty>; const accessorProp = member as Undone<N.ClassAccessorProperty>; const method: typeof publicMethod | typeof privateMethod = publicMethod; const publicMember: typeof publicMethod | typeof publicProp = publicMethod; member.static = isStatic; this.parsePropertyNamePrefixOperator(member); if (this.eat(tt.star)) { // a generator method.kind = "method"; const isPrivateName = this.match(tt.privateName); this.parseClassElementName(method); this.parsePostMemberNameModifiers(method); if (isPrivateName) { // Private generator method this.pushClassPrivateMethod(classBody, privateMethod, true, false); return; } if (this.isNonstaticConstructor(publicMethod)) { this.raise(Errors.ConstructorIsGenerator, publicMethod.key); } this.pushClassMethod( classBody, publicMethod, true, false, /* isConstructor */ false, false, ); return; } const isContextual = !this.state.containsEsc && tokenIsIdentifier(this.state.type); const key = this.parseClassElementName(member); const maybeContextualKw = isContextual ? (key as N.Identifier).name : null; const isPrivate = this.isPrivateName(key); const maybeQuestionTokenStartLoc = this.state.startLoc; this.parsePostMemberNameModifiers(publicMember); if (this.isClassMethod()) { method.kind = "method"; if (isPrivate) { this.pushClassPrivateMethod(classBody, privateMethod, false, false); return; } // a normal method const isConstructor = this.isNonstaticConstructor(publicMethod); let allowsDirectSuper = false; if (isConstructor) { publicMethod.kind = "constructor"; if (publicMethod.decorators && publicMethod.decorators.length > 0) { this.raise(Errors.DecoratorConstructor, member); } // TypeScript allows multiple overloaded constructor declarations. if (state.hadConstructor && !this.hasPlugin("typescript")) { this.raise(Errors.DuplicateConstructor, key); } // @ts-expect-error override is not defined in ClassMethod, here we parse it // and throw a recoverable error if (isConstructor && this.hasPlugin("typescript") && member.override) { this.raise(Errors.OverrideOnConstructor, key); } state.hadConstructor = true; allowsDirectSuper = state.hadSuperClass; } this.pushClassMethod( classBody, publicMethod, false, false, isConstructor, allowsDirectSuper, ); } else if (this.isClassProperty()) { if (isPrivate) { this.pushClassPrivateProperty(classBody, privateProp); } else { this.pushClassProperty(classBody, publicProp); } } else if (maybeContextualKw === "async" && !this.isLineTerminator()) { // an async method this.resetPreviousNodeTrailingComments(key); const isGenerator = this.eat(tt.star); if (publicMember.optional) { this.unexpected(maybeQuestionTokenStartLoc); } method.kind = "method"; // The so-called parsed name would have been "async": get the real name. const isPrivate = this.match(tt.privateName); this.parseClassElementName(method); this.parsePostMemberNameModifiers(publicMember); if (isPrivate) { // private async method this.pushClassPrivateMethod( classBody, privateMethod, isGenerator, true, ); } else { if (this.isNonstaticConstructor(publicMethod)) { this.raise(Errors.ConstructorIsAsync, publicMethod.key); } this.pushClassMethod( classBody, publicMethod, isGenerator, true, /* isConstructor */ false, false, ); } } else if ( (maybeContextualKw === "get" || maybeContextualKw === "set") && !(this.match(tt.star) && this.isLineTerminator()) ) { // `get\n*` is an uninitialized property named 'get' followed by a generator. // a getter or setter this.resetPreviousNodeTrailingComments(key); method.kind = maybeContextualKw; // The so-called parsed name would have been "get/set": get the real name. const isPrivate = this.match(tt.privateName); this.parseClassElementName(publicMethod); if (isPrivate) { // private getter/setter this.pushClassPrivateMethod(classBody, privateMethod, false, false); } else { if (this.isNonstaticConstructor(publicMethod)) { this.raise(Errors.ConstructorIsAccessor, publicMethod.key); } this.pushClassMethod( classBody, publicMethod, false, false, /* isConstructor */ false, false, ); } this.checkGetterSetterParams(publicMethod); } else if (maybeContextualKw === "accessor" && !this.isLineTerminator()) { this.expectPlugin("decoratorAutoAccessors"); this.resetPreviousNodeTrailingComments(key); // The so-called parsed name would have been "accessor": get the real name. const isPrivate = this.match(tt.privateName); this.parseClassElementName(publicProp); this.pushClassAccessorProperty(classBody, accessorProp, isPrivate); } else if (this.isLineTerminator()) { // an uninitialized class property (due to ASI, since we don't otherwise recognize the next token) if (isPrivate) { this.pushClassPrivateProperty(classBody, privateProp); } else { this.pushClassProperty(classBody, publicProp); } } else { this.unexpected(); } } // https://tc39.es/ecma262/#prod-ClassElementName parseClassElementName( this: Parser, member: Undone<N.ClassMember>, ): N.Expression | N.Identifier | N.PrivateName { const { type, value } = this.state; if ( (type === tt.name || type === tt.string) && member.static && value === "prototype" ) { this.raise(Errors.StaticPrototype, this.state.startLoc); } if (type === tt.privateName) { if (value === "constructor") { this.raise(Errors.ConstructorClassPrivateField, this.state.startLoc); } const key = this.parsePrivateName(); member.key = key; return key; } this.parsePropertyName(member); return member.key; } parseClassStaticBlock( this: Parser, classBody: Undone<N.ClassBody>, member: Undone< N.StaticBlock & { decorators?: N.Decorator[]; } >, ) { // Start a new lexical scope this.scope.enter( ScopeFlag.CLASS | ScopeFlag.STATIC_BLOCK | ScopeFlag.SUPER, ); // Start a new scope with regard to loop labels const oldLabels = this.state.labels; this.state.labels = []; // ClassStaticBlockStatementList: // StatementList[~Yield, ~Await, ~Return] opt this.prodParam.enter(ParamKind.PARAM); const body: N.Statement[] = (member.body = []); this.parseBlockOrModuleBlockBody(body, undefined, false, tt.braceR); this.prodParam.exit(); this.scope.exit(); this.state.labels = oldLabels; classBody.body.push(this.finishNode<N.StaticBlock>(member, "StaticBlock")); if (member.decorators?.length) { this.raise(Errors.DecoratorStaticBlock, member); } } pushClassProperty( this: Parser, classBody: Undone<N.ClassBody>, prop: Undone<N.ClassProperty>, ) { if (!prop.computed && this.nameIsConstructor(prop.key)) { // Non-computed field, which is either an identifier named "constructor" // or a string literal named "constructor" this.raise(Errors.ConstructorClassField, prop.key); } classBody.body.push(this.parseClassProperty(prop)); } pushClassPrivateProperty( this: Parser, classBody: Undone<N.ClassBody>, prop: Undone<N.ClassPrivateProperty>, ) { const node = this.parseClassPrivateProperty(prop); classBody.body.push(node); this.classScope.declarePrivateName( this.getPrivateNameSV(node.key), ClassElementType.OTHER, node.key.start!, ); } pushClassAccessorProperty( this: Parser, classBody: Undone<N.ClassBody>, prop: Undone<N.ClassAccessorProperty>, isPrivate: boolean, ) { if (!isPrivate && !prop.computed && this.nameIsConstructor(prop.key)) { // Non-computed field, which is either an identifier named "constructor" // or a string literal named "constructor" this.raise(Errors.ConstructorClassField, prop.key); } const node = this.parseClassAccessorProperty(prop); classBody.body.push(node); if (isPrivate) { this.classScope.declarePrivateName( this.getPrivateNameSV(node.key as N.PrivateName), ClassElementType.OTHER, node.key.start!, ); } } pushClassMethod( this: Parser, classBody: Undone<N.ClassBody>, method: Undone<N.ClassMethod>, isGenerator: boolean, isAsync: boolean, isConstructor: boolean, allowsDirectSuper: boolean, ): void { classBody.body.push( this.parseMethod( method, isGenerator, isAsync, isConstructor, allowsDirectSuper, "ClassMethod", true, ), ); } pushClassPrivateMethod( this: Parser, classBody: Undone<N.ClassBody>, method: Undone<N.ClassPrivateMethod>, isGenerator: boolean, isAsync: boolean, ): void { const node = this.parseMethod( method, isGenerator, isAsync, /* isConstructor */ false, false, "ClassPrivateMethod", true, ); classBody.body.push(node); const kind = node.kind === "get" ? node.static ? ClassElementType.STATIC_GETTER : ClassElementType.INSTANCE_GETTER : node.kind === "set" ? node.static ? ClassElementType.STATIC_SETTER : ClassElementType.INSTANCE_SETTER : ClassElementType.OTHER; this.declareClassPrivateMethodInScope(node, kind); } declareClassPrivateMethodInScope( node: Undone<N.ClassPrivateMethod | N.TSDeclareMethod>, kind: number, ) { this.classScope.declarePrivateName( this.getPrivateNameSV(node.key as N.PrivateName), kind, node.key.start!, ); } // Overridden in typescript.js parsePostMemberNameModifiers( // eslint-disable-next-line @typescript-eslint/no-unused-vars methodOrProp: Undone<N.ClassMethod | N.ClassProperty>, ): void {} // https://tc39.es/ecma262/#prod-FieldDefinition parseClassPrivateProperty( this: Parser, node: Undone<N.ClassPrivateProperty>, ): N.ClassPrivateProperty { this.parseInitializer(node); this.semicolon(); return this.finishNode(node, "ClassPrivateProperty"); } // https://tc39.es/ecma262/#prod-FieldDefinition parseClassProperty( this: Parser, node: Undone<N.ClassProperty>, ): N.ClassProperty { this.parseInitializer(node); this.semicolon(); return this.finishNode(node, "ClassProperty"); } parseClassAccessorProperty( this: Parser, node: Undone<N.ClassAccessorProperty>, ): N.ClassAccessorProperty { this.parseInitializer(node); this.semicolon(); return this.finishNode(node, "ClassAccessorProperty"); } // https://tc39.es/ecma262/#prod-Initializer parseInitializer( this: Parser, node: Undone< N.ClassProperty | N.ClassPrivateProperty | N.ClassAccessorProperty >, ): void { this.scope.enter(ScopeFlag.CLASS | ScopeFlag.SUPER); this.expressionScope.enter(newExpressionScope()); this.prodParam.enter(ParamKind.PARAM); node.value = this.eat(tt.eq) ? this.parseMaybeAssignAllowIn() : null; this.expressionScope.exit(); this.prodParam.exit(); this.scope.exit(); } parseClassId( node: Undone<N.Class>, isStatement: boolean, optionalId?: boolean | null, bindingType: BindingFlag = BindingFlag.TYPE_CLASS, ): void { if (tokenIsIdentifier(this.state.type)) { node.id = this.parseIdentifier(); if (isStatement) { this.declareNameFromIdentifier(node.id, bindingType); } } else { if (optionalId || !isStatement) { node.id = null; } else { throw this.raise(Errors.MissingClassName, this.state.startLoc); } } } // https://tc39.es/ecma262/#prod-ClassHeritage parseClassSuper(this: Parser, node: Undone<N.Class>): void { if (this.eat(tt._extends)) { this.state.canStartArrow = false; node.superClass = this.parseExprSubscripts(); } else { node.superClass = null; } } // Parses module export declaration. // https://tc39.es/ecma262/#prod-ExportDeclaration parseExport( this: Parser, node: Undone< | N.ExportDefaultDeclaration | N.ExportAllDeclaration | N.ExportNamedDeclaration >, decorators: N.Decorator[] | null | undefined, ): N.AnyExport { const maybeDefaultIdentifier = this.parseMaybeImportPhase( node, /* isExport */ true, ); const hasDefault = this.maybeParseExportDefaultSpecifier( node, maybeDefaultIdentifier, ); const parseAfterDefault = !hasDefault || this.eat(tt.comma); const hasStar = parseAfterDefault && this.eatExportStar(node); const hasNamespace = hasStar && this.maybeParseExportNamespaceSpecifier(node); const parseAfterNamespace = parseAfterDefault && (!hasNamespace || this.eat(tt.comma)); const isFromRequired = hasDefault || hasStar; if (hasStar && !hasNamespace) { if (hasDefault) this.unexpected(); if (decorators) { throw this.raise(Errors.UnsupportedDecoratorExport, node); } this.parseExportFrom(node, true); this.sawUnambiguousESM = true; return this.finishNode(node, "ExportAllDeclaration"); } const hasSpecifiers = this.maybeParseExportNamedSpecifiers(node); if (hasDefault && parseAfterDefault && !hasStar && !hasSpecifiers) { this.unexpected(null, tt.braceL); } if (hasNamespace && parseAfterNamespace) { this.unexpected(null, tt._from); } let hasDeclaration; if (isFromRequired || hasSpecifiers) { hasDeclaration = false; if (decorators) { throw this.raise(Errors.UnsupportedDecoratorExport, node); } this.parseExportFrom(node, isFromRequired); } else { hasDeclaration = this.maybeParseExportDeclaration( node as Undone<N.ExportNamedDeclaration>, ); } if (isFromRequired || hasSpecifiers || hasDeclaration) { const node2 = node as Undone<N.ExportNamedDeclaration>; this.checkExport(node2, true, false, !!node2.source); if (node2.declaration?.type === "ClassDeclaration") { this.maybeTakeDecorators(decorators, node2.declaration, node2); } else if (decorators) { throw this.raise(Errors.UnsupportedDecoratorExport, node); } this.sawUnambiguousESM = true; return this.finishNode(node2, "ExportNamedDeclaration"); } if (this.eat(tt._default)) { const node2 = node as Undone<N.ExportDefaultDeclaration>; // export default ... const decl = this.parseExportDefaultExpression(); node2.declaration = decl; if (decl.type === "ClassDeclaration") { this.maybeTakeDecorators(decorators, decl, node2); } else if (decorators) { throw this.raise(Errors.UnsupportedDecoratorExport, node); } this.checkExport(node2, true, true); this.sawUnambiguousESM = true; return this.finishNode(node2, "ExportDefaultDeclaration"); } throw this.unexpected(null, tt.braceL); } eatExportStar( _: Undone<N.Node>, ): _ is Undone<N.ExportNamedDeclaration | N.ExportAllDeclaration> { return this.eat(tt.star); } maybeParseExportDefaultSpecifier( node: Undone< | N.ExportDefaultDeclaration | N.ExportAllDeclaration | N.ExportNamedDeclaration >, maybeDefaultIdentifier: N.Identifier | null, ): node is Undone<N.ExportNamedDeclaration> { if (maybeDefaultIdentifier || this.isExportDefaultSpecifier()) { // export defaultObj ... this.expectPlugin("exportDefaultFrom", maybeDefaultIdentifier?.start); const id = maybeDefaultIdentifier || this.parseIdentifier(true); const specifier = this.startNodeAtNode<N.ExportDefaultSpecifier>(id); specifier.exported = id; (node as Undone<N.ExportNamedDeclaration>).specifiers = [ this.finishNode(specifier, "ExportDefaultSpecifier"), ]; return true; } return false; } maybeParseExportNamespaceSpecifier( node: Undone<N.ExportNamedDeclaration | N.ExportAllDeclaration>, ): node is Undone<N.ExportNamedDeclaration> { if (this.isContextual(tt._as)) { (node as Undone<N.ExportNamedDeclaration>).specifiers ??= []; const specifier = this.startNodeAt<N.ExportNamespaceSpecifier>( this.state.lastTokStartLoc!, ); this.next(); specifier.exported = this.parseModuleExportName(); (node as Undone<N.ExportNamedDeclaration>).specifiers.push( this.finishNode(specifier, "ExportNamespaceSpecifier"), ); return true; } return false; } maybeParseExportNamedSpecifiers( node: Undone<N.Node>, ): node is Undone<N.ExportNamedDeclaration> { if (this.match(tt.braceL)) { const node2 = node as Undone<N.ExportNamedDeclaration>; if (!node2.specifiers) node2.specifiers = []; const isTypeExport = node2.exportKind === "type"; node2.specifiers.push(...this.parseExportSpecifiers(isTypeExport)); node2.source = null; node2.attributes = []; node2.declaration = null; return true; } return false; } maybeParseExportDeclaration( this: Parser, node: Undone<N.ExportNamedDeclaration>, ): boolean { if (this.shouldParseExportDeclaration()) { node.specifiers = []; node.source = null; node.attributes = []; node.declaration = this.parseExportDeclaration(node); return true; } return false; } isAsyncFunction(): boolean { if (!this.isContextual(tt._async)) return false; const next = this.nextTokenInLineStart(); return this.isUnparsedContextual(next, "function"); } parseExportDefaultExpression( this: Parser, ): N.ExportDefaultDeclaration["declaration"] | N.EnumDeclaration { const expr = this.startNode(); if (this.match(tt._function)) { this.next(); return this.parseFunction( expr, ParseFunctionFlag.Declaration | ParseFunctionFlag.NullableId, ); } else if (this.isAsyncFunction()) { this.next(); // eat 'async' this.next(); // eat 'function' return this.parseFunction( expr, ParseFunctionFlag.Declaration | ParseFunctionFlag.NullableId | ParseFunctionFlag.Async, ); } if (this.match(tt._class)) { return this.parseClass(expr, true, true); } if (this.match(tt.at)) { return this.parseClass( this.maybeTakeDecorators( this.parseDecorators(false), this.startNode<N.ClassDeclaration>(), ), true, true, ); } if ( this.match(tt._const) || this.match(tt._var) || this.isLet() || this.isUsing() || this.isAwaitUsing() ) { throw this.raise(Errors.UnsupportedDefaultExport, this.state.startLoc); } const res = this.parseMaybeAssignAllowIn(); this.semicolon(); return res; } // https://tc39.es/ecma262/#prod-ExportDeclaration parseExportDeclaration( this: Parser, // eslint-disable-next-line @typescript-eslint/no-unused-vars node: Undone<N.ExportNamedDeclaration>, ): N.ExportNamedDeclaration["declaration"] | undefined { if (this.match(tt._class)) { const node = this.parseClass( this.startNode<N.ClassDeclaration>(), true, false, ); return node; } return this.parseStatementListItem() as N.ExportNamedDeclaration["declaration"]; } isExportDefaultSpecifier(): boolean { const { type } = this.state; if (tokenIsIdentifier(type)) { if ((type === tt._async && !this.state.containsEsc) || type === tt._let) { return false; } if ( (type === tt._type || type === tt._interface) && !this.state.containsEsc ) { // If we see any variable name other than `from` after `type` keyword, // we consider it as flow/typescript type exports // note that this approach may fail on some pedantic cases // export type from = number const next = this.nextTokenStart(); const nextChar = this.input.charCodeAt(next); if ( nextChar === charCodes.leftCurlyBrace || (this.chStartsBindingIdentifier(nextChar, next) && !this.input.startsWith("from", next)) ) { this.expectOnePlugin(["flow", "typescript"]); return false; } } } else if (!this.match(tt._default)) { return false; } const next = this.nextTokenStart(); const hasFrom = this.isUnparsedContextual(next, "from"); if ( this.input.charCodeAt(next) === charCodes.comma || (tokenIsIdentifier(this.state.type) && hasFrom) ) { return true; } // lookahead again when `export default from` is seen if (this.match(tt._default) && hasFrom) { const nextAfterFrom = this.input.charCodeAt( this.nextTokenStartSince(next + 4), ); return ( nextAfterFrom === charCodes.quotationMark || nextAfterFrom === charCodes.apostrophe ); } return false; } parseExportFrom( this: Parser, node: Undone<N.ExportNamedDeclaration | N.ExportAllDeclaration>, expect?: boolean, ): void { if (this.eatContextual(tt._from)) { node.source = this.parseImportSource(); this.checkExport(node); this.maybeParseImportAttributes(node); } else if (expect) { this.unexpected(); } this.semicolon(); } shouldParseExportDeclaration(): boolean { const { type } = this.state; if (type === tt.at) { this.expectOnePlugin(["decorators", "decorators-legacy"]); if (this.hasPlugin("decorators")) { return true; } } if (this.isUsing()) { this.raise(Errors.UsingDeclarationExport, this.state.startLoc); return true; } if (this.isAwaitUsing()) { this.raise(Errors.UsingDeclarationExport, this.state.startLoc); return true; } return ( type === tt._var || type === tt._const || type === tt._function || type === tt._class || this.isLet() || this.isAsyncFunction() ); } checkExport( node: Undone< | N.ExportNamedDeclaration | N.ExportAllDeclaration | N.ExportDefaultDeclaration >, checkNames?: boolean, isDefault?: boolean, isFrom?: boolean, ): void { if (checkNames) { // Check for duplicate exports if (isDefault) { // Default exports this.checkDuplicateExports(node, "default"); if (this.hasPlugin("exportDefaultFrom")) { const declaration = (node as any as N.ExportDefaultDeclaration) .declaration; if ( declaration.type === "Identifier" && declaration.name === "from" && // eslint-disable-next-line @typescript-eslint/no-confusing-non-null-assertion declaration.end! - declaration.start! === 4 && // does not contain escape !declaration.extra?.parenthesized ) { this.raise(Errors.ExportDefaultFromAsIdentifier, declaration); } } // @ts-expect-error node.specifiers may not exist } else if (node.specifiers?.length) { // Named exports // @ts-expect-error node.specifiers may not exist for (const specifier of node.specifiers) { const { exported } = specifier; const exportName = exported.type === "Identifier" ? exported.name : exported.value; this.checkDuplicateExports(specifier, exportName); if (!isFrom && specifier.local) { const { local } = specifier; if (local.type !== "Identifier") { this.raise(Errors.ExportBindingIsString, specifier, { localName: local.value, exportName, }); } else { // check for keywords used as local names this.checkReservedWord(local.name, local.start, true, false); // check if export is defined this.scope.checkLocalExport(local); } } } } else if ((node as Undone<N.ExportNamedDeclaration>).declaration) { const decl = (node as Undone<N.ExportNamedDeclaration>).declaration!; // Exported declarations if ( decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" ) { const { id } = decl; if (!id) throw new Error("Assertion failure"); this.checkDuplicateExports(node, id.name); } else if (decl.type === "VariableDeclaration") { for (const declaration of decl.declarations) { this.checkDeclaration(declaration.id); } } } } } checkDeclaration( node: N.Identifier | N.Pattern | N.RestElement | N.ObjectProperty, ): void { if (node.type === "Identifier") { this.checkDuplicateExports(node, node.name); } else if (node.type === "ObjectPattern") { for (const prop of node.properties) { this.checkDeclaration(prop); } } else if (node.type === "ArrayPattern") { for (const elem of node.elements) { if (elem) { this.checkDeclaration(elem as N.Pattern | N.Identifier); } } } else if (node.type === "ObjectProperty") { this.checkDeclaration(node.value as N.Pattern | N.Identifier); } else if (node.type === "RestElement") { this.checkDeclaration(node.argument as N.Pattern | N.Identifier); } else if (node.type === "AssignmentPattern") { this.checkDeclaration( node.left as N.AssignmentPattern["left"] & (N.Pattern | N.Identifier), ); } } checkDuplicateExports( node: Undone< | N.Identifier | N.StringLiteral | N.ExportNamedDeclaration | N.ExportSpecifier | N.ExportDefaultSpecifier >, exportName: string, ): void { if (this.exportedIdentifiers.has(exportName)) { if (exportName === "default") { this.raise(Errors.DuplicateDefaultExport, node); } else { this.raise(Errors.DuplicateExport, node, { exportName }); } } this.exportedIdentifiers.add(exportName); } // Parses a comma-separated list of module exports. parseExportSpecifiers(isInTypeExport: boolean): N.ExportSpecifier[] { const nodes = []; let first = true; // export { x, y as z } [from '...'] this.expect(tt.braceL); while (!this.eat(tt.braceR)) { if (first) { first = false; } else { this.expect(tt.comma); if (this.eat(tt.braceR)) break; } const isMaybeTypeOnly = this.isContextual(tt._type); const isString = this.match(tt.string); const node = this.startNode<N.ExportSpecifier>(); node.local = this.parseModuleExportName(); nodes.push( this.parseExportSpecifier( node, isString, isInTypeExport, isMaybeTypeOnly, ), ); } return nodes; } parseExportSpecifier( node: any, isString: boolean, /* eslint-disable @typescript-eslint/no-unused-vars -- used in TypeScript parser */ isInTypeExport: boolean, isMaybeTypeOnly /* eslint-enable @typescript-eslint/no-unused-vars */ : boolean, ): N.ExportSpecifier { if (this.eatContextual(tt._as)) { node.exported = this.parseModuleExportName(); } else if (isString) { node.exported = this.cloneStringLiteral(node.local); } else if (!node.exported) { node.exported = this.cloneIdentifier(node.local); } return this.finishNode<N.ExportSpecifier>(node, "ExportSpecifier"); } // https://tc39.es/ecma262/#prod-ModuleExportName parseModuleExportName(): N.StringLiteral | N.Identifier { if (this.match(tt.string)) { const result = this.parseStringLiteral(this.state.value); const surrogate = loneSurrogate.exec(result.value); if (surrogate) { this.raise(Errors.ModuleExportNameHasLoneSurrogate, result, { surrogateCharCode: surrogate[0].charCodeAt(0), }); } return result; } return this.parseIdentifier(true); } checkImportPhase(node: Undone<N.ImportDeclaration>) { const { specifiers } = node; const singleBindingType = specifiers.length === 1 ? specifiers[0].type : null; if (node.phase === "source") { if (singleBindingType !== "ImportDefaultSpecifier") { this.raise(Errors.SourcePhaseImportRequiresDefault, specifiers[0]); } } else if (node.phase === "defer") { if (singleBindingType !== "ImportNamespaceSpecifier") { this.raise(Errors.DeferImportRequiresNamespace, specifiers[0]); } } } isPotentialImportPhase(isExport: boolean): boolean { if (isExport) return false; return this.isContextual(tt._source) || this.isContextual(tt._defer); } applyImportPhase( node: Undone<N.ImportDeclaration | N.ExportNamedDeclaration>, isExport: boolean, phase: string | null, loc?: number, ): void { if (isExport) { if (!process.env.IS_PUBLISH) { if (phase === "source") { throw new Error( `Assertion failure: export declarations do not support the '${phase}' phase.`, ); } } return; } if (phase === "source") { this.expectPlugin("sourcePhaseImports", loc); (node as N.ImportDeclaration).phase = "source"; } else if (phase === "defer") { this.expectPlugin("deferredImportEvaluation", loc); (node as N.ImportDeclaration).phase = "defer"; } else if (this.hasPlugin("sourcePhaseImports")) { (node as N.ImportDeclaration).phase = null; } } /* * Parse `source` in `import source x from "x"`, disambiguating * `import source from "x"` and `import source from from "x"`. * * This function might return an identifier representing the `source` * if it eats `source` and then discovers that it was the default import * binding and not the import reflection. * * This function is also used to parse `import type` and `import typeof` * in the TS and Flow plugins, and for parsing `import defer`. */ parseMaybeImportPhase( node: Undone<N.ImportDeclaration | N.TSImportEqualsDeclaration>, isExport: boolean, ): N.Identifier | null { if (!this.isPotentialImportPhase(isExport)) { this.applyImportPhase( node as Undone<N.ImportDeclaration>, isExport, null, ); return null; } const phaseIdentifier = this.startNode<N.Identifier>(); const phaseIdentifierName = this.parseIdentifierName(true); const { type } = this.state; const isImportPhase = tokenIsKeywordOrIdentifier(type) ? // OK: import <phase> x from "foo"; // OK: import <phase> from from "foo"; // NO: import <phase> from "foo"; // NO: import <phase> from 'foo'; // With the module declarations proposals, we will need further disambiguation // for `import module from from;`. type !== tt._from || this.lookaheadCharCode() === charCodes.lowercaseF : // OK: import <phase> { x } from "foo"; // OK: import <phase> x from "foo"; // OK: import <phase> * as T from "foo"; // NO: import <phase> from "foo"; // OK: import <phase> "foo"; // The last one is invalid, we will continue parsing and throw // an error later type !== tt.comma; if (isImportPhase) { this.applyImportPhase( node as Undone<N.ImportDeclaration>, isExport, phaseIdentifierName, phaseIdentifier.start!, ); return null; } else { this.applyImportPhase( node as Undone<N.ImportDeclaration>, isExport, null, ); // `<phase>` is a default binding, return it to the main import declaration parser return this.createIdentifier(phaseIdentifier, phaseIdentifierName); } } isPrecedingIdImportPhase( // eslint-disable-next-line @typescript-eslint/no-unused-vars phase: string, ) { const { type } = this.state; return tokenIsIdentifier(type) ? // OK: import <phase> x from "foo"; // OK: import <phase> from from "foo"; // NO: import <phase> from "foo"; // NO: import <phase> from 'foo'; // With the module declarations proposals, we will need further disambiguation // for `import module from from;`. type !== tt._from || this.lookaheadCharCode() === charCodes.lowercaseF : // OK: import <phase> { x } from "foo"; // OK: import <phase> x from "foo"; // OK: import <phase> * as T from "foo"; // NO: import <phase> from "foo"; // OK: import <phase> "foo"; // The last one is invalid, we will continue parsing and throw // an error later type !== tt.comma; } // Parses import declaration. // https://tc39.es/ecma262/#prod-ImportDeclaration parseImport(this: Parser, node: Undone<N.ImportDeclaration>): N.AnyImport { if (this.match(tt.string)) { // import '...' return this.parseImportSourceAndAttributes(node); } return this.parseImportSpecifiersAndAfter( node, this.parseMaybeImportPhase(node, /* isExport */ false), ); } parseImportSpecifiersAndAfter( this: Parser, node: Undone<N.ImportDeclaration>, maybeDefaultIdentifier: N.Identifier | null, ): N.AnyImport { node.specifiers = []; // check if we have a default import like // import React from "react"; const hasDefault = this.maybeParseDefaultImportSpecifier( node, maybeDefaultIdentifier, ); /* we are checking if we do not have a default import, then it is obvious that we need named imports * import { get } from "axios"; * but if we do have a default import * we need to check if we have a comma after that and * that is where this `|| this.eat` condition comes into play */ const parseNext = !hasDefault || this.eat(tt.comma); // if we do have to parse the next set of specifiers, we first check for star imports // import React, * from "react"; const hasStar = parseNext && this.maybeParseStarImportSpecifier(node); // now we check if we need to parse the next imports // but only if they are not importing * (everything) if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node); this.expectContextual(tt._from); return this.parseImportSourceAndAttributes(node); } parseImportSourceAndAttributes( this: Parser, node: Undone<N.ImportDeclaration>, ): N.AnyImport { node.specifiers ??= []; node.source = this.parseImportSource(); this.maybeParseImportAttributes(node); this.checkImportPhase(node); this.semicolon(); this.sawUnambiguousESM = true; return this.finishNode(node, "ImportDeclaration"); } parseImportSource(this: Parser): N.StringLiteral { if (!this.match(tt.string)) this.unexpected(); return this.parseExprAtom() as N.StringLiteral; } parseImportSpecifierLocal< T extends N.ImportSpecifier | N.ImportDefaultSpecifier | N.ImportNamespaceSpecifier, >( node: Undone<N.ImportDeclaration>, specifier: Undone<T>, type: T["type"], ): void { specifier.local = this.parseIdentifier(); node.specifiers.push(this.finishImportSpecifier(specifier, type)); } finishImportSpecifier< T extends N.ImportSpecifier | N.ImportDefaultSpecifier | N.ImportNamespaceSpecifier, >( specifier: Undone<T>, type: T["type"], bindingType: BindingFlag = BindingFlag.TYPE_LEXICAL, ) { this.checkLVal(specifier.local, { type }, bindingType); return this.finishNode(specifier, type); } /** * parse assert entries * * @see {@link https://tc39.es/proposal-import-attributes/#prod-WithEntries WithEntries} */ parseImportAttributes(): N.ImportAttribute[] { this.expect(tt.braceL); const attrs = []; const attrNames = new Set(); do { if (this.match(tt.braceR)) { break; } const node = this.startNode<N.ImportAttribute>(); // parse AssertionKey : IdentifierName, StringLiteral const keyName = this.state.value; // check if we already have an entry for an attribute // if a duplicate entry is found, throw an error // for now this logic will come into play only when someone declares `type` twice if (attrNames.has(keyName)) { this.raise( Errors.ModuleAttributesWithDuplicateKeys, this.state.startLoc, { key: keyName, }, ); } attrNames.add(keyName); if (this.match(tt.string)) { node.key = this.parseStringLiteral(keyName); } else { node.key = this.parseIdentifier(true); } this.expect(tt.colon); if (!this.match(tt.string)) { throw this.raise( Errors.ModuleAttributeInvalidValue, this.state.startLoc, ); } node.value = this.parseStringLiteral(this.state.value); attrs.push(this.finishNode(node, "ImportAttribute")); } while (this.eat(tt.comma)); this.expect(tt.braceR); return attrs; } maybeParseImportAttributes( node: Undone< N.ImportDeclaration | N.ExportNamedDeclaration | N.ExportAllDeclaration >, ) { let attributes: N.ImportAttribute[]; // https://tc39.es/proposal-import-attributes/#prod-WithClause if (this.match(tt._with)) { if ( this.hasPrecedingLineBreak() && this.lookaheadCharCode() === charCodes.leftParenthesis ) { // This will be parsed as a with statement, and we will throw a // better error about it not being supported in strict mode. return; } this.next(); // eat `with` attributes = this.parseImportAttributes(); } else { attributes = []; } node.attributes = attributes; } maybeParseDefaultImportSpecifier( node: Undone<N.ImportDeclaration>, maybeDefaultIdentifier: N.Identifier | null, ): boolean { // import defaultObj, { x, y as z } from '...' if (maybeDefaultIdentifier) { const specifier = this.startNodeAtNode<N.ImportDefaultSpecifier>( maybeDefaultIdentifier, ); specifier.local = maybeDefaultIdentifier; node.specifiers.push( this.finishImportSpecifier(specifier, "ImportDefaultSpecifier"), ); return true; } else if ( // We allow keywords, and parseImportSpecifierLocal will report a recoverable error tokenIsKeywordOrIdentifier(this.state.type) ) { this.parseImportSpecifierLocal( node, this.startNode<N.ImportDefaultSpecifier>(), "ImportDefaultSpecifier", ); return true; } return false; } maybeParseStarImportSpecifier(node: Undone<N.ImportDeclaration>): boolean { if (this.match(tt.star)) { const specifier = this.startNode<N.ImportNamespaceSpecifier>(); this.next(); this.expectContextual(tt._as); this.parseImportSpecifierLocal( node, specifier, "ImportNamespaceSpecifier", ); return true; } return false; } parseNamedImportSpecifiers(node: Undone<N.ImportDeclaration>) { let first = true; this.expect(tt.braceL); while (!this.eat(tt.braceR)) { if (first) { first = false; } else { // Detect an attempt to deep destructure if (this.eat(tt.colon)) { throw this.raise(Errors.DestructureNamedImport, this.state.startLoc); } this.expect(tt.comma); if (this.eat(tt.braceR)) break; } const specifier = this.startNode<N.ImportSpecifier>(); const importedIsString = this.match(tt.string); const isMaybeTypeOnly = this.isContextual(tt._type); specifier.imported = this.parseModuleExportName(); const importSpecifier = this.parseImportSpecifier( specifier, importedIsString, node.importKind === "type" || node.importKind === "typeof", isMaybeTypeOnly, undefined, ); node.specifiers.push(importSpecifier); } } // https://tc39.es/ecma262/#prod-ImportSpecifier parseImportSpecifier( specifier: Undone<N.ImportSpecifier>, importedIsString: boolean, /* used in TypeScript and Flow parser */ isInTypeOnlyImport: boolean, isMaybeTypeOnly: boolean, bindingType: BindingFlag | undefined, ): N.ImportSpecifier { if (this.eatContextual(tt._as)) { specifier.local = this.parseIdentifier(); } else { const { imported } = specifier; if (importedIsString) { throw this.raise(Errors.ImportBindingIsString, specifier, { importName: (imported as N.StringLiteral).value, }); } this.checkReservedWord( (imported as N.Identifier).name, specifier.start!, true, true, ); if (!specifier.local) { specifier.local = this.cloneIdentifier(imported as N.Identifier); } } return this.finishImportSpecifier( specifier, "ImportSpecifier", bindingType, ); } // This is used in flow and typescript plugin // Determine whether a parameter is a this param isThisParam( param: N.Pattern | N.Identifier | N.TSParameterProperty | N.RestElement, ): boolean { return param.type === "Identifier" && param.name === "this"; } }