/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-parser/src/plugins/estree.ts
665 строк
21 KB
Huáng Jùnliàng
polish(parser): improve ImportExpression error recovery (#17902)
19 апр 2026, 21:29
Не верифицирован
19 апр 2026, 21:29
ed4a811
Код
Авторство
О чём код?
import type { TokenType } from "../tokenizer/types.ts"; import type Parser from "../parser/index.ts"; import type * as N from "../types.ts"; import type { Node as NodeType, BaseNode } from "../types.ts"; import { Position } from "../util/location.ts"; import { Errors } from "../parse-error.ts"; import type { Undone } from "../parser/node.ts"; import type { BindingFlag } from "../util/scopeflags.ts"; import { OptionFlags } from "../options.ts"; import type { ExpressionErrors } from "../parser/util.ts"; import type { ParseResult, File } from "../index.ts"; function toESTreeLocation(node: any) { const { start, end } = node.loc; node.loc.start = new Position(start.line, start.column); node.loc.end = new Position(end.line, end.column); return node; } export default (superClass: typeof Parser) => class ESTreeParserMixin extends superClass implements Parser { createPosition(loc: Position): Position { return new Position(loc.line, loc.column); } parse(): ParseResult<File> { const file = super.parse(); if (this.optionFlags & OptionFlags.Tokens) { file.tokens = file.tokens!.map(toESTreeLocation); } return toESTreeLocation(file); } // @ts-expect-error ESTree plugin changes node types parseRegExpLiteral({ pattern, flags }): N.EstreeRegExpLiteral { let regex: RegExp | null = null; try { regex = new RegExp(pattern, flags); } catch (_) { // In environments that don't support these flags value will // be null as the regex can't be represented natively. } const node = this.estreeParseLiteral<N.EstreeRegExpLiteral>(regex); node.regex = { pattern, flags }; return node; } // @ts-expect-error ESTree plugin changes node types parseBigIntLiteral(value: any): N.Node { // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral let bigInt: bigint | null; try { bigInt = BigInt(value); } catch { bigInt = null; } const node = this.estreeParseLiteral<N.EstreeBigIntLiteral>(bigInt); node.bigint = String(node.value || value); return node; } estreeParseLiteral<T extends N.EstreeLiteral>(value: any) { // @ts-expect-error ESTree plugin changes node types return this.parseLiteral<T>(value, "Literal"); } // @ts-expect-error ESTree plugin changes node types parseStringLiteral(value: any): N.EstreeLiteral { return this.estreeParseLiteral(value); } // @ts-expect-error ESTree plugin changes node types parseNumericLiteral(value: any): N.EstreeLiteral { return this.estreeParseLiteral(value); } // @ts-expect-error ESTree plugin changes node types parseNullLiteral(): N.EstreeLiteral { return this.estreeParseLiteral(null); } parseBooleanLiteral(value: boolean): N.BooleanLiteral { // @ts-expect-error ESTree plugin changes node types return this.estreeParseLiteral(value); } // https://github.com/estree/estree/blob/master/es2020.md#chainexpression estreeParseChainExpression( node: N.Expression, endNode: NodeType, ): N.EstreeChainExpression { const chain = this.startNodeAtNode<N.EstreeChainExpression>(node); chain.expression = node; return this.finishNodeAtNode(chain, "ChainExpression", endNode); } // Cast a Directive to an ExpressionStatement. Mutates the input Directive. directiveToStmt(directive: N.Directive): N.ExpressionStatement { const expression = directive.value as any as N.EstreeLiteral; // @ts-expect-error delete non-optional properties delete directive.value; this.castNodeTo(expression, "Literal"); expression.raw = expression.extra!.raw; expression.value = expression.extra!.expressionValue; const stmt = this.castNodeTo(directive, "ExpressionStatement"); // @ts-expect-error ESTree plugin changes node types stmt.expression = expression; // @ts-expect-error ESTree plugin changes node types stmt.directive = expression.extra!.rawValue; delete expression.extra; return stmt; } /** * The TS-ESLint always define optional AST properties, here we provide the * default value for such properties immediately after `finishNode` was invoked. * This hook will be implemented by the typescript plugin. * * Note: This hook should be manually invoked when we change the `type` of a given AST * node, to ensure that the optional properties are correctly filled. * @param node The AST node finished by finishNode */ // eslint-disable-next-line @typescript-eslint/no-unused-vars fillOptionalPropertiesForTSESLint(node: NodeType) {} cloneEstreeStringLiteral(node: N.EstreeLiteral): N.EstreeLiteral { const { start, end, loc, range, raw, value } = node; const cloned = Object.create(node.constructor.prototype); cloned.type = "Literal"; cloned.start = start; cloned.end = end; cloned.loc = loc; cloned.range = range; cloned.raw = raw; cloned.value = value; return cloned; } // ================================== // Overrides // ================================== initFunction(node: Undone<N.Function>, isAsync: boolean): void { super.initFunction(node, isAsync); (node as unknown as N.BodilessFunctionOrMethodBase).expression = false; } checkDeclaration(node: N.Pattern | N.ObjectProperty): void { if (node != null && this.isObjectProperty(node)) { // @ts-expect-error plugin typings this.checkDeclaration((node as unknown as N.EstreeProperty).value); } else { super.checkDeclaration(node); } } getObjectOrClassMethodParams( method: Undone<N.ObjectMethod | N.ClassMethod>, ) { return (method as unknown as N.EstreeMethodDefinition).value.params; } isValidDirective(stmt: N.Statement): stmt is N.ExpressionStatement { return ( stmt.type === "ExpressionStatement" && (stmt.expression as N.Node as N.ESTreeExpression).type === "Literal" && typeof (stmt.expression as N.Node as N.EstreeLiteral).value === "string" && !(stmt.expression as N.Node as N.EstreeLiteral).extra?.parenthesized ); } parseBlockBody( node: N.BlockStatementLike, allowDirectives: boolean | undefined | null, topLevel: boolean, end: TokenType, afterBlockParse?: (hasStrictModeDirective: boolean) => void, ): void { super.parseBlockBody( node, allowDirectives, topLevel, end, afterBlockParse, ); const directiveStatements = node.directives.map(d => this.directiveToStmt(d), ); // @ts-expect-error estree plugin typings node.body = directiveStatements.concat(node.body); // @ts-expect-error delete non-optional properties delete node.directives; } parsePrivateName(): any { const node = super.parsePrivateName(); return this.convertPrivateNameToPrivateIdentifier(node); } convertPrivateNameToPrivateIdentifier( node: N.PrivateName, ): N.EstreePrivateIdentifier { const name = super.getPrivateNameSV(node); // @ts-expect-error delete non-optional properties delete node.id; // @ts-expect-error mutate AST types node.name = name; return this.castNodeTo(node, "PrivateIdentifier"); } // @ts-expect-error ESTree plugin changes node types isPrivateName(node: N.Node): node is N.EstreePrivateIdentifier { return node.type === "PrivateIdentifier"; } // @ts-expect-error ESTree plugin changes node types getPrivateNameSV(node: N.EstreePrivateIdentifier): string { return node.name; } // @ts-expect-error plugin may override interfaces parseLiteral<T extends N.Literal>(value: any, type: T["type"]): T { const node = super.parseLiteral<T>(value, type); // @ts-expect-error mutating AST types node.raw = node.extra.raw; delete node.extra; return node; } parseFunctionBody( node: N.Function, allowExpression?: boolean | null, isMethod: boolean = false, ): void { super.parseFunctionBody(node, allowExpression, isMethod); (node as unknown as N.BodilessFunctionOrMethodBase).expression = node.body.type !== "BlockStatement"; } // @ts-expect-error plugin may override interfaces parseMethod< T extends N.ClassPrivateMethod | N.ObjectMethod | N.ClassMethod, >( node: Undone<T>, isGenerator: boolean, isAsync: boolean, isConstructor: boolean, allowDirectSuper: boolean, type: T["type"], inClassScope: boolean = false, ): | N.EstreeProperty | N.EstreeMethodDefinition | N.EstreeTSAbstractMethodDefinition { let funcNode = this.startNode<N.MethodLike>(); funcNode.kind = node.kind; // provide kind, so super method correctly sets state funcNode = super.parseMethod( // @ts-expect-error estree plugin change node types funcNode, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope, ); // @ts-expect-error delete non-optional properties delete funcNode.kind; const { typeParameters } = node; if (typeParameters) { delete node.typeParameters; funcNode.typeParameters = typeParameters; this.resetStartLocationFromNode(funcNode, typeParameters); } const valueNode = this.castNodeTo( funcNode as N.MethodLike, // @ts-expect-error test if body in funcNode and cast to different node types this.hasPlugin("typescript") && !funcNode.body ? "TSEmptyBodyFunctionExpression" : "FunctionExpression", ); ( node as unknown as Undone< | N.EstreeProperty | N.EstreeMethodDefinition | N.EstreeTSAbstractMethodDefinition > ).value = valueNode; if (type === "ClassPrivateMethod") { node.computed = false; } if (this.hasPlugin("typescript")) { // @ts-expect-error todo(flow->ts) property not defined for all types in union if (node.abstract) { // @ts-expect-error remove abstract from TSAbstractMethodDefinition delete node.abstract; return this.finishNode( // @ts-expect-error cast methods to estree types node as Undone<N.EstreeTSAbstractMethodDefinition>, "TSAbstractMethodDefinition", ); } } if (type === "ObjectMethod") { if ((node as any as N.ObjectMethod).kind === "method") { (node as any as N.EstreeProperty).kind = "init"; } (node as any as N.EstreeProperty).shorthand = false; return this.finishNode( // @ts-expect-error cast methods to estree types node as Undone<N.EstreeProperty>, "Property", ); } else { return this.finishNode( // @ts-expect-error cast methods to estree types node as Undone<N.EstreeMethodDefinition>, "MethodDefinition", ); } } nameIsConstructor( key: N.Expression | N.PrivateName | N.EstreeLiteral, ): boolean { if (key.type === "Literal") return key.value === "constructor"; return super.nameIsConstructor(key); } parseClassProperty(...args: [N.ClassProperty]): any { const propertyNode = super.parseClassProperty(...args); if (propertyNode.abstract && this.hasPlugin("typescript")) { delete propertyNode.abstract; this.castNodeTo(propertyNode, "TSAbstractPropertyDefinition"); } else { this.castNodeTo(propertyNode, "PropertyDefinition"); } return propertyNode; } parseClassPrivateProperty(...args: [N.ClassPrivateProperty]): any { const propertyNode = super.parseClassPrivateProperty(...args); // @ts-expect-error abstract is not defined on ClassPrivateProperty if (propertyNode.abstract && this.hasPlugin("typescript")) { this.castNodeTo(propertyNode, "TSAbstractPropertyDefinition"); } else { this.castNodeTo(propertyNode, "PropertyDefinition"); } // @ts-expect-error computed is not defined on ClassPrivateProperty propertyNode.computed = false; return propertyNode; } parseClassAccessorProperty( this: Parser, node: N.ClassAccessorProperty, ): any { const accessorPropertyNode = super.parseClassAccessorProperty(node); if (accessorPropertyNode.abstract && this.hasPlugin("typescript")) { delete accessorPropertyNode.abstract; this.castNodeTo(accessorPropertyNode, "TSAbstractAccessorProperty"); } else { this.castNodeTo(accessorPropertyNode, "AccessorProperty"); } return accessorPropertyNode; } parseObjectProperty( prop: N.ObjectProperty, startLoc: Position | undefined | null, isPattern: boolean, refExpressionErrors?: ExpressionErrors | null, ): N.ObjectProperty | undefined | null { const node: N.EstreeProperty = super.parseObjectProperty( prop, startLoc, isPattern, refExpressionErrors, ) as any; if (node) { node.kind = "init"; this.castNodeTo(node, "Property"); } return node as any; } finishObjectProperty(node: Undone<N.ObjectProperty>): N.ObjectProperty { (node as unknown as Undone<N.EstreeProperty>).kind = "init"; return this.finishNode( node as unknown as Undone<N.EstreeProperty>, "Property", ) as any; } isValidLVal( type: string, disallowCallExpression: boolean, isUnparenthesizedInAssign: boolean, binding: BindingFlag, ) { return type === "Property" ? "value" : super.isValidLVal( type, disallowCallExpression, isUnparenthesizedInAssign, binding, ); } isAssignable(node: N.Node, isBinding?: boolean): boolean { if (node != null && this.isObjectProperty(node)) { return this.isAssignable(node.value, isBinding); } return super.isAssignable(node, isBinding); } toAssignable(node: N.Node, isLHS: boolean = false): void { if (node != null && this.isObjectProperty(node)) { const { key, value } = node; if (this.isPrivateName(key)) { this.classScope.usePrivateName( this.getPrivateNameSV(key), key.start!, ); } this.toAssignable(value, isLHS); } else { super.toAssignable(node, isLHS); } } toAssignableObjectExpressionProp( prop: N.Node, isLast: boolean, isLHS: boolean, ) { if ( prop.type === "Property" && (prop.kind === "get" || prop.kind === "set") ) { this.raise(Errors.PatternHasAccessor, prop.key); } else if (prop.type === "Property" && prop.method) { this.raise(Errors.PatternHasMethod, prop.key); } else { super.toAssignableObjectExpressionProp(prop, isLast, isLHS); } } finishCallExpression<T extends N.CallExpression | N.OptionalCallExpression>( unfinished: Undone<T>, optional: boolean, ): T { const node = super.finishCallExpression(unfinished, optional); if (node.callee.type === "Import") { this.castNodeTo(node, "ImportExpression"); (node as N.Node as N.ImportExpression).source = node .arguments[0] as N.Expression; (node as N.Node as N.ImportExpression).options = (node.arguments[1] as N.Expression) ?? null; // arguments isn't optional in the type definition // @ts-expect-error delete non-optional properties delete node.arguments; // callee isn't optional in the type definition // @ts-expect-error delete non-optional properties delete node.callee; } else if (node.type === "OptionalCallExpression") { this.castNodeTo(node, "CallExpression"); } else { // @ts-expect-error ESTree AST: optional is not defined on CallExpression node.optional = false; } return node; } parseExport( unfinished: Undone<N.AnyExport>, decorators: N.Decorator[] | null, ) { const exportStartLoc = this.state.lastTokStartLoc!; const node = super.parseExport(unfinished, decorators); switch (node.type) { case "ExportAllDeclaration": // @ts-expect-error mutating AST types node.exported = null; break; case "ExportNamedDeclaration": if ( node.specifiers.length === 1 && node.specifiers[0].type === "ExportNamespaceSpecifier" ) { this.castNodeTo(node, "ExportAllDeclaration"); // @ts-expect-error mutating AST types node.exported = node.specifiers[0].exported; // @ts-expect-error The ESTree AST shape differs from the Babel AST delete node.specifiers; } // fallthrough case "ExportDefaultDeclaration": { const { declaration } = node; if ( declaration?.type === "ClassDeclaration" && // @ts-expect-error comparing undefined and number declaration.decorators?.length > 0 && // decorator comes before export declaration.start === node.start ) { this.resetStartLocation( node, // For compatibility with ESLint's keyword-spacing rule, which assumes that an // export declaration must start with export. // https://github.com/babel/babel/issues/15085 // Here we reset export declaration's start to be the start of the export token exportStartLoc, ); } } break; } return node; } // @ts-expect-error plugin may override interfaces stopParseSubscript(base: N.Expression, state: N.ParseSubscriptState) { const node = super.stopParseSubscript(base, state); if (state.optionalChainMember) { return this.estreeParseChainExpression(node, base); } return node; } parseMember( base: N.Expression, startLoc: Position, state: N.ParseSubscriptState, computed: boolean, optional: boolean, ) { const node = super.parseMember(base, startLoc, state, computed, optional); if (node.type === "OptionalMemberExpression") { this.castNodeTo(node, "MemberExpression"); } else { // @ts-expect-error ESTree AST: optional is not defined on MemberExpression node.optional = false; } return node; } isOptionalMemberExpression(node: N.Node) { if (node.type === "ChainExpression") { return node.expression.type === "MemberExpression"; } return super.isOptionalMemberExpression(node); } hasPropertyAsPrivateName(node: N.Node): boolean { if (node.type === "ChainExpression") { node = node.expression; } return super.hasPropertyAsPrivateName(node); } // @ts-expect-error ESTree plugin changes node types isObjectProperty(node: N.Node): node is N.EstreeProperty { return node.type === "Property" && node.kind === "init" && !node.method; } // @ts-expect-error ESTree plugin changes node types isObjectMethod(node: N.Node): node is N.EstreeProperty { return ( node.type === "Property" && (node.method || node.kind === "get" || node.kind === "set") ); } /* ============================================================ * * parser/node.ts * * ============================================================ */ castNodeTo<T extends N.Node["type"]>( node: N.Node, type: T, ): Extract<N.Node, { type: T }> { const result = super.castNodeTo(node, type); this.fillOptionalPropertiesForTSESLint(result); return result; } cloneIdentifier<T extends N.Identifier | N.Placeholder>(node: T): T { const cloned = super.cloneIdentifier(node); this.fillOptionalPropertiesForTSESLint(cloned); return cloned; } cloneStringLiteral< T extends N.EstreeLiteral | N.StringLiteral | N.Placeholder, >(node: T): T { if (node.type === "Literal") { return this.cloneEstreeStringLiteral(node) as T; } return super.cloneStringLiteral(node); } finishNodeAt<T extends NodeType>( node: Undone<T>, type: T["type"], endLoc: Position, ): T { return toESTreeLocation(super.finishNodeAt(node, type, endLoc)); } finishNodeAtNode<T extends NodeType>( node: Undone<T>, type: T["type"], endNode: NodeType, ): T { return toESTreeLocation(super.finishNodeAtNode(node, type, endNode)); } // Override for TS-ESLint that does not allow optional AST properties finishNode<T extends NodeType>(node: Undone<T>, type: T["type"]): T { const result = super.finishNode(node, type); this.fillOptionalPropertiesForTSESLint(result); return result; } resetStartLocation(node: N.Node, startLoc: Position) { super.resetStartLocation(node, startLoc); toESTreeLocation(node); } resetEndLocation( node: BaseNode, endLoc: Position = this.state.lastTokEndLoc!, ): void { super.resetEndLocation(node, endLoc); toESTreeLocation(node); } };