prettier/src/language-js/printer-estree.js

5577 lines
155 KiB
JavaScript
Raw Normal View History

2017-01-10 20:18:22 +03:00
"use strict";
2017-01-28 18:50:22 +03:00
const assert = require("assert");
// TODO(azz): anything that imports from main shouldn't be in a `language-*` dir.
const comments = require("../main/comments");
const privateUtil = require("../common/util");
const sharedUtil = require("../common/util-shared");
const isIdentifierName = require("esutils").keyword.isIdentifierNameES6;
const embed = require("./embed");
const clean = require("./clean");
const insertPragma = require("./pragma").insertPragma;
const handleComments = require("./comments");
const pathNeedsParens = require("./needs-parens");
const doc = require("../doc");
const docBuilders = doc.builders;
const concat = docBuilders.concat;
const join = docBuilders.join;
const line = docBuilders.line;
const hardline = docBuilders.hardline;
const softline = docBuilders.softline;
const literalline = docBuilders.literalline;
const group = docBuilders.group;
const indent = docBuilders.indent;
const align = docBuilders.align;
const conditionalGroup = docBuilders.conditionalGroup;
const fill = docBuilders.fill;
const ifBreak = docBuilders.ifBreak;
const breakParent = docBuilders.breakParent;
const lineSuffixBoundary = docBuilders.lineSuffixBoundary;
Add `--range-start` and `--range-end` options to format only parts of the input (#1609) * Add `--range-start` and `--range-end` options to format only parts of the input These options default to `0` and `Infinity`, respectively, so that the entire input is formatted by default. However, if either option is specified such that a node lies completely outside the resulting range, the node will be treated as if it has a `// prettier-ignore` comment. Related to https://github.com/prettier/prettier/pull/1577#issuecomment-300551179 Related to https://github.com/prettier/prettier/issues/1324 Related to https://github.com/prettier/prettier/issues/593 * printer: Extract hasPrettierIgnoreComment() helper * Move isOutsideRange() to util * Don't throw errors about comments outside range "not printing" * Remove unnecessary check from isOutsideRange() * Make --range-end exclusive This lets it use the conventional way of specifying ranges in strings. Note that if the rangeEnd in the tests is changed to 158, it will fail, but it wouldn't have failed before this change. * Change range formatting approach NOTE: This doesn't pass its test yet. Note that since we're reading the indentation from the first line, it is expected not to change. However, a semicolon is added, and the lines outside the range are not changed. The new approach is roughly: * Require that the range exactly covers an integer number of lines of the input * Detect the indentation of the line the range starts on * Format the range's substring using `printAstToDoc` * Add enough `indent`s to the doc to restore the detected indentation * Format the doc to a string with `printDocToString` * Prepend/append the original input before/after the range See https://github.com/prettier/prettier/pull/1609#issuecomment-301582273 --- Given `tests/range/range.js`, run the following: prettier tests/range/range.js --range-start 165 --range-end 246 See the range's text with: dd if=tests/range/range.js ibs=1 skip=165 count=81 2>/dev/null * Don't use default function parameters Node v4 doesn't support them. See http://node.green/#ES2015-syntax-default-function-parameters * Hackily fix indentation of range formatting See https://github.com/prettier/prettier/pull/1609#issuecomment-301625368 Also update the snapshot to reflect that the indentation actually should decrease by one space, since there were 13 spaces in the input and we round down after dividing by tabWidth. * Revert "printer: Extract hasPrettierIgnoreComment() helper" See https://github.com/prettier/prettier/pull/1609#discussion_r116804853 This reverts commit 62bf068ca98f69d4a7fd0ae188b3554d409eee8d. * Test automatically using the beginning of the rangeStart line and same for the end See https://github.com/prettier/prettier/pull/1609#issuecomment-301862076 * Fix automatically using the beginning of the rangeStart line and same for the end See https://github.com/prettier/prettier/pull/1609#issuecomment-301862076 * Propagate breaks after adding an indentation-triggering hardline See https://github.com/prettier/prettier/pull/1609/files/c1a61ebde8be73414c0a54bde3f323ac24295715#r116805581 * Extract getAlignmentSize(), use instead of countIndents() See https://github.com/prettier/prettier/pull/1609/files/c1a61ebde8be73414c0a54bde3f323ac24295715#r116804694 * Extract addAlignmentToDoc(), use instead of addIndentsToDoc() See https://github.com/prettier/prettier/pull/1609/files/c1a61ebde8be73414c0a54bde3f323ac24295715#r116804694 * Document that --range-start and --range-end include the entire line * Fix rangeStart calculation Before, it was incorrectly resulting in 1 when the originally provided value was 0 * Extract formatRange() helper function * Move getAlignmentSize() from printer to util This addresses https://github.com/prettier/prettier/pull/1609#discussion_r117636241 * Move addAlignmentToDoc() from printer to doc-builders This addresses https://github.com/prettier/prettier/pull/1609#discussion_r117636251
2017-05-21 20:14:13 +03:00
const addAlignmentToDoc = docBuilders.addAlignmentToDoc;
const dedent = docBuilders.dedent;
const docUtils = doc.utils;
const willBreak = docUtils.willBreak;
const isLineNext = docUtils.isLineNext;
const isEmpty = docUtils.isEmpty;
function shouldPrintComma(options, level) {
level = level || "es5";
2017-02-23 20:57:51 +03:00
switch (options.trailingComma) {
case "all":
2017-02-23 20:57:51 +03:00
if (level === "all") {
return true;
}
// fallthrough
case "es5":
2017-02-23 20:57:51 +03:00
if (level === "es5") {
return true;
}
// fallthrough
case "none":
default:
return false;
}
}
function genericPrint(path, options, printPath, args) {
const node = path.getValue();
let needsParens = false;
const linesWithoutParens = printPathNoParens(path, options, printPath, args);
if (!node || isEmpty(linesWithoutParens)) {
return linesWithoutParens;
}
const decorators = [];
if (
node.decorators &&
2017-02-16 06:56:11 +03:00
node.decorators.length > 0 &&
// If the parent node is an export declaration, it will be
// responsible for printing node.decorators.
!privateUtil.getParentExportDeclaration(path)
) {
let separator = hardline;
path.each(decoratorPath => {
let prefix = "@";
let decorator = decoratorPath.getValue();
if (decorator.expression) {
decorator = decorator.expression;
prefix = "";
}
if (
node.decorators.length === 1 &&
node.type !== "ClassDeclaration" &&
node.type !== "MethodDefinition" &&
node.type !== "ClassMethod" &&
(decorator.type === "Identifier" ||
decorator.type === "MemberExpression" ||
(decorator.type === "CallExpression" &&
(decorator.arguments.length === 0 ||
(decorator.arguments.length === 1 &&
(isStringLiteral(decorator.arguments[0]) ||
decorator.arguments[0].type === "Identifier" ||
decorator.arguments[0].type === "MemberExpression")))))
) {
separator = line;
}
decorators.push(prefix, printPath(decoratorPath), separator);
}, "decorators");
2017-01-09 20:09:04 +03:00
} else if (
privateUtil.isExportDeclaration(node) &&
2017-02-16 06:56:11 +03:00
node.declaration &&
node.declaration.decorators
2017-01-09 20:09:04 +03:00
) {
// Export declarations are responsible for printing any decorators
// that logically apply to node.declaration.
path.each(
decoratorPath => {
const decorator = decoratorPath.getValue();
const prefix = decorator.type === "Decorator" ? "" : "@";
decorators.push(prefix, printPath(decoratorPath), hardline);
2017-01-09 20:09:04 +03:00
},
"declaration",
"decorators"
);
} else {
// Nodes with decorators can't have parentheses, so we can avoid
// computing pathNeedsParens() except in this case.
needsParens = pathNeedsParens(path, options);
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
const parts = [];
if (needsParens) {
parts.unshift("(");
}
2016-12-31 07:10:22 +03:00
parts.push(linesWithoutParens);
2016-12-31 07:10:22 +03:00
if (needsParens) {
parts.push(")");
}
2016-12-31 07:10:22 +03:00
if (decorators.length > 0) {
return group(concat(decorators.concat(parts)));
}
return concat(parts);
}
function hasPrettierIgnore(path) {
return privateUtil.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
// and TSConditionalType
function formatTernaryOperator(path, options, print, operatorOptions) {
const n = path.getValue();
const parts = [];
const operatorOpts = Object.assign(
{
beforeParts: () => [""],
afterParts: () => [""],
shouldCheckJsx: true,
operatorName: "ConditionalExpression",
consequentNode: "consequent",
alternateNode: "alternate",
2018-04-05 03:50:15 +03:00
testNode: "test",
breakNested: true
},
operatorOptions || {}
);
// We print a ConditionalExpression in either "JSX mode" or "normal mode".
// See tests/jsx/conditional-expression.js for more info.
let jsxMode = false;
const parent = path.getParentNode();
let forceNoIndent = parent.type === operatorOpts.operatorName;
// Find the outermost non-ConditionalExpression parent, and the outermost
// ConditionalExpression parent. We'll use these to determine if we should
// print in JSX mode.
let currentParent;
let previousParent;
let i = 0;
do {
previousParent = currentParent || n;
currentParent = path.getParentNode(i);
i++;
} while (currentParent && currentParent.type === operatorOpts.operatorName);
const firstNonConditionalParent = currentParent || parent;
const lastConditionalParent = previousParent;
if (
(operatorOpts.shouldCheckJsx && isJSXNode(n[operatorOpts.testNode])) ||
isJSXNode(n[operatorOpts.consequentNode]) ||
isJSXNode(n[operatorOpts.alternateNode]) ||
conditionalExpressionChainContainsJSX(lastConditionalParent)
) {
jsxMode = true;
forceNoIndent = true;
// Even though they don't need parens, we wrap (almost) everything in
// parens when using ?: within JSX, because the parens are analogous to
// curly braces in an if statement.
const wrap = doc =>
concat([
ifBreak("(", ""),
indent(concat([softline, doc])),
softline,
ifBreak(")", "")
]);
// The only things we don't wrap are:
// * Nested conditional expressions in alternates
// * null
const isNull = node =>
node.type === "NullLiteral" ||
(node.type === "Literal" && node.value === null);
parts.push(
" ? ",
isNull(n[operatorOpts.consequentNode])
? path.call(print, operatorOpts.consequentNode)
: wrap(path.call(print, operatorOpts.consequentNode)),
" : ",
n[operatorOpts.alternateNode].type === operatorOpts.operatorName ||
isNull(n[operatorOpts.alternateNode])
? path.call(print, operatorOpts.alternateNode)
: wrap(path.call(print, operatorOpts.alternateNode))
);
} else {
// normal mode
const part = concat([
line,
"? ",
n[operatorOpts.consequentNode].type === operatorOpts.operatorName
? ifBreak("", "(")
: "",
align(2, path.call(print, operatorOpts.consequentNode)),
n[operatorOpts.consequentNode].type === operatorOpts.operatorName
? ifBreak("", ")")
: "",
line,
": ",
align(2, path.call(print, operatorOpts.alternateNode))
]);
parts.push(
parent.type === operatorOpts.operatorName
? options.useTabs
? dedent(indent(part))
: align(Math.max(0, options.tabWidth - 2), part)
: part
);
}
2018-04-05 03:50:15 +03:00
// We want a whole chain of ConditionalExpressions to all
// break if any of them break. That means we should only group around the
// outer-most ConditionalExpression.
const maybeGroup = doc =>
2018-04-05 03:50:15 +03:00
operatorOpts.breakNested
2018-04-12 07:39:13 +03:00
? parent === firstNonConditionalParent
? group(doc)
: doc
: group(doc); // Always group in normal mode.
// Break the closing paren to keep the chain right after it:
// (a
// ? b
// : c
// ).call()
const breakClosingParen =
!jsxMode && parent.type === "MemberExpression" && !parent.computed;
return maybeGroup(
concat(
[].concat(
operatorOpts.beforeParts(),
forceNoIndent ? concat(parts) : indent(concat(parts)),
operatorOpts.afterParts(breakClosingParen)
)
)
);
}
2018-03-16 09:08:25 +03:00
function getTypeScriptMappedTypeModifier(tokenNode, keyword) {
if (tokenNode.type === "TSPlusToken") {
return "+" + keyword;
} else if (tokenNode.type === "TSMinusToken") {
return "-" + keyword;
}
2018-03-16 09:47:51 +03:00
return keyword;
2018-03-16 09:08:25 +03:00
}
function printPathNoParens(path, options, print, args) {
const n = path.getValue();
const semi = options.semi ? ";" : "";
2016-12-31 22:38:58 +03:00
if (!n) {
return "";
}
2016-12-31 07:10:22 +03:00
if (typeof n === "string") {
return n;
}
2016-12-31 07:10:22 +03:00
let parts = [];
switch (n.type) {
2017-01-13 23:03:53 +03:00
case "File":
return path.call(print, "program");
case "Program":
// Babel 6
if (n.directives) {
path.each(childPath => {
parts.push(print(childPath), semi, hardline);
if (
sharedUtil.isNextLineEmpty(
options.originalText,
childPath.getValue(),
options
)
) {
parts.push(hardline);
}
}, "directives");
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(
path.call(bodyPath => {
return printStatementSequence(bodyPath, options, print);
}, "body")
2017-01-13 23:03:53 +03:00
);
2016-12-31 07:10:22 +03:00
parts.push(
comments.printDanglingComments(path, options, /* sameIndent */ true)
);
// Only force a trailing newline if there were any contents.
if (n.body.length || n.comments) {
parts.push(hardline);
}
2017-01-11 17:39:32 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
// Babel extension.
case "EmptyStatement":
return "";
2017-01-13 23:03:53 +03:00
case "ExpressionStatement":
Preserve code unit sequence of directive literals (#1571) * Print directive literals verbatim This addresses https://github.com/prettier/prettier/issues/1555, but doesn't seem to pass the AST_COMPARE=1 tests: AST_COMPARE=1 npm test -- tests/quotes -t strings However, running `prettier --debug-check` on the relevant file *does* work: prettier tests/quotes/strings.js --debug-check * Change directive literal quotes if it doesn't contain quotes This addresses https://github.com/prettier/prettier/pull/1560#discussion_r115396257 From https://github.com/prettier/prettier/issues/1555#issue-227206837: > It's okay to change the type of quotation marks used, but only if doing so does not require changing any characters within the directive. * Don't change directive literal quotes if it contains a backslash This passes the `--debug-check` tests again: prettier tests/quotes/strings.js --debug-check * Try to add regression test for escaped directive literals This seems not to work, despite the following command having the correct output: echo "'\''" | prettier You can use the following to get an idea of how flow/typescript parse this: node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'flow'}), null, 2)" node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'typescript'}), null, 2)" * WIP Disable Flow/Typescript for ./tests/directives We don't yet handle escaped directives for them, but Babylon works. (similar to https://github.com/prettier/prettier/pull/602/commits/90bf93713c78a6a6b3f55e52d7be172ece9b56df#diff-0de18284f37da79ab8af4e4690919abaR1) * Revert "WIP Disable Flow/Typescript for ./tests/directives" This reverts commit 2aba6231271f6985a395c31e3df9323e8f3da115. * Prevent test strings from being parsed as directives See https://github.com/prettier/prettier/pull/1560#issue-227225960 * Add more escaped directive tests * Infer DirectiveLiterals from Flow parser * Don't test TypeScript on directives See https://github.com/prettier/prettier/pull/1560#issuecomment-300296221 * fixup! Infer DirectiveLiterals from Flow parser * Don't fake objects that look like a DirectiveLiteral Instead, add a flag to nodeStr() that deals with the Flow node accordingly. See https://github.com/prettier/prettier/pull/1560#discussion_r115605758 * Print preferred quotes around escaped DirectiveLiteral when it doesn't contain quotes See https://github.com/prettier/prettier/pull/1560#discussion_r115606122 * Simplify `canChangeDirectiveQuotes` logic * Add directive test with unnecessarily escaped non-quote character * Fix boolean logic error I thought that this would result in the following if-block executing, as needed to pass the test case in the previous commit. However, it appears that it's not actually needed to pass the test case, since `makeString` doesn't unescape unnecessarily escaped non-quote characters. Nevertheless, I think we should leave that if-block (`if (canChangeDirectiveQuotes)`) there, in case `makeString` is updated. See https://github.com/prettier/prettier/pull/1571#discussion_r115658398 * Make isFlowDirectiveLiteral a separate argument to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115810988 * Simplify isFlowDirectiveLiteral logic by passing n.expression to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115811216
2017-05-10 22:15:27 +03:00
// Detect Flow-parsed directives
if (n.directive) {
return concat([nodeStr(n.expression, options, true), semi]);
Preserve code unit sequence of directive literals (#1571) * Print directive literals verbatim This addresses https://github.com/prettier/prettier/issues/1555, but doesn't seem to pass the AST_COMPARE=1 tests: AST_COMPARE=1 npm test -- tests/quotes -t strings However, running `prettier --debug-check` on the relevant file *does* work: prettier tests/quotes/strings.js --debug-check * Change directive literal quotes if it doesn't contain quotes This addresses https://github.com/prettier/prettier/pull/1560#discussion_r115396257 From https://github.com/prettier/prettier/issues/1555#issue-227206837: > It's okay to change the type of quotation marks used, but only if doing so does not require changing any characters within the directive. * Don't change directive literal quotes if it contains a backslash This passes the `--debug-check` tests again: prettier tests/quotes/strings.js --debug-check * Try to add regression test for escaped directive literals This seems not to work, despite the following command having the correct output: echo "'\''" | prettier You can use the following to get an idea of how flow/typescript parse this: node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'flow'}), null, 2)" node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'typescript'}), null, 2)" * WIP Disable Flow/Typescript for ./tests/directives We don't yet handle escaped directives for them, but Babylon works. (similar to https://github.com/prettier/prettier/pull/602/commits/90bf93713c78a6a6b3f55e52d7be172ece9b56df#diff-0de18284f37da79ab8af4e4690919abaR1) * Revert "WIP Disable Flow/Typescript for ./tests/directives" This reverts commit 2aba6231271f6985a395c31e3df9323e8f3da115. * Prevent test strings from being parsed as directives See https://github.com/prettier/prettier/pull/1560#issue-227225960 * Add more escaped directive tests * Infer DirectiveLiterals from Flow parser * Don't test TypeScript on directives See https://github.com/prettier/prettier/pull/1560#issuecomment-300296221 * fixup! Infer DirectiveLiterals from Flow parser * Don't fake objects that look like a DirectiveLiteral Instead, add a flag to nodeStr() that deals with the Flow node accordingly. See https://github.com/prettier/prettier/pull/1560#discussion_r115605758 * Print preferred quotes around escaped DirectiveLiteral when it doesn't contain quotes See https://github.com/prettier/prettier/pull/1560#discussion_r115606122 * Simplify `canChangeDirectiveQuotes` logic * Add directive test with unnecessarily escaped non-quote character * Fix boolean logic error I thought that this would result in the following if-block executing, as needed to pass the test case in the previous commit. However, it appears that it's not actually needed to pass the test case, since `makeString` doesn't unescape unnecessarily escaped non-quote characters. Nevertheless, I think we should leave that if-block (`if (canChangeDirectiveQuotes)`) there, in case `makeString` is updated. See https://github.com/prettier/prettier/pull/1571#discussion_r115658398 * Make isFlowDirectiveLiteral a separate argument to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115810988 * Simplify isFlowDirectiveLiteral logic by passing n.expression to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115811216
2017-05-10 22:15:27 +03:00
}
// Do not append semicolon after the only JSX element in a program
return concat([
path.call(print, "expression"),
isTheOnlyJSXElementInMarkdown(options, path) ? "" : semi
]); // Babel extension.
2017-01-28 18:50:22 +03:00
case "ParenthesizedExpression":
return concat(["(", path.call(print, "expression"), ")"]);
2017-01-13 23:03:53 +03:00
case "AssignmentExpression":
return printAssignment(
n.left,
path.call(print, "left"),
concat([" ", n.operator]),
n.right,
path.call(print, "right"),
options
2017-01-13 23:03:53 +03:00
);
case "BinaryExpression":
case "LogicalExpression": {
const parent = path.getParentNode();
const parentParent = path.getParentNode(1);
const isInsideParenthesis =
n !== parent.body &&
(parent.type === "IfStatement" ||
parent.type === "WhileStatement" ||
parent.type === "DoWhileStatement");
const parts = printBinaryishExpressions(
path,
print,
options,
/* isNested */ false,
isInsideParenthesis
);
// if (
// this.hasPlugin("dynamicImports") && this.lookahead().type === tt.parenLeft
// ) {
//
// looks super weird, we want to break the children if the parent breaks
//
// if (
// this.hasPlugin("dynamicImports") &&
// this.lookahead().type === tt.parenLeft
// ) {
if (isInsideParenthesis) {
return concat(parts);
}
// Break between the parens in unaries or in a member expression, i.e.
//
// (
// a &&
// b &&
// c
// ).call()
if (
parent.type === "UnaryExpression" ||
(parent.type === "MemberExpression" && !parent.computed)
) {
return group(
concat([indent(concat([softline, concat(parts)])), softline])
);
}
2017-09-08 06:25:54 +03:00
// Avoid indenting sub-expressions in some cases where the first sub-expression is already
2017-10-18 15:57:26 +03:00
// indented accordingly. We should indent sub-expressions where the first case isn't indented.
const shouldNotIndent =
parent.type === "ReturnStatement" ||
(parent.type === "JSXExpressionContainer" &&
parentParent.type === "JSXAttribute") ||
(n === parent.body && parent.type === "ArrowFunctionExpression") ||
(n !== parent.body && parent.type === "ForStatement") ||
(parent.type === "ConditionalExpression" &&
parentParent.type !== "ReturnStatement");
2017-10-18 15:57:26 +03:00
const shouldIndentIfInlining =
2017-09-08 06:25:54 +03:00
parent.type === "AssignmentExpression" ||
parent.type === "VariableDeclarator" ||
parent.type === "ClassProperty" ||
parent.type === "TSAbstractClassProperty" ||
parent.type === "ClassPrivateProperty" ||
2017-09-08 06:25:54 +03:00
parent.type === "ObjectProperty" ||
parent.type === "Property";
const samePrecedenceSubExpression =
isBinaryish(n.left) &&
privateUtil.shouldFlatten(n.operator, n.left.operator);
2017-09-08 06:25:54 +03:00
if (
2017-10-18 15:57:26 +03:00
shouldNotIndent ||
(shouldInlineLogicalExpression(n) && !samePrecedenceSubExpression) ||
2017-10-18 15:57:26 +03:00
(!shouldInlineLogicalExpression(n) && shouldIndentIfInlining)
) {
return group(concat(parts));
}
const rest = concat(parts.slice(1));
2017-02-03 19:50:51 +03:00
return group(
concat([
// Don't include the initial expression in the indentation
// level. The first item is guaranteed to be the first
// left-most expression.
parts.length > 0 ? parts[0] : "",
indent(rest)
2017-02-03 19:50:51 +03:00
])
);
}
2017-01-13 23:03:53 +03:00
case "AssignmentPattern":
return concat([
2017-01-09 20:09:04 +03:00
path.call(print, "left"),
2017-01-13 23:03:53 +03:00
" = ",
2017-01-09 20:09:04 +03:00
path.call(print, "right")
2017-01-13 23:03:53 +03:00
]);
case "TSTypeAssertionExpression":
return concat([
"<",
path.call(print, "typeAnnotation"),
">",
path.call(print, "expression")
]);
2017-01-13 23:03:53 +03:00
case "MemberExpression": {
const parent = path.getParentNode();
let firstNonMemberParent;
let i = 0;
do {
firstNonMemberParent = path.getParentNode(i);
i++;
} while (
firstNonMemberParent &&
(firstNonMemberParent.type === "MemberExpression" ||
firstNonMemberParent.type === "TSNonNullExpression")
);
const shouldInline =
(firstNonMemberParent &&
(firstNonMemberParent.type === "NewExpression" ||
(firstNonMemberParent.type === "VariableDeclarator" &&
firstNonMemberParent.id.type !== "Identifier") ||
(firstNonMemberParent.type === "AssignmentExpression" &&
firstNonMemberParent.left.type !== "Identifier"))) ||
n.computed ||
(n.object.type === "Identifier" &&
n.property.type === "Identifier" &&
parent.type !== "MemberExpression");
2017-01-09 20:09:04 +03:00
return concat([
path.call(print, "object"),
shouldInline
? printMemberLookup(path, options, print)
: group(
indent(
concat([softline, printMemberLookup(path, options, print)])
)
)
2017-01-09 20:09:04 +03:00
]);
}
2017-01-13 23:03:53 +03:00
case "MetaProperty":
return concat([
path.call(print, "meta"),
".",
path.call(print, "property")
]);
case "BindExpression":
if (n.object) {
parts.push(path.call(print, "object"));
}
2016-12-31 07:10:22 +03:00
parts.push(printBindExpressionCallee(path, options, print));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "Identifier": {
2017-01-13 23:03:53 +03:00
return concat([
n.name,
printOptionalToken(path),
printTypeAnnotation(path, options, print)
2017-01-13 23:03:53 +03:00
]);
}
2017-01-13 23:03:53 +03:00
case "SpreadElement":
case "SpreadElementPattern":
case "RestProperty":
case "ExperimentalRestProperty":
case "ExperimentalSpreadProperty":
2017-01-13 23:03:53 +03:00
case "SpreadProperty":
case "SpreadPropertyPattern":
case "RestElement":
case "ObjectTypeSpreadProperty":
2017-01-13 23:03:53 +03:00
return concat([
"...",
path.call(print, "argument"),
printTypeAnnotation(path, options, print)
2017-01-13 23:03:53 +03:00
]);
case "FunctionDeclaration":
case "FunctionExpression":
if (isNodeStartingWithDeclare(n, options)) {
parts.push("declare ");
}
parts.push(printFunctionDeclaration(path, print, options));
if (!n.body) {
parts.push(semi);
}
return concat(parts);
case "ArrowFunctionExpression": {
if (n.async) {
parts.push("async ");
}
2016-12-31 07:10:22 +03:00
if (shouldPrintParamsWithoutParens(path, options)) {
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "params", 0));
} else {
parts.push(
group(
2017-01-13 23:03:53 +03:00
concat([
printFunctionParams(
path,
print,
options,
/* expandLast */ args &&
(args.expandLastArg || args.expandFirstArg),
/* printTypeParams */ true
),
printReturnType(path, print, options)
2017-01-13 23:03:53 +03:00
])
)
);
}
2016-12-31 07:10:22 +03:00
const dangling = comments.printDanglingComments(
path,
options,
/* sameIndent */ true,
comment => {
const nextCharacter = sharedUtil.getNextNonSpaceNonCommentCharacterIndex(
options.originalText,
comment,
options
);
return options.originalText.substr(nextCharacter, 2) === "=>";
}
);
if (dangling) {
parts.push(" ", dangling);
}
2017-01-13 23:03:53 +03:00
parts.push(" =>");
const body = path.call(bodyPath => print(bodyPath, args), "body");
2017-01-24 21:54:01 +03:00
// We want to always keep these types of nodes on the same line
// as the arrow.
if (
!hasLeadingOwnLineComment(options.originalText, n.body, options) &&
(n.body.type === "ArrayExpression" ||
n.body.type === "ObjectExpression" ||
n.body.type === "BlockStatement" ||
2017-11-30 06:09:45 +03:00
isJSXNode(n.body) ||
isTemplateOnItsOwnLine(n.body, options.originalText, options) ||
n.body.type === "ArrowFunctionExpression")
) {
return group(concat([concat(parts), " ", body]));
}
// We handle sequence expressions as the body of arrows specially,
// so that the required parentheses end up on their own lines.
if (n.body.type === "SequenceExpression") {
return group(
concat([
concat(parts),
group(
concat([" (", indent(concat([softline, body])), softline, ")"])
)
])
);
2017-01-13 23:03:53 +03:00
}
// if the arrow function is expanded as last argument, we are adding a
// level of indentation and need to add a softline to align the closing )
2017-11-04 06:36:44 +03:00
// with the opening (, or if it's inside a JSXExpression (e.g. an attribute)
// we should align the expression's closing } with the line with the opening {.
const shouldAddSoftLine =
((args && args.expandLastArg) ||
path.getParentNode().type === "JSXExpressionContainer") &&
!(n.comments && n.comments.length);
const printTrailingComma =
args && args.expandLastArg && shouldPrintComma(options, "all");
// In order to avoid confusion between
// a => a ? a : a
// a <= a ? a : a
const shouldAddParens =
n.body.type === "ConditionalExpression" &&
!privateUtil.startsWithNoLookaheadToken(
n.body,
/* forbidFunctionAndClass */ false
);
2017-01-24 21:54:01 +03:00
return group(
concat([
concat(parts),
group(
concat([
indent(
concat([
line,
shouldAddParens ? ifBreak("", "(") : "",
body,
shouldAddParens ? ifBreak("", ")") : ""
])
),
shouldAddSoftLine
? concat([ifBreak(printTrailingComma ? "," : ""), softline])
: ""
])
)
])
2017-01-24 21:54:01 +03:00
);
}
2017-01-13 23:03:53 +03:00
case "MethodDefinition":
case "TSAbstractMethodDefinition":
if (n.accessibility) {
parts.push(n.accessibility + " ");
}
if (n.static) {
parts.push("static ");
}
if (n.type === "TSAbstractMethodDefinition") {
parts.push("abstract ");
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(printMethod(path, options, print));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "YieldExpression":
parts.push("yield");
2016-12-31 07:10:22 +03:00
if (n.delegate) {
parts.push("*");
}
if (n.argument) {
parts.push(" ", path.call(print, "argument"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "AwaitExpression":
return concat(["await ", path.call(print, "argument")]);
2017-01-13 23:03:53 +03:00
case "ImportSpecifier":
if (n.importKind) {
parts.push(path.call(print, "importKind"), " ");
}
2017-02-15 23:56:34 +03:00
parts.push(path.call(print, "imported"));
2016-12-31 07:10:22 +03:00
if (n.local && n.local.name !== n.imported.name) {
parts.push(" as ", path.call(print, "local"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ExportSpecifier":
parts.push(path.call(print, "local"));
2016-12-31 07:10:22 +03:00
if (n.exported && n.exported.name !== n.local.name) {
parts.push(" as ", path.call(print, "exported"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ImportNamespaceSpecifier":
parts.push("* as ");
if (n.local) {
parts.push(path.call(print, "local"));
} else if (n.id) {
parts.push(path.call(print, "id"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ImportDefaultSpecifier":
if (n.local) {
return path.call(print, "local");
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return path.call(print, "id");
case "TSExportAssignment":
return concat(["export = ", path.call(print, "expression"), semi]);
2017-01-13 23:03:53 +03:00
case "ExportDefaultDeclaration":
case "ExportNamedDeclaration":
return printExportDeclaration(path, options, print);
case "ExportAllDeclaration":
parts.push("export ");
if (n.exportKind === "type") {
parts.push("type ");
}
parts.push("* from ", path.call(print, "source"), semi);
return concat(parts);
2017-01-13 23:03:53 +03:00
case "ExportNamespaceSpecifier":
case "ExportDefaultSpecifier":
return path.call(print, "exported");
case "ImportDeclaration": {
2017-01-13 23:03:53 +03:00
parts.push("import ");
if (n.importKind && n.importKind !== "value") {
parts.push(n.importKind + " ");
}
2016-12-31 07:10:22 +03:00
const standalones = [];
const grouped = [];
2017-01-13 23:03:53 +03:00
if (n.specifiers && n.specifiers.length > 0) {
path.each(specifierPath => {
const value = specifierPath.getValue();
if (
value.type === "ImportDefaultSpecifier" ||
value.type === "ImportNamespaceSpecifier"
) {
standalones.push(print(specifierPath));
} else {
grouped.push(print(specifierPath));
}
}, "specifiers");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (standalones.length > 0) {
parts.push(join(", ", standalones));
}
2017-01-13 23:03:53 +03:00
if (standalones.length > 0 && grouped.length > 0) {
parts.push(", ");
}
2016-12-31 07:10:22 +03:00
if (
grouped.length === 1 &&
standalones.length === 0 &&
n.specifiers &&
!n.specifiers.some(node => node.comments)
) {
parts.push(
concat([
"{",
options.bracketSpacing ? " " : "",
concat(grouped),
options.bracketSpacing ? " " : "",
"}"
])
);
} else if (grouped.length >= 1) {
2017-01-13 23:03:53 +03:00
parts.push(
2017-01-24 21:54:01 +03:00
group(
concat([
2017-01-13 23:03:53 +03:00
"{",
indent(
concat([
options.bracketSpacing ? line : softline,
2017-01-28 18:50:22 +03:00
join(concat([",", line]), grouped)
2017-01-13 23:03:53 +03:00
])
),
ifBreak(shouldPrintComma(options) ? "," : ""),
options.bracketSpacing ? line : softline,
2017-01-13 23:03:53 +03:00
"}"
])
2017-01-13 23:03:53 +03:00
)
);
}
2016-12-31 22:38:58 +03:00
parts.push(" from ");
} else if (
(n.importKind && n.importKind === "type") ||
// import {} from 'x'
/{\s*}/.test(
options.originalText.slice(
options.locStart(n),
options.locStart(n.source)
)
)
) {
parts.push("{} from ");
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
parts.push(path.call(print, "source"), semi);
2017-02-04 00:19:14 +03:00
return concat(parts);
}
2017-01-25 18:33:48 +03:00
case "Import":
2017-01-25 18:33:48 +03:00
return "import";
case "BlockStatement": {
const naked = path.call(bodyPath => {
return printStatementSequence(bodyPath, options, print);
}, "body");
2016-12-31 07:10:22 +03:00
const hasContent = n.body.find(node => node.type !== "EmptyStatement");
const hasDirectives = n.directives && n.directives.length > 0;
const parent = path.getParentNode();
const parentParent = path.getParentNode(1);
2017-01-28 18:50:22 +03:00
if (
!hasContent &&
2017-02-16 06:56:11 +03:00
!hasDirectives &&
!hasDanglingComments(n) &&
2017-02-16 06:56:11 +03:00
(parent.type === "ArrowFunctionExpression" ||
parent.type === "FunctionExpression" ||
parent.type === "FunctionDeclaration" ||
parent.type === "ObjectMethod" ||
parent.type === "ClassMethod" ||
parent.type === "ForStatement" ||
parent.type === "WhileStatement" ||
parent.type === "DoWhileStatement" ||
(parent.type === "CatchClause" && !parentParent.finalizer))
2017-01-28 18:50:22 +03:00
) {
return "{}";
}
2017-01-13 23:03:53 +03:00
parts.push("{");
// Babel 6
if (hasDirectives) {
path.each(childPath => {
parts.push(indent(concat([hardline, print(childPath), semi])));
if (
sharedUtil.isNextLineEmpty(
options.originalText,
childPath.getValue(),
options
)
) {
parts.push(hardline);
}
}, "directives");
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
if (hasContent) {
parts.push(indent(concat([hardline, naked])));
}
2016-12-31 07:10:22 +03:00
parts.push(comments.printDanglingComments(path, options));
2017-01-13 23:03:53 +03:00
parts.push(hardline, "}");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "ReturnStatement":
parts.push("return");
if (n.argument) {
if (returnArgumentHasLeadingComment(options, n.argument)) {
parts.push(
concat([
" (",
indent(concat([hardline, path.call(print, "argument")])),
hardline,
")"
])
);
} else if (
n.argument.type === "LogicalExpression" ||
n.argument.type === "BinaryExpression" ||
n.argument.type === "SequenceExpression"
) {
parts.push(
group(
concat([
ifBreak(" (", " "),
indent(concat([softline, path.call(print, "argument")])),
softline,
ifBreak(")")
])
)
);
} else {
parts.push(" ", path.call(print, "argument"));
}
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
if (hasDanglingComments(n)) {
parts.push(
" ",
comments.printDanglingComments(path, options, /* sameIndent */ true)
);
}
parts.push(semi);
2017-01-13 23:03:53 +03:00
return concat(parts);
case "NewExpression":
2017-01-13 23:03:53 +03:00
case "CallExpression": {
const isNew = n.type === "NewExpression";
const optional = printOptionalToken(path);
if (
// We want to keep CommonJS- and AMD-style require calls, and AMD-style
// define calls, as a unit.
// e.g. `define(["some/lib", (lib) => {`
(!isNew &&
n.callee.type === "Identifier" &&
(n.callee.name === "require" || n.callee.name === "define")) ||
n.callee.type === "Import" ||
// Template literals as single arguments
(n.arguments.length === 1 &&
isTemplateOnItsOwnLine(
n.arguments[0],
options.originalText,
options
)) ||
// Keep test declarations on a single line
// e.g. `it('long name', () => {`
(!isNew && isTestCall(n, path.getParentNode()))
) {
return concat([
isNew ? "new " : "",
path.call(print, "callee"),
optional,
path.call(print, "typeParameters"),
concat(["(", join(", ", path.map(print, "arguments")), ")"])
]);
}
// We detect calls on member lookups and possibly print them in a
// special chain format. See `printMemberChain` for more info.
if (!isNew && isMemberish(n.callee)) {
return printMemberChain(path, options, print);
}
2017-01-09 20:09:04 +03:00
return concat([
isNew ? "new " : "",
2017-01-09 20:09:04 +03:00
path.call(print, "callee"),
optional,
printFunctionTypeParameters(path, options, print),
2017-01-09 20:09:04 +03:00
printArgumentsList(path, options, print)
]);
}
case "TSInterfaceDeclaration":
if (isNodeStartingWithDeclare(n, options)) {
parts.push("declare ");
}
parts.push(
n.abstract ? "abstract " : "",
printTypeScriptModifiers(path, options, print),
"interface ",
path.call(print, "id"),
n.typeParameters ? path.call(print, "typeParameters") : "",
" "
);
if (n.heritage.length) {
parts.push(
group(
indent(
concat([
softline,
"extends ",
indent(join(concat([",", line]), path.map(print, "heritage"))),
" "
])
)
)
);
}
parts.push(path.call(print, "body"));
return concat(parts);
2017-01-13 23:03:53 +03:00
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation":
case "TSInterfaceBody":
case "TSTypeLiteral": {
const isTypeAnnotation = n.type === "ObjectTypeAnnotation";
const parent = path.getParentNode(0);
const shouldBreak =
n.type === "TSInterfaceBody" ||
(n.type === "ObjectPattern" &&
parent.type !== "FunctionDeclaration" &&
parent.type !== "FunctionExpression" &&
parent.type !== "ArrowFunctionExpression" &&
parent.type !== "AssignmentPattern" &&
n.properties.some(
property =>
property.value &&
(property.value.type === "ObjectPattern" ||
property.value.type === "ArrayPattern")
)) ||
(n.type !== "ObjectPattern" &&
privateUtil.hasNewlineInRange(
options.originalText,
options.locStart(n),
options.locEnd(n)
));
const isFlowInterfaceLikeBody =
isTypeAnnotation &&
parent &&
(parent.type === "InterfaceDeclaration" ||
parent.type === "DeclareInterface" ||
parent.type === "DeclareClass") &&
path.getName() === "body";
const separator = isFlowInterfaceLikeBody
? ";"
: n.type === "TSInterfaceBody" || n.type === "TSTypeLiteral"
? ifBreak(semi, ";")
: ",";
const fields = [];
const leftBrace = n.exact ? "{|" : "{";
const rightBrace = n.exact ? "|}" : "}";
let propertiesField;
if (n.type === "TSTypeLiteral") {
propertiesField = "members";
} else if (n.type === "TSInterfaceBody") {
propertiesField = "body";
} else {
propertiesField = "properties";
}
2017-01-13 23:03:53 +03:00
if (isTypeAnnotation) {
fields.push("indexers", "callProperties");
}
fields.push(propertiesField);
2016-12-31 07:10:22 +03:00
// Unfortunately, things are grouped together in the ast can be
// interleaved in the source code. So we need to reorder them before
// printing them.
const propsAndLoc = [];
fields.forEach(field => {
path.each(childPath => {
const node = childPath.getValue();
propsAndLoc.push({
node: node,
printed: print(childPath),
loc: options.locStart(node)
});
}, field);
});
let separatorParts = [];
const props = propsAndLoc.sort((a, b) => a.loc - b.loc).map(prop => {
const result = concat(separatorParts.concat(group(prop.printed)));
separatorParts = [separator, line];
if (
prop.node.type === "TSPropertySignature" &&
privateUtil.hasNodeIgnoreComment(prop.node)
) {
separatorParts.shift();
}
if (
sharedUtil.isNextLineEmpty(options.originalText, prop.node, options)
) {
separatorParts.push(hardline);
}
return result;
});
2016-12-31 07:10:22 +03:00
const lastElem = privateUtil.getLast(n[propertiesField]);
const canHaveTrailingSeparator = !(
lastElem &&
(lastElem.type === "RestProperty" ||
lastElem.type === "RestElement" ||
lastElem.type === "ExperimentalRestProperty" ||
privateUtil.hasNodeIgnoreComment(lastElem))
);
let content;
if (props.length === 0 && !n.typeAnnotation) {
if (!hasDanglingComments(n)) {
return concat([leftBrace, rightBrace]);
}
content = group(
concat([
leftBrace,
comments.printDanglingComments(path, options),
softline,
rightBrace,
printOptionalToken(path)
])
);
2017-01-13 23:03:53 +03:00
} else {
content = concat([
leftBrace,
indent(
concat([options.bracketSpacing ? line : softline, concat(props)])
),
ifBreak(
canHaveTrailingSeparator &&
(separator !== "," || shouldPrintComma(options))
? separator
: ""
),
concat([options.bracketSpacing ? line : softline, rightBrace]),
printOptionalToken(path),
printTypeAnnotation(path, options, print)
]);
}
// If we inline the object as first argument of the parent, we don't want
// to create another group so that the object breaks before the return
// type
const parentParentParent = path.getParentNode(2);
if (
(n.type === "ObjectPattern" &&
parent &&
shouldHugArguments(parent) &&
parent.params[0] === n) ||
(shouldHugType(n) &&
parentParentParent &&
shouldHugArguments(parentParentParent) &&
parentParentParent.params[0].typeAnnotation &&
parentParentParent.params[0].typeAnnotation.typeAnnotation === n)
) {
return content;
2017-01-13 23:03:53 +03:00
}
return group(content, { shouldBreak });
}
2017-01-13 23:03:53 +03:00
// Babel 6
2017-01-28 18:50:22 +03:00
case "ObjectProperty": // Non-standard AST node type.
case "Property":
2017-01-13 23:03:53 +03:00
if (n.method || n.kind === "get" || n.kind === "set") {
return printMethod(path, options, print);
}
if (n.shorthand) {
parts.push(path.call(print, "value"));
} else {
let printedLeft;
2017-01-13 23:03:53 +03:00
if (n.computed) {
printedLeft = concat(["[", path.call(print, "key"), "]"]);
2017-01-13 23:03:53 +03:00
} else {
printedLeft = printPropertyKey(path, options, print);
2017-01-13 23:03:53 +03:00
}
parts.push(
printAssignment(
n.key,
printedLeft,
":",
n.value,
path.call(print, "value"),
options
)
);
2017-01-13 23:03:53 +03:00
}
2017-01-28 18:50:22 +03:00
return concat(parts); // Babel 6
case "ClassMethod":
2017-01-13 23:03:53 +03:00
if (n.static) {
parts.push("static ");
}
parts = parts.concat(printObjectMethod(path, options, print));
2017-01-28 18:50:22 +03:00
return concat(parts); // Babel 6
case "ObjectMethod":
2017-01-13 23:03:53 +03:00
return printObjectMethod(path, options, print);
case "Decorator":
2017-01-28 18:50:22 +03:00
return concat(["@", path.call(print, "expression")]);
2017-01-13 23:03:53 +03:00
case "ArrayExpression":
case "ArrayPattern":
2017-01-13 23:03:53 +03:00
if (n.elements.length === 0) {
if (!hasDanglingComments(n)) {
parts.push("[]");
} else {
parts.push(
group(
concat([
"[",
comments.printDanglingComments(path, options),
softline,
"]"
])
)
);
}
2017-01-13 23:03:53 +03:00
} else {
const lastElem = privateUtil.getLast(n.elements);
const canHaveTrailingComma = !(
lastElem && lastElem.type === "RestElement"
);
// JavaScript allows you to have empty elements in an array which
// changes its length based on the number of commas. The algorithm
// is that if the last argument is null, we need to force insert
// a comma to ensure JavaScript recognizes it.
// [,].length === 1
// [1,].length === 1
// [1,,].length === 2
//
// Note that privateUtil.getLast returns null if the array is empty, but
// we already check for an empty array just above so we are safe
const needsForcedTrailingComma =
canHaveTrailingComma && lastElem === null;
2017-01-13 23:03:53 +03:00
parts.push(
2017-01-24 21:54:01 +03:00
group(
2017-01-13 23:03:53 +03:00
concat([
"[",
indent(
concat([
softline,
printArrayItems(path, options, "elements", print)
])
2017-01-13 23:03:53 +03:00
),
needsForcedTrailingComma ? "," : "",
ifBreak(
canHaveTrailingComma &&
!needsForcedTrailingComma &&
shouldPrintComma(options)
2017-01-23 20:49:46 +03:00
? ","
: ""
),
2017-02-23 20:57:51 +03:00
comments.printDanglingComments(
path,
options,
/* sameIndent */ true
),
softline,
2017-01-13 23:03:53 +03:00
"]"
])
)
);
}
parts.push(
printOptionalToken(path),
printTypeAnnotation(path, options, print)
);
2017-01-13 23:03:53 +03:00
return concat(parts);
case "SequenceExpression": {
const parent = path.getParentNode(0);
if (
parent.type === "ExpressionStatement" ||
parent.type === "ForStatement"
) {
// For ExpressionStatements and for-loop heads, which are among
// the few places a SequenceExpression appears unparenthesized, we want
// to indent expressions after the first.
const parts = [];
path.each(p => {
if (p.getName() === 0) {
parts.push(print(p));
} else {
parts.push(",", indent(concat([line, print(p)])));
}
}, "expressions");
return group(concat(parts));
}
return group(
concat([join(concat([",", line]), path.map(print, "expressions"))])
);
}
2017-01-13 23:03:53 +03:00
case "ThisExpression":
2017-01-20 21:12:37 +03:00
return "this";
2017-01-13 23:03:53 +03:00
case "Super":
return "super";
case "NullLiteral": // Babel 6 Literal split
return "null";
case "RegExpLiteral": // Babel 6 Literal split
return printRegex(n);
case "NumericLiteral": // Babel 6 Literal split
return privateUtil.printNumber(n.extra.raw);
case "BooleanLiteral": // Babel 6 Literal split
case "StringLiteral": // Babel 6 Literal split
case "Literal": {
if (n.regex) {
return printRegex(n.regex);
}
if (typeof n.value === "number") {
return privateUtil.printNumber(n.raw);
}
if (typeof n.value !== "string") {
return "" + n.value;
}
// TypeScript workaround for eslint/typescript-eslint-parser#267
// See corresponding workaround in needs-parens.js
const grandParent = path.getParentNode(1);
const isTypeScriptDirective =
options.parser === "typescript" &&
typeof n.value === "string" &&
grandParent &&
(grandParent.type === "Program" ||
grandParent.type === "BlockStatement");
return nodeStr(n, options, isTypeScriptDirective);
}
2017-01-28 18:50:22 +03:00
case "Directive":
return path.call(print, "value"); // Babel 6
case "DirectiveLiteral":
return nodeStr(n, options);
2017-01-13 23:03:53 +03:00
case "UnaryExpression":
parts.push(n.operator);
if (/[a-z]$/.test(n.operator)) {
parts.push(" ");
}
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "argument"));
return concat(parts);
case "UpdateExpression":
parts.push(path.call(print, "argument"), n.operator);
if (n.prefix) {
parts.reverse();
}
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ConditionalExpression":
return formatTernaryOperator(path, options, print, {
beforeParts: () => [path.call(print, "test")],
afterParts: breakClosingParen => [breakClosingParen ? softline : ""]
});
case "VariableDeclaration": {
const printed = path.map(childPath => {
return print(childPath);
}, "declarations");
2016-12-31 07:10:22 +03:00
// We generally want to terminate all variable declarations with a
// semicolon, except when they in the () part of for loops.
const parentNode = path.getParentNode();
const isParentForLoop =
parentNode.type === "ForStatement" ||
parentNode.type === "ForInStatement" ||
parentNode.type === "ForOfStatement" ||
parentNode.type === "ForAwaitStatement";
const hasValue = n.declarations.some(decl => decl.init);
let firstVariable;
if (printed.length === 1) {
firstVariable = printed[0];
} else if (printed.length > 1) {
// Indent first var to comply with eslint one-var rule
firstVariable = indent(printed[0]);
}
2017-01-13 23:03:53 +03:00
parts = [
isNodeStartingWithDeclare(n, options) ? "declare " : "",
2017-01-13 23:03:53 +03:00
n.kind,
firstVariable ? concat([" ", firstVariable]) : "",
indent(
concat(
printed
.slice(1)
.map(p =>
concat([",", hasValue && !isParentForLoop ? hardline : line, p])
)
)
)
2017-01-13 23:03:53 +03:00
];
if (!(isParentForLoop && parentNode.body !== n)) {
parts.push(semi);
2017-01-13 23:03:53 +03:00
}
2017-01-24 21:54:01 +03:00
return group(concat(parts));
}
2017-01-13 23:03:53 +03:00
case "VariableDeclarator":
return printAssignment(
n.id,
concat([path.call(print, "id"), path.call(print, "typeParameters")]),
" =",
n.init,
n.init && path.call(print, "init"),
options
);
2017-01-13 23:03:53 +03:00
case "WithStatement":
return group(
concat([
"with (",
path.call(print, "object"),
")",
adjustClause(n.body, path.call(print, "body"))
])
);
case "IfStatement": {
const con = adjustClause(n.consequent, path.call(print, "consequent"));
const opening = group(
concat([
"if (",
group(
concat([
indent(concat([softline, path.call(print, "test")])),
softline
])
),
")",
con
])
);
parts.push(opening);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.alternate) {
2018-04-05 21:53:41 +03:00
const commentOnOwnLine =
(hasTrailingComment(n.consequent) &&
n.consequent.comments.some(
comment =>
comment.trailing && !handleComments.isBlockComment(comment)
)) ||
needsHardlineAfterDanglingComment(n);
2018-04-05 21:53:41 +03:00
const elseOnSameLine =
n.consequent.type === "BlockStatement" && !commentOnOwnLine;
parts.push(elseOnSameLine ? " " : hardline);
if (hasDanglingComments(n)) {
parts.push(
comments.printDanglingComments(path, options, true),
commentOnOwnLine ? hardline : " "
);
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(
2018-04-05 21:53:41 +03:00
"else",
group(
adjustClause(
n.alternate,
path.call(print, "alternate"),
n.alternate.type === "IfStatement"
)
)
2017-01-13 23:03:53 +03:00
);
}
2016-12-31 07:10:22 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "ForStatement": {
const body = adjustClause(n.body, path.call(print, "body"));
2016-12-31 07:10:22 +03:00
// We want to keep dangling comments above the loop to stay consistent.
// Any comment positioned between the for statement and the parentheses
// is going to be printed before the statement.
2017-02-23 20:57:51 +03:00
const dangling = comments.printDanglingComments(
path,
options,
/* sameLine */ true
);
const printedComments = dangling ? concat([dangling, softline]) : "";
2017-01-13 23:03:53 +03:00
if (!n.init && !n.test && !n.update) {
return concat([printedComments, group(concat(["for (;;)", body]))]);
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat([
printedComments,
group(
concat([
"for (",
group(
concat([
indent(
concat([
softline,
path.call(print, "init"),
";",
line,
path.call(print, "test"),
";",
line,
path.call(print, "update")
])
),
softline
])
),
")",
body
])
)
]);
}
case "WhileStatement":
return group(
concat([
"while (",
group(
concat([
indent(concat([softline, path.call(print, "test")])),
softline
])
),
")",
adjustClause(n.body, path.call(print, "body"))
])
);
2017-01-13 23:03:53 +03:00
case "ForInStatement":
// Note: esprima can't actually parse "for each (".
return group(
concat([
n.each ? "for each (" : "for (",
path.call(print, "left"),
" in ",
path.call(print, "right"),
")",
adjustClause(n.body, path.call(print, "body"))
])
);
2017-01-13 23:03:53 +03:00
case "ForOfStatement":
case "ForAwaitStatement": {
2017-04-05 23:28:02 +03:00
// Babylon 7 removed ForAwaitStatement in favor of ForOfStatement
// with `"await": true`:
// https://github.com/estree/estree/pull/138
const isAwait = n.type === "ForAwaitStatement" || n.await;
return group(
concat([
"for",
isAwait ? " await" : "",
" (",
path.call(print, "left"),
" of ",
path.call(print, "right"),
")",
adjustClause(n.body, path.call(print, "body"))
])
);
}
case "DoWhileStatement": {
const clause = adjustClause(n.body, path.call(print, "body"));
const doBody = group(concat(["do", clause]));
parts = [doBody];
2016-12-31 07:10:22 +03:00
if (n.body.type === "BlockStatement") {
parts.push(" ");
} else {
parts.push(hardline);
}
parts.push("while (");
2016-12-31 22:38:58 +03:00
parts.push(
group(
concat([
indent(concat([softline, path.call(print, "test")])),
softline
])
),
")",
semi
);
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "DoExpression":
return concat(["do ", path.call(print, "body")]);
2017-01-13 23:03:53 +03:00
case "BreakStatement":
parts.push("break");
2016-12-31 07:10:22 +03:00
if (n.label) {
parts.push(" ", path.call(print, "label"));
}
2016-12-31 22:38:58 +03:00
parts.push(semi);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ContinueStatement":
parts.push("continue");
2016-12-31 22:38:58 +03:00
if (n.label) {
parts.push(" ", path.call(print, "label"));
}
2016-12-31 07:10:22 +03:00
parts.push(semi);
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "LabeledStatement":
if (n.body.type === "EmptyStatement") {
2017-01-28 18:50:22 +03:00
return concat([path.call(print, "label"), ":;"]);
}
2017-01-13 23:03:53 +03:00
return concat([
path.call(print, "label"),
": ",
2017-01-13 23:03:53 +03:00
path.call(print, "body")
]);
case "TryStatement":
return concat([
"try ",
path.call(print, "block"),
n.handler ? concat([" ", path.call(print, "handler")]) : "",
n.finalizer ? concat([" finally ", path.call(print, "finalizer")]) : ""
]);
2017-01-13 23:03:53 +03:00
case "CatchClause":
return concat([
"catch ",
n.param ? concat(["(", path.call(print, "param"), ") "]) : "",
path.call(print, "body")
]);
2017-01-13 23:03:53 +03:00
case "ThrowStatement":
return concat(["throw ", path.call(print, "argument"), semi]);
2017-01-13 23:03:53 +03:00
// Note: ignoring n.lexical because it has no printing consequences.
case "SwitchStatement":
return concat([
group(
concat([
"switch (",
indent(concat([softline, path.call(print, "discriminant")])),
softline,
")"
])
),
" {",
2017-01-23 20:49:46 +03:00
n.cases.length > 0
? indent(
concat([
hardline,
join(
hardline,
path.map(casePath => {
const caseNode = casePath.getValue();
return concat([
casePath.call(print),
n.cases.indexOf(caseNode) !== n.cases.length - 1 &&
sharedUtil.isNextLineEmpty(
options.originalText,
caseNode,
options
)
? hardline
: ""
]);
}, "cases")
)
])
)
2017-01-23 20:49:46 +03:00
: "",
2017-01-13 23:03:53 +03:00
hardline,
"}"
]);
case "SwitchCase": {
if (n.test) {
parts.push("case ", path.call(print, "test"), ":");
} else {
parts.push("default:");
}
2017-01-13 23:03:53 +03:00
const consequent = n.consequent.filter(
node => node.type !== "EmptyStatement"
);
if (consequent.length > 0) {
const cons = path.call(consequentPath => {
return printStatementSequence(consequentPath, options, print);
}, "consequent");
2017-01-13 23:03:53 +03:00
parts.push(
consequent.length === 1 && consequent[0].type === "BlockStatement"
2017-01-28 18:50:22 +03:00
? concat([" ", cons])
: indent(concat([hardline, cons]))
2017-01-13 23:03:53 +03:00
);
}
return concat(parts);
}
2017-01-13 23:03:53 +03:00
// JSX extensions below.
case "DebuggerStatement":
return concat(["debugger", semi]);
2017-01-13 23:03:53 +03:00
case "JSXAttribute":
parts.push(path.call(print, "name"));
if (n.value) {
let res;
if (isStringLiteral(n.value)) {
const value = rawText(n.value);
res = '"' + value.slice(1, -1).replace(/"/g, "&quot;") + '"';
2017-01-13 23:03:53 +03:00
} else {
res = path.call(print, "value");
}
parts.push("=", res);
}
2017-01-13 23:03:53 +03:00
return concat(parts);
case "JSXIdentifier":
// Can be removed when this is fixed:
// https://github.com/eslint/typescript-eslint-parser/issues/337
if (!n.name) {
return "this";
}
return "" + n.name;
2017-01-13 23:03:53 +03:00
case "JSXNamespacedName":
return join(":", [
path.call(print, "namespace"),
path.call(print, "name")
]);
case "JSXMemberExpression":
return join(".", [
path.call(print, "object"),
path.call(print, "property")
]);
case "TSQualifiedName":
return join(".", [path.call(print, "left"), path.call(print, "right")]);
2017-01-13 23:03:53 +03:00
case "JSXSpreadAttribute":
case "JSXSpreadChild": {
return concat([
"{",
path.call(p => {
const printed = concat(["...", print(p)]);
const n = p.getValue();
if (!n.comments || !n.comments.length) {
return printed;
}
return concat([
indent(
concat([
softline,
comments.printComments(p, () => printed, options)
])
),
softline
]);
}, n.type === "JSXSpreadAttribute" ? "argument" : "expression"),
"}"
]);
}
case "JSXExpressionContainer": {
const parent = path.getParentNode(0);
const preventInline =
parent.type === "JSXAttribute" &&
n.expression.comments &&
n.expression.comments.length > 0;
const shouldInline =
!preventInline &&
(n.expression.type === "ArrayExpression" ||
n.expression.type === "ObjectExpression" ||
n.expression.type === "ArrowFunctionExpression" ||
n.expression.type === "CallExpression" ||
n.expression.type === "FunctionExpression" ||
n.expression.type === "JSXEmptyExpression" ||
n.expression.type === "TemplateLiteral" ||
n.expression.type === "TaggedTemplateExpression" ||
n.expression.type === "DoExpression" ||
2017-11-30 06:09:45 +03:00
(isJSXNode(parent) &&
(n.expression.type === "ConditionalExpression" ||
isBinaryish(n.expression))));
if (shouldInline) {
2017-02-23 20:57:51 +03:00
return group(
concat(["{", path.call(print, "expression"), lineSuffixBoundary, "}"])
2017-02-23 20:57:51 +03:00
);
}
return group(
2017-01-13 23:03:53 +03:00
concat([
"{",
indent(concat([softline, path.call(print, "expression")])),
2017-01-13 23:03:53 +03:00
softline,
lineSuffixBoundary,
2017-01-13 23:03:53 +03:00
"}"
])
);
}
2017-11-30 06:09:45 +03:00
case "JSXFragment":
case "TSJsxFragment":
case "JSXElement": {
const elem = comments.printComments(
path,
() => printJSXElement(path, options, print),
options
);
return maybeWrapJSXElementInParens(path, elem);
}
case "JSXOpeningElement": {
const n = path.getValue();
const nameHasComments =
n.name && n.name.comments && n.name.comments.length > 0;
// Don't break self-closing elements with no attributes and no comments
if (n.selfClosing && !n.attributes.length && !nameHasComments) {
return concat([
"<",
path.call(print, "name"),
path.call(print, "typeParameters"),
" />"
]);
}
// don't break up opening elements with a single long text attribute
2017-01-28 18:50:22 +03:00
if (
2017-11-30 06:09:45 +03:00
n.attributes &&
2017-01-28 18:50:22 +03:00
n.attributes.length === 1 &&
2017-02-16 06:56:11 +03:00
n.attributes[0].value &&
isStringLiteral(n.attributes[0].value) &&
!n.attributes[0].value.value.includes("\n") &&
// We should break for the following cases:
// <div
// // comment
// attr="value"
// >
// <div
// attr="value"
// // comment
// >
!nameHasComments &&
(!n.attributes[0].comments || !n.attributes[0].comments.length)
) {
2017-01-28 18:50:22 +03:00
return group(
concat([
"<",
path.call(print, "name"),
path.call(print, "typeParameters"),
2017-01-28 18:50:22 +03:00
" ",
concat(path.map(print, "attributes")),
n.selfClosing ? " />" : ">"
])
);
}
const lastAttrHasTrailingComments =
n.attributes.length &&
hasTrailingComment(privateUtil.getLast(n.attributes));
const bracketSameLine =
options.jsxBracketSameLine &&
// We should print the bracket in a new line for the following cases:
// <div
// // comment
// >
// <div
// attr // comment
// >
(!nameHasComments || n.attributes.length) &&
!lastAttrHasTrailingComments;
// We should print the opening element expanded if any prop value is a
// string literal with newlines
const shouldBreak =
n.attributes &&
n.attributes.some(
attr =>
attr.value &&
isStringLiteral(attr.value) &&
attr.value.value.includes("\n")
);
2017-01-13 23:03:53 +03:00
return group(
2017-01-09 20:09:04 +03:00
concat([
2017-01-13 23:03:53 +03:00
"<",
path.call(print, "name"),
path.call(print, "typeParameters"),
2017-01-24 21:54:01 +03:00
concat([
indent(
concat(
2017-01-28 18:50:22 +03:00
path.map(attr => concat([line, print(attr)]), "attributes")
2017-01-24 21:54:01 +03:00
)
),
n.selfClosing ? line : bracketSameLine ? ">" : softline
2017-01-24 21:54:01 +03:00
]),
n.selfClosing ? "/>" : bracketSameLine ? "" : ">"
]),
{ shouldBreak }
2017-01-09 20:09:04 +03:00
);
}
2017-01-13 23:03:53 +03:00
case "JSXClosingElement":
2017-01-28 18:50:22 +03:00
return concat(["</", path.call(print, "name"), ">"]);
2017-11-30 06:09:45 +03:00
case "JSXOpeningFragment":
case "JSXClosingFragment":
case "TSJsxOpeningFragment":
case "TSJsxClosingFragment": {
const hasComment = n.comments && n.comments.length;
const hasOwnLineComment =
hasComment && !n.comments.every(handleComments.isBlockComment);
2017-11-30 06:09:45 +03:00
const isOpeningFragment =
n.type === "JSXOpeningFragment" || n.type === "TSJsxOpeningFragment";
return concat([
isOpeningFragment ? "<" : "</",
indent(
concat([
hasOwnLineComment
? hardline
2018-04-12 07:39:13 +03:00
: hasComment && !isOpeningFragment
? " "
: "",
2017-11-30 06:09:45 +03:00
comments.printDanglingComments(path, options, true)
])
),
hasOwnLineComment ? hardline : "",
">"
]);
}
2017-01-13 23:03:53 +03:00
case "JSXText":
/* istanbul ignore next */
2017-01-13 23:03:53 +03:00
throw new Error("JSXTest should be handled by JSXElement");
case "JSXEmptyExpression": {
const requiresHardline =
n.comments && !n.comments.every(handleComments.isBlockComment);
return concat([
comments.printDanglingComments(
path,
options,
2017-03-16 23:26:44 +03:00
/* sameIndent */ !requiresHardline
),
requiresHardline ? hardline : ""
]);
}
2017-01-13 23:03:53 +03:00
case "ClassBody":
if (!n.comments && n.body.length === 0) {
return "{}";
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
2017-02-03 19:50:51 +03:00
return concat([
"{",
n.body.length > 0
? indent(
concat([
hardline,
path.call(bodyPath => {
return printStatementSequence(bodyPath, options, print);
}, "body")
])
2017-01-13 23:03:53 +03:00
)
: comments.printDanglingComments(path, options),
2017-02-03 19:50:51 +03:00
hardline,
"}"
]);
2017-01-13 23:03:53 +03:00
case "ClassProperty":
case "TSAbstractClassProperty":
case "ClassPrivateProperty": {
if (n.accessibility) {
parts.push(n.accessibility + " ");
}
if (n.static) {
parts.push("static ");
}
if (n.type === "TSAbstractClassProperty") {
parts.push("abstract ");
}
if (n.readonly) {
parts.push("readonly ");
}
const variance = getFlowVariance(n);
if (variance) {
parts.push(variance);
}
2017-01-13 23:03:53 +03:00
if (n.computed) {
parts.push("[", path.call(print, "key"), "]");
} else {
parts.push(printPropertyKey(path, options, print));
}
parts.push(printTypeAnnotation(path, options, print));
if (n.value) {
parts.push(
" =",
printAssignmentRight(
n.value,
path.call(print, "value"),
false, // canBreak
options
)
);
}
2016-12-31 22:38:58 +03:00
parts.push(semi);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "ClassDeclaration":
case "ClassExpression":
case "TSAbstractClassDeclaration":
if (isNodeStartingWithDeclare(n, options)) {
parts.push("declare ");
}
parts.push(concat(printClass(path, options, print)));
return concat(parts);
case "TSInterfaceHeritage":
parts.push(path.call(print, "id"));
if (n.typeParameters) {
parts.push(path.call(print, "typeParameters"));
}
return concat(parts);
2017-01-13 23:03:53 +03:00
case "TemplateElement":
return join(literalline, n.value.raw.split(/\r?\n/g));
case "TemplateLiteral": {
const expressions = path.map(print, "expressions");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push("`");
2016-12-31 07:10:22 +03:00
path.each(childPath => {
const i = childPath.getName();
2016-12-31 07:10:22 +03:00
parts.push(print(childPath));
2016-12-31 07:10:22 +03:00
if (i < expressions.length) {
// For a template literal of the following form:
// `someQuery {
// ${call({
// a,
// b,
// })}
// }`
// the expression is on its own line (there is a \n in the previous
// quasi literal), therefore we want to indent the JavaScript
// expression inside at the beginning of ${ instead of the beginning
// of the `.
const tabWidth = options.tabWidth;
const indentSize = privateUtil.getIndentSize(
childPath.getValue().value.raw,
tabWidth
);
let printed = expressions[i];
if (
(n.expressions[i].comments && n.expressions[i].comments.length) ||
n.expressions[i].type === "MemberExpression" ||
n.expressions[i].type === "ConditionalExpression"
) {
printed = concat([indent(concat([softline, printed])), softline]);
}
const aligned = addAlignmentToDoc(printed, indentSize, tabWidth);
parts.push(group(concat(["${", aligned, lineSuffixBoundary, "}"])));
}
}, "quasis");
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
parts.push("`");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
// These types are unprintable because they serve as abstract
// supertypes for other (printable) types.
case "TaggedTemplateExpression":
return concat([
path.call(print, "tag"),
path.call(print, "typeParameters"),
path.call(print, "quasi")
]);
2017-01-13 23:03:53 +03:00
case "Node":
case "Printable":
case "SourceLocation":
case "Position":
case "Statement":
case "Function":
case "Pattern":
case "Expression":
case "Declaration":
case "Specifier":
case "NamedSpecifier":
case "Comment":
2017-01-28 18:50:22 +03:00
case "MemberTypeAnnotation": // Flow
case "Type":
/* istanbul ignore next */
2017-01-13 23:03:53 +03:00
throw new Error("unprintable type: " + JSON.stringify(n.type));
// Type Annotations for Facebook Flow, typically stripped out or
// transformed away before printing.
case "TypeAnnotation":
case "TSTypeAnnotation":
2017-01-13 23:03:53 +03:00
if (n.typeAnnotation) {
return path.call(print, "typeAnnotation");
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
/* istanbul ignore next */
2017-01-13 23:03:53 +03:00
return "";
case "TSTupleType":
case "TupleTypeAnnotation": {
const typesField = n.type === "TSTupleType" ? "elementTypes" : "types";
return group(
concat([
"[",
indent(
concat([
softline,
printArrayItems(path, options, typesField, print)
])
),
// TypeScript doesn't support trailing commas in tuple types
n.type === "TSTupleType"
? ""
: ifBreak(shouldPrintComma(options) ? "," : ""),
comments.printDanglingComments(path, options, /* sameIndent */ true),
softline,
"]"
])
);
}
2017-01-13 23:03:53 +03:00
case "ExistsTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "*";
2017-01-13 23:03:53 +03:00
case "EmptyTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "empty";
2017-01-13 23:03:53 +03:00
case "AnyTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "any";
2017-01-13 23:03:53 +03:00
case "MixedTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "mixed";
2017-01-13 23:03:53 +03:00
case "ArrayTypeAnnotation":
2017-01-28 18:50:22 +03:00
return concat([path.call(print, "elementType"), "[]"]);
2017-01-13 23:03:53 +03:00
case "BooleanTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "boolean";
2017-01-13 23:03:53 +03:00
case "BooleanLiteralTypeAnnotation":
return "" + n.value;
case "DeclareClass":
return printFlowDeclaration(path, printClass(path, options, print));
2017-01-13 23:03:53 +03:00
case "DeclareFunction":
// For TypeScript the DeclareFunction node shares the AST
// structure with FunctionDeclaration
if (n.params) {
return concat([
"declare ",
printFunctionDeclaration(path, print, options),
semi
]);
}
2017-01-13 23:03:53 +03:00
return printFlowDeclaration(path, [
"function ",
path.call(print, "id"),
n.predicate ? " " : "",
path.call(print, "predicate"),
semi
2017-01-13 23:03:53 +03:00
]);
case "DeclareModule":
return printFlowDeclaration(path, [
"module ",
path.call(print, "id"),
" ",
path.call(print, "body")
]);
case "DeclareModuleExports":
return printFlowDeclaration(path, [
"module.exports",
": ",
2017-01-13 23:03:53 +03:00
path.call(print, "typeAnnotation"),
semi
2017-01-13 23:03:53 +03:00
]);
case "DeclareVariable":
return printFlowDeclaration(path, ["var ", path.call(print, "id"), semi]);
2017-01-13 23:03:53 +03:00
case "DeclareExportAllDeclaration":
2017-01-28 18:50:22 +03:00
return concat(["declare export * from ", path.call(print, "source")]);
2017-01-13 23:03:53 +03:00
case "DeclareExportDeclaration":
2017-01-28 18:50:22 +03:00
return concat(["declare ", printExportDeclaration(path, options, print)]);
case "DeclareOpaqueType":
case "OpaqueType": {
parts.push(
"opaque type ",
path.call(print, "id"),
path.call(print, "typeParameters")
);
if (n.supertype) {
parts.push(": ", path.call(print, "supertype"));
}
if (n.impltype) {
parts.push(" = ", path.call(print, "impltype"));
}
parts.push(semi);
if (n.type === "DeclareOpaqueType") {
return printFlowDeclaration(path, parts);
}
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "FunctionTypeAnnotation":
case "TSFunctionType": {
2017-01-13 23:03:53 +03:00
// FunctionTypeAnnotation is ambiguous:
// declare function foo(a: B): void; OR
// var A: (a: B) => void;
const parent = path.getParentNode(0);
const parentParent = path.getParentNode(1);
const parentParentParent = path.getParentNode(2);
let isArrowFunctionTypeAnnotation =
n.type === "TSFunctionType" ||
!(
(parent.type === "ObjectTypeProperty" &&
!getFlowVariance(parent) &&
!parent.optional &&
options.locStart(parent) === options.locStart(n)) ||
parent.type === "ObjectTypeCallProperty" ||
(parentParentParent && parentParentParent.type === "DeclareFunction")
);
let needsColon =
isArrowFunctionTypeAnnotation &&
(parent.type === "TypeAnnotation" ||
parent.type === "TSTypeAnnotation");
2017-01-13 23:03:53 +03:00
// Sadly we can't put it inside of FastPath::needsColon because we are
// printing ":" as part of the expression and it would put parenthesis
// around :(
const needsParens =
needsColon &&
isArrowFunctionTypeAnnotation &&
(parent.type === "TypeAnnotation" ||
parent.type === "TSTypeAnnotation") &&
parentParent.type === "ArrowFunctionExpression";
if (isObjectTypePropertyAFunction(parent, options)) {
isArrowFunctionTypeAnnotation = true;
needsColon = true;
}
if (needsParens) {
parts.push("(");
}
parts.push(
printFunctionParams(
path,
print,
options,
/* expandArg */ false,
/* printTypeParams */ true
)
);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
// The returnType is not wrapped in a TypeAnnotation, so the colon
// needs to be added separately.
if (n.returnType || n.predicate || n.typeAnnotation) {
2017-01-13 23:03:53 +03:00
parts.push(
isArrowFunctionTypeAnnotation ? " => " : ": ",
path.call(print, "returnType"),
path.call(print, "predicate"),
path.call(print, "typeAnnotation")
2017-01-13 23:03:53 +03:00
);
}
if (needsParens) {
parts.push(")");
}
2016-12-31 07:10:22 +03:00
return group(concat(parts));
}
2017-01-13 23:03:53 +03:00
case "FunctionTypeParam":
return concat([
path.call(print, "name"),
printOptionalToken(path),
2017-01-13 23:03:53 +03:00
n.name ? ": " : "",
path.call(print, "typeAnnotation")
]);
case "GenericTypeAnnotation":
return concat([
path.call(print, "id"),
path.call(print, "typeParameters")
]);
case "DeclareInterface":
case "InterfaceDeclaration": {
if (
n.type === "DeclareInterface" ||
isNodeStartingWithDeclare(n, options)
) {
parts.push("declare ");
}
2016-12-31 07:10:22 +03:00
parts.push(
"interface ",
2017-01-13 23:03:53 +03:00
path.call(print, "id"),
path.call(print, "typeParameters")
);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n["extends"].length > 0) {
parts.push(
group(
indent(
concat([line, "extends ", join(", ", path.map(print, "extends"))])
)
)
);
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
parts.push(" ");
parts.push(path.call(print, "body"));
2016-12-31 07:10:22 +03:00
return group(concat(parts));
}
2017-01-13 23:03:53 +03:00
case "ClassImplements":
case "InterfaceExtends":
return concat([
path.call(print, "id"),
path.call(print, "typeParameters")
]);
case "TSIntersectionType":
case "IntersectionTypeAnnotation": {
const types = path.map(print, "types");
const result = [];
let wasIndented = false;
for (let i = 0; i < types.length; ++i) {
if (i === 0) {
result.push(types[i]);
} else if (isObjectType(n.types[i - 1]) && isObjectType(n.types[i])) {
// If both are objects, don't indent
result.push(
concat([" & ", wasIndented ? indent(types[i]) : types[i]])
);
} else if (!isObjectType(n.types[i - 1]) && !isObjectType(n.types[i])) {
// If no object is involved, go to the next line if it breaks
result.push(indent(concat([" &", line, types[i]])));
} else {
// If you go from object to non-object or vis-versa, then inline it
if (i > 1) {
wasIndented = true;
}
result.push(" & ", i > 1 ? indent(types[i]) : types[i]);
}
}
return group(concat(result));
}
case "TSUnionType":
2017-01-13 23:03:53 +03:00
case "UnionTypeAnnotation": {
// single-line variation
// A | B | C
// multi-line variation
// | A
// | B
// | C
const parent = path.getParentNode();
// If there's a leading comment, the parent is doing the indentation
const shouldIndent =
2017-05-05 05:06:30 +03:00
parent.type !== "TypeParameterInstantiation" &&
parent.type !== "TSTypeParameterInstantiation" &&
parent.type !== "GenericTypeAnnotation" &&
parent.type !== "TSTypeReference" &&
!(parent.type === "FunctionTypeParam" && !parent.name) &&
!(
(parent.type === "TypeAlias" ||
parent.type === "VariableDeclarator") &&
hasLeadingOwnLineComment(options.originalText, n, options)
);
// {
// a: string
// } | null | void
// should be inlined and not be printed in the multi-line variant
const shouldHug = shouldHugType(n);
// We want to align the children but without its comment, so it looks like
// | child1
// // comment
// | child2
const printed = path.map(typePath => {
let printedType = typePath.call(print);
if (!shouldHug) {
printedType = align(2, printedType);
}
return comments.printComments(typePath, () => printedType, options);
}, "types");
if (shouldHug) {
return join(" | ", printed);
}
const code = concat([
ifBreak(concat([shouldIndent ? line : "", "| "])),
join(concat([line, "| "]), printed)
]);
let hasParens;
if (n.type === "TSUnionType") {
const greatGrandParent = path.getParentNode(2);
const greatGreatGrandParent = path.getParentNode(3);
hasParens =
greatGrandParent &&
greatGrandParent.type === "TSParenthesizedType" &&
greatGreatGrandParent &&
(greatGreatGrandParent.type === "TSUnionType" ||
greatGreatGrandParent.type === "TSIntersectionType");
} else {
hasParens = pathNeedsParens(path, options);
}
if (hasParens) {
return group(concat([indent(code), softline]));
}
return group(shouldIndent ? indent(code) : code);
}
2017-01-13 23:03:53 +03:00
case "NullableTypeAnnotation":
2017-01-28 18:50:22 +03:00
return concat(["?", path.call(print, "typeAnnotation")]);
case "TSNullKeyword":
2017-01-13 23:03:53 +03:00
case "NullLiteralTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "null";
2017-01-13 23:03:53 +03:00
case "ThisTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "this";
2017-01-13 23:03:53 +03:00
case "NumberTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "number";
2017-01-13 23:03:53 +03:00
case "ObjectTypeCallProperty":
if (n.static) {
parts.push("static ");
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "value"));
return concat(parts);
case "ObjectTypeIndexer": {
const variance = getFlowVariance(n);
2017-01-13 23:03:53 +03:00
return concat([
variance || "",
2017-01-13 23:03:53 +03:00
"[",
path.call(print, "id"),
n.id ? ": " : "",
path.call(print, "key"),
"]: ",
path.call(print, "value")
]);
}
case "ObjectTypeProperty": {
const variance = getFlowVariance(n);
2017-01-13 23:03:53 +03:00
return concat([
n.static ? "static " : "",
isGetterOrSetter(n) ? n.kind + " " : "",
variance || "",
printPropertyKey(path, options, print),
printOptionalToken(path),
isFunctionNotation(n, options) ? "" : ": ",
2017-01-13 23:03:53 +03:00
path.call(print, "value")
]);
}
2017-01-13 23:03:53 +03:00
case "QualifiedTypeIdentifier":
return concat([
path.call(print, "qualification"),
".",
path.call(print, "id")
]);
case "StringLiteralTypeAnnotation":
return nodeStr(n, options);
2017-01-13 23:03:53 +03:00
case "NumberLiteralTypeAnnotation":
assert.strictEqual(typeof n.value, "number");
if (n.extra != null) {
return privateUtil.printNumber(n.extra.raw);
}
return privateUtil.printNumber(n.raw);
2017-01-13 23:03:53 +03:00
case "StringTypeAnnotation":
2017-01-20 21:12:37 +03:00
return "string";
2017-01-13 23:03:53 +03:00
case "DeclareTypeAlias":
case "TypeAlias": {
if (
n.type === "DeclareTypeAlias" ||
isNodeStartingWithDeclare(n, options)
) {
parts.push("declare ");
}
2016-12-31 07:10:22 +03:00
const canBreak = n.right.type === "StringLiteralTypeAnnotation";
const printed = printAssignmentRight(
n.right,
path.call(print, "right"),
canBreak,
options
);
parts.push(
"type ",
path.call(print, "id"),
path.call(print, "typeParameters"),
" =",
printed,
semi
);
2016-12-31 07:10:22 +03:00
return group(concat(parts));
}
2017-01-13 23:03:53 +03:00
case "TypeCastExpression":
return concat([
"(",
path.call(print, "expression"),
": ",
2017-01-13 23:03:53 +03:00
path.call(print, "typeAnnotation"),
")"
]);
case "TypeParameterDeclaration":
case "TypeParameterInstantiation":
case "TSTypeParameterDeclaration":
case "TSTypeParameterInstantiation":
return printTypeParameters(path, options, print, "params");
case "TSTypeParameter":
case "TypeParameter": {
const parent = path.getParentNode();
if (parent.type === "TSMappedType") {
parts.push(path.call(print, "name"));
if (n.constraint) {
parts.push(" in ", path.call(print, "constraint"));
}
return concat(parts);
}
const variance = getFlowVariance(n);
2017-01-13 23:03:53 +03:00
if (variance) {
parts.push(variance);
2017-01-13 23:03:53 +03:00
}
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "name"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.bound) {
parts.push(": ");
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "bound"));
}
2016-12-31 07:10:22 +03:00
if (n.constraint) {
parts.push(" extends ", path.call(print, "constraint"));
}
2017-01-13 23:03:53 +03:00
if (n["default"]) {
parts.push(" = ", path.call(print, "default"));
2017-01-13 23:03:53 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "TypeofTypeAnnotation":
2017-01-28 18:50:22 +03:00
return concat(["typeof ", path.call(print, "argument")]);
2017-01-13 23:03:53 +03:00
case "VoidTypeAnnotation":
return "void";
case "InferredPredicate":
return "%checks";
// Unhandled types below. If encountered, nodes of these types should
// be either left alone or desugared into AST types that are fully
// supported by the pretty-printer.
case "DeclaredPredicate":
2017-01-28 18:50:22 +03:00
return concat(["%checks(", path.call(print, "value"), ")"]);
case "TSAbstractKeyword":
return "abstract";
case "TSAnyKeyword":
return "any";
case "TSAsyncKeyword":
return "async";
case "TSBooleanKeyword":
return "boolean";
case "TSConstKeyword":
return "const";
case "TSDeclareKeyword":
return "declare";
case "TSExportKeyword":
return "export";
case "TSNeverKeyword":
return "never";
case "TSNumberKeyword":
return "number";
case "TSObjectKeyword":
return "object";
case "TSProtectedKeyword":
return "protected";
case "TSPrivateKeyword":
return "private";
case "TSPublicKeyword":
return "public";
case "TSReadonlyKeyword":
return "readonly";
case "TSSymbolKeyword":
return "symbol";
case "TSStaticKeyword":
return "static";
case "TSStringKeyword":
return "string";
case "TSUndefinedKeyword":
return "undefined";
case "TSVoidKeyword":
return "void";
case "TSAsExpression":
return concat([
2017-04-05 23:28:02 +03:00
path.call(print, "expression"),
" as ",
path.call(print, "typeAnnotation")
]);
case "TSArrayType":
return concat([path.call(print, "elementType"), "[]"]);
case "TSPropertySignature": {
if (n.export) {
parts.push("export ");
}
if (n.accessibility) {
parts.push(n.accessibility + " ");
}
if (n.static) {
parts.push("static ");
}
if (n.readonly) {
parts.push("readonly ");
}
if (n.computed) {
parts.push("[");
}
parts.push(printPropertyKey(path, options, print));
if (n.computed) {
parts.push("]");
}
parts.push(printOptionalToken(path));
if (n.typeAnnotation) {
parts.push(": ");
parts.push(path.call(print, "typeAnnotation"));
}
// This isn't valid semantically, but it's in the AST so we can print it.
if (n.initializer) {
parts.push(" = ", path.call(print, "initializer"));
}
return concat(parts);
}
case "TSParameterProperty":
if (n.accessibility) {
parts.push(n.accessibility + " ");
}
if (n.export) {
parts.push("export ");
}
if (n.static) {
parts.push("static ");
}
if (n.readonly) {
parts.push("readonly ");
}
parts.push(path.call(print, "parameter"));
return concat(parts);
case "TSTypeReference":
return concat([
path.call(print, "typeName"),
printTypeParameters(path, options, print, "typeParameters")
]);
case "TSTypeQuery":
return concat(["typeof ", path.call(print, "exprName")]);
case "TSParenthesizedType": {
return path.call(print, "typeAnnotation");
}
case "TSIndexSignature": {
const parent = path.getParentNode();
return concat([
n.export ? "export " : "",
n.accessibility ? concat([n.accessibility, " "]) : "",
n.static ? "static " : "",
n.readonly ? "readonly " : "",
2017-04-05 23:28:02 +03:00
"[",
path.call(print, "index"),
"]: ",
path.call(print, "typeAnnotation"),
parent.type === "ClassBody" ? semi : ""
]);
}
case "TSTypePredicate":
return concat([
path.call(print, "parameterName"),
" is ",
path.call(print, "typeAnnotation")
]);
case "TSNonNullExpression":
return concat([path.call(print, "expression"), "!"]);
case "TSThisType":
return "this";
case "TSLiteralType":
return path.call(print, "literal");
case "TSIndexedAccessType":
return concat([
path.call(print, "objectType"),
"[",
path.call(print, "indexType"),
"]"
]);
case "TSConstructSignature":
case "TSConstructorType":
case "TSCallSignature": {
if (n.type !== "TSCallSignature") {
parts.push("new ");
}
parts.push(
group(
printFunctionParams(
path,
print,
options,
/* expandArg */ false,
/* printTypeParams */ true
)
)
);
if (n.typeAnnotation) {
const isType = n.type === "TSConstructorType";
parts.push(isType ? " => " : ": ", path.call(print, "typeAnnotation"));
}
return concat(parts);
}
2017-04-23 18:25:25 +03:00
case "TSTypeOperator":
return concat([n.operator, " ", path.call(print, "typeAnnotation")]);
case "TSMappedType":
return group(
concat([
"{",
indent(
concat([
options.bracketSpacing ? line : softline,
n.readonlyToken
2018-03-16 05:45:18 +03:00
? concat([
2018-03-16 09:08:25 +03:00
getTypeScriptMappedTypeModifier(
n.readonlyToken,
"readonly"
),
2018-03-16 05:45:18 +03:00
" "
])
: "",
printTypeScriptModifiers(path, options, print),
"[",
path.call(print, "typeParameter"),
"]",
2018-03-16 05:45:18 +03:00
n.questionToken
2018-03-16 09:08:25 +03:00
? getTypeScriptMappedTypeModifier(n.questionToken, "?")
2018-03-16 05:45:18 +03:00
: "",
": ",
path.call(print, "typeAnnotation")
])
),
comments.printDanglingComments(path, options, /* sameIndent */ true),
options.bracketSpacing ? line : softline,
"}"
])
);
2017-04-26 00:15:33 +03:00
case "TSMethodSignature":
parts.push(
n.accessibility ? concat([n.accessibility, " "]) : "",
n.export ? "export " : "",
n.static ? "static " : "",
n.readonly ? "readonly " : "",
n.computed ? "[" : "",
path.call(print, "key"),
n.computed ? "]" : "",
printOptionalToken(path),
printFunctionParams(
path,
print,
options,
/* expandArg */ false,
/* printTypeParams */ true
)
);
if (n.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
}
return group(concat(parts));
case "TSNamespaceExportDeclaration":
parts.push("export as namespace ", path.call(print, "name"));
if (options.semi) {
parts.push(";");
}
return group(concat(parts));
case "TSEnumDeclaration":
if (isNodeStartingWithDeclare(n, options)) {
parts.push("declare ");
}
if (n.modifiers) {
parts.push(printTypeScriptModifiers(path, options, print));
}
if (n.const) {
parts.push("const ");
}
parts.push("enum ", path.call(print, "id"), " ");
if (n.members.length === 0) {
parts.push(
group(
concat([
"{",
comments.printDanglingComments(path, options),
softline,
"}"
])
)
);
} else {
parts.push(
group(
concat([
"{",
indent(
concat([
hardline,
printArrayItems(path, options, "members", print),
shouldPrintComma(options, "es5") ? "," : ""
])
),
comments.printDanglingComments(
path,
options,
/* sameIndent */ true
),
hardline,
"}"
])
)
);
}
return concat(parts);
case "TSEnumMember":
parts.push(path.call(print, "id"));
if (n.initializer) {
parts.push(" = ", path.call(print, "initializer"));
}
return concat(parts);
case "TSImportEqualsDeclaration":
parts.push(
printTypeScriptModifiers(path, options, print),
"import ",
path.call(print, "name"),
" = ",
path.call(print, "moduleReference")
);
if (options.semi) {
parts.push(";");
}
return group(concat(parts));
case "TSExternalModuleReference":
return concat(["require(", path.call(print, "expression"), ")"]);
case "TSModuleDeclaration": {
const parent = path.getParentNode();
const isExternalModule = isLiteral(n.id);
const parentIsDeclaration = parent.type === "TSModuleDeclaration";
2017-05-24 03:13:47 +03:00
const bodyIsDeclaration = n.body && n.body.type === "TSModuleDeclaration";
if (parentIsDeclaration) {
parts.push(".");
} else {
if (n.declare === true) {
parts.push("declare ");
}
parts.push(printTypeScriptModifiers(path, options, print));
// Global declaration looks like this:
// (declare)? global { ... }
const isGlobalDeclaration =
n.id.type === "Identifier" &&
n.id.name === "global" &&
!/namespace|module/.test(
options.originalText.slice(
options.locStart(n),
options.locStart(n.id)
)
);
if (!isGlobalDeclaration) {
parts.push(isExternalModule ? "module " : "namespace ");
}
}
parts.push(path.call(print, "id"));
if (bodyIsDeclaration) {
parts.push(path.call(print, "body"));
2017-05-24 03:13:47 +03:00
} else if (n.body) {
parts.push(
" {",
indent(
concat([
line,
path.call(
bodyPath =>
comments.printDanglingComments(bodyPath, options, true),
"body"
),
group(path.call(print, "body"))
])
),
line,
"}"
);
2017-05-24 03:13:47 +03:00
} else {
parts.push(semi);
}
return concat(parts);
}
case "TSModuleBlock":
return path.call(bodyPath => {
return printStatementSequence(bodyPath, options, print);
}, "body");
case "PrivateName":
return concat(["#", path.call(print, "id")]);
case "TSConditionalType":
return formatTernaryOperator(path, options, print, {
beforeParts: () => [
path.call(print, "checkType"),
" ",
"extends",
" ",
path.call(print, "extendsType")
],
shouldCheckJsx: false,
operatorName: "TSConditionalType",
consequentNode: "trueType",
alternateNode: "falseType",
2018-04-05 03:50:15 +03:00
testNode: "checkType",
breakNested: false
});
case "TSInferType":
return concat(["infer", " ", path.call(print, "typeParameter")]);
2017-01-13 23:03:53 +03:00
default:
/* istanbul ignore next */
2017-01-13 23:03:53 +03:00
throw new Error("unknown type: " + JSON.stringify(n.type));
}
}
function printStatementSequence(path, options, print) {
const printed = [];
2016-12-31 22:38:58 +03:00
const bodyNode = path.getNode();
const isClass = bodyNode.type === "ClassBody";
path.map((stmtPath, i) => {
const stmt = stmtPath.getValue();
2016-12-31 22:38:58 +03:00
2017-01-05 06:27:25 +03:00
// Just in case the AST has been modified to contain falsy
// "statements," it's safer simply to skip them.
/* istanbul ignore if */
2017-01-05 06:27:25 +03:00
if (!stmt) {
return;
}
2016-12-31 07:10:22 +03:00
2017-01-05 06:27:25 +03:00
// Skip printing EmptyStatement nodes to avoid leaving stray
// semicolons lying around.
if (stmt.type === "EmptyStatement") {
return;
}
2016-12-31 07:10:22 +03:00
2017-01-05 06:27:25 +03:00
const stmtPrinted = print(stmtPath);
const text = options.originalText;
2017-01-05 06:27:25 +03:00
const parts = [];
2016-12-31 22:38:58 +03:00
// in no-semi mode, prepend statement with semicolon if it might break ASI
// don't prepend the only JSX element in a program with semicolon
if (
!options.semi &&
!isClass &&
!isTheOnlyJSXElementInMarkdown(options, stmtPath) &&
stmtNeedsASIProtection(stmtPath, options)
) {
if (stmt.comments && stmt.comments.some(comment => comment.leading)) {
parts.push(print(stmtPath, { needsSemi: true }));
} else {
parts.push(";", stmtPrinted);
}
} else {
parts.push(stmtPrinted);
}
if (!options.semi && isClass) {
if (classPropMayCauseASIProblems(stmtPath)) {
parts.push(";");
} else if (stmt.type === "ClassProperty") {
const nextChild = bodyNode.body[i + 1];
if (classChildNeedsASIProtection(nextChild)) {
parts.push(";");
}
}
}
if (
sharedUtil.isNextLineEmpty(text, stmt, options) &&
!isLastStatement(stmtPath)
) {
2017-01-05 06:27:25 +03:00
parts.push(hardline);
}
2016-12-31 07:10:22 +03:00
2017-01-05 06:27:25 +03:00
printed.push(concat(parts));
});
2016-12-31 07:10:22 +03:00
return join(hardline, printed);
}
function printPropertyKey(path, options, print) {
const node = path.getNode();
const key = node.key;
if (
key.type === "Identifier" &&
!node.computed &&
options.parser === "json"
) {
// a -> "a"
return path.call(
keyPath =>
comments.printComments(
keyPath,
() => JSON.stringify(key.name),
options
),
"key"
);
}
if (
isStringLiteral(key) &&
isIdentifierName(key.value) &&
!node.computed &&
options.parser !== "json"
) {
2017-01-11 20:31:34 +03:00
// 'a' -> a
2017-04-05 23:10:10 +03:00
return path.call(
keyPath => comments.printComments(keyPath, () => key.value, options),
2017-04-05 23:10:10 +03:00
"key"
);
2017-01-11 08:48:49 +03:00
}
return path.call(print, "key");
}
function printMethod(path, options, print) {
const node = path.getNode();
const semi = options.semi ? ";" : "";
const kind = node.kind;
const parts = [];
2016-12-31 22:38:58 +03:00
if (node.type === "ObjectMethod" || node.type === "ClassMethod") {
node.value = node;
}
2016-12-31 07:10:22 +03:00
if (node.value.async) {
parts.push("async ");
}
2016-12-31 07:10:22 +03:00
if (!kind || kind === "init" || kind === "method" || kind === "constructor") {
if (node.value.generator) {
parts.push("*");
}
} else {
assert.ok(kind === "get" || kind === "set");
2016-12-31 07:10:22 +03:00
parts.push(kind, " ");
}
2016-12-31 07:10:22 +03:00
let key = printPropertyKey(path, options, print);
2016-12-31 22:38:58 +03:00
if (node.computed) {
2017-01-28 18:50:22 +03:00
key = concat(["[", key, "]"]);
}
2016-12-31 07:10:22 +03:00
2017-02-03 19:50:51 +03:00
parts.push(
key,
concat(
path.call(
valuePath => [
printFunctionTypeParameters(valuePath, options, print),
group(
concat([
printFunctionParams(valuePath, print, options),
printReturnType(valuePath, print, options)
])
)
],
"value"
)
)
2017-02-03 19:50:51 +03:00
);
2016-12-31 07:10:22 +03:00
if (!node.value.body || node.value.body.length === 0) {
parts.push(semi);
} else {
parts.push(" ", path.call(print, "value", "body"));
}
2017-01-09 21:47:02 +03:00
return concat(parts);
}
function couldGroupArg(arg) {
return (
(arg.type === "ObjectExpression" &&
(arg.properties.length > 0 || arg.comments)) ||
(arg.type === "ArrayExpression" &&
(arg.elements.length > 0 || arg.comments)) ||
arg.type === "TSTypeAssertionExpression" ||
arg.type === "TSAsExpression" ||
arg.type === "FunctionExpression" ||
(arg.type === "ArrowFunctionExpression" &&
(arg.body.type === "BlockStatement" ||
arg.body.type === "ArrowFunctionExpression" ||
arg.body.type === "ObjectExpression" ||
arg.body.type === "ArrayExpression" ||
arg.body.type === "CallExpression" ||
2017-11-30 06:09:45 +03:00
isJSXNode(arg.body)))
);
}
function shouldGroupLastArg(args) {
const lastArg = privateUtil.getLast(args);
const penultimateArg = privateUtil.getPenultimate(args);
return (
!hasLeadingComment(lastArg) &&
!hasTrailingComment(lastArg) &&
couldGroupArg(lastArg) &&
2017-03-01 20:37:02 +03:00
// If the last two arguments are of the same type,
// disable last element expansion.
(!penultimateArg || penultimateArg.type !== lastArg.type)
);
}
function shouldGroupFirstArg(args) {
if (args.length !== 2) {
return false;
}
const firstArg = args[0];
const secondArg = args[1];
return (
(!firstArg.comments || !firstArg.comments.length) &&
(firstArg.type === "FunctionExpression" ||
(firstArg.type === "ArrowFunctionExpression" &&
firstArg.body.type === "BlockStatement")) &&
!couldGroupArg(secondArg)
);
}
function printArgumentsList(path, options, print) {
const args = path.getValue().arguments;
if (args.length === 0) {
return concat([
"(",
comments.printDanglingComments(path, options, /* sameIndent */ true),
")"
]);
}
let anyArgEmptyLine = false;
let hasEmptyLineFollowingFirstArg = false;
const lastArgIndex = args.length - 1;
const printedArguments = path.map((argPath, index) => {
const arg = argPath.getNode();
const parts = [print(argPath)];
if (index === lastArgIndex) {
// do nothing
} else if (sharedUtil.isNextLineEmpty(options.originalText, arg, options)) {
if (index === 0) {
hasEmptyLineFollowingFirstArg = true;
}
anyArgEmptyLine = true;
parts.push(",", hardline, hardline);
} else {
parts.push(",", line);
}
return concat(parts);
}, "arguments");
const shouldGroupFirst = shouldGroupFirstArg(args);
const shouldGroupLast = shouldGroupLastArg(args);
if (shouldGroupFirst || shouldGroupLast) {
const shouldBreak =
(shouldGroupFirst
? printedArguments.slice(1).some(willBreak)
: printedArguments.slice(0, -1).some(willBreak)) || anyArgEmptyLine;
// We want to print the last argument with a special flag
let printedExpanded;
let i = 0;
path.each(argPath => {
if (shouldGroupFirst && i === 0) {
printedExpanded = [
concat([
argPath.call(p => print(p, { expandFirstArg: true })),
printedArguments.length > 1 ? "," : "",
hasEmptyLineFollowingFirstArg ? hardline : line,
hasEmptyLineFollowingFirstArg ? hardline : ""
])
].concat(printedArguments.slice(1));
}
if (shouldGroupLast && i === args.length - 1) {
printedExpanded = printedArguments
.slice(0, -1)
.concat(argPath.call(p => print(p, { expandLastArg: true })));
}
i++;
}, "arguments");
const somePrintedArgumentsWillBreak = printedArguments.some(willBreak);
const maybeTrailingComma = shouldPrintComma(options, "all") ? "," : "";
return concat([
somePrintedArgumentsWillBreak ? breakParent : "",
conditionalGroup(
[
concat([
ifBreak(
indent(concat(["(", softline, concat(printedExpanded)])),
concat(["(", concat(printedExpanded)])
),
somePrintedArgumentsWillBreak
? concat([ifBreak(maybeTrailingComma), softline])
: "",
")"
]),
shouldGroupFirst
? concat([
"(",
group(printedExpanded[0], { shouldBreak: true }),
concat(printedExpanded.slice(1)),
")"
])
: concat([
"(",
concat(printedArguments.slice(0, -1)),
group(privateUtil.getLast(printedExpanded), {
shouldBreak: true
}),
")"
]),
group(
concat([
"(",
indent(concat([line, concat(printedArguments)])),
maybeTrailingComma,
line,
")"
]),
{ shouldBreak: true }
)
],
{ shouldBreak }
)
]);
}
2017-01-24 21:54:01 +03:00
return group(
concat([
"(",
indent(concat([softline, concat(printedArguments)])),
ifBreak(shouldPrintComma(options, "all") ? "," : ""),
softline,
")"
]),
{ shouldBreak: printedArguments.some(willBreak) || anyArgEmptyLine }
);
}
function printTypeAnnotation(path, options, print) {
const node = path.getValue();
if (!node.typeAnnotation) {
return "";
}
const parentNode = path.getParentNode();
const isDefinite =
node.definite ||
(parentNode &&
parentNode.type === "VariableDeclarator" &&
parentNode.definite);
const isFunctionDeclarationIdentifier =
parentNode.type === "DeclareFunction" && parentNode.id === node;
if (
isFlowAnnotationComment(options.originalText, node.typeAnnotation, options)
) {
return concat([" /*: ", path.call(print, "typeAnnotation"), " */"]);
}
return concat([
isFunctionDeclarationIdentifier ? "" : isDefinite ? "!: " : ": ",
path.call(print, "typeAnnotation")
]);
}
function printFunctionTypeParameters(path, options, print) {
const fun = path.getValue();
2017-05-13 01:33:03 +03:00
if (fun.typeParameters) {
return path.call(print, "typeParameters");
}
return "";
}
function printFunctionParams(path, print, options, expandArg, printTypeParams) {
const fun = path.getValue();
const paramsField = fun.parameters ? "parameters" : "params";
const typeParams = printTypeParams
? printFunctionTypeParameters(path, options, print)
: "";
let printed = [];
if (fun[paramsField]) {
printed = path.map(print, paramsField);
}
2016-12-31 22:38:58 +03:00
if (fun.rest) {
2017-01-28 18:50:22 +03:00
printed.push(concat(["...", path.call(print, "rest")]));
}
2016-12-31 07:10:22 +03:00
if (printed.length === 0) {
return concat([
typeParams,
"(",
comments.printDanglingComments(
path,
options,
/* sameIndent */ true,
comment =>
privateUtil.getNextNonSpaceNonCommentCharacter(
options.originalText,
comment,
options.locEnd
) === ")"
),
")"
2017-03-01 20:37:02 +03:00
]);
}
const lastParam = privateUtil.getLast(fun[paramsField]);
// If the parent is a call with the first/last argument expansion and this is the
// params of the first/last argument, we dont want the arguments to break and instead
// want the whole expression to be on a new line.
//
// Good: Bad:
// verylongcall( verylongcall((
// (a, b) => { a,
// } b,
// }) ) => {
// })
if (
expandArg &&
!(fun[paramsField] && fun[paramsField].some(n => n.comments))
) {
return group(
concat([
docUtils.removeLines(typeParams),
"(",
join(", ", printed.map(docUtils.removeLines)),
")"
])
);
}
// Single object destructuring should hug
//
// function({
// a,
// b,
// c
// }) {}
if (shouldHugArguments(fun)) {
return concat([typeParams, "(", join(", ", printed), ")"]);
}
const parent = path.getParentNode();
2017-04-14 03:54:20 +03:00
// don't break in specs, eg; `it("should maintain parens around done even when long", (done) => {})`
if (parent.type === "CallExpression" && isTestCall(parent)) {
return concat([typeParams, "(", join(", ", printed), ")"]);
}
2017-04-14 03:54:20 +03:00
const flowTypeAnnotations = [
"AnyTypeAnnotation",
"NullLiteralTypeAnnotation",
"GenericTypeAnnotation",
"ThisTypeAnnotation",
"NumberTypeAnnotation",
"VoidTypeAnnotation",
"EmptyTypeAnnotation",
"MixedTypeAnnotation",
"BooleanTypeAnnotation",
"BooleanLiteralTypeAnnotation",
"StringTypeAnnotation"
];
const isFlowShorthandWithOneArg =
(isObjectTypePropertyAFunction(parent, options) ||
isTypeAnnotationAFunction(parent, options) ||
parent.type === "TypeAlias" ||
parent.type === "UnionTypeAnnotation" ||
parent.type === "TSUnionType" ||
parent.type === "IntersectionTypeAnnotation" ||
(parent.type === "FunctionTypeAnnotation" &&
parent.returnType === fun)) &&
fun[paramsField].length === 1 &&
fun[paramsField][0].name === null &&
2017-04-14 03:54:20 +03:00
fun[paramsField][0].typeAnnotation &&
fun.typeParameters === null &&
flowTypeAnnotations.indexOf(fun[paramsField][0].typeAnnotation.type) !==
-1 &&
!(
fun[paramsField][0].typeAnnotation.type === "GenericTypeAnnotation" &&
fun[paramsField][0].typeAnnotation.typeParameters
) &&
!fun.rest;
if (isFlowShorthandWithOneArg) {
if (options.arrowParens === "always") {
return concat(["(", concat(printed), ")"]);
}
return concat(printed);
}
const canHaveTrailingComma =
!(lastParam && lastParam.type === "RestElement") && !fun.rest;
return concat([
typeParams,
"(",
indent(concat([softline, join(concat([",", line]), printed)])),
2017-02-23 20:57:51 +03:00
ifBreak(
canHaveTrailingComma && shouldPrintComma(options, "all") ? "," : ""
),
softline,
")"
]);
}
function shouldPrintParamsWithoutParens(path, options) {
2017-11-28 07:52:49 +03:00
if (options.arrowParens === "always") {
return false;
}
2017-11-28 07:52:49 +03:00
if (options.arrowParens === "avoid") {
const node = path.getValue();
return canPrintParamsWithoutParens(node);
}
2017-11-28 07:52:49 +03:00
// Fallback default; should be unreachable
return false;
}
function canPrintParamsWithoutParens(node) {
return (
node.params.length === 1 &&
!node.rest &&
!node.typeParameters &&
!hasDanglingComments(node) &&
node.params[0].type === "Identifier" &&
!node.params[0].typeAnnotation &&
!node.params[0].comments &&
!node.params[0].optional &&
!node.predicate &&
!node.returnType
);
}
function printFunctionDeclaration(path, print, options) {
const n = path.getValue();
const parts = [];
if (n.async) {
parts.push("async ");
}
parts.push("function");
if (n.generator) {
parts.push("*");
}
if (n.id) {
parts.push(" ", path.call(print, "id"));
}
parts.push(
printFunctionTypeParameters(path, options, print),
group(
concat([
printFunctionParams(path, print, options),
printReturnType(path, print, options)
])
),
n.body ? " " : "",
path.call(print, "body")
);
return concat(parts);
}
function printObjectMethod(path, options, print) {
const objMethod = path.getValue();
const parts = [];
2016-12-31 22:38:58 +03:00
if (objMethod.async) {
parts.push("async ");
}
if (objMethod.generator) {
parts.push("*");
}
if (
objMethod.method ||
objMethod.kind === "get" ||
objMethod.kind === "set"
) {
return printMethod(path, options, print);
}
2016-12-31 07:10:22 +03:00
const key = printPropertyKey(path, options, print);
2016-12-31 22:38:58 +03:00
if (objMethod.computed) {
parts.push("[", key, "]");
} else {
parts.push(key);
}
2016-12-31 07:10:22 +03:00
parts.push(
printFunctionTypeParameters(path, options, print),
2017-01-24 21:54:01 +03:00
group(
2017-01-13 23:03:53 +03:00
concat([
printFunctionParams(path, print, options),
printReturnType(path, print, options)
2017-01-13 23:03:53 +03:00
])
),
" ",
path.call(print, "body")
);
2016-12-31 07:10:22 +03:00
2017-01-09 21:47:02 +03:00
return concat(parts);
}
function printReturnType(path, print, options) {
2016-12-30 21:32:43 +03:00
const n = path.getValue();
const returnType = path.call(print, "returnType");
if (
n.returnType &&
isFlowAnnotationComment(options.originalText, n.returnType, options)
) {
return concat([" /*: ", returnType, " */"]);
}
const parts = [returnType];
2016-12-31 22:38:58 +03:00
// prepend colon to TypeScript type annotation
if (n.returnType && n.returnType.typeAnnotation) {
parts.unshift(": ");
}
if (n.predicate) {
2016-12-31 07:10:22 +03:00
// The return type will already add the colon, but otherwise we
// need to do it ourselves
2017-01-09 20:09:04 +03:00
parts.push(n.returnType ? " " : ": ", path.call(print, "predicate"));
2016-12-30 21:32:43 +03:00
}
2016-12-31 07:10:22 +03:00
2016-12-30 21:32:43 +03:00
return concat(parts);
}
function printExportDeclaration(path, options, print) {
const decl = path.getValue();
const semi = options.semi ? ";" : "";
const parts = ["export "];
2016-12-31 22:38:58 +03:00
const isDefault = decl["default"] || decl.type === "ExportDefaultDeclaration";
if (isDefault) {
parts.push("default ");
}
2016-12-31 07:10:22 +03:00
2017-02-23 20:57:51 +03:00
parts.push(
comments.printDanglingComments(path, options, /* sameIndent */ true)
);
if (needsHardlineAfterDanglingComment(decl)) {
parts.push(hardline);
}
if (decl.declaration) {
parts.push(path.call(print, "declaration"));
2017-01-13 23:03:53 +03:00
if (
isDefault &&
2017-02-16 06:56:11 +03:00
(decl.declaration.type !== "ClassDeclaration" &&
decl.declaration.type !== "FunctionDeclaration" &&
decl.declaration.type !== "TSAbstractClassDeclaration" &&
decl.declaration.type !== "TSInterfaceDeclaration" &&
decl.declaration.type !== "DeclareClass" &&
decl.declaration.type !== "DeclareFunction")
2017-01-13 23:03:53 +03:00
) {
parts.push(semi);
}
} else {
if (decl.specifiers && decl.specifiers.length > 0) {
const specifiers = [];
const defaultSpecifiers = [];
const namespaceSpecifiers = [];
path.each(specifierPath => {
const specifierType = path.getValue().type;
if (specifierType === "ExportSpecifier") {
specifiers.push(print(specifierPath));
} else if (specifierType === "ExportDefaultSpecifier") {
defaultSpecifiers.push(print(specifierPath));
} else if (specifierType === "ExportNamespaceSpecifier") {
namespaceSpecifiers.push(concat(["* as ", print(specifierPath)]));
}
}, "specifiers");
const isNamespaceFollowed =
2017-12-02 01:39:10 +03:00
namespaceSpecifiers.length !== 0 && specifiers.length !== 0;
const isDefaultFollowed =
2017-12-02 01:39:10 +03:00
defaultSpecifiers.length !== 0 &&
(namespaceSpecifiers.length !== 0 || specifiers.length !== 0);
parts.push(
decl.exportKind === "type" ? "type " : "",
concat(defaultSpecifiers),
concat([isDefaultFollowed ? ", " : ""]),
2017-12-02 01:39:10 +03:00
concat(namespaceSpecifiers),
concat([isNamespaceFollowed ? ", " : ""]),
specifiers.length !== 0
? group(
concat([
"{",
indent(
concat([
options.bracketSpacing ? line : softline,
join(concat([",", line]), specifiers)
])
),
ifBreak(shouldPrintComma(options) ? "," : ""),
options.bracketSpacing ? line : softline,
"}"
])
)
: ""
);
2017-01-09 20:09:04 +03:00
} else {
parts.push("{}");
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-09 20:09:04 +03:00
if (decl.source) {
parts.push(" from ", path.call(print, "source"));
}
parts.push(semi);
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
return concat(parts);
}
function printFlowDeclaration(path, parts) {
const parentExportDecl = privateUtil.getParentExportDeclaration(path);
2016-12-31 22:38:58 +03:00
if (parentExportDecl) {
assert.strictEqual(parentExportDecl.type, "DeclareExportDeclaration");
} else {
// If the parent node has type DeclareExportDeclaration, then it
// will be responsible for printing the "declare" token. Otherwise
// it needs to be printed with this non-exported declaration node.
parts.unshift("declare ");
}
2016-12-31 07:10:22 +03:00
return concat(parts);
}
function getFlowVariance(path) {
if (!path.variance) {
return null;
}
// Babylon 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) {
return "";
}
return concat([join(" ", path.map(print, "modifiers")), " "]);
}
function printTypeParameters(path, options, print, paramsKey) {
2017-05-13 01:33:03 +03:00
const n = path.getValue();
2017-05-13 01:33:03 +03:00
if (!n[paramsKey]) {
return "";
}
// for TypeParameterDeclaration typeParameters is a single node
if (!Array.isArray(n[paramsKey])) {
return path.call(print, paramsKey);
}
const grandparent = path.getNode(2);
const isParameterInTestCall =
grandparent != null &&
grandparent.type === "CallExpression" &&
isTestCall(grandparent);
2017-05-13 01:33:03 +03:00
const shouldInline =
isParameterInTestCall ||
n[paramsKey].length === 0 ||
(n[paramsKey].length === 1 &&
(shouldHugType(n[paramsKey][0]) ||
(n[paramsKey][0].type === "GenericTypeAnnotation" &&
shouldHugType(n[paramsKey][0].id)) ||
(n[paramsKey][0].type === "TSTypeReference" &&
shouldHugType(n[paramsKey][0].typeName)) ||
n[paramsKey][0].type === "NullableTypeAnnotation"));
2017-05-13 01:33:03 +03:00
if (shouldInline) {
return concat(["<", join(", ", path.map(print, paramsKey)), ">"]);
}
2017-05-13 01:33:03 +03:00
return group(
concat([
"<",
indent(
concat([
softline,
join(concat([",", line]), path.map(print, paramsKey))
])
),
ifBreak(
options.parser !== "typescript" && shouldPrintComma(options, "all")
? ","
: ""
),
2017-05-13 01:33:03 +03:00
softline,
">"
])
);
}
function printClass(path, options, print) {
const n = path.getValue();
const parts = [];
if (n.type === "TSAbstractClassDeclaration") {
parts.push("abstract ");
}
parts.push("class");
2016-12-31 22:38:58 +03:00
if (n.id) {
parts.push(" ", path.call(print, "id"));
}
2016-12-31 07:10:22 +03:00
parts.push(path.call(print, "typeParameters"));
const partsGroup = [];
if (n.superClass) {
const printed = concat([
"extends ",
2017-01-09 20:09:04 +03:00
path.call(print, "superClass"),
path.call(print, "superTypeParameters")
]);
// Keep old behaviour of extends in same line
// If there is only on extends and there are not comments
if (
(!n.implements || n.implements.length === 0) &&
(!n.superClass.comments || n.superClass.comments.length === 0)
) {
parts.push(
concat([
" ",
path.call(
superClass =>
comments.printComments(superClass, () => printed, options),
"superClass"
)
])
);
} else {
partsGroup.push(
group(
concat([
line,
path.call(
superClass =>
comments.printComments(superClass, () => printed, options),
"superClass"
)
])
)
);
}
2017-01-09 20:09:04 +03:00
} else if (n.extends && n.extends.length > 0) {
parts.push(" extends ", join(", ", path.map(print, "extends")));
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
2018-04-17 05:28:56 +03:00
if (n["mixins"] && n["mixins"].length > 0) {
partsGroup.push(
line,
"mixins ",
group(indent(join(concat([",", line]), path.map(print, "mixins"))))
);
}
if (n["implements"] && n["implements"].length > 0) {
2017-01-28 18:50:22 +03:00
partsGroup.push(
line,
"implements",
group(
indent(
concat([
line,
join(concat([",", line]), path.map(print, "implements"))
])
)
)
2017-01-28 18:50:22 +03:00
);
}
if (partsGroup.length > 0) {
parts.push(group(indent(concat(partsGroup))));
}
2016-12-31 07:10:22 +03:00
if (
n.body &&
n.body.comments &&
hasLeadingOwnLineComment(options.originalText, n.body, options)
) {
parts.push(hardline);
} else {
parts.push(" ");
}
parts.push(path.call(print, "body"));
2016-12-31 07:10:22 +03:00
return parts;
}
function printOptionalToken(path) {
const node = path.getValue();
if (!node.optional) {
return "";
}
if (
node.type === "CallExpression" ||
(node.type === "MemberExpression" && node.computed)
) {
return "?.";
}
return "?";
}
function printMemberLookup(path, options, print) {
const property = path.call(print, "property");
const n = path.getValue();
const optional = printOptionalToken(path);
if (!n.computed) {
return concat([optional, ".", property]);
}
if (!n.property || isNumericLiteral(n.property)) {
return concat([optional, "[", property, "]"]);
}
return group(
concat([optional, "[", indent(concat([softline, property])), softline, "]"])
2017-01-28 18:50:22 +03:00
);
}
function printBindExpressionCallee(path, options, print) {
return concat(["::", path.call(print, "callee")]);
}
// We detect calls on member expressions specially to format a
// common pattern better. The pattern we are looking for is this:
//
// arr
// .map(x => x + 1)
// .filter(x => x > 10)
// .some(x => x % 2)
//
// The way it is structured in the AST is via a nested sequence of
// MemberExpression and CallExpression. We need to traverse the AST
// and make groups out of it to print it in the desired way.
function printMemberChain(path, options, print) {
// The first phase is to linearize the AST by traversing it down.
//
// a().b()
// has the following AST structure:
// CallExpression(MemberExpression(CallExpression(Identifier)))
// and we transform it into
// [Identifier, CallExpression, MemberExpression, CallExpression]
const printedNodes = [];
// Here we try to retain one typed empty line after each call expression or
// the first group whether it is in parentheses or not
function shouldInsertEmptyLineAfter(node) {
const originalText = options.originalText;
const nextCharIndex = sharedUtil.getNextNonSpaceNonCommentCharacterIndex(
originalText,
node,
options
);
const nextChar = originalText.charAt(nextCharIndex);
// if it is cut off by a parenthesis, we only account for one typed empty
// line after that parenthesis
if (nextChar == ")") {
return sharedUtil.isNextLineEmptyAfterIndex(
originalText,
nextCharIndex + 1,
options
);
}
return sharedUtil.isNextLineEmpty(originalText, node, options);
}
function rec(path) {
const node = path.getValue();
if (
node.type === "CallExpression" &&
(isMemberish(node.callee) || node.callee.type === "CallExpression")
) {
printedNodes.unshift({
node: node,
printed: concat([
comments.printComments(
path,
() =>
concat([
printOptionalToken(path),
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
]),
options
),
shouldInsertEmptyLineAfter(node) ? hardline : ""
])
});
path.call(callee => rec(callee), "callee");
} else if (isMemberish(node)) {
printedNodes.unshift({
node: node,
2017-02-03 19:50:51 +03:00
printed: comments.printComments(
path,
() =>
node.type === "MemberExpression"
? printMemberLookup(path, options, print)
: printBindExpressionCallee(path, options, print),
2017-02-03 19:50:51 +03:00
options
)
});
path.call(object => rec(object), "object");
} else if (node.type === "TSNonNullExpression") {
printedNodes.unshift({
node: node,
printed: comments.printComments(path, () => "!", options)
});
path.call(expression => rec(expression), "expression");
} else {
printedNodes.unshift({
node: node,
printed: path.call(print)
});
}
}
// Note: the comments of the root node have already been printed, so we
// need to extract this first call without printing them as they would
// if handled inside of the recursive call.
const node = path.getValue();
printedNodes.unshift({
node,
printed: concat([
printOptionalToken(path),
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
])
});
path.call(callee => rec(callee), "callee");
// Once we have a linear list of printed nodes, we want to create groups out
// of it.
//
// a().b.c().d().e
// will be grouped as
// [
// [Identifier, CallExpression],
// [MemberExpression, MemberExpression, CallExpression],
// [MemberExpression, CallExpression],
// [MemberExpression],
// ]
// so that we can print it as
// a()
// .b.c()
// .d()
// .e
// The first group is the first node followed by
// - as many CallExpression as possible
// < fn()()() >.something()
// - as many array acessors as possible
// < fn()[0][1][2] >.something()
// - then, as many MemberExpression as possible but the last one
// < this.items >.something()
const groups = [];
let currentGroup = [printedNodes[0]];
let i = 1;
for (; i < printedNodes.length; ++i) {
if (
printedNodes[i].node.type === "TSNonNullExpression" ||
printedNodes[i].node.type === "CallExpression" ||
(printedNodes[i].node.type === "MemberExpression" &&
printedNodes[i].node.computed &&
isNumericLiteral(printedNodes[i].node.property))
) {
currentGroup.push(printedNodes[i]);
2017-01-09 20:09:04 +03:00
} else {
break;
}
}
if (printedNodes[0].node.type !== "CallExpression") {
for (; i + 1 < printedNodes.length; ++i) {
if (
isMemberish(printedNodes[i].node) &&
isMemberish(printedNodes[i + 1].node)
) {
currentGroup.push(printedNodes[i]);
} else {
break;
}
}
}
groups.push(currentGroup);
currentGroup = [];
// Then, each following group is a sequence of MemberExpression followed by
// a sequence of CallExpression. To compute it, we keep adding things to the
// group until we has seen a CallExpression in the past and reach a
// MemberExpression
let hasSeenCallExpression = false;
for (; i < printedNodes.length; ++i) {
if (hasSeenCallExpression && isMemberish(printedNodes[i].node)) {
// [0] should be appended at the end of the group instead of the
// beginning of the next one
if (
printedNodes[i].node.computed &&
isNumericLiteral(printedNodes[i].node.property)
) {
currentGroup.push(printedNodes[i]);
continue;
}
groups.push(currentGroup);
currentGroup = [];
hasSeenCallExpression = false;
}
if (printedNodes[i].node.type === "CallExpression") {
hasSeenCallExpression = true;
}
currentGroup.push(printedNodes[i]);
if (
printedNodes[i].node.comments &&
printedNodes[i].node.comments.some(comment => comment.trailing)
) {
groups.push(currentGroup);
currentGroup = [];
hasSeenCallExpression = false;
}
}
if (currentGroup.length > 0) {
groups.push(currentGroup);
}
// There are cases like Object.keys(), Observable.of(), _.values() where
// they are the subject of all the chained calls and therefore should
// be kept on the same line:
//
// Object.keys(items)
// .filter(x => x)
// .map(x => x)
//
// In order to detect those cases, we use an heuristic: if the first
// node is an identifier with the name starting with a capital letter,
// or shorter than tabWidth. The rationale is that they are likely
// to be factories.
function isNoWrap(name) {
return name.match(/(^[A-Z])/) || name.length <= options.tabWidth;
}
const shouldMerge =
groups.length >= 2 &&
!groups[1][0].node.comments &&
((groups[0].length === 1 &&
(groups[0][0].node.type === "ThisExpression" ||
(groups[0][0].node.type === "Identifier" &&
(isNoWrap(groups[0][0].node.name) ||
(groups[1].length && groups[1][0].node.computed))))) ||
(groups[0].length > 1 &&
groups[0][groups[0].length - 1].node.type === "MemberExpression" &&
groups[0][groups[0].length - 1].node.property.type === "Identifier" &&
(isNoWrap(groups[0][groups[0].length - 1].node.property.name) ||
(groups[1].length && groups[1][0].node.computed))));
function printGroup(printedGroup) {
return concat(printedGroup.map(tuple => tuple.printed));
}
function printIndentedGroup(groups) {
if (groups.length === 0) {
return "";
}
return indent(
group(concat([hardline, join(hardline, groups.map(printGroup))]))
);
}
const printedGroups = groups.map(printGroup);
const oneLine = concat(printedGroups);
const cutoff = shouldMerge ? 3 : 2;
const flatGroups = groups
.slice(0, cutoff)
2017-05-24 03:13:01 +03:00
.reduce((res, group) => res.concat(group), []);
const hasComment =
flatGroups.slice(1, -1).some(node => hasLeadingComment(node.node)) ||
flatGroups.slice(0, -1).some(node => hasTrailingComment(node.node)) ||
(groups[cutoff] && hasLeadingComment(groups[cutoff][0].node));
// If we only have a single `.`, we shouldn't do anything fancy and just
// render everything concatenated together.
2017-09-10 23:17:45 +03:00
if (groups.length <= cutoff && !hasComment) {
return group(oneLine);
}
// Find out the last node in the first group and check if it has an
// empty line after
const lastNodeBeforeIndent = privateUtil.getLast(
shouldMerge ? groups.slice(1, 2)[0] : groups[0]
).node;
const shouldHaveEmptyLineBeforeIndent =
lastNodeBeforeIndent.type !== "CallExpression" &&
shouldInsertEmptyLineAfter(lastNodeBeforeIndent);
const expanded = concat([
printGroup(groups[0]),
shouldMerge ? concat(groups.slice(1, 2).map(printGroup)) : "",
shouldHaveEmptyLineBeforeIndent ? hardline : "",
printIndentedGroup(groups.slice(shouldMerge ? 2 : 1))
]);
const callExpressionCount = printedNodes.filter(
tuple => tuple.node.type === "CallExpression"
).length;
// We don't want to print in one line if there's:
// * A comment.
// * 3 or more chained calls.
// * Any group but the last one has a hard line.
// If the last group is a function it's okay to inline if it fits.
if (
hasComment ||
callExpressionCount >= 3 ||
printedGroups.slice(0, -1).some(willBreak)
) {
return group(expanded);
}
return concat([
// We only need to check `oneLine` because if `expanded` is chosen
// that means that the parent group has already been broken
// naturally
willBreak(oneLine) || shouldHaveEmptyLineBeforeIndent ? breakParent : "",
conditionalGroup([oneLine, expanded])
]);
}
2017-11-30 06:09:45 +03:00
function isJSXNode(node) {
return (
node.type === "JSXElement" ||
node.type === "JSXFragment" ||
node.type === "TSJsxFragment"
);
}
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) {
2017-11-30 06:09:45 +03:00
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" : <span className="warning">Unknown</span>;
//
// 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
);
}
// JSX Children are strange, mostly for two reasons:
// 1. JSX reads newlines into string values, instead of skipping them like JS
// 2. up to one whitespace between elements within a line is significant,
// but not between lines.
//
// Leading, trailing, and lone whitespace all need to
// turn themselves into the rather ugly `{' '}` when breaking.
//
// We print JSX using the `fill` doc primitive.
// This requires that we give it an array of alternating
// content and whitespace elements.
// To ensure this we add dummy `""` content elements as needed.
function printJSXChildren(path, options, print, jsxWhitespace) {
const n = path.getValue();
const children = [];
// using `map` instead of `each` because it provides `i`
path.map((childPath, i) => {
const child = childPath.getValue();
if (isLiteral(child)) {
const text = rawText(child);
// Contains a non-whitespace character
if (isMeaningfulJSXText(child)) {
const words = text.split(matchJsxWhitespaceRegex);
// Starts with whitespace
if (words[0] === "") {
children.push("");
words.shift();
if (/\n/.test(words[0])) {
children.push(hardline);
} else {
children.push(jsxWhitespace);
}
words.shift();
}
let endWhitespace;
// Ends with whitespace
if (privateUtil.getLast(words) === "") {
words.pop();
endWhitespace = words.pop();
}
// This was whitespace only without a new line.
if (words.length === 0) {
return;
}
words.forEach((word, i) => {
if (i % 2 === 1) {
children.push(line);
} else {
children.push(word);
}
});
if (endWhitespace !== undefined) {
if (/\n/.test(endWhitespace)) {
children.push(hardline);
} else {
children.push(jsxWhitespace);
}
} else {
// Ideally this would be a `hardline` to allow a break between
// tags and text.
// Unfortunately Facebook have a custom translation pipeline
// (https://github.com/prettier/prettier/issues/1581#issuecomment-300975032)
// that uses the JSX syntax, but does not follow the React whitespace
// rules.
// Ensuring that we never have a break between tags and text in JSX
// will allow Facebook to adopt Prettier without too much of an
// adverse effect on formatting algorithm.
children.push("");
}
} else if (/\n/.test(text)) {
// Keep (up to one) blank line between tags/expressions/text.
// Note: We don't keep blank lines between text elements.
if (text.match(/\n/g).length > 1) {
children.push("");
children.push(hardline);
}
} else {
children.push("");
children.push(jsxWhitespace);
}
} else {
const printedChild = print(childPath);
children.push(printedChild);
const next = n.children[i + 1];
const directlyFollowedByMeaningfulText =
next && isMeaningfulJSXText(next) && !/^[ \n\r\t]/.test(rawText(next));
if (directlyFollowedByMeaningfulText) {
// Potentially this could be a hardline as well.
// See the comment above about the Facebook translation pipeline as
// to why this is an empty string.
children.push("");
} else {
children.push(hardline);
}
}
}, "children");
return children;
}
// JSX expands children from the inside-out, instead of the outside-in.
// This is both to break children before attributes,
// and to ensure that when children break, their parents do as well.
//
// Any element that is written without any newlines and fits on a single line
// is left that way.
// Not only that, any user-written-line containing multiple JSX siblings
// should also be kept on one line if possible,
// so each user-written-line is wrapped in its own group.
//
// Elements that contain newlines or don't fit on a single line (recursively)
// are fully-split, using hardline and shouldBreak: true.
//
// To support that case properly, all leading and trailing spaces
// are stripped from the list of children, and replaced with a single hardline.
function printJSXElement(path, options, print) {
const n = path.getValue();
// Turn <div></div> into <div />
2017-11-30 06:09:45 +03:00
if (n.type === "JSXElement" && isEmptyJSXElement(n)) {
n.openingElement.selfClosing = true;
2017-11-30 06:09:45 +03:00
return path.call(print, "openingElement");
}
2017-11-30 06:09:45 +03:00
const openingLines =
n.type === "JSXElement"
? path.call(print, "openingElement")
: path.call(print, "openingFragment");
const closingLines =
n.type === "JSXElement"
? path.call(print, "closingElement")
: path.call(print, "closingFragment");
if (
n.children.length === 1 &&
n.children[0].type === "JSXExpressionContainer" &&
(n.children[0].expression.type === "TemplateLiteral" ||
n.children[0].expression.type === "TaggedTemplateExpression")
) {
return concat([
openingLines,
concat(path.map(print, "children")),
closingLines
]);
}
// Convert `{" "}` to text nodes containing a space.
// This makes it easy to turn them into `jsxWhitespace` which
// can then print as either a space or `{" "}` when breaking.
n.children = n.children.map(child => {
if (isJSXWhitespaceExpression(child)) {
return {
type: "JSXText",
value: " ",
raw: " "
};
}
return child;
});
2017-11-30 06:09:45 +03:00
const containsTag = n.children.filter(isJSXNode).length > 0;
const containsMultipleExpressions =
n.children.filter(child => child.type === "JSXExpressionContainer").length >
1;
2017-11-30 06:09:45 +03:00
const containsMultipleAttributes =
n.type === "JSXElement" && n.openingElement.attributes.length > 1;
// Record any breaks. Should never go from true to false, only false to true.
let forcedBreak =
willBreak(openingLines) ||
containsTag ||
containsMultipleAttributes ||
containsMultipleExpressions;
const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}';
const jsxWhitespace = ifBreak(concat([rawJsxWhitespace, softline]), " ");
const children = printJSXChildren(path, options, print, jsxWhitespace);
const containsText =
n.children.filter(child => isMeaningfulJSXText(child)).length > 0;
// We can end up we multiple whitespace elements with empty string
// content between them.
// We need to remove empty whitespace and softlines before JSX whitespace
// to get the correct output.
for (let i = children.length - 2; i >= 0; i--) {
const isPairOfEmptyStrings = children[i] === "" && children[i + 1] === "";
const isPairOfHardlines =
children[i] === hardline &&
children[i + 1] === "" &&
children[i + 2] === hardline;
const isLineFollowedByJSXWhitespace =
(children[i] === softline || children[i] === hardline) &&
children[i + 1] === "" &&
children[i + 2] === jsxWhitespace;
const isJSXWhitespaceFollowedByLine =
children[i] === jsxWhitespace &&
children[i + 1] === "" &&
(children[i + 2] === softline || children[i + 2] === hardline);
2017-10-03 22:51:18 +03:00
const isDoubleJSXWhitespace =
children[i] === jsxWhitespace &&
children[i + 1] === "" &&
children[i + 2] === jsxWhitespace;
if (
(isPairOfHardlines && containsText) ||
isPairOfEmptyStrings ||
2017-10-03 22:51:18 +03:00
isLineFollowedByJSXWhitespace ||
isDoubleJSXWhitespace
) {
children.splice(i, 2);
} else if (isJSXWhitespaceFollowedByLine) {
children.splice(i + 1, 2);
}
}
// Trim trailing lines (or empty strings)
while (
children.length &&
(isLineNext(privateUtil.getLast(children)) ||
isEmpty(privateUtil.getLast(children)))
) {
children.pop();
}
// Trim leading lines (or empty strings)
while (
children.length &&
(isLineNext(children[0]) || isEmpty(children[0])) &&
(isLineNext(children[1]) || isEmpty(children[1]))
) {
children.shift();
children.shift();
}
// Tweak how we format children if outputting this element over multiple lines.
// Also detect whether we will force this element to output over multiple lines.
const multilineChildren = [];
children.forEach((child, i) => {
// There are a number of situations where we need to ensure we display
// whitespace as `{" "}` when outputting this element over multiple lines.
if (child === jsxWhitespace) {
if (i === 1 && children[i - 1] === "") {
if (children.length === 2) {
// Solitary whitespace
multilineChildren.push(rawJsxWhitespace);
return;
}
// Leading whitespace
multilineChildren.push(concat([rawJsxWhitespace, hardline]));
return;
} else if (i === children.length - 1) {
// Trailing whitespace
multilineChildren.push(rawJsxWhitespace);
return;
} else if (children[i - 1] === "" && children[i - 2] === hardline) {
// Whitespace after line break
multilineChildren.push(rawJsxWhitespace);
return;
}
}
multilineChildren.push(child);
if (willBreak(child)) {
forcedBreak = true;
}
});
// If there is text we use `fill` to fit as much onto each line as possible.
// When there is no text (just tags and expressions) we use `group`
// to output each on a separate line.
const content = containsText
? fill(multilineChildren)
: group(concat(multilineChildren), { shouldBreak: true });
const multiLineElem = group(
concat([
openingLines,
indent(concat([hardline, content])),
hardline,
closingLines
])
);
if (forcedBreak) {
return multiLineElem;
}
return conditionalGroup([
group(concat([openingLines, concat(children), closingLines])),
multiLineElem
]);
}
function maybeWrapJSXElementInParens(path, elem) {
const parent = path.getParentNode();
if (!parent) {
return elem;
}
const NO_WRAP_PARENTS = {
ArrayExpression: true,
JSXAttribute: true,
JSXElement: true,
JSXExpressionContainer: true,
2017-11-30 06:09:45 +03:00
JSXFragment: true,
TSJsxFragment: true,
ExpressionStatement: true,
CallExpression: true,
ConditionalExpression: true
};
if (NO_WRAP_PARENTS[parent.type]) {
return elem;
}
2017-01-24 21:54:01 +03:00
return group(
2017-01-13 23:03:53 +03:00
concat([
ifBreak("("),
indent(concat([softline, elem])),
2017-01-13 23:03:53 +03:00
softline,
ifBreak(")")
])
);
}
function isBinaryish(node) {
return node.type === "BinaryExpression" || node.type === "LogicalExpression";
}
function isMemberish(node) {
return (
node.type === "MemberExpression" ||
(node.type === "BindExpression" && node.object)
);
}
function shouldInlineLogicalExpression(node) {
if (node.type !== "LogicalExpression") {
return false;
}
if (
node.right.type === "ObjectExpression" &&
node.right.properties.length !== 0
) {
return true;
}
if (
node.right.type === "ArrayExpression" &&
node.right.elements.length !== 0
) {
return true;
}
2017-11-30 06:09:45 +03:00
if (isJSXNode(node.right)) {
return true;
}
return false;
}
// For binary expressions to be consistent, we need to group
// subsequent operators with the same precedence level under a single
// group. Otherwise they will be nested such that some of them break
// onto new lines but not all. Operators with the same precedence
// level should either all break or not. Because we group them by
// precedence level and the AST is structured based on precedence
// level, things are naturally broken up correctly, i.e. `&&` is
// broken before `+`.
function printBinaryishExpressions(
path,
print,
options,
isNested,
isInsideParenthesis
) {
let parts = [];
const node = path.getValue();
// We treat BinaryExpression and LogicalExpression nodes the same.
if (isBinaryish(node)) {
// Put all operators with the same precedence level in the same
// group. The reason we only need to do this with the `left`
// expression is because given an expression like `1 + 2 - 3`, it
// is always parsed like `((1 + 2) - 3)`, meaning the `left` side
// is where the rest of the expression will exist. Binary
// expressions on the right side mean they have a difference
// precedence level and should be treated as a separate group, so
// print them normally. (This doesn't hold for the `**` operator,
// which is unique in that it is right-associative.)
if (privateUtil.shouldFlatten(node.operator, node.left.operator)) {
// Flatten them out by recursively calling this function.
parts = parts.concat(
path.call(
left =>
printBinaryishExpressions(
left,
print,
options,
/* isNested */ true,
isInsideParenthesis
),
"left"
)
);
} else {
parts.push(path.call(print, "left"));
}
const shouldInline = shouldInlineLogicalExpression(node);
const lineBeforeOperator = node.operator === "|>";
const right = shouldInline
? concat([node.operator, " ", path.call(print, "right")])
: concat([
lineBeforeOperator ? softline : "",
node.operator,
lineBeforeOperator ? " " : line,
path.call(print, "right")
]);
// If there's only a single binary expression, we want to create a group
// in order to avoid having a small right part like -1 be on its own line.
const parent = path.getParentNode();
const shouldGroup =
!(isInsideParenthesis && node.type === "LogicalExpression") &&
parent.type !== node.type &&
node.left.type !== node.type &&
node.right.type !== node.type;
parts.push(" ", shouldGroup ? group(right) : right);
// The root comments are already printed, but we need to manually print
// the other ones since we don't call the normal print on BinaryExpression,
// only for the left and right parts
if (isNested && node.comments) {
parts = comments.printComments(path, () => concat(parts), options);
}
} else {
// Our stopping case. Simply print the node normally.
parts.push(path.call(print));
}
return parts;
}
function printAssignmentRight(rightNode, printedRight, canBreak, options) {
if (hasLeadingOwnLineComment(options.originalText, rightNode, options)) {
return indent(concat([hardline, printedRight]));
}
if (canBreak) {
return indent(concat([line, printedRight]));
}
return concat([" ", printedRight]);
}
function printAssignment(
leftNode,
printedLeft,
operator,
rightNode,
printedRight,
options
) {
if (!rightNode) {
return printedLeft;
}
const canBreak =
(isBinaryish(rightNode) && !shouldInlineLogicalExpression(rightNode)) ||
(rightNode.type === "ConditionalExpression" &&
isBinaryish(rightNode.test) &&
!shouldInlineLogicalExpression(rightNode.test)) ||
((leftNode.type === "Identifier" ||
isStringLiteral(leftNode) ||
leftNode.type === "MemberExpression") &&
(isStringLiteral(rightNode) || isMemberExpressionChain(rightNode)));
const printed = printAssignmentRight(
rightNode,
printedRight,
canBreak,
options
);
return group(concat([printedLeft, operator, printed]));
}
function adjustClause(node, clause, forceSpace) {
if (node.type === "EmptyStatement") {
return ";";
}
if (node.type === "BlockStatement" || forceSpace) {
2017-01-28 18:50:22 +03:00
return concat([" ", clause]);
}
2016-12-31 07:10:22 +03:00
return indent(concat([line, clause]));
}
function nodeStr(node, options, isFlowOrTypeScriptDirectiveLiteral) {
const raw = rawText(node);
Preserve code unit sequence of directive literals (#1571) * Print directive literals verbatim This addresses https://github.com/prettier/prettier/issues/1555, but doesn't seem to pass the AST_COMPARE=1 tests: AST_COMPARE=1 npm test -- tests/quotes -t strings However, running `prettier --debug-check` on the relevant file *does* work: prettier tests/quotes/strings.js --debug-check * Change directive literal quotes if it doesn't contain quotes This addresses https://github.com/prettier/prettier/pull/1560#discussion_r115396257 From https://github.com/prettier/prettier/issues/1555#issue-227206837: > It's okay to change the type of quotation marks used, but only if doing so does not require changing any characters within the directive. * Don't change directive literal quotes if it contains a backslash This passes the `--debug-check` tests again: prettier tests/quotes/strings.js --debug-check * Try to add regression test for escaped directive literals This seems not to work, despite the following command having the correct output: echo "'\''" | prettier You can use the following to get an idea of how flow/typescript parse this: node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'flow'}), null, 2)" node -p "JSON.stringify(require('./src/parser').parse('\\'\\\\\'\\'', {parser: 'typescript'}), null, 2)" * WIP Disable Flow/Typescript for ./tests/directives We don't yet handle escaped directives for them, but Babylon works. (similar to https://github.com/prettier/prettier/pull/602/commits/90bf93713c78a6a6b3f55e52d7be172ece9b56df#diff-0de18284f37da79ab8af4e4690919abaR1) * Revert "WIP Disable Flow/Typescript for ./tests/directives" This reverts commit 2aba6231271f6985a395c31e3df9323e8f3da115. * Prevent test strings from being parsed as directives See https://github.com/prettier/prettier/pull/1560#issue-227225960 * Add more escaped directive tests * Infer DirectiveLiterals from Flow parser * Don't test TypeScript on directives See https://github.com/prettier/prettier/pull/1560#issuecomment-300296221 * fixup! Infer DirectiveLiterals from Flow parser * Don't fake objects that look like a DirectiveLiteral Instead, add a flag to nodeStr() that deals with the Flow node accordingly. See https://github.com/prettier/prettier/pull/1560#discussion_r115605758 * Print preferred quotes around escaped DirectiveLiteral when it doesn't contain quotes See https://github.com/prettier/prettier/pull/1560#discussion_r115606122 * Simplify `canChangeDirectiveQuotes` logic * Add directive test with unnecessarily escaped non-quote character * Fix boolean logic error I thought that this would result in the following if-block executing, as needed to pass the test case in the previous commit. However, it appears that it's not actually needed to pass the test case, since `makeString` doesn't unescape unnecessarily escaped non-quote characters. Nevertheless, I think we should leave that if-block (`if (canChangeDirectiveQuotes)`) there, in case `makeString` is updated. See https://github.com/prettier/prettier/pull/1571#discussion_r115658398 * Make isFlowDirectiveLiteral a separate argument to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115810988 * Simplify isFlowDirectiveLiteral logic by passing n.expression to nodeStr() See https://github.com/prettier/prettier/pull/1571#discussion_r115811216
2017-05-10 22:15:27 +03:00
const isDirectiveLiteral =
isFlowOrTypeScriptDirectiveLiteral || node.type === "DirectiveLiteral";
return privateUtil.printString(raw, options, isDirectiveLiteral);
}
function printRegex(node) {
const flags = node.flags
.split("")
.sort()
.join("");
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) {
2017-11-30 06:09:45 +03:00
if (isJSXNode(node)) {
return privateUtil.hasNodeIgnoreComment(node);
}
const res =
node.comments &&
2017-03-01 20:37:02 +03:00
node.comments.some(
comment =>
comment.leading && privateUtil.hasNewline(text, options.locEnd(comment))
);
return res;
}
function hasNakedLeftSide(node) {
return (
node.type === "AssignmentExpression" ||
node.type === "BinaryExpression" ||
node.type === "LogicalExpression" ||
node.type === "ConditionalExpression" ||
node.type === "CallExpression" ||
node.type === "MemberExpression" ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
(node.type === "BindExpression" && !node.object) ||
(node.type === "UpdateExpression" && !node.prefix)
);
}
function isFlowAnnotationComment(text, typeAnnotation, options) {
const start = options.locStart(typeAnnotation);
const end = privateUtil.skipWhitespace(text, options.locEnd(typeAnnotation));
return text.substr(start, 2) === "/*" && text.substr(end, 2) === "*/";
}
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(path, node) {
if (node.expressions) {
return ["expressions", 0];
}
if (node.left) {
return ["left"];
}
if (node.test) {
return ["test"];
}
if (node.callee) {
return ["callee"];
}
if (node.object) {
return ["object"];
}
if (node.tag) {
return ["tag"];
}
if (node.argument) {
return ["argument"];
}
if (node.expression) {
return ["expression"];
}
throw new Error("Unexpected node has no left side", node);
}
function exprNeedsASIProtection(path, options) {
const node = path.getValue();
const maybeASIProblem =
pathNeedsParens(path, options) ||
node.type === "ParenthesizedExpression" ||
node.type === "TypeCastExpression" ||
(node.type === "ArrowFunctionExpression" &&
!shouldPrintParamsWithoutParens(path, options)) ||
node.type === "ArrayExpression" ||
node.type === "ArrayPattern" ||
(node.type === "UnaryExpression" &&
node.prefix &&
(node.operator === "+" || node.operator === "-")) ||
node.type === "TemplateLiteral" ||
node.type === "TemplateElement" ||
2017-11-30 06:09:45 +03:00
isJSXNode(node) ||
node.type === "BindExpression" ||
node.type === "RegExpLiteral" ||
(node.type === "Literal" && node.pattern) ||
(node.type === "Literal" && node.regex);
if (maybeASIProblem) {
return true;
}
if (!hasNakedLeftSide(node)) {
return false;
}
return path.call.apply(
path,
[childPath => exprNeedsASIProtection(childPath, options)].concat(
getLeftSidePathName(path, node)
)
);
}
function stmtNeedsASIProtection(path, options) {
const node = path.getNode();
if (node.type !== "ExpressionStatement") {
return false;
}
return path.call(
childPath => exprNeedsASIProtection(childPath, options),
"expression"
);
}
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.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": {
// Babylon
const isAsync = node.value ? node.value.async : node.async;
const isGenerator = node.value ? node.value.generator : node.generator;
if (
isAsync ||
node.static ||
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") {
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.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 (isObjectType(node)) {
return true;
}
if (node.type === "UnionTypeAnnotation" || node.type === "TSUnionType") {
const voidCount = node.types.filter(
n =>
n.type === "VoidTypeAnnotation" ||
n.type === "TSVoidKeyword" ||
n.type === "NullLiteralTypeAnnotation" ||
n.type === "TSNullKeyword"
).length;
const objectCount = node.types.filter(
n =>
n.type === "ObjectTypeAnnotation" ||
n.type === "TSTypeLiteral" ||
// This is a bit aggressive but captures Array<{x}>
n.type === "GenericTypeAnnotation" ||
n.type === "TSTypeReference"
).length;
if (node.types.length - 1 === voidCount && objectCount > 0) {
return true;
}
}
return false;
}
function shouldHugArguments(fun) {
return (
fun &&
fun.params &&
fun.params.length === 1 &&
!fun.params[0].comments &&
(fun.params[0].type === "ObjectPattern" ||
fun.params[0].type === "ArrayPattern" ||
(fun.params[0].type === "Identifier" &&
fun.params[0].typeAnnotation &&
(fun.params[0].typeAnnotation.type === "TypeAnnotation" ||
fun.params[0].typeAnnotation.type === "TSTypeAnnotation") &&
isObjectType(fun.params[0].typeAnnotation.typeAnnotation)) ||
(fun.params[0].type === "FunctionTypeParam" &&
isObjectType(fun.params[0].typeAnnotation)) ||
(fun.params[0].type === "AssignmentPattern" &&
(fun.params[0].left.type === "ObjectPattern" ||
fun.params[0].left.type === "ArrayPattern") &&
(fun.params[0].right.type === "Identifier" ||
(fun.params[0].right.type === "ObjectExpression" &&
fun.params[0].right.properties.length === 0) ||
(fun.params[0].right.type === "ArrayExpression" &&
fun.params[0].right.elements.length === 0)))) &&
!fun.rest
);
}
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))) &&
!privateUtil.hasNewline(text, options.locStart(n), { backwards: true })
);
}
function printArrayItems(path, options, printPath, print) {
const printedElements = [];
let separatorParts = [];
path.each(childPath => {
printedElements.push(concat(separatorParts));
printedElements.push(group(print(childPath)));
separatorParts = [",", line];
if (
childPath.getValue() &&
sharedUtil.isNextLineEmpty(
options.originalText,
childPath.getValue(),
options
)
) {
separatorParts.push(softline);
}
}, printPath);
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 = privateUtil.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 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";
}
// eg; `describe("some string", (done) => {})`
function isTestCall(n, parent) {
const unitTestRe = /^(skip|(f|x)?(it|describe|test))$/;
if (n.arguments.length === 1) {
if (
n.callee.type === "Identifier" &&
n.callee.name === "async" &&
parent &&
parent.type === "CallExpression" &&
isTestCall(parent)
) {
return isFunctionOrArrowExpression(n.arguments[0].type);
}
if (isUnitTestSetUp(n)) {
return (
isFunctionOrArrowExpression(n.arguments[0].type) ||
isIdentiferAsync(n.arguments[0])
);
}
} else if (n.arguments.length === 2) {
if (
((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) ||
isSkipOrOnlyBlock(n)) &&
(isTemplateLiteral(n.arguments[0]) || isStringLiteral(n.arguments[0]))
) {
return (
(isFunctionOrArrowExpression(n.arguments[1].type) &&
n.arguments[1].params.length <= 1) ||
isIdentiferAsync(n.arguments[1])
);
}
}
return false;
}
function isSkipOrOnlyBlock(node) {
const unitTestRe = /^(skip|(f|x)?(it|describe|test))$/;
return (
node.callee.type === "MemberExpression" &&
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";
}
function isIdentiferAsync(node) {
return (
node.type === "CallExpression" &&
node.callee.type === "Identifier" &&
node.callee.name === "async"
);
}
function isFunctionOrArrowExpression(type) {
return type === "FunctionExpression" || type === "ArrowFunctionExpression";
}
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") {
return false;
}
const node = path.getNode();
2017-11-30 06:09:45 +03:00
if (!node.expression || !isJSXNode(node.expression)) {
return false;
}
const parent = path.getParentNode();
return parent.type === "Program" && parent.body.length == 1;
}
function willPrintOwnComments(path) {
const node = path.getValue();
const parent = path.getParentNode();
return (
((node && isJSXNode(node)) ||
(parent &&
(parent.type === "JSXSpreadAttribute" ||
parent.type === "JSXSpreadChild" ||
parent.type === "UnionTypeAnnotation" ||
parent.type === "TSUnionType" ||
((parent.type === "ClassDeclaration" ||
parent.type === "ClassExpression") &&
parent.superClass === node)))) &&
!privateUtil.hasIgnoreComment(path)
);
}
function canAttachComment(node) {
return (
node.type &&
node.type !== "CommentBlock" &&
node.type !== "CommentLine" &&
node.type !== "Line" &&
node.type !== "Block" &&
node.type !== "EmptyStatement" &&
node.type !== "TemplateElement" &&
node.type !== "Import" &&
!(node.callee && node.callee.type === "Import")
);
}
function printComment(commentPath, options) {
const comment = commentPath.getValue();
switch (comment.type) {
case "CommentBlock":
case "Block": {
if (isJsDocComment(comment)) {
return printJsDocComment(comment);
}
const isInsideFlowComment =
options.originalText.substr(options.locEnd(comment) - 3, 3) === "*-/";
return "/*" + comment.value + (isInsideFlowComment ? "*-/" : "*/");
}
case "CommentLine":
case "Line":
// Print shebangs with the proper comment characters
if (
options.originalText.slice(options.locStart(comment)).startsWith("#!")
) {
return "#!" + comment.value.trimRight();
}
return "//" + comment.value.trimRight();
default:
throw new Error("Not a comment: " + JSON.stringify(comment));
}
}
function isJsDocComment(comment) {
const lines = comment.value.split("\n");
return (
lines.length > 1 &&
lines.slice(0, lines.length - 1).every(line => line.trim()[0] === "*")
);
}
function printJsDocComment(comment) {
const lines = comment.value.split("\n");
return concat([
"/*",
join(
hardline,
lines.map(
(line, index) =>
(index > 0 ? " " : "") +
(index < lines.length - 1 ? line.trim() : line.trimLeft())
)
),
"*/"
]);
}
function rawText(node) {
return node.extra ? node.extra.raw : node.raw;
}
module.exports = {
print: genericPrint,
embed,
insertPragma,
massageAstNode: clean,
hasPrettierIgnore,
willPrintOwnComments,
canAttachComment,
printComment,
isBlockComment: handleComments.isBlockComment,
handleComments: {
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment
}
};