diff --git a/src/language-js/comments/can-attach-comment.js b/src/language-js/comments/can-attach-comment.js index f4ca1c492..a2abbacac 100644 --- a/src/language-js/comments/can-attach-comment.js +++ b/src/language-js/comments/can-attach-comment.js @@ -1,12 +1,10 @@ import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; +import { isTsAsConstExpression } from "../utilities/is-as-const-expression.js"; import { isMeaningfulEmptyStatement } from "../utilities/is-meaningful-empty-statement.js"; -import { - getFunctionParameters, - isMethod, - isShorthandSpecifier, - isTsAsConstExpression, -} from "../utilities/utilities.js"; +import { isMethod } from "../utilities/is-method.js"; +import { isShorthandSpecifier } from "../utilities/is-shorthand-specifier.js"; /** @import {Node} from "../types/estree.js"; diff --git a/src/language-js/comments/handle-comments.js b/src/language-js/comments/handle-comments.js index 85693ab9a..59773594b 100644 --- a/src/language-js/comments/handle-comments.js +++ b/src/language-js/comments/handle-comments.js @@ -9,13 +9,17 @@ import hasNewline from "../../utilities/has-newline.js"; import hasNewlineInRange from "../../utilities/has-newline-in-range.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; import { locEnd, locStart } from "../loc.js"; +import { getCallArguments } from "../utilities/call-arguments.js"; import { isInsideCallOrNewExpressionParentheses } from "../utilities/call-or-new-expression-parentheses.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; import getTextWithoutComments from "../utilities/get-text-without-comments.js"; -import isBlockComment from "../utilities/is-block-comment.js"; -import isLineComment from "../utilities/is-line-comment.js"; +import { isBlockComment } from "../utilities/is-block-comment.js"; +import { isLineComment } from "../utilities/is-line-comment.js"; +import { isMethod } from "../utilities/is-method.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; import { isPrettierIgnoreComment } from "../utilities/is-prettier-ignore-comment.js"; -import isTypeCastComment from "../utilities/is-type-cast-comment.js"; +import { isTypeCastComment } from "../utilities/is-type-cast-comment.js"; import { isBinaryCastExpression, isCallLikeExpression, @@ -25,12 +29,6 @@ import { isMemberExpression, isUnionType, } from "../utilities/node-types.js"; -import { - getCallArguments, - getFunctionParameters, - isMethod, - isObjectProperty, -} from "../utilities/utilities.js"; /** @import {Node, Comment, NodeMap} from "../types/estree.js"; diff --git a/src/language-js/comments/will-print-own-comments.js b/src/language-js/comments/will-print-own-comments.js index d72256a17..59a4927fe 100644 --- a/src/language-js/comments/will-print-own-comments.js +++ b/src/language-js/comments/will-print-own-comments.js @@ -1,11 +1,9 @@ import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import { hasNodeIgnoreComment } from "../utilities/has-node-ignore-comment.js"; +import { isIifeCalleeOrTaggedTemplateExpressionTag } from "../utilities/is-iife-callee-or-tagged-template-expression-tag.js"; import { isJsxElement, isUnionType } from "../utilities/node-types.js"; import { shouldExpressionStatementPrintOwnComments } from "../utilities/should-expression-statement-print-own-comments.js"; -import { - isIifeCalleeOrTaggedTemplateExpressionTag, - shouldUnionTypePrintOwnComments, -} from "../utilities/utilities.js"; +import { shouldUnionTypePrintOwnComments } from "../utilities/should-union-type-print-own-comments.js"; /** @import {Node} from "../types/estree.js"; diff --git a/src/language-js/embed/utilities.js b/src/language-js/embed/utilities.js index bdc954976..148b603ce 100644 --- a/src/language-js/embed/utilities.js +++ b/src/language-js/embed/utilities.js @@ -1,6 +1,6 @@ import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; import { isArrayExpression } from "../utilities/node-types.js"; -import { isObjectProperty } from "../utilities/utilities.js"; const angularComponentObjectExpressionPredicates = [ (node, name) => name === "properties" && node.type === "ObjectExpression", diff --git a/src/language-js/parentheses/identifier.js b/src/language-js/parentheses/identifier.js index 55306f019..9c6b3ae27 100644 --- a/src/language-js/parentheses/identifier.js +++ b/src/language-js/parentheses/identifier.js @@ -1,5 +1,5 @@ import { isBinaryCastExpression } from "../utilities/node-types.js"; -import { startsWithNoLookaheadToken } from "../utilities/utilities.js"; +import { startsWithNoLookaheadToken } from "../utilities/starts-with-no-lookahead-token.js"; function shouldAddParenthesesToIdentifier(path) { const { node } = path; diff --git a/src/language-js/parentheses/needs-parentheses.js b/src/language-js/parentheses/needs-parentheses.js index 4a8a9652f..9d7dd6de6 100644 --- a/src/language-js/parentheses/needs-parentheses.js +++ b/src/language-js/parentheses/needs-parentheses.js @@ -1,5 +1,10 @@ import { hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; +import { getPrecedence } from "../utilities/get-precedence.js"; +import { isBitwiseOperator } from "../utilities/is-bitwise-operator.js"; +import { isNullishCoalescing } from "../utilities/is-nullish-coalescing.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; import { isArrayExpression, isBinaryCastExpression, @@ -13,15 +18,8 @@ import { isReturnOrThrowStatement, isUnionType, } from "../utilities/node-types.js"; -import { - getFunctionParameters, - getPrecedence, - isBitwiseOperator, - isNullishCoalescing, - isObjectProperty, - shouldFlatten, - startsWithNoLookaheadToken, -} from "../utilities/utilities.js"; +import { shouldFlatten } from "../utilities/should-flatten.js"; +import { startsWithNoLookaheadToken } from "../utilities/starts-with-no-lookahead-token.js"; import { shouldAddParenthesesToChainElement } from "./chain-expression.js"; import { shouldAddParenthesesToIdentifier } from "./identifier.js"; import { parentNeedsParentheses } from "./parent-needs-parentheses.js"; diff --git a/src/language-js/parentheses/parent-needs-parentheses.js b/src/language-js/parentheses/parent-needs-parentheses.js index d7087bd2c..317d4c042 100644 --- a/src/language-js/parentheses/parent-needs-parentheses.js +++ b/src/language-js/parentheses/parent-needs-parentheses.js @@ -1,16 +1,16 @@ import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; +import { hasNode } from "../utilities/has-node.js"; +import { + getLeftSidePathName, + hasNakedLeftSide, +} from "../utilities/left-side.js"; import { isCallExpression, isMemberExpression, isReturnOrThrowStatement, } from "../utilities/node-types.js"; import { returnArgumentHasLeadingComment } from "../utilities/return-statement-has-leading-comment.js"; -import { - getLeftSidePathName, - hasNakedLeftSide, - hasNode, - stripChainElementWrappers, -} from "../utilities/utilities.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; /** * @import AstPath from "../../common/ast-path.js" diff --git a/src/language-js/parse/postprocess/index.js b/src/language-js/parse/postprocess/index.js index 0f5c8e365..80136349d 100644 --- a/src/language-js/parse/postprocess/index.js +++ b/src/language-js/parse/postprocess/index.js @@ -1,12 +1,12 @@ import * as assert from "#universal/assert"; import { locEnd, locEndWithFullText, locStart } from "../../loc.js"; import { createTypeCheckFunction } from "../../utilities/create-type-check-function.js"; -import getRaw from "../../utilities/get-raw.js"; +import { getRaw } from "../../utilities/get-raw.js"; import getTextWithoutComments from "../../utilities/get-text-without-comments.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 { 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([ diff --git a/src/language-js/parse/postprocess/merge-nestled-jsdoc-comments.js b/src/language-js/parse/postprocess/merge-nestled-jsdoc-comments.js index 53cf3108f..1b1002285 100644 --- a/src/language-js/parse/postprocess/merge-nestled-jsdoc-comments.js +++ b/src/language-js/parse/postprocess/merge-nestled-jsdoc-comments.js @@ -1,7 +1,7 @@ import { locEnd, locStart } from "../../loc.js"; -import isBlockComment from "../../utilities/is-block-comment.js"; +import { isBlockComment } from "../../utilities/is-block-comment.js"; import isIndentableBlockComment from "../../utilities/is-indentable-block-comment.js"; -import isLineComment from "../../utilities/is-line-comment.js"; +import { isLineComment } from "../../utilities/is-line-comment.js"; function mergeNestledJsdocComments(comments) { if (comments.length < 2) { @@ -32,4 +32,4 @@ function mergeNestledJsdocComments(comments) { } } -export default mergeNestledJsdocComments; +export { mergeNestledJsdocComments }; diff --git a/src/language-js/print/angular.js b/src/language-js/print/angular.js index 59f48a9f9..0e197d0c8 100644 --- a/src/language-js/print/angular.js +++ b/src/language-js/print/angular.js @@ -1,7 +1,7 @@ import { group, join, line } from "../../document/index.js"; import UnexpectedNodeError from "../../utilities/unexpected-node-error.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; -import { hasNode } from "../utilities/utilities.js"; +import { hasNode } from "../utilities/has-node.js"; import { printBinaryishExpression } from "./binaryish.js"; /** @import AstPath from "../../common/ast-path.js" */ diff --git a/src/language-js/print/array.js b/src/language-js/print/array.js index 01784e1b7..c70f4c887 100644 --- a/src/language-js/print/array.js +++ b/src/language-js/print/array.js @@ -14,16 +14,14 @@ import skipInlineComment from "../../utilities/skip-inline-comment.js"; import skipTrailingComment from "../../utilities/skip-trailing-comment.js"; import { locEnd, locStart } from "../loc.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { isSignedNumericLiteral } from "../utilities/is-signed-numeric-literal.js"; import { isArrayExpression, isNumericLiteral, isObjectExpression, isTupleType, } from "../utilities/node-types.js"; -import { - isSignedNumericLiteral, - shouldPrintComma, -} from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { printDanglingCommentsInList, printOptionalToken, diff --git a/src/language-js/print/arrow-function.js b/src/language-js/print/arrow-function.js index 221b0a45f..f971dd417 100644 --- a/src/language-js/print/arrow-function.js +++ b/src/language-js/print/arrow-function.js @@ -15,6 +15,9 @@ import { printDanglingComments, } from "../../main/comments/print.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; +import { isTemplateOnItsOwnLine } from "../utilities/is-template-on-its-own-line.js"; import { isArrayExpression, isBinaryish, @@ -22,13 +25,8 @@ import { isJsxElement, isObjectExpression, } from "../utilities/node-types.js"; -import { - getFunctionParameters, - hasLeadingOwnLineComment, - isTemplateOnItsOwnLine, - shouldPrintComma, - startsWithNoLookaheadToken, -} from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; +import { startsWithNoLookaheadToken } from "../utilities/starts-with-no-lookahead-token.js"; import { printReturnType, shouldPrintParamsWithoutParens } from "./function.js"; import { printFunctionParameters } from "./function-parameters.js"; diff --git a/src/language-js/print/assignment.js b/src/language-js/print/assignment.js index 106b6c6bb..4be90cc6d 100644 --- a/src/language-js/print/assignment.js +++ b/src/language-js/print/assignment.js @@ -10,6 +10,10 @@ import { } from "../../document/index.js"; import getStringWidth from "../../utilities/get-string-width.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; +import { getCallArguments } from "../utilities/call-arguments.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; +import { isLoneShortArgument } from "../utilities/is-lone-short-argument.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; import { isBinaryish, isBooleanLiteral, @@ -22,12 +26,6 @@ import { isTypeAlias, isUnionType, } from "../utilities/node-types.js"; -import { - getCallArguments, - hasLeadingOwnLineComment, - isLoneShortArgument, - isObjectProperty, -} from "../utilities/utilities.js"; import { shouldInlineLogicalExpression } from "./binaryish.js"; import { printCallExpression } from "./call-expression.js"; diff --git a/src/language-js/print/await-expression.js b/src/language-js/print/await-expression.js index 910f88be0..d0717c062 100644 --- a/src/language-js/print/await-expression.js +++ b/src/language-js/print/await-expression.js @@ -3,7 +3,7 @@ import { isCallExpression, isMemberExpression, } from "../utilities/node-types.js"; -import { startsWithNoLookaheadToken } from "../utilities/utilities.js"; +import { startsWithNoLookaheadToken } from "../utilities/starts-with-no-lookahead-token.js"; /** * @import AstPath from "../../common/ast-path.js" diff --git a/src/language-js/print/binaryish.js b/src/language-js/print/binaryish.js index 3694ed9a3..599e996d0 100644 --- a/src/language-js/print/binaryish.js +++ b/src/language-js/print/binaryish.js @@ -15,7 +15,10 @@ import { } from "../../document/index.js"; import { printComments } from "../../main/comments/print.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; -import isTypeCastComment from "../utilities/is-type-cast-comment.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; +import { isBooleanTypeCoercion } from "../utilities/is-boolean-type-coercion.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; +import { isTypeCastComment } from "../utilities/is-type-cast-comment.js"; import { isArrayExpression, isBinaryish, @@ -25,12 +28,7 @@ import { isObjectExpression, isReturnOrThrowStatement, } from "../utilities/node-types.js"; -import { - hasLeadingOwnLineComment, - isBooleanTypeCoercion, - isObjectProperty, - shouldFlatten, -} from "../utilities/utilities.js"; +import { shouldFlatten } from "../utilities/should-flatten.js"; /** @import {Doc} from "../../document/index.js" */ diff --git a/src/language-js/print/block.js b/src/language-js/print/block.js index 855a7e194..49ffd9e77 100644 --- a/src/language-js/print/block.js +++ b/src/language-js/print/block.js @@ -2,7 +2,7 @@ import { hardline, indent } from "../../document/index.js"; import { printDanglingComments } from "../../main/comments/print.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; -import { isNextLineEmpty } from "../utilities/utilities.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; import { printStatementSequence } from "./statement-sequence.js"; /** @import {Doc} from "../../document/index.js" */ diff --git a/src/language-js/print/call-arguments.js b/src/language-js/print/call-arguments.js index 2257349d6..8150c49ea 100644 --- a/src/language-js/print/call-arguments.js +++ b/src/language-js/print/call-arguments.js @@ -10,7 +10,19 @@ import { softline, willBreak, } from "../../document/index.js"; +import { + getCallArguments, + getCallArgumentSelector, + iterateCallArgumentsPath, +} from "../utilities/call-arguments.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; +import { isFunctionCompositionArguments } from "../utilities/is-function-composition-arguments.js"; +import { isLongCurriedCallExpression } from "../utilities/is-long-curried-call-expression.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; +import { isObjectProperty } from "../utilities/is-object-property.js"; +import { isSimpleCallArgument } from "../utilities/is-simple-call-argument.js"; +import { isSimpleType } from "../utilities/is-simple-type.js"; import { isArrayExpression, isBinaryCastExpression, @@ -22,20 +34,8 @@ import { isRegExpLiteral, isStringLiteral, } from "../utilities/node-types.js"; -import { - getCallArguments, - getCallArgumentSelector, - getFunctionParameters, - isFunctionCompositionArgs, - isLongCurriedCallExpression, - isNextLineEmpty, - isObjectProperty, - isSimpleCallArgument, - isSimpleType, - iterateCallArgumentsPath, - shouldPrintComma, - stripChainElementWrappers, -} from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; import { isConciselyPrintedArray } from "./array.js"; import { printDanglingCommentsInList } from "./miscellaneous.js"; @@ -109,7 +109,7 @@ function printCallArguments(path, options, print) { if ( anyArgEmptyLine || - (path.parent.type !== "Decorator" && isFunctionCompositionArgs(args)) + (path.parent.type !== "Decorator" && isFunctionCompositionArguments(args)) ) { return allArgsBrokenOut(); } diff --git a/src/language-js/print/call-expression.js b/src/language-js/print/call-expression.js index ccc35ee66..378895efb 100644 --- a/src/language-js/print/call-expression.js +++ b/src/language-js/print/call-expression.js @@ -1,15 +1,15 @@ import { group, join, lineSuffixBoundary } from "../../document/index.js"; import needsParentheses from "../parentheses/needs-parentheses.js"; -import { hasComment } from "../utilities/comments.js"; -import { isNodeMatches } from "../utilities/is-node-matches.js"; -import { isCallExpression, isStringLiteral } from "../utilities/node-types.js"; -import { isTestCall } from "../utilities/test-libraries.js"; import { getCallArguments, - isMemberish, - isTemplateOnItsOwnLine, iterateCallArgumentsPath, -} from "../utilities/utilities.js"; +} from "../utilities/call-arguments.js"; +import { hasComment } from "../utilities/comments.js"; +import { isMemberish } from "../utilities/is-memberish.js"; +import { isNodeMatches } from "../utilities/is-node-matches.js"; +import { isTemplateOnItsOwnLine } from "../utilities/is-template-on-its-own-line.js"; +import { isCallExpression, isStringLiteral } from "../utilities/node-types.js"; +import { isTestCall } from "../utilities/test-libraries.js"; import printCallArguments from "./call-arguments.js"; import printMemberChain from "./member-chain.js"; import { printOptionalToken } from "./miscellaneous.js"; diff --git a/src/language-js/print/class-body.js b/src/language-js/print/class-body.js index 4bc9d9c9b..bb068099f 100644 --- a/src/language-js/print/class-body.js +++ b/src/language-js/print/class-body.js @@ -16,7 +16,8 @@ import { hasComment, } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; -import { isNextLineEmpty, shouldPrintComma } from "../utilities/utilities.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { shouldHugTheOnlyParameter } from "./function-parameters.js"; /* diff --git a/src/language-js/print/class.js b/src/language-js/print/class.js index 2f278e119..f7a47665a 100644 --- a/src/language-js/print/class.js +++ b/src/language-js/print/class.js @@ -17,7 +17,7 @@ import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import { isMemberExpression } from "../utilities/node-types.js"; -import { stripChainElementWrappers } from "../utilities/utilities.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; import { printAssignment } from "./assignment.js"; import { printClassMemberDecorators } from "./decorators.js"; import { printMethod } from "./function.js"; diff --git a/src/language-js/print/comment.js b/src/language-js/print/comment.js index 673378f75..c5d10577f 100644 --- a/src/language-js/print/comment.js +++ b/src/language-js/print/comment.js @@ -5,9 +5,9 @@ import { replaceEndOfLine, } from "../../document/index.js"; import { locEnd, locStart } from "../loc.js"; -import isBlockComment from "../utilities/is-block-comment.js"; +import { isBlockComment } from "../utilities/is-block-comment.js"; import isIndentableBlockComment from "../utilities/is-indentable-block-comment.js"; -import isLineComment from "../utilities/is-line-comment.js"; +import { isLineComment } from "../utilities/is-line-comment.js"; /** @import {Doc} from "../../document/index.js" diff --git a/src/language-js/print/estree.js b/src/language-js/print/estree.js index a01d0d891..175f6a689 100644 --- a/src/language-js/print/estree.js +++ b/src/language-js/print/estree.js @@ -9,12 +9,12 @@ import { printDanglingComments } from "../../main/comments/print.js"; import UnexpectedNodeError from "../../utilities/unexpected-node-error.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { isMeaningfulEmptyStatement } from "../utilities/is-meaningful-empty-statement.js"; +import { isMethod } from "../utilities/is-method.js"; import { isArrayExpression, isLiteral, isObjectExpression, } from "../utilities/node-types.js"; -import { isMethod } from "../utilities/utilities.js"; import { printArray } from "./array.js"; import { printArrowFunction } from "./arrow-function.js"; import { diff --git a/src/language-js/print/flow.js b/src/language-js/print/flow.js index 725b2f895..24f374e83 100644 --- a/src/language-js/print/flow.js +++ b/src/language-js/print/flow.js @@ -4,9 +4,9 @@ import * as assert from "#universal/assert"; import { replaceEndOfLine } from "../../document/index.js"; import printNumber from "../../utilities/print-number.js"; import printString from "../../utilities/print-string.js"; -import getRaw from "../utilities/get-raw.js"; +import { getRaw } from "../utilities/get-raw.js"; import isFlowKeywordType from "../utilities/is-flow-keyword-type.js"; -import { isMethod } from "../utilities/utilities.js"; +import { isMethod } from "../utilities/is-method.js"; import { printArray } from "./array.js"; import { printArrayType } from "./array-type.js"; import { printBinaryCastExpression } from "./cast-expression.js"; diff --git a/src/language-js/print/function-parameters.js b/src/language-js/print/function-parameters.js index c6a3773d3..1acc7bb04 100644 --- a/src/language-js/print/function-parameters.js +++ b/src/language-js/print/function-parameters.js @@ -11,22 +11,22 @@ import { } from "../../document/index.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; import { hasComment } from "../utilities/comments.js"; +import { + getFunctionParameters, + hasRestParameter, + iterateFunctionParametersPath, +} from "../utilities/function-parameters.js"; +import { isFlowObjectTypePropertyAFunction } from "../utilities/is-flow-object-type-property-a-function.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; +import { isSimpleType } from "../utilities/is-simple-type.js"; +import { isTypeAnnotationAFunction } from "../utilities/is-type-annotation-a-function.js"; import { isArrayExpression, isObjectExpression, isObjectType, } from "../utilities/node-types.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { isTestCall } from "../utilities/test-libraries.js"; -import { - getFunctionParameters, - hasRestParameter, - isFlowObjectTypePropertyAFunction, - isNextLineEmpty, - isSimpleType, - isTypeAnnotationAFunction, - iterateFunctionParametersPath, - shouldPrintComma, -} from "../utilities/utilities.js"; import { printDanglingCommentsInList } from "./miscellaneous.js"; /** @import AstPath from "../../common/ast-path.js" */ diff --git a/src/language-js/print/function-type.js b/src/language-js/print/function-type.js index 2c54b87e5..e767d7cf6 100644 --- a/src/language-js/print/function-type.js +++ b/src/language-js/print/function-type.js @@ -1,6 +1,6 @@ import { group } from "../../document/index.js"; import { hasSameLocStart } from "../loc.js"; -import { isFlowObjectTypePropertyAFunction } from "../utilities/utilities.js"; +import { isFlowObjectTypePropertyAFunction } from "../utilities/is-flow-object-type-property-a-function.js"; import { printClassMemberSemicolon } from "./class.js"; import { printFunctionParameters, diff --git a/src/language-js/print/function.js b/src/language-js/print/function.js index 78c1aefb9..92edc8098 100644 --- a/src/language-js/print/function.js +++ b/src/language-js/print/function.js @@ -1,12 +1,10 @@ import * as assert from "#universal/assert"; import { group } from "../../document/index.js"; +import { getCallArguments } from "../utilities/call-arguments.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; +import { isMethod } from "../utilities/is-method.js"; import { isCallExpression } from "../utilities/node-types.js"; -import { - getCallArguments, - getFunctionParameters, - isMethod, -} from "../utilities/utilities.js"; import { printFunctionParameters, shouldBreakFunctionParameters, diff --git a/src/language-js/print/if-statement.js b/src/language-js/print/if-statement.js index 6d070c501..cba3947f7 100644 --- a/src/language-js/print/if-statement.js +++ b/src/language-js/print/if-statement.js @@ -1,7 +1,7 @@ import { group, hardline } from "../../document/index.js"; import { printDanglingComments } from "../../main/comments/print.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; -import { needsHardlineAfterDanglingComment } from "../utilities/utilities.js"; +import { needsHardlineAfterDanglingComment } from "../utilities/needs-hardline-after-dangling-comment.js"; import { adjustClause, printIfStatementCondition } from "./miscellaneous.js"; /** diff --git a/src/language-js/print/index-signature.js b/src/language-js/print/index-signature.js index 7f07246d9..3f55a853a 100644 --- a/src/language-js/print/index-signature.js +++ b/src/language-js/print/index-signature.js @@ -5,7 +5,7 @@ import { join, softline, } from "../../document/index.js"; -import { shouldPrintComma } from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { printClassMemberSemicolon } from "./class.js"; import { printTypeAnnotationProperty } from "./type-annotation.js"; diff --git a/src/language-js/print/index.js b/src/language-js/print/index.js index 3dd7c68db..d49f63c52 100644 --- a/src/language-js/print/index.js +++ b/src/language-js/print/index.js @@ -11,7 +11,7 @@ import needsParentheses from "../parentheses/needs-parentheses.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import isIgnored from "../utilities/is-ignored.js"; -import { isIifeCalleeOrTaggedTemplateExpressionTag } from "../utilities/utilities.js"; +import { isIifeCalleeOrTaggedTemplateExpressionTag } from "../utilities/is-iife-callee-or-tagged-template-expression-tag.js"; import { printAngular } from "./angular.js"; import { printDecorators } from "./decorators.js"; import { printEstree } from "./estree.js"; diff --git a/src/language-js/print/intersection-type.js b/src/language-js/print/intersection-type.js index 392610599..edc285219 100644 --- a/src/language-js/print/intersection-type.js +++ b/src/language-js/print/intersection-type.js @@ -1,6 +1,6 @@ import { group, indent, line } from "../../document/index.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; import { isObjectType } from "../utilities/node-types.js"; -import { hasLeadingOwnLineComment } from "../utilities/utilities.js"; // `TSIntersectionType` and `IntersectionTypeAnnotation` function printIntersectionType(path, options, print) { diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index 9e8567c6e..e8f50335d 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -23,7 +23,7 @@ import UnexpectedNodeError from "../../utilities/unexpected-node-error.js"; import needsParentheses from "../parentheses/needs-parentheses.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; -import getRaw from "../utilities/get-raw.js"; +import { getRaw } from "../utilities/get-raw.js"; import { isMeaningfulJsxText } from "../utilities/is-meaningful-jsx-text.js"; import { jsxWhitespace } from "../utilities/jsx-whitespace.js"; import { @@ -34,7 +34,7 @@ import { isObjectExpression, isStringLiteral, } from "../utilities/node-types.js"; -import { stripChainElementWrappers } from "../utilities/utilities.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; /** @import AstPath from "../../common/ast-path.js"; diff --git a/src/language-js/print/key.js b/src/language-js/print/key.js index 14ee31623..df944d172 100644 --- a/src/language-js/print/key.js +++ b/src/language-js/print/key.js @@ -2,7 +2,7 @@ import isEs5IdentifierName from "is-es5-identifier-name"; import { printComments } from "../../main/comments/print.js"; import printNumber from "../../utilities/print-number.js"; import printString from "../../utilities/print-string.js"; -import getRaw from "../utilities/get-raw.js"; +import { getRaw } from "../utilities/get-raw.js"; import { isNumericLiteral, isStringLiteral } from "../utilities/node-types.js"; /** diff --git a/src/language-js/print/mapped-type.js b/src/language-js/print/mapped-type.js index f76f3ed8e..ccdeed940 100644 --- a/src/language-js/print/mapped-type.js +++ b/src/language-js/print/mapped-type.js @@ -12,7 +12,7 @@ import hasNewlineInRange from "../../utilities/has-newline-in-range.js"; import { locEnd, locStart } from "../loc.js"; import { CommentCheckFlags, getComments } from "../utilities/comments.js"; import getTextWithoutComments from "../utilities/get-text-without-comments.js"; -import isLineComment from "../utilities/is-line-comment.js"; +import { isLineComment } from "../utilities/is-line-comment.js"; import { printClassMemberSemicolon } from "./class.js"; /** diff --git a/src/language-js/print/match.js b/src/language-js/print/match.js index 11283e15a..69c7d2afc 100644 --- a/src/language-js/print/match.js +++ b/src/language-js/print/match.js @@ -15,10 +15,8 @@ import { import needsParentheses from "../parentheses/needs-parentheses.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; -import { - hasLeadingOwnLineComment, - isNextLineEmpty, -} from "../utilities/utilities.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; /* - `MatchExpression` (Flow) diff --git a/src/language-js/print/member-chain.js b/src/language-js/print/member-chain.js index 2a30cbf93..954aa083a 100644 --- a/src/language-js/print/member-chain.js +++ b/src/language-js/print/member-chain.js @@ -14,18 +14,16 @@ import isNextLineEmptyAfterIndex from "../../utilities/is-next-line-empty.js"; import { locEnd } from "../loc.js"; import needsParentheses from "../parentheses/needs-parentheses.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { isLongCurriedCallExpression } from "../utilities/is-long-curried-call-expression.js"; +import { isMemberish } from "../utilities/is-memberish.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; +import { isSimpleCallArgument } from "../utilities/is-simple-call-argument.js"; import { isCallExpression, isFunctionOrArrowExpression, isMemberExpression, isNumericLiteral, } from "../utilities/node-types.js"; -import { - isLongCurriedCallExpression, - isMemberish, - isNextLineEmpty, - isSimpleCallArgument, -} from "../utilities/utilities.js"; import { printBindExpressionCallee } from "./bind-expression.js"; import printCallArguments from "./call-arguments.js"; import { printMemberLookup } from "./member.js"; diff --git a/src/language-js/print/member.js b/src/language-js/print/member.js index 4ff745998..b9481ebdb 100644 --- a/src/language-js/print/member.js +++ b/src/language-js/print/member.js @@ -5,16 +5,14 @@ import { lineSuffixBoundary, softline, } from "../../document/index.js"; +import { getCallArguments } from "../utilities/call-arguments.js"; import { isCallExpression, isChainElementWrapper, isMemberExpression, isNumericLiteral, } from "../utilities/node-types.js"; -import { - getCallArguments, - stripChainElementWrappers, -} from "../utilities/utilities.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; import { printOptionalToken } from "./miscellaneous.js"; const isCallExpressionWithArguments = (node) => diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index 4310d4bac..a729c62e5 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -15,12 +15,10 @@ import { locEnd, locStart } from "../loc.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import getTextWithoutComments from "../utilities/get-text-without-comments.js"; +import { isShorthandSpecifier } from "../utilities/is-shorthand-specifier.js"; +import { needsHardlineAfterDanglingComment } from "../utilities/needs-hardline-after-dangling-comment.js"; import { isStringLiteral } from "../utilities/node-types.js"; -import { - isShorthandSpecifier, - needsHardlineAfterDanglingComment, - shouldPrintComma, -} from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { printDecoratorsBeforeExport } from "./decorators.js"; import { printDeclareToken } from "./miscellaneous.js"; import { printObject } from "./object.js"; diff --git a/src/language-js/print/object.js b/src/language-js/print/object.js index 40377d91b..ff26c700f 100644 --- a/src/language-js/print/object.js +++ b/src/language-js/print/object.js @@ -19,8 +19,9 @@ import { } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import getTextWithoutComments from "../utilities/get-text-without-comments.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; import { isObjectType } from "../utilities/node-types.js"; -import { isNextLineEmpty, shouldPrintComma } from "../utilities/utilities.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { shouldHugTheOnlyParameter } from "./function-parameters.js"; import { printDanglingCommentsInList, diff --git a/src/language-js/print/statement-sequence.js b/src/language-js/print/statement-sequence.js index dd4efc4e3..76d9562ba 100644 --- a/src/language-js/print/statement-sequence.js +++ b/src/language-js/print/statement-sequence.js @@ -1,5 +1,5 @@ import { hardline } from "../../document/index.js"; -import { isNextLineEmpty } from "../utilities/utilities.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; /** * @import {Doc} from "../../document/index.js" diff --git a/src/language-js/print/switch-statement.js b/src/language-js/print/switch-statement.js index 02577c903..9c3284d40 100644 --- a/src/language-js/print/switch-statement.js +++ b/src/language-js/print/switch-statement.js @@ -7,7 +7,7 @@ import { } from "../../document/index.js"; import { printDanglingComments } from "../../main/comments/print.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; -import { isNextLineEmpty } from "../utilities/utilities.js"; +import { isNextLineEmpty } from "../utilities/is-next-line-empty.js"; import { printStatementSequence } from "./statement-sequence.js"; function printSwitchStatement(path, options, print) { diff --git a/src/language-js/print/template-literal.js b/src/language-js/print/template-literal.js index ac1fb01d0..a90e9b1ed 100644 --- a/src/language-js/print/template-literal.js +++ b/src/language-js/print/template-literal.js @@ -25,7 +25,7 @@ import { isBinaryish, isMemberExpression, } from "../utilities/node-types.js"; -import { stripChainElementWrappers } from "../utilities/utilities.js"; +import { stripChainElementWrappers } from "../utilities/strip-chain-element-wrappers.js"; /** * @import {Doc} from "../../document/index.js" diff --git a/src/language-js/print/ternary.js b/src/language-js/print/ternary.js index bab09ce2c..b4ef17dba 100644 --- a/src/language-js/print/ternary.js +++ b/src/language-js/print/ternary.js @@ -17,7 +17,9 @@ import { getComments, hasComment, } from "../utilities/comments.js"; -import isBlockComment from "../utilities/is-block-comment.js"; +import { isBlockComment } from "../utilities/is-block-comment.js"; +import { isLoneShortArgument } from "../utilities/is-lone-short-argument.js"; +import { isSimpleExpressionByNodeCount } from "../utilities/is-simple-expression-by-node-count.js"; import { isBinaryCastExpression, isCallExpression, @@ -27,10 +29,6 @@ import { isMemberExpression, isReturnOrThrowStatement, } from "../utilities/node-types.js"; -import { - isLoneShortArgument, - isSimpleExpressionByNodeCount, -} from "../utilities/utilities.js"; import { printTernaryOld } from "./ternary-old.js"; /** diff --git a/src/language-js/print/try-statement.js b/src/language-js/print/try-statement.js index 1675fd71a..9f0c373db 100644 --- a/src/language-js/print/try-statement.js +++ b/src/language-js/print/try-statement.js @@ -2,7 +2,7 @@ import { indent, softline } from "../../document/index.js"; import hasNewline from "../../utilities/has-newline.js"; import { locEnd, locStart } from "../loc.js"; import { hasComment } from "../utilities/comments.js"; -import isBlockComment from "../utilities/is-block-comment.js"; +import { isBlockComment } from "../utilities/is-block-comment.js"; function printTryStatement(path, options, print) { const { node } = path; diff --git a/src/language-js/print/type-annotation.js b/src/language-js/print/type-annotation.js index 02578ea7a..384e970b6 100644 --- a/src/language-js/print/type-annotation.js +++ b/src/language-js/print/type-annotation.js @@ -1,6 +1,6 @@ import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { isSimpleType } from "../utilities/is-simple-type.js"; import { isObjectType, isUnionType } from "../utilities/node-types.js"; -import { isSimpleType } from "../utilities/utilities.js"; import { shouldHugUnionType } from "./union-type.js"; /** diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js index 5f5d4cf79..8c01b7140 100644 --- a/src/language-js/print/type-parameters.js +++ b/src/language-js/print/type-parameters.js @@ -13,12 +13,10 @@ import { printDanglingComments } from "../../main/comments/print.js"; import hasNewline from "../../utilities/has-newline.js"; import { locEnd } from "../loc.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; +import { getFunctionParameters } from "../utilities/function-parameters.js"; import { isObjectType } from "../utilities/node-types.js"; +import { shouldPrintComma } from "../utilities/should-print-comma.js"; import { isTestCall } from "../utilities/test-libraries.js"; -import { - getFunctionParameters, - shouldPrintComma, -} from "../utilities/utilities.js"; import { isArrowFunctionVariableDeclarator } from "./assignment.js"; import { printTypeAnnotationProperty, diff --git a/src/language-js/print/union-type.js b/src/language-js/print/union-type.js index 8b6877697..b33e7a385 100644 --- a/src/language-js/print/union-type.js +++ b/src/language-js/print/union-type.js @@ -14,15 +14,13 @@ import { import needsParentheses from "../parentheses/needs-parentheses.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; import { isConditionalType, isTupleType, isTypeAlias, } from "../utilities/node-types.js"; -import { - hasLeadingOwnLineComment, - shouldUnionTypePrintOwnComments, -} from "../utilities/utilities.js"; +import { shouldUnionTypePrintOwnComments } from "../utilities/should-union-type-print-own-comments.js"; /** @import {Doc} from "../../document/index.js"; diff --git a/src/language-js/printers.js b/src/language-js/printers.js index 9a6c314bc..ab186e280 100644 --- a/src/language-js/printers.js +++ b/src/language-js/printers.js @@ -9,7 +9,7 @@ import { insertPragma } from "./pragma.js"; import { printComment } from "./print/comment.js"; import { printEstree } from "./print/index.js"; import getVisitorKeys from "./traverse/get-visitor-keys.js"; -import isBlockComment from "./utilities/is-block-comment.js"; +import { isBlockComment } from "./utilities/is-block-comment.js"; import hasPrettierIgnore from "./utilities/is-ignored.js"; const features = { diff --git a/src/language-js/semicolon/semicolon.js b/src/language-js/semicolon/semicolon.js index 7135a6143..4d4065cd0 100644 --- a/src/language-js/semicolon/semicolon.js +++ b/src/language-js/semicolon/semicolon.js @@ -1,10 +1,10 @@ import needsParentheses from "../parentheses/needs-parentheses.js"; import { shouldPrintParamsWithoutParens } from "../print/function.js"; -import { isJsxElement } from "../utilities/node-types.js"; import { getLeftSidePathName, hasNakedLeftSide, -} from "../utilities/utilities.js"; +} from "../utilities/left-side.js"; +import { isJsxElement } from "../utilities/node-types.js"; function shouldExpressionStatementPrintLeadingSemicolon(path, options) { if (options.semi) { diff --git a/src/language-js/types/estree.d.ts b/src/language-js/types/estree.d.ts index b672c237f..9bdc79c87 100644 --- a/src/language-js/types/estree.d.ts +++ b/src/language-js/types/estree.d.ts @@ -1,7 +1,7 @@ import type { ESTree as MeriyahESTree } from "meriyah"; import type * as Babel from "@babel/types"; import type { NGTree } from "angular-estree-parser"; -import type * as TSESTree from "./typescript-estree.ts"; +import type * as TSESTree from "./typescript-estree.js"; import type * as FlowESTree from "./flow-estree.js"; import type * as Babel from "./babel.js"; diff --git a/src/language-js/utilities/call-arguments.js b/src/language-js/utilities/call-arguments.js new file mode 100644 index 000000000..b9ddab24d --- /dev/null +++ b/src/language-js/utilities/call-arguments.js @@ -0,0 +1,89 @@ +/** +@import { + Node, + NodeMap, +} from "../types/estree.js"; +@typedef { + | NodeMap["NewExpression"] + | NodeMap["ImportExpression"] + | NodeMap["OptionalCallExpression"] + | NodeMap["CallExpression"] + | NodeMap["TSImportType"] + | NodeMap["TSExternalModuleReference"] +} CallLikeNode +*/ + +const callArgumentsCache = new WeakMap(); + +/** +@param {CallLikeNode} node +*/ +function getCallArguments(node) { + if (callArgumentsCache.has(node)) { + return callArgumentsCache.get(node); + } + + 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 === "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"); + } +} + +/** +@param {CallLikeNode} node +@param {number} index +*/ +function getCallArgumentSelector(node, index) { + if (node.type === "ImportExpression" || node.type === "TSImportType") { + if (index === 0 || index === (node.options ? -2 : -1)) { + return ["source"]; + } + if (node.options && (index === 1 || index === -1)) { + return ["options"]; + } + throw new RangeError("Invalid argument index"); + } else if (node.type === "TSExternalModuleReference") { + if (index === 0 || index === -1) { + return ["expression"]; + } + } else { + if (index < 0) { + index = node.arguments.length + index; + } + if (index >= 0 && index < node.arguments.length) { + return ["arguments", index]; + } + } + + /* c8 ignore next */ + throw new RangeError("Invalid argument index"); +} + +export { getCallArguments, getCallArgumentSelector, iterateCallArgumentsPath }; diff --git a/src/language-js/utilities/comments.js b/src/language-js/utilities/comments.js index 6f4c758ac..ffc857bb0 100644 --- a/src/language-js/utilities/comments.js +++ b/src/language-js/utilities/comments.js @@ -1,6 +1,6 @@ import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; -import isBlockComment from "./is-block-comment.js"; -import isLineComment from "./is-line-comment.js"; +import { isBlockComment } from "./is-block-comment.js"; +import { isLineComment } from "./is-line-comment.js"; import { isPrettierIgnoreComment } from "./is-prettier-ignore-comment.js"; /** diff --git a/src/language-js/utilities/function-parameters.js b/src/language-js/utilities/function-parameters.js new file mode 100644 index 000000000..b521ee32a --- /dev/null +++ b/src/language-js/utilities/function-parameters.js @@ -0,0 +1,47 @@ +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"); + } +} + +function hasRestParameter(node) { + if (node.rest) { + return true; + } + const parameters = getFunctionParameters(node); + return parameters.at(-1)?.type === "RestElement"; +} + +export { + getFunctionParameters, + hasRestParameter, + iterateFunctionParametersPath, +}; diff --git a/src/language-js/utilities/get-precedence.js b/src/language-js/utilities/get-precedence.js new file mode 100644 index 000000000..1277538e8 --- /dev/null +++ b/src/language-js/utilities/get-precedence.js @@ -0,0 +1,24 @@ +const PRECEDENCE = new Map( + [ + ["|>"], + ["??"], + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "===", "!=", "!=="], + ["<", ">", "<=", ">=", "in", "instanceof"], + [">>", "<<", ">>>"], + ["+", "-"], + ["*", "/", "%"], + ["**"], + ].flatMap((operators, index) => + operators.map((operator) => [operator, index]), + ), +); +function getPrecedence(operator) { + return PRECEDENCE.get(operator); +} + +export { getPrecedence }; diff --git a/src/language-js/utilities/get-raw.js b/src/language-js/utilities/get-raw.js index 1be1c737f..7d76afbfe 100644 --- a/src/language-js/utilities/get-raw.js +++ b/src/language-js/utilities/get-raw.js @@ -2,4 +2,4 @@ function getRaw(node) { return node.extra?.raw ?? node.raw; } -export default getRaw; +export { getRaw }; diff --git a/src/language-js/utilities/has-leading-own-line-comment.js b/src/language-js/utilities/has-leading-own-line-comment.js new file mode 100644 index 000000000..5b797fe49 --- /dev/null +++ b/src/language-js/utilities/has-leading-own-line-comment.js @@ -0,0 +1,35 @@ +import hasNewline from "../../utilities/has-newline.js"; +import { locEnd } from "../loc.js"; +import { CommentCheckFlags, hasComment } from "./comments.js"; +import { hasNodeIgnoreComment } from "./has-node-ignore-comment.js"; +import { isJsxElement } from "./node-types.js"; + +/** +@import { + Node, + NodeMap, + Comment, + NumericLiteral, + StringLiteral, + RegExpLiteral, + BigIntLiteral, +} from "../types/estree.js"; +@import AstPath from "../../common/ast-path.js"; +*/ + +/** + * @param {string} text + * @param {Node} node + * @returns {boolean} + */ +function hasLeadingOwnLineComment(text, node) { + if (isJsxElement(node)) { + return hasNodeIgnoreComment(node); + } + + return hasComment(node, CommentCheckFlags.Leading, (comment) => + hasNewline(text, locEnd(comment)), + ); +} + +export { hasLeadingOwnLineComment }; diff --git a/src/language-js/utilities/has-node.js b/src/language-js/utilities/has-node.js new file mode 100644 index 000000000..7e05f2d73 --- /dev/null +++ b/src/language-js/utilities/has-node.js @@ -0,0 +1,19 @@ +import { hasDescendant } from "../../utilities/ast.js"; +import getVisitorKeys from "../traverse/get-visitor-keys.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @param {(node: Node) => boolean} predicate + * @returns {boolean} + */ +function hasNode(node, predicate) { + return predicate(node) || hasDescendant(node, { getVisitorKeys, predicate }); +} + +export { hasNode }; diff --git a/src/language-js/utilities/is-as-const-expression.js b/src/language-js/utilities/is-as-const-expression.js new file mode 100644 index 000000000..104772346 --- /dev/null +++ b/src/language-js/utilities/is-as-const-expression.js @@ -0,0 +1,17 @@ +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +const isTsAsConstExpression = (node) => + node?.type === "TSAsExpression" && + node.typeAnnotation.type === "TSTypeReference" && + node.typeAnnotation.typeName.type === "Identifier" && + node.typeAnnotation.typeName.name === "const"; + +export { isTsAsConstExpression }; diff --git a/src/language-js/utilities/is-bitwise-operator.js b/src/language-js/utilities/is-bitwise-operator.js new file mode 100644 index 000000000..13b9b1d3d --- /dev/null +++ b/src/language-js/utilities/is-bitwise-operator.js @@ -0,0 +1,12 @@ +import { bitshiftOperators } from "./should-flatten.js"; + +function isBitwiseOperator(operator) { + return ( + Boolean(bitshiftOperators[operator]) || + operator === "|" || + operator === "^" || + operator === "&" + ); +} + +export { isBitwiseOperator }; diff --git a/src/language-js/utilities/is-block-comment.js b/src/language-js/utilities/is-block-comment.js index 0211d8091..e2d98c0ec 100644 --- a/src/language-js/utilities/is-block-comment.js +++ b/src/language-js/utilities/is-block-comment.js @@ -15,4 +15,4 @@ const isBlockComment = createTypeCheckFunction([ "MultiLine", ]); -export default isBlockComment; +export { isBlockComment }; diff --git a/src/language-js/utilities/is-boolean-type-coercion.js b/src/language-js/utilities/is-boolean-type-coercion.js new file mode 100644 index 000000000..0701a50e6 --- /dev/null +++ b/src/language-js/utilities/is-boolean-type-coercion.js @@ -0,0 +1,22 @@ +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function isBooleanTypeCoercion(node) { + return ( + node.type === "CallExpression" && + // @ts-expect-error -- expected + !node.optional && + node.arguments.length === 1 && + node.callee.type === "Identifier" && + node.callee.name === "Boolean" + ); +} + +export { isBooleanTypeCoercion }; diff --git a/src/language-js/utilities/is-flow-object-type-property-a-function.js b/src/language-js/utilities/is-flow-object-type-property-a-function.js new file mode 100644 index 000000000..a18603dbb --- /dev/null +++ b/src/language-js/utilities/is-flow-object-type-property-a-function.js @@ -0,0 +1,25 @@ +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {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" + ); +} + +export { isFlowObjectTypePropertyAFunction }; diff --git a/src/language-js/utilities/is-function-composition-arguments.js b/src/language-js/utilities/is-function-composition-arguments.js new file mode 100644 index 000000000..5f026740b --- /dev/null +++ b/src/language-js/utilities/is-function-composition-arguments.js @@ -0,0 +1,33 @@ +import { getCallArguments } from "./call-arguments.js"; +import { isCallExpression, isFunctionOrArrowExpression } from "./node-types.js"; +import { stripChainElementWrappers } from "./strip-chain-element-wrappers.js"; + +// 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 isFunctionCompositionArguments(args) { + if (args.length <= 1) { + return false; + } + let count = 0; + for (let arg of args) { + if (isFunctionOrArrowExpression(arg)) { + count += 1; + if (count > 1) { + return true; + } + } else { + arg = stripChainElementWrappers(arg); + if (isCallExpression(arg)) { + for (const childArg of getCallArguments(arg)) { + if (isFunctionOrArrowExpression(childArg)) { + return true; + } + } + } + } + } + return false; +} + +export { isFunctionCompositionArguments }; diff --git a/src/language-js/utilities/is-iife-callee-or-tagged-template-expression-tag.js b/src/language-js/utilities/is-iife-callee-or-tagged-template-expression-tag.js new file mode 100644 index 000000000..7b85ee8a4 --- /dev/null +++ b/src/language-js/utilities/is-iife-callee-or-tagged-template-expression-tag.js @@ -0,0 +1,13 @@ +import { isCallExpression } from "./node-types.js"; + +function isIifeCalleeOrTaggedTemplateExpressionTag(path) { + const { node } = path; + return ( + (node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression") && + ((path.key === "callee" && isCallExpression(path.parent)) || + (path.key === "tag" && path.parent.type === "TaggedTemplateExpression")) + ); +} + +export { isIifeCalleeOrTaggedTemplateExpressionTag }; diff --git a/src/language-js/utilities/is-indentable-block-comment.js b/src/language-js/utilities/is-indentable-block-comment.js index c9b9afb44..e36ee8b32 100644 --- a/src/language-js/utilities/is-indentable-block-comment.js +++ b/src/language-js/utilities/is-indentable-block-comment.js @@ -1,4 +1,4 @@ -import isBlockComment from "./is-block-comment.js"; +import { isBlockComment } from "./is-block-comment.js"; function isIndentableBlockCommentInternal(comment) { /* diff --git a/src/language-js/utilities/is-line-comment.js b/src/language-js/utilities/is-line-comment.js index 16e67ae45..9ec70501e 100644 --- a/src/language-js/utilities/is-line-comment.js +++ b/src/language-js/utilities/is-line-comment.js @@ -18,4 +18,4 @@ const isLineComment = createTypeCheckFunction([ "InterpreterDirective", ]); -export default isLineComment; +export { isLineComment }; diff --git a/src/language-js/utilities/is-lone-short-argument.js b/src/language-js/utilities/is-lone-short-argument.js new file mode 100644 index 000000000..925b42645 --- /dev/null +++ b/src/language-js/utilities/is-lone-short-argument.js @@ -0,0 +1,71 @@ +import printString from "../../utilities/print-string.js"; +import { hasComment } from "./comments.js"; +import { getRaw } from "./get-raw.js"; +import { isSignedNumericLiteral } from "./is-signed-numeric-literal.js"; +import { isLiteral, isStringLiteral } from "./node-types.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +const LONE_SHORT_ARGUMENT_THRESHOLD_RATE = 0.25; + +/** + * @param {Node} node + * @returns {boolean} + */ +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); +} + +export { isLoneShortArgument }; diff --git a/src/language-js/utilities/is-long-curried-call-expression.js b/src/language-js/utilities/is-long-curried-call-expression.js new file mode 100644 index 000000000..cc2d0c262 --- /dev/null +++ b/src/language-js/utilities/is-long-curried-call-expression.js @@ -0,0 +1,37 @@ +import { isCallExpression } from "./node-types.js"; + +/** +@import { + Node, + NodeMap, + Comment, + NumericLiteral, + StringLiteral, + RegExpLiteral, + BigIntLiteral, +} from "../types/estree.js"; +@import AstPath from "../../common/ast-path.js"; +*/ + +// 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 + ); +} + +export { isLongCurriedCallExpression }; diff --git a/src/language-js/utilities/is-meaningful-jsx-text.js b/src/language-js/utilities/is-meaningful-jsx-text.js index 47279befc..879589f64 100644 --- a/src/language-js/utilities/is-meaningful-jsx-text.js +++ b/src/language-js/utilities/is-meaningful-jsx-text.js @@ -1,4 +1,4 @@ -import getRaw from "./get-raw.js"; +import { getRaw } from "./get-raw.js"; import { jsxWhitespace } from "./jsx-whitespace.js"; /** diff --git a/src/language-js/utilities/is-memberish.js b/src/language-js/utilities/is-memberish.js new file mode 100644 index 000000000..3f06ae192 --- /dev/null +++ b/src/language-js/utilities/is-memberish.js @@ -0,0 +1,20 @@ +import { isMemberExpression } from "./node-types.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function isMemberish(node) { + return ( + isMemberExpression(node) || + (node.type === "BindExpression" && Boolean(node.object)) + ); +} + +export { isMemberish }; diff --git a/src/language-js/utilities/is-method.js b/src/language-js/utilities/is-method.js new file mode 100644 index 000000000..27773e5ca --- /dev/null +++ b/src/language-js/utilities/is-method.js @@ -0,0 +1,9 @@ +function isMethod(node) { + return ( + (node.method && node.kind === "init") || + node.kind === "get" || + node.kind === "set" + ); +} + +export { isMethod }; diff --git a/src/language-js/utilities/is-next-line-empty.js b/src/language-js/utilities/is-next-line-empty.js new file mode 100644 index 000000000..9f6f51f71 --- /dev/null +++ b/src/language-js/utilities/is-next-line-empty.js @@ -0,0 +1,29 @@ +import isNextLineEmptyAfterIndex from "../../utilities/is-next-line-empty.js"; +import { locEnd, locEndWithFullText } from "../loc.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +const isNextLineEmpty = (node, { originalText }) => { + const end = locEnd(node); + + if (isNextLineEmptyAfterIndex(originalText, end)) { + return true; + } + + const endWithSemicolon = locEndWithFullText(node); + if (endWithSemicolon === end) { + return false; + } + + return isNextLineEmptyAfterIndex(originalText, endWithSemicolon); +}; + +export { isNextLineEmpty }; diff --git a/src/language-js/utilities/is-nullish-coalescing.js b/src/language-js/utilities/is-nullish-coalescing.js new file mode 100644 index 000000000..cce65d962 --- /dev/null +++ b/src/language-js/utilities/is-nullish-coalescing.js @@ -0,0 +1,15 @@ +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function isNullishCoalescing(node) { + return node.type === "LogicalExpression" && node.operator === "??"; +} + +export { isNullishCoalescing }; diff --git a/src/language-js/utilities/is-object-property.js b/src/language-js/utilities/is-object-property.js new file mode 100644 index 000000000..f1620e890 --- /dev/null +++ b/src/language-js/utilities/is-object-property.js @@ -0,0 +1,20 @@ +import { isMethod } from "./is-method.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function isObjectProperty(node) { + return ( + node?.type === "ObjectProperty" || + (node?.type === "Property" && !isMethod(node)) + ); +} + +export { isObjectProperty }; diff --git a/src/language-js/utilities/is-shorthand-specifier.js b/src/language-js/utilities/is-shorthand-specifier.js new file mode 100644 index 000000000..6b353774d --- /dev/null +++ b/src/language-js/utilities/is-shorthand-specifier.js @@ -0,0 +1,53 @@ +import { hasSameLoc } from "../loc.js"; +import { getRaw } from "./get-raw.js"; +import { isStringLiteral } from "./node-types.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} specifier + * @returns {boolean} + */ +function isShorthandSpecifier(specifier) { + if ( + specifier.type !== "ImportSpecifier" && + specifier.type !== "ExportSpecifier" + ) { + return false; + } + + const { + local, + // @ts-expect-error -- FIXME + [specifier.type === "ImportSpecifier" ? "imported" : "exported"]: + importedOrExported, + } = specifier; + + if ( + local.type !== importedOrExported.type || + !hasSameLoc(local, importedOrExported) + ) { + return false; + } + + if (isStringLiteral(local)) { + return ( + local.value === importedOrExported.value && + getRaw(local) === getRaw(importedOrExported) + ); + } + + switch (local.type) { + case "Identifier": + return local.name === importedOrExported.name; + default: + /* c8 ignore next */ + return false; + } +} + +export { isShorthandSpecifier }; diff --git a/src/language-js/utilities/is-signed-numeric-literal.js b/src/language-js/utilities/is-signed-numeric-literal.js new file mode 100644 index 000000000..61ac41b44 --- /dev/null +++ b/src/language-js/utilities/is-signed-numeric-literal.js @@ -0,0 +1,23 @@ +import { isNumericLiteral } from "./node-types.js"; + +/** +@import { + Node, + NodeMap, + NumericLiteral, +} from "../types/estree.js"; +*/ + +/** +@param {Node} node +@returns {node is NodeMap["UnaryExpression"] & {operator: "+" | "-", argument: NumericLiteral}} +*/ +function isSignedNumericLiteral(node) { + return ( + node.type === "UnaryExpression" && + (node.operator === "+" || node.operator === "-") && + isNumericLiteral(node.argument) + ); +} + +export { isSignedNumericLiteral }; diff --git a/src/language-js/utilities/is-simple-call-argument.js b/src/language-js/utilities/is-simple-call-argument.js new file mode 100644 index 000000000..c609ef24e --- /dev/null +++ b/src/language-js/utilities/is-simple-call-argument.js @@ -0,0 +1,111 @@ +import getStringWidth from "../../utilities/get-string-width.js"; +import { getCallArguments } from "./call-arguments.js"; +import { createTypeCheckFunction } from "./create-type-check-function.js"; +import { + isArrayExpression, + isCallLikeExpression, + isLiteral, + isMemberExpression, + isObjectExpression, + isRegExpLiteral, +} from "./node-types.js"; +import { stripChainElementWrappers } from "./strip-chain-element-wrappers.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +const simpleCallArgumentUnaryOperators = new Set(["!", "-", "+", "~"]); + +const isSingleWordType = createTypeCheckFunction([ + "Identifier", + "ThisExpression", + "Super", + "PrivateName", + "PrivateIdentifier", +]); + +/** + * @param {Node} node + * @param {number} depth + * @returns {boolean} + */ +function isSimpleCallArgument(node, depth = 2) { + if (depth <= 0) { + return false; + } + + const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1); + + node = stripChainElementWrappers(node); + + if (isRegExpLiteral(node)) { + return ( + // @ts-expect-error -- safe + 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( + (property) => + // @ts-expect-error -- FIXME + !property.computed && + // @ts-expect-error -- FIXME + (property.shorthand || + // @ts-expect-error -- FIXME + (property.value && isChildSimple(property.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; +} + +export { isSimpleCallArgument }; diff --git a/src/language-js/utilities/is-simple-expression-by-node-count.js b/src/language-js/utilities/is-simple-expression-by-node-count.js new file mode 100644 index 000000000..19eefe119 --- /dev/null +++ b/src/language-js/utilities/is-simple-expression-by-node-count.js @@ -0,0 +1,30 @@ +import isObject from "../../utilities/is-object.js"; + +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; +} + +/** + * 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; +} + +export { isSimpleExpressionByNodeCount }; diff --git a/src/language-js/utilities/is-simple-type-annotation.js b/src/language-js/utilities/is-simple-type-annotation.js new file mode 100644 index 000000000..5681d4cc0 --- /dev/null +++ b/src/language-js/utilities/is-simple-type-annotation.js @@ -0,0 +1,15 @@ +import { createTypeCheckFunction } from "./create-type-check-function.js"; + +const isSimpleTypeAnnotation = createTypeCheckFunction([ + "TSThisType", + // literals + "NullLiteralTypeAnnotation", + "BooleanLiteralTypeAnnotation", + "StringLiteralTypeAnnotation", + "BigIntLiteralTypeAnnotation", + "NumberLiteralTypeAnnotation", + "TSLiteralType", + "TSTemplateLiteralType", +]); + +export { isSimpleTypeAnnotation }; diff --git a/src/language-js/utilities/is-simple-type.js b/src/language-js/utilities/is-simple-type.js new file mode 100644 index 000000000..fd7646a63 --- /dev/null +++ b/src/language-js/utilities/is-simple-type.js @@ -0,0 +1,25 @@ +import isFlowKeywordType from "./is-flow-keyword-type.js"; +import { isSimpleTypeAnnotation } from "./is-simple-type-annotation.js"; +import isTsKeywordType from "./is-ts-keyword-type.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function isSimpleType(node) { + return ( + isTsKeywordType(node) || + isFlowKeywordType(node) || + isSimpleTypeAnnotation(node) || + (node.type === "GenericTypeAnnotation" && !node.typeParameters) || + (node.type === "TSTypeReference" && !node.typeArguments) + ); +} + +export { isSimpleType }; diff --git a/src/language-js/utilities/is-template-on-its-own-line.js b/src/language-js/utilities/is-template-on-its-own-line.js new file mode 100644 index 000000000..2d2ae5269 --- /dev/null +++ b/src/language-js/utilities/is-template-on-its-own-line.js @@ -0,0 +1,32 @@ +import hasNewline from "../../utilities/has-newline.js"; +import { locStart } from "../loc.js"; +import { templateLiteralHasNewLines } from "./template-literal-has-new-lines.js"; + +/** +@import { + Node, + NodeMap, + Comment, + NumericLiteral, + StringLiteral, + RegExpLiteral, + BigIntLiteral, +} from "../types/estree.js"; +@import AstPath from "../../common/ast-path.js"; +*/ + +/** + * @param {NodeMap["TemplateLiteral"] | NodeMap["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 }) + ); +} + +export { isTemplateOnItsOwnLine }; diff --git a/src/language-js/utilities/is-type-annotation-a-function.js b/src/language-js/utilities/is-type-annotation-a-function.js new file mode 100644 index 000000000..71f889190 --- /dev/null +++ b/src/language-js/utilities/is-type-annotation-a-function.js @@ -0,0 +1,15 @@ +import { hasSameLocStart } from "../loc.js"; + +// 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) + ); +} + +export { isTypeAnnotationAFunction }; diff --git a/src/language-js/utilities/is-type-cast-comment.js b/src/language-js/utilities/is-type-cast-comment.js index 00dd25851..707238c12 100644 --- a/src/language-js/utilities/is-type-cast-comment.js +++ b/src/language-js/utilities/is-type-cast-comment.js @@ -1,4 +1,4 @@ -import isBlockComment from "./is-block-comment.js"; +import { isBlockComment } from "./is-block-comment.js"; /** @import {Comment} from "../types/estree.js" @@ -26,4 +26,4 @@ function isTypeCastComment(comment) { return cache.get(comment); } -export default isTypeCastComment; +export { isTypeCastComment }; diff --git a/src/language-js/utilities/left-side.js b/src/language-js/utilities/left-side.js new file mode 100644 index 000000000..54a8c622d --- /dev/null +++ b/src/language-js/utilities/left-side.js @@ -0,0 +1,86 @@ +import { + isBinaryCastExpression, + isCallExpression, + isChainElementWrapper, + isMemberExpression, +} from "./node-types.js"; + +/** +@import { + Node, + NodeMap, + Comment, + NumericLiteral, + StringLiteral, + RegExpLiteral, + BigIntLiteral, +} from "../types/estree.js"; +@import AstPath from "../../common/ast-path.js"; +*/ + +/** + * @param {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) || + isChainElementWrapper(node) + ); +} + +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."); +} + +export { getLeftSide, getLeftSidePathName, hasNakedLeftSide }; diff --git a/src/language-js/utilities/needs-hardline-after-dangling-comment.js b/src/language-js/utilities/needs-hardline-after-dangling-comment.js new file mode 100644 index 000000000..223d51143 --- /dev/null +++ b/src/language-js/utilities/needs-hardline-after-dangling-comment.js @@ -0,0 +1,24 @@ +import { CommentCheckFlags, getComments, hasComment } from "./comments.js"; +import { isBlockComment } from "./is-block-comment.js"; + +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * @param {Node} node + * @returns {boolean} + */ +function needsHardlineAfterDanglingComment(node) { + if (!hasComment(node)) { + return false; + } + const lastDanglingComment = getComments(node, CommentCheckFlags.Dangling).at( + -1, + ); + return lastDanglingComment && !isBlockComment(lastDanglingComment); +} + +export { needsHardlineAfterDanglingComment }; diff --git a/src/language-js/utilities/return-statement-has-leading-comment.js b/src/language-js/utilities/return-statement-has-leading-comment.js index c59d320f1..a181ded21 100644 --- a/src/language-js/utilities/return-statement-has-leading-comment.js +++ b/src/language-js/utilities/return-statement-has-leading-comment.js @@ -1,12 +1,9 @@ import hasNewlineInRange from "../../utilities/has-newline-in-range.js"; import { locEnd, locStart } from "../loc.js"; +import { hasLeadingOwnLineComment } from "../utilities/has-leading-own-line-comment.js"; import { CommentCheckFlags, hasComment } from "./comments.js"; +import { getLeftSide, hasNakedLeftSide } from "./left-side.js"; import { isJsxElement } from "./node-types.js"; -import { - getLeftSide, - hasLeadingOwnLineComment, - hasNakedLeftSide, -} from "./utilities.js"; // This recurses the return argument, looking for the first token // (the leftmost leaf node) and, if it (or its parents) has any diff --git a/src/language-js/utilities/should-expression-statement-print-own-comments.js b/src/language-js/utilities/should-expression-statement-print-own-comments.js index 48f3f3ea7..2130e7744 100644 --- a/src/language-js/utilities/should-expression-statement-print-own-comments.js +++ b/src/language-js/utilities/should-expression-statement-print-own-comments.js @@ -1,6 +1,6 @@ import { shouldExpressionStatementPrintLeadingSemicolon } from "../semicolon/semicolon.js"; import { CommentCheckFlags, getComments } from "./comments.js"; -import isTypeCastComment from "./is-type-cast-comment.js"; +import { isTypeCastComment } from "./is-type-cast-comment.js"; function shouldExpressionStatementPrintOwnComments(path, options) { if (!shouldExpressionStatementPrintLeadingSemicolon(path, options)) { diff --git a/src/language-js/utilities/should-flatten.js b/src/language-js/utilities/should-flatten.js new file mode 100644 index 000000000..64bb93e64 --- /dev/null +++ b/src/language-js/utilities/should-flatten.js @@ -0,0 +1,62 @@ +import { getPrecedence } from "./get-precedence.js"; + +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; +} + +export { bitshiftOperators, shouldFlatten }; diff --git a/src/language-js/utilities/should-print-comma.js b/src/language-js/utilities/should-print-comma.js new file mode 100644 index 000000000..ab41332bc --- /dev/null +++ b/src/language-js/utilities/should-print-comma.js @@ -0,0 +1,13 @@ +/** + * @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")) + ); +} + +export { shouldPrintComma }; diff --git a/src/language-js/utilities/should-union-type-print-own-comments.js b/src/language-js/utilities/should-union-type-print-own-comments.js new file mode 100644 index 000000000..713e84622 --- /dev/null +++ b/src/language-js/utilities/should-union-type-print-own-comments.js @@ -0,0 +1,16 @@ +import { isIntersectionType, isUnionType } from "./node-types.js"; + +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; +} + +export { shouldUnionTypePrintOwnComments }; diff --git a/src/language-js/utilities/starts-with-no-lookahead-token.js b/src/language-js/utilities/starts-with-no-lookahead-token.js new file mode 100644 index 000000000..5445c0d8c --- /dev/null +++ b/src/language-js/utilities/starts-with-no-lookahead-token.js @@ -0,0 +1,68 @@ +/** +@import { + Node, +} from "../types/estree.js"; +*/ + +/** + * 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 {Node} node + * @param {(leftmostNode: 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 "TSNonNullExpression": + case "TSSatisfiesExpression": + case "TSAsExpression": + case "AsExpression": + case "AsConstExpression": + case "SatisfiesExpression": + return startsWithNoLookaheadToken(node.expression, predicate); + default: + return predicate(node); + } +} + +export { startsWithNoLookaheadToken }; diff --git a/src/language-js/utilities/strip-chain-element-wrappers.js b/src/language-js/utilities/strip-chain-element-wrappers.js new file mode 100644 index 000000000..2e76f0511 --- /dev/null +++ b/src/language-js/utilities/strip-chain-element-wrappers.js @@ -0,0 +1,22 @@ +import { isChainElementWrapper } from "./node-types.js"; + +/** +@import { + Node, + NodeMap, +} from "../types/estree.js"; +*/ + +/** +@param {Node} node +@returns {Exclude} +*/ +function stripChainElementWrappers(node) { + while (isChainElementWrapper(node)) { + node = node.expression; + } + + return node; +} + +export { stripChainElementWrappers }; diff --git a/src/language-js/utilities/template-literal-has-new-lines.js b/src/language-js/utilities/template-literal-has-new-lines.js new file mode 100644 index 000000000..822283633 --- /dev/null +++ b/src/language-js/utilities/template-literal-has-new-lines.js @@ -0,0 +1,22 @@ +/** +@import { + Node, + NodeMap, + Comment, + NumericLiteral, + StringLiteral, + RegExpLiteral, + BigIntLiteral, +} from "../types/estree.js"; +@import AstPath from "../../common/ast-path.js"; +*/ + +/** + * @param {NodeMap["TemplateLiteral"]} template + * @returns {boolean} + */ +function templateLiteralHasNewLines(template) { + return template.quasis.some((quasi) => quasi.value.raw.includes("\n")); +} + +export { templateLiteralHasNewLines }; diff --git a/src/language-js/utilities/test-libraries.js b/src/language-js/utilities/test-libraries.js index a1962721e..f8207423f 100644 --- a/src/language-js/utilities/test-libraries.js +++ b/src/language-js/utilities/test-libraries.js @@ -1,3 +1,5 @@ +import { getCallArguments } from "./call-arguments.js"; +import { getFunctionParameters } from "./function-parameters.js"; import { isNodeMatches } from "./is-node-matches.js"; import { isCallExpression, @@ -5,7 +7,6 @@ import { isNumericLiteral, isStringLiteral, } from "./node-types.js"; -import { getCallArguments, getFunctionParameters } from "./utilities.js"; /** @import { diff --git a/src/language-js/utilities/utilities.js b/src/language-js/utilities/utilities.js deleted file mode 100644 index f13d4410d..000000000 --- a/src/language-js/utilities/utilities.js +++ /dev/null @@ -1,920 +0,0 @@ -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 isObject from "../../utilities/is-object.js"; -import printString from "../../utilities/print-string.js"; -import { - hasSameLoc, - hasSameLocStart, - locEnd, - locEndWithFullText, - locStart, -} from "../loc.js"; -import getVisitorKeys from "../traverse/get-visitor-keys.js"; -import { CommentCheckFlags, getComments, hasComment } from "./comments.js"; -import { createTypeCheckFunction } from "./create-type-check-function.js"; -import getRaw from "./get-raw.js"; -import { hasNodeIgnoreComment } from "./has-node-ignore-comment.js"; -import isBlockComment from "./is-block-comment.js"; -import isFlowKeywordType from "./is-flow-keyword-type.js"; -import isTsKeywordType from "./is-ts-keyword-type.js"; -import { - isArrayExpression, - isBinaryCastExpression, - isCallExpression, - isCallLikeExpression, - isChainElementWrapper, - isFunctionOrArrowExpression, - isIntersectionType, - isJsxElement, - isLiteral, - isMemberExpression, - isNumericLiteral, - isObjectExpression, - isRegExpLiteral, - isStringLiteral, - isUnionType, -} from "./node-types.js"; - -/** -@import { - Node, - NodeMap, - Comment, - NumericLiteral, - StringLiteral, - RegExpLiteral, - BigIntLiteral, -} from "../types/estree.js"; -@import AstPath from "../../common/ast-path.js"; -*/ - -/** - * @param {Node} node - * @param {(node: Node) => boolean} predicate - * @returns {boolean} - */ -function hasNode(node, predicate) { - return predicate(node) || hasDescendant(node, { getVisitorKeys, predicate }); -} - -/** - * @param {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) || - isChainElementWrapper(node) - ); -} - -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."); -} - -/** - * @param {Node} node - * @returns {boolean} - */ -function isNullishCoalescing(node) { - return node.type === "LogicalExpression" && node.operator === "??"; -} - -/** -@param {Node} node -@returns {node is NodeMap["UnaryExpression"] & {operator: "+" | "-", argument: NumericLiteral}} -*/ -function isSignedNumericLiteral(node) { - return ( - node.type === "UnaryExpression" && - (node.operator === "+" || node.operator === "-") && - isNumericLiteral(node.argument) - ); -} - -const isSingleWordType = createTypeCheckFunction([ - "Identifier", - "ThisExpression", - "Super", - "PrivateName", - "PrivateIdentifier", -]); - -function isMethod(node) { - return ( - (node.method && node.kind === "init") || - node.kind === "get" || - node.kind === "set" - ); -} - -/** - * @param {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) - ); -} - -/** - * @param {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 {Node} node - * @returns {boolean} - */ -function isSimpleType(node) { - return ( - isTsKeywordType(node) || - isFlowKeywordType(node) || - isSimpleTypeAnnotation(node) || - (node.type === "GenericTypeAnnotation" && !node.typeParameters) || - (node.type === "TSTypeReference" && !node.typeArguments) - ); -} - -/** - * 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 {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 {NodeMap["TemplateLiteral"]} template - * @returns {boolean} - */ -function templateLiteralHasNewLines(template) { - return template.quasis.some((quasi) => quasi.value.raw.includes("\n")); -} - -/** - * @param {NodeMap["TemplateLiteral"] | NodeMap["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 {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 (let arg of args) { - if (isFunctionOrArrowExpression(arg)) { - count += 1; - if (count > 1) { - return true; - } - } else { - arg = stripChainElementWrappers(arg); - 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; - } - - const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1); - - node = stripChainElementWrappers(node); - - if (isRegExpLiteral(node)) { - return ( - // @ts-expect-error -- safe - 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( - (property) => - // @ts-expect-error -- FIXME - !property.computed && - // @ts-expect-error -- FIXME - (property.shorthand || - // @ts-expect-error -- FIXME - (property.value && isChildSimple(property.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 {Node} node - * @param {(leftmostNode: 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 "TSNonNullExpression": - case "TSSatisfiesExpression": - case "TSAsExpression": - 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); - } - - 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 === "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) { - if (node.type === "ImportExpression" || node.type === "TSImportType") { - if (index === 0 || index === (node.options ? -2 : -1)) { - return ["source"]; - } - if (node.options && (index === 1 || index === -1)) { - return ["options"]; - } - throw new RangeError("Invalid argument index"); - } else if (node.type === "TSExternalModuleReference") { - if (index === 0 || index === -1) { - return ["expression"]; - } - } else { - if (index < 0) { - index = node.arguments.length + index; - } - if (index >= 0 && index < node.arguments.length) { - return ["arguments", index]; - } - } - - /* c8 ignore next */ - throw new RangeError("Invalid argument index"); -} - -/** - * @param {Node} node - * @returns {boolean} - */ -const isNextLineEmpty = (node, { originalText }) => { - const end = locEnd(node); - - if (isNextLineEmptyAfterIndex(originalText, end)) { - return true; - } - - const endWithSemicolon = locEndWithFullText(node); - if (endWithSemicolon === end) { - return false; - } - - return isNextLineEmptyAfterIndex(originalText, endWithSemicolon); -}; - -function isObjectProperty(node) { - return ( - node && - (node.type === "ObjectProperty" || - (node.type === "Property" && !isMethod(node))) - ); -} - -const isTsAsConstExpression = (node) => - node?.type === "TSAsExpression" && - node.typeAnnotation.type === "TSTypeReference" && - node.typeAnnotation.typeName.type === "Identifier" && - node.typeAnnotation.typeName.name === "const"; - -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" - ); -} - -function isShorthandSpecifier(specifier) { - if ( - specifier.type !== "ImportSpecifier" && - specifier.type !== "ExportSpecifier" - ) { - return false; - } - - const { - local, - [specifier.type === "ImportSpecifier" ? "imported" : "exported"]: - importedOrExported, - } = specifier; - - if ( - local.type !== importedOrExported.type || - !hasSameLoc(local, importedOrExported) - ) { - return false; - } - - if (isStringLiteral(local)) { - return ( - local.value === importedOrExported.value && - getRaw(local) === getRaw(importedOrExported) - ); - } - - switch (local.type) { - case "Identifier": - return local.name === importedOrExported.name; - default: - /* c8 ignore next */ - return false; - } -} - -function isIifeCalleeOrTaggedTemplateExpressionTag(path) { - const { node } = path; - return ( - (node.type === "FunctionExpression" || - node.type === "ArrowFunctionExpression") && - ((path.key === "callee" && isCallExpression(path.parent)) || - (path.key === "tag" && path.parent.type === "TaggedTemplateExpression")) - ); -} - -/** -@param {Node} node -@returns {Exclude} -*/ -function stripChainElementWrappers(node) { - while (isChainElementWrapper(node)) { - node = node.expression; - } - - return node; -} - -export { - getCallArguments, - getCallArgumentSelector, - getFunctionParameters, - getLeftSide, - getLeftSidePathName, - getPrecedence, - hasLeadingOwnLineComment, - hasNakedLeftSide, - hasNode, - hasRestParameter, - isBitwiseOperator, - isBooleanTypeCoercion, - isFlowObjectTypePropertyAFunction, - isFunctionCompositionArgs, - isIifeCalleeOrTaggedTemplateExpressionTag, - isLoneShortArgument, - isLongCurriedCallExpression, - isMemberish, - isMethod, - isNextLineEmpty, - isNullishCoalescing, - isObjectProperty, - isShorthandSpecifier, - isSignedNumericLiteral, - isSimpleCallArgument, - isSimpleExpressionByNodeCount, - isSimpleType, - isTemplateOnItsOwnLine, - isTsAsConstExpression, - isTypeAnnotationAFunction, - iterateCallArgumentsPath, - iterateFunctionParametersPath, - needsHardlineAfterDanglingComment, - shouldFlatten, - shouldPrintComma, - shouldUnionTypePrintOwnComments, - startsWithNoLookaheadToken, - stripChainElementWrappers, -};