Files
test-repo/src/language-js/utilities/index.js
T
2025-12-06 14:39:00 +08:00

1180 lines
29 KiB
JavaScript

import { hasDescendant } from "../../utilities/ast.js";
import getStringWidth from "../../utilities/get-string-width.js";
import hasNewline from "../../utilities/has-newline.js";
import isNextLineEmptyAfterIndex from "../../utilities/is-next-line-empty.js";
import isNonEmptyArray from "../../utilities/is-non-empty-array.js";
import isObject from "../../utilities/is-object.js";
import printString from "../../utilities/print-string.js";
import { hasSameLocStart, locEnd, locStart } from "../loc.js";
import getVisitorKeys from "../traverse/get-visitor-keys.js";
import createTypeCheckFunction from "./create-type-check-function.js";
import getRaw from "./get-raw.js";
import isBlockComment from "./is-block-comment.js";
import isFlowKeywordType from "./is-flow-keyword-type.js";
import isLineComment from "./is-line-comment.js";
import isNodeMatches from "./is-node-matches.js";
import isTsKeywordType from "./is-ts-keyword-type.js";
/**
@import * as Estree from "../types/estree.js"
@import AstPath from "../../common/ast-path.js"
*/
/**
* @param {Estree.Node} node
* @param {(node: Estree.Node) => boolean} predicate
* @returns {boolean}
*/
function hasNode(node, predicate) {
return predicate(node) || hasDescendant(node, { getVisitorKeys, predicate });
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function hasNakedLeftSide(node) {
return (
node.type === "AssignmentExpression" ||
node.type === "BinaryExpression" ||
node.type === "LogicalExpression" ||
node.type === "NGPipeExpression" ||
node.type === "ConditionalExpression" ||
isCallExpression(node) ||
isMemberExpression(node) ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
node.type === "BindExpression" ||
(node.type === "UpdateExpression" && !node.prefix) ||
isBinaryCastExpression(node) ||
node.type === "TSNonNullExpression" ||
node.type === "ChainExpression"
);
}
function getLeftSide(node) {
if (node.expressions) {
return node.expressions[0];
}
return (
node.left ??
node.test ??
node.callee ??
node.object ??
node.tag ??
node.argument ??
node.expression
);
}
function getLeftSidePathName(node) {
if (node.expressions) {
return ["expressions", 0];
}
if (node.left) {
return ["left"];
}
if (node.test) {
return ["test"];
}
if (node.object) {
return ["object"];
}
if (node.callee) {
return ["callee"];
}
if (node.tag) {
return ["tag"];
}
if (node.argument) {
return ["argument"];
}
if (node.expression) {
return ["expression"];
}
throw new Error("Unexpected node has no left side.");
}
const isExportDeclaration = createTypeCheckFunction([
"ExportDefaultDeclaration",
"DeclareExportDeclaration",
"ExportNamedDeclaration",
"ExportAllDeclaration",
"DeclareExportAllDeclaration",
]);
// These two functions exists because we used support `recordAndTuple`
// Feel free to check `node.type` instead
// https://github.com/prettier/prettier/pull/17363
const isArrayExpression = createTypeCheckFunction(["ArrayExpression"]);
const isObjectExpression = createTypeCheckFunction(["ObjectExpression"]);
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isNullishCoalescing(node) {
return node.type === "LogicalExpression" && node.operator === "??";
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isNumericLiteral(node) {
return (
node.type === "NumericLiteral" ||
(node.type === "Literal" && typeof node.value === "number")
);
}
function isBooleanLiteral(node) {
return (
node.type === "BooleanLiteral" ||
(node.type === "Literal" && typeof node.value === "boolean")
);
}
function isSignedNumericLiteral(node) {
return (
node.type === "UnaryExpression" &&
(node.operator === "+" || node.operator === "-") &&
isNumericLiteral(node.argument)
);
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isStringLiteral(node) {
return Boolean(
node &&
(node.type === "StringLiteral" ||
(node.type === "Literal" && typeof node.value === "string")),
);
}
function isRegExpLiteral(node) {
return (
node.type === "RegExpLiteral" ||
(node.type === "Literal" && Boolean(node.regex))
);
}
const isLiteral = createTypeCheckFunction([
"Literal",
"BooleanLiteral",
"BigIntLiteral", // Babel, flow use `BigIntLiteral` too
"DirectiveLiteral",
"NullLiteral",
"NumericLiteral",
"RegExpLiteral",
"StringLiteral",
]);
const isSingleWordType = createTypeCheckFunction([
"Identifier",
"ThisExpression",
"Super",
"PrivateName",
"PrivateIdentifier",
]);
const isObjectType = createTypeCheckFunction([
"ObjectTypeAnnotation",
"TSTypeLiteral",
"TSMappedType",
]);
const isFunctionOrArrowExpression = createTypeCheckFunction([
"FunctionExpression",
"ArrowFunctionExpression",
]);
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isFunctionOrArrowExpressionWithBody(node) {
return (
node.type === "FunctionExpression" ||
(node.type === "ArrowFunctionExpression" &&
node.body.type === "BlockStatement")
);
}
/**
* Note: `inject` is used in AngularJS 1.x, `async` and `fakeAsync` in
* Angular 2+, although `async` is deprecated and replaced by `waitForAsync`
* since Angular 12.
*
* example: https://docs.angularjs.org/guide/unit-testing#using-beforeall-
*
* @param {Estree.CallExpression} node
* @returns {boolean}
*/
function isAngularTestWrapper(node) {
return (
isCallExpression(node) &&
node.callee.type === "Identifier" &&
["async", "inject", "fakeAsync", "waitForAsync"].includes(node.callee.name)
);
}
const isJsxElement = createTypeCheckFunction(["JSXElement", "JSXFragment"]);
function isMethod(node) {
return (
(node.method && node.kind === "init") ||
node.kind === "get" ||
node.kind === "set"
);
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isFlowObjectTypePropertyAFunction(node) {
return (
(node.type === "ObjectTypeProperty" ||
node.type === "ObjectTypeInternalSlot") &&
!node.static &&
!node.method &&
// @ts-expect-error -- exists on `ObjectTypeProperty` but not `ObjectTypeInternalSlot`
node.kind !== "get" &&
// @ts-expect-error -- exists on `ObjectTypeProperty` but not `ObjectTypeInternalSlot`
node.kind !== "set" &&
node.value.type === "FunctionTypeAnnotation"
);
}
// Hack to differentiate between the following two which have the same ast
// declare function f(a): void;
// var f: (a) => void;
function isTypeAnnotationAFunction(node) {
return (
(node.type === "TypeAnnotation" || node.type === "TSTypeAnnotation") &&
node.typeAnnotation.type === "FunctionTypeAnnotation" &&
!node.static &&
!hasSameLocStart(node, node.typeAnnotation)
);
}
const isBinaryish = createTypeCheckFunction([
"BinaryExpression",
"LogicalExpression",
"NGPipeExpression",
]);
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isMemberish(node) {
return (
isMemberExpression(node) ||
(node.type === "BindExpression" && Boolean(node.object))
);
}
const isSimpleTypeAnnotation = createTypeCheckFunction([
"TSThisType",
// literals
"NullLiteralTypeAnnotation",
"BooleanLiteralTypeAnnotation",
"StringLiteralTypeAnnotation",
"BigIntLiteralTypeAnnotation",
"NumberLiteralTypeAnnotation",
"TSLiteralType",
"TSTemplateLiteralType",
]);
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function isSimpleType(node) {
return (
isTsKeywordType(node) ||
isFlowKeywordType(node) ||
isSimpleTypeAnnotation(node) ||
(node.type === "GenericTypeAnnotation" && !node.typeParameters) ||
(node.type === "TSTypeReference" && !node.typeArguments)
);
}
/**
* @param {*} node
* @returns {boolean}
*/
function isUnitTestSetupIdentifier(node) {
return (
node.type === "Identifier" &&
(node.name === "beforeEach" ||
node.name === "beforeAll" ||
node.name === "afterEach" ||
node.name === "afterAll")
);
}
const testCallCalleePatterns = [
"it",
"it.only",
"it.skip",
"describe",
"describe.only",
"describe.skip",
"test",
"test.only",
"test.skip",
"test.fixme",
"test.step",
"test.describe",
"test.describe.only",
"test.describe.skip",
"test.describe.fixme",
"test.describe.parallel",
"test.describe.parallel.only",
"test.describe.serial",
"test.describe.serial.only",
"skip",
"xit",
"xdescribe",
"xtest",
"fit",
"fdescribe",
"ftest",
];
function isTestCallCallee(node) {
return isNodeMatches(node, testCallCalleePatterns);
}
// eg; `describe("some string", (done) => {})`
function isTestCall(node, parent) {
if (node?.type !== "CallExpression" || node.optional) {
return false;
}
const args = getCallArguments(node);
if (args.length === 1) {
if (isAngularTestWrapper(node) && isTestCall(parent)) {
return isFunctionOrArrowExpression(args[0]);
}
if (isUnitTestSetupIdentifier(node.callee)) {
return isAngularTestWrapper(args[0]);
}
} else if (
(args.length === 2 || args.length === 3) &&
(args[0].type === "TemplateLiteral" || isStringLiteral(args[0])) &&
isTestCallCallee(node.callee)
) {
// it("name", () => { ... }, 2500)
if (args[2] && !isNumericLiteral(args[2])) {
return false;
}
return (
(args.length === 2
? isFunctionOrArrowExpression(args[1])
: isFunctionOrArrowExpressionWithBody(args[1]) &&
getFunctionParameters(args[1]).length <= 1) ||
isAngularTestWrapper(args[1])
);
}
return false;
}
/** @return {(node: Estree.Node) => boolean} */
const skipChainExpression = (fn) => (node) => {
if (node?.type === "ChainExpression") {
node = node.expression;
}
return fn(node);
};
const isCallExpression = skipChainExpression(
createTypeCheckFunction(["CallExpression", "OptionalCallExpression"]),
);
const isMemberExpression = skipChainExpression(
createTypeCheckFunction(["MemberExpression", "OptionalMemberExpression"]),
);
/**
* Attempts to gauge the rough complexity of a node, for example
* to detect deeply-nested booleans, call expressions with lots of arguments, etc.
*/
function isSimpleExpressionByNodeCount(node, maxInnerNodeCount = 5) {
const count = getExpressionInnerNodeCount(node, maxInnerNodeCount);
return count <= maxInnerNodeCount;
}
function getExpressionInnerNodeCount(node, maxCount) {
let count = 0;
for (const k in node) {
const prop = node[k];
if (isObject(prop) && typeof prop.type === "string") {
count++;
count += getExpressionInnerNodeCount(prop, maxCount - count);
}
// Bail early to protect against bad performance.
if (count > maxCount) {
return count;
}
}
return count;
}
const LONE_SHORT_ARGUMENT_THRESHOLD_RATE = 0.25;
function isLoneShortArgument(node, options) {
const { printWidth } = options;
if (hasComment(node)) {
return false;
}
const threshold = printWidth * LONE_SHORT_ARGUMENT_THRESHOLD_RATE;
if (
node.type === "ThisExpression" ||
(node.type === "Identifier" && node.name.length <= threshold) ||
(isSignedNumericLiteral(node) && !hasComment(node.argument))
) {
return true;
}
const regexpPattern =
(node.type === "Literal" && "regex" in node && node.regex.pattern) ||
(node.type === "RegExpLiteral" && node.pattern);
if (regexpPattern) {
return regexpPattern.length <= threshold;
}
if (isStringLiteral(node)) {
return printString(getRaw(node), options).length <= threshold;
}
if (node.type === "TemplateLiteral") {
return (
node.expressions.length === 0 &&
node.quasis[0].value.raw.length <= threshold &&
!node.quasis[0].value.raw.includes("\n")
);
}
if (node.type === "UnaryExpression") {
return isLoneShortArgument(node.argument, { printWidth });
}
if (
node.type === "CallExpression" &&
node.arguments.length === 0 &&
node.callee.type === "Identifier"
) {
return node.callee.name.length <= threshold - 2;
}
return isLiteral(node);
}
/**
* @param {string} text
* @param {Estree.Node} node
* @returns {boolean}
*/
function hasLeadingOwnLineComment(text, node) {
if (isJsxElement(node)) {
return hasNodeIgnoreComment(node);
}
return hasComment(node, CommentCheckFlags.Leading, (comment) =>
hasNewline(text, locEnd(comment)),
);
}
/**
* @param {Estree.TemplateLiteral} template
* @returns {boolean}
*/
function templateLiteralHasNewLines(template) {
return template.quasis.some((quasi) => quasi.value.raw.includes("\n"));
}
/**
* @param {Estree.TemplateLiteral | Estree.TaggedTemplateExpression} node
* @param {string} text
* @returns {boolean}
*/
function isTemplateOnItsOwnLine(node, text) {
return (
((node.type === "TemplateLiteral" && templateLiteralHasNewLines(node)) ||
(node.type === "TaggedTemplateExpression" &&
templateLiteralHasNewLines(node.quasi))) &&
!hasNewline(text, locStart(node), { backwards: true })
);
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
function needsHardlineAfterDanglingComment(node) {
if (!hasComment(node)) {
return false;
}
const lastDanglingComment = getComments(node, CommentCheckFlags.Dangling).at(
-1,
);
return lastDanglingComment && !isBlockComment(lastDanglingComment);
}
// Logic to check for args with multiple anonymous functions. For instance,
// the following call should be split on multiple lines for readability:
// source.pipe(map((x) => x + x), filter((x) => x % 2 === 0))
function isFunctionCompositionArgs(args) {
if (args.length <= 1) {
return false;
}
let count = 0;
for (const arg of args) {
if (isFunctionOrArrowExpression(arg)) {
count += 1;
if (count > 1) {
return true;
}
} else if (isCallExpression(arg)) {
for (const childArg of getCallArguments(arg)) {
if (isFunctionOrArrowExpression(childArg)) {
return true;
}
}
}
}
return false;
}
// Logic to determine if a call is a “long curried function call”.
// See https://github.com/prettier/prettier/issues/1420.
//
// `connect(a, b, c)(d)`
// In the above call expression, the second call is the parent node and the
// first call is the current node.
/**
* @param {AstPath} path
* @returns {boolean}
*/
function isLongCurriedCallExpression(path) {
const { node, parent, key } = path;
return (
key === "callee" &&
isCallExpression(node) &&
isCallExpression(parent) &&
parent.arguments.length > 0 &&
node.arguments.length > parent.arguments.length
);
}
const simpleCallArgumentUnaryOperators = new Set(["!", "-", "+", "~"]);
/**
* @param {any} node
* @param {number} depth
* @returns {boolean}
*/
function isSimpleCallArgument(node, depth = 2) {
if (depth <= 0) {
return false;
}
if (node.type === "ChainExpression" || node.type === "TSNonNullExpression") {
return isSimpleCallArgument(node.expression, depth);
}
const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1);
if (isRegExpLiteral(node)) {
return getStringWidth(node.pattern ?? node.regex.pattern) <= 5;
}
if (
isLiteral(node) ||
isSingleWordType(node) ||
node.type === "ArgumentPlaceholder"
) {
return true;
}
if (node.type === "TemplateLiteral") {
return (
node.quasis.every((element) => !element.value.raw.includes("\n")) &&
node.expressions.every(isChildSimple)
);
}
if (isObjectExpression(node)) {
return node.properties.every(
(p) =>
!p.computed && (p.shorthand || (p.value && isChildSimple(p.value))),
);
}
if (isArrayExpression(node)) {
return node.elements.every((x) => x === null || isChildSimple(x));
}
if (isCallLikeExpression(node)) {
if (
node.type === "ImportExpression" ||
isSimpleCallArgument(node.callee, depth)
) {
const args = getCallArguments(node);
return args.length <= depth && args.every(isChildSimple);
}
return false;
}
if (isMemberExpression(node)) {
return (
isSimpleCallArgument(node.object, depth) &&
isSimpleCallArgument(node.property, depth)
);
}
if (
(node.type === "UnaryExpression" &&
simpleCallArgumentUnaryOperators.has(node.operator)) ||
node.type === "UpdateExpression"
) {
return isSimpleCallArgument(node.argument, depth);
}
return false;
}
/**
* @param {any} options
* @param {("es5" | "all")} [level]
* @returns {boolean}
*/
function shouldPrintComma(options, level = "es5") {
return (
(options.trailingComma === "es5" && level === "es5") ||
(options.trailingComma === "all" && (level === "all" || level === "es5"))
);
}
/**
* Tests if the leftmost node of the expression matches the predicate. E.g.,
* used to check whether an expression statement needs to be wrapped in extra
* parentheses because it starts with:
*
* - `{`
* - `function`, `class`, or `do {}`
* - `let[`
*
* Will be overzealous if there already are necessary grouping parentheses.
*
* @param {Estree.Node} node
* @param {(leftmostNode: Estree.Node) => boolean} predicate
* @returns {boolean}
*/
function startsWithNoLookaheadToken(node, predicate) {
switch (node.type) {
case "BinaryExpression":
case "LogicalExpression":
case "AssignmentExpression":
case "NGPipeExpression":
return startsWithNoLookaheadToken(node.left, predicate);
case "MemberExpression":
case "OptionalMemberExpression":
return startsWithNoLookaheadToken(node.object, predicate);
case "TaggedTemplateExpression":
if (node.tag.type === "FunctionExpression") {
// IIFEs are always already parenthesized
return false;
}
return startsWithNoLookaheadToken(node.tag, predicate);
case "CallExpression":
case "OptionalCallExpression":
if (node.callee.type === "FunctionExpression") {
// IIFEs are always already parenthesized
return false;
}
return startsWithNoLookaheadToken(node.callee, predicate);
case "ConditionalExpression":
return startsWithNoLookaheadToken(node.test, predicate);
case "UpdateExpression":
return (
!node.prefix && startsWithNoLookaheadToken(node.argument, predicate)
);
case "BindExpression":
return node.object && startsWithNoLookaheadToken(node.object, predicate);
case "SequenceExpression":
return startsWithNoLookaheadToken(node.expressions[0], predicate);
case "ChainExpression":
case "TSSatisfiesExpression":
case "TSAsExpression":
case "TSNonNullExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
return startsWithNoLookaheadToken(node.expression, predicate);
default:
return predicate(node);
}
}
const equalityOperators = {
"==": true,
"!=": true,
"===": true,
"!==": true,
};
const multiplicativeOperators = {
"*": true,
"/": true,
"%": true,
};
const bitshiftOperators = {
">>": true,
">>>": true,
"<<": true,
};
function shouldFlatten(parentOp, nodeOp) {
if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) {
return false;
}
// ** is right-associative
// x ** y ** z --> x ** (y ** z)
if (parentOp === "**") {
return false;
}
// x == y == z --> (x == y) == z
if (equalityOperators[parentOp] && equalityOperators[nodeOp]) {
return false;
}
// x * y % z --> (x * y) % z
if (
(nodeOp === "%" && multiplicativeOperators[parentOp]) ||
(parentOp === "%" && multiplicativeOperators[nodeOp])
) {
return false;
}
// x * y / z --> (x * y) / z
// x / y * z --> (x / y) * z
if (
nodeOp !== parentOp &&
multiplicativeOperators[nodeOp] &&
multiplicativeOperators[parentOp]
) {
return false;
}
// x << y << z --> (x << y) << z
if (bitshiftOperators[parentOp] && bitshiftOperators[nodeOp]) {
return false;
}
return true;
}
const PRECEDENCE = new Map(
[
["|>"],
["??"],
["||"],
["&&"],
["|"],
["^"],
["&"],
["==", "===", "!=", "!=="],
["<", ">", "<=", ">=", "in", "instanceof"],
[">>", "<<", ">>>"],
["+", "-"],
["*", "/", "%"],
["**"],
].flatMap((operators, index) =>
operators.map((operator) => [operator, index]),
),
);
function getPrecedence(operator) {
return PRECEDENCE.get(operator);
}
function isBitwiseOperator(operator) {
return (
Boolean(bitshiftOperators[operator]) ||
operator === "|" ||
operator === "^" ||
operator === "&"
);
}
function hasRestParameter(node) {
if (node.rest) {
return true;
}
const parameters = getFunctionParameters(node);
return parameters.at(-1)?.type === "RestElement";
}
const functionParametersCache = new WeakMap();
function getFunctionParameters(node) {
if (functionParametersCache.has(node)) {
return functionParametersCache.get(node);
}
const parameters = [];
if (node.this) {
parameters.push(node.this);
}
parameters.push(...node.params);
if (node.rest) {
parameters.push(node.rest);
}
functionParametersCache.set(node, parameters);
return parameters;
}
function iterateFunctionParametersPath(path, iteratee) {
const { node } = path;
let index = 0;
const callback = () => iteratee(path, index++);
if (node.this) {
path.call(callback, "this");
}
path.each(callback, "params");
if (node.rest) {
path.call(callback, "rest");
}
}
const callArgumentsCache = new WeakMap();
function getCallArguments(node) {
if (callArgumentsCache.has(node)) {
return callArgumentsCache.get(node);
}
if (node.type === "ChainExpression") {
return getCallArguments(node.expression);
}
let args;
if (node.type === "ImportExpression" || node.type === "TSImportType") {
args = [node.source];
if (node.options) {
args.push(node.options);
}
} else if (node.type === "TSExternalModuleReference") {
args = [node.expression];
} else {
args = node.arguments;
}
callArgumentsCache.set(node, args);
return args;
}
function iterateCallArgumentsPath(path, iteratee) {
const { node } = path;
if (node.type === "ChainExpression") {
return path.call(
() => iterateCallArgumentsPath(path, iteratee),
"expression",
);
}
if (node.type === "ImportExpression" || node.type === "TSImportType") {
path.call(() => iteratee(path, 0), "source");
if (node.options) {
path.call(() => iteratee(path, 1), "options");
}
} else if (node.type === "TSExternalModuleReference") {
path.call(() => iteratee(path, 0), "expression");
} else {
path.each(iteratee, "arguments");
}
}
function getCallArgumentSelector(node, index) {
const selectors = [];
if (node.type === "ChainExpression") {
node = node.expression;
selectors.push("expression");
}
if (node.type === "ImportExpression" || node.type === "TSImportType") {
if (index === 0 || index === (node.options ? -2 : -1)) {
return [...selectors, "source"];
}
if (node.options && (index === 1 || index === -1)) {
return [...selectors, "options"];
}
throw new RangeError("Invalid argument index");
} else if (node.type === "TSExternalModuleReference") {
if (index === 0 || index === -1) {
return [...selectors, "expression"];
}
} else {
if (index < 0) {
index = node.arguments.length + index;
}
if (index >= 0 && index < node.arguments.length) {
return [...selectors, "arguments", index];
}
}
/* c8 ignore next */
throw new RangeError("Invalid argument index");
}
function isPrettierIgnoreComment(comment) {
return comment.value.trim() === "prettier-ignore" && !comment.unignore;
}
function hasNodeIgnoreComment(node) {
return (
node?.prettierIgnore || hasComment(node, CommentCheckFlags.PrettierIgnore)
);
}
/** @enum {number} */
const CommentCheckFlags = {
/** Check comment is a leading comment */
Leading: 1 << 1,
/** Check comment is a trailing comment */
Trailing: 1 << 2,
/** Check comment is a dangling comment */
Dangling: 1 << 3,
/** Check comment is a block comment */
Block: 1 << 4,
/** Check comment is a line comment */
Line: 1 << 5,
/** Check comment is a `prettier-ignore` comment */
PrettierIgnore: 1 << 6,
/** Check comment is the first attached comment */
First: 1 << 7,
/** Check comment is the last attached comment */
Last: 1 << 8,
};
const getCommentTestFunction = (flags, fn) => {
if (typeof flags === "function") {
fn = flags;
flags = 0;
}
if (flags || fn) {
return (comment, index, comments) =>
!(
(flags & CommentCheckFlags.Leading && !comment.leading) ||
(flags & CommentCheckFlags.Trailing && !comment.trailing) ||
(flags & CommentCheckFlags.Dangling &&
(comment.leading || comment.trailing)) ||
(flags & CommentCheckFlags.Block && !isBlockComment(comment)) ||
(flags & CommentCheckFlags.Line && !isLineComment(comment)) ||
(flags & CommentCheckFlags.First && index !== 0) ||
(flags & CommentCheckFlags.Last && index !== comments.length - 1) ||
(flags & CommentCheckFlags.PrettierIgnore &&
!isPrettierIgnoreComment(comment)) ||
(fn && !fn(comment))
);
}
};
/**
* @param {Estree.Node} node
* @param {number | function} [flags]
* @param {function} [fn]
* @returns {boolean}
*/
function hasComment(node, flags, fn) {
if (!isNonEmptyArray(node?.comments)) {
return false;
}
const test = getCommentTestFunction(flags, fn);
return test ? node.comments.some(test) : true;
}
/**
* @param {Estree.Node} node
* @param {number | function} [flags]
* @param {function} [fn]
* @returns {Estree.Comment[]}
*/
function getComments(node, flags, fn) {
if (!Array.isArray(node?.comments)) {
return [];
}
const test = getCommentTestFunction(flags, fn);
return test ? node.comments.filter(test) : node.comments;
}
/**
* @param {Estree.Node} node
* @returns {boolean}
*/
const isNextLineEmpty = (node, { originalText }) =>
isNextLineEmptyAfterIndex(originalText, locEnd(node));
function isCallLikeExpression(node) {
return (
isCallExpression(node) ||
node.type === "NewExpression" ||
node.type === "ImportExpression"
);
}
function isObjectProperty(node) {
return (
node &&
(node.type === "ObjectProperty" ||
(node.type === "Property" && !isMethod(node)))
);
}
const isBinaryCastExpression = createTypeCheckFunction([
// TS
"TSAsExpression",
"TSSatisfiesExpression",
// Flow
"AsExpression",
"AsConstExpression",
"SatisfiesExpression",
]);
const isUnionType = createTypeCheckFunction([
"TSUnionType",
"UnionTypeAnnotation",
]);
const isIntersectionType = createTypeCheckFunction([
"TSIntersectionType",
"IntersectionTypeAnnotation",
]);
const isConditionalType = createTypeCheckFunction([
"TSConditionalType",
"ConditionalTypeAnnotation",
]);
const isTsAsConstExpression = (node) =>
node?.type === "TSAsExpression" &&
node.typeAnnotation.type === "TSTypeReference" &&
node.typeAnnotation.typeName.type === "Identifier" &&
node.typeAnnotation.typeName.name === "const";
const isTypeAlias = createTypeCheckFunction([
"TSTypeAliasDeclaration",
"TypeAlias",
]);
function shouldUnionTypePrintOwnComments({ key, parent }) {
if (
// If it's `types` of union type, parent will print comment for it
(key === "types" && isUnionType(parent)) ||
// Inside intersection type let the comment print outside of parentheses
(key === "types" && isIntersectionType(parent))
) {
return false;
}
return true;
}
/**
@returns {boolean}
*/
function isBooleanTypeCoercion(node) {
return (
node.type === "CallExpression" &&
!node.optional &&
node.arguments.length === 1 &&
node.callee.type === "Identifier" &&
node.callee.name === "Boolean"
);
}
export {
CommentCheckFlags,
createTypeCheckFunction,
getCallArguments,
getCallArgumentSelector,
getComments,
getFunctionParameters,
getLeftSide,
getLeftSidePathName,
getPrecedence,
hasComment,
hasLeadingOwnLineComment,
hasNakedLeftSide,
hasNode,
hasNodeIgnoreComment,
hasRestParameter,
isArrayExpression,
isBinaryCastExpression,
isBinaryish,
isBitwiseOperator,
isBooleanLiteral,
isBooleanTypeCoercion,
isCallExpression,
isCallLikeExpression,
isConditionalType,
isExportDeclaration,
isFlowObjectTypePropertyAFunction,
isFunctionCompositionArgs,
isFunctionOrArrowExpression,
isIntersectionType,
isJsxElement,
isLiteral,
isLoneShortArgument,
isLongCurriedCallExpression,
isMemberExpression,
isMemberish,
isMethod,
isNextLineEmpty,
isNullishCoalescing,
isNumericLiteral,
isObjectExpression,
isObjectProperty,
isObjectType,
isPrettierIgnoreComment,
isRegExpLiteral,
isSignedNumericLiteral,
isSimpleCallArgument,
isSimpleExpressionByNodeCount,
isSimpleType,
isStringLiteral,
isTemplateOnItsOwnLine,
isTestCall,
isTsAsConstExpression,
isTypeAlias,
isTypeAnnotationAFunction,
isUnionType,
iterateCallArgumentsPath,
iterateFunctionParametersPath,
needsHardlineAfterDanglingComment,
shouldFlatten,
shouldPrintComma,
shouldUnionTypePrintOwnComments,
startsWithNoLookaheadToken,
};
export { default as isMeaningfulEmptyStatement } from "./is-meaningful-empty-statement.js";
export { default as isNodeMatches } from "./is-node-matches.js";