diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js
index 86ebc3d72..47aafc252 100644
--- a/src/language-js/printer-estree.js
+++ b/src/language-js/printer-estree.js
@@ -16,7 +16,6 @@ const {
printString,
printNumber,
hasIgnoreComment,
- skipWhitespace,
hasNodeIgnoreComment,
getPenultimate,
startsWithNoLookaheadToken,
@@ -29,7 +28,6 @@ const {
isNextLineEmptyAfterIndex,
getNextNonSpaceNonCommentCharacterIndex
} = require("../common/util-shared");
-const isIdentifierName = require("esutils").keyword.isIdentifierNameES5;
const embed = require("./embed");
const clean = require("./clean");
const insertPragma = require("./pragma").insertPragma;
@@ -41,12 +39,57 @@ const {
} = require("./html-binding");
const preprocess = require("./preprocess");
const {
- getLeftSide,
+ classChildNeedsASIProtection,
+ classPropMayCauseASIProblems,
+ conditionalExpressionChainContainsJSX,
+ getFlowVariance,
getLeftSidePathName,
- hasNakedLeftSide,
- hasNode,
+ getTypeScriptMappedTypeModifier,
+ hasDanglingComments,
hasFlowAnnotationComment,
- hasFlowShorthandAnnotationComment
+ hasFlowShorthandAnnotationComment,
+ hasLeadingComment,
+ hasLeadingOwnLineComment,
+ hasNakedLeftSide,
+ hasNewlineBetweenOrAfterDecorators,
+ hasNgSideEffect,
+ hasPrettierIgnore,
+ hasTrailingComment,
+ identity,
+ isBinaryish,
+ isCallOrOptionalCallExpression,
+ isEmptyJSXElement,
+ isFlowAnnotationComment,
+ isFunctionCompositionArgs,
+ isFunctionNotation,
+ isFunctionOrArrowExpression,
+ isGetterOrSetter,
+ isJestEachTemplateLiteral,
+ isJSXNode,
+ isJSXWhitespaceExpression,
+ isLastStatement,
+ isLiteral,
+ isLongCurriedCallExpression,
+ isMeaningfulJSXText,
+ isMemberExpressionChain,
+ isMemberish,
+ isNgForOf,
+ isNodeStartingWithDeclare,
+ isNumericLiteral,
+ isObjectType,
+ isObjectTypePropertyAFunction,
+ isSimpleFlowType,
+ isSimpleTemplateLiteral,
+ isStringLiteral,
+ isStringPropSafeToCoerceToIdentifier,
+ isTemplateOnItsOwnLine,
+ isTestCall,
+ isTheOnlyJSXElementInMarkdown,
+ isTypeAnnotationAFunction,
+ matchJsxWhitespaceRegex,
+ needsHardlineAfterDanglingComment,
+ rawText,
+ returnArgumentHasLeadingComment
} = require("./utils");
const needsQuoteProps = new WeakMap();
@@ -202,17 +245,6 @@ function genericPrint(path, options, printPath, args) {
return concat(parts);
}
-function hasNewlineBetweenOrAfterDecorators(node, options) {
- return (
- hasNewlineInRange(
- options.originalText,
- options.locStart(node.decorators[0]),
- options.locEnd(getLast(node.decorators))
- ) ||
- hasNewline(options.originalText, options.locEnd(getLast(node.decorators)))
- );
-}
-
function printDecorators(path, options, print) {
const node = path.getValue();
return group(
@@ -223,40 +255,6 @@ function printDecorators(path, options, print) {
);
}
-function hasPrettierIgnore(path) {
- return hasIgnoreComment(path) || hasJsxIgnoreComment(path);
-}
-
-function hasJsxIgnoreComment(path) {
- const node = path.getValue();
- const parent = path.getParentNode();
- if (!parent || !node || !isJSXNode(node) || !isJSXNode(parent)) {
- return false;
- }
-
- // Lookup the previous sibling, ignoring any empty JSXText elements
- const index = parent.children.indexOf(node);
- let prevSibling = null;
- for (let i = index; i > 0; i--) {
- const candidate = parent.children[i - 1];
- if (candidate.type === "JSXText" && !isMeaningfulJSXText(candidate)) {
- continue;
- }
- prevSibling = candidate;
- break;
- }
-
- return (
- prevSibling &&
- prevSibling.type === "JSXExpressionContainer" &&
- prevSibling.expression.type === "JSXEmptyExpression" &&
- prevSibling.expression.comments &&
- prevSibling.expression.comments.find(
- comment => comment.value.trim() === "prettier-ignore"
- )
- );
-}
-
/**
* The following is the shared logic for
* ternary operators, namely ConditionalExpression
@@ -419,15 +417,6 @@ function printTernaryOperator(path, options, print, operatorOptions) {
);
}
-function getTypeScriptMappedTypeModifier(tokenNode, keyword) {
- if (tokenNode === "+") {
- return "+" + keyword;
- } else if (tokenNode === "-") {
- return "-" + keyword;
- }
- return keyword;
-}
-
function printPathNoParens(path, options, print, args) {
const n = path.getValue();
const semi = options.semi ? ";" : "";
@@ -3560,30 +3549,6 @@ function printPathNoParens(path, options, print, args) {
}
}
-function isNgForOf(node, index, parentNode) {
- return (
- node.type === "NGMicrosyntaxKeyedExpression" &&
- node.key.name === "of" &&
- index === 1 &&
- parentNode.body[0].type === "NGMicrosyntaxLet" &&
- parentNode.body[0].value === null
- );
-}
-
-/** identify if an angular expression seems to have side effects */
-function hasNgSideEffect(path) {
- return hasNode(path.getValue(), node => {
- switch (node.type) {
- case undefined:
- return false;
- case "CallExpression":
- case "OptionalCallExpression":
- case "AssignmentExpression":
- return true;
- }
- });
-}
-
function printStatementSequence(path, options, print) {
const printed = [];
@@ -3825,57 +3790,6 @@ function shouldGroupFirstArg(args) {
);
}
-function isSimpleFlowType(node) {
- const flowTypeAnnotations = [
- "AnyTypeAnnotation",
- "NullLiteralTypeAnnotation",
- "GenericTypeAnnotation",
- "ThisTypeAnnotation",
- "NumberTypeAnnotation",
- "VoidTypeAnnotation",
- "EmptyTypeAnnotation",
- "MixedTypeAnnotation",
- "BooleanTypeAnnotation",
- "BooleanLiteralTypeAnnotation",
- "StringTypeAnnotation"
- ];
-
- return (
- node &&
- flowTypeAnnotations.indexOf(node.type) !== -1 &&
- !(node.type === "GenericTypeAnnotation" && node.typeParameters)
- );
-}
-
-function isJestEachTemplateLiteral(node, parentNode) {
- /**
- * describe.each`table`(name, fn)
- * describe.only.each`table`(name, fn)
- * describe.skip.each`table`(name, fn)
- * test.each`table`(name, fn)
- * test.only.each`table`(name, fn)
- * test.skip.each`table`(name, fn)
- *
- * Ref: https://github.com/facebook/jest/pull/6102
- */
- const jestEachTriggerRegex = /^[xf]?(describe|it|test)$/;
- return (
- parentNode.type === "TaggedTemplateExpression" &&
- parentNode.quasi === node &&
- parentNode.tag.type === "MemberExpression" &&
- parentNode.tag.property.type === "Identifier" &&
- parentNode.tag.property.name === "each" &&
- ((parentNode.tag.object.type === "Identifier" &&
- jestEachTriggerRegex.test(parentNode.tag.object.name)) ||
- (parentNode.tag.object.type === "MemberExpression" &&
- parentNode.tag.object.property.type === "Identifier" &&
- (parentNode.tag.object.property.name === "only" ||
- parentNode.tag.object.property.name === "skip") &&
- parentNode.tag.object.object.type === "Identifier" &&
- jestEachTriggerRegex.test(parentNode.tag.object.object.name)))
- );
-}
-
function printJestEachTemplateLiteral(node, expressions, options) {
/**
* a | b | expected
@@ -3966,97 +3880,6 @@ function printJestEachTemplateLiteral(node, expressions, options) {
}
}
-/** @param node {import("estree").TemplateLiteral} */
-function isSimpleTemplateLiteral(node) {
- if (node.expressions.length === 0) {
- return false;
- }
-
- return node.expressions.every(expr => {
- // Disallow comments since printDocToString can't print them here
- if (expr.comments) {
- return false;
- }
-
- // Allow `x` and `this`
- if (expr.type === "Identifier" || expr.type === "ThisExpression") {
- return true;
- }
-
- // Allow `a.b.c`, `a.b[c]`, and `this.x.y`
- if (
- (expr.type === "MemberExpression" ||
- expr.type === "OptionalMemberExpression") &&
- (expr.property.type === "Identifier" || expr.property.type === "Literal")
- ) {
- let ancestor = expr;
- while (
- ancestor.type === "MemberExpression" ||
- ancestor.type === "OptionalMemberExpression"
- ) {
- ancestor = ancestor.object;
- if (ancestor.comments) {
- return false;
- }
- }
-
- if (
- ancestor.type === "Identifier" ||
- ancestor.type === "ThisExpression"
- ) {
- return true;
- }
-
- return false;
- }
-
- return false;
- });
-}
-
-// Logic to check for args with multiple anonymous functions. For instance,
-// the following call should be split on multiple lines for readability:
-// source.pipe(map((x) => x + x), filter((x) => x % 2 === 0))
-function isFunctionCompositionArgs(args) {
- if (args.length <= 1) {
- return false;
- }
- let count = 0;
- for (const arg of args) {
- if (isFunctionOrArrowExpression(arg)) {
- count += 1;
- if (count > 1) {
- return true;
- }
- } else if (isCallOrOptionalCallExpression(arg)) {
- for (const childArg of arg.arguments) {
- 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.
-function isLongCurriedCallExpression(path) {
- const node = path.getValue();
- const parent = path.getParentNode();
- return (
- isCallOrOptionalCallExpression(node) &&
- isCallOrOptionalCallExpression(parent) &&
- parent.callee === node &&
- node.arguments.length > parent.arguments.length &&
- parent.arguments.length > 0
- );
-}
-
function printArgumentsList(path, options, print) {
const node = path.getValue();
const args = node.arguments;
@@ -4683,27 +4506,6 @@ function printFlowDeclaration(path, parts) {
return concat(parts);
}
-function getFlowVariance(path) {
- if (!path.variance) {
- return null;
- }
-
- // Babel 7.0 currently uses variance node type, and flow should
- // follow suit soon:
- // https://github.com/babel/babel/issues/4722
- const variance = path.variance.kind || path.variance;
-
- switch (variance) {
- case "plus":
- return "+";
- case "minus":
- return "-";
- default:
- /* istanbul ignore next */
- return variance;
- }
-}
-
function printTypeScriptModifiers(path, options, print) {
const n = path.getValue();
if (!n.modifiers || !n.modifiers.length) {
@@ -5275,153 +5077,6 @@ function printMemberChain(path, options, print) {
]);
}
-function isCallOrOptionalCallExpression(node) {
- return (
- node.type === "CallExpression" || node.type === "OptionalCallExpression"
- );
-}
-
-function isJSXNode(node) {
- return node.type === "JSXElement" || node.type === "JSXFragment";
-}
-
-function isEmptyJSXElement(node) {
- if (node.children.length === 0) {
- return true;
- }
- if (node.children.length > 1) {
- return false;
- }
-
- // if there is one text child and does not contain any meaningful text
- // we can treat the element as empty.
- const child = node.children[0];
- return isLiteral(child) && !isMeaningfulJSXText(child);
-}
-
-// Only space, newline, carriage return, and tab are treated as whitespace
-// inside JSX.
-const jsxWhitespaceChars = " \n\r\t";
-const containsNonJsxWhitespaceRegex = new RegExp(
- "[^" + jsxWhitespaceChars + "]"
-);
-const matchJsxWhitespaceRegex = new RegExp("([" + jsxWhitespaceChars + "]+)");
-
-// Meaningful if it contains non-whitespace characters,
-// or it contains whitespace without a new line.
-function isMeaningfulJSXText(node) {
- return (
- isLiteral(node) &&
- (containsNonJsxWhitespaceRegex.test(rawText(node)) ||
- !/\n/.test(rawText(node)))
- );
-}
-
-function conditionalExpressionChainContainsJSX(node) {
- return Boolean(getConditionalChainContents(node).find(isJSXNode));
-}
-
-// If we have nested conditional expressions, we want to print them in JSX mode
-// if there's at least one JSXElement somewhere in the tree.
-//
-// A conditional expression chain like this should be printed in normal mode,
-// because there aren't JSXElements anywhere in it:
-//
-// isA ? "A" : isB ? "B" : isC ? "C" : "Unknown";
-//
-// But a conditional expression chain like this should be printed in JSX mode,
-// because there is a JSXElement in the last ConditionalExpression:
-//
-// isA ? "A" : isB ? "B" : isC ? "C" : Unknown;
-//
-// This type of ConditionalExpression chain is structured like this in the AST:
-//
-// ConditionalExpression {
-// test: ...,
-// consequent: ...,
-// alternate: ConditionalExpression {
-// test: ...,
-// consequent: ...,
-// alternate: ConditionalExpression {
-// test: ...,
-// consequent: ...,
-// alternate: ...,
-// }
-// }
-// }
-//
-// We want to traverse over that shape and convert it into a flat structure so
-// that we can find if there's a JSXElement somewhere inside.
-function getConditionalChainContents(node) {
- // Given this code:
- //
- // // Using a ConditionalExpression as the consequent is uncommon, but should
- // // be handled.
- // A ? B : C ? D : E ? F ? G : H : I
- //
- // which has this AST:
- //
- // ConditionalExpression {
- // test: Identifier(A),
- // consequent: Identifier(B),
- // alternate: ConditionalExpression {
- // test: Identifier(C),
- // consequent: Identifier(D),
- // alternate: ConditionalExpression {
- // test: Identifier(E),
- // consequent: ConditionalExpression {
- // test: Identifier(F),
- // consequent: Identifier(G),
- // alternate: Identifier(H),
- // },
- // alternate: Identifier(I),
- // }
- // }
- // }
- //
- // we should return this Array:
- //
- // [
- // Identifier(A),
- // Identifier(B),
- // Identifier(C),
- // Identifier(D),
- // Identifier(E),
- // Identifier(F),
- // Identifier(G),
- // Identifier(H),
- // Identifier(I)
- // ];
- //
- // This loses the information about whether each node was the test,
- // consequent, or alternate, but we don't care about that here- we are only
- // flattening this structure to find if there's any JSXElements inside.
- const nonConditionalExpressions = [];
-
- function recurse(node) {
- if (node.type === "ConditionalExpression") {
- recurse(node.test);
- recurse(node.consequent);
- recurse(node.alternate);
- } else {
- nonConditionalExpressions.push(node);
- }
- }
- recurse(node);
-
- return nonConditionalExpressions;
-}
-
-// Detect an expression node representing `{" "}`
-function isJSXWhitespaceExpression(node) {
- return (
- node.type === "JSXExpressionContainer" &&
- isLiteral(node.expression) &&
- node.expression.value === " " &&
- !node.expression.comments
- );
-}
-
function separatorNoWhitespace(
isFacebookTranslationTag,
child,
@@ -5852,22 +5507,6 @@ function maybeWrapJSXElementInParens(path, elem) {
);
}
-function isBinaryish(node) {
- return (
- node.type === "BinaryExpression" ||
- node.type === "LogicalExpression" ||
- node.type === "NGPipeExpression"
- );
-}
-
-function isMemberish(node) {
- return (
- node.type === "MemberExpression" ||
- node.type === "OptionalMemberExpression" ||
- (node.type === "BindExpression" && node.object)
- );
-}
-
function shouldInlineLogicalExpression(node) {
if (node.type !== "LogicalExpression") {
return false;
@@ -6080,45 +5719,6 @@ function printRegex(node) {
return `/${node.pattern}/${flags}`;
}
-function isLastStatement(path) {
- const parent = path.getParentNode();
- if (!parent) {
- return true;
- }
- const node = path.getValue();
- const body = (parent.body || parent.consequent).filter(
- stmt => stmt.type !== "EmptyStatement"
- );
- return body && body[body.length - 1] === node;
-}
-
-function hasLeadingComment(node) {
- return node.comments && node.comments.some(comment => comment.leading);
-}
-
-function hasTrailingComment(node) {
- return node.comments && node.comments.some(comment => comment.trailing);
-}
-
-function hasLeadingOwnLineComment(text, node, options) {
- if (isJSXNode(node)) {
- return hasNodeIgnoreComment(node);
- }
-
- const res =
- node.comments &&
- node.comments.some(
- comment => comment.leading && hasNewline(text, options.locEnd(comment))
- );
- return res;
-}
-
-function isFlowAnnotationComment(text, typeAnnotation, options) {
- const start = options.locStart(typeAnnotation);
- const end = skipWhitespace(text, options.locEnd(typeAnnotation));
- return text.substr(start, 2) === "/*" && text.substr(end, 2) === "*/";
-}
-
function exprNeedsASIProtection(path, options) {
const node = path.getValue();
@@ -6170,159 +5770,6 @@ function stmtNeedsASIProtection(path, options) {
);
}
-function classPropMayCauseASIProblems(path) {
- const node = path.getNode();
-
- if (node.type !== "ClassProperty") {
- return false;
- }
-
- const name = node.key && node.key.name;
-
- // this isn't actually possible yet with most parsers available today
- // so isn't properly tested yet.
- if (
- (name === "static" || name === "get" || name === "set") &&
- !node.value &&
- !node.typeAnnotation
- ) {
- return true;
- }
-}
-
-function classChildNeedsASIProtection(node) {
- if (!node) {
- return;
- }
-
- if (
- node.static ||
- node.accessibility // TypeScript
- ) {
- return false;
- }
-
- if (!node.computed) {
- const name = node.key && node.key.name;
- if (name === "in" || name === "instanceof") {
- return true;
- }
- }
- switch (node.type) {
- case "ClassProperty":
- case "TSAbstractClassProperty":
- return node.computed;
- case "MethodDefinition": // Flow
- case "TSAbstractMethodDefinition": // TypeScript
- case "ClassMethod":
- case "ClassPrivateMethod": {
- // Babel
- const isAsync = node.value ? node.value.async : node.async;
- const isGenerator = node.value ? node.value.generator : node.generator;
- if (isAsync || node.kind === "get" || node.kind === "set") {
- return false;
- }
- if (node.computed || isGenerator) {
- return true;
- }
- return false;
- }
-
- default:
- /* istanbul ignore next */
- return false;
- }
-}
-
-// This recurses the return argument, looking for the first token
-// (the leftmost leaf node) and, if it (or its parents) has any
-// leadingComments, returns true (so it can be wrapped in parens).
-function returnArgumentHasLeadingComment(options, argument) {
- if (hasLeadingOwnLineComment(options.originalText, argument, options)) {
- return true;
- }
-
- if (hasNakedLeftSide(argument)) {
- let leftMost = argument;
- let newLeftMost;
- while ((newLeftMost = getLeftSide(leftMost))) {
- leftMost = newLeftMost;
-
- if (hasLeadingOwnLineComment(options.originalText, leftMost, options)) {
- return true;
- }
- }
- }
-
- return false;
-}
-
-function isMemberExpressionChain(node) {
- if (
- node.type !== "MemberExpression" &&
- node.type !== "OptionalMemberExpression"
- ) {
- return false;
- }
- if (node.object.type === "Identifier") {
- return true;
- }
- return isMemberExpressionChain(node.object);
-}
-
-// Hack to differentiate between the following two which have the same ast
-// type T = { method: () => void };
-// type T = { method(): void };
-function isObjectTypePropertyAFunction(node, options) {
- return (
- (node.type === "ObjectTypeProperty" ||
- node.type === "ObjectTypeInternalSlot") &&
- node.value.type === "FunctionTypeAnnotation" &&
- !node.static &&
- !isFunctionNotation(node, options)
- );
-}
-
-// TODO: This is a bad hack and we need a better way to distinguish between
-// arrow functions and otherwise
-function isFunctionNotation(node, options) {
- return isGetterOrSetter(node) || sameLocStart(node, node.value, options);
-}
-
-function isGetterOrSetter(node) {
- return node.kind === "get" || node.kind === "set";
-}
-
-function sameLocStart(nodeA, nodeB, options) {
- return options.locStart(nodeA) === options.locStart(nodeB);
-}
-
-// Hack to differentiate between the following two which have the same ast
-// declare function f(a): void;
-// var f: (a) => void;
-function isTypeAnnotationAFunction(node, options) {
- return (
- (node.type === "TypeAnnotation" || node.type === "TSTypeAnnotation") &&
- node.typeAnnotation.type === "FunctionTypeAnnotation" &&
- !node.static &&
- !sameLocStart(node, node.typeAnnotation, options)
- );
-}
-
-function isNodeStartingWithDeclare(node, options) {
- if (!(options.parser === "flow" || options.parser === "typescript")) {
- return false;
- }
- return (
- options.originalText
- .slice(0, options.locStart(node))
- .match(/declare[ \t]*$/) ||
- options.originalText
- .slice(node.range[0], node.range[1])
- .startsWith("declare ")
- );
-}
-
function shouldHugType(node) {
if (isSimpleFlowType(node) || isObjectType(node)) {
return true;
@@ -6381,19 +5828,6 @@ function shouldHugArguments(fun) {
);
}
-function templateLiteralHasNewLines(template) {
- return template.quasis.some(quasi => quasi.value.raw.includes("\n"));
-}
-
-function isTemplateOnItsOwnLine(n, text, options) {
- return (
- ((n.type === "TemplateLiteral" && templateLiteralHasNewLines(n)) ||
- (n.type === "TaggedTemplateExpression" &&
- templateLiteralHasNewLines(n.quasi))) &&
- !hasNewline(text, options.locStart(n), { backwards: true })
- );
-}
-
function printArrayItems(path, options, printPath, print) {
const printedElements = [];
let separatorParts = [];
@@ -6414,173 +5848,6 @@ function printArrayItems(path, options, printPath, print) {
return concat(printedElements);
}
-function hasDanglingComments(node) {
- return (
- node.comments &&
- node.comments.some(comment => !comment.leading && !comment.trailing)
- );
-}
-
-function needsHardlineAfterDanglingComment(node) {
- if (!node.comments) {
- return false;
- }
- const lastDanglingComment = getLast(
- node.comments.filter(comment => !comment.leading && !comment.trailing)
- );
- return (
- lastDanglingComment && !handleComments.isBlockComment(lastDanglingComment)
- );
-}
-
-function isLiteral(node) {
- return (
- node.type === "BooleanLiteral" ||
- node.type === "DirectiveLiteral" ||
- node.type === "Literal" ||
- node.type === "NullLiteral" ||
- node.type === "NumericLiteral" ||
- node.type === "RegExpLiteral" ||
- node.type === "StringLiteral" ||
- node.type === "TemplateLiteral" ||
- node.type === "TSTypeLiteral" ||
- node.type === "JSXText"
- );
-}
-
-function isStringPropSafeToCoerceToIdentifier(node, options) {
- return (
- isStringLiteral(node.key) &&
- isIdentifierName(node.key.value) &&
- options.parser !== "json" &&
- !(options.parser === "typescript" && node.type === "ClassProperty")
- );
-}
-
-function isNumericLiteral(node) {
- return (
- node.type === "NumericLiteral" ||
- (node.type === "Literal" && typeof node.value === "number")
- );
-}
-
-function isStringLiteral(node) {
- return (
- node.type === "StringLiteral" ||
- (node.type === "Literal" && typeof node.value === "string")
- );
-}
-
-function isObjectType(n) {
- return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral";
-}
-
-const unitTestRe = /^(skip|[fx]?(it|describe|test))$/;
-
-// eg; `describe("some string", (done) => {})`
-function isTestCall(n, parent) {
- if (n.type !== "CallExpression") {
- return false;
- }
- if (n.arguments.length === 1) {
- if (isAngularTestWrapper(n) && parent && isTestCall(parent)) {
- return isFunctionOrArrowExpression(n.arguments[0]);
- }
-
- if (isUnitTestSetUp(n)) {
- return isAngularTestWrapper(n.arguments[0]);
- }
- } else if (n.arguments.length === 2 || n.arguments.length === 3) {
- if (
- ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) ||
- isSkipOrOnlyBlock(n)) &&
- (isTemplateLiteral(n.arguments[0]) || isStringLiteral(n.arguments[0]))
- ) {
- // it("name", () => { ... }, 2500)
- if (n.arguments[2] && !isNumericLiteral(n.arguments[2])) {
- return false;
- }
- return (
- (n.arguments.length === 2
- ? isFunctionOrArrowExpression(n.arguments[1])
- : isFunctionOrArrowExpressionWithBody(n.arguments[1]) &&
- n.arguments[1].params.length <= 1) ||
- isAngularTestWrapper(n.arguments[1])
- );
- }
- }
- return false;
-}
-
-function isSkipOrOnlyBlock(node) {
- return (
- (node.callee.type === "MemberExpression" ||
- node.callee.type === "OptionalMemberExpression") &&
- node.callee.object.type === "Identifier" &&
- node.callee.property.type === "Identifier" &&
- unitTestRe.test(node.callee.object.name) &&
- (node.callee.property.name === "only" ||
- node.callee.property.name === "skip")
- );
-}
-
-function isTemplateLiteral(node) {
- return node.type === "TemplateLiteral";
-}
-
-// `inject` is used in AngularJS 1.x, `async` in Angular 2+
-// example: https://docs.angularjs.org/guide/unit-testing#using-beforeall-
-function isAngularTestWrapper(node) {
- return (
- (node.type === "CallExpression" ||
- node.type === "OptionalCallExpression") &&
- node.callee.type === "Identifier" &&
- (node.callee.name === "async" ||
- node.callee.name === "inject" ||
- node.callee.name === "fakeAsync")
- );
-}
-
-function isFunctionOrArrowExpression(node) {
- return (
- node.type === "FunctionExpression" ||
- node.type === "ArrowFunctionExpression"
- );
-}
-
-function isFunctionOrArrowExpressionWithBody(node) {
- return (
- node.type === "FunctionExpression" ||
- (node.type === "ArrowFunctionExpression" &&
- node.body.type === "BlockStatement")
- );
-}
-
-function isUnitTestSetUp(n) {
- const unitTestSetUpRe = /^(before|after)(Each|All)$/;
- return (
- n.callee.type === "Identifier" &&
- unitTestSetUpRe.test(n.callee.name) &&
- n.arguments.length === 1
- );
-}
-
-function isTheOnlyJSXElementInMarkdown(options, path) {
- if (options.parentParser !== "markdown" && options.parentParser !== "mdx") {
- return false;
- }
-
- const node = path.getNode();
-
- if (!node.expression || !isJSXNode(node.expression)) {
- return false;
- }
-
- const parent = path.getParentNode();
-
- return parent.type === "Program" && parent.body.length == 1;
-}
-
function willPrintOwnComments(path /*, options */) {
const node = path.getValue();
const parent = path.getParentNode();
@@ -6685,14 +5952,6 @@ function printIndentableBlockComment(comment) {
]);
}
-function rawText(node) {
- return node.extra ? node.extra.raw : node.raw;
-}
-
-function identity(x) {
- return x;
-}
-
module.exports = {
preprocess,
print: genericPrint,
diff --git a/src/language-js/utils.js b/src/language-js/utils.js
index 5f60e04ae..60f7cbc05 100644
--- a/src/language-js/utils.js
+++ b/src/language-js/utils.js
@@ -1,5 +1,16 @@
"use strict";
+const {
+ getLast,
+ hasNewline,
+ hasNewlineInRange,
+ hasIgnoreComment,
+ hasNodeIgnoreComment,
+ skipWhitespace
+} = require("../common/util");
+const isIdentifierName = require("esutils").keyword.isIdentifierNameES5;
+const handleComments = require("./comments");
+
// We match any whitespace except line terminators because
// Flow annotation comments cannot be split across lines. For example:
//
@@ -107,11 +118,840 @@ function getLeftSidePathName(path, node) {
throw new Error("Unexpected node has no left side", node);
}
+function isLiteral(node) {
+ return (
+ node.type === "BooleanLiteral" ||
+ node.type === "DirectiveLiteral" ||
+ node.type === "Literal" ||
+ node.type === "NullLiteral" ||
+ node.type === "NumericLiteral" ||
+ node.type === "RegExpLiteral" ||
+ node.type === "StringLiteral" ||
+ node.type === "TemplateLiteral" ||
+ node.type === "TSTypeLiteral" ||
+ node.type === "JSXText"
+ );
+}
+
+function isNumericLiteral(node) {
+ return (
+ node.type === "NumericLiteral" ||
+ (node.type === "Literal" && typeof node.value === "number")
+ );
+}
+
+function isStringLiteral(node) {
+ return (
+ node.type === "StringLiteral" ||
+ (node.type === "Literal" && typeof node.value === "string")
+ );
+}
+
+function isObjectType(n) {
+ return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral";
+}
+
+function isFunctionOrArrowExpression(node) {
+ return (
+ node.type === "FunctionExpression" ||
+ node.type === "ArrowFunctionExpression"
+ );
+}
+
+function isFunctionOrArrowExpressionWithBody(node) {
+ return (
+ node.type === "FunctionExpression" ||
+ (node.type === "ArrowFunctionExpression" &&
+ node.body.type === "BlockStatement")
+ );
+}
+
+function isTemplateLiteral(node) {
+ return node.type === "TemplateLiteral";
+}
+
+// `inject` is used in AngularJS 1.x, `async` in Angular 2+
+// example: https://docs.angularjs.org/guide/unit-testing#using-beforeall-
+function isAngularTestWrapper(node) {
+ return (
+ (node.type === "CallExpression" ||
+ node.type === "OptionalCallExpression") &&
+ node.callee.type === "Identifier" &&
+ (node.callee.name === "async" ||
+ node.callee.name === "inject" ||
+ node.callee.name === "fakeAsync")
+ );
+}
+
+function isJSXNode(node) {
+ return node.type === "JSXElement" || node.type === "JSXFragment";
+}
+
+function isTheOnlyJSXElementInMarkdown(options, path) {
+ if (options.parentParser !== "markdown" && options.parentParser !== "mdx") {
+ return false;
+ }
+
+ const node = path.getNode();
+
+ if (!node.expression || !isJSXNode(node.expression)) {
+ return false;
+ }
+
+ const parent = path.getParentNode();
+
+ return parent.type === "Program" && parent.body.length == 1;
+}
+
+// Detect an expression node representing `{" "}`
+function isJSXWhitespaceExpression(node) {
+ return (
+ node.type === "JSXExpressionContainer" &&
+ isLiteral(node.expression) &&
+ node.expression.value === " " &&
+ !node.expression.comments
+ );
+}
+
+function isMemberExpressionChain(node) {
+ if (
+ node.type !== "MemberExpression" &&
+ node.type !== "OptionalMemberExpression"
+ ) {
+ return false;
+ }
+ if (node.object.type === "Identifier") {
+ return true;
+ }
+ return isMemberExpressionChain(node.object);
+}
+
+function isGetterOrSetter(node) {
+ return node.kind === "get" || node.kind === "set";
+}
+
+function sameLocStart(nodeA, nodeB, options) {
+ return options.locStart(nodeA) === options.locStart(nodeB);
+}
+
+// TODO: This is a bad hack and we need a better way to distinguish between
+// arrow functions and otherwise
+function isFunctionNotation(node, options) {
+ return isGetterOrSetter(node) || sameLocStart(node, node.value, options);
+}
+
+// Hack to differentiate between the following two which have the same ast
+// type T = { method: () => void };
+// type T = { method(): void };
+function isObjectTypePropertyAFunction(node, options) {
+ return (
+ (node.type === "ObjectTypeProperty" ||
+ node.type === "ObjectTypeInternalSlot") &&
+ node.value.type === "FunctionTypeAnnotation" &&
+ !node.static &&
+ !isFunctionNotation(node, options)
+ );
+}
+
+// Hack to differentiate between the following two which have the same ast
+// declare function f(a): void;
+// var f: (a) => void;
+function isTypeAnnotationAFunction(node, options) {
+ return (
+ (node.type === "TypeAnnotation" || node.type === "TSTypeAnnotation") &&
+ node.typeAnnotation.type === "FunctionTypeAnnotation" &&
+ !node.static &&
+ !sameLocStart(node, node.typeAnnotation, options)
+ );
+}
+
+function isBinaryish(node) {
+ return (
+ node.type === "BinaryExpression" ||
+ node.type === "LogicalExpression" ||
+ node.type === "NGPipeExpression"
+ );
+}
+
+function isMemberish(node) {
+ return (
+ node.type === "MemberExpression" ||
+ node.type === "OptionalMemberExpression" ||
+ (node.type === "BindExpression" && node.object)
+ );
+}
+
+function isNodeStartingWithDeclare(node, options) {
+ if (!(options.parser === "flow" || options.parser === "typescript")) {
+ return false;
+ }
+ return (
+ options.originalText
+ .slice(0, options.locStart(node))
+ .match(/declare[ \t]*$/) ||
+ options.originalText
+ .slice(node.range[0], node.range[1])
+ .startsWith("declare ")
+ );
+}
+
+function isSimpleFlowType(node) {
+ const flowTypeAnnotations = [
+ "AnyTypeAnnotation",
+ "NullLiteralTypeAnnotation",
+ "GenericTypeAnnotation",
+ "ThisTypeAnnotation",
+ "NumberTypeAnnotation",
+ "VoidTypeAnnotation",
+ "EmptyTypeAnnotation",
+ "MixedTypeAnnotation",
+ "BooleanTypeAnnotation",
+ "BooleanLiteralTypeAnnotation",
+ "StringTypeAnnotation"
+ ];
+
+ return (
+ node &&
+ flowTypeAnnotations.indexOf(node.type) !== -1 &&
+ !(node.type === "GenericTypeAnnotation" && node.typeParameters)
+ );
+}
+
+const unitTestRe = /^(skip|[fx]?(it|describe|test))$/;
+
+function isSkipOrOnlyBlock(node) {
+ return (
+ (node.callee.type === "MemberExpression" ||
+ node.callee.type === "OptionalMemberExpression") &&
+ node.callee.object.type === "Identifier" &&
+ node.callee.property.type === "Identifier" &&
+ unitTestRe.test(node.callee.object.name) &&
+ (node.callee.property.name === "only" ||
+ node.callee.property.name === "skip")
+ );
+}
+
+function isUnitTestSetUp(n) {
+ const unitTestSetUpRe = /^(before|after)(Each|All)$/;
+ return (
+ n.callee.type === "Identifier" &&
+ unitTestSetUpRe.test(n.callee.name) &&
+ n.arguments.length === 1
+ );
+}
+
+// eg; `describe("some string", (done) => {})`
+function isTestCall(n, parent) {
+ if (n.type !== "CallExpression") {
+ return false;
+ }
+ if (n.arguments.length === 1) {
+ if (isAngularTestWrapper(n) && parent && isTestCall(parent)) {
+ return isFunctionOrArrowExpression(n.arguments[0]);
+ }
+
+ if (isUnitTestSetUp(n)) {
+ return isAngularTestWrapper(n.arguments[0]);
+ }
+ } else if (n.arguments.length === 2 || n.arguments.length === 3) {
+ if (
+ ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) ||
+ isSkipOrOnlyBlock(n)) &&
+ (isTemplateLiteral(n.arguments[0]) || isStringLiteral(n.arguments[0]))
+ ) {
+ // it("name", () => { ... }, 2500)
+ if (n.arguments[2] && !isNumericLiteral(n.arguments[2])) {
+ return false;
+ }
+ return (
+ (n.arguments.length === 2
+ ? isFunctionOrArrowExpression(n.arguments[1])
+ : isFunctionOrArrowExpressionWithBody(n.arguments[1]) &&
+ n.arguments[1].params.length <= 1) ||
+ isAngularTestWrapper(n.arguments[1])
+ );
+ }
+ }
+ return false;
+}
+
+function hasLeadingComment(node) {
+ return node.comments && node.comments.some(comment => comment.leading);
+}
+
+function hasTrailingComment(node) {
+ return node.comments && node.comments.some(comment => comment.trailing);
+}
+
+function isCallOrOptionalCallExpression(node) {
+ return (
+ node.type === "CallExpression" || node.type === "OptionalCallExpression"
+ );
+}
+
+function hasDanglingComments(node) {
+ return (
+ node.comments &&
+ node.comments.some(comment => !comment.leading && !comment.trailing)
+ );
+}
+
+/** identify if an angular expression seems to have side effects */
+function hasNgSideEffect(path) {
+ return hasNode(path.getValue(), node => {
+ switch (node.type) {
+ case undefined:
+ return false;
+ case "CallExpression":
+ case "OptionalCallExpression":
+ case "AssignmentExpression":
+ return true;
+ }
+ });
+}
+
+function isNgForOf(node, index, parentNode) {
+ return (
+ node.type === "NGMicrosyntaxKeyedExpression" &&
+ node.key.name === "of" &&
+ index === 1 &&
+ parentNode.body[0].type === "NGMicrosyntaxLet" &&
+ parentNode.body[0].value === null
+ );
+}
+
+/** @param node {import("estree").TemplateLiteral} */
+function isSimpleTemplateLiteral(node) {
+ if (node.expressions.length === 0) {
+ return false;
+ }
+
+ return node.expressions.every(expr => {
+ // Disallow comments since printDocToString can't print them here
+ if (expr.comments) {
+ return false;
+ }
+
+ // Allow `x` and `this`
+ if (expr.type === "Identifier" || expr.type === "ThisExpression") {
+ return true;
+ }
+
+ // Allow `a.b.c`, `a.b[c]`, and `this.x.y`
+ if (
+ (expr.type === "MemberExpression" ||
+ expr.type === "OptionalMemberExpression") &&
+ (expr.property.type === "Identifier" || expr.property.type === "Literal")
+ ) {
+ let ancestor = expr;
+ while (
+ ancestor.type === "MemberExpression" ||
+ ancestor.type === "OptionalMemberExpression"
+ ) {
+ ancestor = ancestor.object;
+ if (ancestor.comments) {
+ return false;
+ }
+ }
+
+ if (
+ ancestor.type === "Identifier" ||
+ ancestor.type === "ThisExpression"
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+ });
+}
+
+function getFlowVariance(path) {
+ if (!path.variance) {
+ return null;
+ }
+
+ // Babel 7.0 currently uses variance node type, and flow should
+ // follow suit soon:
+ // https://github.com/babel/babel/issues/4722
+ const variance = path.variance.kind || path.variance;
+
+ switch (variance) {
+ case "plus":
+ return "+";
+ case "minus":
+ return "-";
+ default:
+ /* istanbul ignore next */
+ return variance;
+ }
+}
+
+function classPropMayCauseASIProblems(path) {
+ const node = path.getNode();
+
+ if (node.type !== "ClassProperty") {
+ return false;
+ }
+
+ const name = node.key && node.key.name;
+
+ // this isn't actually possible yet with most parsers available today
+ // so isn't properly tested yet.
+ if (
+ (name === "static" || name === "get" || name === "set") &&
+ !node.value &&
+ !node.typeAnnotation
+ ) {
+ return true;
+ }
+}
+
+function classChildNeedsASIProtection(node) {
+ if (!node) {
+ return;
+ }
+
+ if (
+ node.static ||
+ node.accessibility // TypeScript
+ ) {
+ return false;
+ }
+
+ if (!node.computed) {
+ const name = node.key && node.key.name;
+ if (name === "in" || name === "instanceof") {
+ return true;
+ }
+ }
+ switch (node.type) {
+ case "ClassProperty":
+ case "TSAbstractClassProperty":
+ return node.computed;
+ case "MethodDefinition": // Flow
+ case "TSAbstractMethodDefinition": // TypeScript
+ case "ClassMethod":
+ case "ClassPrivateMethod": {
+ // Babel
+ const isAsync = node.value ? node.value.async : node.async;
+ const isGenerator = node.value ? node.value.generator : node.generator;
+ if (isAsync || node.kind === "get" || node.kind === "set") {
+ return false;
+ }
+ if (node.computed || isGenerator) {
+ return true;
+ }
+ return false;
+ }
+
+ default:
+ /* istanbul ignore next */
+ return false;
+ }
+}
+
+function getTypeScriptMappedTypeModifier(tokenNode, keyword) {
+ if (tokenNode === "+") {
+ return "+" + keyword;
+ } else if (tokenNode === "-") {
+ return "-" + keyword;
+ }
+ return keyword;
+}
+
+function hasNewlineBetweenOrAfterDecorators(node, options) {
+ return (
+ hasNewlineInRange(
+ options.originalText,
+ options.locStart(node.decorators[0]),
+ options.locEnd(getLast(node.decorators))
+ ) ||
+ hasNewline(options.originalText, options.locEnd(getLast(node.decorators)))
+ );
+}
+
+// Only space, newline, carriage return, and tab are treated as whitespace
+// inside JSX.
+const jsxWhitespaceChars = " \n\r\t";
+const matchJsxWhitespaceRegex = new RegExp("([" + jsxWhitespaceChars + "]+)");
+const containsNonJsxWhitespaceRegex = new RegExp(
+ "[^" + jsxWhitespaceChars + "]"
+);
+
+// Meaningful if it contains non-whitespace characters,
+// or it contains whitespace without a new line.
+function isMeaningfulJSXText(node) {
+ return (
+ isLiteral(node) &&
+ (containsNonJsxWhitespaceRegex.test(rawText(node)) ||
+ !/\n/.test(rawText(node)))
+ );
+}
+
+function hasJsxIgnoreComment(path) {
+ const node = path.getValue();
+ const parent = path.getParentNode();
+ if (!parent || !node || !isJSXNode(node) || !isJSXNode(parent)) {
+ return false;
+ }
+
+ // Lookup the previous sibling, ignoring any empty JSXText elements
+ const index = parent.children.indexOf(node);
+ let prevSibling = null;
+ for (let i = index; i > 0; i--) {
+ const candidate = parent.children[i - 1];
+ if (candidate.type === "JSXText" && !isMeaningfulJSXText(candidate)) {
+ continue;
+ }
+ prevSibling = candidate;
+ break;
+ }
+
+ return (
+ prevSibling &&
+ prevSibling.type === "JSXExpressionContainer" &&
+ prevSibling.expression.type === "JSXEmptyExpression" &&
+ prevSibling.expression.comments &&
+ prevSibling.expression.comments.find(
+ comment => comment.value.trim() === "prettier-ignore"
+ )
+ );
+}
+
+function isEmptyJSXElement(node) {
+ if (node.children.length === 0) {
+ return true;
+ }
+ if (node.children.length > 1) {
+ return false;
+ }
+
+ // if there is one text child and does not contain any meaningful text
+ // we can treat the element as empty.
+ const child = node.children[0];
+ return isLiteral(child) && !isMeaningfulJSXText(child);
+}
+
+function hasPrettierIgnore(path) {
+ return hasIgnoreComment(path) || hasJsxIgnoreComment(path);
+}
+
+function isLastStatement(path) {
+ const parent = path.getParentNode();
+ if (!parent) {
+ return true;
+ }
+ const node = path.getValue();
+ const body = (parent.body || parent.consequent).filter(
+ stmt => stmt.type !== "EmptyStatement"
+ );
+ return body && body[body.length - 1] === node;
+}
+
+function isFlowAnnotationComment(text, typeAnnotation, options) {
+ const start = options.locStart(typeAnnotation);
+ const end = skipWhitespace(text, options.locEnd(typeAnnotation));
+ return text.substr(start, 2) === "/*" && text.substr(end, 2) === "*/";
+}
+
+function hasLeadingOwnLineComment(text, node, options) {
+ if (isJSXNode(node)) {
+ return hasNodeIgnoreComment(node);
+ }
+
+ const res =
+ node.comments &&
+ node.comments.some(
+ comment => comment.leading && hasNewline(text, options.locEnd(comment))
+ );
+ return res;
+}
+
+// This recurses the return argument, looking for the first token
+// (the leftmost leaf node) and, if it (or its parents) has any
+// leadingComments, returns true (so it can be wrapped in parens).
+function returnArgumentHasLeadingComment(options, argument) {
+ if (hasLeadingOwnLineComment(options.originalText, argument, options)) {
+ return true;
+ }
+
+ if (hasNakedLeftSide(argument)) {
+ let leftMost = argument;
+ let newLeftMost;
+ while ((newLeftMost = getLeftSide(leftMost))) {
+ leftMost = newLeftMost;
+
+ if (hasLeadingOwnLineComment(options.originalText, leftMost, options)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+function isStringPropSafeToCoerceToIdentifier(node, options) {
+ return (
+ isStringLiteral(node.key) &&
+ isIdentifierName(node.key.value) &&
+ options.parser !== "json" &&
+ !(options.parser === "typescript" && node.type === "ClassProperty")
+ );
+}
+
+function isJestEachTemplateLiteral(node, parentNode) {
+ /**
+ * describe.each`table`(name, fn)
+ * describe.only.each`table`(name, fn)
+ * describe.skip.each`table`(name, fn)
+ * test.each`table`(name, fn)
+ * test.only.each`table`(name, fn)
+ * test.skip.each`table`(name, fn)
+ *
+ * Ref: https://github.com/facebook/jest/pull/6102
+ */
+ const jestEachTriggerRegex = /^[xf]?(describe|it|test)$/;
+ return (
+ parentNode.type === "TaggedTemplateExpression" &&
+ parentNode.quasi === node &&
+ parentNode.tag.type === "MemberExpression" &&
+ parentNode.tag.property.type === "Identifier" &&
+ parentNode.tag.property.name === "each" &&
+ ((parentNode.tag.object.type === "Identifier" &&
+ jestEachTriggerRegex.test(parentNode.tag.object.name)) ||
+ (parentNode.tag.object.type === "MemberExpression" &&
+ parentNode.tag.object.property.type === "Identifier" &&
+ (parentNode.tag.object.property.name === "only" ||
+ parentNode.tag.object.property.name === "skip") &&
+ parentNode.tag.object.object.type === "Identifier" &&
+ jestEachTriggerRegex.test(parentNode.tag.object.object.name)))
+ );
+}
+
+function templateLiteralHasNewLines(template) {
+ return template.quasis.some(quasi => quasi.value.raw.includes("\n"));
+}
+
+function isTemplateOnItsOwnLine(n, text, options) {
+ return (
+ ((n.type === "TemplateLiteral" && templateLiteralHasNewLines(n)) ||
+ (n.type === "TaggedTemplateExpression" &&
+ templateLiteralHasNewLines(n.quasi))) &&
+ !hasNewline(text, options.locStart(n), { backwards: true })
+ );
+}
+
+function needsHardlineAfterDanglingComment(node) {
+ if (!node.comments) {
+ return false;
+ }
+ const lastDanglingComment = getLast(
+ node.comments.filter(comment => !comment.leading && !comment.trailing)
+ );
+ return (
+ lastDanglingComment && !handleComments.isBlockComment(lastDanglingComment)
+ );
+}
+
+// If we have nested conditional expressions, we want to print them in JSX mode
+// if there's at least one JSXElement somewhere in the tree.
+//
+// A conditional expression chain like this should be printed in normal mode,
+// because there aren't JSXElements anywhere in it:
+//
+// isA ? "A" : isB ? "B" : isC ? "C" : "Unknown";
+//
+// But a conditional expression chain like this should be printed in JSX mode,
+// because there is a JSXElement in the last ConditionalExpression:
+//
+// isA ? "A" : isB ? "B" : isC ? "C" : Unknown;
+//
+// This type of ConditionalExpression chain is structured like this in the AST:
+//
+// ConditionalExpression {
+// test: ...,
+// consequent: ...,
+// alternate: ConditionalExpression {
+// test: ...,
+// consequent: ...,
+// alternate: ConditionalExpression {
+// test: ...,
+// consequent: ...,
+// alternate: ...,
+// }
+// }
+// }
+//
+// We want to traverse over that shape and convert it into a flat structure so
+// that we can find if there's a JSXElement somewhere inside.
+function getConditionalChainContents(node) {
+ // Given this code:
+ //
+ // // Using a ConditionalExpression as the consequent is uncommon, but should
+ // // be handled.
+ // A ? B : C ? D : E ? F ? G : H : I
+ //
+ // which has this AST:
+ //
+ // ConditionalExpression {
+ // test: Identifier(A),
+ // consequent: Identifier(B),
+ // alternate: ConditionalExpression {
+ // test: Identifier(C),
+ // consequent: Identifier(D),
+ // alternate: ConditionalExpression {
+ // test: Identifier(E),
+ // consequent: ConditionalExpression {
+ // test: Identifier(F),
+ // consequent: Identifier(G),
+ // alternate: Identifier(H),
+ // },
+ // alternate: Identifier(I),
+ // }
+ // }
+ // }
+ //
+ // we should return this Array:
+ //
+ // [
+ // Identifier(A),
+ // Identifier(B),
+ // Identifier(C),
+ // Identifier(D),
+ // Identifier(E),
+ // Identifier(F),
+ // Identifier(G),
+ // Identifier(H),
+ // Identifier(I)
+ // ];
+ //
+ // This loses the information about whether each node was the test,
+ // consequent, or alternate, but we don't care about that here- we are only
+ // flattening this structure to find if there's any JSXElements inside.
+ const nonConditionalExpressions = [];
+
+ function recurse(node) {
+ if (node.type === "ConditionalExpression") {
+ recurse(node.test);
+ recurse(node.consequent);
+ recurse(node.alternate);
+ } else {
+ nonConditionalExpressions.push(node);
+ }
+ }
+ recurse(node);
+
+ return nonConditionalExpressions;
+}
+
+function conditionalExpressionChainContainsJSX(node) {
+ return Boolean(getConditionalChainContents(node).find(isJSXNode));
+}
+
+// Logic to check for args with multiple anonymous functions. For instance,
+// the following call should be split on multiple lines for readability:
+// source.pipe(map((x) => x + x), filter((x) => x % 2 === 0))
+function isFunctionCompositionArgs(args) {
+ if (args.length <= 1) {
+ return false;
+ }
+ let count = 0;
+ for (const arg of args) {
+ if (isFunctionOrArrowExpression(arg)) {
+ count += 1;
+ if (count > 1) {
+ return true;
+ }
+ } else if (isCallOrOptionalCallExpression(arg)) {
+ for (const childArg of arg.arguments) {
+ 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.
+function isLongCurriedCallExpression(path) {
+ const node = path.getValue();
+ const parent = path.getParentNode();
+ return (
+ isCallOrOptionalCallExpression(node) &&
+ isCallOrOptionalCallExpression(parent) &&
+ parent.callee === node &&
+ node.arguments.length > parent.arguments.length &&
+ parent.arguments.length > 0
+ );
+}
+
+function rawText(node) {
+ return node.extra ? node.extra.raw : node.raw;
+}
+
+function identity(x) {
+ return x;
+}
+
module.exports = {
- getLeftSide,
+ classChildNeedsASIProtection,
+ classPropMayCauseASIProblems,
+ conditionalExpressionChainContainsJSX,
+ getFlowVariance,
getLeftSidePathName,
- hasNakedLeftSide,
- hasNode,
+ getTypeScriptMappedTypeModifier,
+ hasDanglingComments,
+ hasFlowAnnotationComment,
hasFlowShorthandAnnotationComment,
- hasFlowAnnotationComment
+ hasLeadingComment,
+ hasLeadingOwnLineComment,
+ hasNakedLeftSide,
+ hasNewlineBetweenOrAfterDecorators,
+ hasNgSideEffect,
+ hasPrettierIgnore,
+ hasTrailingComment,
+ identity,
+ isBinaryish,
+ isCallOrOptionalCallExpression,
+ isEmptyJSXElement,
+ isFlowAnnotationComment,
+ isFunctionCompositionArgs,
+ isFunctionNotation,
+ isFunctionOrArrowExpression,
+ isGetterOrSetter,
+ isJestEachTemplateLiteral,
+ isJSXNode,
+ isJSXWhitespaceExpression,
+ isLastStatement,
+ isLiteral,
+ isLongCurriedCallExpression,
+ isMeaningfulJSXText,
+ isMemberExpressionChain,
+ isMemberish,
+ isNgForOf,
+ isNodeStartingWithDeclare,
+ isNumericLiteral,
+ isObjectType,
+ isObjectTypePropertyAFunction,
+ isSimpleFlowType,
+ isSimpleTemplateLiteral,
+ isStringLiteral,
+ isStringPropSafeToCoerceToIdentifier,
+ isTemplateOnItsOwnLine,
+ isTestCall,
+ isTheOnlyJSXElementInMarkdown,
+ isTypeAnnotationAFunction,
+ matchJsxWhitespaceRegex,
+ needsHardlineAfterDanglingComment,
+ rawText,
+ returnArgumentHasLeadingComment
};