/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-parser/src/parser/comments.ts
295 строк
9 KB
Huáng Jùnliàng
refactor(parser): use AST definitions from babel/types (#17853)
12 мар 2026, 12:43
Не верифицирован
12 мар 2026, 12:43
46204ac
Код
Авторство
О чём код?
/*:: declare var invariant; */ import BaseParser from "./base.ts"; import type { Comment, Node } from "../types.ts"; import * as charCodes from "charcodes"; import type { Undone } from "./node.ts"; /** * A whitespace token containing comments */ export type CommentWhitespace = { /** * the start of the whitespace token. */ start: number; /** * the end of the whitespace token. */ end: number; /** * the containing comments */ comments: Comment[]; /** * the immediately preceding AST node of the whitespace token */ leadingNode: Node | null; /** * the immediately following AST node of the whitespace token */ trailingNode: Node | null; /** * the innermost AST node containing the whitespace with minimal size (|end - start|) */ containingNode: Node | null; }; /** * Merge comments with node's trailingComments or assign comments to be * trailingComments. New comments will be placed before old comments * because the commentStack is enumerated reversely. */ function setTrailingComments(node: Undone<Node>, comments: Comment[]) { if (node.trailingComments === undefined) { node.trailingComments = comments; } else { node.trailingComments!.unshift(...comments); } } /** * Merge comments with node's leadingComments or assign comments to be * leadingComments. New comments will be placed before old comments * because the commentStack is enumerated reversely. */ function setLeadingComments(node: Undone<Node>, comments: Comment[]) { if (node.leadingComments === undefined) { node.leadingComments = comments; } else { node.leadingComments!.unshift(...comments); } } /** * Merge comments with node's innerComments or assign comments to be * innerComments. New comments will be placed before old comments * because the commentStack is enumerated reversely. */ export function setInnerComments(node: Undone<Node>, comments: Comment[]) { if (node.innerComments === undefined) { node.innerComments = comments; } else { node.innerComments!.unshift(...comments); } } /** * Given node and elements array, if elements has non-null element, * merge comments to its trailingComments, otherwise merge comments * to node's innerComments */ function adjustInnerComments( node: Undone<Node>, elements: (Node | null)[], commentWS: CommentWhitespace, ) { let lastElement = null; let i = elements.length; while (lastElement === null && i > 0) { lastElement = elements[--i]; } if (lastElement === null || lastElement.start! > commentWS.start) { setInnerComments(node, commentWS.comments); } else { setTrailingComments(lastElement, commentWS.comments); } } export default class CommentsParser extends BaseParser { addComment(comment: Comment): void { if (this.filename) comment.loc!.filename = this.filename; const { commentsLen } = this.state; if (this.comments.length !== commentsLen) { this.comments.length = commentsLen; } this.comments.push(comment); this.state.commentsLen++; } /** * Given a newly created AST node _n_, attach _n_ to a comment whitespace _w_ if applicable * {@see {@link CommentWhitespace}} */ processComment(node: Node): void { const { commentStack } = this.state; const commentStackLength = commentStack.length; if (commentStackLength === 0) return; let i = commentStackLength - 1; const lastCommentWS = commentStack[i]; if (lastCommentWS.start === node.end) { lastCommentWS.leadingNode = node; i--; } const nodeStart = node.start!; // invariant: for all 0 <= j <= i, let c = commentStack[j], c must satisfy c.end < node.end for (; i >= 0; i--) { const commentWS = commentStack[i]; const commentEnd = commentWS.end; if (commentEnd > nodeStart) { // by definition of commentWhiteSpace, this implies commentWS.start > nodeStart // so node can be a containingNode candidate. At this time we can finalize the comment // whitespace, because // 1) its leadingNode or trailingNode, if exists, will not change // 2) its containingNode have been assigned and will not change because it is the // innermost minimal-sized AST node commentWS.containingNode = node; this.finalizeComment(commentWS); commentStack.splice(i, 1); } else { if (commentEnd === nodeStart) { commentWS.trailingNode = node; } // stop the loop when commentEnd <= nodeStart break; } } } /** * Assign the comments of comment whitespaces to related AST nodes. * Also adjust innerComments following trailing comma. */ finalizeComment(commentWS: CommentWhitespace) { const { comments } = commentWS; if (commentWS.leadingNode !== null || commentWS.trailingNode !== null) { if (commentWS.leadingNode !== null) { setTrailingComments(commentWS.leadingNode, comments); } if (commentWS.trailingNode !== null) { setLeadingComments(commentWS.trailingNode, comments); } } else { /*:: invariant(commentWS.containingNode !== null) */ const node = commentWS.containingNode!; const commentStart = commentWS.start; if ( this.input.charCodeAt(this.offsetToSourcePos(commentStart) - 1) === charCodes.comma ) { // If a commentWhitespace follows a comma and the containingNode allows // list structures with trailing comma, merge it to the trailingComment // of the last non-null list element switch (node.type) { case "ObjectExpression": case "ObjectPattern": adjustInnerComments(node, node.properties, commentWS); break; case "CallExpression": case "NewExpression": case "OptionalCallExpression": adjustInnerComments(node, node.arguments, commentWS); break; case "ImportExpression": adjustInnerComments( node, [node.source, node.options ?? null], commentWS, ); break; case "FunctionDeclaration": case "FunctionExpression": case "ArrowFunctionExpression": case "ObjectMethod": case "ClassMethod": case "ClassPrivateMethod": case "TSTypeParameterDeclaration": adjustInnerComments(node, node.params, commentWS); break; case "ArrayExpression": case "ArrayPattern": adjustInnerComments(node, node.elements, commentWS); break; case "ExportNamedDeclaration": case "ImportDeclaration": adjustInnerComments(node, node.specifiers, commentWS); break; case "TSEnumBody": adjustInnerComments(node, node.members, commentWS); break; case "TSInterfaceBody": adjustInnerComments(node, node.body, commentWS); break; default: { setInnerComments(node, comments); } } } else { setInnerComments(node, comments); } } } /** * Drains remaining commentStack and applies finalizeComment * to each comment whitespace. Used only in parseExpression * where the top level AST node is _not_ Program * {@see {@link CommentsParser#finalizeComment}} */ finalizeRemainingComments() { const { commentStack } = this.state; for (let i = commentStack.length - 1; i >= 0; i--) { this.finalizeComment(commentStack[i]); } this.state.commentStack = []; } /* eslint-disable no-irregular-whitespace */ /** * Reset previous node trailing comments. Used in object / class * property parsing. We parse `async`, `static`, `set` and `get` * as an identifier but may reinterpret it into an async/static/accessor * method later. In this case the identifier is not part of the AST and we * should sync the knowledge to commentStacks * * For example, when parsing * ``` * async /* 1 */ function f() {} * ``` * the comment whitespace `/* 1 */` has leading node Identifier(async). When * we see the function token, we create a Function node and mark `/* 1 */` as * inner comments. So `/* 1 */` should be detached from the Identifier node. * * @param node the last finished AST node _before_ current token */ /* eslint-enable no-irregular-whitespace */ resetPreviousNodeTrailingComments(node: Node) { const { commentStack } = this.state; const { length } = commentStack; if (length === 0) return; const commentWS = commentStack[length - 1]; if (commentWS.leadingNode === node) { commentWS.leadingNode = null; } } /** * Attach a node to the comment whitespaces right before/after * the given range. * * This is used to properly attach comments around parenthesized * expressions as leading/trailing comments of the inner expression. */ takeSurroundingComments(node: Node, start: number, end: number) { const { commentStack } = this.state; const commentStackLength = commentStack.length; if (commentStackLength === 0) return; let i = commentStackLength - 1; for (; i >= 0; i--) { const commentWS = commentStack[i]; const commentEnd = commentWS.end; const commentStart = commentWS.start; if (commentStart === end) { commentWS.leadingNode = node; } else if (commentEnd === start) { commentWS.trailingNode = node; } else if (commentEnd < start) { break; } } } }