From 3e2cbbf1135c745bba00d9bff1e1eb48cc805df2 Mon Sep 17 00:00:00 2001 From: Solo-steven Date: Sun, 27 Oct 2024 14:13:24 +0800 Subject: [PATCH] feat: refactor the file structure of parser --- web-infras/parser/src/dev.ts | 4 +- web-infras/parser/src/index.ts | 6 +- web-infras/parser/src/lexer/lexer.ts | 2 + web-infras/parser/src/parser/config.ts | 1 + web-infras/parser/src/parser/context.ts | 750 ++ web-infras/parser/src/parser/index.ts | 73 +- web-infras/parser/src/parser/js/const.ts | 22 + .../parser/src/parser/js/declaration.ts | 819 ++ web-infras/parser/src/parser/js/expression.ts | 1276 +++ web-infras/parser/src/parser/js/index.ts | 6 + web-infras/parser/src/parser/js/literal.ts | 1587 ++++ web-infras/parser/src/parser/js/module.ts | 565 ++ web-infras/parser/src/parser/js/pattern.ts | 710 ++ web-infras/parser/src/parser/js/statement.ts | 729 ++ web-infras/parser/src/parser/jsx/index.ts | 413 + web-infras/parser/src/parser/parser.ts | 7504 ----------------- .../parser/src/parser/scope/arrowExprScope.ts | 2 + .../parser/scope/lexicalScope/lexicalScope.ts | 1 + .../src/parser/scope/strictModeScope.ts | 2 + .../parser/scope/symbolScope/symbolScope.ts | 3 + web-infras/parser/src/parser/ts/index.ts | 927 ++ web-infras/parser/src/parser/type.ts | 153 +- .../decorator-before-export/output.json | 57 +- .../output.json | 57 +- .../decorator-before-export/output.json | 30 +- .../class-decorator-call-expr/output.json | 119 +- .../class-decorator-member-expr/output.json | 88 +- .../decorators/class-decorator/output.json | 30 +- 28 files changed, 8287 insertions(+), 7649 deletions(-) create mode 100644 web-infras/parser/src/parser/context.ts create mode 100644 web-infras/parser/src/parser/js/const.ts create mode 100644 web-infras/parser/src/parser/js/declaration.ts create mode 100644 web-infras/parser/src/parser/js/expression.ts create mode 100644 web-infras/parser/src/parser/js/index.ts create mode 100644 web-infras/parser/src/parser/js/literal.ts create mode 100644 web-infras/parser/src/parser/js/module.ts create mode 100644 web-infras/parser/src/parser/js/pattern.ts create mode 100644 web-infras/parser/src/parser/js/statement.ts create mode 100644 web-infras/parser/src/parser/jsx/index.ts delete mode 100644 web-infras/parser/src/parser/parser.ts create mode 100644 web-infras/parser/src/parser/ts/index.ts diff --git a/web-infras/parser/src/dev.ts b/web-infras/parser/src/dev.ts index b026e735..4eed06f4 100644 --- a/web-infras/parser/src/dev.ts +++ b/web-infras/parser/src/dev.ts @@ -4,7 +4,7 @@ import { parse } from "@/src/index"; import { transformSyntaxKindToLiteral } from "../tests/parserRunner/helpers/transform"; import fs from "fs"; import path from "path"; -import { ParserPlugin } from "./parser/config"; +// import { ParserPlugin } from "./parser/config"; const code = fs.readFileSync(path.join(__dirname, "test.ts")).toString(); // console.log(code); // const writePath = path.join(__dirname, "test.json"); @@ -50,7 +50,7 @@ function printLexer(code: string) { ); } function printParser(code: string) { - const ast = parse(code, { sourceType: "module", plugins: [ParserPlugin.TypeScript] }); + const ast = parse(code, { sourceType: "module", plugins: [] }); transformSyntaxKindToLiteral(ast); const astJsonString = JSON.stringify(ast, null, 4); fs.writeFileSync("./test.json", astJsonString); diff --git a/web-infras/parser/src/index.ts b/web-infras/parser/src/index.ts index 11fb689f..5a7c97b3 100644 --- a/web-infras/parser/src/index.ts +++ b/web-infras/parser/src/index.ts @@ -1,4 +1,4 @@ -import { createParser } from "@/src/parser"; +import { Parser } from "@/src/parser"; import { createLexer } from "@/src/lexer"; import { Token } from "@/src/lexer/type"; import { SyntaxKinds } from "web-infra-common"; @@ -7,8 +7,8 @@ import { createErrorHandler } from "./errorHandler"; export function parse(code: string, config?: ParserUserConfig) { const errorHandler = createErrorHandler(code); - const parser = createParser(code, errorHandler, config); - const program = parser.parse(); + const parser = new Parser(code, errorHandler, config); + const program = parser.parseProgram(); if (errorHandler.haveError()) { throw new Error(errorHandler.formatErrorString()); } diff --git a/web-infras/parser/src/lexer/lexer.ts b/web-infras/parser/src/lexer/lexer.ts index 8eadd496..c15e831c 100644 --- a/web-infras/parser/src/lexer/lexer.ts +++ b/web-infras/parser/src/lexer/lexer.ts @@ -23,6 +23,8 @@ const KeywordLiteralSet = new Set([ ...LexicalLiteral.UndefinbedLiteral, ]); +export type Lexer = ReturnType; + export function createLexer(code: string) { /** * state and context for lexer, diff --git a/web-infras/parser/src/parser/config.ts b/web-infras/parser/src/parser/config.ts index 4dfc04b5..12e9024c 100644 --- a/web-infras/parser/src/parser/config.ts +++ b/web-infras/parser/src/parser/config.ts @@ -29,6 +29,7 @@ const defaultConfig: ParserConfig = { allowUndeclaredExports: false, plugins: [], }; + export function getConfigFromUserInput(config?: ParserUserConfig): ParserConfig { return { ...defaultConfig, diff --git a/web-infras/parser/src/parser/context.ts b/web-infras/parser/src/parser/context.ts new file mode 100644 index 00000000..12d790c6 --- /dev/null +++ b/web-infras/parser/src/parser/context.ts @@ -0,0 +1,750 @@ +import { + ModuleItem, + PropertyName, + SyntaxKinds, + SourcePosition, + SytaxKindsMapLexicalLiteral, + Decorator, +} from "web-infra-common"; +import { ExpectToken } from "./type"; +import { ErrorMessageMap } from "./error"; +import { ParserPlugin } from "./config"; +import { LookaheadToken } from "../lexer/type"; +import { AsyncArrowExpressionScope } from "./scope/arrowExprScope"; +import { StrictModeScope } from "./scope/strictModeScope"; +import { ExpressionScopeKind } from "./scope/type"; +import { ExportContext, PrivateNameDefKind } from "./scope/lexicalScope"; +import { FunctionSymbolScope, NonFunctionalSymbolType, SymbolType } from "./scope/symbolScope"; +import type { Parser } from "@/src/parser"; + +export interface Context { + maybeArrowStart: number; + isInType: boolean; + inOperatorStack: Array; + propertiesInitSet: Set; + propertiesProtoDuplicateSet: Set; + lastTokenIndexOfIfStmt: number; + cache: { + decorators: Decorator[] | null; + }; +} +/** + * Create context for parser + * @returns {Context} + */ +export function createContext(): Context { + return { + maybeArrowStart: -1, + isInType: false, + inOperatorStack: [], + propertiesInitSet: new Set(), + propertiesProtoDuplicateSet: new Set(), + lastTokenIndexOfIfStmt: -1, + cache: { + decorators: null, + }, + }; +} +/** + * Private API for parser, helper to require some plugin + */ +export function requirePlugin(this: Parser, ...plugins: Array): boolean { + for (const plugin of plugins) { + if (!this.config.plugins.includes(plugin)) { + return false; + } + } + return true; +} +/** + * Private API for parser, move to next token, skip comment and + * block comment token. + * @returns {SyntaxKinds} + */ +export function nextToken(this: Parser): SyntaxKinds { + this.lexer.nextToken(); + const token = this.lexer.getTokenKind(); + if (token === SyntaxKinds.Comment || token == SyntaxKinds.BlockComment) { + return this.nextToken(); + } + return token; +} +/** + * Private API for parser, get current token kind, skip comment + * and block comment token + * @returns {SyntaxKinds} + */ +export function getToken(this: Parser): SyntaxKinds { + const token = this.lexer.getTokenKind(); + if (token === SyntaxKinds.Comment || token == SyntaxKinds.BlockComment) { + return this.nextToken(); + } + return token; +} +/** + * Private API for parser, get current token's string value. + * @returns {string} + */ +export function getSourceValue(this: Parser): string { + return this.lexer.getSourceValue(); +} +/** + * Private API for parser, just wrapper of this.lexer, get start + * position of current token. + * @return {SourcePosition} + */ +export function getStartPosition(this: Parser): SourcePosition { + return this.lexer.getStartPosition(); +} +/** + * Private API for parser, just wrapper of this.lexer, get end + * position of current token. + * @return {SourcePosition} + */ +export function getEndPosition(this: Parser): SourcePosition { + return this.lexer.getEndPosition(); +} +/** + * Private API for parser, just wrapper of this.lexer, get end + * position of last token. + * @returns {SourcePosition} + */ +export function getLastTokenEndPositon(this: Parser): SourcePosition { + return this.lexer.getLastTokenEndPositon(); +} +/** + * Private API for parser, just wrapper of lookahead method + * of this.lexer. + * @returns + */ +export function lookahead(this: Parser): LookaheadToken { + return this.lexer.lookahead(); +} +/** + * Private API for parser, just wrapper of this.lexer method. + * @returns + */ +export function readRegex(this: Parser) { + return this.lexer.readRegex(); +} +/** + * Private API for parser, just wrapper of this.lexer method, check + * is a line terminator in the range of last token end and start + * of current token. + * @returns {boolean} + */ +export function getLineTerminatorFlag(this: Parser): boolean { + return this.lexer.getLineTerminatorFlag(); +} +/** + * Private API for parser, just wrapper of this.lexer methid, check + * is current token contain a unicode esc. + */ +export function getEscFlag(this: Parser): boolean { + return this.lexer.getEscFlag(); +} +/** + * Private API for parser, is current token kind match the + * given token kind ? + * @param {SyntaxKinds | Array} kind + * @returns {boolean} + */ +export function match(this: Parser, kind: SyntaxKinds | Array): boolean { + const currentToken = this.getToken(); + if (Array.isArray(kind)) { + const tokenSet = new Set(kind); + return tokenSet.has(currentToken); + } + return currentToken === kind; +} +/** + * Private API for check if current identifier is a given value, + * used when we need to detect a contextual keyword (like `async`). + * @param {string} value + * @returns {boolean} + */ +export function isContextKeyword(this: Parser, value: string): boolean { + if ( + this.getSourceValue() === value && + this.getToken() === SyntaxKinds.Identifier && + !this.lexer.getEscFlag() + ) { + return true; + } + return false; +} +/** + * Private API for parser, expect current token is one of given token(s), + * it not, it will create a unexpect error, if is one of given token, it will + * eat token and return `value`, `start`, `end` of token + * @param kind + * @param message + * @returns {ExpectToken} + */ +export function expect(this: Parser, kind: SyntaxKinds | Array): ExpectToken { + if (this.match(kind)) { + const metaData = { + value: this.getSourceValue(), + start: this.getStartPosition(), + end: this.getEndPosition(), + }; + this.nextToken(); + return metaData; + } + throw this.createUnexpectError(); +} +/** + * Private API for parser, expect current token is one of given token(s), + * it not, it will create a unexpect error, if is one of given token, it + * will NOT eat token. + * @param kind + * @param message + * @returns {void} + */ +export function expectButNotEat(this: Parser, kind: SyntaxKinds | Array): void { + if (this.match(kind)) { + return; + } + throw this.createUnexpectError(); +} +/** + * Private API for `shouldInsertSemi` and `isSoftInsertSemi`, + * test is there a semi or equal syntax, return three state + * - `SemiExisted`: there is a semi token. + * - `SemiInsertAble`: there is a equal to semi syntax. + * - `SemiNotExisted`: there is no semi or equal syntax, + * @returns + */ +export function isSemiInsertable(this: Parser) { + if (this.match(SyntaxKinds.SemiPunctuator)) { + return "SemiExisted"; + } + if (this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + return "SemiInsertAble"; + } + if (this.getLineTerminatorFlag()) { + return "SemiInsertAble"; + } + return "SemiNotExisted"; +} +/** + * Private API for most of insert semi check, if semi exist, eat token, + * pass if equal syntax exist, throw error if not existed semi or equal + * syntax. + * @returns + */ +export function shouldInsertSemi(this: Parser) { + const semiState = this.isSemiInsertable(); + switch (semiState) { + case "SemiExisted": + this.nextToken(); + return; + case "SemiInsertAble": + return; + case "SemiNotExisted": + // recoverable error + this.raiseError(ErrorMessageMap.missing_semicolon, this.getStartPosition()); + } +} +/** + * Private API for insert semi for three edge case + * - `DoWhileStatement` + * - `ReturnStatement` + * - `YeildExpression` + * @param {boolean} shouldEat - false whem used in yield expression. + * @returns + */ +export function isSoftInsertSemi(this: Parser, shouldEat: boolean = true) { + const semiState = this.isSemiInsertable(); + switch (semiState) { + case "SemiExisted": + if (shouldEat) { + this.nextToken(); + } + return true; + case "SemiInsertAble": + return true; + case "SemiNotExisted": + return false; + } +} +/** + * Create a Message error from parser's error map. + * @param {string} messsage + */ +export function createMessageError(this: Parser, messsage: string, position?: SourcePosition) { + if (position === undefined) position = this.getStartPosition(); + + return new Error(`[Syntax Error]: ${messsage} (${position.row}, ${position.col})`); +} +/** + * Create a error object with message tell developer that get a + * unexpect token. + * @returns {Error} + */ +export function createUnexpectError(this: Parser): Error { + const startPos = this.getStartPosition(); + return new Error( + `[Syntax Error]: Unexpect token ${SytaxKindsMapLexicalLiteral[this.getToken()]}(${startPos.row}, ${startPos.col}).`, + ); +} +/** + * Given that this parser is recurive decent parser, some + * export function must call with some start token, if export function call + * with unexecpt start token, it should throw this error. + * @param {Array} startTokens + * @returns {Error} + */ +export function createUnreachError(this: Parser, startTokens: Array = []): Error { + const start = this.getStartPosition(); + let message = `[Unreach Zone]: this piece of code should not be reach (${start.row}, ${start.col}), have a unexpect token ${this.getToken()} (${this.getSourceValue()}).`; + if (startTokens.length !== 0) { + message += " it should call with start token["; + for (const token of startTokens) { + message += `${token}, `; + } + message += "]"; + } + message += ", please report to developer."; + return new Error(message); +} +export function disAllowInOperaotr(this: Parser, parseCallback: () => T): T { + this.context.inOperatorStack.push(false); + const result = parseCallback(); + this.context.inOperatorStack.pop(); + return result; +} +/** + * Private API for parse api for expression, in ECMAscript, there is + * a syntax tranlation for in operator production rule. + * @param parseCallback + * @returns + */ +export function allowInOperaotr(this: Parser, parseCallback: () => T) { + this.context.inOperatorStack.push(true); + const result = parseCallback(); + this.context.inOperatorStack.pop(); + return result; +} +/** =========================================================== + * Private API for other Parser API, just wrapper of recorder + * =========================================================== + */ + +/** + * please reference to recorder api + */ +export function enterFunctionScope(this: Parser, isAsync: boolean = false, isGenerator: boolean = false) { + this.asyncArrowExprScopeRecorder.enterFunctionScope(); + this.strictModeScopeRecorder.enterRHSStrictModeScope(); + this.symbolScopeRecorder.enterFunctionSymbolScope(); + this.lexicalScopeRecorder.enterFunctionLexicalScope(isAsync, isGenerator); + this.lexer.setStrictModeContext(this.isInStrictMode()); +} +export function exitFunctionScope(this: Parser, focusCheck: boolean) { + const isNonSimpleParam = !this.isCurrentFunctionParameterListSimple(); + const isStrict = this.isInStrictMode(); + const symbolScope = this.symbolScopeRecorder.exitSymbolScope()!; + if (isNonSimpleParam || isStrict || focusCheck) { + const functionSymbolScope = symbolScope as FunctionSymbolScope; + for (const symPos of functionSymbolScope.duplicateParams) { + this.raiseError(ErrorMessageMap.duplicate_param, symPos); + } + } + this.asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); + this.strictModeScopeRecorder.exitStrictModeScope(); + this.lexicalScopeRecorder.exitFunctionLexicalScope(); + this.lexer.setStrictModeContext(this.isInStrictMode()); +} +export function enterProgram(this: Parser) { + this.symbolScopeRecorder.enterProgramSymbolScope(); + this.lexicalScopeRecorder.enterProgramLexicalScope( + this.config.allowAwaitOutsideFunction || false, + this.config.sourceType === "module", + ); + this.lexer.setStrictModeContext(this.config.sourceType === "module"); +} +export function exitProgram(this: Parser) { + if (!this.config.allowUndeclaredExports) { + for (const pos of this.symbolScopeRecorder.getProgramContainUndefSymbol()) { + this.raiseError(ErrorMessageMap.babel_error_export_is_not_defined, pos); + } + } + this.symbolScopeRecorder.exitSymbolScope(); + this.lexicalScopeRecorder.exitProgramLexicalScope(); + this.lexer.setStrictModeContext(false); +} +export function enterBlockScope(this: Parser) { + this.lexicalScopeRecorder.enterBlockLexicalScope(false); + this.symbolScopeRecorder.enterBlockSymbolScope(); +} +export function exitBlockScope(this: Parser) { + this.lexicalScopeRecorder.exitBlockLexicalScope(); + this.symbolScopeRecorder.exitSymbolScope(); +} +export function enterCatchBlockScope(this: Parser) { + this.lexicalScopeRecorder.enterBlockLexicalScope(true); + this.symbolScopeRecorder.enterFunctionSymbolScope(); +} +export function exitCatchBlockScope(this: Parser) { + this.lexicalScopeRecorder.exitBlockLexicalScope(); + this.symbolScopeRecorder.exitSymbolScope(); +} +export function parseAsLoop(this: Parser, callback: () => T): T { + this.lexicalScopeRecorder.enterVirtualBlockScope("Loop"); + const result = callback(); + this.lexicalScopeRecorder.exitVirtualBlockScope(); + return result; +} +export function parseAsSwitch(this: Parser, callback: () => T): T { + this.lexicalScopeRecorder.enterVirtualBlockScope("Switch"); + const result = callback(); + this.lexicalScopeRecorder.exitVirtualBlockScope(); + return result; +} +export function recordScope(this: Parser, kind: ExpressionScopeKind, position: SourcePosition) { + this.strictModeScopeRecorder.record(kind, position); + this.asyncArrowExprScopeRecorder.record(kind, position); +} +export function enterArrowFunctionBodyScope(this: Parser, isAsync: boolean = false) { + this.lexicalScopeRecorder.enterArrowFunctionBodyScope(isAsync); + this.symbolScopeRecorder.enterFunctionSymbolScope(); +} +export function exitArrowFunctionBodyScope(this: Parser) { + const symbolScope = this.symbolScopeRecorder.exitSymbolScope()!; + const functionSymbolScope = symbolScope as FunctionSymbolScope; + for (const symPos of functionSymbolScope.duplicateParams) { + this.raiseError(ErrorMessageMap.duplicate_param, symPos); + } + this.lexicalScopeRecorder.exitArrowFunctionBodyScope(); +} +export function parseWithArrowExpressionScope( + this: Parser, + callback: () => T, +): [T, AsyncArrowExpressionScope] { + this.asyncArrowExprScopeRecorder.enterAsyncArrowExpressionScope(); + const result = callback(); + const scope = this.asyncArrowExprScopeRecorder.getCurrentAsyncArrowExpressionScope()!; + this.asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); + return [result, scope]; +} +export function parseWithCatpureLayer(this: Parser, callback: () => T): [T, StrictModeScope] { + this.strictModeScopeRecorder.enterCatpureStrictModeScope(); + const result = callback(); + const scope = this.strictModeScopeRecorder.getCurrentStrictModeScope(); + this.strictModeScopeRecorder.exitStrictModeScope(); + return [result, scope]; +} +export function parseWithLHSLayer(this: Parser, callback: () => T): T { + this.strictModeScopeRecorder.enterLHSStrictModeScope(); + const result = callback(); + this.strictModeScopeRecorder.exitStrictModeScope(); + return result; +} +export function parseWithLHSLayerReturnScope(this: Parser, callback: () => T): [T, StrictModeScope] { + this.strictModeScopeRecorder.enterLHSStrictModeScope(); + const result = callback(); + const scope = this.strictModeScopeRecorder.getCurrentStrictModeScope(); + this.strictModeScopeRecorder.exitStrictModeScope(); + return [result, scope]; +} +export function parseWithRHSLayer(this: Parser, callback: () => T): T { + this.strictModeScopeRecorder.enterRHSStrictModeScope(); + const result = callback(); + this.strictModeScopeRecorder.exitStrictModeScope(); + return result; +} +export function checkStrictModeScopeError(this: Parser, scope: StrictModeScope) { + if (this.isInStrictMode() && this.strictModeScopeRecorder.isStrictModeScopeViolateStrictMode(scope)) { + if (scope.kind !== "RHSLayer") { + for (const pos of scope.argumentsIdentifier) { + this.raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); + } + for (const pos of scope.evalIdentifier) { + this.raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); + } + for (const pos of scope.letIdentifier) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); + } + for (const pos of scope.yieldIdentifier) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); + } + for (const pos of scope.preservedWordIdentifier) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); + } + } + } +} +export function checkAsyncArrowExprScopeError(this: Parser, scope: AsyncArrowExpressionScope) { + if (this.asyncArrowExprScopeRecorder.isAsyncArrowExpressionScopeHaveError(scope)) { + for (const pos of scope.awaitExpressionInParameter) { + this.raiseError(ErrorMessageMap.extra_error_await_expression_can_not_used_in_parameter_list, pos); + } + for (const pos of scope.yieldExpressionInParameter) { + this.raiseError(ErrorMessageMap.extra_error_yield_expression_can_not_used_in_parameter_list, pos); + } + for (const pos of scope.awaitIdentifier) { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + pos, + ); + } + } +} +export function enterFunctionParameter(this: Parser) { + this.lexicalScopeRecorder.enterFunctionLexicalScopeParamemter(); +} +export function existFunctionParameter(this: Parser) { + this.lexicalScopeRecorder.exitFunctionLexicalScopeParamemter(); +} +export function setCurrentFunctionContextAsGenerator(this: Parser) { + this.lexicalScopeRecorder.setCurrentFunctionLexicalScopeAsGenerator(); +} +export function setCurrentFunctionContextAsStrictMode(this: Parser) { + this.lexicalScopeRecorder.setCurrentFunctionLexicalScopeAsStrictMode(); + this.lexer.setStrictModeContext(true); +} +/** + * Private API for `For-In` parsering problem. + * @returns {boolean} + */ +export function getCurrentInOperatorStack(this: Parser): boolean { + if (this.context.inOperatorStack.length === 0) { + return false; + } + return this.context.inOperatorStack[this.context.inOperatorStack.length - 1]; +} +export function isTopLevel(this: Parser): boolean { + return this.lexicalScopeRecorder.isInTopLevel(); +} +export function isCurrentScopeParseAwaitAsExpression(this: Parser): boolean { + return this.lexicalScopeRecorder.canAwaitParseAsExpression(); +} +export function isCurrentScopeParseYieldAsExpression(this: Parser): boolean { + return this.lexicalScopeRecorder.canYieldParseAsExpression(); +} +export function isInParameter(this: Parser): boolean { + return this.lexicalScopeRecorder.isInParameter(); +} +export function setCurrentFunctionParameterListAsNonSimple(this: Parser) { + return this.lexicalScopeRecorder.setCurrentFunctionLexicalScopeParameterAsNonSimple(); +} +export function isCurrentFunctionParameterListSimple(this: Parser): boolean { + return this.lexicalScopeRecorder.isCurrentFunctionLexicalScopeParameterSimple(); +} +export function isParentFunctionAsync(this: Parser): boolean { + return this.lexicalScopeRecorder.isParentFunctionAsync(); +} +export function isParentFunctionGenerator(this: Parser): boolean { + return this.lexicalScopeRecorder.isParentFunctionGenerator(); +} +export function enterClassScope(this: Parser, isExtend: boolean = false) { + this.lexicalScopeRecorder.enterClassLexicalScope(isExtend); + this.asyncArrowExprScopeRecorder.enterAsyncArrowExpressionScope(); + this.symbolScopeRecorder.enterClassSymbolScope(); +} +export function existClassScope(this: Parser) { + const duplicateSet = this.symbolScopeRecorder.isDuplicatePrivateName(); + if (duplicateSet) { + for (const pos of duplicateSet.values()) { + this.raiseError(ErrorMessageMap.babel_error_private_name_duplicate, pos); + } + } + const undefSet = this.symbolScopeRecorder.isUndeinfedPrivateName(); + if (undefSet) { + for (const pos of undefSet.values()) { + this.raiseError(ErrorMessageMap.babel_error_private_name_undeinfed, pos); + } + } + this.lexicalScopeRecorder.exitClassLexicalScope(); + this.symbolScopeRecorder.exitClassSymbolScope(); + this.asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); +} +export function isInClassScope(this: Parser): boolean { + return this.lexicalScopeRecorder.isInClassScope(); +} +export function isCurrentClassExtend(this: Parser): boolean { + return this.lexicalScopeRecorder.isCurrentClassExtend(); +} +export function usePrivateName( + this: Parser, + name: string, + position: SourcePosition, + type: PrivateNameDefKind = "other", +) { + return this.symbolScopeRecorder.usePrivateName(name, position, type); +} +export function defPrivateName( + this: Parser, + name: string, + position: SourcePosition, + type: PrivateNameDefKind = "other", +) { + return this.symbolScopeRecorder.defPrivateName(name, position, type); +} +export function enterDelete(this: Parser) { + this.lexicalScopeRecorder.enterDelete(); +} +export function exitDelete(this: Parser) { + this.lexicalScopeRecorder.exitDelete(); +} +export function isInDelete(this: Parser) { + return this.lexicalScopeRecorder.isCurrentInDelete(); +} +export function isInStrictMode(this: Parser): boolean { + return this.lexicalScopeRecorder.isInStrictMode(); +} +export function isDirectToFunctionContext(this: Parser): boolean { + return this.lexicalScopeRecorder.isDirectToFunctionContext(); +} +export function isDirectToClassScope(this: Parser): boolean { + return this.lexicalScopeRecorder.isDirectToClassScope(); +} +export function isReturnValidate(this: Parser): boolean { + return this.config.allowReturnOutsideFunction || this.lexicalScopeRecorder.isReturnValidate(); +} +export function isBreakValidate(this: Parser): boolean { + return this.lexicalScopeRecorder.isBreakValidate(); +} +export function isContinueValidate(this: Parser): boolean { + return this.lexicalScopeRecorder.isContinueValidate(); +} +export function canLabelReach(this: Parser, name: string): boolean { + return this.lexicalScopeRecorder.canLabelReach(name); +} +export function isEncloseInFunction(this: Parser): boolean { + return this.lexicalScopeRecorder.isEncloseInFunction(); +} +export function isInPropertyName(this: Parser): boolean { + return this.lexicalScopeRecorder.isInPropertyName(); +} +export function setExportContext(this: Parser, context: ExportContext) { + this.lexicalScopeRecorder.setExportContext(context); +} +export function getExportContext(this: Parser): ExportContext { + return this.lexicalScopeRecorder.getExportContext(); +} +export function takeCacheDecorator(this: Parser) { + const list = this.context.cache.decorators; + this.context.cache.decorators = null; + return list; +} +export function mergeDecoratorList(input1: Decorator[] | null, input2: Decorator[] | null) { + const list = [...(input1 || []), ...(input2 || [])]; + if (list.length === 0) { + return null; + } + return list; +} +/** + * + * @param name + * @param position + * @returns + */ +export function declarateNonFunctionalSymbol(this: Parser, name: string, position: SourcePosition) { + if (this.context.isInType) return; + if (this.isInParameter()) { + this.symbolScopeRecorder.declarateParam(name, position); + return; + } + if (!this.symbolScopeRecorder.declarateNonFunctionalSymbol(name)) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); + } + const isExportAlreadyExist = this.declarateExportSymbolIfInContext(name, position); + if (isExportAlreadyExist) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); + } + return; +} +/** + * + * @param name + * @param generator + * @param position + * @returns + */ +export function delcarateFcuntionSymbol( + this: Parser, + name: string, + generator: boolean, + position: SourcePosition, +) { + if (this.context.isInType) return; + const duplicateType = this.symbolScopeRecorder.declarateFuncrtionSymbol(name, generator); + if (duplicateType) { + if ( + (!generator && + ((duplicateType === SymbolType.Function && this.config.sourceType === "module") || + duplicateType === SymbolType.GenFunction)) || + (generator && + ((duplicateType === SymbolType.GenFunction && this.config.sourceType === "module") || + duplicateType === SymbolType.Function)) || + (duplicateType === SymbolType.Var && this.lexicalScopeRecorder.isInCatch()) || + duplicateType === SymbolType.Let || + duplicateType === SymbolType.Const + ) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); + } + } + const isExportAlreadyExist = this.declarateExportSymbolIfInContext(name, position); + if (isExportAlreadyExist) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); + } + return; +} +export function declarateLetSymbol(this: Parser, name: string, position: SourcePosition) { + if (this.context.isInType) return; + if (!this.symbolScopeRecorder.declarateLetSymbol(name)) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); + } + const isExportAlreadyExist = this.declarateExportSymbolIfInContext(name, position); + if (isExportAlreadyExist) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); + } +} +export function declarateParam(this: Parser, name: string, position: SourcePosition) { + if (this.context.isInType) return; + this.symbolScopeRecorder.declarateParam(name, position); + this.declarateExportSymbolIfInContext(name, position); + return; +} +export function declarateExportSymbolIfInContext(this: Parser, name: string, position: SourcePosition) { + if (this.context.isInType) return; + switch (this.getExportContext()) { + case ExportContext.NotInExport: + return null; + case ExportContext.InExport: { + this.setExportContext(ExportContext.NotInExport); + return this.declarateExportSymbol(name, position); + } + case ExportContext.InExportBinding: { + return this.declarateExportSymbol(name, position); + } + } +} +export function declarateExportSymbol(this: Parser, name: string, position: SourcePosition) { + if (this.context.isInType) return; + return this.symbolScopeRecorder.declarateExportSymbol(name, position); +} +export function isVariableDeclarated(this: Parser, name: string) { + return this.symbolScopeRecorder.isVariableDeclarated(name); +} +export function getSymbolType(this: Parser) { + return this.symbolScopeRecorder.getSymbolType() as NonFunctionalSymbolType; +} +export function setSymbolType(this: Parser, symbolType: NonFunctionalSymbolType) { + this.symbolScopeRecorder.setSymbolType(symbolType); +} +/** + * Raise a error, if config recoverable set to false, it will throw a error and + * cause stack unwidning. + * @param message + * @param position + */ +export function raiseError(this: Parser, message: string, position: SourcePosition) { + this.errorHandler.pushSyntaxErrors({ + message, + position, + }); +} diff --git a/web-infras/parser/src/parser/index.ts b/web-infras/parser/src/parser/index.ts index 5c58fd8f..174589ec 100644 --- a/web-infras/parser/src/parser/index.ts +++ b/web-infras/parser/src/parser/index.ts @@ -1 +1,72 @@ -export * from "./parser"; +import { createLexer, type Lexer } from "@/src/lexer"; +import type { Context } from "./context"; +import { + createAsyncArrowExpressionScopeRecorder, + type AsyncArrowExpressionScopeRecorder, +} from "./scope/arrowExprScope"; +import { createStrictModeScopeRecorder, type StrictModeScopeRecorder } from "./scope/strictModeScope"; +import { createLexicalScopeRecorder, type LexicalScopeRecorder } from "./scope/lexicalScope"; +import { createSymbolScopeRecorder, type SymbolScopeRecorder } from "./scope/symbolScope"; +import { getConfigFromUserInput, ParserUserConfig, type ParserConfig } from "./config"; +import type { SyntaxErrorHandler } from "../errorHandler/type"; + +import * as ContextImpl from "./context"; +import * as JSImpl from "./js"; +import * as TSImpl from "./ts"; +import * as JSXImpl from "./jsx"; + +type ContextImplType = typeof ContextImpl; +type JSImplType = typeof JSImpl; +type TSImplType = typeof TSImpl; +type JSXImplType = typeof JSXImpl; + +export class Parser { + lexer: Lexer; + context: Context; + config: ParserConfig; + // recorder + strictModeScopeRecorder: StrictModeScopeRecorder; + asyncArrowExprScopeRecorder: AsyncArrowExpressionScopeRecorder; + lexicalScopeRecorder: LexicalScopeRecorder; + symbolScopeRecorder: SymbolScopeRecorder; + // error + errorHandler: SyntaxErrorHandler; + constructor(code: string, errorHandler: SyntaxErrorHandler, config?: ParserUserConfig) { + this.lexer = createLexer(code); + this.context = ContextImpl.createContext(); + this.config = getConfigFromUserInput(config); + this.strictModeScopeRecorder = createStrictModeScopeRecorder(); + this.asyncArrowExprScopeRecorder = createAsyncArrowExpressionScopeRecorder(); + this.lexicalScopeRecorder = createLexicalScopeRecorder(); + this.symbolScopeRecorder = createSymbolScopeRecorder(); + this.errorHandler = errorHandler; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function bindingParserMethod(methods: any) { + Object.entries(methods).forEach(([name, method]) => { + // @ts-expect-error implement in JS prototype + Parser.prototype[name] = method; + }); +} + +bindingParserMethod(ContextImpl); +bindingParserMethod(JSImpl); +bindingParserMethod(TSImpl); +bindingParserMethod(JSXImpl); + +declare module "./index" { + interface Parser extends ContextImplType, JSImplType, TSImplType, JSXImplType { + lexer: Lexer; + context: Context; + config: ParserConfig; + // recorder + strictModeScopeRecorder: StrictModeScopeRecorder; + asyncArrowExprScopeRecorder: AsyncArrowExpressionScopeRecorder; + lexicalScopeRecorder: LexicalScopeRecorder; + symbolScopeRecorder: SymbolScopeRecorder; + // error + errorHandler: SyntaxErrorHandler; + } +} diff --git a/web-infras/parser/src/parser/js/const.ts b/web-infras/parser/src/parser/js/const.ts new file mode 100644 index 00000000..d3f2b64e --- /dev/null +++ b/web-infras/parser/src/parser/js/const.ts @@ -0,0 +1,22 @@ +import { Keywords, LexicalLiteral, SourcePosition, SyntaxKinds } from "web-infra-common"; + +export const IdentiferWithKeyworArray = [SyntaxKinds.Identifier, ...Keywords]; +export const PreserveWordSet = new Set(LexicalLiteral.preserveword); +export const BindingIdentifierSyntaxKindArray = [ + SyntaxKinds.Identifier, + SyntaxKinds.AwaitKeyword, + SyntaxKinds.YieldKeyword, + SyntaxKinds.LetKeyword, +]; +export const KeywordSet = new Set([ + ...LexicalLiteral.keywords, + ...LexicalLiteral.BooleanLiteral, + ...LexicalLiteral.NullLiteral, + ...LexicalLiteral.UndefinbedLiteral, +]); + +export interface ASTArrayWithMetaData { + nodes: Array; + start: SourcePosition; + end: SourcePosition; +} diff --git a/web-infras/parser/src/parser/js/declaration.ts b/web-infras/parser/src/parser/js/declaration.ts new file mode 100644 index 00000000..24f8f16b --- /dev/null +++ b/web-infras/parser/src/parser/js/declaration.ts @@ -0,0 +1,819 @@ +/** ================================================================= + * Parse Delcarations + * entry point reference: https://tc39.es/ecma262/#prod-Declaration + * ================================================================== + */ +import { Parser } from "@/src/parser"; +import { + Function as FunctionAST, + VariableDeclaration, + SyntaxKinds, + VariableDeclarator, + isIdentifer, + TSParameter, + Factory, + cloneSourcePosition, + FunctionDeclaration, + TSDeclareFunction, + Identifier, + FunctionBody, + StatementListItem, + Pattern, + Decorator, + Expression, + ClassDeclaration, + Class, + ClassBody, + ClassElement, + ClassMethodDefinition, + PropertyName, + PrivateName, + Keywords, + isPrivateName, + isStringLiteral, + Declaration, +} from "web-infra-common"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { ExportContext } from "@/src/parser/scope/lexicalScope"; +import { StrictModeScope } from "@/src/parser/scope/strictModeScope"; +import { SymbolType } from "@/src/parser/scope/symbolScope"; +import { BindingIdentifierSyntaxKindArray, PreserveWordSet } from "@/src/parser/type"; +/** + * Parse Declaration + * + * ``` + * Declaration := ('let' | 'const') BindingLst + * := FunctionDeclaration + * := FunctionGeneratorDeclaration + * := 'async' FunctionDeclaration + * := 'async' FunctionGeneratorDeclaration + * := ClassDeclaration + * ``` + * when call parseDeclaration, please make sure currentToken is + * - `let` or `const` keyword + * - `function` keyword + * - `class` keyword + * - `async` with `function` keyword + * + * ref: https://tc39.es/ecma262/#prod-Declaration + * @returns + */ +export function parseDeclaration(this: Parser): Declaration { + const token = this.getToken(); + switch (token) { + // async function declaration + case SyntaxKinds.Identifier: + case SyntaxKinds.EnumKeyword: { + const declar = this.tryParseDeclarationWithIdentifierStart(); + if (!declar) { + throw this.createUnexpectError(); + } + return declar; + } + // function delcaration + case SyntaxKinds.FunctionKeyword: + return this.parseFunctionDeclaration(false, false); + case SyntaxKinds.ConstKeyword: + case SyntaxKinds.LetKeyword: + return this.parseVariableDeclaration(); + case SyntaxKinds.AtPunctuator: + return this.parseClassDeclaration(this.parseDecoratorList()); + case SyntaxKinds.ClassKeyword: + return this.parseClassDeclaration(null); + default: + throw this.createUnexpectError(); + } +} +export function tryParseDeclarationWithIdentifierStart(this: Parser): Declaration | undefined { + if (this.getEscFlag()) { + return; + } + if (this.match(SyntaxKinds.EnumKeyword)) { + return this.parseTSEnumDeclaration(); + } + const { kind: lookaheadToken, lineTerminatorFlag } = this.lookahead(); + const sourceValue = this.getSourceValue(); + switch (sourceValue) { + case "async": { + if (!(lookaheadToken === SyntaxKinds.FunctionKeyword && !lineTerminatorFlag)) { + return; + } + this.nextToken(); + if (this.getLineTerminatorFlag()) { + this.raiseError(ErrorMessageMap.missing_semicolon, this.getStartPosition()); + } + return this.parseFunctionDeclaration(true, false); + } + case "type": { + if (!(lookaheadToken === SyntaxKinds.Identifier && !lineTerminatorFlag)) { + return; + } + return this.parseTSTypeAlias(); + } + case "interface": { + if (!(lookaheadToken === SyntaxKinds.Identifier && !lineTerminatorFlag)) { + return; + } + return this.parseTSInterfaceDeclaration(); + } + default: { + return; + } + } +} +/** + * Parse VariableStatement and LexicalBindingDeclaration. + * + * when in for-in statement, variable declaration do not need semi for + * ending, in binding pattern of for-in statement, variable declaration + * maybe do not need init.(for-in, for-of do not need, but for still need) + * + * Anthoer side, let can be identifier in VariableStatement. and parsrIdentifier + * function would always parse let as identifier if not in strict mode. so we need + * to implement custom function for check is identifier or value of pattern is let + * when in LexicalBindingDeclaration + * ``` + * VariableStatement := 'var' VariableDeclarationList + * LexicalBidningDeclaration := '(let | const)' LexicalBinding + * VariableDeclarationList := BindingIdentidier initalizer + * := BindingPattern initalizer + * LexicalBinding := BindingIdentidier initalizer + * := BindingPattern initalizer + * ``` + * @returns {VariableDeclaration} + */ +export function parseVariableDeclaration(this: Parser, inForInit: boolean = false): VariableDeclaration { + const variableKind = this.match(SyntaxKinds.VarKeyword) ? "var" : "lexical"; + const { start: keywordStart, value: variant } = this.expect([ + SyntaxKinds.VarKeyword, + SyntaxKinds.ConstKeyword, + SyntaxKinds.LetKeyword, + ]); + let shouldStop = false, + isStart = true; + const declarations: Array = []; + const lastSymbolKind = this.getSymbolType(); + this.setSymbolType( + variant === "var" ? SymbolType.Var : variant === "const" ? SymbolType.Const : SymbolType.Let, + ); + if (this.getExportContext() === ExportContext.InExport) { + this.setExportContext(ExportContext.InExportBinding); + } + while (!shouldStop) { + if (isStart) { + isStart = false; + } else { + if (!this.match(SyntaxKinds.CommaToken)) { + shouldStop = true; + continue; + } + this.nextToken(); + } + // eslint-disable-next-line prefer-const + let [id, scope] = this.parseWithCatpureLayer(() => this.parseBindingElement(false)); + const isBindingPattern = !isIdentifer(id); + if (variableKind === "lexical" && scope.kind !== "RHSLayer" && scope.letIdentifier.length > 0) { + throw new Error("TODO ERROR: Better"); + } + id = this.parseFunctionParamType(id as TSParameter, false); + // custom logical for check is lexical binding have let identifier ? + if ( + // variable declarations binding pattern but but have init. + (isBindingPattern || variant === "const") && + !this.match(SyntaxKinds.AssginOperator) && + // variable declaration in for statement can existed with `of`, `in` operator + !inForInit + ) { + // recoverable error + this.raiseError(ErrorMessageMap.syntax_error_missing_init_in_const_declaration, id.start); + } + if (this.match(SyntaxKinds.AssginOperator)) { + this.nextToken(); + const init = this.parseAssignmentExpressionInheritIn(); + declarations.push( + Factory.createVariableDeclarator( + id, + init, + cloneSourcePosition(id.start), + cloneSourcePosition(init.end), + ), + ); + continue; + } + declarations.push( + Factory.createVariableDeclarator(id, null, cloneSourcePosition(id.start), cloneSourcePosition(id.end)), + ); + } + this.setSymbolType(lastSymbolKind); + this.setExportContext(ExportContext.NotInExport); + if (!inForInit) { + this.shouldInsertSemi(); + } + return Factory.createVariableDeclaration( + declarations, + variant as VariableDeclaration["variant"], + keywordStart, + declarations[declarations.length - 1].end, + ); +} +export function parseFunctionDeclaration( + this: Parser, + isAsync: boolean, + isDefault: boolean, +): FunctionDeclaration | TSDeclareFunction { + this.enterFunctionScope(isAsync); + const { start } = this.expect(SyntaxKinds.FunctionKeyword); + let generator = false; + if (this.match(SyntaxKinds.MultiplyOperator)) { + generator = true; + this.setCurrentFunctionContextAsGenerator(); + this.nextToken(); + } + const [[name, typeParameters, params], scope] = this.parseWithCatpureLayer(() => { + const name = this.parseFunctionName(isDefault); + if (!name && !isDefault) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_function_statement_requires_a_name, + this.getStartPosition(), + ); + } + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + const params = this.parseFunctionParam(); + return [name, typeParameters, params]; + }); + const returnType = this.tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + const body = this.parseFunctionBody(); + this.postStaticSematicEarlyErrorForStrictModeOfFunction(name, scope); + const func = Factory.createFunction( + name, + body, + params, + typeParameters, + returnType, + generator, + this.isCurrentScopeParseAwaitAsExpression(), + start, + cloneSourcePosition(body.end), + ); + this.exitFunctionScope(false); + // for function declaration, symbol should declar in parent scope. + if (name) { + this.delcarateFcuntionSymbol(name.name, func.generator, func.start); + } + return Factory.transFormFunctionToFunctionDeclaration(func); + } else { + const funcDeclar = Factory.createTSDeclarFunction( + name, + returnType, + params, + typeParameters, + generator, + this.isCurrentScopeParseAwaitAsExpression(), + start, + this.getLastTokenEndPositon(), + ); + this.shouldInsertSemi(); + this.exitFunctionScope(false); + return funcDeclar; + } +} +/** + * Parse function maybe call by parseFunctionDeclaration and parseFunctionExpression, + * first different of those two function is that function-declaration can not have null + * name. + * @returns {FunctionAST} + */ +export function parseFunction(this: Parser, isExpression: boolean): FunctionAST { + const { start } = this.expect(SyntaxKinds.FunctionKeyword); + let generator = false; + if (this.match(SyntaxKinds.MultiplyOperator)) { + generator = true; + this.setCurrentFunctionContextAsGenerator(); + this.nextToken(); + } + const [[name, typeParameters, params], scope] = this.parseWithCatpureLayer(() => { + const name = this.parseFunctionName(isExpression); + if (!name && !isExpression) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_function_statement_requires_a_name, + this.getStartPosition(), + ); + } + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + const params = this.parseFunctionParam(); + return [name, typeParameters, params]; + }); + const returnType = this.tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); + const body = this.parseFunctionBody(); + this.postStaticSematicEarlyErrorForStrictModeOfFunction(name, scope); + return Factory.createFunction( + name, + body, + params, + typeParameters, + returnType, + generator, + this.isCurrentScopeParseAwaitAsExpression(), + start, + cloneSourcePosition(body.end), + ); +} +/** + * Because we "use strict" directive is in function body, we will not sure + * if this function contain stric directive or not until we enter the function body. + * as the result, we need to check function name and function paramemter's name after + * parseFunctionBody. + * @param name + * @param params + */ +export function postStaticSematicEarlyErrorForStrictModeOfFunction( + this: Parser, + name: Identifier | null, + scope: StrictModeScope, +) { + if (this.isInStrictMode()) { + this.checkStrictModeScopeError(scope); + if (name) { + if ( + name.name === "arugments" || + name.name === "eval" || + name.name === "yield" || + name.name === "let" || + PreserveWordSet.has(name.name) + ) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, name.start); + } + } + } +} +/** + * When parse name of function, can not just call parseIdentifier, because function name + * maybe await or yield, and function name's context rule is different from identifier in + * scope (function body). so there we need to implement special logical for parse function + * name. and you need to note that name of function expression and name of function delcaration + * have different context rule for parse function name. + * @param {boolean} optionalName + * @returns {Identifier | null} + */ +export function parseFunctionName(this: Parser, optionalName: boolean): Identifier | null { + return this.parseWithLHSLayer(() => { + let name: Identifier | null = null; + // there we do not just using parseIdentifier function as the reason above + // let can be function name as other place + if (this.match([SyntaxKinds.Identifier, SyntaxKinds.LetKeyword])) { + name = this.parseIdentifierReference(); + } else { + if (this.match(SyntaxKinds.AwaitKeyword)) { + // for function expression, can await treat as function name is dep on current scope. + if (optionalName && this.isCurrentScopeParseAwaitAsExpression()) { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + this.getStartPosition(), + ); + } + // for function declaration, can await treat as function name is dep on parent scope. + if (!optionalName && this.isParentFunctionAsync()) { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + this.getStartPosition(), + ); + } + if (this.config.sourceType === "module") { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + this.getStartPosition(), + ); + } + name = this.parseIdentifierName(); + } else if (this.match(SyntaxKinds.YieldKeyword)) { + // for function expression, can yield treat as function name is dep on current scope. + if (optionalName && this.isCurrentScopeParseYieldAsExpression()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, this.getStartPosition()); + } + // for function declaration, can yield treat as function name is dep on parent scope. + if (!optionalName && this.isParentFunctionGenerator()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, this.getStartPosition()); + } + // if in strict mode, yield can not be function name. + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, this.getStartPosition()); + } + name = this.parseIdentifierName(); + } + } + return name; + }); +} +/** + * Parse Function Body + * ``` + * FunctionBody := '{' StatementList '}' + * StatementList := StatementList StatementListItem + * := StatementListItem + * ``` + * @return {FunctionBody} + */ +export function parseFunctionBody(this: Parser): FunctionBody { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const body: Array = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + body.push(this.parseStatementListItem()); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createFunctionBody(body, start, end); +} +/** + * Parse Function Params, parameter list is a spcial place for await and yield, + * function parameter list is in current function scope, so await and yield would + * parse as expression, but parameter list can not call await and yield expression. + * + * Anthoer thing is that trailing comma of restElement is error, multi restElement is + * error for function param list. + * ``` + * FunctionParams := '(' FunctionParamsList ')' + * := '(' FunctionParamsList ',' ')' + * := '(' FunctionPramsList ',' RestElement ')' + * := '(' RestElement ')' + * FunctiinParamList := FunctionParamList ',' FunctionParam + * := FunctionParam + * FunctionParam := BindingElement + * ``` + */ +export function parseFunctionParam(this: Parser): Array { + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + this.enterFunctionParameter(); + let isStart = true; + let isEndWithRest = false; + const params: Array = []; + while (!this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + if (isStart) { + if (this.match(SyntaxKinds.CommaToken)) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_unexpect_trailing_comma, this.getStartPosition()); + this.nextToken(); + } + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + if (this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + continue; + } + // parse SpreadElement (identifer, Object, Array) + let param: Pattern; + if (this.match(SyntaxKinds.SpreadOperator)) { + isEndWithRest = true; + param = this.parseRestElement(true); + param = this.parseFunctionParamType(param as TSParameter, true); + params.push(param); + break; + } else { + param = this.parseBindingElement(); + param = this.parseFunctionParamType(param as TSParameter, true); + params.push(param); + } + } + if (!this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + if (isEndWithRest && this.match(SyntaxKinds.CommaToken)) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, + this.getStartPosition(), + ); + this.nextToken(); + } + throw this.createUnexpectError(); + } + this.nextToken(); + this.setContextIfParamsIsSimpleParameterList(params); + this.existFunctionParameter(); + return params; +} +/** + * Helper function for check if parameter list is simple + * parameter list or not, if is simple parameter, set + * the context. + * @param {Array} params + * @returns + */ +export function setContextIfParamsIsSimpleParameterList(this: Parser, params: Array) { + for (const param of params) { + if (!isIdentifer(param)) { + this.setCurrentFunctionParameterListAsNonSimple(); + return; + } + } +} +export function parseDecoratorListToCache(this: Parser) { + const decoratorList = this.parseDecoratorList(); + this.context.cache.decorators = decoratorList; +} +export function parseDecoratorList(this: Parser): [Decorator] { + const decoratorList: [Decorator] = [this.parseDecorator()]; + while (this.match(SyntaxKinds.AtPunctuator)) { + decoratorList.push(this.parseDecorator()); + } + if ( + this.match(SyntaxKinds.ClassKeyword) || + (this.match(SyntaxKinds.ExportKeyword) && this.config.sourceType === "module") || + this.isInClassScope() + ) { + return decoratorList; + } + this.raiseError( + ErrorMessageMap.babel_error_leading_decorators_must_be_attached_to_a_class_declaration, + decoratorList[0].start, + ); + return decoratorList; +} +export function parseDecorator(this: Parser): Decorator { + const { start } = this.expect(SyntaxKinds.AtPunctuator); + switch (this.getToken()) { + case SyntaxKinds.ParenthesesLeftPunctuator: { + this.nextToken(); + const expr = this.parseExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + return Factory.createDecorator(expr, start, expr.end); + } + default: { + let expr: Expression = this.parseIdentifierName(); + while (this.match(SyntaxKinds.DotOperator)) { + this.nextToken(); + const property = this.match(SyntaxKinds.PrivateName) + ? this.parsePrivateName() + : this.parseIdentifierName(); + expr = Factory.createMemberExpression( + false, + property, + expr, + false, + cloneSourcePosition(expr.start), + cloneSourcePosition(property.end), + ); + } + if ( + this.match(SyntaxKinds.LtOperator) || + this.match(SyntaxKinds.BitwiseLeftShiftOperator) || + this.match(SyntaxKinds.ParenthesesLeftPunctuator) + ) { + const typeArguments = this.tryParseTSTypeParameterInstantiation(false); + const { nodes, end } = this.parseArguments(); + const callExpr = Factory.createCallExpression( + expr, + nodes, + typeArguments, + false, + cloneSourcePosition(expr.start), + end, + ); + return Factory.createDecorator(callExpr, start, cloneSourcePosition(callExpr.end)); + } + return Factory.createDecorator(expr, start, expr.end); + } + } +} + +/** + * + */ +export function parseClassDeclaration(this: Parser, decoratorList: Decorator[] | null): ClassDeclaration { + this.expectButNotEat(SyntaxKinds.ClassKeyword); + decoratorList = decoratorList || this.takeCacheDecorator(); + const classDelcar = this.parseClass(decoratorList); + if (classDelcar.id === null) { + this.raiseError(ErrorMessageMap.babel_error_a_class_name_is_required, classDelcar.start); + } + return Factory.transFormClassToClassDeclaration(classDelcar); +} +/** + * Parse Class + * ``` + * Class := 'class' identifer ('extends' LeftHandSideExpression) ClassBody + * ``` + * @returns {Class} + */ +export function parseClass(this: Parser, decoratorList: Decorator[] | null): Class { + const { start } = this.expect(SyntaxKinds.ClassKeyword); + let name: Identifier | null = null; + if (this.match(BindingIdentifierSyntaxKindArray)) { + name = this.parseIdentifierReference(); + this.declarateLetSymbol(name.name, name.start); + } + let superClass: Expression | null = null; + if (this.match(SyntaxKinds.ExtendsKeyword)) { + this.enterClassScope(true); + this.nextToken(); + superClass = this.parseLeftHandSideExpression(); + } else { + this.enterClassScope(false); + } + const body = this.parseClassBody(); + this.existClassScope(); + return Factory.createClass(name, superClass, body, decoratorList, start, cloneSourcePosition(body.end)); +} +/** + * Parse ClassBody + * ``` + * ClassBody := '{' [ClassElement] '}' + * ``` + * @return {ClassBody} + */ +export function parseClassBody(this: Parser): ClassBody { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const classbody: ClassBody["body"] = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (this.match(SyntaxKinds.SemiPunctuator)) { + this.nextToken(); + continue; + } + classbody.push(this.parseClassElement()); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createClassBody(classbody, cloneSourcePosition(start), cloneSourcePosition(end)); +} +/** + * Parse ClassElement + * ``` + * ClassElement := MethodDefinition + * := 'static' MethodDefinition + * := FieldDefintion ; + * := 'static' FieldDefintion ; + * := ClassStaticBlock + * := ; (this production rule handle by caller) + * FieldDefintion := ClassElementName ('=' AssignmentExpression)? + * ``` + * - frist, parse 'static' keyword if possible, next follow cases + * 1. start with some method modifier like 'set', 'get', 'async', '*' must be methodDefintion + * 2. start with '{', must be static block + * - then parse ClassElement + * 1. if next token is '(', must be MethodDefintion, + * 2. else this only case is FieldDefinition with init or not. + * @returns {ClassElement} + */ +export function parseClassElement(this: Parser): ClassElement { + let decorators: Decorator[] | null = null; + if (this.match(SyntaxKinds.AtPunctuator)) { + decorators = this.parseDecoratorList(); + } else { + decorators = this.takeCacheDecorator(); + } + // parse static modifier + const isStatic = this.checkIsMethodStartWithStaticModifier(); + if (this.checkIsMethodStartWithModifier()) { + return this.parseMethodDefintion(true, undefined, isStatic, decorators) as ClassMethodDefinition; + } + if (this.match(SyntaxKinds.BracesLeftPunctuator) && isStatic) { + if (decorators) { + this.raiseError( + ErrorMessageMap.babel_error_decorators_can_not_be_used_with_a_static_block, + decorators[0].start, + ); + } + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + this.symbolScopeRecorder.enterFunctionSymbolScope(); + const body: Array = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + body.push(this.parseStatementListItem()); + } + this.symbolScopeRecorder.exitSymbolScope(); + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createClassStaticBlock(body, start, end); + } + let accessor = false; + if (this.isContextKeyword("accessor")) { + const { kind, lineTerminatorFlag } = this.lookahead(); + if (kind === SyntaxKinds.Identifier && !lineTerminatorFlag) { + this.nextToken(); + accessor = true; + } + } + // parse ClassElementName + const isComputedRef = { isComputed: false }; + let key: PropertyName | PrivateName | undefined; + if (this.match(SyntaxKinds.PrivateName)) { + key = this.parsePrivateName(); + this.defPrivateName(key.name, key.start); + } else { + key = this.parsePropertyName(isComputedRef); + } + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + return this.parseMethodDefintion( + true, + [key, isComputedRef.isComputed], + isStatic, + decorators, + ) as ClassMethodDefinition; + } + this.staticSematicForClassPropertyName(key, isComputedRef.isComputed, isStatic); + let propertyValue = undefined, + shorted = true; + if (this.match([SyntaxKinds.AssginOperator])) { + this.nextToken(); + shorted = false; + const [value, scope] = this.parseWithCatpureLayer(() => this.parseAssignmentExpressionAllowIn()); + propertyValue = value; + this.staticSematicForClassPropertyValue(scope); + } + this.shouldInsertSemi(); + if (accessor) { + return Factory.createClassAccessorProperty( + key, + propertyValue, + isComputedRef.isComputed, + isStatic, + shorted, + decorators, + cloneSourcePosition(key.start), + cloneSourcePosition(key.end), + ); + } + return Factory.createClassProperty( + key, + propertyValue, + isComputedRef.isComputed, + isStatic, + shorted, + decorators, + cloneSourcePosition(key.start), + cloneSourcePosition(key.end), + ); +} +export function checkIsMethodStartWithStaticModifier(this: Parser) { + const { kind } = this.lookahead(); + if (this.isContextKeyword("static")) { + switch (kind) { + // static + // static get/set/async + // static { + // static [] + // static * + case SyntaxKinds.Identifier: + case SyntaxKinds.PrivateName: + case SyntaxKinds.StringLiteral: + case SyntaxKinds.BracesLeftPunctuator: + case SyntaxKinds.BracketLeftPunctuator: + case SyntaxKinds.MultiplyOperator: + this.nextToken(); + return true; + default: + // static for/if ...etc + if (Keywords.find((kw) => kw === kind)) return true; + return false; + } + } + return false; +} +/** + * For a class scope, it must be strict mode, so argument identifier can + * @param {StrictModeScope} scope + * @returns + */ +export function staticSematicForClassPropertyValue(this: Parser, scope: StrictModeScope) { + if (scope.kind !== "CatpureLayer") { + return; + } + for (const pos of scope.argumentsIdentifier) { + this.raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); + } +} +/** + * Check sematic for class property name. + * - `constructor` can not used as property name + * - `prototype` can not be a static property + * @param {PropertyName | PrivateName} propertyName + * @param isComputed + * @param {boolean} isStatic + * @returns + */ +export function staticSematicForClassPropertyName( + this: Parser, + propertyName: PropertyName | PrivateName, + isComputed: boolean, + isStatic: boolean, +) { + if (isComputed) return; + let value; + if (isPrivateName(propertyName) || isIdentifer(propertyName)) { + value = propertyName.name; + } else if (isStringLiteral(propertyName)) { + value = propertyName.value; + } + if (value) { + if (value === "constructor") { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_classe_may_not_have_a_field_named_constructor, + propertyName.start, + ); + } + if (value === "prototype" && isStatic) { + this.raiseError( + ErrorMessageMap.v8_error_class_may_not_have_static_property_named_prototype, + propertyName.start, + ); + } + } +} diff --git a/web-infras/parser/src/parser/js/expression.ts b/web-infras/parser/src/parser/js/expression.ts new file mode 100644 index 00000000..08b31d27 --- /dev/null +++ b/web-infras/parser/src/parser/js/expression.ts @@ -0,0 +1,1276 @@ +import { + Expression, + SyntaxKinds, + Factory, + cloneSourcePosition, + AssigmentOperators, + Pattern, + AssigmentOperatorKinds, + YieldExpression, + isArrowFunctionExpression, + isPrivateName, + BinaryOperatorKinds, + isUnaryExpression, + isAwaitExpression, + UnaryOperators, + UnaryOperatorKinds, + UnaryExpression, + isIdentifer, + UpdateOperators, + UpdateOperatorKinds, + TSTypeParameterInstantiation, + ModuleItem, + TSTypeAnnotation, + PrivateName, + ExpressionStatement, + isClassExpression, + isFunctionExpression, + isStringLiteral, +} from "web-infra-common"; +import { Parser } from "@/src/parser"; +import { ParserPlugin } from "@/src/parser/config"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { ExpressionScopeKind } from "@/src/parser/scope/type"; +import { ASTArrayWithMetaData } from "@/src/parser/type"; + +interface LefthansSideParseState { + shouldStop: boolean; + hasOptional: boolean; + optional: boolean; + abortLastTime: boolean; +} +/** ==================================================================== + * Parse Expression + * entry point reference : https://tc39.es/ecma262/#sec-comma-operator + * ===================================================================== + */ + +/** + * Entry function for parse a expression statement. + * @returns {ExpressionStatement} + */ +export function parseExpressionStatement(this: Parser): ExpressionStatement { + this.preStaticSematicEarlyErrorForExpressionStatement(); + const lastTokenIndex = this.lexer.getLastTokenEndPositon().index; + const expr = this.parseExpressionAllowIn(); + this.checkStrictMode(expr); + this.postStaticSematicEarlyErrorForExpressionStatement(expr, lastTokenIndex); + this.shouldInsertSemi(); + return Factory.createExpressionStatement( + expr, + cloneSourcePosition(expr.start), + cloneSourcePosition(expr.end), + ); +} +/** + * Implement part of NOTE section in 14.5 + */ +export function preStaticSematicEarlyErrorForExpressionStatement(this: Parser) { + if (this.match(SyntaxKinds.LetKeyword)) { + const { kind, lineTerminatorFlag } = this.lookahead(); + if ( + kind === SyntaxKinds.BracketLeftPunctuator || + (!lineTerminatorFlag && (kind === SyntaxKinds.BracesLeftPunctuator || kind === SyntaxKinds.Identifier)) + ) { + this.raiseError( + ErrorMessageMap.v8_error_lexical_declaration_cannot_appear_in_a_single_statement_context, + this.getStartPosition(), + ); + } + } +} +/** + * Implement part of NOTE section in 14.5 + */ +export function postStaticSematicEarlyErrorForExpressionStatement( + this: Parser, + expr: Expression, + lastTokenIndex: number, +) { + if (!expr.parentheses) { + if (isClassExpression(expr)) { + this.raiseError(ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, expr.start); + return; + } + if (isFunctionExpression(expr)) { + if (expr.async) { + this.raiseError(ErrorMessageMap.syntax_error_async_function_declare, expr.start); + } + if (expr.generator) { + this.raiseError(ErrorMessageMap.syntax_error_generator_function_declare, expr.start); + } + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.syntax_error_functions_declare_strict_mode, expr.start); + } else { + if (lastTokenIndex !== this.context.lastTokenIndexOfIfStmt) { + this.raiseError(ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, expr.start); + } + } + } + } +} +/** + * Helper function for checking `use strict` directive, according to + * ECMA spec, `use strict` directive is a `ExpressionStatement` in + * which value is `use strict`, and this function is doing the same + * thing as spec required. + * + * NOTE: this function would perform side effect to parse context + * + * ref: https://tc39.es/ecma262/#use-strict-directive + * @param {Expression} expr + */ +export function checkStrictMode(this: Parser, expr: Expression) { + if (isStringLiteral(expr)) { + if (expr.value === "use strict" && !expr.parentheses) { + if (this.isDirectToFunctionContext()) { + if (!this.isCurrentFunctionParameterListSimple()) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_use_strict_not_allowed_in_function_with_non_simple_parameters, + expr.start, + ); + } + this.setCurrentFunctionContextAsStrictMode(); + } + } + } +} +/** + * Private Parse API, parse Expression. + * ``` + * Expression: + * AssignmentExpression + * Expression, AssignmentExpression + * ``` + * Since Expression have in operator syntax action, so there we split parseExpression + * into three kind of function. + * - `parseExpressionAllowIn`: equal to production rule with parameter `Expression[+In]` + * - `parseExpressionDisallowIn`: equal to production rule with parameter `Expression[~In]` + * - `parseExpressionInheritIn`: equal to production rule with parameter `Expression[?in]` + * @returns {Expression} + */ +export function parseExpressionAllowIn(this: Parser): Expression { + return this.allowInOperaotr(() => this.parseExpressionInheritIn()); +} +/** + * Private Parse API, parse Expression. + * - allow disallow in operator syntax transition action. + * - for more detail, please refer to `parseExpressionAllowIn`. + * @returns {Expression} + */ +export function parseExpressionDisallowIn(this: Parser): Expression { + return this.disAllowInOperaotr(() => this.parseExpressionInheritIn()); +} +/** + * Private Parse API, parse Expression. + * - inherit in operator syntax transition action. + * - for more detail, please refer to `parseExpressionAllowIn`. + * @returns {Expression} + */ +export function parseExpressionInheritIn(this: Parser): Expression { + const exprs = [this.parseAssignmentExpressionInheritIn()]; + while (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + exprs.push(this.parseAssignmentExpressionInheritIn()); + } + if (exprs.length === 1) { + return exprs[0]; + } + return Factory.createSequenceExpression( + exprs, + cloneSourcePosition(exprs[0].start), + cloneSourcePosition(exprs[exprs.length - 1].end), + ); +} +/** + * Private Parse API, parse AssignmentExpression + * + * Since AssignmentExpression usually is a entry point of in operator syntax action. just like + * parseExpression, there we split function into three kind: + * - `parseAssignmentExpressionAllowIn`: equals to production rule with parameter + * - `parseAssignmentExpressionInhertIn`: equals to production rule with parameter + * - It since that disallow is not show in current spec, so ignore it. + * @returns {Expression} + */ +export function parseAssignmentExpressionAllowIn(this: Parser): Expression { + return this.allowInOperaotr(() => this.parseAssignmentExpressionInheritIn()); +} +/** + * Private Parse API, parse AssignmentExpression + * - inherit in operator syntax transition action. + * - for more detail, please refer to `parseAssignmentExpressionAllowIn`. + * @returns {Expression} + */ +export function parseAssignmentExpressionInheritIn(this: Parser): Expression { + if ( + this.match([ + SyntaxKinds.ParenthesesLeftPunctuator, + SyntaxKinds.Identifier, + SyntaxKinds.LetKeyword, + SyntaxKinds.YieldKeyword, + SyntaxKinds.AwaitKeyword, + ]) + ) { + this.context.maybeArrowStart = this.getStartPosition().index; + } + if (this.match(SyntaxKinds.YieldKeyword) && this.isCurrentScopeParseYieldAsExpression()) { + return this.parseYieldExpression(); + } + if ( + !this.requirePlugin(ParserPlugin.JSX) && + this.requirePlugin(ParserPlugin.TypeScript) && + (this.match(SyntaxKinds.LtOperator) || this.match(SyntaxKinds.BitwiseLeftShiftOperator)) + ) { + const expr = this.parseTSGenericArrowFunctionExpression(); + if (expr) return expr; + } + const [leftExpr, scope] = this.parseWithCatpureLayer(() => this.parseConditionalExpression()); + if (!this.match(AssigmentOperators)) { + return leftExpr; + } + const left = this.exprToPattern(leftExpr, false); + this.checkStrictModeScopeError(scope); + const operator = this.getToken(); + if (operator !== SyntaxKinds.AssginOperator) { + this.checkExpressionAsLeftValue(left); + } + this.nextToken(); + const right = this.parseAssignmentExpressionInheritIn(); + return Factory.createAssignmentExpression( + left as Pattern, + right, + operator as AssigmentOperatorKinds, + cloneSourcePosition(left.start), + cloneSourcePosition(right.end), + ); +} +/** + * Helper function for any parse API that need to parse Arrow function. + * From production rule in `AssignmentExpression`, `ArrowFunctionExpression` and + * `AsyncArrowFunctionExpression` is same level of `ConditionalExpression`, but we + * parse ArrowFunction in the bottom of parse recursion(parsePrimary), so we should + * mark context there to make parseArrowFunction only parse when context is mark. + * @returns {boolean} + */ +export function canParseAsArrowFunction(this: Parser): boolean { + return this.getStartPosition().index === this.context.maybeArrowStart; +} +/** + * Parse Yield Expression, when current function scope is generator, yield would + * seems as keyword of yield expression, but even if in generator function scope, + * function parameter can call yield expression, so this function would check is + * current place is in function paramter or not. if yield expression shows in + * parameter list, it would throw error. + * ``` + * YieldExpression := 'yield' + * := 'yield' AssignmentExpression + * := 'yield' '*' AssignmentExpression + * ``` + * @returns {YieldExpression} + */ +export function parseYieldExpression(this: Parser): YieldExpression { + const { start } = this.expect(SyntaxKinds.YieldKeyword); + let delegate = false; + if (this.match(SyntaxKinds.MultiplyOperator)) { + if (!this.getLineTerminatorFlag()) { + this.nextToken(); + delegate = true; + } + } + const isLineDterminator = this.getLineTerminatorFlag(); + let argument: Expression | null = null; + if (!this.isSoftInsertSemi(false) && this.checkIsFollowByExpreesion()) { + if (delegate || (!delegate && !isLineDterminator)) { + argument = this.parseAssignmentExpressionInheritIn(); + } + } + if (delegate && !argument) { + this.raiseError( + ErrorMessageMap.extra_error_yield_deletgate_can_must_be_followed_by_assignment_expression, + this.getStartPosition(), + ); + } + if (this.isInParameter()) { + this.raiseError(ErrorMessageMap.extra_error_yield_expression_can_not_used_in_parameter_list, start); + } + this.recordScope(ExpressionScopeKind.YieldExpressionInParameter, start); + return Factory.createYieldExpression( + argument, + delegate, + start, + cloneSourcePosition(argument ? argument.end : start), + ); +} +export function checkIsFollowByExpreesion(this: Parser) { + switch (this.getToken()) { + case SyntaxKinds.ColonPunctuator: + case SyntaxKinds.ParenthesesRightPunctuator: + case SyntaxKinds.BracketRightPunctuator: + case SyntaxKinds.CommaToken: + return false; + default: + return true; + } +} +export function parseConditionalExpression(this: Parser): Expression { + const test = this.parseBinaryExpression(); + if (this.shouldEarlyReturn(test)) { + return test; + } + // for `arrow function` param, will first + if (!this.match(SyntaxKinds.QustionOperator)) { + return test; + } + if (this.requirePlugin(ParserPlugin.TypeScript)) { + const { kind } = this.lookahead(); + if (kind === SyntaxKinds.ColonPunctuator || kind === SyntaxKinds.ParenthesesRightPunctuator) { + return test; + } + } + this.nextToken(); + const conseq = this.parseAssignmentExpressionAllowIn(); + this.expect(SyntaxKinds.ColonPunctuator); + const alter = this.parseAssignmentExpressionInheritIn(); + return Factory.createConditionalExpression( + test, + conseq, + alter, + cloneSourcePosition(test.start), + cloneSourcePosition(alter.end), + ); +} +/** + * Private Parse Helper API for parse arrow function. + * @param {Expression} expr + * @returns + */ +export function shouldEarlyReturn(this: Parser, expr: Expression) { + return isArrowFunctionExpression(expr) && !expr.parentheses; +} +/** + * Using Operator-precedence parser algorithm is used for parse binary expressiom + * with precedence order. and this is entry function for parseBinaryExpression. + * @returns {Expression} + */ +export function parseBinaryExpression(this: Parser): Expression { + let atom = this.parseUnaryOrPrivateName(); + if (this.shouldEarlyReturn(atom)) { + return atom; + } + atom = this.parseBinaryOps(atom); + if (isPrivateName(atom)) { + this.raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, atom.start); + } + return atom; +} +/** + * Return the precedence order by given binary operator. + * this function should only used by parseBinaryOps + * @param {SyntaxKinds} kind Binary Operator + * @returns {number} + */ +export function getBinaryPrecedence(this: Parser, kind: SyntaxKinds): number { + switch (kind) { + case SyntaxKinds.NullishOperator: + case SyntaxKinds.LogicalOROperator: + return 4; + case SyntaxKinds.LogicalANDOperator: + return 5; + case SyntaxKinds.BitwiseOROperator: + return 6; + case SyntaxKinds.BitwiseXOROperator: + return 7; + case SyntaxKinds.BitwiseANDOperator: + return 8; + case SyntaxKinds.StrictEqOperator: + case SyntaxKinds.StrictNotEqOperator: + case SyntaxKinds.EqOperator: + case SyntaxKinds.NotEqOperator: + return 9; + case SyntaxKinds.InKeyword: + case SyntaxKinds.InstanceofKeyword: + case SyntaxKinds.GtOperator: + case SyntaxKinds.GeqtOperator: + case SyntaxKinds.LeqtOperator: + case SyntaxKinds.LtOperator: + if (kind === SyntaxKinds.InKeyword && !this.getCurrentInOperatorStack()) { + return -1; + } + return 10; + case SyntaxKinds.BitwiseLeftShiftOperator: + case SyntaxKinds.BitwiseRightShiftOperator: + case SyntaxKinds.BitwiseRightShiftFillOperator: + return 11; + case SyntaxKinds.PlusOperator: + case SyntaxKinds.MinusOperator: + return 12; + case SyntaxKinds.ModOperator: + case SyntaxKinds.DivideOperator: + case SyntaxKinds.MultiplyOperator: + return 13; + case SyntaxKinds.ExponOperator: + return 14; + default: + return -1; + } +} +export function isBinaryOps(this: Parser, kind: SyntaxKinds) { + return this.getBinaryPrecedence(kind) > 0; +} +/** + * Bottom up recurive function for parse binary operator and next + * expression. + * @param {Expression} left + * @param {number} lastPre + * @returns {Expression} + */ +export function parseBinaryOps(this: Parser, left: Expression, lastPre: number = 0): Expression { + // eslint-disable-next-line no-constant-condition + while (1) { + // TS handle + if ( + this.requirePlugin(ParserPlugin.TypeScript) && + (this.isContextKeyword("as") || this.isContextKeyword("satisfies")) + ) { + const isSatisfies = this.isContextKeyword("satisfies"); + this.nextToken(); + const typeNode = this.parseTSTypeNode(); + if (isSatisfies) { + left = Factory.createTSSatisfiesExpression( + left, + typeNode, + cloneSourcePosition(left.start), + this.getLastTokenEndPositon(), + ); + } else { + left = Factory.createTSAsExpression( + left, + typeNode, + cloneSourcePosition(left.start), + this.getLastTokenEndPositon(), + ); + } + continue; + } + const currentOp = this.getToken(); + if (!this.isBinaryOps(currentOp) || this.getBinaryPrecedence(currentOp) < lastPre) { + break; + } + this.nextToken(); + let right = this.parseUnaryOrPrivateName(); + const nextOp = this.getToken(); + if (this.isBinaryOps(nextOp) && this.getBinaryPrecedence(nextOp) > this.getBinaryPrecedence(currentOp)) { + right = this.parseBinaryOps(right, this.getBinaryPrecedence(nextOp)); + } + this.staticSematicForBinaryExpr(currentOp, nextOp, left, right); + left = Factory.createBinaryExpression( + left, + right, + currentOp as BinaryOperatorKinds, + cloneSourcePosition(left.start), + cloneSourcePosition(right.end), + ); + } + return left; +} +export function staticSematicForBinaryExpr( + this: Parser, + currentOps: SyntaxKinds, + nextOps: SyntaxKinds, + left: Expression, + right: Expression, +) { + if (isPrivateName(right) || (isPrivateName(left) && currentOps !== SyntaxKinds.InKeyword)) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, left.start); + } + if (left.parentheses) { + return; + } + if (currentOps === SyntaxKinds.ExponOperator) { + if (isUnaryExpression(left) || isAwaitExpression(left)) { + // recoverable error + this.raiseError(ErrorMessageMap.v8_error_expont_operator_need_parans, left.start); + } + } + // if currentOp is nullish, next is logical or not + // if current Ops is logical, check next is nullish or not + if ( + currentOps === SyntaxKinds.NullishOperator && + (nextOps === SyntaxKinds.LogicalANDOperator || nextOps === SyntaxKinds.LogicalOROperator) + ) { + // recoverable error + this.raiseError(ErrorMessageMap.v8_error_nullish_require_parans, left.end); + } + if ( + nextOps === SyntaxKinds.NullishOperator && + (currentOps === SyntaxKinds.LogicalANDOperator || currentOps === SyntaxKinds.LogicalOROperator) + ) { + // recoverable error + this.raiseError(ErrorMessageMap.v8_error_nullish_require_parans, left.end); + } +} +export function parseUnaryOrPrivateName(this: Parser): Expression { + if (this.match(SyntaxKinds.PrivateName)) { + const privateName = this.parsePrivateName(); + this.usePrivateName(privateName.name, privateName.start); + return privateName; + } + if ( + (this.match(SyntaxKinds.LtOperator) || this.match(SyntaxKinds.BitwiseLeftShiftOperator)) && + !this.requirePlugin(ParserPlugin.JSX) + ) { + const start = this.getStartPosition(); + const typeArguments = this.parseTSTypeParameterInstantiation(false); + const expression = this.parseUnaryExpression(); + return Factory.createTSTypeAssertionExpression( + expression, + typeArguments.params[0], + start, + this.getLastTokenEndPositon(), + ); + } + return this.parseUnaryExpression(); +} +export function parseUnaryExpression(this: Parser): Expression { + if (this.match(UnaryOperators)) { + const operator = this.getToken() as UnaryOperatorKinds; + const isDelete = operator === SyntaxKinds.DeleteKeyword; + const start = this.getStartPosition(); + this.nextToken(); + let argument; + if (isDelete) { + this.enterDelete(); + argument = this.parseUnaryExpression(); + this.exitDelete(); + } else { + argument = this.parseUnaryExpression(); + } + const unaryExpr = Factory.createUnaryExpression( + argument, + operator, + start, + cloneSourcePosition(argument.end), + ); + this.staticSematicEarlyErrorForUnaryExpression(unaryExpr); + return unaryExpr; + } + if (this.match(SyntaxKinds.AwaitKeyword) && this.isCurrentScopeParseAwaitAsExpression()) { + return this.parseAwaitExpression(); + } + return this.parseUpdateExpression(); +} +// 13.5.1.1 +export function staticSematicEarlyErrorForUnaryExpression(this: Parser, expr: UnaryExpression) { + if (this.isInStrictMode() && expr.operator === SyntaxKinds.DeleteKeyword && isIdentifer(expr.argument)) { + this.raiseError( + ErrorMessageMap.syntax_error_applying_the_delete_operator_to_an_unqualified_name_is_deprecated, + expr.start, + ); + } +} +export function parseAwaitExpression(this: Parser) { + if (this.isInParameter()) { + this.raiseError( + ErrorMessageMap.extra_error_await_expression_can_not_used_in_parameter_list, + this.getStartPosition(), + ); + } + const start = this.getStartPosition(); + this.nextToken(); + this.recordScope(ExpressionScopeKind.AwaitExpressionImParameter, start); + const argu = this.parseUnaryExpression(); + return Factory.createAwaitExpression(argu, start, cloneSourcePosition(argu.end)); +} +export function parseUpdateExpression(this: Parser): Expression { + if (this.match(UpdateOperators)) { + const operator = this.getToken() as UpdateOperatorKinds; + const start = this.getStartPosition(); + this.nextToken(); + const argument = this.parseWithLHSLayer(() => this.parseLeftHandSideExpression()); + this.checkExpressionAsLeftValue(argument); + return Factory.createUpdateExpression(argument, operator, true, start, cloneSourcePosition(argument.end)); + } + const [argument, scope] = this.parseWithCatpureLayer(() => this.parseLeftHandSideExpression()); + if (this.match(UpdateOperators) && !this.getLineTerminatorFlag()) { + this.checkStrictModeScopeError(scope); + this.checkExpressionAsLeftValue(argument); + const operator = this.getToken() as UpdateOperatorKinds; + const end = this.getEndPosition(); + this.nextToken(); + return Factory.createUpdateExpression( + argument, + operator, + false, + cloneSourcePosition(argument.start), + end, + ); + } + return argument; +} +/** + * Parse Left hand side Expression. This syntax is reference babel function, which is simplify original syntax of TS39, + * 'this' and super 'super' would be meanful when apper at start of atoms, which can be handle by parseAtoms. NewExpression + * is a spacial case , because it can not using optionalChain, so i handle it into a atom. + * ``` + * LeftHandSideExpression := Atoms '?.' CallExpression + * := Atoms '?.' MemberExpression + * := Atoms TagTemplateExpression + * ``` + * @returns {Expression} + */ +export function parseLeftHandSideExpression(this: Parser): Expression { + let base = this.parsePrimaryExpression(); + if (this.shouldEarlyReturn(base)) { + return base; + } + const state: LefthansSideParseState = { + shouldStop: false, + hasOptional: false, + optional: false, + abortLastTime: false, + }; + while (!state.shouldStop) { + state.optional = false; + if ( + this.requirePlugin(ParserPlugin.TypeScript) && + !this.getLineTerminatorFlag() && + this.match(SyntaxKinds.LogicalNOTOperator) + ) { + this.nextToken(); + base = Factory.createTSNonNullExpression( + base, + cloneSourcePosition(base.start), + this.getLastTokenEndPositon(), + ); + continue; + } + if (state.abortLastTime) { + base = this.parseLeftHandSideExpressionWithoutTypeArguments(base, state); + } else { + base = this.parseLeftHandSideExpressionWithTypeArguments(base, state); + } + } + if (state.abortLastTime && state.hasOptional) { + throw this.createUnexpectError(); + } + if (state.hasOptional) { + return Factory.createChainExpression( + base, + cloneSourcePosition(base.start), + cloneSourcePosition(base.end), + ); + } + return base; +} +export function parseLeftHandSideExpressionWithTypeArguments( + this: Parser, + base: Expression, + state: LefthansSideParseState, +) { + this.parseQuestionDotOfLeftHandSideExpression(state); + const result = this.parseTypeArgumentsOfLeftHandSideExpression(state); + const [typeArguments, abort] = result; + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + // callexpression + base = this.parseCallExpression(base, state.optional, typeArguments); + } else if (this.match([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]) || state.optional) { + // memberexpression + if (typeArguments) { + abort(); + } else { + base = this.parseMemberExpression(base, state.optional); + } + } else if (this.match(SyntaxKinds.TemplateHead) || this.match(SyntaxKinds.TemplateNoSubstitution)) { + // tag template expressuin + if (state.hasOptional) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_tag_template_expression_can_not_use_option_chain, + this.getStartPosition(), + ); + } + base = this.parseTagTemplateExpression(base); + } else { + if (typeArguments) { + const currentToken = this.getToken(); + if ( + currentToken === SyntaxKinds.GtOperator || + currentToken === SyntaxKinds.BitwiseRightShiftOperator || + (currentToken !== SyntaxKinds.ParenthesesLeftPunctuator && + this.canStartExpression() && + !this.getLineTerminatorFlag()) + ) { + abort(); + } else { + // base == TSInstanitExpr + base = Factory.createTSInstantiationExpression( + base, + typeArguments, + cloneSourcePosition(base.start), + this.getLastTokenEndPositon(), + ); + if ( + this.match(SyntaxKinds.DotOperator) || + (this.match(SyntaxKinds.QustionDotOperator) && + this.lookahead().kind !== SyntaxKinds.ParenthesesLeftPunctuator) + ) { + // TODO: should error + } + } + } else { + state.shouldStop = true; + } + } + return base; +} +export function parseLeftHandSideExpressionWithoutTypeArguments( + this: Parser, + base: Expression, + state: LefthansSideParseState, +) { + this.parseQuestionDotOfLeftHandSideExpression(state); + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + // callexpression + state.abortLastTime = false; + base = this.parseCallExpression(base, state.optional, undefined); + } else if (this.match([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]) || state.optional) { + // memberexpression + state.abortLastTime = false; + base = this.parseMemberExpression(base, state.optional); + } else if (this.match(SyntaxKinds.TemplateHead) || this.match(SyntaxKinds.TemplateNoSubstitution)) { + // tag template expressuin + if (state.hasOptional) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_tag_template_expression_can_not_use_option_chain, + this.getStartPosition(), + ); + } + state.abortLastTime = false; + base = this.parseTagTemplateExpression(base); + } else { + state.shouldStop = true; + } + return base; +} +export function parseQuestionDotOfLeftHandSideExpression(this: Parser, state: LefthansSideParseState) { + if (this.match(SyntaxKinds.QustionDotOperator)) { + state.optional = true; + state.hasOptional = true; + this.nextToken(); + } +} +export function parseTypeArgumentsOfLeftHandSideExpression( + this: Parser, + state: LefthansSideParseState, +): [TSTypeParameterInstantiation | undefined, () => void] { + let typeArguments: TSTypeParameterInstantiation | undefined = undefined; + let abort = () => {}; + if (this.match(SyntaxKinds.LtOperator) || this.match(SyntaxKinds.BitwiseLeftShiftOperator)) { + const result = this.tryParse(() => { + return this.tryParseTSTypeParameterInstantiation(false); + }); + if (result) { + typeArguments = result?.[0]; + abort = () => { + this.lexer.restoreState(result[1], result[2]); + this.errorHandler.restoreTryFail(result[3]); + state.abortLastTime = true; + }; + return [typeArguments, abort]; + } + //return undefined; + } + return [typeArguments, abort]; +} +/** + * Check is a assignable left value + * @param expression + * @returns + */ +export function checkExpressionAsLeftValue(this: Parser, expression: ModuleItem) { + if (this.isAssignable(expression)) { + return; + } + this.raiseError(ErrorMessageMap.invalid_left_value, expression.start); +} +/** + * Parse CallExpression + * ``` + * CallExpresion := GivenBase(base, optional) '(' Arguments ')' + * ``` + * @param {Expression} callee base expression + * @param {boolean} optional is this call optional ? + * @returns {Expression} + */ +export function parseCallExpression( + this: Parser, + callee: Expression, + optional: boolean, + typeParameter: TSTypeParameterInstantiation | undefined, +): Expression { + this.expectButNotEat([SyntaxKinds.ParenthesesLeftPunctuator]); + const { nodes, end } = this.parseArguments(); + return Factory.createCallExpression( + callee, + nodes, + typeParameter, + optional, + cloneSourcePosition(callee.start), + end, + ); +} +/** + * // TODO: remove possble dep of arrow function paramemter need to call this function. + * + * Parse Arguments, used by call expression, and arrow function paramemter. + * ``` + * Arguments := '(' ArgumentList ')' + * ArgumentList := ArgumentList AssigmentExpression + * := ArgumentList SpreadElement + * := AssignmentExpression + * := SpreadElement + * ``` + */ +export function parseArguments(this: Parser) { + return this.parseArgumentsBase(false); +} +export function parseArgumentsWithType(this: Parser) { + return this.parseArgumentsBase(true); +} +export function parseArgumentsBase( + this: Parser, + acceptType: boolean, +): ASTArrayWithMetaData & { + trailingComma: boolean; + typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; +} { + const { start } = this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + let isStart = true; + // TODO: refactor logic to remove shoulStop + const callerArguments: Array = []; + const typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> = []; + let trailingComma = false; + while (!this.match(SyntaxKinds.ParenthesesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + isStart = false; + if (this.match(SyntaxKinds.CommaToken)) { + // trailing comma + this.raiseError(ErrorMessageMap.extra_error_unexpect_trailing_comma, this.getStartPosition()); + this.nextToken(); + } + } else { + trailingComma = true; + this.expect(SyntaxKinds.CommaToken); + } + // case 1: ',' following by ')' + if (this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + break; + } + trailingComma = false; + // case 2: ',' following by SpreadElement, maybe follwed by ',' + if (this.match(SyntaxKinds.SpreadOperator)) { + const spreadElementStart = this.getStartPosition(); + this.nextToken(); + let argu: Expression = Factory.createSpreadElement( + this.parseAssignmentExpressionAllowIn(), + spreadElementStart, + this.getLastTokenEndPositon(), + ); + if (acceptType) { + typeAnnotations.push(this.parsePossibleArugmentType()); + argu = this.parsePossibleArugmentDefaultValue(argu); + } + callerArguments.push(argu); + continue; + } + // case 3 : ',' AssigmentExpression + let argu = this.parseAssignmentExpressionAllowIn(); + if (acceptType) { + typeAnnotations.push(this.parsePossibleArugmentType()); + argu = this.parsePossibleArugmentDefaultValue(argu); + } + callerArguments.push(argu); + } + const { end } = this.expect(SyntaxKinds.ParenthesesRightPunctuator); + return { + end, + start, + nodes: callerArguments, + typeAnnotations: typeAnnotations.length === 0 ? undefined : typeAnnotations, + trailingComma, + }; +} + +/** + * Parse with base, this different between parseLeftHandSideExpression is + * that parseMemberExpression would only eat a `atom` of chain of expression. + * ``` + * MemberExpression := GivenBase(base ,optional) '.' IdentiferWithKeyword + * := GivenBase(base, optional) '[' Expreession ']' + * := GivenBase(base, optional) IdentiferWithKeyword + * // for last condition, optional prope must be True + * ``` + * @param {Expression} base base expression + * @param {boolean} optional is base expression contain a optional + * @returns {Expression} + */ +export function parseMemberExpression(this: Parser, base: Expression, optional: boolean): Expression { + if (!this.match(SyntaxKinds.DotOperator) && !this.match(SyntaxKinds.BracketLeftPunctuator) && !optional) { + throw this.createUnreachError([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]); + } + // if start with dot, must be a access property, can not with optional. + // because optional means that last token is `?.` + if (this.match(SyntaxKinds.DotOperator) && !optional) { + this.expect(SyntaxKinds.DotOperator); + const property = this.parseMemberExpressionProperty(); + + return Factory.createMemberExpression( + false, + base, + property, + optional, + cloneSourcePosition(base.start), + cloneSourcePosition(property.end), + ); + } + // if start with `[`, must be computed property access. + else if (this.match(SyntaxKinds.BracketLeftPunctuator)) { + this.expect(SyntaxKinds.BracketLeftPunctuator); + const property = this.parseExpressionAllowIn(); + const { end } = this.expect(SyntaxKinds.BracketRightPunctuator); + return Factory.createMemberExpression( + true, + base, + property, + optional, + cloneSourcePosition(base.start), + end, + ); + } else { + // because parseLeftHandSideExpression would eat optional mark (QustionDotToken) frist, so maybe there + // is not dot or `[` for start a member expression, so we can check optional is + const property = this.parseMemberExpressionProperty(); + return Factory.createMemberExpression( + false, + base, + property, + optional, + cloneSourcePosition(base.start), + cloneSourcePosition(property.end), + ); + } +} +export function parseMemberExpressionProperty(this: Parser) { + let property: Expression | PrivateName; + if (this.match(SyntaxKinds.PrivateName)) { + property = this.parsePrivateName(); + this.usePrivateName(property.name, property.start); + if (this.isInDelete()) { + this.raiseError( + ErrorMessageMap.syntax_error_applying_the_delete_operator_to_an_unqualified_name_is_deprecated, + property.start, + ); + } + } else { + property = this.parseIdentifierName(); + } + return property; +} +export function parseTagTemplateExpression(this: Parser, base: Expression) { + const quasi = this.parseTemplateLiteral(true); + return Factory.createTagTemplateExpression( + base, + quasi, + cloneSourcePosition(base.end), + cloneSourcePosition(quasi.end), + ); +} +export function parsePrimaryExpression(this: Parser): Expression { + switch (this.getToken()) { + case SyntaxKinds.LtOperator: + return this.parseJSXElementOrJSXFragment(false); + case SyntaxKinds.DivideOperator: + case SyntaxKinds.DivideAssignOperator: + return this.parseRegexLiteral(); + case SyntaxKinds.NullKeyword: + return this.parseNullLiteral(); + case SyntaxKinds.UndefinedKeyword: + return this.parseUndefinedLiteral(); + case SyntaxKinds.TrueKeyword: + case SyntaxKinds.FalseKeyword: + return this.parseBoolLiteral(); + case SyntaxKinds.DecimalLiteral: + return this.parseDecimalLiteral(); + case SyntaxKinds.DecimalBigIntegerLiteral: + return this.parseDecimalBigIntegerLiteral(); + case SyntaxKinds.NonOctalDecimalLiteral: + return this.parseNonOctalDecimalLiteral(); + case SyntaxKinds.BinaryIntegerLiteral: + return this.parseBinaryIntegerLiteral(); + case SyntaxKinds.BinaryBigIntegerLiteral: + return this.parseBinaryBigIntegerLiteral(); + case SyntaxKinds.OctalIntegerLiteral: + return this.parseOctalIntegerLiteral(); + case SyntaxKinds.OctalBigIntegerLiteral: + return this.parseOctalBigIntegerLiteral(); + case SyntaxKinds.HexIntegerLiteral: + return this.parseHexIntegerLiteral(); + case SyntaxKinds.HexBigIntegerLiteral: + return this.parseHexBigIntegerLiteral(); + case SyntaxKinds.LegacyOctalIntegerLiteral: + return this.parseLegacyOctalIntegerLiteral(); + case SyntaxKinds.StringLiteral: + return this.parseStringLiteral(); + case SyntaxKinds.TemplateHead: + case SyntaxKinds.TemplateNoSubstitution: + return this.parseTemplateLiteral(false); + case SyntaxKinds.ImportKeyword: { + const { kind } = this.lookahead(); + if (kind === SyntaxKinds.DotOperator) return this.parseImportMeta(); + if (kind === SyntaxKinds.ParenthesesLeftPunctuator) { + return this.parseImportCall(); + } + throw this.createUnexpectError(); + } + case SyntaxKinds.NewKeyword: { + const { kind } = this.lookahead(); + if (kind === SyntaxKinds.DotOperator) { + return this.parseNewTarget(); + } + return this.parseNewExpression(); + } + case SyntaxKinds.SuperKeyword: + return this.parseSuper(); + case SyntaxKinds.ThisKeyword: + return this.parseThisExpression(); + case SyntaxKinds.BracesLeftPunctuator: + return this.parseObjectExpression(); + case SyntaxKinds.BracketLeftPunctuator: + return this.parseArrayExpression(); + case SyntaxKinds.FunctionKeyword: + return this.parseFunctionExpression(false); + case SyntaxKinds.AtPunctuator: { + return this.parseClassExpression(this.parseDecoratorList()); + } + case SyntaxKinds.ClassKeyword: + return this.parseClassExpression(null); + case SyntaxKinds.ParenthesesLeftPunctuator: + return this.parseCoverExpressionORArrowFunction(); + // TODO: consider wrap as function or default case ? + case SyntaxKinds.PrivateName: + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, this.getStartPosition()); + return this.parsePrivateName(); + // return parsePrivateName(); + case SyntaxKinds.Identifier: + case SyntaxKinds.LetKeyword: + case SyntaxKinds.AwaitKeyword: + case SyntaxKinds.YieldKeyword: { + const { kind, lineTerminatorFlag: flag } = this.lookahead(); + // case 0: identifier `=>` ... + if (kind === SyntaxKinds.ArrowOperator && this.canParseAsArrowFunction()) { + const [[argus, strictModeScope], arrowExprScope] = this.parseWithArrowExpressionScope(() => + this.parseWithLHSLayerReturnScope(() => [this.parseIdentifierReference()]), + ); + if (this.getLineTerminatorFlag()) { + this.raiseError( + ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, + this.getStartPosition(), + ); + } + this.enterArrowFunctionBodyScope(); + const arrowExpr = this.parseArrowFunctionExpression( + { + nodes: argus, + start: argus[0].start, + end: argus[0].end, + trailingComma: false, + typeAnnotations: undefined, + }, + undefined, + strictModeScope, + arrowExprScope, + ); + this.exitArrowFunctionBodyScope(); + return arrowExpr; + } + if (this.getSourceValue() === "async") { + // case 1: `async` `function` ==> must be async function () {} + if (kind === SyntaxKinds.FunctionKeyword && !this.getEscFlag()) { + const { value, start, end } = this.expect(SyntaxKinds.Identifier); + if (this.getLineTerminatorFlag()) { + return Factory.createIdentifier(value, start, end, undefined, undefined); + } + return this.parseFunctionExpression(true); + } + if (this.canParseAsArrowFunction()) { + // case 2 `async` `(` + // There might be two case : + // 1.frist case is there are line change after async, which make this case into + // call expression + // 2.second case is not change line after async, making it become async arrow + // function. + // -------------------------- + if (kind === SyntaxKinds.ParenthesesLeftPunctuator) { + const containEsc = this.getEscFlag(); + const id = this.parseIdentifierReference(); // async + // TODO: better accept type param or argument to create async arrow or async call. + const [[meta, strictModeScope], arrowExprScope] = this.parseWithArrowExpressionScope(() => + this.parseWithCatpureLayer(() => this.parseArgumentsWithType()), + ); + if ( + flag || + (!this.match(SyntaxKinds.ArrowOperator) && + !(this.match(SyntaxKinds.ColonPunctuator) && this.requirePlugin(ParserPlugin.TypeScript))) + ) { + return Factory.createCallExpression( + id, + meta.nodes, + undefined, + false, + cloneSourcePosition(id.start), + meta.end, + ); + } + if (containEsc) { + this.raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, id.start); + } + const returnType = this.tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); + this.enterArrowFunctionBodyScope(true); + const arrowFunExpr = this.parseArrowFunctionExpression( + meta, + undefined, + strictModeScope, + arrowExprScope, + ); + this.exitArrowFunctionBodyScope(); + arrowFunExpr.returnType = returnType; + return arrowFunExpr; + } + // case 2-TS: `async` `<` or async `<<` + // for `<`, is possible to be a + // - typeParameter: for a async function declaration + // - typeArguments: for a function call which callee is `async` + // - binary expression: `async < literal-item`. + if (kind === SyntaxKinds.LtOperator) { + const id = this.parseIdentifierReference(); + const typeParameterResult = this.tryParse(() => this.parseTSTypeParameterDeclaration(false)); + if (typeParameterResult) { + // there + const [ + [{ start, end, nodes, trailingComma, typeAnnotations }, strictModeScope], + arrowExprScope, + ] = this.parseWithArrowExpressionScope(() => + this.parseWithCatpureLayer(() => this.parseArgumentsWithType()), + ); + const returnType = this.tryParseTSReturnTypeOrTypePredicateForArrowExpression(true, nodes); + if (this.match(SyntaxKinds.ArrowOperator)) { + this.enterArrowFunctionBodyScope(true); + const arrowExpr = this.parseArrowFunctionExpression( + { start, end, nodes, trailingComma, typeAnnotations }, + undefined, + strictModeScope, + arrowExprScope, + ); + this.exitArrowFunctionBodyScope(); + arrowExpr.returnType = returnType; + arrowExpr.typeParameters = typeParameterResult[0]; + return arrowExpr; + } + this.abortTryParseResult( + typeParameterResult[1], + typeParameterResult[2], + typeParameterResult[3], + ); + } + const typeArgumentResult = this.tryParse(() => this.parseTSTypeParameterInstantiation(false)); + if (typeArgumentResult) { + const typeArguments = typeArgumentResult[0]; + const callArguments = this.parseArguments().nodes; + return Factory.createCallExpression( + id, + callArguments, + typeArguments, + false, + cloneSourcePosition(id.start), + this.getLastTokenEndPositon(), + ); + } + return id; + } + // for '<<', it must be `async< async as function call + if (kind === SyntaxKinds.BitwiseLeftShiftOperator) { + const id = this.parseIdentifierReference(); + this.lexer.reLexLtRelateToken(); + const typeArguments = this.parseTSTypeParameterInstantiation(false); + const callArguments = this.parseArguments().nodes; + return Factory.createCallExpression( + id, + callArguments, + typeArguments, + false, + cloneSourcePosition(id.start), + this.getLastTokenEndPositon(), + ); + } + // case 3: `async` `Identifer` ... + // There might be two case : + // 1.frist case is there are line change after async, or there is no arrow operator, + // which make this case into async as identifier + // 2.second case is not change line after async, making it become async arrow + // function. + if ( + kind === SyntaxKinds.Identifier || + kind === SyntaxKinds.YieldKeyword || + kind === SyntaxKinds.AwaitKeyword + ) { + // async followed by line break + if (flag) { + return this.parseIdentifierReference(); + } + const isAsyncContainUnicode = this.getEscFlag(); + const { start, end } = this.expect(SyntaxKinds.Identifier); // eat async + const { kind: maybeArrowToken } = this.lookahead(); + // there is no arrow operator. + if (maybeArrowToken !== SyntaxKinds.ArrowOperator) { + return Factory.createIdentifier("async", start, end, undefined, undefined); + } + if (isAsyncContainUnicode) { + this.raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, start); + } + const [[argus, strictModeScope], arrowExprScope] = this.parseWithArrowExpressionScope(() => + this.parseWithCatpureLayer(() => [this.parseIdentifierReference()]), + ); + if (this.getLineTerminatorFlag()) { + this.raiseError( + ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, + this.getStartPosition(), + ); + } + this.enterArrowFunctionBodyScope(true); + const arrowExpr = this.parseArrowFunctionExpression( + { + nodes: argus, + start: argus[0].start, + end: argus[0].end, + trailingComma: false, + typeAnnotations: undefined, + }, + undefined, + strictModeScope, + arrowExprScope, + ); + this.exitArrowFunctionBodyScope(); + return arrowExpr; + } + } + } + return this.parseIdentifierReference(); + } + default: + throw this.createUnexpectError(); + } +} diff --git a/web-infras/parser/src/parser/js/index.ts b/web-infras/parser/src/parser/js/index.ts new file mode 100644 index 00000000..20f44b3a --- /dev/null +++ b/web-infras/parser/src/parser/js/index.ts @@ -0,0 +1,6 @@ +export * from "./expression"; +export * from "./literal"; +export * from "./pattern"; +export * from "./declaration"; +export * from "./statement"; +export * from "./module"; diff --git a/web-infras/parser/src/parser/js/literal.ts b/web-infras/parser/src/parser/js/literal.ts new file mode 100644 index 00000000..1533df0a --- /dev/null +++ b/web-infras/parser/src/parser/js/literal.ts @@ -0,0 +1,1587 @@ +import { + RegexLiteral, + SyntaxKinds, + Factory, + Identifier, + SourcePosition, + PrivateName, + NumberLiteral, + TemplateElement, + MetaProperty, + cloneSourcePosition, + CallExpression, + Expression, + isCallExpression, + ThisExpression, + PropertyName, + isIdentifer, + isStringLiteral, + ObjectMethodDefinition, + ModuleItem, + NumericLiteralKinds, + isNumnerLiteral, + Decorator, + ClassMethodDefinition, + ObjectAccessor, + ClassAccessor, + ClassConstructor, + MethodDefinition, + Keywords, + Pattern, + isRestElement, + isPrivateName, + isSpreadElement, + ArrorFunctionExpression, + PropertyDefinition, + FunctionBody, + TSParameter, + TSTypeAnnotation, + isAssignmentPattern, +} from "web-infra-common"; +import { ParserPlugin } from "@/src/parser/config"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { ExpressionScopeKind } from "@/src/parser/scope/type"; +import { Parser } from ".."; +import { StrictModeScope } from "@/src/parser/scope/strictModeScope"; +import { AsyncArrowExpressionScope } from "@/src/parser/scope/arrowExprScope"; +import { + ASTArrayWithMetaData, + IdentiferWithKeyworArray, + PreserveWordSet, + KeywordSet, +} from "@/src/parser/type"; + +export function parseRegexLiteral(this: Parser): RegexLiteral { + this.expectButNotEat([SyntaxKinds.DivideOperator, SyntaxKinds.DivideAssignOperator]); + const startWithAssignOperator = this.match(SyntaxKinds.DivideAssignOperator); + const start = this.getStartPosition(); + // eslint-disable-next-line prefer-const + let { pattern, flag } = this.readRegex(); + this.nextToken(); + if (startWithAssignOperator) { + pattern = "=" + pattern; + } + return Factory.createRegexLiteral(pattern, flag, start, this.getEndPosition()); +} +/** + * IdentifierReference, IdentifierName and BindingIdentifier is not samething in the + * spec. + * - IdentifierReference is a id in Lval or Rval + * - IdentifierName is a property of member expression or object, class + * - BindingIdentifier is a lval. + * @returns {Identifier} + */ +export function parseIdentifierReference(this: Parser): Identifier { + this.expectButNotEat([ + SyntaxKinds.Identifier, + SyntaxKinds.AwaitKeyword, + SyntaxKinds.YieldKeyword, + SyntaxKinds.LetKeyword, + ]); + // sematic check for a binding identifier + let identifer: Identifier; + switch (this.getToken()) { + // for most of yield keyword, if it should treat as identifier, + // it should not in generator function. + case SyntaxKinds.YieldKeyword: { + const { value, start, end } = this.expect(SyntaxKinds.YieldKeyword); + this.staticSematicForIdentifierAsYield(start); + identifer = Factory.createIdentifier(value, start, end, undefined, undefined); + break; + } + // for most of await keyword, if it should treat as identifier, + // it should not in async function. + case SyntaxKinds.AwaitKeyword: { + const { value, start, end } = this.expect(SyntaxKinds.AwaitKeyword); + this.staticSematicForIdentifierAsAwait(start); + identifer = Factory.createIdentifier(value, start, end, undefined, undefined); + break; + } + // let maybe treat as identifier in not strict mode, and not lexical binding declaration. + // so lexical binding declaration should implement it's own checker logical with parseIdentifierWithKeyword + case SyntaxKinds.LetKeyword: { + const { value, start, end } = this.expect(SyntaxKinds.LetKeyword); + this.staticSematicForIdentifierAsLet(start); + identifer = Factory.createIdentifier(value, start, end, undefined, undefined); + break; + } + case SyntaxKinds.Identifier: { + const { value, start, end } = this.expect(SyntaxKinds.Identifier); + this.staticSematicForIdentifierDefault(value, start); + identifer = Factory.createIdentifier(value, start, end, undefined, undefined); + break; + } + default: { + throw this.createUnexpectError(); + } + } + return identifer; +} +/** + * Yield only could be used as a identifier when + * + * - it is not in strict mode. + * - not in a generator context. + * + * record it's usage for defer check. + * @param {SourcePosition} start + */ +export function staticSematicForIdentifierAsYield(this: Parser, start: SourcePosition) { + if (this.isCurrentScopeParseYieldAsExpression() || this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, start); + } + this.recordScope(ExpressionScopeKind.YieldIdentifier, start); +} +/** + * Await only could be used as identifirt when + * + * - it is not in module mode + * - not in async context + * + * record it's usgae for defer check. + * @param {SourcePosition} start + */ +export function staticSematicForIdentifierAsAwait(this: Parser, start: SourcePosition) { + if (this.isCurrentScopeParseAwaitAsExpression() || this.config.sourceType === "module") { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + start, + ); + } + // skip if is using await in class property name in async context + if (this.isDirectToClassScope() && !this.isInPropertyName()) { + return; + } + this.recordScope(ExpressionScopeKind.AwaitIdentifier, start); +} +/** + * Let only could be used as identifirt when + * + * - it is not in strict mode + * + * record it's usgae for defer check. + * @param {SourcePosition} start + */ +export function staticSematicForIdentifierAsLet(this: Parser, start: SourcePosition) { + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); + } + this.recordScope(ExpressionScopeKind.LetIdentifiier, start); +} +/** + * Checking the usage for arguments and eval, presverveword + * + * - for presverveword, only could be used when not in strict mode + * - for argument, can not used when + * 1. in strict mode, and in the lhs + * 2. in strict mode, not in function. + * - for eval, can not used when + * 1. in strict mode, and in the lhs + * + * record it's usgae for defer check. + * @param {SourcePosition} start + */ +export function staticSematicForIdentifierDefault(this: Parser, value: string, start: SourcePosition) { + const isPreserveWord = PreserveWordSet.has(value); + if (isPreserveWord) { + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); + } + this.recordScope(ExpressionScopeKind.PresveredWordIdentifier, start); + } + if (value === "arguments") { + if (this.isInStrictMode()) { + if (!this.isEncloseInFunction() && !this.isInPropertyName()) { + // invalud usage + this.raiseError(ErrorMessageMap.syntax_error_arguments_is_not_valid_in_fields, start); + } + if (this.strictModeScopeRecorder.isInLHS()) { + // invalid assignment + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); + } + } + this.recordScope(ExpressionScopeKind.ArgumentsIdentifier, start); + } + if (value === "eval") { + if (this.isInStrictMode() && this.strictModeScopeRecorder.isInLHS()) { + this.raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); + } + this.recordScope(ExpressionScopeKind.EvalIdentifier, start); + } +} +/** + * Relatedly loose function for parseIdentifier, it not only can parse identifier, + * it also can parse keyword as identifier. + * @returns {Identifier} + */ +export function parseIdentifierName(this: Parser): Identifier { + const { value, start, end } = this.expect(IdentiferWithKeyworArray); + return Factory.createIdentifier(value, start, end, undefined, undefined); +} +/** + * ECMA spec has every strict rule to private name, but in this parser, most of + * strict rule check is implemented by callee, there we only gonna check is in + * class scope or not. + * @returns {PrivateName} + */ +export function parsePrivateName(this: Parser): PrivateName { + const { value, start, end } = this.expect(SyntaxKinds.PrivateName); + if (!this.isInClassScope()) { + this.raiseError(ErrorMessageMap.syntax_error_unexpected_hash_used_outside_of_class_body, start); // semantics check for private + } + return Factory.createPrivateName(value, start, end); +} +export function parseNullLiteral(this: Parser) { + const { start, end } = this.expect(SyntaxKinds.NullKeyword); + return Factory.createNullLiteral(start, end); +} +export function parseUndefinedLiteral(this: Parser) { + const { start, end } = this.expect(SyntaxKinds.UndefinedKeyword); + return Factory.createUndefinedLiteral(start, end); +} +export function parseDecimalLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.DecimalLiteral); + return Factory.createDecimalLiteral(value, start, end); +} +export function parseDecimalBigIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.DecimalBigIntegerLiteral); + return Factory.createDecimalBigIntegerLiteral(value, start, end); +} +export function parseNonOctalDecimalLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.NonOctalDecimalLiteral); + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.Syntax_error_0_prefixed_octal_literals_are_deprecated, start); + } + return Factory.createNonOctalDecimalLiteral(value, start, end); +} +export function parseBinaryIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.BinaryIntegerLiteral); + return Factory.createBinaryIntegerLiteral(value, start, end); +} +export function parseBinaryBigIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.BinaryBigIntegerLiteral); + return Factory.createBinaryBigIntegerLiteral(value, start, end); +} +export function parseOctalIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.OctalIntegerLiteral); + return Factory.createOctalIntegerLiteral(value, start, end); +} +export function parseOctalBigIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.OctalBigIntegerLiteral); + return Factory.createOctBigIntegerLiteral(value, start, end); +} +export function parseHexIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.HexIntegerLiteral); + return Factory.createHexIntegerLiteral(value, start, end); +} +export function parseHexBigIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.HexBigIntegerLiteral); + return Factory.createHexBigIntegerLiteral(value, start, end); +} +export function parseLegacyOctalIntegerLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.LegacyOctalIntegerLiteral); + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.Syntax_error_0_prefixed_octal_literals_are_deprecated, start); + } + return Factory.createLegacyOctalIntegerLiteral(value, start, end); +} +export function parseNumericLiteral(this: Parser): NumberLiteral { + switch (this.getToken()) { + case SyntaxKinds.DecimalLiteral: + return this.parseDecimalLiteral(); + case SyntaxKinds.DecimalBigIntegerLiteral: + return this.parseDecimalBigIntegerLiteral(); + case SyntaxKinds.NonOctalDecimalLiteral: + return this.parseNonOctalDecimalLiteral(); + case SyntaxKinds.BinaryIntegerLiteral: + return this.parseBinaryIntegerLiteral(); + case SyntaxKinds.BinaryBigIntegerLiteral: + return this.parseBinaryBigIntegerLiteral(); + case SyntaxKinds.OctalIntegerLiteral: + return this.parseOctalIntegerLiteral(); + case SyntaxKinds.OctalBigIntegerLiteral: + return this.parseOctalBigIntegerLiteral(); + case SyntaxKinds.HexIntegerLiteral: + return this.parseHexIntegerLiteral(); + case SyntaxKinds.HexBigIntegerLiteral: + return this.parseHexBigIntegerLiteral(); + case SyntaxKinds.LegacyOctalIntegerLiteral: + return this.parseLegacyOctalIntegerLiteral(); + default: + throw this.createUnexpectError(); + } +} +export function parseStringLiteral(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.StringLiteral); + return Factory.createStringLiteral(value, start, end); +} +export function parseBoolLiteral(this: Parser) { + const { start, end, value } = this.expect([SyntaxKinds.TrueKeyword, SyntaxKinds.FalseKeyword]); + return Factory.createBoolLiteral(value === "true" ? true : false, start, end); +} +export function parseTemplateLiteral(this: Parser, tagged: boolean) { + if (!this.match([SyntaxKinds.TemplateHead, SyntaxKinds.TemplateNoSubstitution])) { + throw this.createUnreachError([SyntaxKinds.TemplateHead, SyntaxKinds.TemplateNoSubstitution]); + } + const templateLiteralStart = this.getStartPosition(); + if (this.match(SyntaxKinds.TemplateNoSubstitution)) { + if (!tagged && this.lexer.getTemplateLiteralTag()) { + this.raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, this.getStartPosition()); + } + const value = this.getSourceValue(); + const templateLiteralEnd = this.getEndPosition(); + this.nextToken(); + return Factory.createTemplateLiteral( + [Factory.createTemplateElement(value, true, templateLiteralStart, templateLiteralEnd)], + [], + templateLiteralStart, + templateLiteralEnd, + ); + } + this.nextToken(); + const expressions = [this.parseExpressionAllowIn()]; + const quasis: Array = []; + while ( + !this.match(SyntaxKinds.TemplateTail) && + this.match(SyntaxKinds.TemplateMiddle) && + !this.match(SyntaxKinds.EOFToken) + ) { + if (!tagged && this.lexer.getTemplateLiteralTag()) { + this.raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, this.getStartPosition()); + } + quasis.push( + Factory.createTemplateElement( + this.getSourceValue(), + false, + this.getStartPosition(), + this.getEndPosition(), + ), + ); + this.nextToken(); + expressions.push(this.parseExpressionAllowIn()); + } + if (this.match(SyntaxKinds.EOFToken)) { + throw this.createUnexpectError(); + } + if (!tagged && this.lexer.getTemplateLiteralTag()) { + this.raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, this.getStartPosition()); + } + quasis.push( + Factory.createTemplateElement( + this.getSourceValue(), + true, + this.getStartPosition(), + this.getEndPosition(), + ), + ); + const templateLiteralEnd = this.getEndPosition(); + this.nextToken(); + return Factory.createTemplateLiteral(quasis, expressions, templateLiteralStart, templateLiteralEnd); +} +/** + * Parse import meta property + * ``` + * ImportMeta := import . meta + * ``` + * @returns {MetaProperty} + */ +export function parseImportMeta(this: Parser): MetaProperty { + const { start, end } = this.expect(SyntaxKinds.ImportKeyword); + this.expect(SyntaxKinds.DotOperator); + const ecaFlag = this.getEscFlag(); + const property = this.parseIdentifierReference(); + this.staticSematicForImportMeta(property, ecaFlag, start); + return Factory.createMetaProperty( + Factory.createIdentifier("import", start, end, undefined, undefined), + property, + start, + cloneSourcePosition(property.end), + ); +} +/** + * Sematic check for import meta + * - import member expression's property only could be meta + * - meta should be a contextual keyword + * - import meta can't use in script mode + * @param property + * @param ecaFlag + */ +export function staticSematicForImportMeta( + this: Parser, + property: Identifier, + ecaFlag: boolean, + start: SourcePosition, +) { + if (property.name !== "meta") { + this.raiseError( + ErrorMessageMap.babel_error_the_only_valid_meta_property_for_import_is_import_meta, + property.start, + ); + } + if (ecaFlag) { + this.raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, start); + } + if (this.config.sourceType === "script") { + this.raiseError(ErrorMessageMap.babel_error_import_meta_may_appear_only_with_source_type_module, start); + } +} +/** + * Parse Import call + * ``` + * ImportCall := import ( AssignmentExpression[+In], (optional support attribute) ) + * ``` + * @returns {CallExpression} + */ +export function parseImportCall(this: Parser): CallExpression { + const { start, end } = this.expect(SyntaxKinds.ImportKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const argument = this.parseAssignmentExpressionAllowIn(); + const option = this.parseImportAttributeOptional(); + const { end: finalEnd } = this.expect(SyntaxKinds.ParenthesesRightPunctuator); + return Factory.createCallExpression( + Factory.createImport(start, end), + option ? [argument, option] : [argument], + undefined, + false, + cloneSourcePosition(start), + cloneSourcePosition(finalEnd), + ); +} +/** + * Parse import attribute (stage 3 syntax) + * ref: https://github.com/tc39/proposal-import-attributes + * @returns + */ +export function parseImportAttributeOptional(this: Parser): Expression | null { + if ( + !this.requirePlugin(ParserPlugin.ImportAssertions) && + !this.requirePlugin(ParserPlugin.ImportAttribute) + ) { + return null; + } + if (!this.match(SyntaxKinds.CommaToken)) { + return null; + } + this.nextToken(); + if (this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + return null; + } + const option = this.parseAssignmentExpressionAllowIn(); + if (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + } + return option; +} +/** + * Parse new target + * ``` + * NewTarget := new . target + * ``` + * @returns {MetaProperty} + */ +export function parseNewTarget(this: Parser): MetaProperty { + const { start, end } = this.expect(SyntaxKinds.NewKeyword); + this.expect(SyntaxKinds.DotOperator); + this.staticSematicForNewTarget(start); + const targetStart = this.getStartPosition(); + const targetEnd = this.getEndPosition(); + this.nextToken(); + return Factory.createMetaProperty( + Factory.createIdentifier("new", start, end, undefined, undefined), + Factory.createIdentifier("target", targetStart, targetEnd, undefined, undefined), + start, + targetEnd, + ); +} +export function staticSematicForNewTarget(this: Parser, start: SourcePosition) { + if (!this.isContextKeyword("target")) { + // recoverable error + throw this.createUnexpectError(); + } + if (!this.config.allowNewTargetOutsideFunction && this.isTopLevel() && !this.isInClassScope()) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_new_target_can_only_be_used_in_class_or_function_scope, + start, + ); + } +} +/** + * Parse New Expression, the callee part of new expression is a trick one, + * this is not a member expression, it can not contain qustion dot or call + * expression. + * ``` + * NewExpression := 'new' NewExpression + * := 'new' MemberExpressionWithoutOptional Arugment? + * ``` + * @returns {Expression} + */ +export function parseNewExpression(this: Parser): Expression { + const { start } = this.expect(SyntaxKinds.NewKeyword); + // maybe is new.target + if (this.match(SyntaxKinds.NewKeyword) && this.lookahead().kind !== SyntaxKinds.DotOperator) { + return this.parseNewExpression(); + } + let base = this.parsePrimaryExpression(); + this.staticSematicForBaseInNewExpression(base); + base = this.parseNewExpressionCallee(base); + const typeArgument = this.tryParseTSTypeParameterInstantiationForNewExpression(); + if (!this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + // accpect New XXX -> No argument + return Factory.createNewExpression(base, [], typeArgument, start, cloneSourcePosition(base.end)); + } + const { end, nodes } = this.parseArguments(); + return Factory.createNewExpression(base, nodes, typeArgument, start, end); +} +/** + * The base of new expression can not be a import call expression, if must be a import + * call expression, it must be have a paran. + * @param {Expression} base + */ +export function staticSematicForBaseInNewExpression(this: Parser, base: Expression) { + if (!base.parentheses && isCallExpression(base) && base.callee.kind === SyntaxKinds.Import) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_cannot_use_new_with_import, base.start); + } +} +/** + * Parse the callee of new expression, base of new expression can not + * be a call expression or a qustion dot expression. + * @param {Expression} base + * @returns + */ +export function parseNewExpressionCallee(this: Parser, base: Expression): Expression { + while ( + this.match(SyntaxKinds.DotOperator) || + this.match(SyntaxKinds.BracketLeftPunctuator) || + this.match(SyntaxKinds.QustionDotOperator) + ) { + if (this.match(SyntaxKinds.QustionDotOperator)) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_constructors_in_after_an_optional_chain_are_not_allowed, + this.getStartPosition(), + ); + this.nextToken(); + base = this.parseMemberExpression(base, true); + continue; + } + base = this.parseMemberExpression(base, false); + } + return base; +} +export function tryParseTSTypeParameterInstantiationForNewExpression(this: Parser) { + if (this.match(SyntaxKinds.LtOperator) || this.match(SyntaxKinds.BitwiseLeftShiftOperator)) { + const result = this.tryParse(() => this.tryParseTSTypeParameterInstantiation(false)); + if ( + this.match([ + SyntaxKinds.ParenthesesLeftPunctuator, + SyntaxKinds.TemplateNoSubstitution, + SyntaxKinds.TemplateHead, + ]) + ) { + return result?.[0]; + } + if ( + this.match([ + SyntaxKinds.LtOperator, + SyntaxKinds.GtOperator, + SyntaxKinds.MinusOperator, + SyntaxKinds.MinusOperator, + ]) || + !(this.getLineTerminatorFlag() || this.isBinaryOps(this.getToken()) || !this.canStartExpression()) + ) { + if (result) this.abortTryParseResult(result[1], result[2], result[3]); + return; + } + return result?.[0]; + } +} +// TODO: finish all possible +// reference: https://github.com/oxc-project/oxc/blob/eac34b676f473f79bcb4a55d6322d0b02a15d6fa/crates/oxc_parser/src/ts/types.rs#L1382 +export function canStartExpression(this: Parser) { + switch (this.getToken()) { + case SyntaxKinds.Identifier: + case SyntaxKinds.PlusOperator: + case SyntaxKinds.MinusOperator: + return true; + case SyntaxKinds.TrueKeyword: + case SyntaxKinds.FalseKeyword: + case SyntaxKinds.NullKeyword: + case SyntaxKinds.UndefinedKeyword: + case SyntaxKinds.StringLiteral: + return true; + default: + return false; + } +} +/** + * Parse super expression, only parse the arguments and super or a first level + * of access of member expression. Contain sematic check: + * - Super call only valid in ctor. + * - super property can be used in any method of class. + * ``` + * SuperCall := super argument + * SuperProperty := super[Expression] + * := super.IdentifierName + * ``` + * @returns {Expression} + */ +export function parseSuper(this: Parser): Expression { + if (!this.isCurrentClassExtend()) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_super_is_only_valid_in_derived_class_constructors, + this.getStartPosition(), + ); + } + const { start: keywordStart, end: keywordEnd } = this.expect([SyntaxKinds.SuperKeyword]); + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + if (!this.lexicalScopeRecorder.isInCtor()) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_call_super_outside_of_ctor, keywordStart); + } + const typeArguments = this.tryParseTSTypeParameterInstantiation(false); + const { nodes, end: argusEnd } = this.parseArguments(); + return Factory.createCallExpression( + Factory.createSuper(keywordStart, keywordEnd), + nodes, + typeArguments, + false, + cloneSourcePosition(keywordStart), + argusEnd, + ); + } + let property: Expression; + let isComputed = false; + let end: SourcePosition; + switch (this.getToken()) { + case SyntaxKinds.QustionDotOperator: + // recoverable error. + this.raiseError(ErrorMessageMap.babel_invalid_usage_of_super_call, this.getStartPosition()); + // eslint-disable-next-line no-fallthrough + case SyntaxKinds.DotOperator: { + this.nextToken(); + if (this.match(SyntaxKinds.PrivateName)) { + property = this.parsePrivateName(); + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_private_fields_cant_be_accessed_on_super, property.start); + } else { + property = this.parseIdentifierName(); + } + end = cloneSourcePosition(property.end); + break; + } + case SyntaxKinds.BracketLeftPunctuator: { + this.nextToken(); + property = this.parseExpressionAllowIn(); + isComputed = true; + ({ end } = this.expect(SyntaxKinds.BracketRightPunctuator)); + break; + } + default: + throw this.createUnexpectError(); + } + return Factory.createMemberExpression( + isComputed, + Factory.createSuper(keywordStart, keywordEnd), + property, + false, + cloneSourcePosition(keywordStart), + end, + ); +} +/** + * Parse this expression, only eat `this` token + * @returns {ThisExpression} + */ +export function parseThisExpression(this: Parser): ThisExpression { + const { start, end } = this.expect([SyntaxKinds.ThisKeyword]); + return Factory.createThisExpression(start, end); +} +/** + * Parse ObjectLiterial, object property just a list of PropertyDefinition. + * ### Trailing Comma problem + * object expression maybe transform into `ObjectPattern` in `AssignmentExpression` + * so i add a trailing comma field to object expression to AST struct for `toAssignment` + * function. + * ``` + * ObjectLiteral := '{' PropertyDefinitionList ','? '}' + * PropertyDefinitionList := PropertyDefinitionList ',' PropertyDefinition + * := PropertyDefinition + * ``` + * @returns {Expression} actually is `ObjectExpression` + */ +export function parseObjectExpression(this: Parser): Expression { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + let isStart = true; + const propertyDefinitionList: Array = []; + let trailingComma = false; + const protoPropertyNames: Array = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + propertyDefinitionList.push(this.parsePropertyDefinition(protoPropertyNames)); + isStart = false; + continue; + } + this.expect(SyntaxKinds.CommaToken); + if (this.match(SyntaxKinds.BracesRightPunctuator) || this.match(SyntaxKinds.EOFToken)) { + trailingComma = true; + break; + } + propertyDefinitionList.push(this.parsePropertyDefinition(protoPropertyNames)); + } + this.staticSematicEarlyErrorForObjectExpression(protoPropertyNames); + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createObjectExpression(propertyDefinitionList, trailingComma, start, end); +} +/** + * Adding `__proto__` property key to duplication set, if object expression transform to pattern + * duplication of `__proto__` is ok, but when is not pattern, it not a correct syntax. + * @param {Array} protoPropertyNames + * reference: 13.2.5.1 + */ +export function staticSematicEarlyErrorForObjectExpression( + this: Parser, + protoPropertyNames: Array, +) { + if (protoPropertyNames.length > 1) { + for (let index = 1; index < protoPropertyNames.length; ++index) + this.context.propertiesProtoDuplicateSet.add(protoPropertyNames[index]); + } +} +/** + * Helper for property definition to record the object property which property is + * `__proto__`, since duplication of `__proto__` is a error. + * @param protoPropertyNames + * @param propertyName + * @param isComputed + * @returns + */ +export function staticSematicHelperRecordPropertyNameForEarlyError( + protoPropertyNames: Array, + propertyName: PropertyName, + isComputed: boolean, +) { + if (isComputed) return; + if ( + (isIdentifer(propertyName) && propertyName.name === "__proto__") || + (isStringLiteral(propertyName) && propertyName.value === "__proto__") + ) { + protoPropertyNames.push(propertyName); + return; + } +} +/** + * Parse PropertyDefinition + * ``` + * PropertyDefinition := MethodDefintion + * := Property + * := SpreadElement + * Property := PropertyName '=' AssignmentExpression + * SpreadElement := '...' AssigmentExpression + * ``` + * ### How to parse + * 1. start with `...` operator, must be SpreadElment + * 2. start with privatename is syntax error, but it is common, so we handle it as sematic problem. + * 3. check is start with method modifier prefix by helper function `checkIsMethodStartWithModifier`. + * 4. default case, property name with colon operator. + * 5. this is speical case, we accept a coverinit in object expression, because object expression + * might be transform into object pattern, so we mark accept it and mark it. it coverInit of + * object expression is not transform by `toAssignment` function, it would throw error in the + * end of `parseProgram` + * #### ref: https://tc39.es/ecma262/#prod-PropertyDefinition + */ +export function parsePropertyDefinition( + this: Parser, + protoPropertyNameLocations: Array, +): PropertyDefinition { + // semantics check for private + if (this.match(SyntaxKinds.PrivateName)) { + // TODO: make it recoverable + throw this.createMessageError(ErrorMessageMap.extra_error_private_field_in_object_expression); + } + // spreadElement + if (this.match(SyntaxKinds.SpreadOperator)) { + const spreadElementStart = this.getStartPosition(); + this.nextToken(); + const expr = this.parseAssignmentExpressionAllowIn(); + return Factory.createSpreadElement(expr, spreadElementStart, cloneSourcePosition(expr.end)); + } + // start with possible method modifier + if (this.checkIsMethodStartWithModifier()) { + return this.parseMethodDefintion() as ObjectMethodDefinition; + } + // otherwise, it would be Property start with PropertyName or MethodDeinftion start with PropertyName + const isComputedRef = { isComputed: false }; + const propertyName = this.parsePropertyName(isComputedRef); + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + return this.parseMethodDefintion(false, [ + propertyName, + isComputedRef.isComputed, + ]) as ObjectMethodDefinition; + } + if (isComputedRef.isComputed || this.match(SyntaxKinds.ColonPunctuator)) { + staticSematicHelperRecordPropertyNameForEarlyError( + protoPropertyNameLocations, + propertyName, + isComputedRef.isComputed, + ); + this.nextToken(); + const expr = this.parseAssignmentExpressionAllowIn(); + return Factory.createObjectProperty( + propertyName, + expr, + isComputedRef.isComputed, + false, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(expr.end), + ); + } + this.recordIdentifierValue(propertyName); + if (this.match(SyntaxKinds.AssginOperator)) { + staticSematicHelperRecordPropertyNameForEarlyError( + protoPropertyNameLocations, + propertyName, + isComputedRef.isComputed, + ); + this.nextToken(); + const expr = this.parseAssignmentExpressionAllowIn(); + const property = Factory.createObjectProperty( + propertyName, + expr, + isComputedRef.isComputed, + false, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(expr.end), + ); + this.context.propertiesInitSet.add(property); + return property; + } + this.staticSematicForShortedPropertyNameInObjectLike(propertyName); + this.staticSematicForShortedPropertyNameInObjectExpression(propertyName as Identifier); + return Factory.createObjectProperty( + propertyName, + undefined, + isComputedRef.isComputed, + true, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(propertyName.end), + ); +} +export function recordIdentifierValue(this: Parser, propertyName: ModuleItem) { + if (isIdentifer(propertyName)) { + if (propertyName.name === "await") { + this.recordScope(ExpressionScopeKind.AwaitIdentifier, propertyName.start); + } + if (propertyName.name === "yield") { + this.recordScope(ExpressionScopeKind.YieldIdentifier, propertyName.start); + } + if (propertyName.name === "arguments") { + this.recordScope(ExpressionScopeKind.ArgumentsIdentifier, propertyName.start); + } + if (propertyName.name === "eval") { + this.recordScope(ExpressionScopeKind.EvalIdentifier, propertyName.start); + } + if (propertyName.name === "let") { + this.recordScope(ExpressionScopeKind.LetIdentifiier, propertyName.start); + } + if (PreserveWordSet.has(propertyName.name)) { + this.recordScope(ExpressionScopeKind.PresveredWordIdentifier, propertyName.start); + } + } +} +/** + * Parse PropertyName, using context ref which passed in to record this property is computed or not. + * + * ### Extra action need for callee + * In this function, we accept keywrod as property name, but when the property name use as a shorted + * property name, it will be a syntax error, so father syntax check is needed handle by callee. + * ``` + * PropertyName := Identifer (IdentifierName, not BindingIdentifier) + * := NumberLiteral + * := StringLiteral + * := ComputedPropertyName + * ComputedPropertyName := '[' AssignmentExpression ']' + * ``` + * ref: https://tc39.es/ecma262/#prod-PropertyName + * @returns {PropertyName} + */ +export function parsePropertyName(this: Parser, isComputedRef: { isComputed: boolean }): PropertyName { + this.expectButNotEat([ + SyntaxKinds.BracketLeftPunctuator, + SyntaxKinds.StringLiteral, + ...IdentiferWithKeyworArray, + ...NumericLiteralKinds, + ]); + switch (this.getToken()) { + case SyntaxKinds.StringLiteral: { + return this.parseStringLiteral(); + } + case SyntaxKinds.BracketLeftPunctuator: { + this.nextToken(); + this.lexicalScopeRecorder.enterPropertyName(); + const expr = this.parseAssignmentExpressionAllowIn(); + this.lexicalScopeRecorder.exitPropertyName(); + this.expect(SyntaxKinds.BracketRightPunctuator); + isComputedRef.isComputed = true; + return expr; + } + default: { + if (this.match(NumericLiteralKinds)) { + return this.parseNumericLiteral(); + } + // propty name is a spical test of binding identifier. + // if `await` and `yield` is propty name with colon (means assign), it dose not affected by scope. + if (this.match(IdentiferWithKeyworArray)) { + const identifer = this.parseIdentifierName(); + return identifer; + } + throw this.createUnexpectError(); + } + } +} +/** + * Sematic check when a property name is shorted property + * @param {PropertyName} propertyName + * @returns + */ +export function staticSematicForShortedPropertyNameInObjectLike(this: Parser, propertyName: PropertyName) { + if (isStringLiteral(propertyName) || isNumnerLiteral(propertyName)) { + // recoverable error. + this.raiseError( + ErrorMessageMap.extra_error_when_binding_pattern_property_name_is_literal_can_not_be_shorted, + propertyName.start, + ); + } +} +/** + * Like `staticCheckForPropertyNameAsSingleBinding` for object pattern, when shorted property in + * object expression, if will no longer just + * @param {PropertyName} propertyName + * @returns + */ +export function staticSematicForShortedPropertyNameInObjectExpression( + this: Parser, + propertyName: Identifier, +) { + if (propertyName.name === "await") { + if (this.isCurrentScopeParseAwaitAsExpression() || this.config.sourceType === "module") { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + propertyName.start, + ); + } + return; + } + if (propertyName.name === "yield") { + if (this.isCurrentScopeParseYieldAsExpression() || this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, propertyName.start); + } + return; + } + if (KeywordSet.has(propertyName.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); + } + if (PreserveWordSet.has(propertyName.name) && this.isInStrictMode()) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_reserved_word, propertyName.start); + } +} +/** Parse MethodDefintion, this method should allow using when in class or in object literal. + * 1. ClassElement can be PrivateName, when it used in object literal, it should throw a error. + * 2. It should parse modifier when `withPropertyName` is falsey. + * + * ### Parse Modifier + * we parse modifier according to the pattern `('set' | 'get')? 'async' '*' ClassElement `, this + * is not a regulat syntax, it may accept wrong syntax, but by accept more case then spec, we cam + * provide more concies sematic message to developer. + * ``` + * MethodDefintion := ClassElementName BindingList FunctionBody + * := AyncMethod + * := GeneratorMethod + * := AsyncGeneratorMethod + * := 'set' ClassElementName BindingList FunctionBody + * := 'get' ClassElementName '('')' FunctionBody + * AyncMethod := 'async' ClassElementName BindingList FunctionBody + * GeneratorMethod := '*' ClassElementName BindingList FunctionBody + * AsyncGeneratorMethod := 'async' '*' ClassElementName BindingList FunctionBody + * ClassElementName := PropertyName + * := PrivateName + * ``` + * @param {boolean} inClass is used in class or not. + * @param {PropertyName | PrivateName | undefined } withPropertyName parse methodDeinfition with exited propertyName or not + * @param {boolean} isStatic + * @returns {ObjectMethodDefinition | ClassMethodDefinition | ObjectAccessor | ClassAccessor | ClassConstructor} + */ +export function parseMethodDefintion( + this: Parser, + inClass: boolean = false, + withPropertyName: [PropertyName | PrivateName, boolean] | undefined = undefined, + isStatic: boolean = false, + decorators: Decorator[] | null = null, +): ObjectMethodDefinition | ClassMethodDefinition | ObjectAccessor | ClassAccessor | ClassConstructor { + if (!this.checkIsMethodStartWithModifier() && !withPropertyName) { + throw this.createUnreachError([SyntaxKinds.MultiplyAssignOperator, SyntaxKinds.Identifier]); + } + /** + * Step 1 : if not with propertyName , parse modifier frist, otherwise, if with propertyName, it shouldn't do anything. + * structure would be like : ('set' | 'get')? 'async' '*' PropertyName ...., this strcuture isn't match the spec. + * but in this structure, we can detect some syntax error more concies, like set and get can not use with async + * or generator. + */ + let type: MethodDefinition["type"] = "method"; + let isAsync: MethodDefinition["async"] = false; + let generator: MethodDefinition["generator"] = false; + let computed: MethodDefinition["computed"] = withPropertyName ? withPropertyName[1] : false; + let start: SourcePosition | null = null; + let propertyName: PropertyName; + if (!withPropertyName) { + // frist, is setter or getter + if (this.isContextKeyword("set")) { + type = "set"; + start = this.getStartPosition(); + this.nextToken(); + } else if (this.isContextKeyword("get")) { + type = "get"; + start = this.getStartPosition(); + this.nextToken(); + } + // second, parser async and generator + const { kind } = this.lookahead(); + if (this.isContextKeyword("async") && kind !== SyntaxKinds.ParenthesesLeftPunctuator) { + start = this.getStartPosition(); + isAsync = true; + this.nextToken(); + if (this.match(SyntaxKinds.MultiplyOperator)) { + this.nextToken(); + generator = true; + } + } else if (this.match(SyntaxKinds.MultiplyOperator)) { + start = this.getStartPosition(); + generator = true; + this.nextToken(); + } + if (this.match(SyntaxKinds.PrivateName)) { + propertyName = this.parsePrivateName(); + this.defPrivateName( + propertyName.name, + propertyName.start, + type === "method" ? "other" : isStatic ? `static-${type}` : type, + ); + } else { + const isComputedRef = { isComputed: false }; + propertyName = this.parsePropertyName(isComputedRef); + computed = isComputedRef.isComputed; + } + if (!start) start = cloneSourcePosition(propertyName.start); + } else { + start = cloneSourcePosition(withPropertyName[0].start); + propertyName = withPropertyName[0]; + } + const isCtor = inClass && !isStatic && !computed && helperIsPropertyNameIsCtor(propertyName); + if (isCtor) { + this.lexicalScopeRecorder.enterCtor(); + if (this.lexicalScopeRecorder.testAndSetCtor()) { + this.raiseError(ErrorMessageMap.v8_error_a_class_may_only_have_one_constructor, propertyName.start); + } + } + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + this.enterFunctionScope(isAsync, generator); + const [parmas, scope] = this.parseWithCatpureLayer(() => this.parseFunctionParam()); + const returnType = this.tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); + const body = this.parseFunctionBody(); + this.postStaticSematicEarlyErrorForStrictModeOfFunction(null, scope); + this.exitFunctionScope(true); + if (isCtor) this.lexicalScopeRecorder.exitCtor(); + /** + * Step 2: semantic and more concise syntax check instead just throw a unexpect + * token error. + */ + this.staticSematicEarlyErrorForClassMethodDefinition( + propertyName, + inClass, + isStatic, + isAsync, + generator, + parmas, + type, + ); + /** + * Step 3 return based on type, if accessor or methodDefintion + */ + if (inClass) { + if (isCtor) { + if (decorators) { + this.raiseError( + ErrorMessageMap.babel_error_decorators_can_not_be_used_with_a_constructor, + decorators[0].start, + ); + } + return Factory.createClassConstructor( + propertyName as ClassConstructor["key"], + body, + parmas, + returnType, + start as SourcePosition, + cloneSourcePosition(body.end), + ); + } + if (type === "set" || type === "get") { + return Factory.createClassAccessor( + propertyName, + body, + parmas, + typeParameters, + returnType, + type, + computed, + decorators, + start as SourcePosition, + cloneSourcePosition(body.end), + ); + } + return Factory.createClassMethodDefintion( + propertyName, + body, + parmas, + typeParameters, + returnType, + isAsync, + generator, + computed, + isStatic, + decorators, + start ? start : cloneSourcePosition(propertyName.start), + cloneSourcePosition(body.end), + ); + } + if (type === "set" || type === "get") { + return Factory.createObjectAccessor( + propertyName, + body, + parmas, + typeParameters, + returnType, + type, + computed, + start as SourcePosition, + cloneSourcePosition(body.end), + ); + } + return Factory.createObjectMethodDefintion( + propertyName, + body, + parmas, + typeParameters, + returnType, + isAsync, + generator, + computed, + start ? start : cloneSourcePosition(propertyName.start), + cloneSourcePosition(body.end), + ); +} +/** + * This is a helper function for object expression and class for determiate is property + * a method definition or not. + * + * Please notes that this function not only accept regualer syntax, but also accept something + * like set and get generator, it will left sematic job for `parseMetodDefinition` method. + * @returns {boolean} + */ +export function checkIsMethodStartWithModifier(this: Parser): boolean { + if (this.match(SyntaxKinds.MultiplyOperator)) { + return true; + } + const { kind, lineTerminatorFlag: flag } = this.lookahead(); + const isLookAheadValidatePropertyNameStart = + Keywords.find((keyword) => keyword === kind) || + kind === SyntaxKinds.Identifier || + kind === SyntaxKinds.PrivateName || + kind === SyntaxKinds.StringLiteral || + NumericLiteralKinds.includes(kind) || + kind === SyntaxKinds.BracketLeftPunctuator || + kind === SyntaxKinds.MultiplyOperator; + if (this.isContextKeyword("set") && isLookAheadValidatePropertyNameStart) { + return true; + } + if (this.isContextKeyword("get") && isLookAheadValidatePropertyNameStart) { + return true; + } + if (this.isContextKeyword("async") && isLookAheadValidatePropertyNameStart && !flag) { + return true; + } + return false; +} +function helperIsPropertyNameIsCtor(propertyName: PropertyName) { + switch (propertyName.kind) { + case SyntaxKinds.Identifier: { + return propertyName.name === "constructor"; + } + case SyntaxKinds.StringLiteral: { + return propertyName.value === "constructor"; + } + default: { + return false; + } + } +} +/** + * Spec def of class method, only implement some of spec. + * @param propertyName + * @param isClass + * @param isStatic + * @param isAsync + * @param isGenerator + * @param params + * @param type + * reference: https://tc39.es/ecma262/#sec-class-definitions-static-semantics-early-errors + */ +export function staticSematicEarlyErrorForClassMethodDefinition( + this: Parser, + propertyName: PropertyName, + isClass: boolean, + isStatic: boolean, + isAsync: boolean, + isGenerator: boolean, + params: Array, + type: MethodDefinition["type"], +) { + // general check + if (type === "get" && params.length > 0) { + this.raiseError(ErrorMessageMap.syntax_error_getter_functions_must_have_no_arguments, propertyName.start); + } + if (type === "set") { + if (params.length !== 1) { + this.raiseError(ErrorMessageMap.syntax_error_setter_functions_must_have_one_argument, params[0].start); + } + for (const param of params) { + if (isRestElement(param)) { + this.raiseError( + ErrorMessageMap.syntax_error_setter_functions_must_have_one_argument_not_rest, + param.start, + ); + } + } + } + if (type === "get" && (isAsync || isGenerator)) { + this.raiseError(ErrorMessageMap.extra_error_getter_can_not_be_async_or_generator, propertyName.start); + } + if (type === "set" && (isAsync || isGenerator)) { + this.raiseError(ErrorMessageMap.extra_error_setter_can_not_be_async_or_generator, propertyName.start); + } + // class check + if (isClass) { + let valueOfName: string | undefined, + isPrivate = false, + fromLiteral = false; // + if (isStringLiteral(propertyName)) { + valueOfName = propertyName.value; + fromLiteral = true; + } else if (isIdentifer(propertyName)) { + valueOfName = propertyName.name; + } else if (isPrivateName(propertyName)) { + valueOfName = propertyName.name; + isPrivate = true; + } + if (valueOfName === "constructor" && !fromLiteral) { + if (isAsync) { + this.raiseError( + ErrorMessageMap.v8_error_class_constructor_may_not_be_an_async_method, + propertyName.start, + ); + } + if (isGenerator) { + this.raiseError( + ErrorMessageMap.v8_error_class_constructor_may_not_be_a_generator, + propertyName.start, + ); + } + if (type === "get" || type === "set") { + this.raiseError( + ErrorMessageMap.v8_error_class_constructor_may_not_be_an_accessor, + propertyName.start, + ); + } + if (isPrivate) { + this.raiseError( + ErrorMessageMap.v8_error_class_may_not_have_a_private_field_named_constructor, + propertyName.start, + ); + } + } + if (valueOfName === "prototype" && !isPrivate && type === "method" && isStatic) { + this.raiseError( + ErrorMessageMap.v8_error_class_may_not_have_static_property_named_prototype, + propertyName.start, + ); + } + } +} +export function parseArrayExpression(this: Parser) { + const { start } = this.expect(SyntaxKinds.BracketLeftPunctuator); + const elements: Array = []; + let tralingComma = false; + let isStart = true; + while (!this.match(SyntaxKinds.BracketRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + if (this.match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { + tralingComma = true; + break; + } + if (this.match(SyntaxKinds.CommaToken)) { + elements.push(null); + continue; + } + if (this.match(SyntaxKinds.SpreadOperator)) { + const start = this.getStartPosition(); + this.nextToken(); + const expr = this.parseAssignmentExpressionAllowIn(); + elements.push(Factory.createSpreadElement(expr, start, cloneSourcePosition(expr.end))); + } else { + const expr = this.parseAssignmentExpressionAllowIn(); + elements.push(expr); + } + } + const { end } = this.expect(SyntaxKinds.BracketRightPunctuator); + return Factory.createArrayExpression(elements, start, end, tralingComma); +} +export function parseFunctionExpression(this: Parser, isAsync: boolean) { + this.enterFunctionScope(isAsync); + const funcExpr = this.parseFunction(true); + this.exitFunctionScope(false); + return Factory.transFormFunctionToFunctionExpression(funcExpr); +} +export function parseClassExpression(this: Parser, decoratorList: Decorator[] | null) { + return Factory.transFormClassToClassExpression(this.parseClass(decoratorList)); +} +export function parseCoverExpressionORArrowFunction(this: Parser) { + const possibleBeArrow = this.canParseAsArrowFunction(); + this.expectButNotEat(SyntaxKinds.ParenthesesLeftPunctuator); + const [[{ start, end, nodes, trailingComma, typeAnnotations }, strictModeScope], arrowExprScope] = + this.parseWithArrowExpressionScope(() => this.parseWithCatpureLayer(() => this.parseArgumentsWithType())); + const returnType = this.tryParseTSReturnTypeOrTypePredicateForArrowExpression(possibleBeArrow, nodes); + const notArrowExpression = !possibleBeArrow || !this.match(SyntaxKinds.ArrowOperator); + + if (notArrowExpression) { + // transfor to sequence or signal expression + for (const element of nodes) { + if (isSpreadElement(element)) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_rest_element_invalid, element.start); + } + } + if (trailingComma) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_sequence_expression_can_not_have_trailing_comma, end); + } + if (nodes.length === 1) { + nodes[0].parentheses = true; + return nodes[0]; + } + if (nodes.length === 0) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_empty_parentheses_expression, start); + } + const seq = Factory.createSequenceExpression(nodes, start, end); + seq.parentheses = true; + return seq; + } + this.enterArrowFunctionBodyScope(); + const arrowExpr = this.parseArrowFunctionExpression( + { start, end, nodes, trailingComma, typeAnnotations }, + undefined, + strictModeScope, + arrowExprScope, + ); + this.exitArrowFunctionBodyScope(); + arrowExpr.returnType = returnType; + return arrowExpr; +} +export function tryParseTSReturnTypeOrTypePredicateForArrowExpression( + this: Parser, + possibleBeArrow: boolean, + functionArguments: Expression[], +) { + let returnType: undefined | ArrorFunctionExpression["returnType"] = undefined; + if ( + possibleBeArrow && + simpleCheckIsArgumentCanBeAssignable(functionArguments) && + this.match(SyntaxKinds.ColonPunctuator) && + this.requirePlugin(ParserPlugin.TypeScript) + ) { + const result = this.tryParse(() => this.parseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator)); + if (!this.match(SyntaxKinds.ArrowOperator)) { + if (!result) throw this.createUnreachError(); + this.lexer.restoreState(result[1], result[2]); + this.errorHandler.restoreTryFail(result[3]); + } else { + returnType = result?.[0]; + } + } + return returnType; +} +export function simpleCheckIsArgumentCanBeAssignable(functionArguments: Array) { + for (const argu of functionArguments) { + switch (argu.kind) { + case SyntaxKinds.ObjectExpression: + case SyntaxKinds.ArrayExpression: + case SyntaxKinds.Identifier: + case SyntaxKinds.SpreadElement: + continue; + case SyntaxKinds.AssigmentExpression: + if (argu.operator === SyntaxKinds.AssginOperator) { + continue; + } + return false; + default: + return false; + } + } + return true; +} + +/** + * Parse arrow function expression, by given argumentlist and meta data, include + * - start of `(` + * - end of `)`, + * - is trailing comma of argument list + * please notes that this function accept with arguments, not paramemter list, so we need to + * transform arguments to parameter list, so we need to call `toAssignment` for each argument. + * and we also need to check is parameter duplicate. + * + * @param {ASTArrayWithMetaData & { trailingComma: boolean }} metaData + * @returns {ArrorFunctionExpression} + */ +export function parseArrowFunctionExpression( + this: Parser, + metaData: ASTArrayWithMetaData & { + trailingComma: boolean; + typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; + }, + typeParameters: ArrorFunctionExpression["typeParameters"], + strictModeScope: StrictModeScope, + arrowExprScope: AsyncArrowExpressionScope, +): ArrorFunctionExpression { + if (this.getLineTerminatorFlag()) { + // recoverable error + this.raiseError( + ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, + this.getStartPosition(), + ); + } + this.expect(SyntaxKinds.ArrowOperator); + const functionArguments = this.argumentToFunctionParams( + metaData.nodes, + metaData.trailingComma, + strictModeScope, + arrowExprScope, + ); + let body: Expression | FunctionBody; + let isExpression = false; + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + body = this.parseFunctionBody(); + } else { + body = this.parseAssignmentExpressionInheritIn(); + isExpression = true; + } + this.postStaticSematicEarlyErrorForStrictModeOfFunction(null, strictModeScope); + this.attachTypeToPattern(functionArguments, metaData.typeAnnotations); + return Factory.createArrowExpression( + isExpression, + body, + functionArguments, + typeParameters, + undefined, + this.isCurrentScopeParseAwaitAsExpression(), + cloneSourcePosition(metaData.start), + cloneSourcePosition(body.end), + ); +} +export function attachTypeToPattern( + this: Parser, + patterns: Array, + typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined, +) { + if (!typeAnnotations || !this.requirePlugin(ParserPlugin.TypeScript)) { + return; + } + let index = 0; + for (const pat of patterns) { + if (isAssignmentPattern(pat)) { + const tsPat = pat.left as TSParameter; + tsPat.typeAnnotation = typeAnnotations[index][0]; + tsPat.optional = typeAnnotations[index][1]; + continue; + } + (pat as TSParameter).typeAnnotation = typeAnnotations[index][0]; + (pat as TSParameter).optional = typeAnnotations[index][1]; + index++; + } +} +/** + * Transform function from expressions to patterns (arguments to params), checking syntax error + * by expression scope and post statci sematic check for pattern rule. + * - asycn arrow scope: check await expression, yield expression + * - strict mode scope: expression Rval to Lval has different rule in strict mode + * - multi spread vs multi rest: rest is unique, spread can be multi. + * @param functionArguments + * @param trailingComma + * @param strictModeScope + * @param arrowExprScope + * @returns + */ +export function argumentToFunctionParams( + this: Parser, + functionArguments: Array, + trailingComma: boolean, + strictModeScope: StrictModeScope, + arrowExprScope: AsyncArrowExpressionScope, +): Array { + const params = functionArguments.map((node) => this.exprToPattern(node, true)) as Array; + if ( + this.isCurrentScopeParseAwaitAsExpression() || + this.isParentFunctionAsync() || + this.isParentFunctionGenerator() + ) { + this.checkAsyncArrowExprScopeError(arrowExprScope); + } + if (this.isInStrictMode()) { + this.checkStrictModeScopeError(strictModeScope); + } + const isMultiSpread = this.postStaticSematicForArrowParamAfterTransform(params); + if (isMultiSpread && trailingComma) + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, + this.lexer.getLastTokenEndPositon(), + ); + // check as function params + this.setContextIfParamsIsSimpleParameterList(params); + return params; +} +export function postStaticSematicForArrowParamAfterTransform(this: Parser, params: Array) { + let flag = false; + params.forEach((param) => { + if (flag && isRestElement(param)) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, param.start); + return; + } + if (flag) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, param.start); + return; + } + if (!flag && isRestElement(param)) { + flag = true; + } + }); + return flag; +} diff --git a/web-infras/parser/src/parser/js/module.ts b/web-infras/parser/src/parser/js/module.ts new file mode 100644 index 00000000..f4efb516 --- /dev/null +++ b/web-infras/parser/src/parser/js/module.ts @@ -0,0 +1,565 @@ +import { + ClassDeclaration, + ClassExpression, + cloneSourcePosition, + ExportAllDeclaration, + ExportDeclaration, + ExportDefaultDeclaration, + ExportNamedDeclarations, + ExportSpecifier, + Factory, + Identifier, + ImportAttribute, + ImportDeclaration, + ImportDefaultSpecifier, + ImportNamespaceSpecifier, + ImportSpecifier, + isIdentifer, + isStringLiteral, + Keywords, + ModuleItem, + SourcePosition, + StringLiteral, + SyntaxKinds, +} from "web-infra-common"; +import { ParserPlugin } from "@/src/parser/config"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { ExportContext } from "@/src/parser/scope/lexicalScope"; +import { KeywordSet } from "@/src/parser/type"; +import { Parser } from "@/src/parser"; + +export function parseProgram(this: Parser) { + const body: Array = []; + this.enterProgram(); + while (!this.match(SyntaxKinds.EOFToken)) { + body.push(this.parseModuleItem()); + } + for (const propertyHasInit of this.context.propertiesInitSet) { + this.raiseError( + ErrorMessageMap.Syntax_error_Invalid_shorthand_property_initializer, + propertyHasInit.start, + ); + } + for (const duplicateProto of this.context.propertiesProtoDuplicateSet) { + this.raiseError( + ErrorMessageMap.syntax_error_property_name__proto__appears_more_than_once_in_object_literal, + duplicateProto.start, + ); + } + this.exitProgram(); + return Factory.createProgram( + body, + body.length === 0 ? this.getStartPosition() : cloneSourcePosition(body[0].start), + this.getEndPosition(), + ); +} +export function parseModuleItem(this: Parser): ModuleItem { + if (this.match(SyntaxKinds.AtPunctuator)) { + this.parseDecoratorListToCache(); + } + const token = this.getToken(); + switch (token) { + case SyntaxKinds.ImportKeyword: { + const { kind } = this.lookahead(); + if (kind === SyntaxKinds.DotOperator || kind === SyntaxKinds.ParenthesesLeftPunctuator) { + return this.parseStatementListItem(); + } + return this.parseImportDeclaration(); + } + case SyntaxKinds.ExportKeyword: + return this.parseExportDeclaration(); + default: + return this.parseStatementListItem(); + } +} +/** ================================================================================ + * Parse Import Declaration + * entry point: https://tc39.es/ecma262/#sec-imports + * ================================================================================== + */ +export function expectFormKeyword(this: Parser) { + if (this.getSourceValue() !== "from") { + throw this.createUnexpectError(); + } + if (this.getEscFlag()) { + this.raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, this.getStartPosition()); + } + this.nextToken(); +} +/** + * Parse Import Declaration + * ``` + * ImportDeclaration := 'import' ImportClasue FromClause WithClause? + * := 'import' StringLiteral WithClause? + * FromClause := 'from' StringLiteral + * ImportClause := ImportDefaultBinding + * := ImportNamesapce + * := ImportNamed + * := ImportDefaultBindling ',' ImportNamed + * := ImportDefaultBindling ',' ImportNamespace + * ``` + * - frist, eat import keyword + * 1. if it is string literal, must be `import StringLiteral` + * 2. if it start with `*`, must be import name space + * 3. if it start with '{', must be import named + * 4. fallback case: default import with import named or import namesspace + * or nothing + * @returns {ImportDeclaration} + */ +export function parseImportDeclaration(this: Parser): ImportDeclaration { + const { start } = this.expect(SyntaxKinds.ImportKeyword); + if (this.config.sourceType === "script") { + this.raiseError( + ErrorMessageMap.babel_error_import_and_export_may_appear_only_with_sourceType_module, + start, + ); + } + const specifiers: Array = []; + if (this.match(SyntaxKinds.StringLiteral)) { + const source = this.parseStringLiteral(); + const attributes = this.parseImportAttributesOptional(); + this.shouldInsertSemi(); + return Factory.createImportDeclaration( + specifiers, + source, + attributes, + start, + cloneSourcePosition(source.end), + ); + } + if (this.match(SyntaxKinds.MultiplyOperator)) { + specifiers.push(this.parseImportNamespaceSpecifier()); + this.expectFormKeyword(); + const source = this.parseStringLiteral(); + const attributes = this.parseImportAttributesOptional(); + this.shouldInsertSemi(); + return Factory.createImportDeclaration( + specifiers, + source, + attributes, + start, + cloneSourcePosition(source.end), + ); + } + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + this.parseImportSpecifiers(specifiers); + this.expectFormKeyword(); + const source = this.parseStringLiteral(); + const attributes = this.parseImportAttributesOptional(); + this.shouldInsertSemi(); + return Factory.createImportDeclaration( + specifiers, + source, + attributes, + start, + cloneSourcePosition(source.end), + ); + } + specifiers.push(this.parseImportDefaultSpecifier()); + if (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + this.parseImportSpecifiers(specifiers); + } else if (this.match(SyntaxKinds.MultiplyOperator)) { + specifiers.push(this.parseImportNamespaceSpecifier()); + } else { + throw this.createMessageError( + "import default specifier can only concat with namespace of import named specifier", + ); + } + } + this.expectFormKeyword(); + const source = this.parseStringLiteral(); + const attributes = this.parseImportAttributesOptional(); + this.shouldInsertSemi(); + return Factory.createImportDeclaration( + specifiers, + source, + attributes, + start, + cloneSourcePosition(source.end), + ); +} +/** + * Parse Default import binding + * ``` + * ImportDefaultBinding := Identifer + * ``` + * @returns {ImportDefaultSpecifier} + */ +export function parseImportDefaultSpecifier(this: Parser): ImportDefaultSpecifier { + const name = this.parseIdentifierReference(); + this.declarateLetSymbol(name.name, name.start); + return Factory.createImportDefaultSpecifier( + name, + cloneSourcePosition(name.start), + cloneSourcePosition(name.end), + ); +} +/** + * Parse namespace import + * ``` + * ImportNamespace := '*' 'as' Identifer + * ``` + * @returns {ImportNamespaceSpecifier} + */ +export function parseImportNamespaceSpecifier(this: Parser): ImportNamespaceSpecifier { + const { start } = this.expect(SyntaxKinds.MultiplyOperator); + if (!this.isContextKeyword("as")) { + this.raiseError(ErrorMessageMap.babel_error_unexpected_token_expected_as, this.getStartPosition()); + } + this.nextToken(); + const id = this.parseIdentifierReference(); + this.declarateLetSymbol(id.name, id.start); + return Factory.createImportNamespaceSpecifier(id, start, cloneSourcePosition(id.end)); +} +/** + * Parse Import Nameds + * ``` + * ImportNamed := '{' ImportList ','? '}' + * ImportList := [ ImportItem ] + * ImportItem := IdentiferWithKeyword + * := (Identifer | StringLiteral) 'as' Identifer + * ``` + * @param specifiers + * @return {void} + */ +export function parseImportSpecifiers( + this: Parser, + specifiers: Array, +): void { + this.expect(SyntaxKinds.BracesLeftPunctuator); + let isStart = true; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + if (this.match(SyntaxKinds.BracesRightPunctuator) || this.match(SyntaxKinds.EOFToken)) { + break; + } + const imported = this.parseModuleExportName(); + if (!this.isContextKeyword("as")) { + if (isIdentifer(imported) && KeywordSet.has(imported.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_unexpect_keyword_in_module_name, imported.start); + } else if (isStringLiteral(imported)) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_imported_binding, + imported.start, + ); + } + if (isIdentifer(imported)) this.declarateLetSymbol(imported.name, imported.start); + specifiers.push( + Factory.createImportSpecifier( + imported, + null, + cloneSourcePosition(imported.start), + cloneSourcePosition(imported.end), + ), + ); + continue; + } + this.nextToken(); + const local = this.parseIdentifierReference(); + this.declarateLetSymbol(local.name, local.start); + specifiers.push( + Factory.createImportSpecifier( + imported, + local, + cloneSourcePosition(imported.start), + cloneSourcePosition(local.end), + ), + ); + } + this.expect(SyntaxKinds.BracesRightPunctuator); +} +export function parseImportAttributesOptional(this: Parser): ImportAttribute[] | undefined { + if ( + (this.requirePlugin(ParserPlugin.ImportAttribute) && this.match(SyntaxKinds.WithKeyword)) || + (this.requirePlugin(ParserPlugin.ImportAssertions) && + this.match(SyntaxKinds.Identifier) && + this.getSourceValue() === "assert") + ) { + this.nextToken(); + return this.parseImportAttributes(); + } + return undefined; +} +export function parseImportAttributes(this: Parser): ImportAttribute[] { + this.expect(SyntaxKinds.BracesLeftPunctuator); + const attributes: Array = [this.parseImportAttribute()]; + while (!this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + this.expect(SyntaxKinds.CommaToken); + if (this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + break; + } + attributes.push(this.parseImportAttribute()); + } + this.expect(SyntaxKinds.BracesRightPunctuator); + return attributes; +} +export function parseImportAttribute(this: Parser): ImportAttribute { + const key = this.parseIdentifierName(); + this.expect(SyntaxKinds.ColonPunctuator); + const value = this.parseStringLiteral(); + return Factory.createImportAttribute( + key, + value, + cloneSourcePosition(key.start), + cloneSourcePosition(value.end), + ); +} +/** ================================================================================ + * Parse Export Declaration + * entry point: https://tc39.es/ecma262/#prod-ExportDeclaration + * ================================================================================== + */ +/** + * Parse Export Declaration + * ``` + * ExportDeclaration := 'export' ExportNamedDeclaration ';'? + * := 'export' ExportDefaultDeclaration + * := 'export' ExportAllDeclaration + * ExportNamedDeclaration := '{' ExportList '}' ('from' StringLiteral)? + * := Declaration + * := VarStatement + * ExportAllDeclaration := '*' 'from' StringLiteral + * := '*' 'as' Identifer 'from' StringLiteral + * ``` + * @returns {ExportDeclaration} + */ +export function parseExportDeclaration(this: Parser): ExportDeclaration { + this.setExportContext(ExportContext.InExport); + + const { start } = this.expect(SyntaxKinds.ExportKeyword); + if (this.config.sourceType === "script") { + this.raiseError( + ErrorMessageMap.babel_error_import_and_export_may_appear_only_with_sourceType_module, + start, + ); + } + let exportDeclaration: ExportDeclaration; + switch (this.getToken()) { + case SyntaxKinds.DefaultKeyword: { + exportDeclaration = this.parseExportDefaultDeclaration(start); + break; + } + case SyntaxKinds.MultiplyOperator: { + exportDeclaration = this.parseExportAllDeclaration(start); + break; + } + case SyntaxKinds.BracesLeftPunctuator: { + exportDeclaration = this.parseExportNamedDeclaration(start); + break; + } + default: { + const declaration = this.match(SyntaxKinds.VarKeyword) + ? this.parseVariableDeclaration() + : this.parseDeclaration(); + exportDeclaration = Factory.createExportNamedDeclaration( + [], + declaration, + null, + start, + cloneSourcePosition(declaration.end), + ); + break; + } + } + this.setExportContext(ExportContext.NotInExport); + return exportDeclaration; +} +/** + * Parse default export declaration + * ``` + * ``` + * @param {SourcePosition} start + * @returns {ExportDefaultDeclaration} + */ +export function parseExportDefaultDeclaration(this: Parser, start: SourcePosition): ExportDefaultDeclaration { + this.expect(SyntaxKinds.DefaultKeyword); + switch (this.getToken()) { + case SyntaxKinds.ClassKeyword: + case SyntaxKinds.AtPunctuator: { + let decoratorList = this.takeCacheDecorator(); + if (this.match(SyntaxKinds.AtPunctuator)) { + decoratorList = this.mergeDecoratorList(decoratorList, this.parseDecoratorList()); + } + const classDeclar = Factory.transFormClassToClassDeclaration(this.parseClass(decoratorList)); + this.staticSematicForDuplicateDefaultExport(classDeclar); + return Factory.createExportDefaultDeclaration( + classDeclar as ClassDeclaration | ClassExpression, + start, + cloneSourcePosition(classDeclar.end), + ); + } + case SyntaxKinds.FunctionKeyword: { + const funcDeclar = this.parseFunctionDeclaration(false, true); + this.staticSematicForDuplicateDefaultExport(funcDeclar); + return Factory.createExportDefaultDeclaration(funcDeclar, start, cloneSourcePosition(funcDeclar.end)); + } + default: { + if (this.isContextKeyword("async") && this.lookahead().kind === SyntaxKinds.FunctionKeyword) { + this.nextToken(); + const funcDeclar = this.parseFunctionDeclaration(true, true); + // funcDeclar.async = true; + this.staticSematicForDuplicateDefaultExport(funcDeclar); + return Factory.createExportDefaultDeclaration(funcDeclar, start, cloneSourcePosition(funcDeclar.end)); + } + const typeDeclar = this.tryParseDeclarationWithIdentifierStart(); + if (typeDeclar) { + this.shouldInsertSemi(); + return Factory.createExportDefaultDeclaration( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + typeDeclar as any, + start, + this.getLastTokenEndPositon(), + ); + } + // TODO: parse export default from ""; (experimental feature) + const expr = this.parseAssignmentExpressionAllowIn(); + this.shouldInsertSemi(); + this.staticSematicForDuplicateDefaultExport(expr); + return Factory.createExportDefaultDeclaration(expr, start, cloneSourcePosition(expr.end)); + } + } +} +/** + * Using symbol scope recorder for record the default export. + * @param node + */ +export function staticSematicForDuplicateDefaultExport(this: Parser, node: ModuleItem) { + const isDefaultAlreadyBeDeclarated = this.symbolScopeRecorder.testAndSetDefaultExport(node.start); + if (isDefaultAlreadyBeDeclarated) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isDefaultAlreadyBeDeclarated); + } +} +export function parseExportNamedDeclaration(this: Parser, start: SourcePosition): ExportNamedDeclarations { + this.expect(SyntaxKinds.BracesLeftPunctuator); + const specifier: Array = []; + let isStart = true; + let isMatchKeyword = false; + const undefExportSymbols: Array<[string, SourcePosition]> = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + if (this.match(SyntaxKinds.BracesRightPunctuator) || this.match(SyntaxKinds.EOFToken)) { + break; + } + if (this.match(Keywords)) { + isMatchKeyword = true; + } + const exported = this.parseModuleExportName(); + if (!this.isVariableDeclarated(helperGetValueOfExportName(exported))) { + undefExportSymbols.push([helperGetValueOfExportName(exported), exported.start]); + } + if (this.isContextKeyword("as")) { + this.nextToken(); + const local = this.parseModuleExportName(); + this.staticSematicForDuplicateExportName(local); + specifier.push( + Factory.createExportSpecifier( + exported, + local, + cloneSourcePosition(exported.start), + cloneSourcePosition(local.end), + ), + ); + continue; + } + this.staticSematicForDuplicateExportName(exported); + specifier.push( + Factory.createExportSpecifier( + exported, + null, + cloneSourcePosition(exported.start), + cloneSourcePosition(exported.end), + ), + ); + } + const { end: bracesRightPunctuatorEnd } = this.expect(SyntaxKinds.BracesRightPunctuator); + let source: StringLiteral | null = null; + if (this.getSourceValue() === "from") { + this.nextToken(); + source = this.parseStringLiteral(); + } else { + if (isMatchKeyword) { + throw new Error(); + } + if (undefExportSymbols.length > 0) { + undefExportSymbols.forEach(([sym, pos]) => { + this.symbolScopeRecorder.addToUndefExportSource(sym, pos); + }); + } + this.staticSematicEarlyErrorForExportName(specifier); + } + this.shouldInsertSemi(); + const end = source + ? source.end + : specifier.length === 0 + ? bracesRightPunctuatorEnd + : specifier[specifier.length - 1].end; + return Factory.createExportNamedDeclaration(specifier, null, source, start, cloneSourcePosition(end)); +} +/** + * Static Sematic Check based on + * - 16.2.3.1 Static Semantics: Early Errors + * @param specifiers + */ +export function staticSematicEarlyErrorForExportName(this: Parser, specifiers: Array) { + for (const specifier of specifiers) { + if (isStringLiteral(specifier.exported)) { + this.raiseError( + ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_exported_binding_without_from, + specifier.exported.start, + ); + } + } +} +/** + * Using symbol scope recorder for record export name identifier + * @param exportName + */ +export function staticSematicForDuplicateExportName(this: Parser, exportName: StringLiteral | Identifier) { + const name = helperGetValueOfExportName(exportName); + const isExportNameAlreadyBeDeclar = this.declarateExportSymbol(name, exportName.start); + if (isExportNameAlreadyBeDeclar) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportNameAlreadyBeDeclar); + } + const isDefaultAlreadyBeDeclarated = this.symbolScopeRecorder.testAndSetDefaultExport(exportName.start); + if (name === "default" && isDefaultAlreadyBeDeclarated) { + this.raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isDefaultAlreadyBeDeclarated); + } +} +export function parseExportAllDeclaration(this: Parser, start: SourcePosition): ExportAllDeclaration { + this.expect(SyntaxKinds.MultiplyOperator); + let exported: Identifier | StringLiteral | null = null; + if (this.isContextKeyword("as")) { + this.nextToken(); + exported = this.parseModuleExportName(); + } else { + exported = null; + } + this.expectFormKeyword(); + const source = this.parseStringLiteral(); + this.shouldInsertSemi(); + return Factory.createExportAllDeclaration(exported, source, start, cloneSourcePosition(source.end)); +} +export function parseModuleExportName(this: Parser) { + if (this.match(SyntaxKinds.StringLiteral)) { + return this.parseStringLiteral(); + } + return this.parseIdentifierName(); +} +export function helperGetValueOfExportName(exportName: StringLiteral | Identifier) { + if (isIdentifer(exportName)) { + return exportName.name; + } + return exportName.value; +} diff --git a/web-infras/parser/src/parser/js/pattern.ts b/web-infras/parser/src/parser/js/pattern.ts new file mode 100644 index 00000000..bd1dcc18 --- /dev/null +++ b/web-infras/parser/src/parser/js/pattern.ts @@ -0,0 +1,710 @@ +import { + ArrayExpression, + ArrayPattern, + AssigmentExpression, + AssignmentPattern, + cloneSourcePosition, + Expression, + Factory, + Identifier, + isArrayPattern, + isAssignmentPattern, + isIdentifer, + isMemberExpression, + isObjectPattern, + isObjectPatternProperty, + isPattern, + isRestElement, + isSpreadElement, + ModuleItem, + ObjectExpression, + ObjectPattern, + ObjectPatternProperty, + ObjectProperty, + Pattern, + PropertyName, + RestElement, + SourcePosition, + SpreadElement, + SyntaxKinds, +} from "web-infra-common"; +import { Parser } from ".."; +import { ErrorMessageMap } from "@/src/parser/error"; +import { ExpressionScopeKind } from "@/src/parser/scope/type"; +import { + BindingIdentifierSyntaxKindArray, + IdentiferWithKeyworArray, + KeywordSet, + PreserveWordSet, +} from "@/src/parser/type"; + +/** + * This is a critial helper function for transform expression (major is ObjectExpression + * and ArrayExpression) to Pattern (`BindingObjectPattern`, `BindingArrayPattern`, `AssignmentObjectPattern` + * `AssignmentArrayPattern`). + * + * ### Use Case : ParseAssignmentExpression + * This function is used when `parseAssignmentExpression`, because parseAssignmentExpression + * would parse left as expression first, when left is followed by assignment operator, we need + * to transform expression to AssignmentPattern. + * + * ### Use Case : ParseArrowFunctionExpression + * When `parseArrowFunctionExpression`, it would use this function too. Because `parseArrowFunctionExpresssion` + * might accept argument (array of `AssignmentExpression`) as param, so we need to transform arguments to function + * parameter, which is transform `AssignmenExpression` to `BiningElement`(`Identifier` or `BindingPattern`). + * + * ### Paramemter: isBinding + * Most of BindingPattern and AssignmentPattern's production rule is alike, one key different is that BindingPattern + * `PropertyName` can only have `BindingElement`, but `PropertyName` of AssignmentPattern can have LeftHandSideExpression + * so we add a param `isBinding` to determinate is transform to BindingPattern or not. + * @param {Expression} expr target for transform to Pattern + * @param {boolean} isBinding Is transform to BindingPattern + */ +export function exprToPattern(this: Parser, expr: Expression, isBinding: boolean): Pattern { + // TODO, remove impl function. + return this.exprToPatternImpl(expr, isBinding); +} +export function exprToPatternImpl(this: Parser, node: Expression, isBinding: boolean): Pattern { + /** + * parentheses in pattern only allow in Assignment Pattern + * for MemberExpression and Identifier + */ + if (node.parentheses) { + if (!isParanValidationInPattern(isBinding, node)) + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, node.start); + } + switch (node.kind) { + case SyntaxKinds.TSAsExpression: + case SyntaxKinds.TSTypeAssertionExpression: + case SyntaxKinds.TSSatisfiesExpression: + case SyntaxKinds.TSNonNullExpression: + if (!isBinding) { + // only accept id, member expression and nested TS expression. + node.expression = this.exprToPattern(node.expression, isBinding) as Expression; + return node; + } else { + throw this.createMessageError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side); + } + case SyntaxKinds.AssigmentExpression: { + return this.assignmentExpressionToAssignmentPattern(node, isBinding); + } + case SyntaxKinds.SpreadElement: { + return this.spreadElementToFunctionRestParameter(node); + } + case SyntaxKinds.ArrayExpression: { + return this.arrayExpressionToArrayPattern(node, isBinding); + } + case SyntaxKinds.ObjectExpression: { + return this.objectExpressionToObjectPattern(node, isBinding); + } + case SyntaxKinds.Identifier: + this.declarateSymbolInBindingPatternAsParam(node.name, isBinding, node.start); + return node as Identifier; + case SyntaxKinds.MemberExpression: + if (!isBinding) { + return node as Pattern; + } + // fall to error + // eslint-disable-next-line no-fallthrough + default: + throw this.createMessageError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side); + } +} +export function isParanValidationInPattern(isBinding: boolean, expr: Expression): boolean { + if (isBinding) return false; + return isAssignable(expr); +} +/** + * Is a node match the DestructuringAssignmentTarget in ECMA spec. + * @param node + * @returns + */ +export function isAssignable(node: ModuleItem) { + switch (node.kind) { + case SyntaxKinds.Identifier: + case SyntaxKinds.MemberExpression: + return true; + case SyntaxKinds.TSAsExpression: + case SyntaxKinds.TSTypeAssertionExpression: + case SyntaxKinds.TSSatisfiesExpression: + case SyntaxKinds.TSNonNullExpression: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return isAssignable((node as any).expression); + default: + return false; + } +} +/** + * ## Transform Assignment Expression + * @param expr + * @param isBinding + * @returns + */ +export function assignmentExpressionToAssignmentPattern( + this: Parser, + expr: AssigmentExpression, + isBinding: boolean, +) { + const left = isBinding ? this.helperCheckPatternWithBinding(expr.left) : expr.left; + if (expr.operator !== SyntaxKinds.AssginOperator) { + this.raiseError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side, expr.start); + } + return Factory.createAssignmentPattern( + left as Pattern, + expr.right, + undefined, + undefined, + expr.start, + expr.end, + ); +} +/** + * Transform a assignment pattern to a binding assignment pattern + * @param leftValue + * @returns + */ +export function helperCheckPatternWithBinding(this: Parser, leftValue: Pattern): Pattern { + if (isObjectPattern(leftValue)) { + for (const property of leftValue.properties) { + if (isObjectPatternProperty(property)) { + if (property.value && isMemberExpression(property.value)) { + this.raiseError(ErrorMessageMap.babel_error_binding_member_expression, property.start); + } + if (property.value && isAssignable(property.value) && (property.value as Expression).parentheses) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, leftValue.start); + } + } + } + return leftValue; + } + if (isAssignmentPattern(leftValue)) { + this.helperCheckPatternWithBinding(leftValue.left); + return leftValue; + } + if (isRestElement(leftValue)) { + this.helperCheckPatternWithBinding(leftValue.argument); + return leftValue; + } + if (isArrayPattern(leftValue)) { + for (const pat of leftValue.elements) { + if (pat) { + this.helperCheckPatternWithBinding(pat); + } + } + } + if (isAssignable(leftValue)) { + const expr = leftValue as Expression; + if (expr.parentheses) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, leftValue.start); + } + } + return leftValue; +} +/** + * ## Transform `SpreadElement` to RestElement in function param + * + * Accoring to production rule, `FunctionRestParameter` is just alias + * of `BindingRestElement` which be used in ArrayPattern. + * @param spreadElement + * @returns + */ +export function spreadElementToFunctionRestParameter(this: Parser, spreadElement: SpreadElement) { + return this.spreadElementToArrayRestElement(spreadElement, true); +} +/** + * ## Transform `ArrayExpression` to `ArrayPattern` + * @param elements + * @param isBinding + * @returns + */ +export function arrayExpressionToArrayPattern( + this: Parser, + expr: ArrayExpression, + isBinding: boolean, +): ArrayPattern { + const arrayPatternElements: Array = []; + const restElementIndexs = []; + for (let index = 0; index < expr.elements.length; ++index) { + const element = expr.elements[index]; + if (!element) { + arrayPatternElements.push(null); + continue; + } + if (isSpreadElement(element)) { + arrayPatternElements.push(this.spreadElementToArrayRestElement(element, isBinding)); + restElementIndexs.push(index); + continue; + } + arrayPatternElements.push(this.exprToPattern(element, isBinding)); + } + if ( + restElementIndexs.length > 1 || + (restElementIndexs.length === 1 && + (restElementIndexs[0] !== arrayPatternElements.length - 1 || expr.trailingComma)) + ) { + this.raiseError(ErrorMessageMap.syntax_error_parameter_after_rest_parameter, expr.end); + } + return Factory.createArrayPattern(arrayPatternElements, undefined, undefined, expr.start, expr.end); +} +/** + * ## Transform `SpreadElement` in ArrayPattern + * This function transform spread element to following two production rule AST: + * + * - `BindingRestElement` in `ArrayBindingPattern` + * - `AssignmentRestElement` in `ArrayAssignmentPattern` + * + * According to production rule, `BindingRestElement`'s argument can only be identifier or ObjectPattern + * or ArrayPattern, and argument of `AssignmentRestElement` can only be identifier or memberExpression. + * ``` + * BindingRestElement := ... BindingIdentifier + * := ... BindingPattern + * AssignmentRestElement :=... DestructuringAssignmentTarget + * ``` + * @param spreadElement + * @param isBinding + */ +export function spreadElementToArrayRestElement( + this: Parser, + spreadElement: SpreadElement, + isBinding: boolean, +): RestElement { + const argument = this.exprToPattern(spreadElement.argument, isBinding); + if (isAssignmentPattern(argument)) { + // recoverable error + this.raiseError( + ErrorMessageMap.v8_error_rest_assignment_property_must_be_followed_by_an_identifier_in_declaration_contexts, + argument.start, + ); + } + return Factory.createRestElement(argument, undefined, undefined, spreadElement.start, argument.end); +} +/** + * ## Transform `ObjectExpression` To `ObjectPattern` + * @param properties + * @param isBinding + * @returns + */ +export function objectExpressionToObjectPattern( + this: Parser, + expr: ObjectExpression, + isBinding: boolean, +): ObjectPattern { + const objectPatternProperties: Array = []; + const restElementIndexs = []; + for (let index = 0; index < expr.properties.length; ++index) { + const property = expr.properties[index]; + switch (property.kind) { + case SyntaxKinds.ObjectProperty: + objectPatternProperties.push(this.ObjectPropertyToObjectPatternProperty(property, isBinding)); + break; + case SyntaxKinds.SpreadElement: + restElementIndexs.push(index); + objectPatternProperties.push(this.spreadElementToObjectRestElement(property, isBinding)); + break; + default: + throw this.createMessageError(ErrorMessageMap.invalid_left_value); + } + } + if ( + restElementIndexs.length > 1 || + (restElementIndexs.length === 1 && + (restElementIndexs[0] !== objectPatternProperties.length - 1 || expr.trailingComma)) + ) { + this.raiseError(ErrorMessageMap.syntax_error_parameter_after_rest_parameter, expr.end); + } + return Factory.createObjectPattern(objectPatternProperties, undefined, undefined, expr.start, expr.end); +} +/** + * ## Transform `SpreadElement` in ObjectPattern + * This function transform spread element to following two production rule AST: + * + * - `BindingRestProperty` in BindingObjectPattern + * - `AssignmentRestProperty` in AssignObjectPattern + * + * According to production rule, `BindingRestProperty`'s argument can only be identifier, + * and argument of `AssignmentRestProperty` can only be identifier or memberExpression. + * ``` + * BindingRestProperty := ... BindingIdentifier + * AssignmentRestProperty:= ... DestructuringAssignmentTarget + * ``` + */ +export function spreadElementToObjectRestElement( + this: Parser, + spreadElement: SpreadElement, + isBinding: boolean, +): RestElement { + const argument = this.exprToPattern(spreadElement.argument, isBinding); + if (isBinding) { + if (!isIdentifer(argument)) { + // recoverable error + this.raiseError( + ErrorMessageMap.v8_error_rest_binding_property_must_be_followed_by_an_identifier_in_declaration_contexts, + argument.start, + ); + } + } else { + if (!isAssignable(argument)) { + // recoverable error + this.raiseError( + ErrorMessageMap.v8_error_rest_assignment_property_must_be_followed_by_an_identifier_in_declaration_contexts, + argument.start, + ); + } + } + return Factory.createRestElement(argument, undefined, undefined, spreadElement.start, argument.end); +} +export function ObjectPropertyToObjectPatternProperty( + this: Parser, + objectPropertyNode: ObjectProperty, + isBinding = false, +): ObjectPatternProperty | AssignmentPattern { + // object property's value can not has parentheses. + if (objectPropertyNode.value && objectPropertyNode.value.parentheses && isBinding) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, objectPropertyNode.start); + } + if (this.context.propertiesProtoDuplicateSet.has(objectPropertyNode.key)) { + this.context.propertiesProtoDuplicateSet.delete(objectPropertyNode.key); + } + // When a property name is a CoverInitializedName, we need to cover to assignment pattern + if (this.context.propertiesInitSet.has(objectPropertyNode) && !objectPropertyNode.shorted) { + this.context.propertiesInitSet.delete(objectPropertyNode); + if (objectPropertyNode.computed || !isIdentifer(objectPropertyNode.key)) { + // property name of assignment pattern can not use computed propertyname or literal + throw this.createMessageError( + ErrorMessageMap.assignment_pattern_left_value_can_only_be_idenifier_or_pattern, + ); + } + this.declarateSymbolInBindingPatternAsParam( + objectPropertyNode.key.name, + isBinding, + objectPropertyNode.start, + ); + return Factory.createAssignmentPattern( + objectPropertyNode.key, + objectPropertyNode.value as Expression, + undefined, + undefined, + objectPropertyNode.start, + objectPropertyNode.end, + ); + } + const patternValue = !objectPropertyNode.value + ? objectPropertyNode.value + : this.exprToPatternImpl(objectPropertyNode.value, isBinding); + // for binding pattern, member expression is not allow + // - for assignment pattern: value production rule is `DestructuringAssignmentTarget`, which just a LeftHandSideExpression + // - for binding pattern: value production rule is `BindingElement`, which only can be object-pattern, array-pattern, id. + if (isBinding && patternValue && isMemberExpression(patternValue)) { + this.raiseError(ErrorMessageMap.babel_error_binding_member_expression, patternValue.start); + } + return Factory.createObjectPatternProperty( + objectPropertyNode.key, + patternValue, + objectPropertyNode.computed, + objectPropertyNode.shorted, + objectPropertyNode.start, + objectPropertyNode.end, + ); +} +export function declarateSymbolInBindingPatternAsParam( + this: Parser, + name: string, + isBinding: boolean, + position: SourcePosition, +) { + if (isBinding) { + this.declarateParam(name, position); + } +} +/** ================================================================================ + * Parse Pattern + * entry point: https://tc39.es/ecma262/#sec-destructuring-binding-patterns + * ================================================================================== + */ +/** + * Parse BindingElement + * ``` + * BindingElemet := Identifer ('=' AssigmentExpression)? + * := BindingPattern ('=' AssigmentExpression)? + * ``` + * @returns + */ +export function parseBindingElement(this: Parser, shouldParseAssignment = true): Pattern { + this.expectButNotEat([ + ...IdentiferWithKeyworArray, + SyntaxKinds.BracesLeftPunctuator, + SyntaxKinds.BracketLeftPunctuator, + ]); + let left: Pattern | undefined; + if (this.match(BindingIdentifierSyntaxKindArray)) { + left = this.parseBindingIdentifier(); + } else { + left = this.parseBindingPattern(); + } + if (this.match(SyntaxKinds.AssginOperator) && shouldParseAssignment) { + return this.parseDefaultValueForBindingElement(left); + } + return left; +} +export function parseDefaultValueForBindingElement(this: Parser, left: Pattern) { + this.expect(SyntaxKinds.AssginOperator); + const right = this.parseWithRHSLayer(() => this.parseAssignmentExpressionAllowIn()); + return Factory.createAssignmentPattern( + left, + right, + undefined, + undefined, + cloneSourcePosition(left.start), + cloneSourcePosition(right.end), + ); +} +export function parseRestElement(this: Parser, allowPattern: boolean): RestElement { + const { start } = this.expect([SyntaxKinds.SpreadOperator]); + let id: Pattern | null = null; + if (this.match(BindingIdentifierSyntaxKindArray)) { + id = this.parseBindingIdentifier(); + } + if (this.match([SyntaxKinds.BracesLeftPunctuator, SyntaxKinds.BracketLeftPunctuator])) { + if (allowPattern) { + id = this.parseBindingPattern(); + } + if (!allowPattern) { + // recoverable error ? + throw this.createUnexpectError(); + } + } + if (!id) { + throw this.createUnexpectError(); + } + return Factory.createRestElement(id, undefined, undefined, start, cloneSourcePosition(id.end)); +} +export function parseBindingIdentifier(this: Parser) { + const id = this.parseWithLHSLayer(() => this.parseIdentifierReference()); + this.declarateNonFunctionalSymbol(id.name, id.start); + return id; +} +/** + * Parse BindingPattern + * ``` + * BindingPattern := ObjectPattern + * := ArrayPattern + * ``` + */ +export function parseBindingPattern(this: Parser): ObjectPattern | ArrayPattern { + return this.parseWithLHSLayer(() => { + this.expectButNotEat([SyntaxKinds.BracesLeftPunctuator, SyntaxKinds.BracketLeftPunctuator]); + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + return this.parseObjectPattern(); + } + return this.parseArrayPattern(); + }); +} +/** Parse Object Pattern + * ``` + * ObjectPattern := '{' ObjectPatternProperties '}' + * := '{' ObjtecPatternProperties ',' '}' + * := '{' ObjectPatternProperties ',' RestElement '}' + * := '{' RestElement '} + * ObjectPatternProperties := ObjectPatternProperties ',' ObjectPatternProperty + * ObjectPatternProperty := Identifer ('=' AssigmentExpression) + * := BindingPattern ('=' AssignmentExpression) + * ``` + * @return {ObjectPattern} + */ +export function parseObjectPattern(this: Parser): ObjectPattern { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + if (this.match(SyntaxKinds.BracesRightPunctuator)) { + const end = this.getEndPosition(); + this.nextToken(); + const objectPattern = Factory.createObjectPattern([], undefined, undefined, start, end); + return objectPattern; + } + const properties: Array = [ + this.parseObjectPatternPossibelProperty(), + ]; + while (!this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + this.expect(SyntaxKinds.CommaToken); + if (this.match(SyntaxKinds.BracesRightPunctuator) || this.match(SyntaxKinds.EOFToken)) { + continue; + } + properties.push(this.parseObjectPatternPossibelProperty()); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + const objectPattern = Factory.createObjectPattern(properties, undefined, undefined, start, end); + return objectPattern; +} +export function parseObjectPatternPossibelProperty( + this: Parser, +): ObjectPatternProperty | RestElement | AssignmentPattern { + // parse Rest property + if (this.match(SyntaxKinds.SpreadOperator)) { + const rest = this.parseRestElement(false); + this.staticSematicForRestElementInObjectPattern(); + return rest; + } + // parse Object pattern property (rename) + const isComputedRef = { isComputed: false }; + const propertyName = this.parsePropertyName(isComputedRef); + if (isComputedRef.isComputed || this.match(SyntaxKinds.ColonPunctuator)) { + this.nextToken(); + const pattern = this.parseBindingElement(); + return Factory.createObjectPatternProperty( + propertyName, + pattern, + isComputedRef.isComputed, + false, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(pattern.end), + ); + } + this.staticCheckForPropertyNameAsSingleBinding(propertyName); + // parse object pattern as Assignment pattern + if (this.match(SyntaxKinds.AssginOperator)) { + this.nextToken(); + const expr = this.parseWithRHSLayer(() => this.parseAssignmentExpressionAllowIn()); + this.staticSematicForAssignmentPatternInObjectPattern(propertyName); + this.declarateNonFunctionalSymbol((propertyName as Identifier).name, propertyName.start); + return Factory.createAssignmentPattern( + propertyName as Pattern, + expr, + undefined, + undefined, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(expr.end), + ); + } + // parse object pattern as shorted property. + this.staticSematicForShortedPropertyNameInObjectLike(propertyName); + this.declarateNonFunctionalSymbol((propertyName as Identifier).name, propertyName.start); + return Factory.createObjectPatternProperty( + propertyName, + undefined, + isComputedRef.isComputed, + true, + cloneSourcePosition(propertyName.start), + cloneSourcePosition(propertyName.end), + ); +} +/** + * In Object Pattern, Rest Element should be last one, and can + * not have trailing comma. + */ +export function staticSematicForRestElementInObjectPattern(this: Parser) { + if ( + !this.match(SyntaxKinds.BracesRightPunctuator) || + (this.match(SyntaxKinds.CommaToken) && this.lookahead().kind === SyntaxKinds.BracesRightPunctuator) + ) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, + this.getStartPosition(), + ); + } +} +/** + * Ban some usage like `{ "string-key" = "name" }` in onject pattern + * @param propertyName + */ +export function staticSematicForAssignmentPatternInObjectPattern(this: Parser, propertyName: PropertyName) { + if (!isPattern(propertyName)) { + throw this.createMessageError("assignment pattern left value can only allow identifier or pattern"); + } +} +/** + * As for shorted and assignment patern in object pattern, the property name should be + * a bidning identifier, so we need to check and record the property name. + * @param propertyName + * @returns + */ +export function staticCheckForPropertyNameAsSingleBinding(this: Parser, propertyName: PropertyName) { + if (isIdentifer(propertyName)) { + switch (propertyName.name) { + case "await": { + if (this.isCurrentScopeParseAwaitAsExpression() || this.config.sourceType === "module") { + this.raiseError( + ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, + propertyName.start, + ); + } + return; + } + case "yield": { + if (this.isCurrentScopeParseYieldAsExpression() || this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_yield, propertyName.start); + } + return; + } + case "arguments": { + if (this.isInStrictMode() && this.strictModeScopeRecorder.isInLHS()) { + this.raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, propertyName.start); + } + this.recordScope(ExpressionScopeKind.ArgumentsIdentifier, propertyName.start); + return; + } + case "eval": { + if (this.isInStrictMode() && this.strictModeScopeRecorder.isInLHS()) { + this.raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, propertyName.start); + } + this.recordScope(ExpressionScopeKind.EvalIdentifier, propertyName.start); + return; + } + case "let": { + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); + } + this.recordScope(ExpressionScopeKind.LetIdentifiier, propertyName.start); + return; + } + default: { + if (KeywordSet.has(propertyName.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); + } + if (PreserveWordSet.has(propertyName.name) && this.isInStrictMode()) { + // recoverable error + this.raiseError(ErrorMessageMap.babel_error_unexpected_reserved_word, propertyName.start); + } + return; + } + } + } +} +export function parseArrayPattern(this: Parser): ArrayPattern { + const { start } = this.expect(SyntaxKinds.BracketLeftPunctuator); + let isStart = true; + const elements: Array = []; + while (!this.match(SyntaxKinds.BracketRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + if (isStart) { + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + if (this.match(SyntaxKinds.BracketRightPunctuator) || this.match(SyntaxKinds.EOFToken)) { + continue; + } + if (this.match(SyntaxKinds.CommaToken)) { + elements.push(null); + continue; + } + if (this.match(SyntaxKinds.SpreadOperator)) { + elements.push(this.parseRestElement(true)); + if (!this.match(SyntaxKinds.BracketRightPunctuator)) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, + this.getStartPosition(), + ); + } + break; + } + const pattern = this.parseBindingElement(); + elements.push(pattern); + } + const { end } = this.expect(SyntaxKinds.BracketRightPunctuator); + const arrayPattern = Factory.createArrayPattern(elements, undefined, undefined, start, end); + return arrayPattern; +} diff --git a/web-infras/parser/src/parser/js/statement.ts b/web-infras/parser/src/parser/js/statement.ts new file mode 100644 index 00000000..a0526741 --- /dev/null +++ b/web-infras/parser/src/parser/js/statement.ts @@ -0,0 +1,729 @@ +import { + BlockStatement, + BreakStatement, + CatchClause, + cloneSourcePosition, + ContinueStatement, + DebuggerStatement, + DoWhileStatement, + EmptyStatement, + Expression, + Factory, + ForInStatement, + ForOfStatement, + ForStatement, + FunctionDeclaration, + Identifier, + IfStatement, + isArrayPattern, + isAssignmentPattern, + isFunctionDeclaration, + isIdentifer, + isObjectPattern, + isVarDeclaration, + LabeledStatement, + ReturnStatement, + SourcePosition, + Statement, + StatementListItem, + SwitchCase, + SyntaxKinds, + TryStatement, + TSParameter, + VariableDeclaration, + WhileStatement, + WithStatement, +} from "web-infra-common"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { SymbolType } from "@/src/parser/scope/symbolScope"; +import { Parser } from "@/src/parser"; +import { ASTArrayWithMetaData } from "@/src/parser/type"; + +export function parseStatementListItem(this: Parser): StatementListItem { + const token = this.getToken(); + switch (token) { + // 'aync' maybe is + // 1. aync function -> declaration + // 2. aync arrow function -> statement(expressionStatement) + // 3. identifer -> statement (expressionStatement) + case SyntaxKinds.ConstKeyword: + case SyntaxKinds.FunctionKeyword: + case SyntaxKinds.ClassKeyword: + case SyntaxKinds.AtPunctuator: + case SyntaxKinds.EnumKeyword: + return this.parseDeclaration(); + case SyntaxKinds.Identifier: { + const declar = this.tryParseDeclarationWithIdentifierStart(); + if (!declar) { + return this.parseStatement(); + } + return declar; + } + case SyntaxKinds.LetKeyword: + if (this.isLetPossibleIdentifier()) { + return this.parseStatement(); + } + return this.parseDeclaration(); + default: + return this.parseStatement(); + } +} +export function isLetPossibleIdentifier(this: Parser) { + const { kind: kind } = this.lookahead(); + if ( + kind === SyntaxKinds.BracesLeftPunctuator || // object pattern + kind === SyntaxKinds.BracketLeftPunctuator || // array pattern + kind === SyntaxKinds.Identifier || // id + kind === SyntaxKinds.AwaitKeyword || + kind === SyntaxKinds.YieldKeyword + ) { + return false; + } + return true; +} +/** + * ref: https://tc39.es/ecma262/#prod-Statement + */ +export function parseStatement(this: Parser): Statement { + const token = this.getToken(); + switch (token) { + case SyntaxKinds.SwitchKeyword: + return this.parseSwitchStatement(); + case SyntaxKinds.ContinueKeyword: + return this.parseContinueStatement(); + case SyntaxKinds.BreakKeyword: + return this.parseBreakStatement(); + case SyntaxKinds.ReturnKeyword: + return this.parseReturnStatement(); + case SyntaxKinds.BracesLeftPunctuator: + return this.parseBlockStatement(); + case SyntaxKinds.TryKeyword: + return this.parseTryStatement(); + case SyntaxKinds.ThrowKeyword: + return this.parseThrowStatement(); + case SyntaxKinds.WithKeyword: + return this.parseWithStatement(); + case SyntaxKinds.DebuggerKeyword: + return this.parseDebuggerStatement(); + case SyntaxKinds.SemiPunctuator: + return this.parseEmptyStatement(); + case SyntaxKinds.IfKeyword: + return this.parseIfStatement(); + case SyntaxKinds.ForKeyword: + return this.parseForStatement(); + case SyntaxKinds.WhileKeyword: + return this.parseWhileStatement(); + case SyntaxKinds.DoKeyword: + return this.parseDoWhileStatement(); + case SyntaxKinds.VarKeyword: + return this.parseVariableDeclaration(); + default: + if (this.match(SyntaxKinds.Identifier) && this.lookahead().kind === SyntaxKinds.ColonPunctuator) { + return this.parseLabeledStatement(); + } + return this.parseExpressionStatement(); + } +} + +/** + * Parse For-related Statement, include ForStatement, ForInStatement, ForOfStatement. + * + * This function is pretty complex and hard to understand, some function's flag is only + * used in this function. ex: allowIn flag of parseExpression, parseAssignmentExpression. + * @returns {ForStatement | ForInStatement | ForOfStatement} + */ +export function parseForStatement(this: Parser): ForStatement | ForInStatement | ForOfStatement { + // symbolScopeRecorder.enterPreBlockScope(); + this.symbolScopeRecorder.enterBlockSymbolScope(); + const { start: keywordStart } = this.expect(SyntaxKinds.ForKeyword); + // First, parse await modifier and lefthandside or init of for-related statement, + // init might start with let, const, var keyword, but if is let keyword need to + // lookahead to determinate is identifier. + // delcaration in there should not eat semi, becuase semi is seperator of ForStatement, + // and might not need init for pattern, because maybe used by ForIn or ForOf. + // If not start with let, var or const keyword, it should be expression, but this + // expression can not take `in` operator as operator in toplevel, so we need pass + // false to disallow parseExpression to take in as operator + let isAwait: SourcePosition | null = null, + isParseLetAsExpr: SourcePosition | null = null, + leftOrInit: VariableDeclaration | Expression | null = null; + if (this.match(SyntaxKinds.AwaitKeyword)) { + isAwait = this.getStartPosition(); + this.nextToken(); + if (!this.config.allowAwaitOutsideFunction && !this.isCurrentScopeParseAwaitAsExpression()) { + this.raiseError(ErrorMessageMap.babel_error_invalid_await, isAwait); + } + } + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + if (this.match([SyntaxKinds.LetKeyword, SyntaxKinds.ConstKeyword, SyntaxKinds.VarKeyword])) { + if (this.match(SyntaxKinds.LetKeyword) && this.isLetPossibleIdentifier()) { + isParseLetAsExpr = this.getStartPosition(); + leftOrInit = this.parseExpressionDisallowIn(); + } else { + leftOrInit = this.disAllowInOperaotr(() => this.parseVariableDeclaration(true)); + } + } else if (this.match(SyntaxKinds.SemiPunctuator)) { + // for test case `for(;;)` + leftOrInit = null; + } else { + leftOrInit = this.parseExpressionDisallowIn(); + } + // Second is branching part, determinate the branch by following token + // - if start with semi it should be ForStatement, + // - if is in operator, it should be ForInStatement, + // - if is of contextual keyword, it should be ForOfStatement. + // then according to branch case, parse the following part and do the + // sematic check. + // - ForStatement: if init is variable declaration pattern, it need init. + // - ForInStatement: if left is variable decalration, must not have init, and delcaration length must be 1. + // then if left is expression, must transform it to pattern. + // - ForOfStatement: same as ForInStatement. + // There is one case that even we disallow in operator in top level, there maybe + // wrong init of expression like `for(a = 0 in []);` or `for(a=0 of [])` which would + // make leftOrInit parse all token in () as a expression, so we need to check if those + // case happend. + if (this.match(SyntaxKinds.SemiPunctuator)) { + if (isAwait) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_for_await_not_of_loop, isAwait); + } + if (leftOrInit && isVarDeclaration(leftOrInit)) { + for (const delcar of leftOrInit.declarations) { + if ((isArrayPattern(delcar.id) || isObjectPattern(delcar.id)) && !delcar.init) { + // recoverable error + this.raiseError( + ErrorMessageMap.babel_error_destructing_pattern_must_need_initializer, + delcar.start, + ); + } + } + } + this.nextToken(); + let test: Expression | null = null, + update: Expression | null = null; + if (!this.match(SyntaxKinds.SemiPunctuator)) { + test = this.parseExpressionAllowIn(); + } + this.expect(SyntaxKinds.SemiPunctuator); + if (!this.match(SyntaxKinds.ParenthesesRightPunctuator)) { + update = this.parseExpressionAllowIn(); + } + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseForStatementBody(); + const forStatement = Factory.createForStatement( + body, + leftOrInit, + test, + update, + keywordStart, + cloneSourcePosition(body.end), + ); + this.staticSematicEarlyErrorForFORStatement(forStatement); + return forStatement; + } + // unreach case, even if syntax error, when leftOrInit, it must match semi token. + // and because it match semi token, if would enter forStatement case, will not + // reach there. even syntax error, error would be throw at parseExpression or + // parseDeclaration. + if (!leftOrInit) { + throw this.createUnreachError(); + } + // for case `for(a = 0 of [])`; leftOrInit would parse all token before `of` as one expression + // in this case , leftOrInit would be a assignment expression, and when it pass to toAssignment + // function, it would transform to assignment pattern, so we need to checko if there is Assignment + // pattern, it is , means original is assignment expression, it should throw a error. + if (!isVarDeclaration(leftOrInit)) { + leftOrInit = this.exprToPattern(leftOrInit, false) as Expression; + if (isAssignmentPattern(leftOrInit)) { + throw this.createMessageError(ErrorMessageMap.invalid_left_value); + } + } + // branch case for `for-in` statement + if (this.match(SyntaxKinds.InKeyword)) { + if (isAwait) { + // recoverable error + this.raiseError(ErrorMessageMap.extra_error_for_await_not_of_loop, isAwait); + } + if (isVarDeclaration(leftOrInit)) { + this.helperCheckDeclarationmaybeForInOrForOfStatement(leftOrInit, "ForIn"); + } + this.nextToken(); + const right = this.parseExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseForStatementBody(); + const forInStatement = Factory.createForInStatement( + leftOrInit, + right, + body, + keywordStart, + cloneSourcePosition(body.end), + ); + this.staticSematicEarlyErrorForFORStatement(forInStatement); + return forInStatement; + } + // branch case for `for-of` statement + if (this.isContextKeyword("of")) { + if (isVarDeclaration(leftOrInit)) { + this.helperCheckDeclarationmaybeForInOrForOfStatement(leftOrInit, "ForOf"); + } + if (isParseLetAsExpr) { + this.raiseError(ErrorMessageMap.extra_error_for_of_can_not_use_let_as_identifier, isParseLetAsExpr); + } + this.nextToken(); + const right = this.parseAssignmentExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseForStatementBody(); + const forOfStatement = Factory.createForOfStatement( + !!isAwait, + leftOrInit, + right, + body, + keywordStart, + cloneSourcePosition(body.end), + ); + this.staticSematicEarlyErrorForFORStatement(forOfStatement); + return forOfStatement; + } + throw this.createUnexpectError(); +} +export function parseForStatementBody(this: Parser): Statement { + const stmt = this.parseAsLoop(() => this.parseStatement()); + this.symbolScopeRecorder.exitSymbolScope(); + return stmt; +} +export function staticSematicEarlyErrorForFORStatement( + this: Parser, + statement: ForStatement | ForInStatement | ForOfStatement, +) { + if (checkIsLabelledFunction(statement.body)) { + this.raiseError( + this.isInStrictMode() + ? ErrorMessageMap.syntax_error_functions_declare_strict_mode + : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, + statement.body.start, + ); + } +} +/** + * Helper function for check sematic error of VariableDeclaration of ForInStatement and ForOfStatement, + * please reference to comment in parseForStatement. + * @param {VariableDeclaration} declaration + */ +export function helperCheckDeclarationmaybeForInOrForOfStatement( + this: Parser, + declaration: VariableDeclaration, + kind: "ForIn" | "ForOf", +) { + if (declaration.declarations.length > 1) { + // recoverable error + this.raiseError( + ErrorMessageMap.v8_error_Invalid_left_hand_side_in_for_in_loop_must_have_a_single_binding, + declaration.start, + ); + } + const delcarationVariant = declaration.variant; + const onlyDeclaration = declaration.declarations[0]; + if (kind === "ForIn") { + if (onlyDeclaration.init !== null) { + if (delcarationVariant === "var" && !this.isInStrictMode() && isIdentifer(onlyDeclaration.id)) { + return; + } + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_for_in_loop_head_declarations_may_not_have_initializer, + onlyDeclaration.start, + ); + } + } else { + if (onlyDeclaration.init !== null) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_for_of_loop_variable_declaration_may_not_have_an_initializer, + onlyDeclaration.init.start, + ); + } + } +} +export function parseIfStatement(this: Parser): IfStatement { + const { start: keywordStart } = this.expect(SyntaxKinds.IfKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const test = this.parseExpressionAllowIn(); + const { end: headerEnd } = this.expect(SyntaxKinds.ParenthesesRightPunctuator); + this.context.lastTokenIndexOfIfStmt = headerEnd.index; + const consequnce = this.parseStatement(); + if (this.match(SyntaxKinds.ElseKeyword)) { + this.nextToken(); + const alter = this.parseStatement(); + return Factory.createIfStatement(test, consequnce, alter, keywordStart, cloneSourcePosition(alter.end)); + } + const ifStatement = Factory.createIfStatement( + test, + consequnce, + null, + keywordStart, + cloneSourcePosition(consequnce.end), + ); + this.staticSematicEarlyErrorForIfStatement(ifStatement); + return ifStatement; +} +export function staticSematicEarlyErrorForIfStatement(this: Parser, statement: IfStatement) { + if (checkIsLabelledFunction(statement.conseqence)) { + this.raiseError( + this.isInStrictMode() + ? ErrorMessageMap.syntax_error_functions_declare_strict_mode + : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, + statement.conseqence.start, + ); + } + if (statement.alternative && checkIsLabelledFunction(statement.alternative)) { + this.raiseError( + this.isInStrictMode() + ? ErrorMessageMap.syntax_error_functions_declare_strict_mode + : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, + statement.alternative.start, + ); + } +} +export function parseWhileStatement(this: Parser): WhileStatement { + const { start: keywordStart } = this.expect(SyntaxKinds.WhileKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const test = this.parseExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseAsLoop(() => this.parseStatement()); + const whileStatement = Factory.createWhileStatement( + test, + body, + keywordStart, + cloneSourcePosition(body.end), + ); + this.staticSematicEarlyErrorForWhileStatement(whileStatement); + return whileStatement; +} +export function checkIsLabelledFunction(statement: Statement) { + while (statement.kind === SyntaxKinds.LabeledStatement) { + if (statement.body.kind === SyntaxKinds.FunctionDeclaration) { + return true; + } + statement = statement.body; + } +} +export function staticSematicEarlyErrorForWhileStatement(this: Parser, statement: WhileStatement) { + if (checkIsLabelledFunction(statement.body)) { + // recoverable error + this.raiseError( + this.isInStrictMode() + ? ErrorMessageMap.syntax_error_functions_declare_strict_mode + : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, + statement.body.start, + ); + } +} +export function parseDoWhileStatement(this: Parser): DoWhileStatement { + const { start: keywordStart } = this.expect(SyntaxKinds.DoKeyword); + const body = this.parseAsLoop(() => this.parseStatement()); + this.expect(SyntaxKinds.WhileKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const test = this.parseExpressionAllowIn(); + const { end: punctEnd } = this.expect(SyntaxKinds.ParenthesesRightPunctuator); + this.isSoftInsertSemi(); + const doWhileStatement = Factory.createDoWhileStatement(test, body, keywordStart, punctEnd); + this.staticSematicEarlyErrorForDoWhileStatement(doWhileStatement); + return doWhileStatement; +} +export function staticSematicEarlyErrorForDoWhileStatement(this: Parser, statement: DoWhileStatement) { + if (checkIsLabelledFunction(statement.body)) { + // recoverable error + this.raiseError( + this.isInStrictMode() + ? ErrorMessageMap.syntax_error_functions_declare_strict_mode + : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, + statement.body.start, + ); + } +} +export function parseBlockStatement(this: Parser) { + const { start: puncStart } = this.expect(SyntaxKinds.BracesLeftPunctuator); + this.enterBlockScope(); + const body: Array = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + body.push(this.parseStatementListItem()); + } + this.exitBlockScope(); + const { end: puncEnd } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createBlockStatement(body, puncStart, puncEnd); +} +export function parseSwitchStatement(this: Parser) { + const { start: keywordStart } = this.expect(SyntaxKinds.SwitchKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const discriminant = this.parseExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const { nodes, end } = this.parseAsSwitch(() => this.parseSwitchCases()); + return Factory.createSwitchStatement(discriminant, nodes, keywordStart, end); +} +export function parseSwitchCases(this: Parser): ASTArrayWithMetaData { + this.enterBlockScope(); + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const cases: Array = []; + let haveDefault = false; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + let test: Expression | null = null; + const start = this.getStartPosition(); + if (this.match(SyntaxKinds.CaseKeyword)) { + this.nextToken(); + test = this.parseExpressionAllowIn(); + } else if (this.match(SyntaxKinds.DefaultKeyword)) { + const start = this.getStartPosition(); + this.nextToken(); + if (haveDefault) { + // recoverable error + this.raiseError(ErrorMessageMap.v8_error_more_than_one_default_clause_in_switch_statement, start); + } else { + haveDefault = true; + } + } + this.expect(SyntaxKinds.ColonPunctuator); + const consequence: Array = []; + while ( + !this.match([ + SyntaxKinds.BracesRightPunctuator, + SyntaxKinds.EOFToken, + SyntaxKinds.CaseKeyword, + SyntaxKinds.DefaultKeyword, + ]) + ) { + consequence.push(this.parseStatementListItem()); + } + const end = this.getStartPosition(); + cases.push(Factory.createSwitchCase(test, consequence, start, end)); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + this.exitBlockScope(); + return { + nodes: cases, + start, + end, + }; +} +export function parseContinueStatement(this: Parser): ContinueStatement { + const { start: keywordStart, end: keywordEnd } = this.expect(SyntaxKinds.ContinueKeyword); + this.staticSematicEarlyErrorForContinueStatement(keywordStart); + if (this.match(SyntaxKinds.Identifier) && !this.getLineTerminatorFlag()) { + const id = this.parseIdentifierReference(); + this.shouldInsertSemi(); + this.staticSematicEarlyErrorForLabelInContinueStatement(id); + return Factory.createContinueStatement(id, keywordStart, cloneSourcePosition(id.end)); + } + this.shouldInsertSemi(); + return Factory.createContinueStatement(null, keywordStart, keywordEnd); +} +export function staticSematicEarlyErrorForContinueStatement(this: Parser, start: SourcePosition) { + if (!this.isContinueValidate()) { + // recoverable error + this.raiseError(ErrorMessageMap.syntax_error_continue_must_be_inside_loop, start); + } +} +export function staticSematicEarlyErrorForLabelInContinueStatement(this: Parser, label: Identifier) { + if (!this.canLabelReach(label.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.syntax_error_label_not_found, label.start); + } +} +/** + * Parse Break Statement. + * ``` + * BreakStatement := break; + * := break [no lineTerminator] LabeledIdentifier; + * ``` + * @returns {BreakStatement} + */ +export function parseBreakStatement(this: Parser): BreakStatement { + const { start, end } = this.expect(SyntaxKinds.BreakKeyword); + if (this.match(SyntaxKinds.Identifier) && !this.getLineTerminatorFlag()) { + const label = this.parseIdentifierReference(); + this.shouldInsertSemi(); + this.staticSematicEarlyErrorForLabelInBreakStatement(label); + return Factory.createBreakStatement(label, start, end); + } + this.shouldInsertSemi(); + const breakStmt = Factory.createBreakStatement(null, start, end); + this.staticSematicEarlyErrorForBreakStatement(breakStmt); + return breakStmt; +} +/** + * Spec def early error checking for break statement. + * @param {BreakStatement} breakStmt + * reference: https://tc39.es/ecma262/#sec-break-statement-static-semantics-early-errors + */ +export function staticSematicEarlyErrorForBreakStatement(this: Parser, breakStmt: BreakStatement) { + if (!this.isBreakValidate()) { + // recoverable error + this.raiseError( + ErrorMessageMap.syntax_error_unlabeled_break_must_be_inside_loop_or_switch, + breakStmt.start, + ); + } +} +/** + * Spec def early error checking for break statement with break label + * @param {Identifier} label + * reference: https://tc39.es/ecma262/#sec-break-statement-static-semantics-early-errors + */ +export function staticSematicEarlyErrorForLabelInBreakStatement(this: Parser, label: Identifier) { + if (!this.canLabelReach(label.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.syntax_error_label_not_found, label.start); + } +} +/** + * Parse labeled statement + * ``` + * LabelledStatement := LabelIdentifier: LabelledItem + * LabelledItem := Statement + * := FunctionDeclaration + * ``` + * @returns {LabeledStatement} + */ +export function parseLabeledStatement(this: Parser): LabeledStatement { + // TODO: using dev mode unreach checking + // if (!this.match(SyntaxKinds.Identifier) || this.lookahead().kind !== SyntaxKinds.ColonPunctuator) { + // } + const label = this.parseIdentifierReference(); + if (this.lexicalScopeRecorder.enterVirtualBlockScope("Label", label.name)) { + // recoverable error + this.raiseError(ErrorMessageMap.v8_error_label_has_already_been_declared, label.start); + } + this.expect(SyntaxKinds.ColonPunctuator); + const labeled = this.match(SyntaxKinds.FunctionKeyword) + ? this.parseFunctionDeclaration(false, false) + : this.parseStatement(); + this.lexicalScopeRecorder.exitVirtualBlockScope(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.staticSematicEarlyErrorForLabelStatement(labeled as any); + return Factory.createLabeledStatement( + label, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + labeled as any, + cloneSourcePosition(label.start), + cloneSourcePosition(labeled.end), + ); +} +/** + * Spec def early error. using alter production rule. + * @param labeled + * reference: https://tc39.es/ecma262/#sec-labelled-statements-static-semantics-early-errors + */ +export function staticSematicEarlyErrorForLabelStatement( + this: Parser, + labeled: Statement | FunctionDeclaration, +) { + if (isFunctionDeclaration(labeled)) { + if (labeled.generator) { + this.raiseError(ErrorMessageMap.syntax_error_generator_function_declare, labeled.start); + } + if (this.isInStrictMode()) { + this.raiseError(ErrorMessageMap.syntax_error_functions_declare_strict_mode, labeled.start); + } + } +} +export function parseReturnStatement(this: Parser): ReturnStatement { + const { start, end } = this.expect(SyntaxKinds.ReturnKeyword); + if (!this.isReturnValidate()) { + this.raiseError(ErrorMessageMap.syntax_error_return_not_in_function, start); + } + if (this.isSoftInsertSemi(true)) { + return Factory.createReturnStatement(null, start, end); + } + const expr = this.parseExpressionAllowIn(); + this.shouldInsertSemi(); + return Factory.createReturnStatement(expr, start, cloneSourcePosition(expr.end)); +} +export function parseTryStatement(this: Parser): TryStatement { + const { start: tryKeywordStart } = this.expect(SyntaxKinds.TryKeyword); + const body = this.parseBlockStatement(); + let handler: CatchClause | null = null, + finalizer: BlockStatement | null = null; + if (this.match(SyntaxKinds.CatchKeyword)) { + const catchKeywordStart = this.getStartPosition(); + this.nextToken(); + //symbolScopeRecorder.enterFunctionSymbolScope(); + this.enterCatchBlockScope(); + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator)) { + this.nextToken(); + this.symbolScopeRecorder.enterCatchParam(); + // catch clause should not have init + const param = this.parseBindingElement(false); + this.parseFunctionParamType(param as TSParameter, false); + if (!this.symbolScopeRecorder.setCatchParamTo(isIdentifer(param) ? SymbolType.Var : SymbolType.Let)) { + throw this.createMessageError(ErrorMessageMap.v8_error_duplicate_identifier); + } + // should check param is duplicate or not. + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseCatchBlock(); + handler = Factory.createCatchClause(param, body, catchKeywordStart, cloneSourcePosition(body.end)); + } else { + const body = this.parseCatchBlock(); + handler = Factory.createCatchClause(null, body, catchKeywordStart, cloneSourcePosition(body.end)); + } + this.exitCatchBlockScope(); + } + if (this.match(SyntaxKinds.FinallyKeyword)) { + this.nextToken(); + finalizer = this.parseBlockStatement(); + } + if (!handler && !finalizer) { + this.raiseError(ErrorMessageMap.v8_error_missing_catch_or_finally_after_try, tryKeywordStart); + } + return Factory.createTryStatement( + body, + handler, + finalizer, + tryKeywordStart, + cloneSourcePosition(finalizer ? finalizer.end : handler ? handler.end : body.end), + ); +} +export function parseCatchBlock(this: Parser) { + const { start: puncStart } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const body: Array = []; + while (!this.match(SyntaxKinds.BracesRightPunctuator) && !this.match(SyntaxKinds.EOFToken)) { + body.push(this.parseStatementListItem()); + } + const { end: puncEnd } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createBlockStatement(body, puncStart, puncEnd); +} +export function parseThrowStatement(this: Parser) { + const { start } = this.expect(SyntaxKinds.ThrowKeyword); + this.staticSmaticEarlyErrorForThrowStatement(); + const expr = this.parseExpressionAllowIn(); + this.shouldInsertSemi(); + return Factory.createThrowStatement(expr, start, cloneSourcePosition(expr.end)); +} +export function staticSmaticEarlyErrorForThrowStatement(this: Parser) { + if (this.getLineTerminatorFlag()) { + this.raiseError(ErrorMessageMap.babel_error_illegal_newline_after_throw, this.getStartPosition()); + } +} +export function parseWithStatement(this: Parser): WithStatement { + const { start } = this.expect(SyntaxKinds.WithKeyword); + this.expect(SyntaxKinds.ParenthesesLeftPunctuator); + const object = this.parseExpressionAllowIn(); + this.expect(SyntaxKinds.ParenthesesRightPunctuator); + const body = this.parseStatement(); + const withStmt = Factory.createWithStatement(object, body, start, cloneSourcePosition(body.end)); + this.staticSmaticEarlyErrorForWithStatement(withStmt); + return withStmt; +} +export function staticSmaticEarlyErrorForWithStatement(this: Parser, withStatement: WithStatement) { + if (this.isInStrictMode()) { + // recoverable error. + this.raiseError(ErrorMessageMap.babel_error_with_statement_in_strict_mode, withStatement.start); + } +} +export function parseDebuggerStatement(this: Parser): DebuggerStatement { + const { start, end } = this.expect(SyntaxKinds.DebuggerKeyword); + this.shouldInsertSemi(); + return Factory.createDebuggerStatement(start, end); +} +export function parseEmptyStatement(this: Parser): EmptyStatement { + const { start, end } = this.expect([SyntaxKinds.SemiPunctuator]); + return Factory.createEmptyStatement(start, end); +} diff --git a/web-infras/parser/src/parser/jsx/index.ts b/web-infras/parser/src/parser/jsx/index.ts new file mode 100644 index 00000000..e0892df2 --- /dev/null +++ b/web-infras/parser/src/parser/jsx/index.ts @@ -0,0 +1,413 @@ +/** ================================================================================ + * Parse JSX + * entry point: https://facebook.github.io/jsx/ + * ================================================================================== + */ + +import { + JSXElement, + JSXFragment, + SyntaxKinds, + Factory, + cloneSourcePosition, + JSXOpeningElement, + JSXClosingElement, + JSXIdentifier, + JSXMemberExpression, + JSXNamespacedName, + JSXAttribute, + JSXSpreadAttribute, + JSXExpressionContainer, +} from "web-infra-common"; +import { ParserPlugin } from "@/src/parser/config"; +import { Parser } from "@/src/parser"; +import { ErrorMessageMap } from "@/src/parser/error"; +import { IdentiferWithKeyworArray } from "@/src/parser/type"; + +/** + * Parse JSX Element or JSX Fragment + * ``` + * PrimaryExpression := JSXElement + * := JSXFragment + * ``` + */ +export function parseJSXElementOrJSXFragment(this: Parser, inJSXChildren: boolean): JSXElement | JSXFragment { + if (!this.requirePlugin(ParserPlugin.JSX)) { + this.raiseError(ErrorMessageMap.babel_error_need_enable_jsx, this.getStartPosition()); + } + const lookaheadToken = this.lookahead(); + if (lookaheadToken.kind !== SyntaxKinds.GtOperator) { + return this.parseJSXElement(inJSXChildren); + } else { + return this.parseJSXFragment(inJSXChildren); + } +} +/** + * Parse JSX Element + * ``` + * JSXElement := JSXOpeningElement JSXChildren JSXClosingElement + * := JSXOpeningElement + * ``` + * @returns {JSXElement} + */ +export function parseJSXElement(this: Parser, inJSXChildren: boolean): JSXElement { + const opeingElement = this.parseJSXOpeingElement(inJSXChildren); + if (opeingElement.selfClosing) { + return Factory.createJSXElement( + opeingElement, + null, + [], + cloneSourcePosition(opeingElement.start), + cloneSourcePosition(opeingElement.end), + ); + } + const children = this.parseJSXChildren(); + const closingElement = this.parseJSXClosingElement(inJSXChildren); + this.staticSematicEarlyErrorForJSXElement(opeingElement, closingElement); + return Factory.createJSXElement( + opeingElement, + closingElement, + children, + cloneSourcePosition(opeingElement.start), + cloneSourcePosition(opeingElement.end), + ); +} +export function staticSematicEarlyErrorForJSXElement( + this: Parser, + openingElement: JSXOpeningElement, + closingElement: JSXClosingElement, +) { + const openElementSourceText = this.lexer.getSourceValueByIndex( + openingElement.name.start.index, + openingElement.name.end.index, + ); + const closeElementSourceText = this.lexer.getSourceValueByIndex( + closingElement.name.start.index, + closingElement.name.end.index, + ); + if (openElementSourceText !== closeElementSourceText) { + throw new Error(); + } +} +/** + * Parse JSXOpeingElement + * ``` + * JSXOpeningElement := `<` JSXElementName JSXAtrributes `>` + * := `<` JSXElementName JSXAtrributes `/>` + * ``` + * @returns {JSXOpeningElement} + */ +export function parseJSXOpeingElement(this: Parser, inJSXChildren: boolean): JSXOpeningElement { + const { start } = this.expect(SyntaxKinds.LtOperator); + const lastLexerJSXEndTagContext = this.lexer.getJSXGtContext(); + this.lexer.setJSXGtContext(true); + const name = this.parseJSXElementName(); + const attributes = this.parseJSXAttributes(); + this.lexer.setJSXGtContext(lastLexerJSXEndTagContext); + if (this.match(SyntaxKinds.GtOperator)) { + const end = this.getEndPosition(); + this.nextTokenInJSXChildren(true); + return Factory.createJSXOpeningElement(name, attributes, false, start, end); + } + if (this.match(SyntaxKinds.JSXSelfClosedToken)) { + const end = this.getEndPosition(); + this.nextTokenInJSXChildren(inJSXChildren); + return Factory.createJSXOpeningElement(name, attributes, true, start, end); + } + // for `/ >` + if (this.match(SyntaxKinds.DivideOperator) && this.lookahead().kind === SyntaxKinds.GtOperator) { + this.nextToken(); + const end = this.getEndPosition(); + this.nextTokenInJSXChildren(inJSXChildren); + return Factory.createJSXOpeningElement(name, attributes, true, start, end); + } + throw this.createUnexpectError(); +} +/** + * Parse name of jsx element or jsx fragment + * ``` + * JSXElementName := JSXIdentifier + * := JSXMemberExpression + * := JSXNamespaceName + * ``` + * @returns {JSXIdentifier | JSXMemberExpression | JSXNamespacedName} + */ +export function parseJSXElementName(this: Parser): JSXIdentifier | JSXMemberExpression | JSXNamespacedName { + let name: JSXIdentifier | JSXMemberExpression | JSXNamespacedName = this.parseJSXIdentifier(); + if (this.match(SyntaxKinds.ColonPunctuator)) { + this.nextToken(); + const subName = this.parseJSXIdentifier(); + name = Factory.createJSXNamespacedName( + name, + subName, + cloneSourcePosition(name.start), + cloneSourcePosition(subName.end), + ); + } else if (this.match(SyntaxKinds.DotOperator)) { + while (this.match(SyntaxKinds.DotOperator) && !this.match(SyntaxKinds.EOFToken)) { + this.nextToken(); + const property = this.parseJSXIdentifier(); + name = Factory.createJSXMemberExpression( + name, + property, + cloneSourcePosition(name.start), + cloneSourcePosition(property.end), + ); + } + } + return name; +} +/** + * Parse JSX Attributes. + * ``` + * JSXAttributes := JSXAttributes JSXAttribute + * := JSXAttributes JSXSpreadAttribute + * := JSXAttribute + * := JSXSpreadAttribute + * JSXAttribute := JSXAttributeName '=' StringLiteral + * := JSXAttributeName '=' JSXExpressionContainer (expression can not be null) + * := JSXAttributeName '=' JSXElement + * := JSxAttributeName '=' JSXFragment + * := JSXAttrbuteName + * JSXSpreadAttribute := '{''...' AssignmentExpression '}' + * JSXAttributeName := JSXIdentifier + * := JSXNamespaceName + * ``` + * @returns {Array} + */ +export function parseJSXAttributes(this: Parser): Array { + const attribute: Array = []; + while ( + !this.match(SyntaxKinds.EOFToken) && + !this.match(SyntaxKinds.GtOperator) && + !this.match(SyntaxKinds.JSXSelfClosedToken) && + !(this.match(SyntaxKinds.DivideOperator) && this.lookahead().kind === SyntaxKinds.GtOperator) + ) { + // parse spread + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + this.nextToken(); + this.expect(SyntaxKinds.SpreadOperator); + const expression = this.parseAssignmentExpressionAllowIn(); + this.expect(SyntaxKinds.BracesRightPunctuator); + attribute.push( + Factory.createJSXSpreadAttribute( + expression, + cloneSourcePosition(expression.start), + cloneSourcePosition(expression.end), + ), + ); + continue; + } + // parse name + let name: JSXIdentifier | JSXNamespacedName = this.parseJSXIdentifier(); + if (this.match(SyntaxKinds.ColonPunctuator)) { + this.nextToken(); + const subName = this.parseJSXIdentifier(); + name = Factory.createJSXNamespacedName( + name, + subName, + cloneSourcePosition(name.start), + cloneSourcePosition(subName.end), + ); + } + // parse value + if (this.match(SyntaxKinds.AssginOperator)) { + this.lexer.setJSXStringContext(true); + this.nextToken(); + this.lexer.setJSXStringContext(false); + if (this.match(SyntaxKinds.StringLiteral)) { + const value = this.parseStringLiteral(); + attribute.push( + Factory.createJSXAttribute( + name, + value, + cloneSourcePosition(name.start), + cloneSourcePosition(value.end), + ), + ); + continue; + } + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + const expression = this.parseJSXExpressionContainer(false); + if (!expression.expression) { + throw new Error("right hand side of jsx attribute must have expression if start with `{`"); + } + attribute.push( + Factory.createJSXAttribute( + name, + expression, + cloneSourcePosition(name.start), + cloneSourcePosition(expression.end), + ), + ); + continue; + } + const element = this.parseJSXElementOrJSXFragment(false); + attribute.push( + Factory.createJSXAttribute( + name, + element, + cloneSourcePosition(name.start), + cloneSourcePosition(element.end), + ), + ); + } else { + attribute.push( + Factory.createJSXAttribute( + name, + null, + cloneSourcePosition(name.start), + cloneSourcePosition(name.end), + ), + ); + } + } + return attribute; +} +/** + * Parse JSX Children + * ``` + * JSXChildren := JSXChildren JSXChild + * := JSXChild + * JSXChild := JSXText + * := JSXExpressionContainer + * := JSXElement + * := JSXFragment + * := JSXSpreadChild + * JSXSpreadChild := {'...AssignmentExpression '}' + * ``` + * @returns {Array} + */ +export function parseJSXChildren(this: Parser): JSXElement["children"] { + const children: JSXElement["children"] = []; + while (!this.match(SyntaxKinds.JSXCloseTagStart) && !this.match(SyntaxKinds.EOFToken)) { + if (this.match(SyntaxKinds.LtOperator)) { + children.push(this.parseJSXElementOrJSXFragment(true)); + continue; + } + if (this.match(SyntaxKinds.BracesLeftPunctuator)) { + if (this.lookahead().kind == SyntaxKinds.SpreadOperator) { + this.expect(SyntaxKinds.BracesLeftPunctuator); + this.expect(SyntaxKinds.SpreadOperator); + const expression = this.parseAssignmentExpressionAllowIn(); + this.expect(SyntaxKinds.BracesRightPunctuator); + children.push( + Factory.createJSXSpreadChild( + expression, + cloneSourcePosition(expression.start), + cloneSourcePosition(expression.end), + ), + ); + continue; + } + children.push(this.parseJSXExpressionContainer(true)); + continue; + } + children.push(this.parseJSXText()); + } + return children; +} +/** + * Parse JSX expression container + * ``` + * JSXExpressionContainer = '{' AssignmentExpression '}' + * ``` + * @returns {JSXExpressionContainer} + */ +export function parseJSXExpressionContainer(this: Parser, inJSXChildren: boolean): JSXExpressionContainer { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const expression = this.match(SyntaxKinds.BracesRightPunctuator) + ? null + : this.parseAssignmentExpressionAllowIn(); + const { end } = this.expectInJSXChildren(SyntaxKinds.BracesRightPunctuator, inJSXChildren); + return Factory.createsJSXExpressionContainer(expression, start, end); +} +/** + * Parse Closing Element of JSXElement + * ``` + * JSXClosingElement := '' + * ``` + * @returns {JSXClosingElement} + */ +export function parseJSXClosingElement(this: Parser, inJSXChildren: boolean): JSXClosingElement { + const { start } = this.expect(SyntaxKinds.JSXCloseTagStart); + const lastLexerJSXEndTagContext = this.lexer.getJSXGtContext(); + this.lexer.setJSXGtContext(true); + const name = this.parseJSXElementName(); + const { end } = this.expectInJSXChildren(SyntaxKinds.GtOperator, inJSXChildren); + this.lexer.setJSXGtContext(lastLexerJSXEndTagContext); + return Factory.createJSXClosingElement(name, start, end); +} +/** + * + * @returns {JSXIdentifier} + */ +export function parseJSXIdentifier(this: Parser): JSXIdentifier { + // eslint-disable-next-line prefer-const + let { start, end } = this.expect(IdentiferWithKeyworArray); + // eslint-disable-next-line no-constant-condition + while (1) { + if (this.match(SyntaxKinds.MinusOperator)) { + end = this.getEndPosition(); + this.nextToken(); + } else { + break; + } + if (this.match(IdentiferWithKeyworArray)) { + end = this.getEndPosition(); + this.nextToken(); + } else { + break; + } + } + const value = this.lexer.getSourceValueByIndex(start.index, end.index); + return Factory.createJSXIdentifier(value, start, end); +} +export function parseJSXText(this: Parser) { + const { start, end, value } = this.expect(SyntaxKinds.JSXText); + return Factory.createJSXText(value, start, end); +} +/** + * Parse JSXFragment + * ``` + * JSXFragment := `<``/>` JSXChildern `` + * ``` + * @returns {JSXFragment} + */ +export function parseJSXFragment(this: Parser, inJSXChildren: boolean): JSXFragment { + const { start: openingStart } = this.expect(SyntaxKinds.LtOperator); + const { end: openingEnd } = this.expectInJSXChildren(SyntaxKinds.GtOperator, true); + const children = this.parseJSXChildren(); + const { start: closingStart } = this.expect(SyntaxKinds.JSXCloseTagStart); + const { end: closingEnd } = this.expectInJSXChildren(SyntaxKinds.GtOperator, inJSXChildren); + return Factory.createJSXFragment( + Factory.createJSXOpeningFragment(openingStart, openingEnd), + Factory.createJSXClosingFragment(closingStart, closingEnd), + children, + cloneSourcePosition(openingStart), + cloneSourcePosition(closingEnd), + ); +} +export function expectInJSXChildren(this: Parser, kind: SyntaxKinds, inJSXChildren: boolean) { + if (this.match(kind)) { + const metaData = { + value: this.getSourceValue(), + start: this.getStartPosition(), + end: this.getEndPosition(), + }; + if (inJSXChildren) { + this.lexer.nextTokenInJSXChildrenContext(); + } else { + this.lexer.nextToken(); + } + return metaData; + } + throw this.createUnexpectError(); +} +export function nextTokenInJSXChildren(this: Parser, inJSXChildren: boolean) { + if (inJSXChildren) { + this.lexer.nextTokenInJSXChildrenContext(); + } else { + this.lexer.nextToken(); + } +} diff --git a/web-infras/parser/src/parser/parser.ts b/web-infras/parser/src/parser/parser.ts deleted file mode 100644 index 09720b19..00000000 --- a/web-infras/parser/src/parser/parser.ts +++ /dev/null @@ -1,7504 +0,0 @@ -import { - Expression, - FunctionBody, - Function as FunctionAST, - Identifier, - ModuleItem, - Pattern, - PropertyDefinition, - PropertyName, - MethodDefinition, - TemplateElement, - PrivateName, - ObjectMethodDefinition, - ClassMethodDefinition, - ClassElement, - ClassBody, - Class, - VariableDeclaration, - VariableDeclarator, - ImportDeclaration, - ImportDefaultSpecifier, - ImportNamespaceSpecifier, - ImportSpecifier, - RestElement, - ObjectPattern, - ArrayPattern, - StatementListItem, - Declaration, - Statement, - IfStatement, - SwitchCase, - LabeledStatement, - BreakStatement, - ContinueStatement, - ReturnStatement, - WhileStatement, - DoWhileStatement, - TryStatement, - CatchClause, - BlockStatement, - WithStatement, - DebuggerStatement, - ForStatement, - ForInStatement, - ForOfStatement, - ExportDeclaration, - ExportAllDeclaration, - ExportNamedDeclarations, - ExportSpecifier, - ExportDefaultDeclaration, - FunctionDeclaration, - ClassDeclaration, - ClassExpression, - ObjectAccessor, - ClassAccessor, - StringLiteral, - ClassConstructor, - ObjectPatternProperty, - SyntaxKinds, - UnaryOperators, - AssigmentOperators, - AssigmentOperatorKinds, - BinaryOperatorKinds, - UnaryOperatorKinds, - UpdateOperators, - UpdateOperatorKinds, - Keywords, - SourcePosition, - cloneSourcePosition, - Factory, - EmptyStatement, - ObjectExpression, - ArrayExpression, - SpreadElement, - SytaxKindsMapLexicalLiteral, - AssigmentExpression, - isRestElement, - isSpreadElement, - isAssignmentPattern, - isVarDeclaration, - RegexLiteral, - isArrowFunctionExpression, - isIdentifer, - isArrayPattern, - isObjectPattern, - AssignmentPattern, - ObjectProperty, - isMemberExpression, - LexicalLiteral, - isAwaitExpression, - isObjectPatternProperty, - ArrorFunctionExpression, - YieldExpression, - isCallExpression, - isStringLiteral, - ExpressionStatement, - Program, - isPattern, - isUnaryExpression, - JSXElement, - JSXOpeningElement, - JSXAttribute, - JSXSpreadAttribute, - JSXIdentifier, - JSXNamespacedName, - JSXFragment, - JSXMemberExpression, - JSXExpressionContainer, - JSXClosingElement, - isNumnerLiteral, - isFunctionExpression, - isPrivateName, - isFunctionDeclaration, - isClassExpression, - NumericLiteralKinds, - NumberLiteral, - UnaryExpression, - ImportAttribute, - Decorator, - MetaProperty, - CallExpression, - ThisExpression, - TSTypeAliasDeclaration, - TSTypeNode, - TSTypeLiteral, - TSTypeElement, - TSParameter, - TSTypeAnnotation, - TSStringKeyword, - TSNumberKeyword, - TSFunctionType, - TSBigIntKeyword, - TSBooleanKeyword, - TSNullKeyword, - TSUndefinedKeyword, - TSSymbolKeyword, - TSAnyKeyword, - TSUnknowKeyword, - TSTypeReference, - TSEntityName, - TSInterfaceDeclaration, - TSTypeParameterDeclaration, - TSTypeParameterInstantiation, - TSTypeParameter, - TSTypeQuery, - TSTypeOperator, - TSTupleType, - TSLiteralType, - TSVoidKeyword, - TSInterfaceHeritage, - TSEnumDeclaration, - TSEnumBody, - TSEnumMember, - TSDeclareFunction, -} from "web-infra-common"; -import { ExpectToken } from "./type"; -import { ErrorMessageMap } from "./error"; -import { ParserPlugin, ParserUserConfig, getConfigFromUserInput } from "./config"; -import { LexerContext, LexerState, LookaheadToken } from "../lexer/type"; -import { createLexer } from "../lexer/index"; -import { createAsyncArrowExpressionScopeRecorder, AsyncArrowExpressionScope } from "./scope/arrowExprScope"; -import { createStrictModeScopeRecorder, StrictModeScope } from "./scope/strictModeScope"; -import { ExpressionScopeKind } from "./scope/type"; -import { createLexicalScopeRecorder, ExportContext, PrivateNameDefKind } from "./scope/lexicalScope"; -import { - createSymbolScopeRecorder, - FunctionSymbolScope, - NonFunctionalSymbolType, - SymbolType, -} from "./scope/symbolScope"; -import { SyntaxErrorHandler } from "@/src/errorHandler/type"; - -interface Context { - maybeArrowStart: number; - isInType: boolean; - inOperatorStack: Array; - propertiesInitSet: Set; - propertiesProtoDuplicateSet: Set; - lastTokenIndexOfIfStmt: number; - cache: { - decorators: Decorator[] | null; - }; -} - -interface ASTArrayWithMetaData { - nodes: Array; - start: SourcePosition; - end: SourcePosition; -} - -interface LefthansSideParseState { - shouldStop: boolean; - hasOptional: boolean; - optional: boolean; - abortLastTime: boolean; -} -/** - * Create context for parser - * @returns {Context} - */ -function createContext(): Context { - return { - maybeArrowStart: -1, - isInType: false, - inOperatorStack: [], - propertiesInitSet: new Set(), - propertiesProtoDuplicateSet: new Set(), - lastTokenIndexOfIfStmt: -1, - cache: { - decorators: null, - }, - }; -} - -const IdentiferWithKeyworArray = [SyntaxKinds.Identifier, ...Keywords]; -const BindingIdentifierSyntaxKindArray = [ - SyntaxKinds.Identifier, - SyntaxKinds.AwaitKeyword, - SyntaxKinds.YieldKeyword, - SyntaxKinds.LetKeyword, -]; -const PreserveWordSet = new Set(LexicalLiteral.preserveword); -const KeywordSet = new Set([ - ...LexicalLiteral.keywords, - ...LexicalLiteral.BooleanLiteral, - ...LexicalLiteral.NullLiteral, - ...LexicalLiteral.UndefinbedLiteral, -]); -/** - * create parser for input code. - * @param {string} code - * @returns - */ -export function createParser(code: string, errorhandler: SyntaxErrorHandler, option?: ParserUserConfig) { - const lexer = createLexer(code); - const context = createContext(); - const config = getConfigFromUserInput(option); - const lexicalScopeRecorder = createLexicalScopeRecorder(); - const symbolScopeRecorder = createSymbolScopeRecorder(); - const strictModeScopeRecorder = createStrictModeScopeRecorder(); - const asyncArrowExprScopeRecorder = createAsyncArrowExpressionScopeRecorder(); - /** =========================================================== - * Public API for Parser - * =========================================================== - */ - /** - * Only Public API for parser, parse code and - * return a program. - * @returns {Program} - */ - function parse(): Program { - return parseProgram(); - } - return { parse }; - /** =========================================================== - * Private API for other Parser API - * =========================================================== - */ - /** - * Private API for parser, helper to require some plugin - */ - function requirePlugin(...plugins: Array): boolean { - for (const plugin of plugins) { - if (!config.plugins.includes(plugin)) { - return false; - } - } - return true; - } - /** - * Private API for parser, move to next token, skip comment and - * block comment token. - * @returns {SyntaxKinds} - */ - function nextToken(): SyntaxKinds { - lexer.nextToken(); - const token = lexer.getTokenKind(); - if (token === SyntaxKinds.Comment || token == SyntaxKinds.BlockComment) { - return nextToken(); - } - return token; - } - /** - * Private API for parser, get current token kind, skip comment - * and block comment token - * @returns {SyntaxKinds} - */ - function getToken(): SyntaxKinds { - const token = lexer.getTokenKind(); - if (token === SyntaxKinds.Comment || token == SyntaxKinds.BlockComment) { - return nextToken(); - } - return token; - } - /** - * Private API for parser, get current token's string value. - * @returns {string} - */ - function getSourceValue(): string { - return lexer.getSourceValue(); - } - /** - * Private API for parser, just wrapper of lexer, get start - * position of current token. - * @return {SourcePosition} - */ - function getStartPosition(): SourcePosition { - return lexer.getStartPosition(); - } - /** - * Private API for parser, just wrapper of lexer, get end - * position of current token. - * @return {SourcePosition} - */ - function getEndPosition(): SourcePosition { - return lexer.getEndPosition(); - } - /** - * Private API for parser, just wrapper of lexer, get end - * position of last token. - * @returns {SourcePosition} - */ - function getLastTokenEndPositon(): SourcePosition { - return lexer.getLastTokenEndPositon(); - } - /** - * Private API for parser, just wrapper of lookahead method - * of lexer. - * @returns - */ - function lookahead(): LookaheadToken { - return lexer.lookahead(); - } - /** - * Private API for parser, just wrapper of lexer method. - * @returns - */ - function readRegex() { - return lexer.readRegex(); - } - /** - * Private API for parser, just wrapper of lexer method, check - * is a line terminator in the range of last token end and start - * of current token. - * @returns {boolean} - */ - function getLineTerminatorFlag(): boolean { - return lexer.getLineTerminatorFlag(); - } - /** - * Private API for parser, just wrapper of lexer methid, check - * is current token contain a unicode esc. - */ - function getEscFlag(): boolean { - return lexer.getEscFlag(); - } - /** - * Private API for parser, is current token kind match the - * given token kind ? - * @param {SyntaxKinds | Array} kind - * @returns {boolean} - */ - function match(kind: SyntaxKinds | Array): boolean { - const currentToken = getToken(); - if (Array.isArray(kind)) { - const tokenSet = new Set(kind); - return tokenSet.has(currentToken); - } - return currentToken === kind; - } - /** - * Private API for check if current identifier is a given value, - * used when we need to detect a contextual keyword (like `async`). - * @param {string} value - * @returns {boolean} - */ - function isContextKeyword(value: string): boolean { - if (getSourceValue() === value && getToken() === SyntaxKinds.Identifier && !lexer.getEscFlag()) { - return true; - } - return false; - } - /** - * Private API for parser, expect current token is one of given token(s), - * it not, it will create a unexpect error, if is one of given token, it will - * eat token and return `value`, `start`, `end` of token - * @param kind - * @param message - * @returns {ExpectToken} - */ - function expect(kind: SyntaxKinds | Array): ExpectToken { - if (match(kind)) { - const metaData = { - value: getSourceValue(), - start: getStartPosition(), - end: getEndPosition(), - }; - nextToken(); - return metaData; - } - throw createUnexpectError(); - } - /** - * Private API for parser, expect current token is one of given token(s), - * it not, it will create a unexpect error, if is one of given token, it - * will NOT eat token. - * @param kind - * @param message - * @returns {void} - */ - function expectButNotEat(kind: SyntaxKinds | Array): void { - if (match(kind)) { - return; - } - throw createUnexpectError(); - } - /** - * Private API for `shouldInsertSemi` and `isSoftInsertSemi`, - * test is there a semi or equal syntax, return three state - * - `SemiExisted`: there is a semi token. - * - `SemiInsertAble`: there is a equal to semi syntax. - * - `SemiNotExisted`: there is no semi or equal syntax, - * @returns - */ - function isSemiInsertable() { - if (match(SyntaxKinds.SemiPunctuator)) { - return "SemiExisted"; - } - if (match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - return "SemiInsertAble"; - } - if (getLineTerminatorFlag()) { - return "SemiInsertAble"; - } - return "SemiNotExisted"; - } - /** - * Private API for most of insert semi check, if semi exist, eat token, - * pass if equal syntax exist, throw error if not existed semi or equal - * syntax. - * @returns - */ - function shouldInsertSemi() { - const semiState = isSemiInsertable(); - switch (semiState) { - case "SemiExisted": - nextToken(); - return; - case "SemiInsertAble": - return; - case "SemiNotExisted": - // recoverable error - raiseError(ErrorMessageMap.missing_semicolon, getStartPosition()); - } - } - /** - * Private API for insert semi for three edge case - * - `DoWhileStatement` - * - `ReturnStatement` - * - `YeildExpression` - * @param {boolean} shouldEat - false whem used in yield expression. - * @returns - */ - function isSoftInsertSemi(shouldEat: boolean = true) { - const semiState = isSemiInsertable(); - switch (semiState) { - case "SemiExisted": - if (shouldEat) { - nextToken(); - } - return true; - case "SemiInsertAble": - return true; - case "SemiNotExisted": - return false; - } - } - /** - * Create a Message error from parser's error map. - * @param {string} messsage - */ - function createMessageError(messsage: string, position?: SourcePosition) { - if (position === undefined) position = getStartPosition(); - - return new Error(`[Syntax Error]: ${messsage} (${position.row}, ${position.col})`); - } - /** - * Create a error object with message tell developer that get a - * unexpect token. - * @returns {Error} - */ - function createUnexpectError(): Error { - const startPos = getStartPosition(); - return new Error( - `[Syntax Error]: Unexpect token ${SytaxKindsMapLexicalLiteral[getToken()]}(${startPos.row}, ${startPos.col}).`, - ); - } - /** - * Given that this parser is recurive decent parser, some - * function must call with some start token, if function call - * with unexecpt start token, it should throw this error. - * @param {Array} startTokens - * @returns {Error} - */ - function createUnreachError(startTokens: Array = []): Error { - const start = getStartPosition(); - let message = `[Unreach Zone]: this piece of code should not be reach (${start.row}, ${start.col}), have a unexpect token ${getToken()} (${getSourceValue()}).`; - if (startTokens.length !== 0) { - message += " it should call with start token["; - for (const token of startTokens) { - message += `${token}, `; - } - message += "]"; - } - message += ", please report to developer."; - return new Error(message); - } - function disAllowInOperaotr(parseCallback: () => T): T { - context.inOperatorStack.push(false); - const result = parseCallback(); - context.inOperatorStack.pop(); - return result; - } - /** - * Private API for parse api for expression, in ECMAscript, there is - * a syntax tranlation for in operator production rule. - * @param parseCallback - * @returns - */ - function allowInOperaotr(parseCallback: () => T) { - context.inOperatorStack.push(true); - const result = parseCallback(); - context.inOperatorStack.pop(); - return result; - } - /** =========================================================== - * Private API for other Parser API, just wrapper of recorder - * =========================================================== - */ - - /** - * please reference to recorder api - */ - function enterFunctionScope(isAsync: boolean = false, isGenerator: boolean = false) { - asyncArrowExprScopeRecorder.enterFunctionScope(); - strictModeScopeRecorder.enterRHSStrictModeScope(); - symbolScopeRecorder.enterFunctionSymbolScope(); - lexicalScopeRecorder.enterFunctionLexicalScope(isAsync, isGenerator); - lexer.setStrictModeContext(isInStrictMode()); - } - function exitFunctionScope(focusCheck: boolean) { - const isNonSimpleParam = !isCurrentFunctionParameterListSimple(); - const isStrict = isInStrictMode(); - const symbolScope = symbolScopeRecorder.exitSymbolScope()!; - if (isNonSimpleParam || isStrict || focusCheck) { - const functionSymbolScope = symbolScope as FunctionSymbolScope; - for (const symPos of functionSymbolScope.duplicateParams) { - raiseError(ErrorMessageMap.duplicate_param, symPos); - } - } - asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); - strictModeScopeRecorder.exitStrictModeScope(); - lexicalScopeRecorder.exitFunctionLexicalScope(); - lexer.setStrictModeContext(isInStrictMode()); - } - function enterProgram() { - symbolScopeRecorder.enterProgramSymbolScope(); - lexicalScopeRecorder.enterProgramLexicalScope( - config.allowAwaitOutsideFunction || false, - config.sourceType === "module", - ); - lexer.setStrictModeContext(config.sourceType === "module"); - } - function exitProgram() { - if (!config.allowUndeclaredExports) { - for (const pos of symbolScopeRecorder.getProgramContainUndefSymbol()) { - raiseError(ErrorMessageMap.babel_error_export_is_not_defined, pos); - } - } - symbolScopeRecorder.exitSymbolScope(); - lexicalScopeRecorder.exitProgramLexicalScope(); - lexer.setStrictModeContext(false); - } - function enterBlockScope() { - lexicalScopeRecorder.enterBlockLexicalScope(false); - symbolScopeRecorder.enterBlockSymbolScope(); - } - function exitBlockScope() { - lexicalScopeRecorder.exitBlockLexicalScope(); - symbolScopeRecorder.exitSymbolScope(); - } - function enterCatchBlockScope() { - lexicalScopeRecorder.enterBlockLexicalScope(true); - symbolScopeRecorder.enterFunctionSymbolScope(); - } - function exitCatchBlockScope() { - lexicalScopeRecorder.exitBlockLexicalScope(); - symbolScopeRecorder.exitSymbolScope(); - } - function parseAsLoop(callback: () => T): T { - lexicalScopeRecorder.enterVirtualBlockScope("Loop"); - const result = callback(); - lexicalScopeRecorder.exitVirtualBlockScope(); - return result; - } - function parseAsSwitch(callback: () => T): T { - lexicalScopeRecorder.enterVirtualBlockScope("Switch"); - const result = callback(); - lexicalScopeRecorder.exitVirtualBlockScope(); - return result; - } - function recordScope(kind: ExpressionScopeKind, position: SourcePosition) { - strictModeScopeRecorder.record(kind, position); - asyncArrowExprScopeRecorder.record(kind, position); - } - function enterArrowFunctionBodyScope(isAsync: boolean = false) { - lexicalScopeRecorder.enterArrowFunctionBodyScope(isAsync); - symbolScopeRecorder.enterFunctionSymbolScope(); - } - function exitArrowFunctionBodyScope() { - const symbolScope = symbolScopeRecorder.exitSymbolScope()!; - const functionSymbolScope = symbolScope as FunctionSymbolScope; - for (const symPos of functionSymbolScope.duplicateParams) { - raiseError(ErrorMessageMap.duplicate_param, symPos); - } - lexicalScopeRecorder.exitArrowFunctionBodyScope(); - } - function parseWithArrowExpressionScope(callback: () => T): [T, AsyncArrowExpressionScope] { - asyncArrowExprScopeRecorder.enterAsyncArrowExpressionScope(); - const result = callback(); - const scope = asyncArrowExprScopeRecorder.getCurrentAsyncArrowExpressionScope()!; - asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); - return [result, scope]; - } - function parseWithCatpureLayer(callback: () => T): [T, StrictModeScope] { - strictModeScopeRecorder.enterCatpureStrictModeScope(); - const result = callback(); - const scope = strictModeScopeRecorder.getCurrentStrictModeScope(); - strictModeScopeRecorder.exitStrictModeScope(); - return [result, scope]; - } - function parseWithLHSLayer(callback: () => T): T { - strictModeScopeRecorder.enterLHSStrictModeScope(); - const result = callback(); - strictModeScopeRecorder.exitStrictModeScope(); - return result; - } - function parseWithLHSLayerReturnScope(callback: () => T): [T, StrictModeScope] { - strictModeScopeRecorder.enterLHSStrictModeScope(); - const result = callback(); - const scope = strictModeScopeRecorder.getCurrentStrictModeScope(); - strictModeScopeRecorder.exitStrictModeScope(); - return [result, scope]; - } - function parseWithRHSLayer(callback: () => T): T { - strictModeScopeRecorder.enterRHSStrictModeScope(); - const result = callback(); - strictModeScopeRecorder.exitStrictModeScope(); - return result; - } - function checkStrictModeScopeError(scope: StrictModeScope) { - if (isInStrictMode() && strictModeScopeRecorder.isStrictModeScopeViolateStrictMode(scope)) { - if (scope.kind !== "RHSLayer") { - for (const pos of scope.argumentsIdentifier) { - raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); - } - for (const pos of scope.evalIdentifier) { - raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); - } - for (const pos of scope.letIdentifier) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); - } - for (const pos of scope.yieldIdentifier) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); - } - for (const pos of scope.preservedWordIdentifier) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, pos); - } - } - } - } - function checkAsyncArrowExprScopeError(scope: AsyncArrowExpressionScope) { - if (asyncArrowExprScopeRecorder.isAsyncArrowExpressionScopeHaveError(scope)) { - for (const pos of scope.awaitExpressionInParameter) { - raiseError(ErrorMessageMap.extra_error_await_expression_can_not_used_in_parameter_list, pos); - } - for (const pos of scope.yieldExpressionInParameter) { - raiseError(ErrorMessageMap.extra_error_yield_expression_can_not_used_in_parameter_list, pos); - } - for (const pos of scope.awaitIdentifier) { - raiseError(ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, pos); - } - } - } - function enterFunctionParameter() { - lexicalScopeRecorder.enterFunctionLexicalScopeParamemter(); - } - function existFunctionParameter() { - lexicalScopeRecorder.exitFunctionLexicalScopeParamemter(); - } - function setCurrentFunctionContextAsGenerator() { - lexicalScopeRecorder.setCurrentFunctionLexicalScopeAsGenerator(); - } - function setCurrentFunctionContextAsStrictMode() { - lexicalScopeRecorder.setCurrentFunctionLexicalScopeAsStrictMode(); - lexer.setStrictModeContext(true); - } - /** - * Private API for `For-In` parsering problem. - * @returns {boolean} - */ - function getCurrentInOperatorStack(): boolean { - if (context.inOperatorStack.length === 0) { - return false; - } - return context.inOperatorStack[context.inOperatorStack.length - 1]; - } - function isTopLevel(): boolean { - return lexicalScopeRecorder.isInTopLevel(); - } - function isCurrentScopeParseAwaitAsExpression(): boolean { - return lexicalScopeRecorder.canAwaitParseAsExpression(); - } - function isCurrentScopeParseYieldAsExpression(): boolean { - return lexicalScopeRecorder.canYieldParseAsExpression(); - } - function isInParameter(): boolean { - return lexicalScopeRecorder.isInParameter(); - } - function setCurrentFunctionParameterListAsNonSimple() { - return lexicalScopeRecorder.setCurrentFunctionLexicalScopeParameterAsNonSimple(); - } - function isCurrentFunctionParameterListSimple(): boolean { - return lexicalScopeRecorder.isCurrentFunctionLexicalScopeParameterSimple(); - } - function isParentFunctionAsync(): boolean { - return lexicalScopeRecorder.isParentFunctionAsync(); - } - function isParentFunctionGenerator(): boolean { - return lexicalScopeRecorder.isParentFunctionGenerator(); - } - function enterClassScope(isExtend: boolean = false) { - lexicalScopeRecorder.enterClassLexicalScope(isExtend); - asyncArrowExprScopeRecorder.enterAsyncArrowExpressionScope(); - symbolScopeRecorder.enterClassSymbolScope(); - } - function existClassScope() { - const duplicateSet = symbolScopeRecorder.isDuplicatePrivateName(); - if (duplicateSet) { - for (const pos of duplicateSet.values()) { - raiseError(ErrorMessageMap.babel_error_private_name_duplicate, pos); - } - } - const undefSet = symbolScopeRecorder.isUndeinfedPrivateName(); - if (undefSet) { - for (const pos of undefSet.values()) { - raiseError(ErrorMessageMap.babel_error_private_name_undeinfed, pos); - } - } - lexicalScopeRecorder.exitClassLexicalScope(); - symbolScopeRecorder.exitClassSymbolScope(); - asyncArrowExprScopeRecorder.exitAsyncArrowExpressionScope(); - } - function isInClassScope(): boolean { - return lexicalScopeRecorder.isInClassScope(); - } - function isCurrentClassExtend(): boolean { - return lexicalScopeRecorder.isCurrentClassExtend(); - } - function usePrivateName(name: string, position: SourcePosition, type: PrivateNameDefKind = "other") { - return symbolScopeRecorder.usePrivateName(name, position, type); - } - function defPrivateName(name: string, position: SourcePosition, type: PrivateNameDefKind = "other") { - return symbolScopeRecorder.defPrivateName(name, position, type); - } - function enterDelete() { - lexicalScopeRecorder.enterDelete(); - } - function exitDelete() { - lexicalScopeRecorder.exitDelete(); - } - function isInDelete() { - return lexicalScopeRecorder.isCurrentInDelete(); - } - function isInStrictMode(): boolean { - return lexicalScopeRecorder.isInStrictMode(); - } - function isDirectToFunctionContext(): boolean { - return lexicalScopeRecorder.isDirectToFunctionContext(); - } - function isDirectToClassScope(): boolean { - return lexicalScopeRecorder.isDirectToClassScope(); - } - function isReturnValidate(): boolean { - return config.allowReturnOutsideFunction || lexicalScopeRecorder.isReturnValidate(); - } - function isBreakValidate(): boolean { - return lexicalScopeRecorder.isBreakValidate(); - } - function isContinueValidate(): boolean { - return lexicalScopeRecorder.isContinueValidate(); - } - function canLabelReach(name: string): boolean { - return lexicalScopeRecorder.canLabelReach(name); - } - function isEncloseInFunction(): boolean { - return lexicalScopeRecorder.isEncloseInFunction(); - } - function isInPropertyName(): boolean { - return lexicalScopeRecorder.isInPropertyName(); - } - function setExportContext(context: ExportContext) { - lexicalScopeRecorder.setExportContext(context); - } - function getExportContext(): ExportContext { - return lexicalScopeRecorder.getExportContext(); - } - function takeCacheDecorator() { - const list = context.cache.decorators; - context.cache.decorators = null; - return list; - } - function mergeDecoratorList(input1: Decorator[] | null, input2: Decorator[] | null) { - const list = [...(input1 || []), ...(input2 || [])]; - if (list.length === 0) { - return null; - } - return list; - } - /** - * - * @param name - * @param position - * @returns - */ - function declarateNonFunctionalSymbol(name: string, position: SourcePosition) { - if (context.isInType) return; - if (isInParameter()) { - symbolScopeRecorder.declarateParam(name, position); - return; - } - if (!symbolScopeRecorder.declarateNonFunctionalSymbol(name)) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); - } - const isExportAlreadyExist = declarateExportSymbolIfInContext(name, position); - if (isExportAlreadyExist) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); - } - return; - } - /** - * - * @param name - * @param generator - * @param position - * @returns - */ - function delcarateFcuntionSymbol(name: string, generator: boolean, position: SourcePosition) { - if (context.isInType) return; - const duplicateType = symbolScopeRecorder.declarateFuncrtionSymbol(name, generator); - if (duplicateType) { - if ( - (!generator && - ((duplicateType === SymbolType.Function && config.sourceType === "module") || - duplicateType === SymbolType.GenFunction)) || - (generator && - ((duplicateType === SymbolType.GenFunction && config.sourceType === "module") || - duplicateType === SymbolType.Function)) || - (duplicateType === SymbolType.Var && lexicalScopeRecorder.isInCatch()) || - duplicateType === SymbolType.Let || - duplicateType === SymbolType.Const - ) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); - } - } - const isExportAlreadyExist = declarateExportSymbolIfInContext(name, position); - if (isExportAlreadyExist) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); - } - return; - } - function declarateLetSymbol(name: string, position: SourcePosition) { - if (context.isInType) return; - if (!symbolScopeRecorder.declarateLetSymbol(name)) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, position); - } - const isExportAlreadyExist = declarateExportSymbolIfInContext(name, position); - if (isExportAlreadyExist) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportAlreadyExist); - } - } - function declarateParam(name: string, position: SourcePosition) { - if (context.isInType) return; - symbolScopeRecorder.declarateParam(name, position); - declarateExportSymbolIfInContext(name, position); - return; - } - function declarateExportSymbolIfInContext(name: string, position: SourcePosition) { - if (context.isInType) return; - switch (getExportContext()) { - case ExportContext.NotInExport: - return null; - case ExportContext.InExport: { - setExportContext(ExportContext.NotInExport); - return declarateExportSymbol(name, position); - } - case ExportContext.InExportBinding: { - return declarateExportSymbol(name, position); - } - } - } - function declarateExportSymbol(name: string, position: SourcePosition) { - if (context.isInType) return; - return symbolScopeRecorder.declarateExportSymbol(name, position); - } - function isVariableDeclarated(name: string) { - return symbolScopeRecorder.isVariableDeclarated(name); - } - function getSymbolType() { - return symbolScopeRecorder.getSymbolType() as NonFunctionalSymbolType; - } - function setSymbolType(symbolType: NonFunctionalSymbolType) { - symbolScopeRecorder.setSymbolType(symbolType); - } - /** - * Raise a error, if config recoverable set to false, it will throw a error and - * cause stack unwidning. - * @param message - * @param position - */ - function raiseError(message: string, position: SourcePosition) { - errorhandler.pushSyntaxErrors({ - message, - position, - }); - } - /** =========================================================== - * Parser internal Parse API - * =========================================================== - */ - /** ================================================== - * Top level parse function - * ================================================== - */ - function parseProgram() { - const body: Array = []; - enterProgram(); - while (!match(SyntaxKinds.EOFToken)) { - body.push(parseModuleItem()); - } - for (const propertyHasInit of context.propertiesInitSet) { - raiseError(ErrorMessageMap.Syntax_error_Invalid_shorthand_property_initializer, propertyHasInit.start); - } - for (const duplicateProto of context.propertiesProtoDuplicateSet) { - raiseError( - ErrorMessageMap.syntax_error_property_name__proto__appears_more_than_once_in_object_literal, - duplicateProto.start, - ); - } - exitProgram(); - return Factory.createProgram( - body, - body.length === 0 ? getStartPosition() : cloneSourcePosition(body[0].start), - getEndPosition(), - ); - } - function parseModuleItem(): ModuleItem { - if (match(SyntaxKinds.AtPunctuator)) { - parseDecoratorListToCache(); - } - const token = getToken(); - switch (token) { - case SyntaxKinds.ImportKeyword: { - const { kind } = lookahead(); - if (kind === SyntaxKinds.DotOperator || kind === SyntaxKinds.ParenthesesLeftPunctuator) { - return parseStatementListItem(); - } - return parseImportDeclaration(); - } - case SyntaxKinds.ExportKeyword: - return parseExportDeclaration(); - default: - return parseStatementListItem(); - } - } - function parseStatementListItem(): StatementListItem { - const token = getToken(); - switch (token) { - // 'aync' maybe is - // 1. aync function -> declaration - // 2. aync arrow function -> statement(expressionStatement) - // 3. identifer -> statement (expressionStatement) - case SyntaxKinds.ConstKeyword: - case SyntaxKinds.FunctionKeyword: - case SyntaxKinds.ClassKeyword: - case SyntaxKinds.AtPunctuator: - case SyntaxKinds.EnumKeyword: - return parseDeclaration(); - case SyntaxKinds.Identifier: { - const declar = tryParseDeclarationWithIdentifierStart(); - if (!declar) { - return parseStatement(); - } - return declar; - } - case SyntaxKinds.LetKeyword: - if (isLetPossibleIdentifier()) { - return parseStatement(); - } - return parseDeclaration(); - default: - return parseStatement(); - } - } - function isLetPossibleIdentifier() { - const { kind: kind } = lookahead(); - if ( - kind === SyntaxKinds.BracesLeftPunctuator || // object pattern - kind === SyntaxKinds.BracketLeftPunctuator || // array pattern - kind === SyntaxKinds.Identifier || // id - kind === SyntaxKinds.AwaitKeyword || - kind === SyntaxKinds.YieldKeyword - ) { - return false; - } - return true; - } - /** - * Parse Declaration - * - * ``` - * Declaration := ('let' | 'const') BindingLst - * := FunctionDeclaration - * := FunctionGeneratorDeclaration - * := 'async' FunctionDeclaration - * := 'async' FunctionGeneratorDeclaration - * := ClassDeclaration - * ``` - * when call parseDeclaration, please make sure currentToken is - * - `let` or `const` keyword - * - `function` keyword - * - `class` keyword - * - `async` with `function` keyword - * - * ref: https://tc39.es/ecma262/#prod-Declaration - * @returns - */ - function parseDeclaration(): Declaration { - const token = getToken(); - switch (token) { - // async function declaration - case SyntaxKinds.Identifier: - case SyntaxKinds.EnumKeyword: { - const declar = tryParseDeclarationWithIdentifierStart(); - if (!declar) { - throw createUnexpectError(); - } - return declar; - } - // function delcaration - case SyntaxKinds.FunctionKeyword: - return parseFunctionDeclaration(false, false); - case SyntaxKinds.ConstKeyword: - case SyntaxKinds.LetKeyword: - return parseVariableDeclaration(); - case SyntaxKinds.AtPunctuator: - return parseClassDeclaration(parseDecoratorList()); - case SyntaxKinds.ClassKeyword: - return parseClassDeclaration(null); - default: - throw createUnexpectError(); - } - } - function tryParseDeclarationWithIdentifierStart(): Declaration | undefined { - if (getEscFlag()) { - return; - } - if (match(SyntaxKinds.EnumKeyword)) { - return parseTSEnumDeclaration(); - } - const { kind: lookaheadToken, lineTerminatorFlag } = lookahead(); - const sourceValue = getSourceValue(); - switch (sourceValue) { - case "async": { - if (!(lookaheadToken === SyntaxKinds.FunctionKeyword && !lineTerminatorFlag)) { - return; - } - nextToken(); - if (getLineTerminatorFlag()) { - raiseError(ErrorMessageMap.missing_semicolon, getStartPosition()); - } - return parseFunctionDeclaration(true, false); - } - case "type": { - if (!(lookaheadToken === SyntaxKinds.Identifier && !lineTerminatorFlag)) { - return; - } - return parseTSTypeAlias(); - } - case "interface": { - if (!(lookaheadToken === SyntaxKinds.Identifier && !lineTerminatorFlag)) { - return; - } - return parseTSInterfaceDeclaration(); - } - default: { - return; - } - } - } - /** - * ref: https://tc39.es/ecma262/#prod-Statement - */ - function parseStatement(): Statement { - const token = getToken(); - switch (token) { - case SyntaxKinds.SwitchKeyword: - return parseSwitchStatement(); - case SyntaxKinds.ContinueKeyword: - return parseContinueStatement(); - case SyntaxKinds.BreakKeyword: - return parseBreakStatement(); - case SyntaxKinds.ReturnKeyword: - return parseReturnStatement(); - case SyntaxKinds.BracesLeftPunctuator: - return parseBlockStatement(); - case SyntaxKinds.TryKeyword: - return parseTryStatement(); - case SyntaxKinds.ThrowKeyword: - return parseThrowStatement(); - case SyntaxKinds.WithKeyword: - return parseWithStatement(); - case SyntaxKinds.DebuggerKeyword: - return parseDebuggerStatement(); - case SyntaxKinds.SemiPunctuator: - return parseEmptyStatement(); - case SyntaxKinds.IfKeyword: - return parseIfStatement(); - case SyntaxKinds.ForKeyword: - return parseForStatement(); - case SyntaxKinds.WhileKeyword: - return parseWhileStatement(); - case SyntaxKinds.DoKeyword: - return parseDoWhileStatement(); - case SyntaxKinds.VarKeyword: - return parseVariableDeclaration(); - default: - if (match(SyntaxKinds.Identifier) && lookahead().kind === SyntaxKinds.ColonPunctuator) { - return parseLabeledStatement(); - } - return parseExpressionStatement(); - } - } - /** - * This is a critial helper function for transform expression (major is ObjectExpression - * and ArrayExpression) to Pattern (`BindingObjectPattern`, `BindingArrayPattern`, `AssignmentObjectPattern` - * `AssignmentArrayPattern`). - * - * ### Use Case : ParseAssignmentExpression - * This function is used when `parseAssignmentExpression`, because parseAssignmentExpression - * would parse left as expression first, when left is followed by assignment operator, we need - * to transform expression to AssignmentPattern. - * - * ### Use Case : ParseArrowFunctionExpression - * When `parseArrowFunctionExpression`, it would use this function too. Because `parseArrowFunctionExpresssion` - * might accept argument (array of `AssignmentExpression`) as param, so we need to transform arguments to function - * parameter, which is transform `AssignmenExpression` to `BiningElement`(`Identifier` or `BindingPattern`). - * - * ### Paramemter: isBinding - * Most of BindingPattern and AssignmentPattern's production rule is alike, one key different is that BindingPattern - * `PropertyName` can only have `BindingElement`, but `PropertyName` of AssignmentPattern can have LeftHandSideExpression - * so we add a param `isBinding` to determinate is transform to BindingPattern or not. - * @param {Expression} expr target for transform to Pattern - * @param {boolean} isBinding Is transform to BindingPattern - */ - function exprToPattern(expr: Expression, isBinding: boolean): Pattern { - // TODO, remove impl function. - return exprToPatternImpl(expr, isBinding); - } - function exprToPatternImpl(node: Expression, isBinding: boolean): Pattern { - /** - * parentheses in pattern only allow in Assignment Pattern - * for MemberExpression and Identifier - */ - if (node.parentheses) { - if (!isParanValidationInPattern(isBinding, node)) - // recoverable error - raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, node.start); - } - switch (node.kind) { - case SyntaxKinds.TSAsExpression: - case SyntaxKinds.TSTypeAssertionExpression: - case SyntaxKinds.TSSatisfiesExpression: - case SyntaxKinds.TSNonNullExpression: - if (!isBinding) { - // only accept id, member expression and nested TS expression. - node.expression = exprToPattern(node.expression, isBinding) as Expression; - return node; - } else { - throw createMessageError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side); - } - case SyntaxKinds.AssigmentExpression: { - return assignmentExpressionToAssignmentPattern(node, isBinding); - } - case SyntaxKinds.SpreadElement: { - return spreadElementToFunctionRestParameter(node); - } - case SyntaxKinds.ArrayExpression: { - return arrayExpressionToArrayPattern(node, isBinding); - } - case SyntaxKinds.ObjectExpression: { - return objectExpressionToObjectPattern(node, isBinding); - } - case SyntaxKinds.Identifier: - declarateSymbolInBindingPatternAsParam(node.name, isBinding, node.start); - return node as Identifier; - case SyntaxKinds.MemberExpression: - if (!isBinding) { - return node as Pattern; - } - // fall to error - // eslint-disable-next-line no-fallthrough - default: - throw createMessageError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side); - } - } - function isParanValidationInPattern(isBinding: boolean, expr: Expression): boolean { - if (isBinding) return false; - return isAssignable(expr); - } - /** - * Is a node match the DestructuringAssignmentTarget in ECMA spec. - * @param node - * @returns - */ - function isAssignable(node: ModuleItem) { - switch (node.kind) { - case SyntaxKinds.Identifier: - case SyntaxKinds.MemberExpression: - return true; - case SyntaxKinds.TSAsExpression: - case SyntaxKinds.TSTypeAssertionExpression: - case SyntaxKinds.TSSatisfiesExpression: - case SyntaxKinds.TSNonNullExpression: - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return isAssignable((node as any).expression); - default: - return false; - } - } - /** - * ## Transform Assignment Expression - * @param expr - * @param isBinding - * @returns - */ - function assignmentExpressionToAssignmentPattern(expr: AssigmentExpression, isBinding: boolean) { - const left = isBinding ? helperCheckPatternWithBinding(expr.left) : expr.left; - if (expr.operator !== SyntaxKinds.AssginOperator) { - raiseError(ErrorMessageMap.syntax_error_invalid_assignment_left_hand_side, expr.start); - } - return Factory.createAssignmentPattern( - left as Pattern, - expr.right, - undefined, - undefined, - expr.start, - expr.end, - ); - } - /** - * Transform a assignment pattern to a binding assignment pattern - * @param leftValue - * @returns - */ - function helperCheckPatternWithBinding(leftValue: Pattern): Pattern { - if (isObjectPattern(leftValue)) { - for (const property of leftValue.properties) { - if (isObjectPatternProperty(property)) { - if (property.value && isMemberExpression(property.value)) { - raiseError(ErrorMessageMap.babel_error_binding_member_expression, property.start); - } - if (property.value && isAssignable(property.value) && (property.value as Expression).parentheses) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, leftValue.start); - } - } - } - return leftValue; - } - if (isAssignmentPattern(leftValue)) { - helperCheckPatternWithBinding(leftValue.left); - return leftValue; - } - if (isRestElement(leftValue)) { - helperCheckPatternWithBinding(leftValue.argument); - return leftValue; - } - if (isArrayPattern(leftValue)) { - for (const pat of leftValue.elements) { - if (pat) { - helperCheckPatternWithBinding(pat); - } - } - } - if (isAssignable(leftValue)) { - const expr = leftValue as Expression; - if (expr.parentheses) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, leftValue.start); - } - } - return leftValue; - } - /** - * ## Transform `SpreadElement` to RestElement in function param - * - * Accoring to production rule, `FunctionRestParameter` is just alias - * of `BindingRestElement` which be used in ArrayPattern. - * @param spreadElement - * @returns - */ - function spreadElementToFunctionRestParameter(spreadElement: SpreadElement) { - return spreadElementToArrayRestElement(spreadElement, true); - } - /** - * ## Transform `ArrayExpression` to `ArrayPattern` - * @param elements - * @param isBinding - * @returns - */ - function arrayExpressionToArrayPattern(expr: ArrayExpression, isBinding: boolean): ArrayPattern { - const arrayPatternElements: Array = []; - const restElementIndexs = []; - for (let index = 0; index < expr.elements.length; ++index) { - const element = expr.elements[index]; - if (!element) { - arrayPatternElements.push(null); - continue; - } - if (isSpreadElement(element)) { - arrayPatternElements.push(spreadElementToArrayRestElement(element, isBinding)); - restElementIndexs.push(index); - continue; - } - arrayPatternElements.push(exprToPattern(element, isBinding)); - } - if ( - restElementIndexs.length > 1 || - (restElementIndexs.length === 1 && - (restElementIndexs[0] !== arrayPatternElements.length - 1 || expr.trailingComma)) - ) { - raiseError(ErrorMessageMap.syntax_error_parameter_after_rest_parameter, expr.end); - } - return Factory.createArrayPattern(arrayPatternElements, undefined, undefined, expr.start, expr.end); - } - /** - * ## Transform `SpreadElement` in ArrayPattern - * This function transform spread element to following two production rule AST: - * - * - `BindingRestElement` in `ArrayBindingPattern` - * - `AssignmentRestElement` in `ArrayAssignmentPattern` - * - * According to production rule, `BindingRestElement`'s argument can only be identifier or ObjectPattern - * or ArrayPattern, and argument of `AssignmentRestElement` can only be identifier or memberExpression. - * ``` - * BindingRestElement := ... BindingIdentifier - * := ... BindingPattern - * AssignmentRestElement :=... DestructuringAssignmentTarget - * ``` - * @param spreadElement - * @param isBinding - */ - function spreadElementToArrayRestElement(spreadElement: SpreadElement, isBinding: boolean): RestElement { - const argument = exprToPattern(spreadElement.argument, isBinding); - if (isAssignmentPattern(argument)) { - // recoverable error - raiseError( - ErrorMessageMap.v8_error_rest_assignment_property_must_be_followed_by_an_identifier_in_declaration_contexts, - argument.start, - ); - } - return Factory.createRestElement(argument, undefined, undefined, spreadElement.start, argument.end); - } - /** - * ## Transform `ObjectExpression` To `ObjectPattern` - * @param properties - * @param isBinding - * @returns - */ - function objectExpressionToObjectPattern(expr: ObjectExpression, isBinding: boolean): ObjectPattern { - const objectPatternProperties: Array = []; - const restElementIndexs = []; - for (let index = 0; index < expr.properties.length; ++index) { - const property = expr.properties[index]; - switch (property.kind) { - case SyntaxKinds.ObjectProperty: - objectPatternProperties.push(ObjectPropertyToObjectPatternProperty(property, isBinding)); - break; - case SyntaxKinds.SpreadElement: - restElementIndexs.push(index); - objectPatternProperties.push(spreadElementToObjectRestElement(property, isBinding)); - break; - default: - throw createMessageError(ErrorMessageMap.invalid_left_value); - } - } - if ( - restElementIndexs.length > 1 || - (restElementIndexs.length === 1 && - (restElementIndexs[0] !== objectPatternProperties.length - 1 || expr.trailingComma)) - ) { - raiseError(ErrorMessageMap.syntax_error_parameter_after_rest_parameter, expr.end); - } - return Factory.createObjectPattern(objectPatternProperties, undefined, undefined, expr.start, expr.end); - } - /** - * ## Transform `SpreadElement` in ObjectPattern - * This function transform spread element to following two production rule AST: - * - * - `BindingRestProperty` in BindingObjectPattern - * - `AssignmentRestProperty` in AssignObjectPattern - * - * According to production rule, `BindingRestProperty`'s argument can only be identifier, - * and argument of `AssignmentRestProperty` can only be identifier or memberExpression. - * ``` - * BindingRestProperty := ... BindingIdentifier - * AssignmentRestProperty:= ... DestructuringAssignmentTarget - * ``` - */ - function spreadElementToObjectRestElement(spreadElement: SpreadElement, isBinding: boolean): RestElement { - const argument = exprToPattern(spreadElement.argument, isBinding); - if (isBinding) { - if (!isIdentifer(argument)) { - // recoverable error - raiseError( - ErrorMessageMap.v8_error_rest_binding_property_must_be_followed_by_an_identifier_in_declaration_contexts, - argument.start, - ); - } - } else { - if (!isAssignable(argument)) { - // recoverable error - raiseError( - ErrorMessageMap.v8_error_rest_assignment_property_must_be_followed_by_an_identifier_in_declaration_contexts, - argument.start, - ); - } - } - return Factory.createRestElement(argument, undefined, undefined, spreadElement.start, argument.end); - } - function ObjectPropertyToObjectPatternProperty( - objectPropertyNode: ObjectProperty, - isBinding = false, - ): ObjectPatternProperty | AssignmentPattern { - // object property's value can not has parentheses. - if (objectPropertyNode.value && objectPropertyNode.value.parentheses && isBinding) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_invalid_parenthesized_pattern, objectPropertyNode.start); - } - if (context.propertiesProtoDuplicateSet.has(objectPropertyNode.key)) { - context.propertiesProtoDuplicateSet.delete(objectPropertyNode.key); - } - // When a property name is a CoverInitializedName, we need to cover to assignment pattern - if (context.propertiesInitSet.has(objectPropertyNode) && !objectPropertyNode.shorted) { - context.propertiesInitSet.delete(objectPropertyNode); - if (objectPropertyNode.computed || !isIdentifer(objectPropertyNode.key)) { - // property name of assignment pattern can not use computed propertyname or literal - throw createMessageError( - ErrorMessageMap.assignment_pattern_left_value_can_only_be_idenifier_or_pattern, - ); - } - declarateSymbolInBindingPatternAsParam( - objectPropertyNode.key.name, - isBinding, - objectPropertyNode.start, - ); - return Factory.createAssignmentPattern( - objectPropertyNode.key, - objectPropertyNode.value as Expression, - undefined, - undefined, - objectPropertyNode.start, - objectPropertyNode.end, - ); - } - const patternValue = !objectPropertyNode.value - ? objectPropertyNode.value - : exprToPatternImpl(objectPropertyNode.value, isBinding); - // for binding pattern, member expression is not allow - // - for assignment pattern: value production rule is `DestructuringAssignmentTarget`, which just a LeftHandSideExpression - // - for binding pattern: value production rule is `BindingElement`, which only can be object-pattern, array-pattern, id. - if (isBinding && patternValue && isMemberExpression(patternValue)) { - raiseError(ErrorMessageMap.babel_error_binding_member_expression, patternValue.start); - } - return Factory.createObjectPatternProperty( - objectPropertyNode.key, - patternValue, - objectPropertyNode.computed, - objectPropertyNode.shorted, - objectPropertyNode.start, - objectPropertyNode.end, - ); - } - function declarateSymbolInBindingPatternAsParam( - name: string, - isBinding: boolean, - position: SourcePosition, - ) { - if (isBinding) { - declarateParam(name, position); - } - } - /** - * Parse For-related Statement, include ForStatement, ForInStatement, ForOfStatement. - * - * This function is pretty complex and hard to understand, some function's flag is only - * used in this function. ex: allowIn flag of parseExpression, parseAssignmentExpression. - * @returns {ForStatement | ForInStatement | ForOfStatement} - */ - function parseForStatement(): ForStatement | ForInStatement | ForOfStatement { - // symbolScopeRecorder.enterPreBlockScope(); - symbolScopeRecorder.enterBlockSymbolScope(); - const { start: keywordStart } = expect(SyntaxKinds.ForKeyword); - // First, parse await modifier and lefthandside or init of for-related statement, - // init might start with let, const, var keyword, but if is let keyword need to - // lookahead to determinate is identifier. - // delcaration in there should not eat semi, becuase semi is seperator of ForStatement, - // and might not need init for pattern, because maybe used by ForIn or ForOf. - // If not start with let, var or const keyword, it should be expression, but this - // expression can not take `in` operator as operator in toplevel, so we need pass - // false to disallow parseExpression to take in as operator - let isAwait: SourcePosition | null = null, - isParseLetAsExpr: SourcePosition | null = null, - leftOrInit: VariableDeclaration | Expression | null = null; - if (match(SyntaxKinds.AwaitKeyword)) { - isAwait = getStartPosition(); - nextToken(); - if (!config.allowAwaitOutsideFunction && !isCurrentScopeParseAwaitAsExpression()) { - raiseError(ErrorMessageMap.babel_error_invalid_await, isAwait); - } - } - expect(SyntaxKinds.ParenthesesLeftPunctuator); - if (match([SyntaxKinds.LetKeyword, SyntaxKinds.ConstKeyword, SyntaxKinds.VarKeyword])) { - if (match(SyntaxKinds.LetKeyword) && isLetPossibleIdentifier()) { - isParseLetAsExpr = getStartPosition(); - leftOrInit = parseExpressionDisallowIn(); - } else { - leftOrInit = disAllowInOperaotr(() => parseVariableDeclaration(true)); - } - } else if (match(SyntaxKinds.SemiPunctuator)) { - // for test case `for(;;)` - leftOrInit = null; - } else { - leftOrInit = parseExpressionDisallowIn(); - } - // Second is branching part, determinate the branch by following token - // - if start with semi it should be ForStatement, - // - if is in operator, it should be ForInStatement, - // - if is of contextual keyword, it should be ForOfStatement. - // then according to branch case, parse the following part and do the - // sematic check. - // - ForStatement: if init is variable declaration pattern, it need init. - // - ForInStatement: if left is variable decalration, must not have init, and delcaration length must be 1. - // then if left is expression, must transform it to pattern. - // - ForOfStatement: same as ForInStatement. - // There is one case that even we disallow in operator in top level, there maybe - // wrong init of expression like `for(a = 0 in []);` or `for(a=0 of [])` which would - // make leftOrInit parse all token in () as a expression, so we need to check if those - // case happend. - if (match(SyntaxKinds.SemiPunctuator)) { - if (isAwait) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_for_await_not_of_loop, isAwait); - } - if (leftOrInit && isVarDeclaration(leftOrInit)) { - for (const delcar of leftOrInit.declarations) { - if ((isArrayPattern(delcar.id) || isObjectPattern(delcar.id)) && !delcar.init) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_destructing_pattern_must_need_initializer, delcar.start); - } - } - } - nextToken(); - let test: Expression | null = null, - update: Expression | null = null; - if (!match(SyntaxKinds.SemiPunctuator)) { - test = parseExpressionAllowIn(); - } - expect(SyntaxKinds.SemiPunctuator); - if (!match(SyntaxKinds.ParenthesesRightPunctuator)) { - update = parseExpressionAllowIn(); - } - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseForStatementBody(); - const forStatement = Factory.createForStatement( - body, - leftOrInit, - test, - update, - keywordStart, - cloneSourcePosition(body.end), - ); - staticSematicEarlyErrorForFORStatement(forStatement); - return forStatement; - } - // unreach case, even if syntax error, when leftOrInit, it must match semi token. - // and because it match semi token, if would enter forStatement case, will not - // reach there. even syntax error, error would be throw at parseExpression or - // parseDeclaration. - if (!leftOrInit) { - throw createUnreachError(); - } - // for case `for(a = 0 of [])`; leftOrInit would parse all token before `of` as one expression - // in this case , leftOrInit would be a assignment expression, and when it pass to toAssignment - // function, it would transform to assignment pattern, so we need to checko if there is Assignment - // pattern, it is , means original is assignment expression, it should throw a error. - if (!isVarDeclaration(leftOrInit)) { - leftOrInit = exprToPattern(leftOrInit, false) as Expression; - if (isAssignmentPattern(leftOrInit)) { - throw createMessageError(ErrorMessageMap.invalid_left_value); - } - } - // branch case for `for-in` statement - if (match(SyntaxKinds.InKeyword)) { - if (isAwait) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_for_await_not_of_loop, isAwait); - } - if (isVarDeclaration(leftOrInit)) { - helperCheckDeclarationmaybeForInOrForOfStatement(leftOrInit, "ForIn"); - } - nextToken(); - const right = parseExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseForStatementBody(); - const forInStatement = Factory.createForInStatement( - leftOrInit, - right, - body, - keywordStart, - cloneSourcePosition(body.end), - ); - staticSematicEarlyErrorForFORStatement(forInStatement); - return forInStatement; - } - // branch case for `for-of` statement - if (isContextKeyword("of")) { - if (isVarDeclaration(leftOrInit)) { - helperCheckDeclarationmaybeForInOrForOfStatement(leftOrInit, "ForOf"); - } - if (isParseLetAsExpr) { - raiseError(ErrorMessageMap.extra_error_for_of_can_not_use_let_as_identifier, isParseLetAsExpr); - } - nextToken(); - const right = parseAssignmentExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseForStatementBody(); - const forOfStatement = Factory.createForOfStatement( - !!isAwait, - leftOrInit, - right, - body, - keywordStart, - cloneSourcePosition(body.end), - ); - staticSematicEarlyErrorForFORStatement(forOfStatement); - return forOfStatement; - } - throw createUnexpectError(); - } - function parseForStatementBody(): Statement { - const stmt = parseAsLoop(parseStatement); - symbolScopeRecorder.exitSymbolScope(); - return stmt; - } - function staticSematicEarlyErrorForFORStatement(statement: ForStatement | ForInStatement | ForOfStatement) { - if (checkIsLabelledFunction(statement.body)) { - raiseError( - isInStrictMode() - ? ErrorMessageMap.syntax_error_functions_declare_strict_mode - : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, - statement.body.start, - ); - } - } - /** - * Helper function for check sematic error of VariableDeclaration of ForInStatement and ForOfStatement, - * please reference to comment in parseForStatement. - * @param {VariableDeclaration} declaration - */ - function helperCheckDeclarationmaybeForInOrForOfStatement( - declaration: VariableDeclaration, - kind: "ForIn" | "ForOf", - ) { - if (declaration.declarations.length > 1) { - // recoverable error - raiseError( - ErrorMessageMap.v8_error_Invalid_left_hand_side_in_for_in_loop_must_have_a_single_binding, - declaration.start, - ); - } - const delcarationVariant = declaration.variant; - const onlyDeclaration = declaration.declarations[0]; - if (kind === "ForIn") { - if (onlyDeclaration.init !== null) { - if (delcarationVariant === "var" && !isInStrictMode() && isIdentifer(onlyDeclaration.id)) { - return; - } - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_for_in_loop_head_declarations_may_not_have_initializer, - onlyDeclaration.start, - ); - } - } else { - if (onlyDeclaration.init !== null) { - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_for_of_loop_variable_declaration_may_not_have_an_initializer, - onlyDeclaration.init.start, - ); - } - } - } - function parseIfStatement(): IfStatement { - const { start: keywordStart } = expect(SyntaxKinds.IfKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const test = parseExpressionAllowIn(); - const { end: headerEnd } = expect(SyntaxKinds.ParenthesesRightPunctuator); - context.lastTokenIndexOfIfStmt = headerEnd.index; - const consequnce = parseStatement(); - if (match(SyntaxKinds.ElseKeyword)) { - nextToken(); - const alter = parseStatement(); - return Factory.createIfStatement(test, consequnce, alter, keywordStart, cloneSourcePosition(alter.end)); - } - const ifStatement = Factory.createIfStatement( - test, - consequnce, - null, - keywordStart, - cloneSourcePosition(consequnce.end), - ); - staticSematicEarlyErrorForIfStatement(ifStatement); - return ifStatement; - } - function staticSematicEarlyErrorForIfStatement(statement: IfStatement) { - if (checkIsLabelledFunction(statement.conseqence)) { - raiseError( - isInStrictMode() - ? ErrorMessageMap.syntax_error_functions_declare_strict_mode - : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, - statement.conseqence.start, - ); - } - if (statement.alternative && checkIsLabelledFunction(statement.alternative)) { - raiseError( - isInStrictMode() - ? ErrorMessageMap.syntax_error_functions_declare_strict_mode - : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, - statement.alternative.start, - ); - } - } - function parseWhileStatement(): WhileStatement { - const { start: keywordStart } = expect(SyntaxKinds.WhileKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const test = parseExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseAsLoop(parseStatement); - const whileStatement = Factory.createWhileStatement( - test, - body, - keywordStart, - cloneSourcePosition(body.end), - ); - staticSematicEarlyErrorForWhileStatement(whileStatement); - return whileStatement; - } - function checkIsLabelledFunction(statement: Statement) { - while (statement.kind === SyntaxKinds.LabeledStatement) { - if (statement.body.kind === SyntaxKinds.FunctionDeclaration) { - return true; - } - statement = statement.body; - } - } - function staticSematicEarlyErrorForWhileStatement(statement: WhileStatement) { - if (checkIsLabelledFunction(statement.body)) { - // recoverable error - raiseError( - isInStrictMode() - ? ErrorMessageMap.syntax_error_functions_declare_strict_mode - : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, - statement.body.start, - ); - } - } - function parseDoWhileStatement(): DoWhileStatement { - const { start: keywordStart } = expect(SyntaxKinds.DoKeyword); - const body = parseAsLoop(parseStatement); - expect(SyntaxKinds.WhileKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const test = parseExpressionAllowIn(); - const { end: punctEnd } = expect(SyntaxKinds.ParenthesesRightPunctuator); - isSoftInsertSemi(); - const doWhileStatement = Factory.createDoWhileStatement(test, body, keywordStart, punctEnd); - staticSematicEarlyErrorForDoWhileStatement(doWhileStatement); - return doWhileStatement; - } - function staticSematicEarlyErrorForDoWhileStatement(statement: DoWhileStatement) { - if (checkIsLabelledFunction(statement.body)) { - // recoverable error - raiseError( - isInStrictMode() - ? ErrorMessageMap.syntax_error_functions_declare_strict_mode - : ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, - statement.body.start, - ); - } - } - function parseBlockStatement() { - const { start: puncStart } = expect(SyntaxKinds.BracesLeftPunctuator); - enterBlockScope(); - const body: Array = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - body.push(parseStatementListItem()); - } - exitBlockScope(); - const { end: puncEnd } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createBlockStatement(body, puncStart, puncEnd); - } - function parseSwitchStatement() { - const { start: keywordStart } = expect(SyntaxKinds.SwitchKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const discriminant = parseExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - const { nodes, end } = parseAsSwitch(parseSwitchCases); - return Factory.createSwitchStatement(discriminant, nodes, keywordStart, end); - } - function parseSwitchCases(): ASTArrayWithMetaData { - enterBlockScope(); - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const cases: Array = []; - let haveDefault = false; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - let test: Expression | null = null; - const start = getStartPosition(); - if (match(SyntaxKinds.CaseKeyword)) { - nextToken(); - test = parseExpressionAllowIn(); - } else if (match(SyntaxKinds.DefaultKeyword)) { - const start = getStartPosition(); - nextToken(); - if (haveDefault) { - // recoverable error - raiseError(ErrorMessageMap.v8_error_more_than_one_default_clause_in_switch_statement, start); - } else { - haveDefault = true; - } - } - expect(SyntaxKinds.ColonPunctuator); - const consequence: Array = []; - while ( - !match([ - SyntaxKinds.BracesRightPunctuator, - SyntaxKinds.EOFToken, - SyntaxKinds.CaseKeyword, - SyntaxKinds.DefaultKeyword, - ]) - ) { - consequence.push(parseStatementListItem()); - } - const end = getStartPosition(); - cases.push(Factory.createSwitchCase(test, consequence, start, end)); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - exitBlockScope(); - return { - nodes: cases, - start, - end, - }; - } - function parseContinueStatement(): ContinueStatement { - const { start: keywordStart, end: keywordEnd } = expect(SyntaxKinds.ContinueKeyword); - staticSematicEarlyErrorForContinueStatement(keywordStart); - if (match(SyntaxKinds.Identifier) && !getLineTerminatorFlag()) { - const id = parseIdentifierReference(); - shouldInsertSemi(); - staticSematicEarlyErrorForLabelInContinueStatement(id); - return Factory.createContinueStatement(id, keywordStart, cloneSourcePosition(id.end)); - } - shouldInsertSemi(); - return Factory.createContinueStatement(null, keywordStart, keywordEnd); - } - function staticSematicEarlyErrorForContinueStatement(start: SourcePosition) { - if (!isContinueValidate()) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_continue_must_be_inside_loop, start); - } - } - function staticSematicEarlyErrorForLabelInContinueStatement(label: Identifier) { - if (!canLabelReach(label.name)) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_label_not_found, label.start); - } - } - /** - * Parse Break Statement. - * ``` - * BreakStatement := break; - * := break [no lineTerminator] LabeledIdentifier; - * ``` - * @returns {BreakStatement} - */ - function parseBreakStatement(): BreakStatement { - const { start, end } = expect(SyntaxKinds.BreakKeyword); - if (match(SyntaxKinds.Identifier) && !getLineTerminatorFlag()) { - const label = parseIdentifierReference(); - shouldInsertSemi(); - staticSematicEarlyErrorForLabelInBreakStatement(label); - return Factory.createBreakStatement(label, start, end); - } - shouldInsertSemi(); - const breakStmt = Factory.createBreakStatement(null, start, end); - staticSematicEarlyErrorForBreakStatement(breakStmt); - return breakStmt; - } - /** - * Spec def early error checking for break statement. - * @param {BreakStatement} breakStmt - * reference: https://tc39.es/ecma262/#sec-break-statement-static-semantics-early-errors - */ - function staticSematicEarlyErrorForBreakStatement(breakStmt: BreakStatement) { - if (!isBreakValidate()) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_unlabeled_break_must_be_inside_loop_or_switch, breakStmt.start); - } - } - /** - * Spec def early error checking for break statement with break label - * @param {Identifier} label - * reference: https://tc39.es/ecma262/#sec-break-statement-static-semantics-early-errors - */ - function staticSematicEarlyErrorForLabelInBreakStatement(label: Identifier) { - if (!canLabelReach(label.name)) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_label_not_found, label.start); - } - } - /** - * Parse labeled statement - * ``` - * LabelledStatement := LabelIdentifier: LabelledItem - * LabelledItem := Statement - * := FunctionDeclaration - * ``` - * @returns {LabeledStatement} - */ - function parseLabeledStatement(): LabeledStatement { - // TODO: using dev mode unreach checking - // if (!match(SyntaxKinds.Identifier) || lookahead().kind !== SyntaxKinds.ColonPunctuator) { - // } - const label = parseIdentifierReference(); - if (lexicalScopeRecorder.enterVirtualBlockScope("Label", label.name)) { - // recoverable error - raiseError(ErrorMessageMap.v8_error_label_has_already_been_declared, label.start); - } - expect(SyntaxKinds.ColonPunctuator); - const labeled = match(SyntaxKinds.FunctionKeyword) - ? parseFunctionDeclaration(false, false) - : parseStatement(); - lexicalScopeRecorder.exitVirtualBlockScope(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - staticSematicEarlyErrorForLabelStatement(labeled as any); - return Factory.createLabeledStatement( - label, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - labeled as any, - cloneSourcePosition(label.start), - cloneSourcePosition(labeled.end), - ); - } - /** - * Spec def early error. using alter production rule. - * @param labeled - * reference: https://tc39.es/ecma262/#sec-labelled-statements-static-semantics-early-errors - */ - function staticSematicEarlyErrorForLabelStatement(labeled: Statement | FunctionDeclaration) { - if (isFunctionDeclaration(labeled)) { - if (labeled.generator) { - raiseError(ErrorMessageMap.syntax_error_generator_function_declare, labeled.start); - } - if (isInStrictMode()) { - raiseError(ErrorMessageMap.syntax_error_functions_declare_strict_mode, labeled.start); - } - } - } - function parseReturnStatement(): ReturnStatement { - const { start, end } = expect(SyntaxKinds.ReturnKeyword); - if (!isReturnValidate()) { - raiseError(ErrorMessageMap.syntax_error_return_not_in_function, start); - } - if (isSoftInsertSemi(true)) { - return Factory.createReturnStatement(null, start, end); - } - const expr = parseExpressionAllowIn(); - shouldInsertSemi(); - return Factory.createReturnStatement(expr, start, cloneSourcePosition(expr.end)); - } - function parseTryStatement(): TryStatement { - const { start: tryKeywordStart } = expect(SyntaxKinds.TryKeyword); - const body = parseBlockStatement(); - let handler: CatchClause | null = null, - finalizer: BlockStatement | null = null; - if (match(SyntaxKinds.CatchKeyword)) { - const catchKeywordStart = getStartPosition(); - nextToken(); - //symbolScopeRecorder.enterFunctionSymbolScope(); - enterCatchBlockScope(); - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - nextToken(); - symbolScopeRecorder.enterCatchParam(); - // catch clause should not have init - const param = parseBindingElement(false); - parseFunctionParamType(param as TSParameter, false); - if (!symbolScopeRecorder.setCatchParamTo(isIdentifer(param) ? SymbolType.Var : SymbolType.Let)) { - throw createMessageError(ErrorMessageMap.v8_error_duplicate_identifier); - } - // should check param is duplicate or not. - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseCatchBlock(); - handler = Factory.createCatchClause(param, body, catchKeywordStart, cloneSourcePosition(body.end)); - } else { - const body = parseCatchBlock(); - handler = Factory.createCatchClause(null, body, catchKeywordStart, cloneSourcePosition(body.end)); - } - exitCatchBlockScope(); - } - if (match(SyntaxKinds.FinallyKeyword)) { - nextToken(); - finalizer = parseBlockStatement(); - } - if (!handler && !finalizer) { - raiseError(ErrorMessageMap.v8_error_missing_catch_or_finally_after_try, tryKeywordStart); - } - return Factory.createTryStatement( - body, - handler, - finalizer, - tryKeywordStart, - cloneSourcePosition(finalizer ? finalizer.end : handler ? handler.end : body.end), - ); - } - function parseCatchBlock() { - const { start: puncStart } = expect(SyntaxKinds.BracesLeftPunctuator); - const body: Array = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - body.push(parseStatementListItem()); - } - const { end: puncEnd } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createBlockStatement(body, puncStart, puncEnd); - } - function parseThrowStatement() { - const { start } = expect(SyntaxKinds.ThrowKeyword); - staticSmaticEarlyErrorForThrowStatement(); - const expr = parseExpressionAllowIn(); - shouldInsertSemi(); - return Factory.createThrowStatement(expr, start, cloneSourcePosition(expr.end)); - } - function staticSmaticEarlyErrorForThrowStatement() { - if (getLineTerminatorFlag()) { - raiseError(ErrorMessageMap.babel_error_illegal_newline_after_throw, getStartPosition()); - } - } - function parseWithStatement(): WithStatement { - const { start } = expect(SyntaxKinds.WithKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const object = parseExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - const body = parseStatement(); - const withStmt = Factory.createWithStatement(object, body, start, cloneSourcePosition(body.end)); - staticSmaticEarlyErrorForWithStatement(withStmt); - return withStmt; - } - function staticSmaticEarlyErrorForWithStatement(withStatement: WithStatement) { - if (isInStrictMode()) { - // recoverable error. - raiseError(ErrorMessageMap.babel_error_with_statement_in_strict_mode, withStatement.start); - } - } - function parseDebuggerStatement(): DebuggerStatement { - const { start, end } = expect(SyntaxKinds.DebuggerKeyword); - shouldInsertSemi(); - return Factory.createDebuggerStatement(start, end); - } - function parseEmptyStatement(): EmptyStatement { - const { start, end } = expect([SyntaxKinds.SemiPunctuator]); - return Factory.createEmptyStatement(start, end); - } - /** ================================================================= - * Parse Delcarations - * entry point reference: https://tc39.es/ecma262/#prod-Declaration - * ================================================================== - */ - /** - * Parse VariableStatement and LexicalBindingDeclaration. - * - * when in for-in statement, variable declaration do not need semi for - * ending, in binding pattern of for-in statement, variable declaration - * maybe do not need init.(for-in, for-of do not need, but for still need) - * - * Anthoer side, let can be identifier in VariableStatement. and parsrIdentifier - * function would always parse let as identifier if not in strict mode. so we need - * to implement custom function for check is identifier or value of pattern is let - * when in LexicalBindingDeclaration - * ``` - * VariableStatement := 'var' VariableDeclarationList - * LexicalBidningDeclaration := '(let | const)' LexicalBinding - * VariableDeclarationList := BindingIdentidier initalizer - * := BindingPattern initalizer - * LexicalBinding := BindingIdentidier initalizer - * := BindingPattern initalizer - * ``` - * @returns {VariableDeclaration} - */ - function parseVariableDeclaration(inForInit: boolean = false): VariableDeclaration { - const variableKind = match(SyntaxKinds.VarKeyword) ? "var" : "lexical"; - const { start: keywordStart, value: variant } = expect([ - SyntaxKinds.VarKeyword, - SyntaxKinds.ConstKeyword, - SyntaxKinds.LetKeyword, - ]); - let shouldStop = false, - isStart = true; - const declarations: Array = []; - const lastSymbolKind = getSymbolType(); - setSymbolType( - variant === "var" ? SymbolType.Var : variant === "const" ? SymbolType.Const : SymbolType.Let, - ); - if (getExportContext() === ExportContext.InExport) { - setExportContext(ExportContext.InExportBinding); - } - while (!shouldStop) { - if (isStart) { - isStart = false; - } else { - if (!match(SyntaxKinds.CommaToken)) { - shouldStop = true; - continue; - } - nextToken(); - } - // eslint-disable-next-line prefer-const - let [id, scope] = parseWithCatpureLayer(() => parseBindingElement(false)); - const isBindingPattern = !isIdentifer(id); - if (variableKind === "lexical" && scope.kind !== "RHSLayer" && scope.letIdentifier.length > 0) { - throw new Error("TODO ERROR: Better"); - } - id = parseFunctionParamType(id as TSParameter, false); - // custom logical for check is lexical binding have let identifier ? - if ( - // variable declarations binding pattern but but have init. - (isBindingPattern || variant === "const") && - !match(SyntaxKinds.AssginOperator) && - // variable declaration in for statement can existed with `of`, `in` operator - !inForInit - ) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_missing_init_in_const_declaration, id.start); - } - if (match(SyntaxKinds.AssginOperator)) { - nextToken(); - const init = parseAssignmentExpressionInheritIn(); - declarations.push( - Factory.createVariableDeclarator( - id, - init, - cloneSourcePosition(id.start), - cloneSourcePosition(init.end), - ), - ); - continue; - } - declarations.push( - Factory.createVariableDeclarator( - id, - null, - cloneSourcePosition(id.start), - cloneSourcePosition(id.end), - ), - ); - } - setSymbolType(lastSymbolKind); - setExportContext(ExportContext.NotInExport); - if (!inForInit) { - shouldInsertSemi(); - } - return Factory.createVariableDeclaration( - declarations, - variant as VariableDeclaration["variant"], - keywordStart, - declarations[declarations.length - 1].end, - ); - } - function parseFunctionDeclaration( - isAsync: boolean, - isDefault: boolean, - ): FunctionDeclaration | TSDeclareFunction { - enterFunctionScope(isAsync); - const { start } = expect(SyntaxKinds.FunctionKeyword); - let generator = false; - if (match(SyntaxKinds.MultiplyOperator)) { - generator = true; - setCurrentFunctionContextAsGenerator(); - nextToken(); - } - const [[name, typeParameters, params], scope] = parseWithCatpureLayer(() => { - const name = parseFunctionName(isDefault); - if (!name && !isDefault) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_function_statement_requires_a_name, getStartPosition()); - } - const typeParameters = tryParseTSTypeParameterDeclaration(false); - const params = parseFunctionParam(); - return [name, typeParameters, params]; - }); - const returnType = tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); - if (match(SyntaxKinds.BracesLeftPunctuator)) { - const body = parseFunctionBody(); - postStaticSematicEarlyErrorForStrictModeOfFunction(name, scope); - const func = Factory.createFunction( - name, - body, - params, - typeParameters, - returnType, - generator, - isCurrentScopeParseAwaitAsExpression(), - start, - cloneSourcePosition(body.end), - ); - exitFunctionScope(false); - // for function declaration, symbol should declar in parent scope. - if (name) { - delcarateFcuntionSymbol(name.name, func.generator, func.start); - } - return Factory.transFormFunctionToFunctionDeclaration(func); - } else { - const funcDeclar = Factory.createTSDeclarFunction( - name, - returnType, - params, - typeParameters, - generator, - isCurrentScopeParseAwaitAsExpression(), - start, - getLastTokenEndPositon(), - ); - shouldInsertSemi(); - exitFunctionScope(false); - return funcDeclar; - } - } - /** - * Parse function maybe call by parseFunctionDeclaration and parseFunctionExpression, - * first different of those two function is that function-declaration can not have null - * name. - * @returns {FunctionAST} - */ - function parseFunction(isExpression: boolean): FunctionAST { - const { start } = expect(SyntaxKinds.FunctionKeyword); - let generator = false; - if (match(SyntaxKinds.MultiplyOperator)) { - generator = true; - setCurrentFunctionContextAsGenerator(); - nextToken(); - } - const [[name, typeParameters, params], scope] = parseWithCatpureLayer(() => { - const name = parseFunctionName(isExpression); - if (!name && !isExpression) { - // recoverable error - raiseError(ErrorMessageMap.syntax_error_function_statement_requires_a_name, getStartPosition()); - } - const typeParameters = tryParseTSTypeParameterDeclaration(false); - const params = parseFunctionParam(); - return [name, typeParameters, params]; - }); - const returnType = tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); - const body = parseFunctionBody(); - postStaticSematicEarlyErrorForStrictModeOfFunction(name, scope); - return Factory.createFunction( - name, - body, - params, - typeParameters, - returnType, - generator, - isCurrentScopeParseAwaitAsExpression(), - start, - cloneSourcePosition(body.end), - ); - } - /** - * Because we "use strict" directive is in function body, we will not sure - * if this function contain stric directive or not until we enter the function body. - * as the result, we need to check function name and function paramemter's name after - * parseFunctionBody. - * @param name - * @param params - */ - function postStaticSematicEarlyErrorForStrictModeOfFunction( - name: Identifier | null, - scope: StrictModeScope, - ) { - if (isInStrictMode()) { - checkStrictModeScopeError(scope); - if (name) { - if ( - name.name === "arugments" || - name.name === "eval" || - name.name === "yield" || - name.name === "let" || - PreserveWordSet.has(name.name) - ) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, name.start); - } - } - } - } - /** - * When parse name of function, can not just call parseIdentifier, because function name - * maybe await or yield, and function name's context rule is different from identifier in - * scope (function body). so there we need to implement special logical for parse function - * name. and you need to note that name of function expression and name of function delcaration - * have different context rule for parse function name. - * @param {boolean} optionalName - * @returns {Identifier | null} - */ - function parseFunctionName(optionalName: boolean): Identifier | null { - return parseWithLHSLayer(() => { - let name: Identifier | null = null; - // there we do not just using parseIdentifier function as the reason above - // let can be function name as other place - if (match([SyntaxKinds.Identifier, SyntaxKinds.LetKeyword])) { - name = parseIdentifierReference(); - } else { - if (match(SyntaxKinds.AwaitKeyword)) { - // for function expression, can await treat as function name is dep on current scope. - if (optionalName && isCurrentScopeParseAwaitAsExpression()) { - raiseError( - ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, - getStartPosition(), - ); - } - // for function declaration, can await treat as function name is dep on parent scope. - if (!optionalName && isParentFunctionAsync()) { - raiseError( - ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, - getStartPosition(), - ); - } - if (config.sourceType === "module") { - raiseError( - ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, - getStartPosition(), - ); - } - name = parseIdentifierName(); - } else if (match(SyntaxKinds.YieldKeyword)) { - // for function expression, can yield treat as function name is dep on current scope. - if (optionalName && isCurrentScopeParseYieldAsExpression()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, getStartPosition()); - } - // for function declaration, can yield treat as function name is dep on parent scope. - if (!optionalName && isParentFunctionGenerator()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, getStartPosition()); - } - // if in strict mode, yield can not be function name. - if (isInStrictMode()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, getStartPosition()); - } - name = parseIdentifierName(); - } - } - return name; - }); - } - /** - * Parse Function Body - * ``` - * FunctionBody := '{' StatementList '}' - * StatementList := StatementList StatementListItem - * := StatementListItem - * ``` - * @return {FunctionBody} - */ - function parseFunctionBody(): FunctionBody { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const body: Array = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - body.push(parseStatementListItem()); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createFunctionBody(body, start, end); - } - /** - * Parse Function Params, parameter list is a spcial place for await and yield, - * function parameter list is in current function scope, so await and yield would - * parse as expression, but parameter list can not call await and yield expression. - * - * Anthoer thing is that trailing comma of restElement is error, multi restElement is - * error for function param list. - * ``` - * FunctionParams := '(' FunctionParamsList ')' - * := '(' FunctionParamsList ',' ')' - * := '(' FunctionPramsList ',' RestElement ')' - * := '(' RestElement ')' - * FunctiinParamList := FunctionParamList ',' FunctionParam - * := FunctionParam - * FunctionParam := BindingElement - * ``` - */ - function parseFunctionParam(): Array { - expect(SyntaxKinds.ParenthesesLeftPunctuator); - enterFunctionParameter(); - let isStart = true; - let isEndWithRest = false; - const params: Array = []; - while (!match(SyntaxKinds.ParenthesesRightPunctuator)) { - if (isStart) { - if (match(SyntaxKinds.CommaToken)) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_unexpect_trailing_comma, getStartPosition()); - nextToken(); - } - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - if (match(SyntaxKinds.ParenthesesRightPunctuator)) { - continue; - } - // parse SpreadElement (identifer, Object, Array) - let param: Pattern; - if (match(SyntaxKinds.SpreadOperator)) { - isEndWithRest = true; - param = parseRestElement(true); - param = parseFunctionParamType(param as TSParameter, true); - params.push(param); - break; - } else { - param = parseBindingElement(); - param = parseFunctionParamType(param as TSParameter, true); - params.push(param); - } - } - if (!match(SyntaxKinds.ParenthesesRightPunctuator)) { - if (isEndWithRest && match(SyntaxKinds.CommaToken)) { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, - getStartPosition(), - ); - nextToken(); - } - throw createUnexpectError(); - } - nextToken(); - setContextIfParamsIsSimpleParameterList(params); - existFunctionParameter(); - return params; - } - /** - * Helper function for check if parameter list is simple - * parameter list or not, if is simple parameter, set - * the context. - * @param {Array} params - * @returns - */ - function setContextIfParamsIsSimpleParameterList(params: Array) { - for (const param of params) { - if (!isIdentifer(param)) { - setCurrentFunctionParameterListAsNonSimple(); - return; - } - } - } - function parseDecoratorListToCache() { - const decoratorList = parseDecoratorList(); - context.cache.decorators = decoratorList; - } - function parseDecoratorList(): [Decorator] { - const decoratorList: [Decorator] = [parseDecorator()]; - while (match(SyntaxKinds.AtPunctuator)) { - decoratorList.push(parseDecorator()); - } - if ( - match(SyntaxKinds.ClassKeyword) || - (match(SyntaxKinds.ExportKeyword) && config.sourceType === "module") || - isInClassScope() - ) { - return decoratorList; - } - raiseError( - ErrorMessageMap.babel_error_leading_decorators_must_be_attached_to_a_class_declaration, - decoratorList[0].start, - ); - return decoratorList; - } - function parseDecorator(): Decorator { - const { start } = expect(SyntaxKinds.AtPunctuator); - switch (getToken()) { - case SyntaxKinds.ParenthesesLeftPunctuator: { - nextToken(); - const expr = parseExpressionAllowIn(); - expect(SyntaxKinds.ParenthesesRightPunctuator); - return Factory.createDecorator(expr, start, expr.end); - } - default: { - let expr: Expression = parseIdentifierName(); - while (match(SyntaxKinds.DotOperator)) { - nextToken(); - const property = match(SyntaxKinds.PrivateName) ? parsePrivateName() : parseIdentifierName(); - expr = Factory.createMemberExpression( - false, - property, - expr, - false, - cloneSourcePosition(expr.start), - cloneSourcePosition(property.end), - ); - } - if ( - match(SyntaxKinds.LtOperator) || - match(SyntaxKinds.BitwiseLeftShiftOperator) || - match(SyntaxKinds.ParenthesesLeftPunctuator) - ) { - const typeArguments = tryParseTSTypeParameterInstantiation(false); - const { nodes, end } = parseArguments(); - const callExpr = Factory.createCallExpression( - expr, - nodes, - typeArguments, - false, - cloneSourcePosition(expr.start), - end, - ); - return Factory.createDecorator(callExpr, start, cloneSourcePosition(callExpr.end)); - } - return Factory.createDecorator(expr, start, expr.end); - } - } - } - - /** - * - */ - function parseClassDeclaration(decoratorList: Decorator[] | null): ClassDeclaration { - expectButNotEat(SyntaxKinds.ClassKeyword); - const classDelcar = parseClass(decoratorList); - if (classDelcar.id === null) { - raiseError(ErrorMessageMap.babel_error_a_class_name_is_required, classDelcar.start); - } - return Factory.transFormClassToClassDeclaration(classDelcar); - } - /** - * Parse Class - * ``` - * Class := 'class' identifer ('extends' LeftHandSideExpression) ClassBody - * ``` - * @returns {Class} - */ - function parseClass(decoratorList: Decorator[] | null): Class { - const { start } = expect(SyntaxKinds.ClassKeyword); - let name: Identifier | null = null; - if (match(BindingIdentifierSyntaxKindArray)) { - name = parseIdentifierReference(); - declarateLetSymbol(name.name, name.start); - } - let superClass: Expression | null = null; - if (match(SyntaxKinds.ExtendsKeyword)) { - enterClassScope(true); - nextToken(); - superClass = parseLeftHandSideExpression(); - } else { - enterClassScope(false); - } - const body = parseClassBody(); - existClassScope(); - return Factory.createClass(name, superClass, body, decoratorList, start, cloneSourcePosition(body.end)); - } - /** - * Parse ClassBody - * ``` - * ClassBody := '{' [ClassElement] '}' - * ``` - * @return {ClassBody} - */ - function parseClassBody(): ClassBody { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const classbody: ClassBody["body"] = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (match(SyntaxKinds.SemiPunctuator)) { - nextToken(); - continue; - } - classbody.push(parseClassElement()); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createClassBody(classbody, cloneSourcePosition(start), cloneSourcePosition(end)); - } - /** - * Parse ClassElement - * ``` - * ClassElement := MethodDefinition - * := 'static' MethodDefinition - * := FieldDefintion ; - * := 'static' FieldDefintion ; - * := ClassStaticBlock - * := ; (this production rule handle by caller) - * FieldDefintion := ClassElementName ('=' AssignmentExpression)? - * ``` - * - frist, parse 'static' keyword if possible, next follow cases - * 1. start with some method modifier like 'set', 'get', 'async', '*' must be methodDefintion - * 2. start with '{', must be static block - * - then parse ClassElement - * 1. if next token is '(', must be MethodDefintion, - * 2. else this only case is FieldDefinition with init or not. - * @returns {ClassElement} - */ - function parseClassElement(): ClassElement { - let decorators: [Decorator] | null = null; - if (match(SyntaxKinds.AtPunctuator)) { - decorators = parseDecoratorList(); - } - // parse static modifier - const isStatic = checkIsMethodStartWithStaticModifier(); - if (checkIsMethodStartWithModifier()) { - return parseMethodDefintion(true, undefined, isStatic, decorators) as ClassMethodDefinition; - } - if (match(SyntaxKinds.BracesLeftPunctuator) && isStatic) { - if (decorators) { - raiseError( - ErrorMessageMap.babel_error_decorators_can_not_be_used_with_a_static_block, - decorators[0].start, - ); - } - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - symbolScopeRecorder.enterFunctionSymbolScope(); - const body: Array = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - body.push(parseStatementListItem()); - } - symbolScopeRecorder.exitSymbolScope(); - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createClassStaticBlock(body, start, end); - } - let accessor = false; - if (isContextKeyword("accessor")) { - const { kind, lineTerminatorFlag } = lookahead(); - if (kind === SyntaxKinds.Identifier && !lineTerminatorFlag) { - nextToken(); - accessor = true; - } - } - // parse ClassElementName - const isComputedRef = { isComputed: false }; - let key: PropertyName | PrivateName | undefined; - if (match(SyntaxKinds.PrivateName)) { - key = parsePrivateName(); - defPrivateName(key.name, key.start); - } else { - key = parsePropertyName(isComputedRef); - } - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - return parseMethodDefintion( - true, - [key, isComputedRef.isComputed], - isStatic, - decorators, - ) as ClassMethodDefinition; - } - staticSematicForClassPropertyName(key, isComputedRef.isComputed, isStatic); - let propertyValue = undefined, - shorted = true; - if (match([SyntaxKinds.AssginOperator])) { - nextToken(); - shorted = false; - const [value, scope] = parseWithCatpureLayer(parseAssignmentExpressionAllowIn); - propertyValue = value; - staticSematicForClassPropertyValue(scope); - } - shouldInsertSemi(); - if (accessor) { - return Factory.createClassAccessorProperty( - key, - propertyValue, - isComputedRef.isComputed, - isStatic, - shorted, - decorators, - cloneSourcePosition(key.start), - cloneSourcePosition(key.end), - ); - } - return Factory.createClassProperty( - key, - propertyValue, - isComputedRef.isComputed, - isStatic, - shorted, - decorators, - cloneSourcePosition(key.start), - cloneSourcePosition(key.end), - ); - } - function checkIsMethodStartWithStaticModifier() { - const { kind } = lookahead(); - if (isContextKeyword("static")) { - switch (kind) { - // static - // static get/set/async - // static { - // static [] - // static * - case SyntaxKinds.Identifier: - case SyntaxKinds.PrivateName: - case SyntaxKinds.StringLiteral: - case SyntaxKinds.BracesLeftPunctuator: - case SyntaxKinds.BracketLeftPunctuator: - case SyntaxKinds.MultiplyOperator: - nextToken(); - return true; - default: - // static for/if ...etc - if (Keywords.find((kw) => kw === kind)) return true; - return false; - } - } - return false; - } - /** - * For a class scope, it must be strict mode, so argument identifier can - * @param {StrictModeScope} scope - * @returns - */ - function staticSematicForClassPropertyValue(scope: StrictModeScope) { - if (scope.kind !== "CatpureLayer") { - return; - } - for (const pos of scope.argumentsIdentifier) { - raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, pos); - } - } - /** - * Check sematic for class property name. - * - `constructor` can not used as property name - * - `prototype` can not be a static property - * @param {PropertyName | PrivateName} propertyName - * @param isComputed - * @param {boolean} isStatic - * @returns - */ - function staticSematicForClassPropertyName( - propertyName: PropertyName | PrivateName, - isComputed: boolean, - isStatic: boolean, - ) { - if (isComputed) return; - let value; - if (isPrivateName(propertyName) || isIdentifer(propertyName)) { - value = propertyName.name; - } else if (isStringLiteral(propertyName)) { - value = propertyName.value; - } - if (value) { - if (value === "constructor") { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_classe_may_not_have_a_field_named_constructor, - propertyName.start, - ); - } - if (value === "prototype" && isStatic) { - raiseError( - ErrorMessageMap.v8_error_class_may_not_have_static_property_named_prototype, - propertyName.start, - ); - } - } - } - /** ==================================================================== - * Parse Expression - * entry point reference : https://tc39.es/ecma262/#sec-comma-operator - * ===================================================================== - */ - - /** - * Entry function for parse a expression statement. - * @returns {ExpressionStatement} - */ - function parseExpressionStatement(): ExpressionStatement { - preStaticSematicEarlyErrorForExpressionStatement(); - const lastTokenIndex = lexer.getLastTokenEndPositon().index; - const expr = parseExpressionAllowIn(); - checkStrictMode(expr); - postStaticSematicEarlyErrorForExpressionStatement(expr, lastTokenIndex); - shouldInsertSemi(); - return Factory.createExpressionStatement( - expr, - cloneSourcePosition(expr.start), - cloneSourcePosition(expr.end), - ); - } - /** - * Implement part of NOTE section in 14.5 - */ - function preStaticSematicEarlyErrorForExpressionStatement() { - if (match(SyntaxKinds.LetKeyword)) { - const { kind, lineTerminatorFlag } = lookahead(); - if ( - kind === SyntaxKinds.BracketLeftPunctuator || - (!lineTerminatorFlag && - (kind === SyntaxKinds.BracesLeftPunctuator || kind === SyntaxKinds.Identifier)) - ) { - raiseError( - ErrorMessageMap.v8_error_lexical_declaration_cannot_appear_in_a_single_statement_context, - getStartPosition(), - ); - } - } - } - /** - * Implement part of NOTE section in 14.5 - */ - function postStaticSematicEarlyErrorForExpressionStatement(expr: Expression, lastTokenIndex: number) { - if (!expr.parentheses) { - if (isClassExpression(expr)) { - raiseError(ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, expr.start); - return; - } - if (isFunctionExpression(expr)) { - if (expr.async) { - raiseError(ErrorMessageMap.syntax_error_async_function_declare, expr.start); - } - if (expr.generator) { - raiseError(ErrorMessageMap.syntax_error_generator_function_declare, expr.start); - } - if (isInStrictMode()) { - raiseError(ErrorMessageMap.syntax_error_functions_declare_strict_mode, expr.start); - } else { - if (lastTokenIndex !== context.lastTokenIndexOfIfStmt) { - raiseError(ErrorMessageMap.syntax_error_functions_declare_non_strict_mode, expr.start); - } - } - } - } - } - /** - * Helper function for checking `use strict` directive, according to - * ECMA spec, `use strict` directive is a `ExpressionStatement` in - * which value is `use strict`, and this function is doing the same - * thing as spec required. - * - * NOTE: this function would perform side effect to parse context - * - * ref: https://tc39.es/ecma262/#use-strict-directive - * @param {Expression} expr - */ - function checkStrictMode(expr: Expression) { - if (isStringLiteral(expr)) { - if (expr.value === "use strict" && !expr.parentheses) { - if (isDirectToFunctionContext()) { - if (!isCurrentFunctionParameterListSimple()) { - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_use_strict_not_allowed_in_function_with_non_simple_parameters, - expr.start, - ); - } - setCurrentFunctionContextAsStrictMode(); - } - } - } - } - /** - * Private Parse API, parse Expression. - * ``` - * Expression: - * AssignmentExpression - * Expression, AssignmentExpression - * ``` - * Since Expression have in operator syntax action, so there we split parseExpression - * into three kind of function. - * - `parseExpressionAllowIn`: equal to production rule with parameter `Expression[+In]` - * - `parseExpressionDisallowIn`: equal to production rule with parameter `Expression[~In]` - * - `parseExpressionInheritIn`: equal to production rule with parameter `Expression[?in]` - * @returns {Expression} - */ - function parseExpressionAllowIn(): Expression { - return allowInOperaotr(parseExpressionInheritIn); - } - /** - * Private Parse API, parse Expression. - * - allow disallow in operator syntax transition action. - * - for more detail, please refer to `parseExpressionAllowIn`. - * @returns {Expression} - */ - function parseExpressionDisallowIn(): Expression { - return disAllowInOperaotr(parseExpressionInheritIn); - } - /** - * Private Parse API, parse Expression. - * - inherit in operator syntax transition action. - * - for more detail, please refer to `parseExpressionAllowIn`. - * @returns {Expression} - */ - function parseExpressionInheritIn(): Expression { - const exprs = [parseAssignmentExpressionInheritIn()]; - while (match(SyntaxKinds.CommaToken)) { - nextToken(); - exprs.push(parseAssignmentExpressionInheritIn()); - } - if (exprs.length === 1) { - return exprs[0]; - } - return Factory.createSequenceExpression( - exprs, - cloneSourcePosition(exprs[0].start), - cloneSourcePosition(exprs[exprs.length - 1].end), - ); - } - /** - * Private Parse API, parse AssignmentExpression - * - * Since AssignmentExpression usually is a entry point of in operator syntax action. just like - * parseExpression, there we split function into three kind: - * - `parseAssignmentExpressionAllowIn`: equals to production rule with parameter - * - `parseAssignmentExpressionInhertIn`: equals to production rule with parameter - * - It since that disallow is not show in current spec, so ignore it. - * @returns {Expression} - */ - function parseAssignmentExpressionAllowIn(): Expression { - return allowInOperaotr(parseAssignmentExpressionInheritIn); - } - /** - * Private Parse API, parse AssignmentExpression - * - inherit in operator syntax transition action. - * - for more detail, please refer to `parseAssignmentExpressionAllowIn`. - * @returns {Expression} - */ - function parseAssignmentExpressionInheritIn(): Expression { - if ( - match([ - SyntaxKinds.ParenthesesLeftPunctuator, - SyntaxKinds.Identifier, - SyntaxKinds.LetKeyword, - SyntaxKinds.YieldKeyword, - SyntaxKinds.AwaitKeyword, - ]) - ) { - context.maybeArrowStart = getStartPosition().index; - } - if (match(SyntaxKinds.YieldKeyword) && isCurrentScopeParseYieldAsExpression()) { - return parseYieldExpression(); - } - if ( - !requirePlugin(ParserPlugin.JSX) && - requirePlugin(ParserPlugin.TypeScript) && - (match(SyntaxKinds.LtOperator) || match(SyntaxKinds.BitwiseLeftShiftOperator)) - ) { - const expr = parseTSGenericArrowFunctionExpression(); - if (expr) return expr; - } - const [leftExpr, scope] = parseWithCatpureLayer(parseConditionalExpression); - if (!match(AssigmentOperators)) { - return leftExpr; - } - const left = exprToPattern(leftExpr, false); - checkStrictModeScopeError(scope); - const operator = getToken(); - if (operator !== SyntaxKinds.AssginOperator) { - checkExpressionAsLeftValue(left); - } - nextToken(); - const right = parseAssignmentExpressionInheritIn(); - return Factory.createAssignmentExpression( - left as Pattern, - right, - operator as AssigmentOperatorKinds, - cloneSourcePosition(left.start), - cloneSourcePosition(right.end), - ); - } - /** - * Helper function for any parse API that need to parse Arrow function. - * From production rule in `AssignmentExpression`, `ArrowFunctionExpression` and - * `AsyncArrowFunctionExpression` is same level of `ConditionalExpression`, but we - * parse ArrowFunction in the bottom of parse recursion(parsePrimary), so we should - * mark context there to make parseArrowFunction only parse when context is mark. - * @returns {boolean} - */ - function canParseAsArrowFunction(): boolean { - return getStartPosition().index === context.maybeArrowStart; - } - /** - * Parse Yield Expression, when current function scope is generator, yield would - * seems as keyword of yield expression, but even if in generator function scope, - * function parameter can call yield expression, so this function would check is - * current place is in function paramter or not. if yield expression shows in - * parameter list, it would throw error. - * ``` - * YieldExpression := 'yield' - * := 'yield' AssignmentExpression - * := 'yield' '*' AssignmentExpression - * ``` - * @returns {YieldExpression} - */ - function parseYieldExpression(): YieldExpression { - const { start } = expect(SyntaxKinds.YieldKeyword); - let delegate = false; - if (match(SyntaxKinds.MultiplyOperator)) { - if (!getLineTerminatorFlag()) { - nextToken(); - delegate = true; - } - } - const isLineDterminator = getLineTerminatorFlag(); - let argument: Expression | null = null; - if (!isSoftInsertSemi(false) && checkIsFollowByExpreesion()) { - if (delegate || (!delegate && !isLineDterminator)) { - argument = parseAssignmentExpressionInheritIn(); - } - } - if (delegate && !argument) { - raiseError( - ErrorMessageMap.extra_error_yield_deletgate_can_must_be_followed_by_assignment_expression, - getStartPosition(), - ); - } - if (isInParameter()) { - raiseError(ErrorMessageMap.extra_error_yield_expression_can_not_used_in_parameter_list, start); - } - recordScope(ExpressionScopeKind.YieldExpressionInParameter, start); - return Factory.createYieldExpression( - argument, - delegate, - start, - cloneSourcePosition(argument ? argument.end : start), - ); - } - function checkIsFollowByExpreesion() { - switch (getToken()) { - case SyntaxKinds.ColonPunctuator: - case SyntaxKinds.ParenthesesRightPunctuator: - case SyntaxKinds.BracketRightPunctuator: - case SyntaxKinds.CommaToken: - return false; - default: - return true; - } - } - function parseConditionalExpression(): Expression { - const test = parseBinaryExpression(); - if (shouldEarlyReturn(test)) { - return test; - } - // for `arrow function` param, will first - if (!match(SyntaxKinds.QustionOperator)) { - return test; - } - if (requirePlugin(ParserPlugin.TypeScript)) { - const { kind } = lookahead(); - if (kind === SyntaxKinds.ColonPunctuator || kind === SyntaxKinds.ParenthesesRightPunctuator) { - return test; - } - } - nextToken(); - const conseq = parseAssignmentExpressionAllowIn(); - expect(SyntaxKinds.ColonPunctuator); - const alter = parseAssignmentExpressionInheritIn(); - return Factory.createConditionalExpression( - test, - conseq, - alter, - cloneSourcePosition(test.start), - cloneSourcePosition(alter.end), - ); - } - /** - * Private Parse Helper API for parse arrow function. - * @param {Expression} expr - * @returns - */ - function shouldEarlyReturn(expr: Expression) { - return isArrowFunctionExpression(expr) && !expr.parentheses; - } - /** - * Using Operator-precedence parser algorithm is used for parse binary expressiom - * with precedence order. and this is entry function for parseBinaryExpression. - * @returns {Expression} - */ - function parseBinaryExpression(): Expression { - let atom = parseUnaryOrPrivateName(); - if (shouldEarlyReturn(atom)) { - return atom; - } - atom = parseBinaryOps(atom); - if (isPrivateName(atom)) { - raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, atom.start); - } - return atom; - } - /** - * Return the precedence order by given binary operator. - * this function should only used by parseBinaryOps - * @param {SyntaxKinds} kind Binary Operator - * @returns {number} - */ - function getBinaryPrecedence(kind: SyntaxKinds): number { - switch (kind) { - case SyntaxKinds.NullishOperator: - case SyntaxKinds.LogicalOROperator: - return 4; - case SyntaxKinds.LogicalANDOperator: - return 5; - case SyntaxKinds.BitwiseOROperator: - return 6; - case SyntaxKinds.BitwiseXOROperator: - return 7; - case SyntaxKinds.BitwiseANDOperator: - return 8; - case SyntaxKinds.StrictEqOperator: - case SyntaxKinds.StrictNotEqOperator: - case SyntaxKinds.EqOperator: - case SyntaxKinds.NotEqOperator: - return 9; - case SyntaxKinds.InKeyword: - case SyntaxKinds.InstanceofKeyword: - case SyntaxKinds.GtOperator: - case SyntaxKinds.GeqtOperator: - case SyntaxKinds.LeqtOperator: - case SyntaxKinds.LtOperator: - if (kind === SyntaxKinds.InKeyword && !getCurrentInOperatorStack()) { - return -1; - } - return 10; - case SyntaxKinds.BitwiseLeftShiftOperator: - case SyntaxKinds.BitwiseRightShiftOperator: - case SyntaxKinds.BitwiseRightShiftFillOperator: - return 11; - case SyntaxKinds.PlusOperator: - case SyntaxKinds.MinusOperator: - return 12; - case SyntaxKinds.ModOperator: - case SyntaxKinds.DivideOperator: - case SyntaxKinds.MultiplyOperator: - return 13; - case SyntaxKinds.ExponOperator: - return 14; - default: - return -1; - } - } - function isBinaryOps(kind: SyntaxKinds) { - return getBinaryPrecedence(kind) > 0; - } - /** - * Bottom up recurive function for parse binary operator and next - * expression. - * @param {Expression} left - * @param {number} lastPre - * @returns {Expression} - */ - function parseBinaryOps(left: Expression, lastPre: number = 0): Expression { - // eslint-disable-next-line no-constant-condition - while (1) { - // TS handle - if ( - requirePlugin(ParserPlugin.TypeScript) && - (isContextKeyword("as") || isContextKeyword("satisfies")) - ) { - const isSatisfies = isContextKeyword("satisfies"); - nextToken(); - const typeNode = parseTSTypeNode(); - if (isSatisfies) { - left = Factory.createTSSatisfiesExpression( - left, - typeNode, - cloneSourcePosition(left.start), - getLastTokenEndPositon(), - ); - } else { - left = Factory.createTSAsExpression( - left, - typeNode, - cloneSourcePosition(left.start), - getLastTokenEndPositon(), - ); - } - continue; - } - const currentOp = getToken(); - if (!isBinaryOps(currentOp) || getBinaryPrecedence(currentOp) < lastPre) { - break; - } - nextToken(); - let right = parseUnaryOrPrivateName(); - const nextOp = getToken(); - if (isBinaryOps(nextOp) && getBinaryPrecedence(nextOp) > getBinaryPrecedence(currentOp)) { - right = parseBinaryOps(right, getBinaryPrecedence(nextOp)); - } - staticSematicForBinaryExpr(currentOp, nextOp, left, right); - left = Factory.createBinaryExpression( - left, - right, - currentOp as BinaryOperatorKinds, - cloneSourcePosition(left.start), - cloneSourcePosition(right.end), - ); - } - return left; - } - function staticSematicForBinaryExpr( - currentOps: SyntaxKinds, - nextOps: SyntaxKinds, - left: Expression, - right: Expression, - ) { - if (isPrivateName(right) || (isPrivateName(left) && currentOps !== SyntaxKinds.InKeyword)) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, left.start); - } - if (left.parentheses) { - return; - } - if (currentOps === SyntaxKinds.ExponOperator) { - if (isUnaryExpression(left) || isAwaitExpression(left)) { - // recoverable error - raiseError(ErrorMessageMap.v8_error_expont_operator_need_parans, left.start); - } - } - // if currentOp is nullish, next is logical or not - // if current Ops is logical, check next is nullish or not - if ( - currentOps === SyntaxKinds.NullishOperator && - (nextOps === SyntaxKinds.LogicalANDOperator || nextOps === SyntaxKinds.LogicalOROperator) - ) { - // recoverable error - raiseError(ErrorMessageMap.v8_error_nullish_require_parans, left.end); - } - if ( - nextOps === SyntaxKinds.NullishOperator && - (currentOps === SyntaxKinds.LogicalANDOperator || currentOps === SyntaxKinds.LogicalOROperator) - ) { - // recoverable error - raiseError(ErrorMessageMap.v8_error_nullish_require_parans, left.end); - } - } - function parseUnaryOrPrivateName(): Expression { - if (match(SyntaxKinds.PrivateName)) { - const privateName = parsePrivateName(); - usePrivateName(privateName.name, privateName.start); - return privateName; - } - if ( - (match(SyntaxKinds.LtOperator) || match(SyntaxKinds.BitwiseLeftShiftOperator)) && - !requirePlugin(ParserPlugin.JSX) - ) { - const start = getStartPosition(); - const typeArguments = parseTSTypeParameterInstantiation(false); - const expression = parseUnaryExpression(); - return Factory.createTSTypeAssertionExpression( - expression, - typeArguments.params[0], - start, - getLastTokenEndPositon(), - ); - } - return parseUnaryExpression(); - } - function parseUnaryExpression(): Expression { - if (match(UnaryOperators)) { - const operator = getToken() as UnaryOperatorKinds; - const isDelete = operator === SyntaxKinds.DeleteKeyword; - const start = getStartPosition(); - nextToken(); - let argument; - if (isDelete) { - enterDelete(); - argument = parseUnaryExpression(); - exitDelete(); - } else { - argument = parseUnaryExpression(); - } - const unaryExpr = Factory.createUnaryExpression( - argument, - operator, - start, - cloneSourcePosition(argument.end), - ); - staticSematicEarlyErrorForUnaryExpression(unaryExpr); - return unaryExpr; - } - if (match(SyntaxKinds.AwaitKeyword) && isCurrentScopeParseAwaitAsExpression()) { - return parseAwaitExpression(); - } - return parseUpdateExpression(); - } - // 13.5.1.1 - function staticSematicEarlyErrorForUnaryExpression(expr: UnaryExpression) { - if (isInStrictMode() && expr.operator === SyntaxKinds.DeleteKeyword && isIdentifer(expr.argument)) { - raiseError( - ErrorMessageMap.syntax_error_applying_the_delete_operator_to_an_unqualified_name_is_deprecated, - expr.start, - ); - } - } - function parseAwaitExpression() { - if (isInParameter()) { - raiseError( - ErrorMessageMap.extra_error_await_expression_can_not_used_in_parameter_list, - getStartPosition(), - ); - } - const start = getStartPosition(); - nextToken(); - recordScope(ExpressionScopeKind.AwaitExpressionImParameter, start); - const argu = parseUnaryExpression(); - return Factory.createAwaitExpression(argu, start, cloneSourcePosition(argu.end)); - } - function parseUpdateExpression(): Expression { - if (match(UpdateOperators)) { - const operator = getToken() as UpdateOperatorKinds; - const start = getStartPosition(); - nextToken(); - const argument = parseWithLHSLayer(parseLeftHandSideExpression); - checkExpressionAsLeftValue(argument); - return Factory.createUpdateExpression( - argument, - operator, - true, - start, - cloneSourcePosition(argument.end), - ); - } - const [argument, scope] = parseWithCatpureLayer(parseLeftHandSideExpression); - if (match(UpdateOperators) && !getLineTerminatorFlag()) { - checkStrictModeScopeError(scope); - checkExpressionAsLeftValue(argument); - const operator = getToken() as UpdateOperatorKinds; - const end = getEndPosition(); - nextToken(); - return Factory.createUpdateExpression( - argument, - operator, - false, - cloneSourcePosition(argument.start), - end, - ); - } - return argument; - } - /** - * Parse Left hand side Expression. This syntax is reference babel function, which is simplify original syntax of TS39, - * 'this' and super 'super' would be meanful when apper at start of atoms, which can be handle by parseAtoms. NewExpression - * is a spacial case , because it can not using optionalChain, so i handle it into a atom. - * ``` - * LeftHandSideExpression := Atoms '?.' CallExpression - * := Atoms '?.' MemberExpression - * := Atoms TagTemplateExpression - * ``` - * @returns {Expression} - */ - function parseLeftHandSideExpression(): Expression { - let base = parsePrimaryExpression(); - if (shouldEarlyReturn(base)) { - return base; - } - const state: LefthansSideParseState = { - shouldStop: false, - hasOptional: false, - optional: false, - abortLastTime: false, - }; - while (!state.shouldStop) { - state.optional = false; - if ( - requirePlugin(ParserPlugin.TypeScript) && - !getLineTerminatorFlag() && - match(SyntaxKinds.LogicalNOTOperator) - ) { - nextToken(); - base = Factory.createTSNonNullExpression( - base, - cloneSourcePosition(base.start), - getLastTokenEndPositon(), - ); - continue; - } - if (state.abortLastTime) { - base = parseLeftHandSideExpressionWithoutTypeArguments(base, state); - } else { - base = parseLeftHandSideExpressionWithTypeArguments(base, state); - } - } - if (state.abortLastTime && state.hasOptional) { - throw createUnexpectError(); - } - if (state.hasOptional) { - return Factory.createChainExpression( - base, - cloneSourcePosition(base.start), - cloneSourcePosition(base.end), - ); - } - return base; - } - function parseLeftHandSideExpressionWithTypeArguments(base: Expression, state: LefthansSideParseState) { - parseQuestionDotOfLeftHandSideExpression(state); - const result = parseTypeArgumentsOfLeftHandSideExpression(state); - const [typeArguments, abort] = result; - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - // callexpression - base = parseCallExpression(base, state.optional, typeArguments); - } else if (match([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]) || state.optional) { - // memberexpression - if (typeArguments) { - abort(); - } else { - base = parseMemberExpression(base, state.optional); - } - } else if (match(SyntaxKinds.TemplateHead) || match(SyntaxKinds.TemplateNoSubstitution)) { - // tag template expressuin - if (state.hasOptional) { - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_tag_template_expression_can_not_use_option_chain, - getStartPosition(), - ); - } - base = parseTagTemplateExpression(base); - } else { - if (typeArguments) { - const currentToken = getToken(); - if ( - currentToken === SyntaxKinds.GtOperator || - currentToken === SyntaxKinds.BitwiseRightShiftOperator || - (currentToken !== SyntaxKinds.ParenthesesLeftPunctuator && - canStartExpression() && - !getLineTerminatorFlag()) - ) { - abort(); - } else { - // base == TSInstanitExpr - base = Factory.createTSInstantiationExpression( - base, - typeArguments, - cloneSourcePosition(base.start), - getLastTokenEndPositon(), - ); - if ( - match(SyntaxKinds.DotOperator) || - (match(SyntaxKinds.QustionDotOperator) && - lookahead().kind !== SyntaxKinds.ParenthesesLeftPunctuator) - ) { - // TODO: should error - } - } - } else { - state.shouldStop = true; - } - } - return base; - } - function parseLeftHandSideExpressionWithoutTypeArguments(base: Expression, state: LefthansSideParseState) { - parseQuestionDotOfLeftHandSideExpression(state); - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - // callexpression - state.abortLastTime = false; - base = parseCallExpression(base, state.optional, undefined); - } else if (match([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]) || state.optional) { - // memberexpression - state.abortLastTime = false; - base = parseMemberExpression(base, state.optional); - } else if (match(SyntaxKinds.TemplateHead) || match(SyntaxKinds.TemplateNoSubstitution)) { - // tag template expressuin - if (state.hasOptional) { - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_tag_template_expression_can_not_use_option_chain, - getStartPosition(), - ); - } - state.abortLastTime = false; - base = parseTagTemplateExpression(base); - } else { - state.shouldStop = true; - } - return base; - } - function parseQuestionDotOfLeftHandSideExpression(state: LefthansSideParseState) { - if (match(SyntaxKinds.QustionDotOperator)) { - state.optional = true; - state.hasOptional = true; - nextToken(); - } - } - function parseTypeArgumentsOfLeftHandSideExpression( - state: LefthansSideParseState, - ): [TSTypeParameterInstantiation | undefined, () => void] { - let typeArguments: TSTypeParameterInstantiation | undefined = undefined; - let abort = () => {}; - if (match(SyntaxKinds.LtOperator) || match(SyntaxKinds.BitwiseLeftShiftOperator)) { - const result = tryParse(() => { - return tryParseTSTypeParameterInstantiation(false); - }); - if (result) { - typeArguments = result?.[0]; - abort = () => { - lexer.restoreState(result[1], result[2]); - errorhandler.restoreTryFail(result[3]); - state.abortLastTime = true; - }; - return [typeArguments, abort]; - } - //return undefined; - } - return [typeArguments, abort]; - } - /** - * Check is a assignable left value - * @param expression - * @returns - */ - function checkExpressionAsLeftValue(expression: ModuleItem) { - if (isAssignable(expression)) { - return; - } - raiseError(ErrorMessageMap.invalid_left_value, expression.start); - } - /** - * Parse CallExpression - * ``` - * CallExpresion := GivenBase(base, optional) '(' Arguments ')' - * ``` - * @param {Expression} callee base expression - * @param {boolean} optional is this call optional ? - * @returns {Expression} - */ - function parseCallExpression( - callee: Expression, - optional: boolean, - typeParameter: TSTypeParameterInstantiation | undefined, - ): Expression { - expectButNotEat([SyntaxKinds.ParenthesesLeftPunctuator]); - const { nodes, end } = parseArguments(); - return Factory.createCallExpression( - callee, - nodes, - typeParameter, - optional, - cloneSourcePosition(callee.start), - end, - ); - } - /** - * // TODO: remove possble dep of arrow function paramemter need to call this function. - * - * Parse Arguments, used by call expression, and arrow function paramemter. - * ``` - * Arguments := '(' ArgumentList ')' - * ArgumentList := ArgumentList AssigmentExpression - * := ArgumentList SpreadElement - * := AssignmentExpression - * := SpreadElement - * ``` - */ - function parseArguments() { - return parseArgumentsBase(false); - } - function parseArgumentsWithType() { - return parseArgumentsBase(true); - } - function parseArgumentsBase(acceptType: boolean): ASTArrayWithMetaData & { - trailingComma: boolean; - typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; - } { - const { start } = expect(SyntaxKinds.ParenthesesLeftPunctuator); - let isStart = true; - // TODO: refactor logic to remove shoulStop - const callerArguments: Array = []; - const typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> = []; - let trailingComma = false; - while (!match(SyntaxKinds.ParenthesesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - isStart = false; - if (match(SyntaxKinds.CommaToken)) { - // trailing comma - raiseError(ErrorMessageMap.extra_error_unexpect_trailing_comma, getStartPosition()); - nextToken(); - } - } else { - trailingComma = true; - expect(SyntaxKinds.CommaToken); - } - // case 1: ',' following by ')' - if (match(SyntaxKinds.ParenthesesRightPunctuator)) { - break; - } - trailingComma = false; - // case 2: ',' following by SpreadElement, maybe follwed by ',' - if (match(SyntaxKinds.SpreadOperator)) { - const spreadElementStart = getStartPosition(); - nextToken(); - let argu: Expression = Factory.createSpreadElement( - parseAssignmentExpressionAllowIn(), - spreadElementStart, - getLastTokenEndPositon(), - ); - if (acceptType) { - typeAnnotations.push(parsePossibleArugmentType()); - argu = parsePossibleArugmentDefaultValue(argu); - } - callerArguments.push(argu); - continue; - } - // case 3 : ',' AssigmentExpression - let argu = parseAssignmentExpressionAllowIn(); - if (acceptType) { - typeAnnotations.push(parsePossibleArugmentType()); - argu = parsePossibleArugmentDefaultValue(argu); - } - callerArguments.push(argu); - } - const { end } = expect(SyntaxKinds.ParenthesesRightPunctuator); - return { - end, - start, - nodes: callerArguments, - typeAnnotations: typeAnnotations.length === 0 ? undefined : typeAnnotations, - trailingComma, - }; - } - - /** - * Parse with base, this different between parseLeftHandSideExpression is - * that parseMemberExpression would only eat a `atom` of chain of expression. - * ``` - * MemberExpression := GivenBase(base ,optional) '.' IdentiferWithKeyword - * := GivenBase(base, optional) '[' Expreession ']' - * := GivenBase(base, optional) IdentiferWithKeyword - * // for last condition, optional prope must be True - * ``` - * @param {Expression} base base expression - * @param {boolean} optional is base expression contain a optional - * @returns {Expression} - */ - function parseMemberExpression(base: Expression, optional: boolean): Expression { - if (!match(SyntaxKinds.DotOperator) && !match(SyntaxKinds.BracketLeftPunctuator) && !optional) { - throw createUnreachError([SyntaxKinds.DotOperator, SyntaxKinds.BracketLeftPunctuator]); - } - // if start with dot, must be a access property, can not with optional. - // because optional means that last token is `?.` - if (match(SyntaxKinds.DotOperator) && !optional) { - expect(SyntaxKinds.DotOperator); - const property = parseMemberExpressionProperty(); - - return Factory.createMemberExpression( - false, - base, - property, - optional, - cloneSourcePosition(base.start), - cloneSourcePosition(property.end), - ); - } - // if start with `[`, must be computed property access. - else if (match(SyntaxKinds.BracketLeftPunctuator)) { - expect(SyntaxKinds.BracketLeftPunctuator); - const property = parseExpressionAllowIn(); - const { end } = expect(SyntaxKinds.BracketRightPunctuator); - return Factory.createMemberExpression( - true, - base, - property, - optional, - cloneSourcePosition(base.start), - end, - ); - } else { - // because parseLeftHandSideExpression would eat optional mark (QustionDotToken) frist, so maybe there - // is not dot or `[` for start a member expression, so we can check optional is - const property = parseMemberExpressionProperty(); - return Factory.createMemberExpression( - false, - base, - property, - optional, - cloneSourcePosition(base.start), - cloneSourcePosition(property.end), - ); - } - } - function parseMemberExpressionProperty() { - let property: Expression | PrivateName; - if (match(SyntaxKinds.PrivateName)) { - property = parsePrivateName(); - usePrivateName(property.name, property.start); - if (isInDelete()) { - raiseError( - ErrorMessageMap.syntax_error_applying_the_delete_operator_to_an_unqualified_name_is_deprecated, - property.start, - ); - } - } else { - property = parseIdentifierName(); - } - return property; - } - function parseTagTemplateExpression(base: Expression) { - const quasi = parseTemplateLiteral(true); - return Factory.createTagTemplateExpression( - base, - quasi, - cloneSourcePosition(base.end), - cloneSourcePosition(quasi.end), - ); - } - function parsePrimaryExpression(): Expression { - switch (getToken()) { - case SyntaxKinds.LtOperator: - return parseJSXElementOrJSXFragment(false); - case SyntaxKinds.DivideOperator: - case SyntaxKinds.DivideAssignOperator: - return parseRegexLiteral(); - case SyntaxKinds.NullKeyword: - return parseNullLiteral(); - case SyntaxKinds.UndefinedKeyword: - return parseUndefinedLiteral(); - case SyntaxKinds.TrueKeyword: - case SyntaxKinds.FalseKeyword: - return parseBoolLiteral(); - case SyntaxKinds.DecimalLiteral: - return parseDecimalLiteral(); - case SyntaxKinds.DecimalBigIntegerLiteral: - return parseDecimalBigIntegerLiteral(); - case SyntaxKinds.NonOctalDecimalLiteral: - return parseNonOctalDecimalLiteral(); - case SyntaxKinds.BinaryIntegerLiteral: - return parseBinaryIntegerLiteral(); - case SyntaxKinds.BinaryBigIntegerLiteral: - return parseBinaryBigIntegerLiteral(); - case SyntaxKinds.OctalIntegerLiteral: - return parseOctalIntegerLiteral(); - case SyntaxKinds.OctalBigIntegerLiteral: - return parseOctalBigIntegerLiteral(); - case SyntaxKinds.HexIntegerLiteral: - return parseHexIntegerLiteral(); - case SyntaxKinds.HexBigIntegerLiteral: - return parseHexBigIntegerLiteral(); - case SyntaxKinds.LegacyOctalIntegerLiteral: - return parseLegacyOctalIntegerLiteral(); - case SyntaxKinds.StringLiteral: - return parseStringLiteral(); - case SyntaxKinds.TemplateHead: - case SyntaxKinds.TemplateNoSubstitution: - return parseTemplateLiteral(false); - case SyntaxKinds.ImportKeyword: { - const { kind } = lookahead(); - if (kind === SyntaxKinds.DotOperator) return parseImportMeta(); - if (kind === SyntaxKinds.ParenthesesLeftPunctuator) { - return parseImportCall(); - } - throw createUnexpectError(); - } - case SyntaxKinds.NewKeyword: { - const { kind } = lookahead(); - if (kind === SyntaxKinds.DotOperator) { - return parseNewTarget(); - } - return parseNewExpression(); - } - case SyntaxKinds.SuperKeyword: - return parseSuper(); - case SyntaxKinds.ThisKeyword: - return parseThisExpression(); - case SyntaxKinds.BracesLeftPunctuator: - return parseObjectExpression(); - case SyntaxKinds.BracketLeftPunctuator: - return parseArrayExpression(); - case SyntaxKinds.FunctionKeyword: - return parseFunctionExpression(false); - case SyntaxKinds.AtPunctuator: { - return parseClassExpression(parseDecoratorList()); - } - case SyntaxKinds.ClassKeyword: - return parseClassExpression(null); - case SyntaxKinds.ParenthesesLeftPunctuator: - return parseCoverExpressionORArrowFunction(); - // TODO: consider wrap as function or default case ? - case SyntaxKinds.PrivateName: - // recoverable error - raiseError(ErrorMessageMap.babel_error_private_name_wrong_used, getStartPosition()); - return parsePrivateName(); - // return parsePrivateName(); - case SyntaxKinds.Identifier: - case SyntaxKinds.LetKeyword: - case SyntaxKinds.AwaitKeyword: - case SyntaxKinds.YieldKeyword: { - const { kind, lineTerminatorFlag: flag } = lookahead(); - // case 0: identifier `=>` ... - if (kind === SyntaxKinds.ArrowOperator && canParseAsArrowFunction()) { - const [[argus, strictModeScope], arrowExprScope] = parseWithArrowExpressionScope(() => - parseWithLHSLayerReturnScope(() => [parseIdentifierReference()]), - ); - if (getLineTerminatorFlag()) { - raiseError(ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, getStartPosition()); - } - enterArrowFunctionBodyScope(); - const arrowExpr = parseArrowFunctionExpression( - { - nodes: argus, - start: argus[0].start, - end: argus[0].end, - trailingComma: false, - typeAnnotations: undefined, - }, - undefined, - strictModeScope, - arrowExprScope, - ); - exitArrowFunctionBodyScope(); - return arrowExpr; - } - if (getSourceValue() === "async") { - // case 1: `async` `function` ==> must be async function () {} - if (kind === SyntaxKinds.FunctionKeyword && !getEscFlag()) { - const { value, start, end } = expect(SyntaxKinds.Identifier); - if (getLineTerminatorFlag()) { - return Factory.createIdentifier(value, start, end, undefined, undefined); - } - return parseFunctionExpression(true); - } - if (canParseAsArrowFunction()) { - // case 2 `async` `(` - // There might be two case : - // 1.frist case is there are line change after async, which make this case into - // call expression - // 2.second case is not change line after async, making it become async arrow - // function. - // -------------------------- - if (kind === SyntaxKinds.ParenthesesLeftPunctuator) { - const containEsc = getEscFlag(); - const id = parseIdentifierReference(); // async - // TODO: better accept type param or argument to create async arrow or async call. - const [[meta, strictModeScope], arrowExprScope] = parseWithArrowExpressionScope(() => - parseWithCatpureLayer(parseArgumentsWithType), - ); - if ( - flag || - (!match(SyntaxKinds.ArrowOperator) && - !(match(SyntaxKinds.ColonPunctuator) && requirePlugin(ParserPlugin.TypeScript))) - ) { - return Factory.createCallExpression( - id, - meta.nodes, - undefined, - false, - cloneSourcePosition(id.start), - meta.end, - ); - } - if (containEsc) { - raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, id.start); - } - const returnType = tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); - enterArrowFunctionBodyScope(true); - const arrowFunExpr = parseArrowFunctionExpression( - meta, - undefined, - strictModeScope, - arrowExprScope, - ); - exitArrowFunctionBodyScope(); - arrowFunExpr.returnType = returnType; - return arrowFunExpr; - } - // case 2-TS: `async` `<` or async `<<` - // for `<`, is possible to be a - // - typeParameter: for a async function declaration - // - typeArguments: for a function call which callee is `async` - // - binary expression: `async < literal-item`. - if (kind === SyntaxKinds.LtOperator) { - const id = parseIdentifierReference(); - const typeParameterResult = tryParse(() => parseTSTypeParameterDeclaration(false)); - if (typeParameterResult) { - // there - const [ - [{ start, end, nodes, trailingComma, typeAnnotations }, strictModeScope], - arrowExprScope, - ] = parseWithArrowExpressionScope(() => parseWithCatpureLayer(parseArgumentsWithType)); - const returnType = tryParseTSReturnTypeOrTypePredicateForArrowExpression(true, nodes); - if (match(SyntaxKinds.ArrowOperator)) { - enterArrowFunctionBodyScope(true); - const arrowExpr = parseArrowFunctionExpression( - { start, end, nodes, trailingComma, typeAnnotations }, - undefined, - strictModeScope, - arrowExprScope, - ); - exitArrowFunctionBodyScope(); - arrowExpr.returnType = returnType; - arrowExpr.typeParameters = typeParameterResult[0]; - return arrowExpr; - } - abortTryParseResult(typeParameterResult[1], typeParameterResult[2], typeParameterResult[3]); - } - const typeArgumentResult = tryParse(() => parseTSTypeParameterInstantiation(false)); - if (typeArgumentResult) { - const typeArguments = typeArgumentResult[0]; - const callArguments = parseArguments().nodes; - return Factory.createCallExpression( - id, - callArguments, - typeArguments, - false, - cloneSourcePosition(id.start), - getLastTokenEndPositon(), - ); - } - return id; - } - // for '<<', it must be `async< async as function call - if (kind === SyntaxKinds.BitwiseLeftShiftOperator) { - const id = parseIdentifierReference(); - lexer.reLexLtRelateToken(); - const typeArguments = parseTSTypeParameterInstantiation(false); - const callArguments = parseArguments().nodes; - return Factory.createCallExpression( - id, - callArguments, - typeArguments, - false, - cloneSourcePosition(id.start), - getLastTokenEndPositon(), - ); - } - // case 3: `async` `Identifer` ... - // There might be two case : - // 1.frist case is there are line change after async, or there is no arrow operator, - // which make this case into async as identifier - // 2.second case is not change line after async, making it become async arrow - // function. - if ( - kind === SyntaxKinds.Identifier || - kind === SyntaxKinds.YieldKeyword || - kind === SyntaxKinds.AwaitKeyword - ) { - // async followed by line break - if (flag) { - return parseIdentifierReference(); - } - const isAsyncContainUnicode = getEscFlag(); - const { start, end } = expect(SyntaxKinds.Identifier); // eat async - const { kind: maybeArrowToken } = lookahead(); - // there is no arrow operator. - if (maybeArrowToken !== SyntaxKinds.ArrowOperator) { - return Factory.createIdentifier("async", start, end, undefined, undefined); - } - if (isAsyncContainUnicode) { - raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, start); - } - const [[argus, strictModeScope], arrowExprScope] = parseWithArrowExpressionScope(() => - parseWithCatpureLayer(() => [parseIdentifierReference()]), - ); - if (getLineTerminatorFlag()) { - raiseError( - ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, - getStartPosition(), - ); - } - enterArrowFunctionBodyScope(true); - const arrowExpr = parseArrowFunctionExpression( - { - nodes: argus, - start: argus[0].start, - end: argus[0].end, - trailingComma: false, - typeAnnotations: undefined, - }, - undefined, - strictModeScope, - arrowExprScope, - ); - exitArrowFunctionBodyScope(); - return arrowExpr; - } - } - } - return parseIdentifierReference(); - } - default: - throw createUnexpectError(); - } - } - function parseRegexLiteral(): RegexLiteral { - expectButNotEat([SyntaxKinds.DivideOperator, SyntaxKinds.DivideAssignOperator]); - const startWithAssignOperator = match(SyntaxKinds.DivideAssignOperator); - const start = getStartPosition(); - // eslint-disable-next-line prefer-const - let { pattern, flag } = readRegex(); - nextToken(); - if (startWithAssignOperator) { - pattern = "=" + pattern; - } - return Factory.createRegexLiteral(pattern, flag, start, getEndPosition()); - } - /** - * IdentifierReference, IdentifierName and BindingIdentifier is not samething in the - * spec. - * - IdentifierReference is a id in Lval or Rval - * - IdentifierName is a property of member expression or object, class - * - BindingIdentifier is a lval. - * @returns {Identifier} - */ - function parseIdentifierReference(): Identifier { - expectButNotEat([ - SyntaxKinds.Identifier, - SyntaxKinds.AwaitKeyword, - SyntaxKinds.YieldKeyword, - SyntaxKinds.LetKeyword, - ]); - // sematic check for a binding identifier - let identifer: Identifier; - switch (getToken()) { - // for most of yield keyword, if it should treat as identifier, - // it should not in generator function. - case SyntaxKinds.YieldKeyword: { - const { value, start, end } = expect(SyntaxKinds.YieldKeyword); - staticSematicForIdentifierAsYield(start); - identifer = Factory.createIdentifier(value, start, end, undefined, undefined); - break; - } - // for most of await keyword, if it should treat as identifier, - // it should not in async function. - case SyntaxKinds.AwaitKeyword: { - const { value, start, end } = expect(SyntaxKinds.AwaitKeyword); - staticSematicForIdentifierAsAwait(start); - identifer = Factory.createIdentifier(value, start, end, undefined, undefined); - break; - } - // let maybe treat as identifier in not strict mode, and not lexical binding declaration. - // so lexical binding declaration should implement it's own checker logical with parseIdentifierWithKeyword - case SyntaxKinds.LetKeyword: { - const { value, start, end } = expect(SyntaxKinds.LetKeyword); - staticSematicForIdentifierAsLet(start); - identifer = Factory.createIdentifier(value, start, end, undefined, undefined); - break; - } - case SyntaxKinds.Identifier: { - const { value, start, end } = expect(SyntaxKinds.Identifier); - staticSematicForIdentifierDefault(value, start); - identifer = Factory.createIdentifier(value, start, end, undefined, undefined); - break; - } - default: { - throw createUnexpectError(); - } - } - return identifer; - } - /** - * Yield only could be used as a identifier when - * - * - it is not in strict mode. - * - not in a generator context. - * - * record it's usage for defer check. - * @param {SourcePosition} start - */ - function staticSematicForIdentifierAsYield(start: SourcePosition) { - if (isCurrentScopeParseYieldAsExpression() || isInStrictMode()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, start); - } - recordScope(ExpressionScopeKind.YieldIdentifier, start); - } - /** - * Await only could be used as identifirt when - * - * - it is not in module mode - * - not in async context - * - * record it's usgae for defer check. - * @param {SourcePosition} start - */ - function staticSematicForIdentifierAsAwait(start: SourcePosition) { - if (isCurrentScopeParseAwaitAsExpression() || config.sourceType === "module") { - raiseError(ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, start); - } - // skip if is using await in class property name in async context - if (isDirectToClassScope() && !isInPropertyName()) { - return; - } - recordScope(ExpressionScopeKind.AwaitIdentifier, start); - } - /** - * Let only could be used as identifirt when - * - * - it is not in strict mode - * - * record it's usgae for defer check. - * @param {SourcePosition} start - */ - function staticSematicForIdentifierAsLet(start: SourcePosition) { - if (isInStrictMode()) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); - } - recordScope(ExpressionScopeKind.LetIdentifiier, start); - } - /** - * Checking the usage for arguments and eval, presverveword - * - * - for presverveword, only could be used when not in strict mode - * - for argument, can not used when - * 1. in strict mode, and in the lhs - * 2. in strict mode, not in function. - * - for eval, can not used when - * 1. in strict mode, and in the lhs - * - * record it's usgae for defer check. - * @param {SourcePosition} start - */ - function staticSematicForIdentifierDefault(value: string, start: SourcePosition) { - const isPreserveWord = PreserveWordSet.has(value); - if (isPreserveWord) { - if (isInStrictMode()) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); - } - recordScope(ExpressionScopeKind.PresveredWordIdentifier, start); - } - if (value === "arguments") { - if (isInStrictMode()) { - if (!isEncloseInFunction() && !isInPropertyName()) { - // invalud usage - raiseError(ErrorMessageMap.syntax_error_arguments_is_not_valid_in_fields, start); - } - if (strictModeScopeRecorder.isInLHS()) { - // invalid assignment - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); - } - } - recordScope(ExpressionScopeKind.ArgumentsIdentifier, start); - } - if (value === "eval") { - if (isInStrictMode() && strictModeScopeRecorder.isInLHS()) { - raiseError(ErrorMessageMap.unexpect_keyword_in_stric_mode, start); - } - recordScope(ExpressionScopeKind.EvalIdentifier, start); - } - } - /** - * Relatedly loose function for parseIdentifier, it not only can parse identifier, - * it also can parse keyword as identifier. - * @returns {Identifier} - */ - function parseIdentifierName(): Identifier { - const { value, start, end } = expect(IdentiferWithKeyworArray); - return Factory.createIdentifier(value, start, end, undefined, undefined); - } - /** - * ECMA spec has every strict rule to private name, but in this parser, most of - * strict rule check is implemented by callee, there we only gonna check is in - * class scope or not. - * @returns {PrivateName} - */ - function parsePrivateName(): PrivateName { - const { value, start, end } = expect(SyntaxKinds.PrivateName); - if (!isInClassScope()) { - raiseError(ErrorMessageMap.syntax_error_unexpected_hash_used_outside_of_class_body, start); // semantics check for private - } - return Factory.createPrivateName(value, start, end); - } - function parseNullLiteral() { - const { start, end } = expect(SyntaxKinds.NullKeyword); - return Factory.createNullLiteral(start, end); - } - function parseUndefinedLiteral() { - const { start, end } = expect(SyntaxKinds.UndefinedKeyword); - return Factory.createUndefinedLiteral(start, end); - } - function parseDecimalLiteral() { - const { start, end, value } = expect(SyntaxKinds.DecimalLiteral); - return Factory.createDecimalLiteral(value, start, end); - } - function parseDecimalBigIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.DecimalBigIntegerLiteral); - return Factory.createDecimalBigIntegerLiteral(value, start, end); - } - function parseNonOctalDecimalLiteral() { - const { start, end, value } = expect(SyntaxKinds.NonOctalDecimalLiteral); - if (isInStrictMode()) { - raiseError(ErrorMessageMap.Syntax_error_0_prefixed_octal_literals_are_deprecated, start); - } - return Factory.createNonOctalDecimalLiteral(value, start, end); - } - function parseBinaryIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.BinaryIntegerLiteral); - return Factory.createBinaryIntegerLiteral(value, start, end); - } - function parseBinaryBigIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.BinaryBigIntegerLiteral); - return Factory.createBinaryBigIntegerLiteral(value, start, end); - } - function parseOctalIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.OctalIntegerLiteral); - return Factory.createOctalIntegerLiteral(value, start, end); - } - function parseOctalBigIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.OctalBigIntegerLiteral); - return Factory.createOctBigIntegerLiteral(value, start, end); - } - function parseHexIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.HexIntegerLiteral); - return Factory.createHexIntegerLiteral(value, start, end); - } - function parseHexBigIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.HexBigIntegerLiteral); - return Factory.createHexBigIntegerLiteral(value, start, end); - } - function parseLegacyOctalIntegerLiteral() { - const { start, end, value } = expect(SyntaxKinds.LegacyOctalIntegerLiteral); - if (isInStrictMode()) { - raiseError(ErrorMessageMap.Syntax_error_0_prefixed_octal_literals_are_deprecated, start); - } - return Factory.createLegacyOctalIntegerLiteral(value, start, end); - } - function parseNumericLiteral(): NumberLiteral { - switch (getToken()) { - case SyntaxKinds.DecimalLiteral: - return parseDecimalLiteral(); - case SyntaxKinds.DecimalBigIntegerLiteral: - return parseDecimalBigIntegerLiteral(); - case SyntaxKinds.NonOctalDecimalLiteral: - return parseNonOctalDecimalLiteral(); - case SyntaxKinds.BinaryIntegerLiteral: - return parseBinaryIntegerLiteral(); - case SyntaxKinds.BinaryBigIntegerLiteral: - return parseBinaryBigIntegerLiteral(); - case SyntaxKinds.OctalIntegerLiteral: - return parseOctalIntegerLiteral(); - case SyntaxKinds.OctalBigIntegerLiteral: - return parseOctalBigIntegerLiteral(); - case SyntaxKinds.HexIntegerLiteral: - return parseHexIntegerLiteral(); - case SyntaxKinds.HexBigIntegerLiteral: - return parseHexBigIntegerLiteral(); - case SyntaxKinds.LegacyOctalIntegerLiteral: - return parseLegacyOctalIntegerLiteral(); - default: - throw createUnexpectError(); - } - } - function parseStringLiteral() { - const { start, end, value } = expect(SyntaxKinds.StringLiteral); - return Factory.createStringLiteral(value, start, end); - } - function parseBoolLiteral() { - const { start, end, value } = expect([SyntaxKinds.TrueKeyword, SyntaxKinds.FalseKeyword]); - return Factory.createBoolLiteral(value === "true" ? true : false, start, end); - } - function parseTemplateLiteral(tagged: boolean) { - if (!match([SyntaxKinds.TemplateHead, SyntaxKinds.TemplateNoSubstitution])) { - throw createUnreachError([SyntaxKinds.TemplateHead, SyntaxKinds.TemplateNoSubstitution]); - } - const templateLiteralStart = getStartPosition(); - if (match(SyntaxKinds.TemplateNoSubstitution)) { - if (!tagged && lexer.getTemplateLiteralTag()) { - raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, getStartPosition()); - } - const value = getSourceValue(); - const templateLiteralEnd = getEndPosition(); - nextToken(); - return Factory.createTemplateLiteral( - [Factory.createTemplateElement(value, true, templateLiteralStart, templateLiteralEnd)], - [], - templateLiteralStart, - templateLiteralEnd, - ); - } - nextToken(); - const expressions = [parseExpressionAllowIn()]; - const quasis: Array = []; - while ( - !match(SyntaxKinds.TemplateTail) && - match(SyntaxKinds.TemplateMiddle) && - !match(SyntaxKinds.EOFToken) - ) { - if (!tagged && lexer.getTemplateLiteralTag()) { - raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, getStartPosition()); - } - quasis.push( - Factory.createTemplateElement(getSourceValue(), false, getStartPosition(), getEndPosition()), - ); - nextToken(); - expressions.push(parseExpressionAllowIn()); - } - if (match(SyntaxKinds.EOFToken)) { - throw createUnexpectError(); - } - if (!tagged && lexer.getTemplateLiteralTag()) { - raiseError(ErrorMessageMap.v8_error_invalid_hexadecimal_escape_sequence, getStartPosition()); - } - quasis.push(Factory.createTemplateElement(getSourceValue(), true, getStartPosition(), getEndPosition())); - const templateLiteralEnd = getEndPosition(); - nextToken(); - return Factory.createTemplateLiteral(quasis, expressions, templateLiteralStart, templateLiteralEnd); - } - /** - * Parse import meta property - * ``` - * ImportMeta := import . meta - * ``` - * @returns {MetaProperty} - */ - function parseImportMeta(): MetaProperty { - const { start, end } = expect(SyntaxKinds.ImportKeyword); - expect(SyntaxKinds.DotOperator); - const ecaFlag = getEscFlag(); - const property = parseIdentifierReference(); - staticSematicForImportMeta(property, ecaFlag, start); - return Factory.createMetaProperty( - Factory.createIdentifier("import", start, end, undefined, undefined), - property, - start, - cloneSourcePosition(property.end), - ); - } - /** - * Sematic check for import meta - * - import member expression's property only could be meta - * - meta should be a contextual keyword - * - import meta can't use in script mode - * @param property - * @param ecaFlag - */ - function staticSematicForImportMeta(property: Identifier, ecaFlag: boolean, start: SourcePosition) { - if (property.name !== "meta") { - raiseError( - ErrorMessageMap.babel_error_the_only_valid_meta_property_for_import_is_import_meta, - property.start, - ); - } - if (ecaFlag) { - raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, start); - } - if (config.sourceType === "script") { - raiseError(ErrorMessageMap.babel_error_import_meta_may_appear_only_with_source_type_module, start); - } - } - /** - * Parse Import call - * ``` - * ImportCall := import ( AssignmentExpression[+In], (optional support attribute) ) - * ``` - * @returns {CallExpression} - */ - function parseImportCall(): CallExpression { - const { start, end } = expect(SyntaxKinds.ImportKeyword); - expect(SyntaxKinds.ParenthesesLeftPunctuator); - const argument = parseAssignmentExpressionAllowIn(); - const option = parseImportAttributeOptional(); - const { end: finalEnd } = expect(SyntaxKinds.ParenthesesRightPunctuator); - return Factory.createCallExpression( - Factory.createImport(start, end), - option ? [argument, option] : [argument], - undefined, - false, - cloneSourcePosition(start), - cloneSourcePosition(finalEnd), - ); - } - /** - * Parse import attribute (stage 3 syntax) - * ref: https://github.com/tc39/proposal-import-attributes - * @returns - */ - function parseImportAttributeOptional(): Expression | null { - if (!requirePlugin(ParserPlugin.ImportAssertions) && !requirePlugin(ParserPlugin.ImportAttribute)) { - return null; - } - if (!match(SyntaxKinds.CommaToken)) { - return null; - } - nextToken(); - if (match(SyntaxKinds.ParenthesesRightPunctuator)) { - return null; - } - const option = parseAssignmentExpressionAllowIn(); - if (match(SyntaxKinds.CommaToken)) { - nextToken(); - } - return option; - } - /** - * Parse new target - * ``` - * NewTarget := new . target - * ``` - * @returns {MetaProperty} - */ - function parseNewTarget(): MetaProperty { - const { start, end } = expect(SyntaxKinds.NewKeyword); - expect(SyntaxKinds.DotOperator); - staticSematicForNewTarget(start); - const targetStart = getStartPosition(); - const targetEnd = getEndPosition(); - nextToken(); - return Factory.createMetaProperty( - Factory.createIdentifier("new", start, end, undefined, undefined), - Factory.createIdentifier("target", targetStart, targetEnd, undefined, undefined), - start, - targetEnd, - ); - } - function staticSematicForNewTarget(start: SourcePosition) { - if (!isContextKeyword("target")) { - // recoverable error - throw createUnexpectError(); - } - if (!config.allowNewTargetOutsideFunction && isTopLevel() && !isInClassScope()) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_new_target_can_only_be_used_in_class_or_function_scope, start); - } - } - /** - * Parse New Expression, the callee part of new expression is a trick one, - * this is not a member expression, it can not contain qustion dot or call - * expression. - * ``` - * NewExpression := 'new' NewExpression - * := 'new' MemberExpressionWithoutOptional Arugment? - * ``` - * @returns {Expression} - */ - function parseNewExpression(): Expression { - const { start } = expect(SyntaxKinds.NewKeyword); - // maybe is new.target - if (match(SyntaxKinds.NewKeyword) && lookahead().kind !== SyntaxKinds.DotOperator) { - return parseNewExpression(); - } - let base = parsePrimaryExpression(); - staticSematicForBaseInNewExpression(base); - base = parseNewExpressionCallee(base); - const typeArgument = tryParseTSTypeParameterInstantiationForNewExpression(); - if (!match(SyntaxKinds.ParenthesesLeftPunctuator)) { - // accpect New XXX -> No argument - return Factory.createNewExpression(base, [], typeArgument, start, cloneSourcePosition(base.end)); - } - const { end, nodes } = parseArguments(); - return Factory.createNewExpression(base, nodes, typeArgument, start, end); - } - /** - * The base of new expression can not be a import call expression, if must be a import - * call expression, it must be have a paran. - * @param {Expression} base - */ - function staticSematicForBaseInNewExpression(base: Expression) { - if (!base.parentheses && isCallExpression(base) && base.callee.kind === SyntaxKinds.Import) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_cannot_use_new_with_import, base.start); - } - } - /** - * Parse the callee of new expression, base of new expression can not - * be a call expression or a qustion dot expression. - * @param {Expression} base - * @returns - */ - function parseNewExpressionCallee(base: Expression): Expression { - while ( - match(SyntaxKinds.DotOperator) || - match(SyntaxKinds.BracketLeftPunctuator) || - match(SyntaxKinds.QustionDotOperator) - ) { - if (match(SyntaxKinds.QustionDotOperator)) { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_constructors_in_after_an_optional_chain_are_not_allowed, - getStartPosition(), - ); - nextToken(); - base = parseMemberExpression(base, true); - continue; - } - base = parseMemberExpression(base, false); - } - return base; - } - function tryParseTSTypeParameterInstantiationForNewExpression() { - if (match(SyntaxKinds.LtOperator) || match(SyntaxKinds.BitwiseLeftShiftOperator)) { - const result = tryParse(() => tryParseTSTypeParameterInstantiation(false)); - if ( - match([ - SyntaxKinds.ParenthesesLeftPunctuator, - SyntaxKinds.TemplateNoSubstitution, - SyntaxKinds.TemplateHead, - ]) - ) { - return result?.[0]; - } - if ( - match([ - SyntaxKinds.LtOperator, - SyntaxKinds.GtOperator, - SyntaxKinds.MinusOperator, - SyntaxKinds.MinusOperator, - ]) || - !(getLineTerminatorFlag() || isBinaryOps(getToken()) || !canStartExpression()) - ) { - if (result) abortTryParseResult(result[1], result[2], result[3]); - return; - } - return result?.[0]; - } - } - // TODO: finish all possible - // reference: https://github.com/oxc-project/oxc/blob/eac34b676f473f79bcb4a55d6322d0b02a15d6fa/crates/oxc_parser/src/ts/types.rs#L1382 - function canStartExpression() { - switch (getToken()) { - case SyntaxKinds.Identifier: - case SyntaxKinds.PlusOperator: - case SyntaxKinds.MinusOperator: - return true; - case SyntaxKinds.TrueKeyword: - case SyntaxKinds.FalseKeyword: - case SyntaxKinds.NullKeyword: - case SyntaxKinds.UndefinedKeyword: - case SyntaxKinds.StringLiteral: - return true; - default: - return false; - } - } - /** - * Parse super expression, only parse the arguments and super or a first level - * of access of member expression. Contain sematic check: - * - Super call only valid in ctor. - * - super property can be used in any method of class. - * ``` - * SuperCall := super argument - * SuperProperty := super[Expression] - * := super.IdentifierName - * ``` - * @returns {Expression} - */ - function parseSuper(): Expression { - if (!isCurrentClassExtend()) { - // recoverable error - raiseError( - ErrorMessageMap.syntax_error_super_is_only_valid_in_derived_class_constructors, - getStartPosition(), - ); - } - const { start: keywordStart, end: keywordEnd } = expect([SyntaxKinds.SuperKeyword]); - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - if (!lexicalScopeRecorder.isInCtor()) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_call_super_outside_of_ctor, keywordStart); - } - const typeArguments = tryParseTSTypeParameterInstantiation(false); - const { nodes, end: argusEnd } = parseArguments(); - return Factory.createCallExpression( - Factory.createSuper(keywordStart, keywordEnd), - nodes, - typeArguments, - false, - cloneSourcePosition(keywordStart), - argusEnd, - ); - } - let property: Expression; - let isComputed = false; - let end: SourcePosition; - switch (getToken()) { - case SyntaxKinds.QustionDotOperator: - // recoverable error. - raiseError(ErrorMessageMap.babel_invalid_usage_of_super_call, getStartPosition()); - // eslint-disable-next-line no-fallthrough - case SyntaxKinds.DotOperator: { - nextToken(); - if (match(SyntaxKinds.PrivateName)) { - property = parsePrivateName(); - // recoverable error - raiseError(ErrorMessageMap.babel_error_private_fields_cant_be_accessed_on_super, property.start); - } else { - property = parseIdentifierName(); - } - end = cloneSourcePosition(property.end); - break; - } - case SyntaxKinds.BracketLeftPunctuator: { - nextToken(); - property = parseExpressionAllowIn(); - isComputed = true; - ({ end } = expect(SyntaxKinds.BracketRightPunctuator)); - break; - } - default: - throw createUnexpectError(); - } - return Factory.createMemberExpression( - isComputed, - Factory.createSuper(keywordStart, keywordEnd), - property, - false, - cloneSourcePosition(keywordStart), - end, - ); - } - /** - * Parse this expression, only eat `this` token - * @returns {ThisExpression} - */ - function parseThisExpression(): ThisExpression { - const { start, end } = expect([SyntaxKinds.ThisKeyword]); - return Factory.createThisExpression(start, end); - } - /** - * Parse ObjectLiterial, object property just a list of PropertyDefinition. - * ### Trailing Comma problem - * object expression maybe transform into `ObjectPattern` in `AssignmentExpression` - * so i add a trailing comma field to object expression to AST struct for `toAssignment` - * function. - * ``` - * ObjectLiteral := '{' PropertyDefinitionList ','? '}' - * PropertyDefinitionList := PropertyDefinitionList ',' PropertyDefinition - * := PropertyDefinition - * ``` - * @returns {Expression} actually is `ObjectExpression` - */ - function parseObjectExpression(): Expression { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - let isStart = true; - const propertyDefinitionList: Array = []; - let trailingComma = false; - const protoPropertyNames: Array = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - propertyDefinitionList.push(parsePropertyDefinition(protoPropertyNames)); - isStart = false; - continue; - } - expect(SyntaxKinds.CommaToken); - if (match(SyntaxKinds.BracesRightPunctuator) || match(SyntaxKinds.EOFToken)) { - trailingComma = true; - break; - } - propertyDefinitionList.push(parsePropertyDefinition(protoPropertyNames)); - } - staticSematicEarlyErrorForObjectExpression(protoPropertyNames); - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createObjectExpression(propertyDefinitionList, trailingComma, start, end); - } - /** - * Adding `__proto__` property key to duplication set, if object expression transform to pattern - * duplication of `__proto__` is ok, but when is not pattern, it not a correct syntax. - * @param {Array} protoPropertyNames - * reference: 13.2.5.1 - */ - function staticSematicEarlyErrorForObjectExpression(protoPropertyNames: Array) { - if (protoPropertyNames.length > 1) { - for (let index = 1; index < protoPropertyNames.length; ++index) - context.propertiesProtoDuplicateSet.add(protoPropertyNames[index]); - } - } - /** - * Helper for property definition to record the object property which property is - * `__proto__`, since duplication of `__proto__` is a error. - * @param protoPropertyNames - * @param propertyName - * @param isComputed - * @returns - */ - function staticSematicHelperRecordPropertyNameForEarlyError( - protoPropertyNames: Array, - propertyName: PropertyName, - isComputed: boolean, - ) { - if (isComputed) return; - if ( - (isIdentifer(propertyName) && propertyName.name === "__proto__") || - (isStringLiteral(propertyName) && propertyName.value === "__proto__") - ) { - protoPropertyNames.push(propertyName); - return; - } - } - /** - * Parse PropertyDefinition - * ``` - * PropertyDefinition := MethodDefintion - * := Property - * := SpreadElement - * Property := PropertyName '=' AssignmentExpression - * SpreadElement := '...' AssigmentExpression - * ``` - * ### How to parse - * 1. start with `...` operator, must be SpreadElment - * 2. start with privatename is syntax error, but it is common, so we handle it as sematic problem. - * 3. check is start with method modifier prefix by helper function `checkIsMethodStartWithModifier`. - * 4. default case, property name with colon operator. - * 5. this is speical case, we accept a coverinit in object expression, because object expression - * might be transform into object pattern, so we mark accept it and mark it. it coverInit of - * object expression is not transform by `toAssignment` function, it would throw error in the - * end of `parseProgram` - * #### ref: https://tc39.es/ecma262/#prod-PropertyDefinition - */ - function parsePropertyDefinition(protoPropertyNameLocations: Array): PropertyDefinition { - // semantics check for private - if (match(SyntaxKinds.PrivateName)) { - // TODO: make it recoverable - throw createMessageError(ErrorMessageMap.extra_error_private_field_in_object_expression); - } - // spreadElement - if (match(SyntaxKinds.SpreadOperator)) { - const spreadElementStart = getStartPosition(); - nextToken(); - const expr = parseAssignmentExpressionAllowIn(); - return Factory.createSpreadElement(expr, spreadElementStart, cloneSourcePosition(expr.end)); - } - // start with possible method modifier - if (checkIsMethodStartWithModifier()) { - return parseMethodDefintion() as ObjectMethodDefinition; - } - // otherwise, it would be Property start with PropertyName or MethodDeinftion start with PropertyName - const isComputedRef = { isComputed: false }; - const propertyName = parsePropertyName(isComputedRef); - if (match(SyntaxKinds.ParenthesesLeftPunctuator)) { - return parseMethodDefintion(false, [propertyName, isComputedRef.isComputed]) as ObjectMethodDefinition; - } - if (isComputedRef.isComputed || match(SyntaxKinds.ColonPunctuator)) { - staticSematicHelperRecordPropertyNameForEarlyError( - protoPropertyNameLocations, - propertyName, - isComputedRef.isComputed, - ); - nextToken(); - const expr = parseAssignmentExpressionAllowIn(); - return Factory.createObjectProperty( - propertyName, - expr, - isComputedRef.isComputed, - false, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(expr.end), - ); - } - recordIdentifierValue(propertyName); - if (match(SyntaxKinds.AssginOperator)) { - staticSematicHelperRecordPropertyNameForEarlyError( - protoPropertyNameLocations, - propertyName, - isComputedRef.isComputed, - ); - nextToken(); - const expr = parseAssignmentExpressionAllowIn(); - const property = Factory.createObjectProperty( - propertyName, - expr, - isComputedRef.isComputed, - false, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(expr.end), - ); - context.propertiesInitSet.add(property); - return property; - } - staticSematicForShortedPropertyNameInObjectLike(propertyName); - staticSematicForShortedPropertyNameInObjectExpression(propertyName as Identifier); - return Factory.createObjectProperty( - propertyName, - undefined, - isComputedRef.isComputed, - true, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(propertyName.end), - ); - } - function recordIdentifierValue(propertyName: ModuleItem) { - if (isIdentifer(propertyName)) { - if (propertyName.name === "await") { - recordScope(ExpressionScopeKind.AwaitIdentifier, propertyName.start); - } - if (propertyName.name === "yield") { - recordScope(ExpressionScopeKind.YieldIdentifier, propertyName.start); - } - if (propertyName.name === "arguments") { - recordScope(ExpressionScopeKind.ArgumentsIdentifier, propertyName.start); - } - if (propertyName.name === "eval") { - recordScope(ExpressionScopeKind.EvalIdentifier, propertyName.start); - } - if (propertyName.name === "let") { - recordScope(ExpressionScopeKind.LetIdentifiier, propertyName.start); - } - if (PreserveWordSet.has(propertyName.name)) { - recordScope(ExpressionScopeKind.PresveredWordIdentifier, propertyName.start); - } - } - } - /** - * Parse PropertyName, using context ref which passed in to record this property is computed or not. - * - * ### Extra action need for callee - * In this function, we accept keywrod as property name, but when the property name use as a shorted - * property name, it will be a syntax error, so father syntax check is needed handle by callee. - * ``` - * PropertyName := Identifer (IdentifierName, not BindingIdentifier) - * := NumberLiteral - * := StringLiteral - * := ComputedPropertyName - * ComputedPropertyName := '[' AssignmentExpression ']' - * ``` - * ref: https://tc39.es/ecma262/#prod-PropertyName - * @returns {PropertyName} - */ - function parsePropertyName(isComputedRef: { isComputed: boolean }): PropertyName { - expectButNotEat([ - SyntaxKinds.BracketLeftPunctuator, - SyntaxKinds.StringLiteral, - ...IdentiferWithKeyworArray, - ...NumericLiteralKinds, - ]); - switch (getToken()) { - case SyntaxKinds.StringLiteral: { - return parseStringLiteral(); - } - case SyntaxKinds.BracketLeftPunctuator: { - nextToken(); - lexicalScopeRecorder.enterPropertyName(); - const expr = parseAssignmentExpressionAllowIn(); - lexicalScopeRecorder.exitPropertyName(); - expect(SyntaxKinds.BracketRightPunctuator); - isComputedRef.isComputed = true; - return expr; - } - default: { - if (match(NumericLiteralKinds)) { - return parseNumericLiteral(); - } - // propty name is a spical test of binding identifier. - // if `await` and `yield` is propty name with colon (means assign), it dose not affected by scope. - if (match(IdentiferWithKeyworArray)) { - const identifer = parseIdentifierName(); - return identifer; - } - throw createUnexpectError(); - } - } - } - /** - * Sematic check when a property name is shorted property - * @param {PropertyName} propertyName - * @returns - */ - function staticSematicForShortedPropertyNameInObjectLike(propertyName: PropertyName) { - if (isStringLiteral(propertyName) || isNumnerLiteral(propertyName)) { - // recoverable error. - raiseError( - ErrorMessageMap.extra_error_when_binding_pattern_property_name_is_literal_can_not_be_shorted, - propertyName.start, - ); - } - } - /** - * Like `staticCheckForPropertyNameAsSingleBinding` for object pattern, when shorted property in - * object expression, if will no longer just - * @param {PropertyName} propertyName - * @returns - */ - function staticSematicForShortedPropertyNameInObjectExpression(propertyName: Identifier) { - if (propertyName.name === "await") { - if (isCurrentScopeParseAwaitAsExpression() || config.sourceType === "module") { - raiseError( - ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, - propertyName.start, - ); - } - return; - } - if (propertyName.name === "yield") { - if (isCurrentScopeParseYieldAsExpression() || isInStrictMode()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, propertyName.start); - } - return; - } - if (KeywordSet.has(propertyName.name)) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); - } - if (PreserveWordSet.has(propertyName.name) && isInStrictMode()) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_reserved_word, propertyName.start); - } - } - /** Parse MethodDefintion, this method should allow using when in class or in object literal. - * 1. ClassElement can be PrivateName, when it used in object literal, it should throw a error. - * 2. It should parse modifier when `withPropertyName` is falsey. - * - * ### Parse Modifier - * we parse modifier according to the pattern `('set' | 'get')? 'async' '*' ClassElement `, this - * is not a regulat syntax, it may accept wrong syntax, but by accept more case then spec, we cam - * provide more concies sematic message to developer. - * ``` - * MethodDefintion := ClassElementName BindingList FunctionBody - * := AyncMethod - * := GeneratorMethod - * := AsyncGeneratorMethod - * := 'set' ClassElementName BindingList FunctionBody - * := 'get' ClassElementName '('')' FunctionBody - * AyncMethod := 'async' ClassElementName BindingList FunctionBody - * GeneratorMethod := '*' ClassElementName BindingList FunctionBody - * AsyncGeneratorMethod := 'async' '*' ClassElementName BindingList FunctionBody - * ClassElementName := PropertyName - * := PrivateName - * ``` - * @param {boolean} inClass is used in class or not. - * @param {PropertyName | PrivateName | undefined } withPropertyName parse methodDeinfition with exited propertyName or not - * @param {boolean} isStatic - * @returns {ObjectMethodDefinition | ClassMethodDefinition | ObjectAccessor | ClassAccessor | ClassConstructor} - */ - function parseMethodDefintion( - inClass: boolean = false, - withPropertyName: [PropertyName | PrivateName, boolean] | undefined = undefined, - isStatic: boolean = false, - decorators: [Decorator] | null = null, - ): ObjectMethodDefinition | ClassMethodDefinition | ObjectAccessor | ClassAccessor | ClassConstructor { - if (!checkIsMethodStartWithModifier() && !withPropertyName) { - throw createUnreachError([SyntaxKinds.MultiplyAssignOperator, SyntaxKinds.Identifier]); - } - /** - * Step 1 : if not with propertyName , parse modifier frist, otherwise, if with propertyName, it shouldn't do anything. - * structure would be like : ('set' | 'get')? 'async' '*' PropertyName ...., this strcuture isn't match the spec. - * but in this structure, we can detect some syntax error more concies, like set and get can not use with async - * or generator. - */ - let type: MethodDefinition["type"] = "method"; - let isAsync: MethodDefinition["async"] = false; - let generator: MethodDefinition["generator"] = false; - let computed: MethodDefinition["computed"] = withPropertyName ? withPropertyName[1] : false; - let start: SourcePosition | null = null; - let propertyName: PropertyName; - if (!withPropertyName) { - // frist, is setter or getter - if (isContextKeyword("set")) { - type = "set"; - start = getStartPosition(); - nextToken(); - } else if (isContextKeyword("get")) { - type = "get"; - start = getStartPosition(); - nextToken(); - } - // second, parser async and generator - const { kind } = lookahead(); - if (isContextKeyword("async") && kind !== SyntaxKinds.ParenthesesLeftPunctuator) { - start = getStartPosition(); - isAsync = true; - nextToken(); - if (match(SyntaxKinds.MultiplyOperator)) { - nextToken(); - generator = true; - } - } else if (match(SyntaxKinds.MultiplyOperator)) { - start = getStartPosition(); - generator = true; - nextToken(); - } - if (match(SyntaxKinds.PrivateName)) { - propertyName = parsePrivateName(); - defPrivateName( - propertyName.name, - propertyName.start, - type === "method" ? "other" : isStatic ? `static-${type}` : type, - ); - } else { - const isComputedRef = { isComputed: false }; - propertyName = parsePropertyName(isComputedRef); - computed = isComputedRef.isComputed; - } - if (!start) start = cloneSourcePosition(propertyName.start); - } else { - start = cloneSourcePosition(withPropertyName[0].start); - propertyName = withPropertyName[0]; - } - const isCtor = inClass && !isStatic && !computed && helperIsPropertyNameIsCtor(propertyName); - if (isCtor) { - lexicalScopeRecorder.enterCtor(); - if (lexicalScopeRecorder.testAndSetCtor()) { - raiseError(ErrorMessageMap.v8_error_a_class_may_only_have_one_constructor, propertyName.start); - } - } - const typeParameters = tryParseTSTypeParameterDeclaration(false); - enterFunctionScope(isAsync, generator); - const [parmas, scope] = parseWithCatpureLayer(parseFunctionParam); - const returnType = tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); - const body = parseFunctionBody(); - postStaticSematicEarlyErrorForStrictModeOfFunction(null, scope); - exitFunctionScope(true); - if (isCtor) lexicalScopeRecorder.exitCtor(); - /** - * Step 2: semantic and more concise syntax check instead just throw a unexpect - * token error. - */ - staticSematicEarlyErrorForClassMethodDefinition( - propertyName, - inClass, - isStatic, - isAsync, - generator, - parmas, - type, - ); - /** - * Step 3 return based on type, if accessor or methodDefintion - */ - if (inClass) { - if (isCtor) { - if (decorators) { - raiseError( - ErrorMessageMap.babel_error_decorators_can_not_be_used_with_a_constructor, - decorators[0].start, - ); - } - return Factory.createClassConstructor( - propertyName as ClassConstructor["key"], - body, - parmas, - returnType, - start as SourcePosition, - cloneSourcePosition(body.end), - ); - } - if (type === "set" || type === "get") { - return Factory.createClassAccessor( - propertyName, - body, - parmas, - typeParameters, - returnType, - type, - computed, - decorators, - start as SourcePosition, - cloneSourcePosition(body.end), - ); - } - return Factory.createClassMethodDefintion( - propertyName, - body, - parmas, - typeParameters, - returnType, - isAsync, - generator, - computed, - isStatic, - decorators, - start ? start : cloneSourcePosition(propertyName.start), - cloneSourcePosition(body.end), - ); - } - if (type === "set" || type === "get") { - return Factory.createObjectAccessor( - propertyName, - body, - parmas, - typeParameters, - returnType, - type, - computed, - start as SourcePosition, - cloneSourcePosition(body.end), - ); - } - return Factory.createObjectMethodDefintion( - propertyName, - body, - parmas, - typeParameters, - returnType, - isAsync, - generator, - computed, - start ? start : cloneSourcePosition(propertyName.start), - cloneSourcePosition(body.end), - ); - } - /** - * This is a helper function for object expression and class for determiate is property - * a method definition or not. - * - * Please notes that this function not only accept regualer syntax, but also accept something - * like set and get generator, it will left sematic job for `parseMetodDefinition` method. - * @returns {boolean} - */ - function checkIsMethodStartWithModifier(): boolean { - if (match(SyntaxKinds.MultiplyOperator)) { - return true; - } - const { kind, lineTerminatorFlag: flag } = lookahead(); - const isLookAheadValidatePropertyNameStart = - Keywords.find((keyword) => keyword === kind) || - kind === SyntaxKinds.Identifier || - kind === SyntaxKinds.PrivateName || - kind === SyntaxKinds.StringLiteral || - NumericLiteralKinds.includes(kind) || - kind === SyntaxKinds.BracketLeftPunctuator || - kind === SyntaxKinds.MultiplyOperator; - if (isContextKeyword("set") && isLookAheadValidatePropertyNameStart) { - return true; - } - if (isContextKeyword("get") && isLookAheadValidatePropertyNameStart) { - return true; - } - if (isContextKeyword("async") && isLookAheadValidatePropertyNameStart && !flag) { - return true; - } - return false; - } - function helperIsPropertyNameIsCtor(propertyName: PropertyName) { - switch (propertyName.kind) { - case SyntaxKinds.Identifier: { - return propertyName.name === "constructor"; - } - case SyntaxKinds.StringLiteral: { - return propertyName.value === "constructor"; - } - default: { - return false; - } - } - } - /** - * Spec def of class method, only implement some of spec. - * @param propertyName - * @param isClass - * @param isStatic - * @param isAsync - * @param isGenerator - * @param params - * @param type - * reference: https://tc39.es/ecma262/#sec-class-definitions-static-semantics-early-errors - */ - function staticSematicEarlyErrorForClassMethodDefinition( - propertyName: PropertyName, - isClass: boolean, - isStatic: boolean, - isAsync: boolean, - isGenerator: boolean, - params: Array, - type: MethodDefinition["type"], - ) { - // general check - if (type === "get" && params.length > 0) { - raiseError(ErrorMessageMap.syntax_error_getter_functions_must_have_no_arguments, propertyName.start); - } - if (type === "set") { - if (params.length !== 1) { - raiseError(ErrorMessageMap.syntax_error_setter_functions_must_have_one_argument, params[0].start); - } - for (const param of params) { - if (isRestElement(param)) { - raiseError( - ErrorMessageMap.syntax_error_setter_functions_must_have_one_argument_not_rest, - param.start, - ); - } - } - } - if (type === "get" && (isAsync || isGenerator)) { - raiseError(ErrorMessageMap.extra_error_getter_can_not_be_async_or_generator, propertyName.start); - } - if (type === "set" && (isAsync || isGenerator)) { - raiseError(ErrorMessageMap.extra_error_setter_can_not_be_async_or_generator, propertyName.start); - } - // class check - if (isClass) { - let valueOfName: string | undefined, - isPrivate = false, - fromLiteral = false; // - if (isStringLiteral(propertyName)) { - valueOfName = propertyName.value; - fromLiteral = true; - } else if (isIdentifer(propertyName)) { - valueOfName = propertyName.name; - } else if (isPrivateName(propertyName)) { - valueOfName = propertyName.name; - isPrivate = true; - } - if (valueOfName === "constructor" && !fromLiteral) { - if (isAsync) { - raiseError( - ErrorMessageMap.v8_error_class_constructor_may_not_be_an_async_method, - propertyName.start, - ); - } - if (isGenerator) { - raiseError(ErrorMessageMap.v8_error_class_constructor_may_not_be_a_generator, propertyName.start); - } - if (type === "get" || type === "set") { - raiseError(ErrorMessageMap.v8_error_class_constructor_may_not_be_an_accessor, propertyName.start); - } - if (isPrivate) { - raiseError( - ErrorMessageMap.v8_error_class_may_not_have_a_private_field_named_constructor, - propertyName.start, - ); - } - } - if (valueOfName === "prototype" && !isPrivate && type === "method" && isStatic) { - raiseError( - ErrorMessageMap.v8_error_class_may_not_have_static_property_named_prototype, - propertyName.start, - ); - } - } - } - function parseArrayExpression() { - const { start } = expect(SyntaxKinds.BracketLeftPunctuator); - const elements: Array = []; - let tralingComma = false; - let isStart = true; - while (!match(SyntaxKinds.BracketRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - if (match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { - tralingComma = true; - break; - } - if (match(SyntaxKinds.CommaToken)) { - elements.push(null); - continue; - } - if (match(SyntaxKinds.SpreadOperator)) { - const start = getStartPosition(); - nextToken(); - const expr = parseAssignmentExpressionAllowIn(); - elements.push(Factory.createSpreadElement(expr, start, cloneSourcePosition(expr.end))); - } else { - const expr = parseAssignmentExpressionAllowIn(); - elements.push(expr); - } - } - const { end } = expect(SyntaxKinds.BracketRightPunctuator); - return Factory.createArrayExpression(elements, start, end, tralingComma); - } - function parseFunctionExpression(isAsync: boolean) { - enterFunctionScope(isAsync); - const funcExpr = parseFunction(true); - exitFunctionScope(false); - return Factory.transFormFunctionToFunctionExpression(funcExpr); - } - function parseClassExpression(decoratorList: Decorator[] | null) { - return Factory.transFormClassToClassExpression(parseClass(decoratorList)); - } - function parseCoverExpressionORArrowFunction() { - const possibleBeArrow = canParseAsArrowFunction(); - expectButNotEat(SyntaxKinds.ParenthesesLeftPunctuator); - const [[{ start, end, nodes, trailingComma, typeAnnotations }, strictModeScope], arrowExprScope] = - parseWithArrowExpressionScope(() => parseWithCatpureLayer(parseArgumentsWithType)); - const returnType = tryParseTSReturnTypeOrTypePredicateForArrowExpression(possibleBeArrow, nodes); - const notArrowExpression = !possibleBeArrow || !match(SyntaxKinds.ArrowOperator); - - if (notArrowExpression) { - // transfor to sequence or signal expression - for (const element of nodes) { - if (isSpreadElement(element)) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_rest_element_invalid, element.start); - } - } - if (trailingComma) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_sequence_expression_can_not_have_trailing_comma, end); - } - if (nodes.length === 1) { - nodes[0].parentheses = true; - return nodes[0]; - } - if (nodes.length === 0) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_empty_parentheses_expression, start); - } - const seq = Factory.createSequenceExpression(nodes, start, end); - seq.parentheses = true; - return seq; - } - enterArrowFunctionBodyScope(); - const arrowExpr = parseArrowFunctionExpression( - { start, end, nodes, trailingComma, typeAnnotations }, - undefined, - strictModeScope, - arrowExprScope, - ); - exitArrowFunctionBodyScope(); - arrowExpr.returnType = returnType; - return arrowExpr; - } - function tryParseTSReturnTypeOrTypePredicateForArrowExpression( - possibleBeArrow: boolean, - functionArguments: Expression[], - ) { - let returnType: undefined | ArrorFunctionExpression["returnType"] = undefined; - if ( - possibleBeArrow && - simpleCheckIsArgumentCanBeAssignable(functionArguments) && - match(SyntaxKinds.ColonPunctuator) && - requirePlugin(ParserPlugin.TypeScript) - ) { - const result = tryParse(() => parseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator)); - if (!match(SyntaxKinds.ArrowOperator)) { - if (!result) throw createUnreachError(); - lexer.restoreState(result[1], result[2]); - errorhandler.restoreTryFail(result[3]); - } else { - returnType = result?.[0]; - } - } - return returnType; - } - function simpleCheckIsArgumentCanBeAssignable(functionArguments: Array) { - for (const argu of functionArguments) { - switch (argu.kind) { - case SyntaxKinds.ObjectExpression: - case SyntaxKinds.ArrayExpression: - case SyntaxKinds.Identifier: - case SyntaxKinds.SpreadElement: - continue; - case SyntaxKinds.AssigmentExpression: - if (argu.operator === SyntaxKinds.AssginOperator) { - continue; - } - return false; - default: - return false; - } - } - return true; - } - /** - * Parse arrow function expression, by given argumentlist and meta data, include - * - start of `(` - * - end of `)`, - * - is trailing comma of argument list - * please notes that this function accept with arguments, not paramemter list, so we need to - * transform arguments to parameter list, so we need to call `toAssignment` for each argument. - * and we also need to check is parameter duplicate. - * - * @param {ASTArrayWithMetaData & { trailingComma: boolean }} metaData - * @returns {ArrorFunctionExpression} - */ - function parseArrowFunctionExpression( - metaData: ASTArrayWithMetaData & { - trailingComma: boolean; - typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; - }, - typeParameters: ArrorFunctionExpression["typeParameters"], - strictModeScope: StrictModeScope, - arrowExprScope: AsyncArrowExpressionScope, - ): ArrorFunctionExpression { - if (getLineTerminatorFlag()) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_no_line_break_is_allowed_before_arrow, getStartPosition()); - } - expect(SyntaxKinds.ArrowOperator); - const functionArguments = argumentToFunctionParams( - metaData.nodes, - metaData.trailingComma, - strictModeScope, - arrowExprScope, - ); - let body: Expression | FunctionBody; - let isExpression = false; - if (match(SyntaxKinds.BracesLeftPunctuator)) { - body = parseFunctionBody(); - } else { - body = parseAssignmentExpressionInheritIn(); - isExpression = true; - } - postStaticSematicEarlyErrorForStrictModeOfFunction(null, strictModeScope); - attachTypeToPattern(functionArguments, metaData.typeAnnotations); - return Factory.createArrowExpression( - isExpression, - body, - functionArguments, - typeParameters, - undefined, - isCurrentScopeParseAwaitAsExpression(), - cloneSourcePosition(metaData.start), - cloneSourcePosition(body.end), - ); - } - function attachTypeToPattern( - patterns: Array, - typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined, - ) { - if (!typeAnnotations || !requirePlugin(ParserPlugin.TypeScript)) { - return; - } - let index = 0; - for (const pat of patterns) { - if (isAssignmentPattern(pat)) { - const tsPat = pat.left as TSParameter; - tsPat.typeAnnotation = typeAnnotations[index][0]; - tsPat.optional = typeAnnotations[index][1]; - continue; - } - (pat as TSParameter).typeAnnotation = typeAnnotations[index][0]; - (pat as TSParameter).optional = typeAnnotations[index][1]; - index++; - } - } - /** - * Transform function from expressions to patterns (arguments to params), checking syntax error - * by expression scope and post statci sematic check for pattern rule. - * - asycn arrow scope: check await expression, yield expression - * - strict mode scope: expression Rval to Lval has different rule in strict mode - * - multi spread vs multi rest: rest is unique, spread can be multi. - * @param functionArguments - * @param trailingComma - * @param strictModeScope - * @param arrowExprScope - * @returns - */ - function argumentToFunctionParams( - functionArguments: Array, - trailingComma: boolean, - strictModeScope: StrictModeScope, - arrowExprScope: AsyncArrowExpressionScope, - ): Array { - const params = functionArguments.map((node) => exprToPattern(node, true)) as Array; - if (isCurrentScopeParseAwaitAsExpression() || isParentFunctionAsync() || isParentFunctionGenerator()) { - checkAsyncArrowExprScopeError(arrowExprScope); - } - if (isInStrictMode()) { - checkStrictModeScopeError(strictModeScope); - } - const isMultiSpread = postStaticSematicForArrowParamAfterTransform(params); - if (isMultiSpread && trailingComma) - // recoverable error - raiseError( - ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, - lexer.getLastTokenEndPositon(), - ); - // check as function params - setContextIfParamsIsSimpleParameterList(params); - return params; - } - function postStaticSematicForArrowParamAfterTransform(params: Array) { - let flag = false; - params.forEach((param) => { - if (flag && isRestElement(param)) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, param.start); - return; - } - if (flag) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, param.start); - return; - } - if (!flag && isRestElement(param)) { - flag = true; - } - }); - return flag; - } - /** ================================================================================ - * Parse JSX - * entry point: https://facebook.github.io/jsx/ - * ================================================================================== - */ - /** - * Parse JSX Element or JSX Fragment - * ``` - * PrimaryExpression := JSXElement - * := JSXFragment - * ``` - */ - function parseJSXElementOrJSXFragment(inJSXChildren: boolean): JSXElement | JSXFragment { - if (!requirePlugin(ParserPlugin.JSX)) { - raiseError(ErrorMessageMap.babel_error_need_enable_jsx, getStartPosition()); - } - const lookaheadToken = lookahead(); - if (lookaheadToken.kind !== SyntaxKinds.GtOperator) { - return parseJSXElement(inJSXChildren); - } else { - return parseJSXFragment(inJSXChildren); - } - } - /** - * Parse JSX Element - * ``` - * JSXElement := JSXOpeningElement JSXChildren JSXClosingElement - * := JSXOpeningElement - * ``` - * @returns {JSXElement} - */ - function parseJSXElement(inJSXChildren: boolean): JSXElement { - const opeingElement = parseJSXOpeingElement(inJSXChildren); - if (opeingElement.selfClosing) { - return Factory.createJSXElement( - opeingElement, - null, - [], - cloneSourcePosition(opeingElement.start), - cloneSourcePosition(opeingElement.end), - ); - } - const children = parseJSXChildren(); - const closingElement = parseJSXClosingElement(inJSXChildren); - staticSematicEarlyErrorForJSXElement(opeingElement, closingElement); - return Factory.createJSXElement( - opeingElement, - closingElement, - children, - cloneSourcePosition(opeingElement.start), - cloneSourcePosition(opeingElement.end), - ); - } - function staticSematicEarlyErrorForJSXElement( - openingElement: JSXOpeningElement, - closingElement: JSXClosingElement, - ) { - const openElementSourceText = lexer.getSourceValueByIndex( - openingElement.name.start.index, - openingElement.name.end.index, - ); - const closeElementSourceText = lexer.getSourceValueByIndex( - closingElement.name.start.index, - closingElement.name.end.index, - ); - if (openElementSourceText !== closeElementSourceText) { - throw new Error(); - } - } - /** - * Parse JSXOpeingElement - * ``` - * JSXOpeningElement := `<` JSXElementName JSXAtrributes `>` - * := `<` JSXElementName JSXAtrributes `/>` - * ``` - * @returns {JSXOpeningElement} - */ - function parseJSXOpeingElement(inJSXChildren: boolean): JSXOpeningElement { - const { start } = expect(SyntaxKinds.LtOperator); - const lastLexerJSXEndTagContext = lexer.getJSXGtContext(); - lexer.setJSXGtContext(true); - const name = parseJSXElementName(); - const attributes = parseJSXAttributes(); - lexer.setJSXGtContext(lastLexerJSXEndTagContext); - if (match(SyntaxKinds.GtOperator)) { - const end = getEndPosition(); - nextTokenInJSXChildren(true); - return Factory.createJSXOpeningElement(name, attributes, false, start, end); - } - if (match(SyntaxKinds.JSXSelfClosedToken)) { - const end = getEndPosition(); - nextTokenInJSXChildren(inJSXChildren); - return Factory.createJSXOpeningElement(name, attributes, true, start, end); - } - // for `/ >` - if (match(SyntaxKinds.DivideOperator) && lookahead().kind === SyntaxKinds.GtOperator) { - nextToken(); - const end = getEndPosition(); - nextTokenInJSXChildren(inJSXChildren); - return Factory.createJSXOpeningElement(name, attributes, true, start, end); - } - throw createUnexpectError(); - } - /** - * Parse name of jsx element or jsx fragment - * ``` - * JSXElementName := JSXIdentifier - * := JSXMemberExpression - * := JSXNamespaceName - * ``` - * @returns {JSXIdentifier | JSXMemberExpression | JSXNamespacedName} - */ - function parseJSXElementName(): JSXIdentifier | JSXMemberExpression | JSXNamespacedName { - let name: JSXIdentifier | JSXMemberExpression | JSXNamespacedName = parseJSXIdentifier(); - if (match(SyntaxKinds.ColonPunctuator)) { - nextToken(); - const subName = parseJSXIdentifier(); - name = Factory.createJSXNamespacedName( - name, - subName, - cloneSourcePosition(name.start), - cloneSourcePosition(subName.end), - ); - } else if (match(SyntaxKinds.DotOperator)) { - while (match(SyntaxKinds.DotOperator) && !match(SyntaxKinds.EOFToken)) { - nextToken(); - const property = parseJSXIdentifier(); - name = Factory.createJSXMemberExpression( - name, - property, - cloneSourcePosition(name.start), - cloneSourcePosition(property.end), - ); - } - } - return name; - } - /** - * Parse JSX Attributes. - * ``` - * JSXAttributes := JSXAttributes JSXAttribute - * := JSXAttributes JSXSpreadAttribute - * := JSXAttribute - * := JSXSpreadAttribute - * JSXAttribute := JSXAttributeName '=' StringLiteral - * := JSXAttributeName '=' JSXExpressionContainer (expression can not be null) - * := JSXAttributeName '=' JSXElement - * := JSxAttributeName '=' JSXFragment - * := JSXAttrbuteName - * JSXSpreadAttribute := '{''...' AssignmentExpression '}' - * JSXAttributeName := JSXIdentifier - * := JSXNamespaceName - * ``` - * @returns {Array} - */ - function parseJSXAttributes(): Array { - const attribute: Array = []; - while ( - !match(SyntaxKinds.EOFToken) && - !match(SyntaxKinds.GtOperator) && - !match(SyntaxKinds.JSXSelfClosedToken) && - !(match(SyntaxKinds.DivideOperator) && lookahead().kind === SyntaxKinds.GtOperator) - ) { - // parse spread - if (match(SyntaxKinds.BracesLeftPunctuator)) { - nextToken(); - expect(SyntaxKinds.SpreadOperator); - const expression = parseAssignmentExpressionAllowIn(); - expect(SyntaxKinds.BracesRightPunctuator); - attribute.push( - Factory.createJSXSpreadAttribute( - expression, - cloneSourcePosition(expression.start), - cloneSourcePosition(expression.end), - ), - ); - continue; - } - // parse name - let name: JSXIdentifier | JSXNamespacedName = parseJSXIdentifier(); - if (match(SyntaxKinds.ColonPunctuator)) { - nextToken(); - const subName = parseJSXIdentifier(); - name = Factory.createJSXNamespacedName( - name, - subName, - cloneSourcePosition(name.start), - cloneSourcePosition(subName.end), - ); - } - // parse value - if (match(SyntaxKinds.AssginOperator)) { - lexer.setJSXStringContext(true); - nextToken(); - lexer.setJSXStringContext(false); - if (match(SyntaxKinds.StringLiteral)) { - const value = parseStringLiteral(); - attribute.push( - Factory.createJSXAttribute( - name, - value, - cloneSourcePosition(name.start), - cloneSourcePosition(value.end), - ), - ); - continue; - } - if (match(SyntaxKinds.BracesLeftPunctuator)) { - const expression = parseJSXExpressionContainer(false); - if (!expression.expression) { - throw new Error("right hand side of jsx attribute must have expression if start with `{`"); - } - attribute.push( - Factory.createJSXAttribute( - name, - expression, - cloneSourcePosition(name.start), - cloneSourcePosition(expression.end), - ), - ); - continue; - } - const element = parseJSXElementOrJSXFragment(false); - attribute.push( - Factory.createJSXAttribute( - name, - element, - cloneSourcePosition(name.start), - cloneSourcePosition(element.end), - ), - ); - } else { - attribute.push( - Factory.createJSXAttribute( - name, - null, - cloneSourcePosition(name.start), - cloneSourcePosition(name.end), - ), - ); - } - } - return attribute; - } - /** - * Parse JSX Children - * ``` - * JSXChildren := JSXChildren JSXChild - * := JSXChild - * JSXChild := JSXText - * := JSXExpressionContainer - * := JSXElement - * := JSXFragment - * := JSXSpreadChild - * JSXSpreadChild := {'...AssignmentExpression '}' - * ``` - * @returns {Array} - */ - function parseJSXChildren(): JSXElement["children"] { - const children: JSXElement["children"] = []; - while (!match(SyntaxKinds.JSXCloseTagStart) && !match(SyntaxKinds.EOFToken)) { - if (match(SyntaxKinds.LtOperator)) { - children.push(parseJSXElementOrJSXFragment(true)); - continue; - } - if (match(SyntaxKinds.BracesLeftPunctuator)) { - if (lookahead().kind == SyntaxKinds.SpreadOperator) { - expect(SyntaxKinds.BracesLeftPunctuator); - expect(SyntaxKinds.SpreadOperator); - const expression = parseAssignmentExpressionAllowIn(); - expect(SyntaxKinds.BracesRightPunctuator); - children.push( - Factory.createJSXSpreadChild( - expression, - cloneSourcePosition(expression.start), - cloneSourcePosition(expression.end), - ), - ); - continue; - } - children.push(parseJSXExpressionContainer(true)); - continue; - } - children.push(parseJSXText()); - } - return children; - } - /** - * Parse JSX expression container - * ``` - * JSXExpressionContainer = '{' AssignmentExpression '}' - * ``` - * @returns {JSXExpressionContainer} - */ - function parseJSXExpressionContainer(inJSXChildren: boolean): JSXExpressionContainer { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const expression = match(SyntaxKinds.BracesRightPunctuator) ? null : parseAssignmentExpressionAllowIn(); - const { end } = expectInJSXChildren(SyntaxKinds.BracesRightPunctuator, inJSXChildren); - return Factory.createsJSXExpressionContainer(expression, start, end); - } - /** - * Parse Closing Element of JSXElement - * ``` - * JSXClosingElement := '' - * ``` - * @returns {JSXClosingElement} - */ - function parseJSXClosingElement(inJSXChildren: boolean): JSXClosingElement { - const { start } = expect(SyntaxKinds.JSXCloseTagStart); - const lastLexerJSXEndTagContext = lexer.getJSXGtContext(); - lexer.setJSXGtContext(true); - const name = parseJSXElementName(); - const { end } = expectInJSXChildren(SyntaxKinds.GtOperator, inJSXChildren); - lexer.setJSXGtContext(lastLexerJSXEndTagContext); - return Factory.createJSXClosingElement(name, start, end); - } - /** - * - * @returns {JSXIdentifier} - */ - function parseJSXIdentifier(): JSXIdentifier { - // eslint-disable-next-line prefer-const - let { start, end } = expect(IdentiferWithKeyworArray); - // eslint-disable-next-line no-constant-condition - while (1) { - if (match(SyntaxKinds.MinusOperator)) { - end = getEndPosition(); - nextToken(); - } else { - break; - } - if (match(IdentiferWithKeyworArray)) { - end = getEndPosition(); - nextToken(); - } else { - break; - } - } - const value = lexer.getSourceValueByIndex(start.index, end.index); - return Factory.createJSXIdentifier(value, start, end); - } - function parseJSXText() { - const { start, end, value } = expect(SyntaxKinds.JSXText); - return Factory.createJSXText(value, start, end); - } - /** - * Parse JSXFragment - * ``` - * JSXFragment := `<``/>` JSXChildern `` - * ``` - * @returns {JSXFragment} - */ - function parseJSXFragment(inJSXChildren: boolean): JSXFragment { - const { start: openingStart } = expect(SyntaxKinds.LtOperator); - const { end: openingEnd } = expectInJSXChildren(SyntaxKinds.GtOperator, true); - const children = parseJSXChildren(); - const { start: closingStart } = expect(SyntaxKinds.JSXCloseTagStart); - const { end: closingEnd } = expectInJSXChildren(SyntaxKinds.GtOperator, inJSXChildren); - return Factory.createJSXFragment( - Factory.createJSXOpeningFragment(openingStart, openingEnd), - Factory.createJSXClosingFragment(closingStart, closingEnd), - children, - cloneSourcePosition(openingStart), - cloneSourcePosition(closingEnd), - ); - } - function expectInJSXChildren(kind: SyntaxKinds, inJSXChildren: boolean) { - if (match(kind)) { - const metaData = { - value: getSourceValue(), - start: getStartPosition(), - end: getEndPosition(), - }; - if (inJSXChildren) { - lexer.nextTokenInJSXChildrenContext(); - } else { - lexer.nextToken(); - } - return metaData; - } - throw createUnexpectError(); - } - function nextTokenInJSXChildren(inJSXChildren: boolean) { - if (inJSXChildren) { - lexer.nextTokenInJSXChildrenContext(); - } else { - lexer.nextToken(); - } - } - /** ================================================================================ - * Parse Pattern - * entry point: https://tc39.es/ecma262/#sec-destructuring-binding-patterns - * ================================================================================== - */ - /** - * Parse BindingElement - * ``` - * BindingElemet := Identifer ('=' AssigmentExpression)? - * := BindingPattern ('=' AssigmentExpression)? - * ``` - * @returns - */ - function parseBindingElement(shouldParseAssignment = true): Pattern { - expectButNotEat([ - ...IdentiferWithKeyworArray, - SyntaxKinds.BracesLeftPunctuator, - SyntaxKinds.BracketLeftPunctuator, - ]); - let left: Pattern | undefined; - if (match(BindingIdentifierSyntaxKindArray)) { - left = parseBindingIdentifier(); - } else { - left = parseBindingPattern(); - } - if (match(SyntaxKinds.AssginOperator) && shouldParseAssignment) { - return parseDefaultValueForBindingElement(left); - } - return left; - } - function parseDefaultValueForBindingElement(left: Pattern) { - expect(SyntaxKinds.AssginOperator); - const right = parseWithRHSLayer(parseAssignmentExpressionAllowIn); - return Factory.createAssignmentPattern( - left, - right, - undefined, - undefined, - cloneSourcePosition(left.start), - cloneSourcePosition(right.end), - ); - } - function parseRestElement(allowPattern: boolean): RestElement { - const { start } = expect([SyntaxKinds.SpreadOperator]); - let id: Pattern | null = null; - if (match(BindingIdentifierSyntaxKindArray)) { - id = parseBindingIdentifier(); - } - if (match([SyntaxKinds.BracesLeftPunctuator, SyntaxKinds.BracketLeftPunctuator])) { - if (allowPattern) { - id = parseBindingPattern(); - } - if (!allowPattern) { - // recoverable error ? - throw createUnexpectError(); - } - } - if (!id) { - throw createUnexpectError(); - } - return Factory.createRestElement(id, undefined, undefined, start, cloneSourcePosition(id.end)); - } - function parseBindingIdentifier() { - const id = parseWithLHSLayer(parseIdentifierReference); - declarateNonFunctionalSymbol(id.name, id.start); - return id; - } - /** - * Parse BindingPattern - * ``` - * BindingPattern := ObjectPattern - * := ArrayPattern - * ``` - */ - function parseBindingPattern(): ObjectPattern | ArrayPattern { - return parseWithLHSLayer(() => { - expectButNotEat([SyntaxKinds.BracesLeftPunctuator, SyntaxKinds.BracketLeftPunctuator]); - if (match(SyntaxKinds.BracesLeftPunctuator)) { - return parseObjectPattern(); - } - return parseArrayPattern(); - }); - } - /** Parse Object Pattern - * ``` - * ObjectPattern := '{' ObjectPatternProperties '}' - * := '{' ObjtecPatternProperties ',' '}' - * := '{' ObjectPatternProperties ',' RestElement '}' - * := '{' RestElement '} - * ObjectPatternProperties := ObjectPatternProperties ',' ObjectPatternProperty - * ObjectPatternProperty := Identifer ('=' AssigmentExpression) - * := BindingPattern ('=' AssignmentExpression) - * ``` - * @return {ObjectPattern} - */ - function parseObjectPattern(): ObjectPattern { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - if (match(SyntaxKinds.BracesRightPunctuator)) { - const end = getEndPosition(); - nextToken(); - const objectPattern = Factory.createObjectPattern([], undefined, undefined, start, end); - return objectPattern; - } - const properties: Array = [ - parseObjectPatternPossibelProperty(), - ]; - while (!match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - expect(SyntaxKinds.CommaToken); - if (match(SyntaxKinds.BracesRightPunctuator) || match(SyntaxKinds.EOFToken)) { - continue; - } - properties.push(parseObjectPatternPossibelProperty()); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - const objectPattern = Factory.createObjectPattern(properties, undefined, undefined, start, end); - return objectPattern; - } - function parseObjectPatternPossibelProperty(): ObjectPatternProperty | RestElement | AssignmentPattern { - // parse Rest property - if (match(SyntaxKinds.SpreadOperator)) { - const rest = parseRestElement(false); - staticSematicForRestElementInObjectPattern(); - return rest; - } - // parse Object pattern property (rename) - const isComputedRef = { isComputed: false }; - const propertyName = parsePropertyName(isComputedRef); - if (isComputedRef.isComputed || match(SyntaxKinds.ColonPunctuator)) { - nextToken(); - const pattern = parseBindingElement(); - return Factory.createObjectPatternProperty( - propertyName, - pattern, - isComputedRef.isComputed, - false, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(pattern.end), - ); - } - staticCheckForPropertyNameAsSingleBinding(propertyName); - // parse object pattern as Assignment pattern - if (match(SyntaxKinds.AssginOperator)) { - nextToken(); - const expr = parseWithRHSLayer(parseAssignmentExpressionAllowIn); - staticSematicForAssignmentPatternInObjectPattern(propertyName); - declarateNonFunctionalSymbol((propertyName as Identifier).name, propertyName.start); - return Factory.createAssignmentPattern( - propertyName as Pattern, - expr, - undefined, - undefined, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(expr.end), - ); - } - // parse object pattern as shorted property. - staticSematicForShortedPropertyNameInObjectLike(propertyName); - declarateNonFunctionalSymbol((propertyName as Identifier).name, propertyName.start); - return Factory.createObjectPatternProperty( - propertyName, - undefined, - isComputedRef.isComputed, - true, - cloneSourcePosition(propertyName.start), - cloneSourcePosition(propertyName.end), - ); - } - /** - * In Object Pattern, Rest Element should be last one, and can - * not have trailing comma. - */ - function staticSematicForRestElementInObjectPattern() { - if ( - !match(SyntaxKinds.BracesRightPunctuator) || - (match(SyntaxKinds.CommaToken) && lookahead().kind === SyntaxKinds.BracesRightPunctuator) - ) { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, - getStartPosition(), - ); - } - } - /** - * Ban some usage like `{ "string-key" = "name" }` in onject pattern - * @param propertyName - */ - function staticSematicForAssignmentPatternInObjectPattern(propertyName: PropertyName) { - if (!isPattern(propertyName)) { - throw createMessageError("assignment pattern left value can only allow identifier or pattern"); - } - } - /** - * As for shorted and assignment patern in object pattern, the property name should be - * a bidning identifier, so we need to check and record the property name. - * @param propertyName - * @returns - */ - function staticCheckForPropertyNameAsSingleBinding(propertyName: PropertyName) { - if (isIdentifer(propertyName)) { - switch (propertyName.name) { - case "await": { - if (isCurrentScopeParseAwaitAsExpression() || config.sourceType === "module") { - raiseError( - ErrorMessageMap.babel_error_can_not_use_await_as_identifier_inside_an_async_function, - propertyName.start, - ); - } - return; - } - case "yield": { - if (isCurrentScopeParseYieldAsExpression() || isInStrictMode()) { - raiseError(ErrorMessageMap.babel_error_invalid_yield, propertyName.start); - } - return; - } - case "arguments": { - if (isInStrictMode() && strictModeScopeRecorder.isInLHS()) { - raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, propertyName.start); - } - recordScope(ExpressionScopeKind.ArgumentsIdentifier, propertyName.start); - return; - } - case "eval": { - if (isInStrictMode() && strictModeScopeRecorder.isInLHS()) { - raiseError(ErrorMessageMap.syntax_error_bad_strict_arguments_eval, propertyName.start); - } - recordScope(ExpressionScopeKind.EvalIdentifier, propertyName.start); - return; - } - case "let": { - if (isInStrictMode()) { - raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); - } - recordScope(ExpressionScopeKind.LetIdentifiier, propertyName.start); - return; - } - default: { - if (KeywordSet.has(propertyName.name)) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_keyword, propertyName.start); - } - if (PreserveWordSet.has(propertyName.name) && isInStrictMode()) { - // recoverable error - raiseError(ErrorMessageMap.babel_error_unexpected_reserved_word, propertyName.start); - } - return; - } - } - } - } - function parseArrayPattern(): ArrayPattern { - const { start } = expect(SyntaxKinds.BracketLeftPunctuator); - let isStart = true; - const elements: Array = []; - while (!match(SyntaxKinds.BracketRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - if (match(SyntaxKinds.BracketRightPunctuator) || match(SyntaxKinds.EOFToken)) { - continue; - } - if (match(SyntaxKinds.CommaToken)) { - elements.push(null); - continue; - } - if (match(SyntaxKinds.SpreadOperator)) { - elements.push(parseRestElement(true)); - if (!match(SyntaxKinds.BracketRightPunctuator)) { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_unexpected_trailing_comma_after_rest_element, - getStartPosition(), - ); - } - break; - } - const pattern = parseBindingElement(); - elements.push(pattern); - } - const { end } = expect(SyntaxKinds.BracketRightPunctuator); - const arrayPattern = Factory.createArrayPattern(elements, undefined, undefined, start, end); - return arrayPattern; - } - /** ================================================================================ - * Parse Import Declaration - * entry point: https://tc39.es/ecma262/#sec-imports - * ================================================================================== - */ - function expectFormKeyword() { - if (getSourceValue() !== "from") { - throw createUnexpectError(); - } - if (getEscFlag()) { - raiseError(ErrorMessageMap.invalid_esc_char_in_keyword, getStartPosition()); - } - nextToken(); - } - /** - * Parse Import Declaration - * ``` - * ImportDeclaration := 'import' ImportClasue FromClause WithClause? - * := 'import' StringLiteral WithClause? - * FromClause := 'from' StringLiteral - * ImportClause := ImportDefaultBinding - * := ImportNamesapce - * := ImportNamed - * := ImportDefaultBindling ',' ImportNamed - * := ImportDefaultBindling ',' ImportNamespace - * ``` - * - frist, eat import keyword - * 1. if it is string literal, must be `import StringLiteral` - * 2. if it start with `*`, must be import name space - * 3. if it start with '{', must be import named - * 4. fallback case: default import with import named or import namesspace - * or nothing - * @returns {ImportDeclaration} - */ - function parseImportDeclaration(): ImportDeclaration { - const { start } = expect(SyntaxKinds.ImportKeyword); - if (config.sourceType === "script") { - raiseError(ErrorMessageMap.babel_error_import_and_export_may_appear_only_with_sourceType_module, start); - } - const specifiers: Array = []; - if (match(SyntaxKinds.StringLiteral)) { - const source = parseStringLiteral(); - const attributes = parseImportAttributesOptional(); - shouldInsertSemi(); - return Factory.createImportDeclaration( - specifiers, - source, - attributes, - start, - cloneSourcePosition(source.end), - ); - } - if (match(SyntaxKinds.MultiplyOperator)) { - specifiers.push(parseImportNamespaceSpecifier()); - expectFormKeyword(); - const source = parseStringLiteral(); - const attributes = parseImportAttributesOptional(); - shouldInsertSemi(); - return Factory.createImportDeclaration( - specifiers, - source, - attributes, - start, - cloneSourcePosition(source.end), - ); - } - if (match(SyntaxKinds.BracesLeftPunctuator)) { - parseImportSpecifiers(specifiers); - expectFormKeyword(); - const source = parseStringLiteral(); - const attributes = parseImportAttributesOptional(); - shouldInsertSemi(); - return Factory.createImportDeclaration( - specifiers, - source, - attributes, - start, - cloneSourcePosition(source.end), - ); - } - specifiers.push(parseImportDefaultSpecifier()); - if (match(SyntaxKinds.CommaToken)) { - nextToken(); - if (match(SyntaxKinds.BracesLeftPunctuator)) { - parseImportSpecifiers(specifiers); - } else if (match(SyntaxKinds.MultiplyOperator)) { - specifiers.push(parseImportNamespaceSpecifier()); - } else { - throw createMessageError( - "import default specifier can only concat with namespace of import named specifier", - ); - } - } - expectFormKeyword(); - const source = parseStringLiteral(); - const attributes = parseImportAttributesOptional(); - shouldInsertSemi(); - return Factory.createImportDeclaration( - specifiers, - source, - attributes, - start, - cloneSourcePosition(source.end), - ); - } - /** - * Parse Default import binding - * ``` - * ImportDefaultBinding := Identifer - * ``` - * @returns {ImportDefaultSpecifier} - */ - function parseImportDefaultSpecifier(): ImportDefaultSpecifier { - const name = parseIdentifierReference(); - declarateLetSymbol(name.name, name.start); - return Factory.createImportDefaultSpecifier( - name, - cloneSourcePosition(name.start), - cloneSourcePosition(name.end), - ); - } - /** - * Parse namespace import - * ``` - * ImportNamespace := '*' 'as' Identifer - * ``` - * @returns {ImportNamespaceSpecifier} - */ - function parseImportNamespaceSpecifier(): ImportNamespaceSpecifier { - const { start } = expect(SyntaxKinds.MultiplyOperator); - if (!isContextKeyword("as")) { - raiseError(ErrorMessageMap.babel_error_unexpected_token_expected_as, getStartPosition()); - } - nextToken(); - const id = parseIdentifierReference(); - declarateLetSymbol(id.name, id.start); - return Factory.createImportNamespaceSpecifier(id, start, cloneSourcePosition(id.end)); - } - /** - * Parse Import Nameds - * ``` - * ImportNamed := '{' ImportList ','? '}' - * ImportList := [ ImportItem ] - * ImportItem := IdentiferWithKeyword - * := (Identifer | StringLiteral) 'as' Identifer - * ``` - * @param specifiers - * @return {void} - */ - function parseImportSpecifiers( - specifiers: Array, - ): void { - expect(SyntaxKinds.BracesLeftPunctuator); - let isStart = true; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - if (match(SyntaxKinds.BracesRightPunctuator) || match(SyntaxKinds.EOFToken)) { - break; - } - const imported = parseModuleExportName(); - if (!isContextKeyword("as")) { - if (isIdentifer(imported) && KeywordSet.has(imported.name)) { - // recoverable error - raiseError(ErrorMessageMap.extra_error_unexpect_keyword_in_module_name, imported.start); - } else if (isStringLiteral(imported)) { - // recoverable error - raiseError( - ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_imported_binding, - imported.start, - ); - } - if (isIdentifer(imported)) declarateLetSymbol(imported.name, imported.start); - specifiers.push( - Factory.createImportSpecifier( - imported, - null, - cloneSourcePosition(imported.start), - cloneSourcePosition(imported.end), - ), - ); - continue; - } - nextToken(); - const local = parseIdentifierReference(); - declarateLetSymbol(local.name, local.start); - specifiers.push( - Factory.createImportSpecifier( - imported, - local, - cloneSourcePosition(imported.start), - cloneSourcePosition(local.end), - ), - ); - } - expect(SyntaxKinds.BracesRightPunctuator); - } - function parseImportAttributesOptional(): ImportAttribute[] | undefined { - if ( - (requirePlugin(ParserPlugin.ImportAttribute) && match(SyntaxKinds.WithKeyword)) || - (requirePlugin(ParserPlugin.ImportAssertions) && - match(SyntaxKinds.Identifier) && - getSourceValue() === "assert") - ) { - nextToken(); - return parseImportAttributes(); - } - return undefined; - } - function parseImportAttributes(): ImportAttribute[] { - expect(SyntaxKinds.BracesLeftPunctuator); - const attributes: Array = [parseImportAttribute()]; - while (!match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - expect(SyntaxKinds.CommaToken); - if (match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - break; - } - attributes.push(parseImportAttribute()); - } - expect(SyntaxKinds.BracesRightPunctuator); - return attributes; - } - function parseImportAttribute(): ImportAttribute { - const key = parseIdentifierName(); - expect(SyntaxKinds.ColonPunctuator); - const value = parseStringLiteral(); - return Factory.createImportAttribute( - key, - value, - cloneSourcePosition(key.start), - cloneSourcePosition(value.end), - ); - } - /** ================================================================================ - * Parse Export Declaration - * entry point: https://tc39.es/ecma262/#prod-ExportDeclaration - * ================================================================================== - */ - /** - * Parse Export Declaration - * ``` - * ExportDeclaration := 'export' ExportNamedDeclaration ';'? - * := 'export' ExportDefaultDeclaration - * := 'export' ExportAllDeclaration - * ExportNamedDeclaration := '{' ExportList '}' ('from' StringLiteral)? - * := Declaration - * := VarStatement - * ExportAllDeclaration := '*' 'from' StringLiteral - * := '*' 'as' Identifer 'from' StringLiteral - * ``` - * @returns {ExportDeclaration} - */ - function parseExportDeclaration(): ExportDeclaration { - setExportContext(ExportContext.InExport); - - const { start } = expect(SyntaxKinds.ExportKeyword); - if (config.sourceType === "script") { - raiseError(ErrorMessageMap.babel_error_import_and_export_may_appear_only_with_sourceType_module, start); - } - let exportDeclaration: ExportDeclaration; - switch (getToken()) { - case SyntaxKinds.DefaultKeyword: { - exportDeclaration = parseExportDefaultDeclaration(start); - break; - } - case SyntaxKinds.MultiplyOperator: { - exportDeclaration = parseExportAllDeclaration(start); - break; - } - case SyntaxKinds.BracesLeftPunctuator: { - exportDeclaration = parseExportNamedDeclaration(start); - break; - } - default: { - const declaration = match(SyntaxKinds.VarKeyword) ? parseVariableDeclaration() : parseDeclaration(); - exportDeclaration = Factory.createExportNamedDeclaration( - [], - declaration, - null, - start, - cloneSourcePosition(declaration.end), - ); - break; - } - } - setExportContext(ExportContext.NotInExport); - return exportDeclaration; - } - /** - * Parse default export declaration - * ``` - * ``` - * @param {SourcePosition} start - * @returns {ExportDefaultDeclaration} - */ - function parseExportDefaultDeclaration(start: SourcePosition): ExportDefaultDeclaration { - expect(SyntaxKinds.DefaultKeyword); - switch (getToken()) { - case SyntaxKinds.ClassKeyword: - case SyntaxKinds.AtPunctuator: { - let decoratorList = takeCacheDecorator(); - if (match(SyntaxKinds.AtPunctuator)) { - decoratorList = mergeDecoratorList(decoratorList, parseDecoratorList()); - } - const classDeclar = Factory.transFormClassToClassDeclaration(parseClass(decoratorList)); - staticSematicForDuplicateDefaultExport(classDeclar); - return Factory.createExportDefaultDeclaration( - classDeclar as ClassDeclaration | ClassExpression, - start, - cloneSourcePosition(classDeclar.end), - ); - } - case SyntaxKinds.FunctionKeyword: { - const funcDeclar = parseFunctionDeclaration(false, true); - staticSematicForDuplicateDefaultExport(funcDeclar); - return Factory.createExportDefaultDeclaration(funcDeclar, start, cloneSourcePosition(funcDeclar.end)); - } - default: { - if (isContextKeyword("async") && lookahead().kind === SyntaxKinds.FunctionKeyword) { - nextToken(); - const funcDeclar = parseFunctionDeclaration(true, true); - // funcDeclar.async = true; - staticSematicForDuplicateDefaultExport(funcDeclar); - return Factory.createExportDefaultDeclaration( - funcDeclar, - start, - cloneSourcePosition(funcDeclar.end), - ); - } - const typeDeclar = tryParseDeclarationWithIdentifierStart(); - if (typeDeclar) { - shouldInsertSemi(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return Factory.createExportDefaultDeclaration(typeDeclar as any, start, getLastTokenEndPositon()); - } - // TODO: parse export default from ""; (experimental feature) - const expr = parseAssignmentExpressionAllowIn(); - shouldInsertSemi(); - staticSematicForDuplicateDefaultExport(expr); - return Factory.createExportDefaultDeclaration(expr, start, cloneSourcePosition(expr.end)); - } - } - } - /** - * Using symbol scope recorder for record the default export. - * @param node - */ - function staticSematicForDuplicateDefaultExport(node: ModuleItem) { - const isDefaultAlreadyBeDeclarated = symbolScopeRecorder.testAndSetDefaultExport(node.start); - if (isDefaultAlreadyBeDeclarated) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isDefaultAlreadyBeDeclarated); - } - } - function parseExportNamedDeclaration(start: SourcePosition): ExportNamedDeclarations { - expect(SyntaxKinds.BracesLeftPunctuator); - const specifier: Array = []; - let isStart = true; - let isMatchKeyword = false; - const undefExportSymbols: Array<[string, SourcePosition]> = []; - while (!match(SyntaxKinds.BracesRightPunctuator) && !match(SyntaxKinds.EOFToken)) { - if (isStart) { - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - if (match(SyntaxKinds.BracesRightPunctuator) || match(SyntaxKinds.EOFToken)) { - break; - } - if (match(Keywords)) { - isMatchKeyword = true; - } - const exported = parseModuleExportName(); - if (!isVariableDeclarated(helperGetValueOfExportName(exported))) { - undefExportSymbols.push([helperGetValueOfExportName(exported), exported.start]); - } - if (isContextKeyword("as")) { - nextToken(); - const local = parseModuleExportName(); - staticSematicForDuplicateExportName(local); - specifier.push( - Factory.createExportSpecifier( - exported, - local, - cloneSourcePosition(exported.start), - cloneSourcePosition(local.end), - ), - ); - continue; - } - staticSematicForDuplicateExportName(exported); - specifier.push( - Factory.createExportSpecifier( - exported, - null, - cloneSourcePosition(exported.start), - cloneSourcePosition(exported.end), - ), - ); - } - const { end: bracesRightPunctuatorEnd } = expect(SyntaxKinds.BracesRightPunctuator); - let source: StringLiteral | null = null; - if (getSourceValue() === "from") { - nextToken(); - source = parseStringLiteral(); - } else { - if (isMatchKeyword) { - throw new Error(); - } - if (undefExportSymbols.length > 0) { - undefExportSymbols.forEach(([sym, pos]) => { - symbolScopeRecorder.addToUndefExportSource(sym, pos); - }); - } - staticSematicEarlyErrorForExportName(specifier); - } - shouldInsertSemi(); - const end = source - ? source.end - : specifier.length === 0 - ? bracesRightPunctuatorEnd - : specifier[specifier.length - 1].end; - return Factory.createExportNamedDeclaration(specifier, null, source, start, cloneSourcePosition(end)); - } - /** - * Static Sematic Check based on - * - 16.2.3.1 Static Semantics: Early Errors - * @param specifiers - */ - function staticSematicEarlyErrorForExportName(specifiers: Array) { - for (const specifier of specifiers) { - if (isStringLiteral(specifier.exported)) { - raiseError( - ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_exported_binding_without_from, - specifier.exported.start, - ); - } - } - } - /** - * Using symbol scope recorder for record export name identifier - * @param exportName - */ - function staticSematicForDuplicateExportName(exportName: StringLiteral | Identifier) { - const name = helperGetValueOfExportName(exportName); - const isExportNameAlreadyBeDeclar = declarateExportSymbol(name, exportName.start); - if (isExportNameAlreadyBeDeclar) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isExportNameAlreadyBeDeclar); - } - const isDefaultAlreadyBeDeclarated = symbolScopeRecorder.testAndSetDefaultExport(exportName.start); - if (name === "default" && isDefaultAlreadyBeDeclarated) { - raiseError(ErrorMessageMap.v8_error_duplicate_identifier, isDefaultAlreadyBeDeclarated); - } - } - function parseExportAllDeclaration(start: SourcePosition): ExportAllDeclaration { - expect(SyntaxKinds.MultiplyOperator); - let exported: Identifier | StringLiteral | null = null; - if (isContextKeyword("as")) { - nextToken(); - exported = parseModuleExportName(); - } else { - exported = null; - } - expectFormKeyword(); - const source = parseStringLiteral(); - shouldInsertSemi(); - return Factory.createExportAllDeclaration(exported, source, start, cloneSourcePosition(source.end)); - } - function parseModuleExportName() { - if (match(SyntaxKinds.StringLiteral)) { - return parseStringLiteral(); - } - return parseIdentifierName(); - } - function helperGetValueOfExportName(exportName: StringLiteral | Identifier) { - if (isIdentifer(exportName)) { - return exportName.name; - } - return exportName.value; - } - /** - * - * @param tryFunc - * @returns - */ - function tryParse(tryFunc: () => T): [T, LexerState, LexerContext, number] | undefined { - const [state, context] = lexer.forkState(); - const errorIndex = errorhandler.markAsTry(); - try { - return [tryFunc(), state, context, errorIndex]; - } catch { - lexer.restoreState(state, context); - errorhandler.restoreTryFail(errorIndex); - } - return; - } - function abortTryParseResult(state: LexerState, context: LexerContext, index: number) { - lexer.restoreState(state, context); - errorhandler.restoreTryFail(index); - } - function parseInType(worker: () => T): T { - context.isInType = true; - let result; - try { - result = worker(); - } catch (e) { - context.isInType = false; - throw e; - } - return result; - } - function parseTSEnumDeclaration(): TSEnumDeclaration { - const start = getStartPosition(); - nextToken(); // eat `enum` - const id = parseIdentifierReference(); - const body = parseTSEnumBody(); - return Factory.createTSEnumDeclaration(id, body, start, getLastTokenEndPositon()); - } - function parseTSEnumBody(): TSEnumBody { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const members = []; - let isStart = true; - while (!match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - if (isStart) { - isStart = false; - } else { - expect(SyntaxKinds.CommaToken); - } - // allow trailing comma - if (match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { - break; - } - members.push(parseTSEnumMember()); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createTSEnumBody(members, start, end); - } - function parseTSEnumMember(): TSEnumMember { - const name = parseIdentifierName(); - let init: Expression | undefined = undefined; - if (match(SyntaxKinds.AssginOperator)) { - nextToken(); - init = parseAssignmentExpressionAllowIn(); - } - return Factory.createTSEnumMember( - name, - false, - init, - cloneSourcePosition(name.start), - getLastTokenEndPositon(), - ); - } - function parseTSTypeAlias(): TSTypeAliasDeclaration { - // TODO: TS garud - const start = getStartPosition(); - nextToken(); // eat `type` - const name = parseIdentifierReference(); - const typeParameters = tryParseTSTypeParameterDeclaration(true); - expect(SyntaxKinds.AssginOperator); - const typeNode = parseTSTypeNode(); - shouldInsertSemi(); - return Factory.createTSTypeAliasDeclaration( - name, - typeNode, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - function parseTSInterfaceDeclaration(): TSInterfaceDeclaration { - // TODO: TS garud - const start = getStartPosition(); - nextToken(); // eat `interface` - const id = parseIdentifierReference(); - const typeParameters = tryParseTSTypeParameterDeclaration(false); - const extendTypes = tryParseTSInterfaceDeclarationExtends(); - const body = parseTSInterfaceBody(); - shouldInsertSemi(); - return Factory.createTSInterface(id, typeParameters, extendTypes, body, start, getLastTokenEndPositon()); - } - function tryParseTSInterfaceDeclarationExtends(): Array { - if (match(SyntaxKinds.ExtendsKeyword)) { - nextToken(); - const extendsInterfaces = [parseTSInterfaceHeritage()]; - while (match(SyntaxKinds.CommaToken)) { - nextToken(); - extendsInterfaces.push(parseTSInterfaceHeritage()); - } - return extendsInterfaces; - } - return []; - } - function parseTSInterfaceHeritage() { - const name = parseTSEntityName(); - const typeArguments = tryParseTSTypeParameterInstantiation(false); - return Factory.createTSInterfaceHeritage( - name, - typeArguments, - cloneSourcePosition(name.start), - getLastTokenEndPositon(), - ); - } - function tryParseTSTypeParameterDeclaration(allowAssign: boolean) { - // TODO: TS garud - if (match(SyntaxKinds.LtOperator)) { - return parseTSTypeParameterDeclaration(allowAssign); - } - } - function parseTSTypeParameterDeclaration(allowAssign: boolean): TSTypeParameterDeclaration { - const { start } = expect(SyntaxKinds.LtOperator); - const params = [parseTSTypeParameter()]; - while (match(SyntaxKinds.CommaToken)) { - nextToken(); - params.push(parseTSTypeParameter()); - } - parseGtTokenAsEndOfTypeParameters(allowAssign); - return Factory.createTSTypeParameterDeclaration(params, start, getLastTokenEndPositon()); - } - function parseTSTypeParameter(): TSTypeParameter { - const name = parseIdentifierReference(); - let constraint: TSTypeNode | undefined = undefined; - if (match(SyntaxKinds.ExtendsKeyword)) { - nextToken(); - constraint = parseTSTypeNode(); - } - let defaultType: TSTypeNode | undefined = undefined; - if (match(SyntaxKinds.AssginOperator)) { - nextToken(); - defaultType = parseTSTypeNode(); - } - return Factory.createTSTypeParameter( - constraint, - defaultType, - name, - cloneSourcePosition(name.start), - getLastTokenEndPositon(), - ); - } - function tryParseTSTypeParameterInstantiation( - allowAssign: boolean, - ): TSTypeParameterInstantiation | undefined { - if (match(SyntaxKinds.LtOperator)) { - return parseTSTypeParameterInstantiation(allowAssign); - } - if (match(SyntaxKinds.BitwiseLeftShiftOperator)) { - lexer.reLexLtRelateToken(); - return parseTSTypeParameterInstantiation(allowAssign); - } - } - function parseTSTypeParameterInstantiation(allowAssign: boolean): TSTypeParameterInstantiation { - const { start } = expect(SyntaxKinds.LtOperator); - const params = [parseTSTypeNode()]; - while (match(SyntaxKinds.CommaToken)) { - nextToken(); - params.push(parseTSTypeNode()); - } - parseGtTokenAsEndOfTypeParameters(allowAssign); - return Factory.createTSTypeParameterInstantiation(params, start, getLastTokenEndPositon()); - } - function parseGtTokenAsEndOfTypeParameters(allowAssign: boolean) { - if ( - match([ - SyntaxKinds.GeqtOperator, - SyntaxKinds.BitwiseLeftShiftOperator, - SyntaxKinds.BitwiseLeftShiftAssginOperator, - SyntaxKinds.BitwiseRightShiftFillOperator, - SyntaxKinds.BitwiseRightShiftFillAssginOperator, - ]) - ) { - lexer.reLexGtRelateToken(allowAssign); - } - expect(SyntaxKinds.GtOperator); - } - function parseTSTypeNode(): TSTypeNode { - const checkType = parseTSNonConditionalType(); - if (!match(SyntaxKinds.ExtendsKeyword)) { - return checkType; - } - nextToken(); - const extendType = parseTSNonConditionalType(); - expect(SyntaxKinds.QustionOperator); - const trueType = parseTSTypeNode(); - expect(SyntaxKinds.ColonPunctuator); - const falseType = parseTSTypeNode(); - return Factory.createTSConditionType( - checkType, - extendType, - trueType, - falseType, - cloneSourcePosition(checkType.start), - getLastTokenEndPositon(), - ); - } - function parseTSNonConditionalType(): TSTypeNode { - if (isTSFunctionTypeStart()) { - return parseTSFunctionType(); - } - if (match(SyntaxKinds.NewKeyword)) { - const start = getStartPosition(); - nextToken(); - const { typeParameters, parameters, returnType } = parseTSFunctionSingnature( - SyntaxKinds.ArrowOperator, - false, - ); - return Factory.createTSConstrcutorType( - returnType, - parameters, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - if (isContextKeyword("abstract")) { - const start = getStartPosition(); - nextToken(); - expect(SyntaxKinds.NewKeyword); - const { typeParameters, parameters, returnType } = parseTSFunctionSingnature( - SyntaxKinds.ArrowOperator, - false, - ); - return Factory.createTSConstrcutorType( - returnType, - parameters, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - return parseTSUnionType(); - } - function parseTSFunctionType(): TSFunctionType { - const start = getStartPosition(); - const { parameters, returnType, typeParameters } = parseTSFunctionSingnature( - SyntaxKinds.ArrowOperator, - false, - ); - return Factory.createTSFunctionType( - returnType, - parameters, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - function parseTSGenericArrowFunctionExpression(): ArrorFunctionExpression | undefined { - const result = tryParse( - (): [ - TSTypeParameterDeclaration | undefined, - [ - ASTArrayWithMetaData & { - trailingComma: boolean; - typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; - }, - StrictModeScope, - AsyncArrowExpressionScope, - ], - TSTypeAnnotation | undefined, - ] => { - const typeParameters = tryParseTSTypeParameterDeclaration(false); - const [[meta, strictModeScope], arrowExprScope] = parseWithArrowExpressionScope(() => - parseWithCatpureLayer(parseArgumentsWithType), - ); - meta.start = typeParameters?.start || meta.start; - const returnType = tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); - return [typeParameters, [meta, strictModeScope, arrowExprScope], returnType]; - }, - ); - if (!result) { - return; - } - const [[typeParameters, [meta, strictModeScope, arrowExprScope], returnType], state, context, index] = - result; - if (!match(SyntaxKinds.ArrowOperator)) { - abortTryParseResult(state, context, index); - return; - } - enterArrowFunctionBodyScope(); - const arrowExpr = parseArrowFunctionExpression(meta, typeParameters, strictModeScope, arrowExprScope); - exitArrowFunctionBodyScope(); - arrowExpr.returnType = returnType; - return arrowExpr; - } - function isTSFunctionTypeStart() { - if (match(SyntaxKinds.LtOperator)) { - return true; - } - return match(SyntaxKinds.ParenthesesLeftPunctuator); - } - - function parseTSUnionType(): TSTypeNode { - if (match(SyntaxKinds.BitwiseANDOperator)) { - return parseTSIntersectionType(); - } - let leadingOperator = false; - const start: SourcePosition = getStartPosition(); - if (match(SyntaxKinds.BitwiseOROperator)) { - nextToken(); - leadingOperator = true; - } - const types: Array = [parseTSIntersectionType()]; - while (match(SyntaxKinds.BitwiseOROperator)) { - nextToken(); - types.push(parseTSIntersectionType()); - } - if (types.length === 1 && !leadingOperator) { - return types[0]; - } - return Factory.createTSUnionType(types, start, getLastTokenEndPositon()); - } - function parseTSIntersectionType(): TSTypeNode { - let leadingOperator = false; - const start: SourcePosition = getStartPosition(); - if (match(SyntaxKinds.BitwiseANDOperator)) { - nextToken(); - leadingOperator = true; - } - const types: Array = [parseTSTypeOperator()]; - while (match(SyntaxKinds.BitwiseANDOperator)) { - nextToken(); - types.push(parseTSTypeOperator()); - } - if (types.length === 1 && !leadingOperator) { - return types[0]; - } - return Factory.createTSUnionType(types, start, getLastTokenEndPositon()); - } - function parseTSTypeOperator(): TSTypeNode { - let operator: TSTypeOperator["operator"] | undefined = undefined; - let start: SourcePosition | undefined = undefined; - if (!getEscFlag()) { - const sourceValue = getSourceValue(); - switch (sourceValue) { - case "unique": { - operator = "unique"; - start = getStartPosition(); - nextToken(); - break; - } - case "keyof": { - operator = "keyof"; - start = getStartPosition(); - nextToken(); - break; - } - case "readonly": { - operator = "readonly"; - start = getStartPosition(); - nextToken(); - break; - } - } - } - if (operator && start) { - const typeNode = parseTSTypeOperator(); - return Factory.createTSTypeOperator(typeNode, operator, start, getLastTokenEndPositon()); - } - return parseTSArrayType(); - } - function parseTSArrayType() { - let base = parseTSNonArrayType(); - while (match(SyntaxKinds.BracketLeftPunctuator)) { - nextToken(); - if (match(SyntaxKinds.BracketRightPunctuator)) { - nextToken(); - base = Factory.createTSArrayType(base, cloneSourcePosition(base.start), getLastTokenEndPositon()); - } else { - const indexedType = parseTSTypeNode(); - base = Factory.createTSIndexedAccessType( - indexedType, - base, - cloneSourcePosition(base.start), - getLastTokenEndPositon(), - ); - expect(SyntaxKinds.BracketRightPunctuator); - } - } - return base; - } - function parseTSNonArrayType(): TSTypeNode { - switch (getToken()) { - case SyntaxKinds.BracesLeftPunctuator: { - return parseTSTypeLiteral(); - } - case SyntaxKinds.BracketLeftPunctuator: { - return parseTSTupleType(); - } - case SyntaxKinds.TypeofKeyword: { - return parseTypeQuery(); - } - case SyntaxKinds.NullKeyword: - case SyntaxKinds.UndefinedKeyword: - case SyntaxKinds.TrueKeyword: - case SyntaxKinds.FalseKeyword: - case SyntaxKinds.DecimalLiteral: - case SyntaxKinds.DecimalBigIntegerLiteral: - case SyntaxKinds.NonOctalDecimalLiteral: - case SyntaxKinds.BinaryIntegerLiteral: - case SyntaxKinds.BinaryBigIntegerLiteral: - case SyntaxKinds.OctalIntegerLiteral: - case SyntaxKinds.OctalBigIntegerLiteral: - case SyntaxKinds.HexIntegerLiteral: - case SyntaxKinds.HexBigIntegerLiteral: - case SyntaxKinds.LegacyOctalIntegerLiteral: - case SyntaxKinds.StringLiteral: { - return parseTSLiteralType(); - } - case SyntaxKinds.VoidKeyword: { - return parseTSVoidKeyword(); - } - default: { - const currentValue = getSourceValue(); - switch (currentValue) { - case "string": { - return parseTSStringKeyword(); - } - case "number": { - return parseTSNunberKeyword(); - } - case "bigint": { - return parseTSBigIntKeyword(); - } - case "boolean": { - return parseTSBoolKeyword(); - } - case "null": { - return parseTSNullKeyword(); - } - case "undefined": { - return parseTSUndefiniedKeyword(); - } - case "symbol": { - return parseTSSymbolKeyword(); - } - case "any": { - return parseTSAnyKeyword(); - } - case "never": { - return parseTSNeverKeyword(); - } - case "unknown": { - return parseTSUnknownKeyword(); - } - default: { - return parseTSTypeReference(); - } - } - } - } - } - function parseTSStringKeyword(): TSStringKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSStringKeyword(start, end); - } - function parseTSNunberKeyword(): TSNumberKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSNumberKeyword(start, end); - } - function parseTSBigIntKeyword(): TSBigIntKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSBigintKeyword(start, end); - } - function parseTSBoolKeyword(): TSBooleanKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSBoolKeyword(start, end); - } - function parseTSNullKeyword(): TSNullKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSNullKeyword(start, end); - } - function parseTSUndefiniedKeyword(): TSUndefinedKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSUndefinedKeyword(start, end); - } - function parseTSSymbolKeyword(): TSSymbolKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSSymbolKeyword(start, end); - } - function parseTSAnyKeyword(): TSAnyKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSAnyKeyword(start, end); - } - function parseTSNeverKeyword(): TSUndefinedKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSNeverKeyword(start, end); - } - function parseTSUnknownKeyword(): TSUnknowKeyword { - const { start, end } = expect(SyntaxKinds.Identifier); - return Factory.createTSUnknowKeyword(start, end); - } - function parseTSVoidKeyword(): TSVoidKeyword { - const { start, end } = expect(SyntaxKinds.VoidKeyword); - return Factory.createTSVoidKeyword(start, end); - } - function parseTypeQuery(): TSTypeQuery { - const { start } = expect(SyntaxKinds.TypeofKeyword); - const exprName = parseTSEntityName(); - return Factory.createTSTypeQuery(exprName, start, getLastTokenEndPositon()); - } - function parseTSTupleType(): TSTupleType { - const { start } = expect(SyntaxKinds.BracketLeftPunctuator); - const elementTypes: Array = [parseTSTypeNode()]; - while (!match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { - expect(SyntaxKinds.CommaToken); - if (match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { - break; - } - elementTypes.push(parseTSTypeNode()); - } - expect(SyntaxKinds.BracketRightPunctuator); - return Factory.createTSTupleType(elementTypes, start, getLastTokenEndPositon()); - } - function parseTSLiteralType(): TSLiteralType { - const start = getStartPosition(); - const literal = parsePrimaryExpression(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return Factory.createTSLiteralType(literal as unknown as any, start, getLastTokenEndPositon()); - } - /** - * Try parse type annotation, optional mark and default vakue - * for function param, parse pattern : - * ``` - * (`?`)? (`:` TypeNode )? (`=` expression)? - * ``` - * This function will create a assignment pattern if default - * value exist. - * @param {TSParameter} param - * @returns - */ - function parseFunctionParamType(param: TSParameter, shouldParseDefaultValue: boolean) { - if (!requirePlugin(ParserPlugin.TypeScript)) return param; - const optional = tryParseOptionalTypeParam(); - const type = tryParseTypeAnnotation(); - param.typeAnnotation = type; - param.optional = optional; - if (match(SyntaxKinds.AssginOperator) && shouldParseDefaultValue) { - return parseDefaultValueForBindingElement(param); - } - return param; - } - /** - * First helper of parse possible type annotation for argument. parse - * expect pattern - * ``` - * (`?`)? (`:` TypeNode)? - * ``` - * return binary tuple, optional TypeAnnotation Node and is optional value. - * - * NOTE: This function only used to parse type of argument for possible arrow - * expression. - * - * @returns {[TSTypeAnnotation | undefined, boolean]} - */ - function parsePossibleArugmentType(): [TSTypeAnnotation | undefined, boolean] { - const optional = tryParseOptionalTypeParam(); - const type = tryParseTypeAnnotation(); - return [type, optional]; - } - /** - * Second helper for parse possible default value for type argument, expect - * parse pattern: - * ``` - * (`=` Expression)? - * ``` - * If assignment operator is exist, will create a assignment expression - * which wrap left expression as left value, so it gonna transform left - * expression into pattern. - * - * NOTE: This function only used to parse type of argument for possible arrow - * expression. - * - * @param {Expression} left - * @returns - */ - function parsePossibleArugmentDefaultValue(left: Expression) { - if (match(SyntaxKinds.AssginOperator)) { - nextToken(); - const leftPat = exprToPattern(left, false); - const right = parseAssignmentExpressionInheritIn(); - return Factory.createAssignmentExpression( - leftPat, - right, - SyntaxKinds.AssginOperator, - cloneSourcePosition(left.start), - cloneSourcePosition(right.end), - ); - } - return left; - } - /** - * Util helper for parse qustion mark for function param type - * @returns - */ - function tryParseOptionalTypeParam() { - let optional = false; - if (match(SyntaxKinds.QustionOperator)) { - optional = true; - nextToken(); - } - return optional; - } - function parseTSTypeReference(): TSTypeReference { - // TODO: TS garud - const typeName = parseTSEntityName(); - const typeArguments = tryParseTSTypeParameterInstantiation(false); - return Factory.createTSTypeReference( - typeName, - typeArguments, - cloneSourcePosition(typeName.start), - cloneSourcePosition(typeName.end), - ); - } - function parseTSEntityName(): TSEntityName { - let left: TSEntityName = parseIdentifierReference(); - while (match(SyntaxKinds.DotOperator)) { - nextToken(); - const right = parseIdentifierName(); - left = Factory.createTSQualifiedName( - left, - right, - cloneSourcePosition(left.start), - cloneSourcePosition(right.end), - ); - } - return left; - } - function parseTSFunctionSingnature(expectToken: SyntaxKinds, optional: boolean) { - const typeParameters = tryParseTSTypeParameterDeclaration(false); - const parameters = parseInType(parseFunctionParam) as TSParameter[]; - const matchToken = match(expectToken); - if (optional && !matchToken) { - return { - typeParameters, - parameters: parameters, - returnType: undefined, - }; - } - const returnType = parseTSReturnTypeOrTypePredicate(expectToken); - return { - parameters, - returnType, - typeParameters, - }; - } - /** - * try parse return type or type predicate, expect syntax is - * ``` - * TypeNode - * TypePredicate - * ``` - * @param {SyntaxKinds} expectToken - * @returns - */ - function tryParseTSReturnTypeOrTypePredicate(expectToken: SyntaxKinds) { - if (!match(expectToken)) { - return; - } - return parseTSReturnTypeOrTypePredicate(expectToken); - } - function parseTSReturnTypeOrTypePredicate(expectToken: SyntaxKinds): TSTypeAnnotation | undefined { - // parse type or type predication - const { start } = expect(expectToken); - const assertion = parseTSAssertionInReturnType(); - const typePredicatePrefix = parseTypePredicatePrefixInReturnType(); - if (!typePredicatePrefix) { - if (!assertion) { - // : type - const returnType = parseTypeAnnoationWithoutColon(start); - return returnType; - } - // : asserts type - const name = parseIdentifierReference(); - const typePredicate = Factory.createTSTypePredicate( - name, - true, - undefined, - start, - cloneSourcePosition(name.end), - ); - const returnType = Factory.createTSTypeAnnotation( - typePredicate, - cloneSourcePosition(typePredicate.start), - cloneSourcePosition(typePredicate.end), - ); - return returnType; - } - // : asserts type is otherType - const name = parseIdentifierReference(); - nextToken(); // eat `is` - const typeAnnotation = parseTypeAnnoationWithoutColon(getStartPosition()); - const typePredicate = Factory.createTSTypePredicate( - name, - assertion, - typeAnnotation, - start, - cloneSourcePosition(typeAnnotation.end), - ); - const returnType = Factory.createTSTypeAnnotation( - typePredicate, - cloneSourcePosition(typePredicate.start), - cloneSourcePosition(typePredicate.end), - ); - return returnType; - } - function parseTSAssertionInReturnType(): boolean { - if (isContextKeyword("asserts")) { - const { kind: lookaheadToken, value, lineTerminatorFlag } = lookahead(); - if ((lookaheadToken === SyntaxKinds.Identifier || value === "is") && !lineTerminatorFlag) { - nextToken(); - return true; - } - } - return false; - } - function parseTypePredicatePrefixInReturnType() { - return match(SyntaxKinds.Identifier) && lookahead().value === "is"; - } - function parseTSTypeLiteral(): TSTypeLiteral { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const members: Array = []; - while (!match([SyntaxKinds.EOFToken, SyntaxKinds.BracesRightPunctuator])) { - members.push(parseTSTypeElment()); - parseTSInterTypeElement(); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return { - kind: SyntaxKinds.TSTypeLiteral, - members, - start, - end, - }; - } - function parseTSInterfaceBody() { - const { start } = expect(SyntaxKinds.BracesLeftPunctuator); - const members: Array = []; - while (!match([SyntaxKinds.EOFToken, SyntaxKinds.BracesRightPunctuator])) { - members.push(parseTSTypeElment()); - parseTSInterTypeElement(); - } - const { end } = expect(SyntaxKinds.BracesRightPunctuator); - return Factory.createTSInterfaceBody(members, start, end); - } - function parseTSTypeElment(): TSTypeElement { - switch (getToken()) { - case SyntaxKinds.ParenthesesLeftPunctuator: - case SyntaxKinds.LtOperator: { - // TSCallSignatureDeclaration - const start = getStartPosition(); - const { parameters, returnType, typeParameters } = parseTSFunctionSingnature( - SyntaxKinds.ColonPunctuator, - true, - ); - return Factory.createTSCallSignatureDeclaration( - parameters, - returnType, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - case SyntaxKinds.NewKeyword: { - // TSConstructSignatureDeclaration - const start = getStartPosition(); - nextToken(); - const { parameters, returnType, typeParameters } = parseTSFunctionSingnature( - SyntaxKinds.ColonPunctuator, - true, - ); - return Factory.createTSConstructSignatureDeclaration( - parameters, - returnType, - typeParameters, - start, - getLastTokenEndPositon(), - ); - } - default: { - // TSMethodSignature - // TSPropertySignature - const isComputedRef = { isComputed: false }; - const key = parsePropertyName(isComputedRef); - let optional = false; - if (match(SyntaxKinds.QustionOperator)) { - optional = true; - nextToken(); - } - if (match(SyntaxKinds.ParenthesesLeftPunctuator) || match(SyntaxKinds.LtOperator)) { - const { parameters, returnType, typeParameters } = parseTSFunctionSingnature( - SyntaxKinds.ColonPunctuator, - true, - ); - return Factory.createTSMethodSignature( - key, - isComputedRef.isComputed, - optional, - parameters, - returnType, - typeParameters, - cloneSourcePosition(key.start), - getLastTokenEndPositon(), - ); - } - const typeAnnotation = tryParseTypeAnnotation(); - return Factory.createTSPropertySignature( - key, - isComputedRef.isComputed, - optional, - typeAnnotation, - cloneSourcePosition(key.start), - getLastTokenEndPositon(), - ); - } - } - } - function parseTSInterTypeElement() { - if (match([SyntaxKinds.SemiPunctuator, SyntaxKinds.CommaToken])) { - nextToken(); - return; - } - if (match(SyntaxKinds.BracesRightPunctuator)) { - return; - } - if (getLineTerminatorFlag()) { - return; - } - // TODO: should error - } - function tryParseTypeAnnotation(): TSTypeAnnotation | undefined { - if (match(SyntaxKinds.ColonPunctuator)) { - return parseTypeAnnoation(); - } - return undefined; - } - function parseTypeAnnoation(): TSTypeAnnotation { - const { start } = expect(SyntaxKinds.ColonPunctuator); - const typeNode = parseTSTypeNode(); - return Factory.createTSTypeAnnotation(typeNode, start, cloneSourcePosition(typeNode.end)); - } - function parseTypeAnnoationWithoutColon(start: SourcePosition): TSTypeAnnotation { - const typeNode = parseTSTypeNode(); - return Factory.createTSTypeAnnotation(typeNode, start, cloneSourcePosition(typeNode.end)); - } -} diff --git a/web-infras/parser/src/parser/scope/arrowExprScope.ts b/web-infras/parser/src/parser/scope/arrowExprScope.ts index 9239d7ed..7d11cde8 100644 --- a/web-infras/parser/src/parser/scope/arrowExprScope.ts +++ b/web-infras/parser/src/parser/scope/arrowExprScope.ts @@ -17,6 +17,8 @@ function mergeScope(target: AsyncArrowExpressionScope, source: AsyncArrowExpress ); } +export type AsyncArrowExpressionScopeRecorder = ReturnType; + export function createAsyncArrowExpressionScopeRecorder() { const asyncArrowExpressionScopes: Array = []; diff --git a/web-infras/parser/src/parser/scope/lexicalScope/lexicalScope.ts b/web-infras/parser/src/parser/scope/lexicalScope/lexicalScope.ts index 95fedf38..58d1f154 100644 --- a/web-infras/parser/src/parser/scope/lexicalScope/lexicalScope.ts +++ b/web-infras/parser/src/parser/scope/lexicalScope/lexicalScope.ts @@ -7,6 +7,7 @@ import { ExportContext, } from "./type"; +export type LexicalScopeRecorder = ReturnType; /** * Lexical Scope Recorder is focus on the position of the current * parse position, is it in function, what kind of function ?, is in diff --git a/web-infras/parser/src/parser/scope/strictModeScope.ts b/web-infras/parser/src/parser/scope/strictModeScope.ts index 9cd4ba56..d0dadb1a 100644 --- a/web-infras/parser/src/parser/scope/strictModeScope.ts +++ b/web-infras/parser/src/parser/scope/strictModeScope.ts @@ -14,6 +14,8 @@ export type StrictModeScope = kind: "RHSLayer"; }; +export type StrictModeScopeRecorder = ReturnType; + export function createStrictModeScopeRecorder() { const strictModeScopes: Array = []; diff --git a/web-infras/parser/src/parser/scope/symbolScope/symbolScope.ts b/web-infras/parser/src/parser/scope/symbolScope/symbolScope.ts index fde3ff89..7a63b6fa 100644 --- a/web-infras/parser/src/parser/scope/symbolScope/symbolScope.ts +++ b/web-infras/parser/src/parser/scope/symbolScope/symbolScope.ts @@ -12,6 +12,9 @@ import { createSymbolScopeRecorderContext, isPrivateNameExist, } from "./type"; + +export type SymbolScopeRecorder = ReturnType; + /** * Symbol recorder is used to check the duplicate identifier, it should work the the lexical * scope recorder for position relate context in program. diff --git a/web-infras/parser/src/parser/ts/index.ts b/web-infras/parser/src/parser/ts/index.ts new file mode 100644 index 00000000..dca6b308 --- /dev/null +++ b/web-infras/parser/src/parser/ts/index.ts @@ -0,0 +1,927 @@ +import { + TSEnumDeclaration, + Factory, + TSEnumBody, + SyntaxKinds, + TSEnumMember, + Expression, + cloneSourcePosition, + TSTypeAliasDeclaration, + TSInterfaceDeclaration, + TSInterfaceHeritage, + TSTypeParameterDeclaration, + TSTypeParameter, + TSTypeNode, + TSTypeParameterInstantiation, + TSFunctionType, + ArrorFunctionExpression, + TSTypeAnnotation, + SourcePosition, + TSTypeOperator, + TSStringKeyword, + TSNumberKeyword, + TSBigIntKeyword, + TSBooleanKeyword, + TSNullKeyword, + TSUndefinedKeyword, + TSSymbolKeyword, + TSAnyKeyword, + TSUnknowKeyword, + TSVoidKeyword, + TSTypeQuery, + TSTupleType, + TSLiteralType, + TSParameter, + TSTypeReference, + TSEntityName, + TSTypeLiteral, + TSTypeElement, +} from "web-infra-common"; +import { LexerState, LexerContext } from "@/src/lexer/type"; +import { ParserPlugin } from "@/src/parser/config"; +import { ASTArrayWithMetaData } from "@/src/parser/type"; +import { AsyncArrowExpressionScope } from "@/src/parser/scope/arrowExprScope"; +import { StrictModeScope } from "@/src/parser/scope/strictModeScope"; +import { Parser } from "@/src/parser"; + +/** + * + * @param tryFunc + * @returns + */ +export function tryParse( + this: Parser, + tryFunc: () => T, +): [T, LexerState, LexerContext, number] | undefined { + const [state, context] = this.lexer.forkState(); + const errorIndex = this.errorHandler.markAsTry(); + try { + return [tryFunc(), state, context, errorIndex]; + } catch { + this.lexer.restoreState(state, context); + this.errorHandler.restoreTryFail(errorIndex); + } + return; +} +export function abortTryParseResult(this: Parser, state: LexerState, context: LexerContext, index: number) { + this.lexer.restoreState(state, context); + this.errorHandler.restoreTryFail(index); +} +export function parseInType(this: Parser, worker: () => T): T { + this.context.isInType = true; + let result; + try { + result = worker(); + } catch (e) { + this.context.isInType = false; + throw e; + } + return result; +} +export function parseTSEnumDeclaration(this: Parser): TSEnumDeclaration { + const start = this.getStartPosition(); + this.nextToken(); // eat `enum` + const id = this.parseIdentifierReference(); + const body = this.parseTSEnumBody(); + return Factory.createTSEnumDeclaration(id, body, start, this.getLastTokenEndPositon()); +} +export function parseTSEnumBody(this: Parser): TSEnumBody { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const members = []; + let isStart = true; + while (!this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + if (isStart) { + isStart = false; + } else { + this.expect(SyntaxKinds.CommaToken); + } + // allow trailing comma + if (this.match([SyntaxKinds.BracesRightPunctuator, SyntaxKinds.EOFToken])) { + break; + } + members.push(this.parseTSEnumMember()); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createTSEnumBody(members, start, end); +} +export function parseTSEnumMember(this: Parser): TSEnumMember { + const name = this.parseIdentifierName(); + let init: Expression | undefined = undefined; + if (this.match(SyntaxKinds.AssginOperator)) { + this.nextToken(); + init = this.parseAssignmentExpressionAllowIn(); + } + return Factory.createTSEnumMember( + name, + false, + init, + cloneSourcePosition(name.start), + this.getLastTokenEndPositon(), + ); +} +export function parseTSTypeAlias(this: Parser): TSTypeAliasDeclaration { + // TODO: TS garud + const start = this.getStartPosition(); + this.nextToken(); // eat `type` + const name = this.parseIdentifierReference(); + const typeParameters = this.tryParseTSTypeParameterDeclaration(true); + this.expect(SyntaxKinds.AssginOperator); + const typeNode = this.parseTSTypeNode(); + this.shouldInsertSemi(); + return Factory.createTSTypeAliasDeclaration( + name, + typeNode, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); +} +export function parseTSInterfaceDeclaration(this: Parser): TSInterfaceDeclaration { + // TODO: TS garud + const start = this.getStartPosition(); + this.nextToken(); // eat `interface` + const id = this.parseIdentifierReference(); + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + const extendTypes = this.tryParseTSInterfaceDeclarationExtends(); + const body = this.parseTSInterfaceBody(); + this.shouldInsertSemi(); + return Factory.createTSInterface( + id, + typeParameters, + extendTypes, + body, + start, + this.getLastTokenEndPositon(), + ); +} +export function tryParseTSInterfaceDeclarationExtends(this: Parser): Array { + if (this.match(SyntaxKinds.ExtendsKeyword)) { + this.nextToken(); + const extendsInterfaces = [this.parseTSInterfaceHeritage()]; + while (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + extendsInterfaces.push(this.parseTSInterfaceHeritage()); + } + return extendsInterfaces; + } + return []; +} +export function parseTSInterfaceHeritage(this: Parser) { + const name = this.parseTSEntityName(); + const typeArguments = this.tryParseTSTypeParameterInstantiation(false); + return Factory.createTSInterfaceHeritage( + name, + typeArguments, + cloneSourcePosition(name.start), + this.getLastTokenEndPositon(), + ); +} +export function tryParseTSTypeParameterDeclaration(this: Parser, allowAssign: boolean) { + // TODO: TS garud + if (this.match(SyntaxKinds.LtOperator)) { + return this.parseTSTypeParameterDeclaration(allowAssign); + } +} +export function parseTSTypeParameterDeclaration( + this: Parser, + allowAssign: boolean, +): TSTypeParameterDeclaration { + const { start } = this.expect(SyntaxKinds.LtOperator); + const params = [this.parseTSTypeParameter()]; + while (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + params.push(this.parseTSTypeParameter()); + } + this.parseGtTokenAsEndOfTypeParameters(allowAssign); + return Factory.createTSTypeParameterDeclaration(params, start, this.getLastTokenEndPositon()); +} +export function parseTSTypeParameter(this: Parser): TSTypeParameter { + const name = this.parseIdentifierReference(); + let constraint: TSTypeNode | undefined = undefined; + if (this.match(SyntaxKinds.ExtendsKeyword)) { + this.nextToken(); + constraint = this.parseTSTypeNode(); + } + let defaultType: TSTypeNode | undefined = undefined; + if (this.match(SyntaxKinds.AssginOperator)) { + this.nextToken(); + defaultType = this.parseTSTypeNode(); + } + return Factory.createTSTypeParameter( + constraint, + defaultType, + name, + cloneSourcePosition(name.start), + this.getLastTokenEndPositon(), + ); +} +export function tryParseTSTypeParameterInstantiation( + this: Parser, + allowAssign: boolean, +): TSTypeParameterInstantiation | undefined { + if (this.match(SyntaxKinds.LtOperator)) { + return this.parseTSTypeParameterInstantiation(allowAssign); + } + if (this.match(SyntaxKinds.BitwiseLeftShiftOperator)) { + this.lexer.reLexLtRelateToken(); + return this.parseTSTypeParameterInstantiation(allowAssign); + } +} +export function parseTSTypeParameterInstantiation( + this: Parser, + allowAssign: boolean, +): TSTypeParameterInstantiation { + const { start } = this.expect(SyntaxKinds.LtOperator); + const params = [this.parseTSTypeNode()]; + while (this.match(SyntaxKinds.CommaToken)) { + this.nextToken(); + params.push(this.parseTSTypeNode()); + } + this.parseGtTokenAsEndOfTypeParameters(allowAssign); + return Factory.createTSTypeParameterInstantiation(params, start, this.getLastTokenEndPositon()); +} +export function parseGtTokenAsEndOfTypeParameters(this: Parser, allowAssign: boolean) { + if ( + this.match([ + SyntaxKinds.GeqtOperator, + SyntaxKinds.BitwiseLeftShiftOperator, + SyntaxKinds.BitwiseLeftShiftAssginOperator, + SyntaxKinds.BitwiseRightShiftFillOperator, + SyntaxKinds.BitwiseRightShiftFillAssginOperator, + ]) + ) { + this.lexer.reLexGtRelateToken(allowAssign); + } + this.expect(SyntaxKinds.GtOperator); +} +export function parseTSTypeNode(this: Parser): TSTypeNode { + const checkType = this.parseTSNonConditionalType(); + if (!this.match(SyntaxKinds.ExtendsKeyword)) { + return checkType; + } + this.nextToken(); + const extendType = this.parseTSNonConditionalType(); + this.expect(SyntaxKinds.QustionOperator); + const trueType = this.parseTSTypeNode(); + this.expect(SyntaxKinds.ColonPunctuator); + const falseType = this.parseTSTypeNode(); + return Factory.createTSConditionType( + checkType, + extendType, + trueType, + falseType, + cloneSourcePosition(checkType.start), + this.getLastTokenEndPositon(), + ); +} +export function parseTSNonConditionalType(this: Parser): TSTypeNode { + if (this.isTSFunctionTypeStart()) { + return this.parseTSFunctionType(); + } + if (this.match(SyntaxKinds.NewKeyword)) { + const start = this.getStartPosition(); + this.nextToken(); + const { typeParameters, parameters, returnType } = this.parseTSFunctionSingnature( + SyntaxKinds.ArrowOperator, + false, + ); + return Factory.createTSConstrcutorType( + returnType, + parameters, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); + } + if (this.isContextKeyword("abstract")) { + const start = this.getStartPosition(); + this.nextToken(); + this.expect(SyntaxKinds.NewKeyword); + const { typeParameters, parameters, returnType } = this.parseTSFunctionSingnature( + SyntaxKinds.ArrowOperator, + false, + ); + return Factory.createTSConstrcutorType( + returnType, + parameters, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); + } + return this.parseTSUnionType(); +} +export function parseTSFunctionType(this: Parser): TSFunctionType { + const start = this.getStartPosition(); + const { parameters, returnType, typeParameters } = this.parseTSFunctionSingnature( + SyntaxKinds.ArrowOperator, + false, + ); + return Factory.createTSFunctionType( + returnType, + parameters, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); +} +export function parseTSGenericArrowFunctionExpression(this: Parser): ArrorFunctionExpression | undefined { + const result = this.tryParse( + (): [ + TSTypeParameterDeclaration | undefined, + [ + ASTArrayWithMetaData & { + trailingComma: boolean; + typeAnnotations: Array<[TSTypeAnnotation | undefined, boolean]> | undefined; + }, + StrictModeScope, + AsyncArrowExpressionScope, + ], + TSTypeAnnotation | undefined, + ] => { + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + const [[meta, strictModeScope], arrowExprScope] = this.parseWithArrowExpressionScope(() => + this.parseWithCatpureLayer(() => this.parseArgumentsWithType()), + ); + meta.start = typeParameters?.start || meta.start; + const returnType = this.tryParseTSReturnTypeOrTypePredicate(SyntaxKinds.ColonPunctuator); + return [typeParameters, [meta, strictModeScope, arrowExprScope], returnType]; + }, + ); + if (!result) { + return; + } + const [[typeParameters, [meta, strictModeScope, arrowExprScope], returnType], state, context, index] = + result; + if (!this.match(SyntaxKinds.ArrowOperator)) { + this.abortTryParseResult(state, context, index); + return; + } + this.enterArrowFunctionBodyScope(); + const arrowExpr = this.parseArrowFunctionExpression(meta, typeParameters, strictModeScope, arrowExprScope); + this.exitArrowFunctionBodyScope(); + arrowExpr.returnType = returnType; + return arrowExpr; +} +export function isTSFunctionTypeStart(this: Parser) { + if (this.match(SyntaxKinds.LtOperator)) { + return true; + } + return this.match(SyntaxKinds.ParenthesesLeftPunctuator); +} + +export function parseTSUnionType(this: Parser): TSTypeNode { + if (this.match(SyntaxKinds.BitwiseANDOperator)) { + return this.parseTSIntersectionType(); + } + let leadingOperator = false; + const start: SourcePosition = this.getStartPosition(); + if (this.match(SyntaxKinds.BitwiseOROperator)) { + this.nextToken(); + leadingOperator = true; + } + const types: Array = [this.parseTSIntersectionType()]; + while (this.match(SyntaxKinds.BitwiseOROperator)) { + this.nextToken(); + types.push(this.parseTSIntersectionType()); + } + if (types.length === 1 && !leadingOperator) { + return types[0]; + } + return Factory.createTSUnionType(types, start, this.getLastTokenEndPositon()); +} +export function parseTSIntersectionType(this: Parser): TSTypeNode { + let leadingOperator = false; + const start: SourcePosition = this.getStartPosition(); + if (this.match(SyntaxKinds.BitwiseANDOperator)) { + this.nextToken(); + leadingOperator = true; + } + const types: Array = [this.parseTSTypeOperator()]; + while (this.match(SyntaxKinds.BitwiseANDOperator)) { + this.nextToken(); + types.push(this.parseTSTypeOperator()); + } + if (types.length === 1 && !leadingOperator) { + return types[0]; + } + return Factory.createTSUnionType(types, start, this.getLastTokenEndPositon()); +} +export function parseTSTypeOperator(this: Parser): TSTypeNode { + let operator: TSTypeOperator["operator"] | undefined = undefined; + let start: SourcePosition | undefined = undefined; + if (!this.getEscFlag()) { + const sourceValue = this.getSourceValue(); + switch (sourceValue) { + case "unique": { + operator = "unique"; + start = this.getStartPosition(); + this.nextToken(); + break; + } + case "keyof": { + operator = "keyof"; + start = this.getStartPosition(); + this.nextToken(); + break; + } + case "readonly": { + operator = "readonly"; + start = this.getStartPosition(); + this.nextToken(); + break; + } + } + } + if (operator && start) { + const typeNode = this.parseTSTypeOperator(); + return Factory.createTSTypeOperator(typeNode, operator, start, this.getLastTokenEndPositon()); + } + return this.parseTSArrayType(); +} +export function parseTSArrayType(this: Parser) { + let base = this.parseTSNonArrayType(); + while (this.match(SyntaxKinds.BracketLeftPunctuator)) { + this.nextToken(); + if (this.match(SyntaxKinds.BracketRightPunctuator)) { + this.nextToken(); + base = Factory.createTSArrayType(base, cloneSourcePosition(base.start), this.getLastTokenEndPositon()); + } else { + const indexedType = this.parseTSTypeNode(); + base = Factory.createTSIndexedAccessType( + indexedType, + base, + cloneSourcePosition(base.start), + this.getLastTokenEndPositon(), + ); + this.expect(SyntaxKinds.BracketRightPunctuator); + } + } + return base; +} +export function parseTSNonArrayType(this: Parser): TSTypeNode { + switch (this.getToken()) { + case SyntaxKinds.BracesLeftPunctuator: { + return this.parseTSTypeLiteral(); + } + case SyntaxKinds.BracketLeftPunctuator: { + return this.parseTSTupleType(); + } + case SyntaxKinds.TypeofKeyword: { + return this.parseTypeQuery(); + } + case SyntaxKinds.NullKeyword: + case SyntaxKinds.UndefinedKeyword: + case SyntaxKinds.TrueKeyword: + case SyntaxKinds.FalseKeyword: + case SyntaxKinds.DecimalLiteral: + case SyntaxKinds.DecimalBigIntegerLiteral: + case SyntaxKinds.NonOctalDecimalLiteral: + case SyntaxKinds.BinaryIntegerLiteral: + case SyntaxKinds.BinaryBigIntegerLiteral: + case SyntaxKinds.OctalIntegerLiteral: + case SyntaxKinds.OctalBigIntegerLiteral: + case SyntaxKinds.HexIntegerLiteral: + case SyntaxKinds.HexBigIntegerLiteral: + case SyntaxKinds.LegacyOctalIntegerLiteral: + case SyntaxKinds.StringLiteral: { + return this.parseTSLiteralType(); + } + case SyntaxKinds.VoidKeyword: { + return this.parseTSVoidKeyword(); + } + default: { + const currentValue = this.getSourceValue(); + switch (currentValue) { + case "string": { + return this.parseTSStringKeyword(); + } + case "number": { + return this.parseTSNunberKeyword(); + } + case "bigint": { + return this.parseTSBigIntKeyword(); + } + case "boolean": { + return this.parseTSBoolKeyword(); + } + case "null": { + return this.parseTSNullKeyword(); + } + case "undefined": { + return this.parseTSUndefiniedKeyword(); + } + case "symbol": { + return this.parseTSSymbolKeyword(); + } + case "any": { + return this.parseTSAnyKeyword(); + } + case "never": { + return this.parseTSNeverKeyword(); + } + case "unknown": { + return this.parseTSUnknownKeyword(); + } + default: { + return this.parseTSTypeReference(); + } + } + } + } +} +export function parseTSStringKeyword(this: Parser): TSStringKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSStringKeyword(start, end); +} +export function parseTSNunberKeyword(this: Parser): TSNumberKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSNumberKeyword(start, end); +} +export function parseTSBigIntKeyword(this: Parser): TSBigIntKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSBigintKeyword(start, end); +} +export function parseTSBoolKeyword(this: Parser): TSBooleanKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSBoolKeyword(start, end); +} +export function parseTSNullKeyword(this: Parser): TSNullKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSNullKeyword(start, end); +} +export function parseTSUndefiniedKeyword(this: Parser): TSUndefinedKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSUndefinedKeyword(start, end); +} +export function parseTSSymbolKeyword(this: Parser): TSSymbolKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSSymbolKeyword(start, end); +} +export function parseTSAnyKeyword(this: Parser): TSAnyKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSAnyKeyword(start, end); +} +export function parseTSNeverKeyword(this: Parser): TSUndefinedKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSNeverKeyword(start, end); +} +export function parseTSUnknownKeyword(this: Parser): TSUnknowKeyword { + const { start, end } = this.expect(SyntaxKinds.Identifier); + return Factory.createTSUnknowKeyword(start, end); +} +export function parseTSVoidKeyword(this: Parser): TSVoidKeyword { + const { start, end } = this.expect(SyntaxKinds.VoidKeyword); + return Factory.createTSVoidKeyword(start, end); +} +export function parseTypeQuery(this: Parser): TSTypeQuery { + const { start } = this.expect(SyntaxKinds.TypeofKeyword); + const exprName = this.parseTSEntityName(); + return Factory.createTSTypeQuery(exprName, start, this.getLastTokenEndPositon()); +} +export function parseTSTupleType(this: Parser): TSTupleType { + const { start } = this.expect(SyntaxKinds.BracketLeftPunctuator); + const elementTypes: Array = [this.parseTSTypeNode()]; + while (!this.match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { + this.expect(SyntaxKinds.CommaToken); + if (this.match([SyntaxKinds.BracketRightPunctuator, SyntaxKinds.EOFToken])) { + break; + } + elementTypes.push(this.parseTSTypeNode()); + } + this.expect(SyntaxKinds.BracketRightPunctuator); + return Factory.createTSTupleType(elementTypes, start, this.getLastTokenEndPositon()); +} +export function parseTSLiteralType(this: Parser): TSLiteralType { + const start = this.getStartPosition(); + const literal = this.parsePrimaryExpression(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Factory.createTSLiteralType(literal as unknown as any, start, this.getLastTokenEndPositon()); +} +/** + * Try parse type annotation, optional mark and default vakue + * for function param, parse pattern : + * ``` + * (`?`)? (`:` TypeNode )? (`=` expression)? + * ``` + * This function will create a assignment pattern if default + * value exist. + * @param {TSParameter} param + * @returns + */ +export function parseFunctionParamType(this: Parser, param: TSParameter, shouldParseDefaultValue: boolean) { + if (!this.requirePlugin(ParserPlugin.TypeScript)) return param; + const optional = this.tryParseOptionalTypeParam(); + const type = this.tryParseTypeAnnotation(); + param.typeAnnotation = type; + param.optional = optional; + if (this.match(SyntaxKinds.AssginOperator) && shouldParseDefaultValue) { + return this.parseDefaultValueForBindingElement(param); + } + return param; +} +/** + * First helper of parse possible type annotation for argument. parse + * expect pattern + * ``` + * (`?`)? (`:` TypeNode)? + * ``` + * return binary tuple, optional TypeAnnotation Node and is optional value. + * + * NOTE: This function only used to parse type of argument for possible arrow + * expression. + * + * @returns {[TSTypeAnnotation | undefined, boolean]} + */ +export function parsePossibleArugmentType(this: Parser): [TSTypeAnnotation | undefined, boolean] { + const optional = this.tryParseOptionalTypeParam(); + const type = this.tryParseTypeAnnotation(); + return [type, optional]; +} +/** + * Second helper for parse possible default value for type argument, expect + * parse pattern: + * ``` + * (`=` Expression)? + * ``` + * If assignment operator is exist, will create a assignment expression + * which wrap left expression as left value, so it gonna transform left + * expression into pattern. + * + * NOTE: This function only used to parse type of argument for possible arrow + * expression. + * + * @param {Expression} left + * @returns + */ +export function parsePossibleArugmentDefaultValue(this: Parser, left: Expression) { + if (this.match(SyntaxKinds.AssginOperator)) { + this.nextToken(); + const leftPat = this.exprToPattern(left, false); + const right = this.parseAssignmentExpressionInheritIn(); + return Factory.createAssignmentExpression( + leftPat, + right, + SyntaxKinds.AssginOperator, + cloneSourcePosition(left.start), + cloneSourcePosition(right.end), + ); + } + return left; +} +/** + * Util helper for parse qustion mark for function param type + * @returns + */ +export function tryParseOptionalTypeParam(this: Parser) { + let optional = false; + if (this.match(SyntaxKinds.QustionOperator)) { + optional = true; + this.nextToken(); + } + return optional; +} +export function parseTSTypeReference(this: Parser): TSTypeReference { + // TODO: TS garud + const typeName = this.parseTSEntityName(); + const typeArguments = this.tryParseTSTypeParameterInstantiation(false); + return Factory.createTSTypeReference( + typeName, + typeArguments, + cloneSourcePosition(typeName.start), + cloneSourcePosition(typeName.end), + ); +} +export function parseTSEntityName(this: Parser): TSEntityName { + let left: TSEntityName = this.parseIdentifierReference(); + while (this.match(SyntaxKinds.DotOperator)) { + this.nextToken(); + const right = this.parseIdentifierName(); + left = Factory.createTSQualifiedName( + left, + right, + cloneSourcePosition(left.start), + cloneSourcePosition(right.end), + ); + } + return left; +} +export function parseTSFunctionSingnature(this: Parser, expectToken: SyntaxKinds, optional: boolean) { + const typeParameters = this.tryParseTSTypeParameterDeclaration(false); + const parameters = this.parseInType(() => this.parseFunctionParam()) as TSParameter[]; + const matchToken = this.match(expectToken); + if (optional && !matchToken) { + return { + typeParameters, + parameters: parameters, + returnType: undefined, + }; + } + const returnType = this.parseTSReturnTypeOrTypePredicate(expectToken); + return { + parameters, + returnType, + typeParameters, + }; +} +/** + * try parse return type or type predicate, expect syntax is + * ``` + * TypeNode + * TypePredicate + * ``` + * @param {SyntaxKinds} expectToken + * @returns + */ +export function tryParseTSReturnTypeOrTypePredicate(this: Parser, expectToken: SyntaxKinds) { + if (!this.match(expectToken)) { + return; + } + return this.parseTSReturnTypeOrTypePredicate(expectToken); +} +export function parseTSReturnTypeOrTypePredicate( + this: Parser, + expectToken: SyntaxKinds, +): TSTypeAnnotation | undefined { + // parse type or type predication + const { start } = this.expect(expectToken); + const assertion = this.parseTSAssertionInReturnType(); + const typePredicatePrefix = this.parseTypePredicatePrefixInReturnType(); + if (!typePredicatePrefix) { + if (!assertion) { + // : type + const returnType = this.parseTypeAnnoationWithoutColon(start); + return returnType; + } + // : asserts type + const name = this.parseIdentifierReference(); + const typePredicate = Factory.createTSTypePredicate( + name, + true, + undefined, + start, + cloneSourcePosition(name.end), + ); + const returnType = Factory.createTSTypeAnnotation( + typePredicate, + cloneSourcePosition(typePredicate.start), + cloneSourcePosition(typePredicate.end), + ); + return returnType; + } + // : asserts type is otherType + const name = this.parseIdentifierReference(); + this.nextToken(); // eat `is` + const typeAnnotation = this.parseTypeAnnoationWithoutColon(this.getStartPosition()); + const typePredicate = Factory.createTSTypePredicate( + name, + assertion, + typeAnnotation, + start, + cloneSourcePosition(typeAnnotation.end), + ); + const returnType = Factory.createTSTypeAnnotation( + typePredicate, + cloneSourcePosition(typePredicate.start), + cloneSourcePosition(typePredicate.end), + ); + return returnType; +} +export function parseTSAssertionInReturnType(this: Parser): boolean { + if (this.isContextKeyword("asserts")) { + const { kind: lookaheadToken, value, lineTerminatorFlag } = this.lookahead(); + if ((lookaheadToken === SyntaxKinds.Identifier || value === "is") && !lineTerminatorFlag) { + this.nextToken(); + return true; + } + } + return false; +} +export function parseTypePredicatePrefixInReturnType(this: Parser) { + return this.match(SyntaxKinds.Identifier) && this.lookahead().value === "is"; +} +export function parseTSTypeLiteral(this: Parser): TSTypeLiteral { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const members: Array = []; + while (!this.match([SyntaxKinds.EOFToken, SyntaxKinds.BracesRightPunctuator])) { + members.push(this.parseTSTypeElment()); + this.parseTSInterTypeElement(); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return { + kind: SyntaxKinds.TSTypeLiteral, + members, + start, + end, + }; +} +export function parseTSInterfaceBody(this: Parser) { + const { start } = this.expect(SyntaxKinds.BracesLeftPunctuator); + const members: Array = []; + while (!this.match([SyntaxKinds.EOFToken, SyntaxKinds.BracesRightPunctuator])) { + members.push(this.parseTSTypeElment()); + this.parseTSInterTypeElement(); + } + const { end } = this.expect(SyntaxKinds.BracesRightPunctuator); + return Factory.createTSInterfaceBody(members, start, end); +} +export function parseTSTypeElment(this: Parser): TSTypeElement { + switch (this.getToken()) { + case SyntaxKinds.ParenthesesLeftPunctuator: + case SyntaxKinds.LtOperator: { + // TSCallSignatureDeclaration + const start = this.getStartPosition(); + const { parameters, returnType, typeParameters } = this.parseTSFunctionSingnature( + SyntaxKinds.ColonPunctuator, + true, + ); + return Factory.createTSCallSignatureDeclaration( + parameters, + returnType, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); + } + case SyntaxKinds.NewKeyword: { + // TSConstructSignatureDeclaration + const start = this.getStartPosition(); + this.nextToken(); + const { parameters, returnType, typeParameters } = this.parseTSFunctionSingnature( + SyntaxKinds.ColonPunctuator, + true, + ); + return Factory.createTSConstructSignatureDeclaration( + parameters, + returnType, + typeParameters, + start, + this.getLastTokenEndPositon(), + ); + } + default: { + // TSMethodSignature + // TSPropertySignature + const isComputedRef = { isComputed: false }; + const key = this.parsePropertyName(isComputedRef); + let optional = false; + if (this.match(SyntaxKinds.QustionOperator)) { + optional = true; + this.nextToken(); + } + if (this.match(SyntaxKinds.ParenthesesLeftPunctuator) || this.match(SyntaxKinds.LtOperator)) { + const { parameters, returnType, typeParameters } = this.parseTSFunctionSingnature( + SyntaxKinds.ColonPunctuator, + true, + ); + return Factory.createTSMethodSignature( + key, + isComputedRef.isComputed, + optional, + parameters, + returnType, + typeParameters, + cloneSourcePosition(key.start), + this.getLastTokenEndPositon(), + ); + } + const typeAnnotation = this.tryParseTypeAnnotation(); + return Factory.createTSPropertySignature( + key, + isComputedRef.isComputed, + optional, + typeAnnotation, + cloneSourcePosition(key.start), + this.getLastTokenEndPositon(), + ); + } + } +} +export function parseTSInterTypeElement(this: Parser) { + if (this.match([SyntaxKinds.SemiPunctuator, SyntaxKinds.CommaToken])) { + this.nextToken(); + return; + } + if (this.match(SyntaxKinds.BracesRightPunctuator)) { + return; + } + if (this.getLineTerminatorFlag()) { + return; + } + // TODO: should error +} +export function tryParseTypeAnnotation(this: Parser): TSTypeAnnotation | undefined { + if (this.match(SyntaxKinds.ColonPunctuator)) { + return this.parseTypeAnnoation(); + } + return undefined; +} +export function parseTypeAnnoation(this: Parser): TSTypeAnnotation { + const { start } = this.expect(SyntaxKinds.ColonPunctuator); + const typeNode = this.parseTSTypeNode(); + return Factory.createTSTypeAnnotation(typeNode, start, cloneSourcePosition(typeNode.end)); +} +export function parseTypeAnnoationWithoutColon(this: Parser, start: SourcePosition): TSTypeAnnotation { + const typeNode = this.parseTSTypeNode(); + return Factory.createTSTypeAnnotation(typeNode, start, cloneSourcePosition(typeNode.end)); +} diff --git a/web-infras/parser/src/parser/type.ts b/web-infras/parser/src/parser/type.ts index 3d9502e9..bf181f3a 100644 --- a/web-infras/parser/src/parser/type.ts +++ b/web-infras/parser/src/parser/type.ts @@ -1,141 +1,28 @@ -import { SourcePosition } from "web-infra-common"; +import { Keywords, LexicalLiteral, SourcePosition, SyntaxKinds } from "web-infra-common"; import { Token } from "../lexer/type"; export type ExpectToken = Omit & { start: SourcePosition; end: SourcePosition; }; -/** - * Function Scope structure, Being used for determinate - * current structure context for async, generator, in- - * parameter and in strict mode. - * @member {"FunctionContext"} type - type enum string. - * @member {boolean} isAsync - * @member {boolean} isGenerator - * @member {boolean} inParameter - * @member {boolean} inStrict - */ -export interface FunctionContext { - readonly type: "FunctionContext"; - readonly isArrow: boolean; - isAsync: boolean; - isGenerator: boolean; - inParameter: boolean; - isSimpleParameter: boolean; - inStrict: boolean; -} -/** - * Simple scope structure for block statement. - * @member {"BlockStatement"} type - type enum string. - */ -export interface BlockContext { - readonly type: "BlockContext"; -} -/** - * Scope structure for function body and block statement, - * just a conbinmation of function scope and block scope. - */ -export type ScopeContext = FunctionContext | BlockContext; -/** - * Inspirt by V8's ExpressionScope - */ -export enum ExpressionErrorKind { - YieldExpressionInParameter, - AwaitExpressionImParameter, - AwaitIdentifier, - - YieldIdentifier, - - LetIdentifiier, - EvalIdentifier, - ArgumentsIdentifier, -} - -type ArrowExpressionErrorScope = { - yieldExpressionInParameter: Array; - awaitExpressionInParameter: Array; - awaitIdentifier: Array; - yieldIdentifier: Array; -}; -export function createExpressionErrorRecorder() { - let arrowExpressionErrorScopesIndex = -1; - let arrowExpressionErrorScopes: Array = []; - - function enterArrowExpressionScope() { - arrowExpressionErrorScopesIndex++; - arrowExpressionErrorScopes.push({ - awaitExpressionInParameter: [], - yieldExpressionInParameter: [], - awaitIdentifier: [], - yieldIdentifier: [], - }); - } - - function enterBlankArrowExpressionScope() { - arrowExpressionErrorScopesIndex++; - arrowExpressionErrorScopes.push(null); - } - - function exitArrowExpressionScope() { - const currentIndex = arrowExpressionErrorScopesIndex; - const currentScope = arrowExpressionErrorScopes[currentIndex]; - arrowExpressionErrorScopesIndex--; - if (arrowExpressionErrorScopesIndex < 0) { - arrowExpressionErrorScopesIndex = 0; - arrowExpressionErrorScopes = []; - } - if (currentScope === null) { - arrowExpressionErrorScopes = arrowExpressionErrorScopes.slice(0, currentIndex); - } - } - - function record(kind: ExpressionErrorKind, position: SourcePosition) { - const scope = arrowExpressionErrorScopes[arrowExpressionErrorScopesIndex]; - if (!scope) { - return; - } - switch (kind) { - case ExpressionErrorKind.AwaitExpressionImParameter: { - scope.awaitExpressionInParameter.push(position); - break; - } - case ExpressionErrorKind.YieldExpressionInParameter: { - scope.yieldExpressionInParameter.push(position); - break; - } - case ExpressionErrorKind.AwaitIdentifier: { - scope.awaitIdentifier.push(position); - break; - } - case ExpressionErrorKind.YieldIdentifier: { - scope.yieldIdentifier.push(position); - break; - } - } - } - function isArrowExpressionScopeHaveError(scope: ArrowExpressionErrorScope) { - return ( - scope.awaitExpressionInParameter.length || - scope.yieldExpressionInParameter.length || - scope.awaitIdentifier.length - ); - } - function getCurrentArrowExpressionScope() { - for (let index = arrowExpressionErrorScopesIndex; index < arrowExpressionErrorScopes.length; ++index) { - const scope = arrowExpressionErrorScopes[index]; - if (scope && isArrowExpressionScopeHaveError(scope)) { - return true; - } - } - return false; - } - - return { - record, - enterArrowExpressionScope, - enterBlankArrowExpressionScope, - exitArrowExpressionScope, - getCurrentArrowExpressionScope, - }; +export const IdentiferWithKeyworArray = [SyntaxKinds.Identifier, ...Keywords]; +export const PreserveWordSet = new Set(LexicalLiteral.preserveword); +export const BindingIdentifierSyntaxKindArray = [ + SyntaxKinds.Identifier, + SyntaxKinds.AwaitKeyword, + SyntaxKinds.YieldKeyword, + SyntaxKinds.LetKeyword, +]; +export const KeywordSet = new Set([ + ...LexicalLiteral.keywords, + ...LexicalLiteral.BooleanLiteral, + ...LexicalLiteral.NullLiteral, + ...LexicalLiteral.UndefinbedLiteral, +]); + +export interface ASTArrayWithMetaData { + nodes: Array; + start: SourcePosition; + end: SourcePosition; } diff --git a/web-infras/parser/tests/fixtures/babel/comments/decorators/decorator-before-export/output.json b/web-infras/parser/tests/fixtures/babel/comments/decorators/decorator-before-export/output.json index ce59da84..e63fcd47 100644 --- a/web-infras/parser/tests/fixtures/babel/comments/decorators/decorator-before-export/output.json +++ b/web-infras/parser/tests/fixtures/babel/comments/decorators/decorator-before-export/output.json @@ -45,7 +45,62 @@ "col": 51, "index": 86 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "dec1", + "start": { + "row": 1, + "col": 10, + "index": 9 + }, + "end": { + "row": 1, + "col": 14, + "index": 13 + } + }, + "start": { + "row": 1, + "col": 9, + "index": 8 + }, + "end": { + "row": 1, + "col": 14, + "index": 13 + } + }, + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "dec2", + "start": { + "row": 1, + "col": 24, + "index": 23 + }, + "end": { + "row": 1, + "col": 28, + "index": 27 + } + }, + "start": { + "row": 1, + "col": 23, + "index": 22 + }, + "end": { + "row": 1, + "col": 28, + "index": 27 + } + } + ] }, "source": null, "start": { diff --git a/web-infras/parser/tests/fixtures/babel/comments/decorators/decorators-legacy-before-export/output.json b/web-infras/parser/tests/fixtures/babel/comments/decorators/decorators-legacy-before-export/output.json index ce59da84..e63fcd47 100644 --- a/web-infras/parser/tests/fixtures/babel/comments/decorators/decorators-legacy-before-export/output.json +++ b/web-infras/parser/tests/fixtures/babel/comments/decorators/decorators-legacy-before-export/output.json @@ -45,7 +45,62 @@ "col": 51, "index": 86 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "dec1", + "start": { + "row": 1, + "col": 10, + "index": 9 + }, + "end": { + "row": 1, + "col": 14, + "index": 13 + } + }, + "start": { + "row": 1, + "col": 9, + "index": 8 + }, + "end": { + "row": 1, + "col": 14, + "index": 13 + } + }, + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "dec2", + "start": { + "row": 1, + "col": 24, + "index": 23 + }, + "end": { + "row": 1, + "col": 28, + "index": 27 + } + }, + "start": { + "row": 1, + "col": 23, + "index": 22 + }, + "end": { + "row": 1, + "col": 28, + "index": 27 + } + } + ] }, "source": null, "start": { diff --git a/web-infras/parser/tests/fixtures/babel/estree/export/decorator-before-export/output.json b/web-infras/parser/tests/fixtures/babel/estree/export/decorator-before-export/output.json index f1ed96b6..06fcbce5 100644 --- a/web-infras/parser/tests/fixtures/babel/estree/export/decorator-before-export/output.json +++ b/web-infras/parser/tests/fixtures/babel/estree/export/decorator-before-export/output.json @@ -45,7 +45,35 @@ "col": 23, "index": 22 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "dec", + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 5, + "index": 4 + } + }, + "start": { + "row": 1, + "col": 1, + "index": 0 + }, + "end": { + "row": 1, + "col": 5, + "index": 4 + } + } + ] }, "source": null, "start": { diff --git a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-call-expr/output.json b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-call-expr/output.json index aea379c2..12eb2472 100644 --- a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-call-expr/output.json +++ b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-call-expr/output.json @@ -42,7 +42,124 @@ "col": 2, "index": 59 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "CallExpression", + "optional": false, + "callee": { + "kind": "Identifer", + "name": "testCallExprDe", + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 16, + "index": 15 + } + }, + "arguments": [ + { + "kind": "Identifer", + "name": "arg1", + "start": { + "row": 1, + "col": 17, + "index": 16 + }, + "end": { + "row": 1, + "col": 21, + "index": 20 + } + }, + { + "kind": "ObjectExpression", + "properties": [ + { + "kind": "ObjectProperty", + "computed": false, + "shorted": false, + "key": { + "kind": "Identifer", + "name": "testProp", + "start": { + "row": 1, + "col": 25, + "index": 24 + }, + "end": { + "row": 1, + "col": 33, + "index": 32 + } + }, + "value": { + "kind": "Identifer", + "name": "argu2", + "start": { + "row": 1, + "col": 35, + "index": 34 + }, + "end": { + "row": 1, + "col": 40, + "index": 39 + } + }, + "start": { + "row": 1, + "col": 25, + "index": 24 + }, + "end": { + "row": 1, + "col": 40, + "index": 39 + } + } + ], + "trailingComma": false, + "start": { + "row": 1, + "col": 23, + "index": 22 + }, + "end": { + "row": 1, + "col": 42, + "index": 41 + } + } + ], + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 43, + "index": 42 + } + }, + "start": { + "row": 1, + "col": 1, + "index": 0 + }, + "end": { + "row": 1, + "col": 43, + "index": 42 + } + } + ] } ], "start": { diff --git a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-member-expr/output.json b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-member-expr/output.json index 65e7a83e..c67ccf96 100644 --- a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-member-expr/output.json +++ b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator-member-expr/output.json @@ -42,7 +42,93 @@ "col": 2, "index": 43 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "MemberExpression", + "computed": false, + "optional": false, + "object": { + "kind": "Identifer", + "name": "otherProp", + "start": { + "row": 1, + "col": 18, + "index": 17 + }, + "end": { + "row": 1, + "col": 27, + "index": 26 + } + }, + "property": { + "kind": "MemberExpression", + "computed": false, + "optional": false, + "object": { + "kind": "Identifer", + "name": "someProp", + "start": { + "row": 1, + "col": 9, + "index": 8 + }, + "end": { + "row": 1, + "col": 17, + "index": 16 + } + }, + "property": { + "kind": "Identifer", + "name": "testDe", + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 8, + "index": 7 + } + }, + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 17, + "index": 16 + } + }, + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 27, + "index": 26 + } + }, + "start": { + "row": 1, + "col": 1, + "index": 0 + }, + "end": { + "row": 1, + "col": 27, + "index": 26 + } + } + ] } ], "start": { diff --git a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator/output.json b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator/output.json index fbe6dd41..a67105ea 100644 --- a/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator/output.json +++ b/web-infras/parser/tests/fixtures/self-added/decorators/class-decorator/output.json @@ -42,7 +42,35 @@ "col": 2, "index": 24 }, - "decorators": null + "decorators": [ + { + "kind": "Decorator", + "expression": { + "kind": "Identifer", + "name": "testDe", + "start": { + "row": 1, + "col": 2, + "index": 1 + }, + "end": { + "row": 1, + "col": 8, + "index": 7 + } + }, + "start": { + "row": 1, + "col": 1, + "index": 0 + }, + "end": { + "row": 1, + "col": 8, + "index": 7 + } + } + ] } ], "start": {