Files
test-repo/src/language-js/parse/postprocess/index.js
T
renovate[bot]andfisker 109d327bcf Update dependency oxc-parser to v0.101.0 (#18409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: fisker <lionkay@gmail.com>
2025-12-02 19:42:19 +08:00

265 lines
7.9 KiB
JavaScript

import * as assert from "#universal/assert";
import { locEnd, locStart } from "../../loc.js";
import createTypeCheckFunction from "../../utilities/create-type-check-function.js";
import getRaw from "../../utilities/get-raw.js";
import isBlockComment from "../../utilities/is-block-comment.js";
import isLineComment from "../../utilities/is-line-comment.js";
import isTypeCastComment from "../../utilities/is-type-cast-comment.js";
import mergeNestledJsdocComments from "./merge-nestled-jsdoc-comments.js";
import visitNode from "./visit-node.js";
const isNodeWithRaw = createTypeCheckFunction([
// Babel
"RegExpLiteral",
"BigIntLiteral",
"NumericLiteral",
"StringLiteral",
// "NullLiteral",
// "BooleanLiteral",
"DirectiveLiteral",
// ESTree
"Literal",
"JSXText",
"TemplateElement",
// Flow
"StringLiteralTypeAnnotation",
"NumberLiteralTypeAnnotation",
"BigIntLiteralTypeAnnotation",
]);
/**
* @param {{
* text: string,
* parser?: string,
* oxcAstType?: string,
* }} options
*/
function postprocess(ast, options) {
const { parser, text } = options;
const { comments } = ast;
const isOxcTs = parser === "oxc" && options.oxcAstType === "ts";
mergeNestledJsdocComments(comments);
// `InterpreterDirective` from babel parser and flow parser
// Other parsers parse it as comment, babel treat it as comment too
// https://github.com/babel/babel/issues/15116
const program = ast.type === "File" ? ast.program : ast;
if (program.interpreter) {
comments.unshift(program.interpreter);
delete program.interpreter;
}
if (isOxcTs && ast.hashbang) {
comments.unshift(ast.hashbang);
delete ast.hashbang;
}
// In `typescript` and `flow`, `Program` doesn't count whitespace and comments
// See https://github.com/typescript-eslint/typescript-eslint/issues/11026
// See https://github.com/facebook/flow/issues/8537
if (ast.type === "Program") {
ast.range = [0, text.length];
}
let typeCastCommentsEnds;
ast = visitNode(ast, {
onEnter(node) {
switch (node.type) {
case "ParenthesizedExpression": {
const { expression } = node;
const start = locStart(node);
// Align range with `flow`
if (expression.type === "TypeCastExpression") {
expression.range = [start, locEnd(node)];
return expression;
}
let shouldKeepParenthesizedExpression = false;
if (!isOxcTs) {
if (!typeCastCommentsEnds) {
typeCastCommentsEnds = [];
for (const comment of comments) {
if (isTypeCastComment(comment)) {
typeCastCommentsEnds.push(locEnd(comment));
}
}
}
// Keep ParenthesizedExpression nodes only if they have Closure-style type cast comments.
const previousCommentEnd = typeCastCommentsEnds.findLast(
(end) => end <= start,
);
shouldKeepParenthesizedExpression =
previousCommentEnd &&
// check that there are only white spaces between the comment and the parenthesis
text.slice(previousCommentEnd, start).trim().length === 0;
}
if (shouldKeepParenthesizedExpression) {
return;
}
expression.extra = { ...expression.extra, parenthesized: true };
return expression;
}
// This happened when use `oxc-parser` to parse `` `${foo satisfies bar}`; ``
// https://github.com/oxc-project/oxc/issues/11313
case "TemplateLiteral":
/* c8 ignore next 3 */
if (node.expressions.length !== node.quasis.length - 1) {
throw new Error("Malformed template literal.");
}
break;
case "TemplateElement":
// `flow`, `hermes`, `typescript`, and `oxc`(with `{astType: 'ts'}`) follows the `espree` style positions
// https://github.com/eslint/js/blob/5826877f7b33548e5ba984878dd4a8eac8448f87/packages/espree/lib/espree.js#L213
if (
parser === "flow" ||
parser === "hermes" ||
parser === "espree" ||
parser === "typescript" ||
isOxcTs
) {
const start = locStart(node) + 1;
const end = locEnd(node) - (node.tail ? 1 : 2);
node.range = [start, end];
}
break;
// fix unexpected locEnd caused by --no-semi style
case "VariableDeclaration": {
const lastDeclaration = node.declarations.at(-1);
if (lastDeclaration?.init && text[locEnd(lastDeclaration)] !== ";") {
node.range = [locStart(node), locEnd(lastDeclaration)];
}
break;
}
// remove redundant TypeScript nodes
case "TSParenthesizedType":
return node.typeAnnotation;
// For hack-style pipeline
case "TopicReference":
ast.extra = { ...ast.extra, __isUsingHackPipeline: true };
break; // In Flow parser, it doesn't generate union/intersection types for single type
case "TSUnionType":
case "TSIntersectionType":
if (node.types.length === 1) {
return node.types[0];
}
break;
// https://github.com/facebook/hermes/issues/1712
case "ImportExpression":
if (parser === "hermes" && node.attributes && !node.options) {
node.options = node.attributes;
}
break;
}
},
onLeave(node) {
switch (node.type) {
// Children can be parenthesized, need do this in `onLeave`
case "LogicalExpression":
// We remove unneeded parens around same-operator LogicalExpressions
if (isUnbalancedLogicalTree(node)) {
return rebalanceLogicalTree(node);
}
break;
// https://github.com/babel/babel/issues/17506
// It's possible to have parenthesized `argument`, need do this in `onLeave`
case "TSImportType":
if (!node.source && node.argument.type === "TSLiteralType") {
node.source = node.argument.literal;
delete node.argument;
}
break;
}
/* c8 ignore next 3 */
if (process.env.NODE_ENV !== "production") {
assertRaw(node, text);
}
},
});
/* c8 ignore next 3 */
if (process.env.NODE_ENV !== "production") {
assertComments(comments, text);
}
return ast;
}
function isUnbalancedLogicalTree(node) {
return (
node.type === "LogicalExpression" &&
node.right.type === "LogicalExpression" &&
node.operator === node.right.operator
);
}
function rebalanceLogicalTree(node) {
if (!isUnbalancedLogicalTree(node)) {
return node;
}
return rebalanceLogicalTree({
type: "LogicalExpression",
operator: node.operator,
left: rebalanceLogicalTree({
type: "LogicalExpression",
operator: node.operator,
left: node.left,
right: node.right.left,
range: [locStart(node.left), locEnd(node.right.left)],
}),
right: node.right.right,
range: [locStart(node), locEnd(node)],
});
}
/* c8 ignore next */
function assertComments(comments, text) {
for (const comment of comments) {
const commentText = text.slice(locStart(comment), locEnd(comment));
if (isLineComment(comment)) {
const openingMark = text.slice(
0,
text.startsWith("<--") || text.startsWith("-->") ? 3 : 2,
);
assert.ok(openingMark + comment.value, commentText);
return;
}
if (isBlockComment(comment)) {
// Flow
const closingMark = commentText.endsWith("*-/") ? "*-/" : "*/";
assert.equal("/*" + comment.value + closingMark, commentText);
}
}
}
/* c8 ignore next */
function assertRaw(node, text) {
if (!isNodeWithRaw(node)) {
return;
}
const raw = node.type === "TemplateElement" ? node.value.raw : getRaw(node);
assert.equal(raw, text.slice(locStart(node), locEnd(node)));
}
export default postprocess;