Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: fisker <lionkay@gmail.com>
265 lines
7.9 KiB
JavaScript
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;
|