/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-parser/src/plugins/placeholders.ts
444 строки
14 KB
Huáng Jùnliàng
Bump typescript-eslint to 8.59.2 (#17984)
11 май 2026, 16:41
Не верифицирован
11 май 2026, 16:41
b34c4e7
Код
Авторство
О чём код?
import * as charCodes from "charcodes"; import { tokenLabelName, tt } from "../tokenizer/types.ts"; import type Parser from "../parser/index.ts"; import type * as N from "../types.ts"; import { ParseErrorEnum, type ParseErrorTemplates } from "../parse-error.ts"; import type { Undone } from "../parser/node.ts"; import type { ExpressionErrors } from "../parser/util.ts"; import type { BindingFlag } from "../util/scopeflags.ts"; type PossiblePlaceholders = { Identifier: N.Identifier; StringLiteral: N.StringLiteral; Expression: N.Expression; Statement: N.Statement; Declaration: N.Declaration; BlockStatement: N.BlockStatement; ClassBody: N.ClassBody; Pattern: Exclude<N.Pattern, N.AssignmentPattern>; }; export type PlaceholderTypes = keyof PossiblePlaceholders; type NodeOf<T extends keyof PossiblePlaceholders> = PossiblePlaceholders[T]; // todo: when there is proper union type for Node // type NodeOf<T extends PlaceholderTypes> = Extract<N.Node, { type: T }>; // todo: Placeholder<T> breaks everything, because its type is incompatible with // the substituted nodes. type MaybePlaceholder<T extends PlaceholderTypes> = NodeOf<T>; // | Placeholder<T> /* eslint sort-keys: "error" */ export const PlaceholderErrorTemplates = { ClassNameIsRequired: "A class name is required.", UnexpectedSpace: "Unexpected space in placeholder.", } satisfies ParseErrorTemplates; const PlaceholderErrors = ParseErrorEnum`placeholders`( PlaceholderErrorTemplates, ); export default (superClass: typeof Parser) => class PlaceholdersParserMixin extends superClass implements Parser { parsePlaceholder<T extends PlaceholderTypes>( expectedNode: T, ): /*?N.Placeholder<T>*/ MaybePlaceholder<T> | undefined { if (this.match(tt.placeholder)) { const node = this.startNode<N.Placeholder<T>>(); this.next(); this.assertNoSpace(); // We can't use this.parseIdentifier because // we don't want nested placeholders. node.name = super.parseIdentifier(/* liberal */ true); this.assertNoSpace(); this.expect(tt.placeholder); return this.finishPlaceholder(node, expectedNode); } } finishPlaceholder<T extends PlaceholderTypes>( node: Undone<N.Placeholder> | Undone<NodeOf<PlaceholderTypes>>, expectedNode: T, ): /*N.Placeholder<T>*/ MaybePlaceholder<T> { let placeholder = node as unknown as N.Placeholder<T>; if (!placeholder.expectedNode || !placeholder.type) { placeholder = this.finishNode( placeholder, "Placeholder", ) as unknown as N.Placeholder<T>; } placeholder.expectedNode = expectedNode; // avoid TS2590: Expression produces a union type that is too complex to represent. // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return placeholder as unknown as MaybePlaceholder<T>; } /* ============================================================ * * tokenizer/index.ts * * ============================================================ */ getTokenFromCode(code: number) { if ( code === charCodes.percentSign && this.input.charCodeAt(this.state.pos + 1) === charCodes.percentSign ) { this.finishOp(tt.placeholder, 2); } else { super.getTokenFromCode(code); } } /* ============================================================ * * parser/expression.ts * * ============================================================ */ parseExprAtom( refExpressionErrors?: ExpressionErrors | null, ): MaybePlaceholder<"Expression"> | N.Super | N.Import { return ( this.parsePlaceholder("Expression") || super.parseExprAtom(refExpressionErrors) ); } parseIdentifier(liberal?: boolean): MaybePlaceholder<"Identifier"> { // NOTE: This function only handles identifiers outside of // expressions and binding patterns, since they are already // handled by the parseExprAtom and parseBindingAtom functions. // This is needed, for example, to parse "class %%NAME%% {}". return ( this.parsePlaceholder("Identifier") || super.parseIdentifier(liberal) ); } checkReservedWord( word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean, ) { // Sometimes we call #checkReservedWord(node.name), expecting // that node is an Identifier. If it is a Placeholder, name // will be undefined. if (word !== undefined) { super.checkReservedWord(word, startLoc, checkKeywords, isBinding); } } /* ============================================================ * * parser/node.ts * * ============================================================ */ cloneIdentifier<T extends N.Identifier | N.Placeholder>(node: T): T { const cloned = super.cloneIdentifier(node); if (cloned.type === "Placeholder") { cloned.expectedNode = (node as N.Placeholder).expectedNode; } return cloned; } cloneStringLiteral< T extends N.EstreeLiteral | N.StringLiteral | N.Placeholder, >(node: T): T { if (node.type === "Placeholder") { return this.cloneIdentifier(node) as T; } return super.cloneStringLiteral(node); } /* ============================================================ * * parser/lval.ts * * ============================================================ */ parseBindingAtom(): MaybePlaceholder<"Pattern" | "Identifier"> { return this.parsePlaceholder("Pattern") || super.parseBindingAtom(); } isValidLVal( type: string, disallowCallExpression: boolean, isParenthesized: boolean, binding: BindingFlag, ) { return ( type === "Placeholder" || super.isValidLVal( type, disallowCallExpression, isParenthesized, binding, ) ); } toAssignable(node: N.Node, isLHS: boolean): void { if ( node && node.type === "Placeholder" && node.expectedNode === "Expression" ) { node.expectedNode = "Pattern"; } else { super.toAssignable(node, isLHS); } } /* ============================================================ * * parser/statement.ts * * ============================================================ */ chStartsBindingIdentifier(ch: number, pos: number): boolean { if (super.chStartsBindingIdentifier(ch, pos)) { return true; } // Accept "let %%" as the start of "let %%placeholder%%", as though the // placeholder were an identifier. const next = this.nextTokenStart(); if ( this.input.charCodeAt(next) === charCodes.percentSign && this.input.charCodeAt(next + 1) === charCodes.percentSign ) { return true; } return false; } verifyBreakContinue( node: N.BreakStatement | N.ContinueStatement, isBreak: boolean, ) { // @ts-expect-error: node.label could be Placeholder if (node.label?.type === "Placeholder") return; super.verifyBreakContinue(node, isBreak); } // @ts-expect-error Plugin will override parser interface parseExpressionStatement( node: MaybePlaceholder<"Statement">, expr: MaybePlaceholder<"Expression">, ): MaybePlaceholder<"Statement"> { // @ts-expect-error placeholder typings if (expr.type !== "Placeholder" || expr.extra?.parenthesized) { // @ts-expect-error placeholder typings return super.parseExpressionStatement(node, expr); } if (this.match(tt.colon)) { // @ts-expect-error placeholder typings const stmt: N.LabeledStatement = node; stmt.label = this.finishPlaceholder(expr, "Identifier"); this.next(); stmt.body = super.parseStatementOrSloppyAnnexBFunctionDeclaration(); return this.finishNode(stmt, "LabeledStatement"); } this.semicolon(); const stmtPlaceholder = node as unknown as N.Placeholder<"Statement">; stmtPlaceholder.name = (expr as N.Placeholder).name; return this.finishPlaceholder(stmtPlaceholder, "Statement"); } parseBlock( allowDirectives?: boolean, createNewLexicalScope?: boolean, afterBlockParse?: (hasStrictModeDirective: boolean) => void, ): MaybePlaceholder<"BlockStatement"> { return ( this.parsePlaceholder("BlockStatement") || super.parseBlock( allowDirectives, createNewLexicalScope, afterBlockParse, ) ); } parseFunctionId( requireId?: boolean, ): MaybePlaceholder<"Identifier"> | undefined | null { return ( this.parsePlaceholder("Identifier") || super.parseFunctionId(requireId) ); } // @ts-expect-error Plugin will override parser interface parseClass<T extends N.Class>( node: T, isStatement: /* T === ClassDeclaration */ boolean, optionalId?: boolean, ): T { const type = isStatement ? "ClassDeclaration" : "ClassExpression"; this.next(); const oldStrict = this.state.strict; const placeholder = this.parsePlaceholder("Identifier"); if (placeholder) { if ( this.match(tt._extends) || this.match(tt.placeholder) || this.match(tt.braceL) ) { node.id = placeholder; } else if (optionalId || !isStatement) { node.id = null; node.body = this.finishPlaceholder(placeholder, "ClassBody"); return this.finishNode(node, type); } else { throw this.raise( PlaceholderErrors.ClassNameIsRequired, this.state.startLoc, ); } } else { this.parseClassId(node, isStatement, optionalId); } super.parseClassSuper(node); node.body = this.parsePlaceholder("ClassBody") || super.parseClassBody(!!node.superClass, oldStrict); return this.finishNode(node, type); } parseExport( node: Undone< | N.ExportDefaultDeclaration | N.ExportAllDeclaration | N.ExportNamedDeclaration >, decorators: N.Decorator[] | null, ): N.AnyExport { const placeholder = this.parsePlaceholder("Identifier"); if (!placeholder) return super.parseExport(node, decorators); const node2 = node as Undone<N.ExportNamedDeclaration>; if (!this.isContextual(tt._from) && !this.match(tt.comma)) { // export %%DECL%%; node2.specifiers = []; node2.source = null; // @ts-expect-error Consider refine placeholder types here to ExportNamedDeclaration["declaration"]. node2.declaration = this.finishPlaceholder(placeholder, "Declaration"); return this.finishNode(node2, "ExportNamedDeclaration"); } // export %%NAME%% from "foo"; this.expectPlugin("exportDefaultFrom"); const specifier = this.startNode<N.ExportDefaultSpecifier>(); specifier.exported = placeholder; node2.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; return super.parseExport(node2, decorators); } isExportDefaultSpecifier(): boolean { if (this.match(tt._default)) { const next = this.nextTokenStart(); if (this.isUnparsedContextual(next, "from")) { if ( this.input.startsWith( tokenLabelName(tt.placeholder), this.nextTokenStartSince(next + 4), ) ) { return true; } } } return super.isExportDefaultSpecifier(); } maybeParseExportDefaultSpecifier( node: Undone< | N.ExportDefaultDeclaration | N.ExportAllDeclaration | N.ExportNamedDeclaration >, maybeDefaultIdentifier: N.Identifier | null, ): node is Undone<N.ExportNamedDeclaration> { if ((node as N.ExportNamedDeclaration).specifiers?.length) { // "export %%NAME%%" has already been parsed by #parseExport. return true; } return super.maybeParseExportDefaultSpecifier( node, maybeDefaultIdentifier, ); } checkExport(node: N.ExportNamedDeclaration): void { const { specifiers } = node; if (specifiers?.length) { node.specifiers = specifiers.filter( // @ts-expect-error placeholder typings node => node.exported.type === "Placeholder", ); } super.checkExport(node); node.specifiers = specifiers; } parseImport( node: Undone<N.ImportDeclaration>, ): N.ImportDeclaration | N.TSImportEqualsDeclaration { const placeholder = this.parsePlaceholder("Identifier"); if (!placeholder) return super.parseImport(node); node.specifiers = []; if (!this.isContextual(tt._from) && !this.match(tt.comma)) { // import %%STRING%%; node.source = this.finishPlaceholder(placeholder, "StringLiteral"); this.semicolon(); return this.finishNode(node, "ImportDeclaration"); } // import %%DEFAULT%% ... const specifier = this.startNodeAtNode<N.ImportDefaultSpecifier>(placeholder); specifier.local = placeholder; node.specifiers.push( this.finishNode(specifier, "ImportDefaultSpecifier"), ); if (this.eat(tt.comma)) { // import %%DEFAULT%%, * as ... const hasStarImport = this.maybeParseStarImportSpecifier(node); // import %%DEFAULT%%, { ... if (!hasStarImport) this.parseNamedImportSpecifiers(node); } this.expectContextual(tt._from); node.source = this.parseImportSource(); this.semicolon(); return this.finishNode(node, "ImportDeclaration"); } parseImportSource(): MaybePlaceholder<"StringLiteral"> { // import ... from %%STRING%%; return ( this.parsePlaceholder("StringLiteral") || super.parseImportSource() ); } // Throws if the current token and the prev one are separated by a space. assertNoSpace(): void { if ( this.state.start > this.offsetToSourcePos(this.state.lastTokEndLoc!.index) ) { this.raise( PlaceholderErrors.UnexpectedSpace, this.state.lastTokEndLoc!, ); } } };