prettier/src/printer.js

4798 lines
134 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");
const comments = require("./comments");
const FastPath = require("./fast-path");
const multiparser = require("./multiparser");
const util = require("./util");
const isIdentifierName = require("esutils").keyword.isIdentifierNameES6;
const docBuilders = require("./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 docUtils = require("./doc-utils");
const willBreak = docUtils.willBreak;
const isLineNext = docUtils.isLineNext;
const isEmpty = docUtils.isEmpty;
const rawText = docUtils.rawText;
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 getPrintFunction(options) {
switch (options.parser) {
case "graphql":
return require("./printer-graphql");
case "parse5":
return require("./printer-htmlparser2");
case "postcss":
return require("./printer-postcss");
default:
return genericPrintNoParens;
}
}
function hasJsxIgnoreComment(path) {
const node = path.getValue();
const parent = path.getParentNode();
if (!parent || node.type !== "JSXElement" || parent.type !== "JSXElement") {
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.find(
comment => comment.value.trim() === "prettier-ignore"
)
);
}
function genericPrint(path, options, printPath, args) {
assert.ok(path instanceof FastPath);
2016-12-31 07:10:22 +03:00
const node = path.getValue();
2016-12-31 07:10:22 +03:00
// Escape hatch
2017-02-16 06:56:11 +03:00
if (
node &&
((node.comments &&
node.comments.length > 0 &&
node.comments.some(
comment => comment.value.trim() === "prettier-ignore"
)) ||
hasJsxIgnoreComment(path))
2017-02-16 06:56:11 +03:00
) {
return options.originalText.slice(util.locStart(node), util.locEnd(node));
}
if (node) {
// Potentially switch to a different parser
const next = multiparser.getSubtreeParser(path, options);
if (next) {
try {
return multiparser.printSubtree(next, path, printPath, options);
} catch (error) {
/* istanbul ignore if */
if (process.env.PRETTIER_DEBUG) {
const e = new Error(error);
e.parser = next.options.parser;
throw e;
}
// Continue with current parser
}
}
}
let needsParens = false;
const linesWithoutParens = getPrintFunction(options)(
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.
!util.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 (
util.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 path.needsParens() except in this case.
needsParens = path.needsParens(options);
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
if (node.type) {
// HACK: ASI prevention in no-semi mode relies on knowledge of whether
// or not a paren has been inserted (see `exprNeedsASIProtection()`).
// For now, we're just passing that information by mutating the AST here,
// but it would be nice to find a cleaner way to do this.
node.needsParens = needsParens;
}
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 genericPrintNoParens(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 (
util.isNextLineEmpty(options.originalText, childPath.getValue())
) {
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
}
return concat([path.call(print, "expression"), 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);
}
if (parent.type === "UnaryExpression") {
return group(
concat([indent(concat([softline, concat(parts)])), softline])
);
}
// Avoid indenting sub-expressions in assignment/return/etc statements.
if (
parent.type === "AssignmentExpression" ||
parent.type === "VariableDeclarator" ||
shouldInlineLogicalExpression(n) ||
parent.type === "ReturnStatement" ||
(parent.type === "JSXExpressionContainer" &&
parentParent.type === "JSXAttribute") ||
(n === parent.body && parent.type === "ArrowFunctionExpression") ||
(n !== parent.body && parent.type === "ForStatement") ||
parent.type === "ObjectProperty" ||
parent.type === "Property" ||
parent.type === "ConditionalExpression"
) {
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"
);
const shouldInline =
(firstNonMemberParent &&
((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": {
const parentNode = path.getParentNode();
const isFunctionDeclarationIdentifier =
parentNode.type === "DeclareFunction" && parentNode.id === n;
2017-01-13 23:03:53 +03:00
return concat([
n.name,
n.optional ? "?" : "",
n.typeAnnotation && !isFunctionDeclarationIdentifier ? ": " : "",
2017-01-13 23:03:53 +03:00
path.call(print, "typeAnnotation")
]);
}
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"),
n.typeAnnotation ? ": " : "",
2017-01-13 23:03:53 +03:00
path.call(print, "typeAnnotation")
]);
case "FunctionDeclaration":
case "FunctionExpression":
case "TSNamespaceFunctionDeclaration":
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 (canPrintParamsWithoutParens(n)) {
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
),
2017-01-13 23:03:53 +03:00
printReturnType(path, print)
])
)
);
}
2016-12-31 07:10:22 +03:00
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) &&
(n.body.type === "ArrayExpression" ||
n.body.type === "ObjectExpression" ||
n.body.type === "BlockStatement" ||
n.body.type === "JSXElement" ||
isTemplateOnItsOwnLine(n.body, options.originalText) ||
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 )
// with the opening (.
const shouldAddSoftLine = args && args.expandLastArg;
// In order to avoid confusion between
// a => a ? a : a
// a <= a ? a : a
const shouldAddParens =
n.body.type === "ConditionalExpression" &&
!util.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(shouldPrintComma(options, "all") ? "," : ""),
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 "TSExportAssigment":
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":
return concat(["export * from ", path.call(print, "source"), semi]);
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(util.locStart(n), util.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 &&
!n.comments &&
(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 (
util.isNextLineEmpty(options.originalText, childPath.getValue())
) {
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([softline, path.call(print, "argument")])),
line,
")"
])
);
} 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";
if (
// We want to keep require calls as a unit
(!isNew &&
n.callee.type === "Identifier" &&
n.callee.name === "require") ||
n.callee.type === "Import" ||
// Template literals as single arguments
(n.arguments.length === 1 &&
isTemplateOnItsOwnLine(n.arguments[0], options.originalText)) ||
// Keep test declarations on a single line
// e.g. `it('long name', () => {`
(!isNew &&
n.callee.type === "Identifier" &&
(n.callee.name === "it" ||
n.callee.name === "test" ||
n.callee.name === "describe") &&
n.arguments.length === 2 &&
2017-03-04 02:39:37 +03:00
(n.arguments[0].type === "StringLiteral" ||
n.arguments[0].type === "TemplateLiteral" ||
2017-03-04 02:39:37 +03:00
(n.arguments[0].type === "Literal" &&
typeof n.arguments[0].value === "string")) &&
(n.arguments[1].type === "FunctionExpression" ||
n.arguments[1].type === "ArrowFunctionExpression") &&
n.arguments[1].params.length <= 1)
) {
return concat([
isNew ? "new " : "",
path.call(print, "callee"),
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"),
printFunctionTypeParameters(path, options, print),
2017-01-09 20:09:04 +03:00
printArgumentsList(path, options, print)
]);
}
case "TSInterfaceDeclaration":
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 shouldBreak =
n.type === "TSInterfaceBody" ||
(n.type !== "ObjectPattern" &&
util.hasNewlineInRange(
options.originalText,
util.locStart(n),
util.locEnd(n)
));
const separator =
n.type === "TSInterfaceBody" || n.type === "TSTypeLiteral"
? ifBreak(semi, ";")
: ",";
const fields = [];
const leftBrace = n.exact ? "{|" : "{";
const rightBrace = n.exact ? "|}" : "}";
const parent = path.getParentNode(0);
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: util.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 (util.isNextLineEmpty(options.originalText, prop.node)) {
separatorParts.push(hardline);
}
return result;
});
2016-12-31 07:10:22 +03:00
const lastElem = util.getLast(n[propertiesField]);
const canHaveTrailingSeparator = !(
lastElem &&
(lastElem.type === "RestProperty" || lastElem.type === "RestElement")
);
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,
n.optional ? "?" : ""
])
);
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]),
n.optional ? "?" : "",
n.typeAnnotation ? ": " : "",
path.call(print, "typeAnnotation")
]);
}
// 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.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 = util.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 util.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
"]"
])
)
);
}
if (n.optional) {
parts.push("?");
}
if (n.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
}
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 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 printNumber(n.raw);
}
if (typeof n.value !== "string") {
return "" + n.value;
}
// TypeScript workaround for eslint/typescript-eslint-parser#267
// See corresponding workaround in fast-path.js needsParens()
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": {
// 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 === "ConditionalExpression";
// 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 === "ConditionalExpression");
const firstNonConditionalParent = currentParent || parent;
const lastConditionalParent = previousParent;
if (
n.test.type === "JSXElement" ||
n.consequent.type === "JSXElement" ||
n.alternate.type === "JSXElement" ||
parent.type === "JSXExpressionContainer" ||
firstNonConditionalParent.type === "JSXExpressionContainer" ||
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 analagous 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
// * null
const shouldNotWrap = node =>
node.type === "ConditionalExpression" ||
node.type === "NullLiteral" ||
(node.type === "Literal" && node.value === null);
parts.push(
" ? ",
shouldNotWrap(n.consequent)
? path.call(print, "consequent")
: wrap(path.call(print, "consequent")),
" : ",
shouldNotWrap(n.alternate)
? path.call(print, "alternate")
: wrap(path.call(print, "alternate"))
);
} else {
// normal mode
parts.push(
line,
"? ",
n.consequent.type === "ConditionalExpression" ? ifBreak("", "(") : "",
align(2, path.call(print, "consequent")),
n.consequent.type === "ConditionalExpression" ? ifBreak("", ")") : "",
line,
": ",
align(2, path.call(print, "alternate"))
);
}
// In JSX mode, 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 =>
jsxMode
? parent === firstNonConditionalParent ? group(doc) : doc
: group(doc); // Always group in normal mode.
return maybeGroup(
2017-01-09 20:09:04 +03:00
concat([
2017-01-13 23:03:53 +03:00
path.call(print, "test"),
forceNoIndent ? concat(parts) : indent(concat(parts))
2017-01-09 20:09:04 +03:00
])
);
}
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) {
if (n.consequent.type === "BlockStatement") {
2017-01-13 23:03:53 +03:00
parts.push(" else");
} else {
parts.push(hardline, "else");
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(
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 (",
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([
2017-01-13 23:03:53 +03:00
"switch (",
path.call(print, "discriminant"),
") {",
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 &&
util.isNextLineEmpty(options.originalText, caseNode)
? 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":
2017-01-28 18:50:22 +03:00
return concat(["{...", path.call(print, "argument"), "}"]);
case "JSXExpressionContainer": {
const parent = path.getParentNode(0);
const shouldInline =
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" ||
2017-02-23 20:57:51 +03:00
(parent.type === "JSXElement" &&
2017-01-28 18:50:22 +03:00
(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, "}"])
);
}
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
"}"
])
);
}
case "JSXElement": {
const elem = comments.printComments(
path,
() => printJSXElement(path, options, print),
options
);
return maybeWrapJSXElementInParens(path, elem);
}
case "JSXOpeningElement": {
const n = path.getValue();
// don't break up opening elements with a single long text attribute
2017-01-28 18:50:22 +03:00
if (
n.attributes.length === 1 &&
2017-02-16 06:56:11 +03:00
n.attributes[0].value &&
isStringLiteral(n.attributes[0].value)
) {
2017-01-28 18:50:22 +03:00
return group(
concat([
"<",
path.call(print, "name"),
" ",
concat(path.map(print, "attributes")),
n.selfClosing ? " />" : ">"
])
);
}
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"),
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
)
),
2017-02-16 06:56:11 +03:00
n.selfClosing ? line : options.jsxBracketSameLine ? ">" : softline
2017-01-24 21:54:01 +03:00
]),
2017-02-16 06:56:11 +03:00
n.selfClosing ? "/>" : options.jsxBracketSameLine ? "" : ">"
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-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(util.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": {
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));
}
if (n.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
}
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 `.
let size = 0;
const value = childPath.getValue().value.raw;
const index = value.lastIndexOf("\n");
const tabWidth = options.tabWidth;
if (index !== -1) {
size = util.getAlignmentSize(
// All the leading whitespaces
value.slice(index + 1).match(/^[ \t]*/)[0],
tabWidth
);
}
const aligned = addAlignmentToDoc(expressions[i], size, tabWidth);
parts.push("${", 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":
2017-01-28 18:50:22 +03:00
return concat([path.call(print, "tag"), 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":
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)
]);
}
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)]);
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 &&
util.locStart(parent) === util.locStart(n)) ||
parent.type === "ObjectTypeCallProperty" ||
(parentParentParent && parentParentParent.type === "DeclareFunction")
);
let needsColon =
isArrowFunctionTypeAnnotation && parent.type === "TypeAnnotation";
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" &&
parentParent.type === "ArrowFunctionExpression";
if (isObjectTypePropertyAFunction(parent)) {
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"),
n.optional ? "?" : "",
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 = [];
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 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
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 !== "GenericTypeAnnotation" &&
!(
(parent.type === "TypeAlias" ||
parent.type === "VariableDeclarator") &&
hasLeadingOwnLineComment(options.originalText, n)
);
// {
// 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 && shouldIndent) {
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)
]);
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 || "",
2017-01-13 23:03:53 +03:00
path.call(print, "key"),
n.optional ? "?" : "",
isFunctionNotation(n) ? "" : ": ",
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 printNumber(n.extra.raw);
}
return 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":
return printTypeParameters(path, options, print, "params");
case "TypeParameter": {
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(path.call(print, "key"));
if (n.computed) {
parts.push("]");
}
if (n.optional) {
parts.push("?");
}
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 "TSLastTypeNode":
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(["keyof ", path.call(print, "typeAnnotation")]);
case "TSMappedType":
return group(
concat([
"{",
indent(
concat([
options.bracketSpacing ? line : softline,
n.readonlyToken
? concat([path.call(print, "readonlyToken"), " "])
: "",
printTypeScriptModifiers(path, options, print),
"[",
path.call(print, "typeParameter"),
"]",
n.questionToken ? "?" : "",
": ",
path.call(print, "typeAnnotation")
])
),
comments.printDanglingComments(path, options, /* sameIndent */ true),
options.bracketSpacing ? line : softline,
"}"
])
);
case "TSTypeParameter":
parts.push(path.call(print, "name"));
if (n.constraint) {
parts.push(" in ", path.call(print, "constraint"));
}
return concat(parts);
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 ? "]" : "",
n.optional ? "?" : "",
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 (n.modifiers) {
parts.push(printTypeScriptModifiers(path, options, print));
}
parts.push("enum ", path.call(print, "name"), " ");
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, "name"));
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.name);
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 {
parts.push(printTypeScriptModifiers(path, options, print));
// Global declaration looks like this:
// (declare)? global { ... }
const isGlobalDeclaration =
n.name.type === "Identifier" &&
n.name.name === "global" &&
!/namespace|module/.test(
options.originalText.slice(util.locStart(n), util.locStart(n.name))
);
if (!isGlobalDeclaration) {
parts.push(isExternalModule ? "module " : "namespace ");
}
}
parts.push(path.call(print, "name"));
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 "json-identifier":
return '"' + n.value + '"';
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
if (!options.semi && !isClass && stmtNeedsASIProtection(stmtPath)) {
if (stmt.comments && stmt.comments.some(comment => comment.leading)) {
// Note: stmtNeedsASIProtection requires stmtPath to already be printed
// as it reads needsParens which is mutated on the instance
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 (util.isNextLineEmpty(text, stmt) && !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 (isStringLiteral(key) && isIdentifierName(key.value) && !node.computed) {
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)
])
)
],
"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.type === "ArrayExpression" && arg.elements.length > 0) ||
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" ||
arg.body.type === "JSXElement"))
);
}
function shouldGroupLastArg(args) {
const lastArg = util.getLast(args);
2017-03-04 02:39:37 +03:00
const penultimateArg = util.getPenultimate(args);
return (
(!lastArg.comments || !lastArg.comments.length) &&
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 printed = path.map(print, "arguments");
if (printed.length === 0) {
return concat([
"(",
comments.printDanglingComments(path, options, /* sameIndent */ true),
")"
]);
}
const args = path.getValue().arguments;
// This is just an optimization; I think we could return the
// conditional group for all function calls, but it's more expensive
// so only do it for specific forms.
const shouldGroupFirst = shouldGroupFirstArg(args);
const shouldGroupLast = shouldGroupLastArg(args);
if (shouldGroupFirst || shouldGroupLast) {
const shouldBreak = shouldGroupFirst
? printed.slice(1).some(willBreak)
: printed.slice(0, -1).some(willBreak);
// We want to print the last argument with a special flag
let printedExpanded;
let i = 0;
path.each(argPath => {
if (shouldGroupFirst && i === 0) {
printedExpanded = [
argPath.call(p => print(p, { expandFirstArg: true }))
].concat(printed.slice(1));
}
if (shouldGroupLast && i === args.length - 1) {
printedExpanded = printed
.slice(0, -1)
.concat(argPath.call(p => print(p, { expandLastArg: true })));
}
i++;
}, "arguments");
return concat([
printed.some(willBreak) ? breakParent : "",
conditionalGroup(
[
concat(["(", join(concat([", "]), printedExpanded), ")"]),
shouldGroupFirst
? concat([
"(",
group(printedExpanded[0], { shouldBreak: true }),
printed.length > 1 ? ", " : "",
join(concat([",", line]), printed.slice(1)),
")"
])
: concat([
"(",
join(concat([",", line]), printed.slice(0, -1)),
printed.length > 1 ? ", " : "",
group(util.getLast(printedExpanded), {
shouldBreak: true
}),
")"
]),
group(
concat([
"(",
indent(concat([line, join(concat([",", line]), printed)])),
shouldPrintComma(options, "all") ? "," : "",
line,
")"
]),
{ shouldBreak: true }
)
],
{ shouldBreak }
)
]);
}
2017-01-24 21:54:01 +03:00
return group(
concat([
"(",
indent(concat([softline, join(concat([",", line]), printed)])),
ifBreak(shouldPrintComma(options, "all") ? "," : ""),
softline,
")"
]),
{ shouldBreak: printed.some(willBreak) }
);
}
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),
")"
2017-03-01 20:37:02 +03:00
]);
}
const lastParam = util.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
const flowTypeAnnotations = [
"AnyTypeAnnotation",
"NullLiteralTypeAnnotation",
"GenericTypeAnnotation",
"ThisTypeAnnotation",
"NumberTypeAnnotation",
"VoidTypeAnnotation",
"EmptyTypeAnnotation",
"MixedTypeAnnotation",
"BooleanTypeAnnotation",
"BooleanLiteralTypeAnnotation",
"StringTypeAnnotation"
];
const isFlowShorthandWithOneArg =
(isObjectTypePropertyAFunction(parent) ||
isTypeAnnotationAFunction(parent) ||
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) {
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 canPrintParamsWithoutParens(node) {
return (
node.params.length === 1 &&
!node.rest &&
!node.typeParameters &&
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)
])
),
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)
])
),
" ",
path.call(print, "body")
);
2016-12-31 07:10:22 +03:00
2017-01-09 21:47:02 +03:00
return concat(parts);
}
2016-12-30 21:32:43 +03:00
function printReturnType(path, print) {
const n = path.getValue();
2017-01-28 18:50:22 +03:00
const parts = [path.call(print, "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
if (decl["default"] || decl.type === "ExportDefaultDeclaration") {
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 (decl.declaration) {
parts.push(path.call(print, "declaration"));
2017-01-13 23:03:53 +03:00
if (
decl.type === "ExportDefaultDeclaration" &&
2017-02-16 06:56:11 +03:00
(decl.declaration.type !== "ClassDeclaration" &&
decl.declaration.type !== "FunctionDeclaration" &&
decl.declaration.type !== "TSAbstractClassDeclaration" &&
decl.declaration.type !== "TSNamespaceFunctionDeclaration")
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 =
namespaceSpecifiers.length !== 0 &&
(specifiers.length !== 0 || defaultSpecifiers.length !== 0);
const isDefaultFollowed =
defaultSpecifiers.length !== 0 && specifiers.length !== 0;
parts.push(
decl.exportKind === "type" ? "type " : "",
concat(namespaceSpecifiers),
concat([isNamespaceFollowed ? ", " : ""]),
concat(defaultSpecifiers),
concat([isDefaultFollowed ? ", " : ""]),
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 = util.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);
}
2017-05-13 01:33:03 +03:00
const shouldInline =
n[paramsKey].length === 1 &&
(shouldHugType(n[paramsKey][0]) ||
(n[paramsKey][0].type === "GenericTypeAnnotation" &&
shouldHugType(n[paramsKey][0].id)) ||
2017-05-13 01:33:03 +03:00
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) {
parts.push(
" extends ",
2017-01-09 20:09:04 +03:00
path.call(print, "superClass"),
path.call(print, "superTypeParameters")
);
} 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
if (n["implements"] && n["implements"].length > 0) {
2017-01-28 18:50:22 +03:00
partsGroup.push(
line,
"implements ",
group(indent(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
parts.push(" ", path.call(print, "body"));
2016-12-31 07:10:22 +03:00
return parts;
}
function printMemberLookup(path, options, print) {
const property = path.call(print, "property");
const n = path.getValue();
if (!n.computed) {
return concat([".", property]);
}
if (
!n.property ||
(n.property.type === "Literal" && typeof n.property.value === "number") ||
n.property.type === "NumericLiteral"
) {
return concat(["[", property, "]"]);
}
return group(
concat(["[", 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 = [];
function rec(path) {
const node = path.getValue();
if (node.type === "CallExpression" && isMemberish(node.callee)) {
printedNodes.unshift({
node: node,
printed: comments.printComments(
path,
() =>
concat([
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
]),
options
)
});
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 {
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.
printedNodes.unshift({
node: path.getValue(),
printed: concat([
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()
// - 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 === "CallExpression") {
currentGroup.push(printedNodes[i]);
2017-01-09 20:09:04 +03:00
} else {
break;
}
}
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 &&
isLiteral(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 just an identifier with the name starting with a capital
// letter, just a sequence of _$ or this. The rationale is that they are
// likely to be factories.
const shouldMerge =
groups.length >= 2 &&
!groups[1][0].node.comments &&
groups[0].length === 1 &&
(groups[0][0].node.type === "ThisExpression" ||
2017-02-23 20:57:51 +03:00
(groups[0][0].node.type === "Identifier" &&
groups[0][0].node.name.match(/(^[A-Z])|^[_$]+$/)));
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-03-01 20:37:02 +03:00
if (
groups.length <= cutoff &&
2017-03-01 20:37:02 +03:00
!hasComment &&
// (a || b).map() should be break before .map() instead of ||
groups[0][0].node.type !== "LogicalExpression"
) {
return group(oneLine);
}
const expanded = concat([
printGroup(groups[0]),
shouldMerge ? concat(groups.slice(1, 2).map(printGroup)) : "",
printIndentedGroup(groups.slice(shouldMerge ? 2 : 1))
]);
// If there's a comment, we don't want to print in one line.
if (hasComment) {
return group(expanded);
}
// If any group but the last one has a hard line, we want to force expand
// it. If the last group is a function it's okay to inline if it fits.
if (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) ? breakParent : "",
conditionalGroup([oneLine, expanded])
]);
}
function isEmptyJSXElement(node) {
if (node.children.length === 0) {
return true;
}
if (node.children.length > 1) {
return false;
}
// if there is one text child and does not contain any meaningful text
// we can treat the element as empty.
const child = node.children[0];
return isLiteral(child) && !isMeaningfulJSXText(child);
}
// Only space, newline, carriage return, and tab are treated as whitespace
// inside JSX.
const jsxWhitespaceChars = " \n\r\t";
const containsNonJsxWhitespaceRegex = new RegExp(
"[^" + jsxWhitespaceChars + "]"
);
const matchJsxWhitespaceRegex = new RegExp("([" + jsxWhitespaceChars + "]+)");
// Meaningful if it contains non-whitespace characters,
// or it contains whitespace without a new line.
function isMeaningfulJSXText(node) {
return (
isLiteral(node) &&
(containsNonJsxWhitespaceRegex.test(rawText(node)) ||
!/\n/.test(rawText(node)))
);
}
function conditionalExpressionChainContainsJSX(node) {
return Boolean(
getConditionalChainContents(node).find(child => child.type === "JSXElement")
);
}
// 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 (util.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 />
if (isEmptyJSXElement(n)) {
n.openingElement.selfClosing = true;
delete n.closingElement;
}
const openingLines = path.call(print, "openingElement");
const closingLines = path.call(print, "closingElement");
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
]);
}
// If no children, just print the opening element
if (n.openingElement.selfClosing) {
assert.ok(!n.closingElement);
return openingLines;
}
// 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;
});
const containsTag =
n.children.filter(child => child.type === "JSXElement").length > 0;
const containsMultipleExpressions =
n.children.filter(child => child.type === "JSXExpressionContainer").length >
1;
const containsMultipleAttributes = 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);
if (
(isPairOfHardlines && containsText) ||
isPairOfEmptyStrings ||
isLineFollowedByJSXWhitespace
) {
children.splice(i, 2);
} else if (isJSXWhitespaceFollowedByLine) {
children.splice(i + 1, 2);
}
}
// Trim trailing lines (or empty strings)
while (
children.length &&
(isLineNext(util.getLast(children)) || isEmpty(util.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,
JSXElement: true,
JSXExpressionContainer: 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;
}
if (node.right.type === "JSXElement") {
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 (util.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 right = concat([
node.operator,
shouldInlineLogicalExpression(node) ? " " : 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)) {
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);
// `rawContent` is the string exactly like it appeared in the input source
// code, with its enclosing quote.
const rawContent = raw.slice(1, -1);
const double = { quote: '"', regex: /"/g };
const single = { quote: "'", regex: /'/g };
const preferred = options.singleQuote ? single : double;
const alternate = preferred === single ? double : single;
let shouldUseAlternateQuote = false;
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";
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
let canChangeDirectiveQuotes = false;
// If `rawContent` contains at least one of the quote preferred for enclosing
// the string, we might want to enclose with the alternate quote instead, to
// minimize the number of escaped quotes.
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
// Also check for the alternate quote, to determine if we're allowed to swap
// the quotes on a DirectiveLiteral.
if (
rawContent.includes(preferred.quote) ||
rawContent.includes(alternate.quote)
) {
const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
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
} else {
canChangeDirectiveQuotes = true;
}
const enclosingQuote =
options.parser === "json"
? double.quote
: shouldUseAlternateQuote ? alternate.quote : preferred.quote;
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
// Directives are exact code unit sequences, which means that you can't
// change the escape sequences they use.
// See https://github.com/prettier/prettier/issues/1555
// and https://tc39.github.io/ecma262/#directive-prologue
if (isDirectiveLiteral) {
if (canChangeDirectiveQuotes) {
return enclosingQuote + rawContent + enclosingQuote;
}
return raw;
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
}
// It might sound unnecessary to use `makeString` even if `node.raw` already
// is enclosed with `enclosingQuote`, but it isn't. `node.raw` could contain
// unnecessary escapes (such as in `"\'"`). Always using `makeString` makes
// sure that we consistently output the minimum amount of escaped quotes.
return makeString(rawContent, enclosingQuote);
}
function makeString(rawContent, enclosingQuote) {
const otherQuote = enclosingQuote === '"' ? "'" : '"';
// Matches _any_ escape and unescaped quotes (both single and double).
const regex = /\\([\s\S])|(['"])/g;
// Escape and unescape single and double quotes as needed to be able to
// enclose `rawContent` with `enclosingQuote`.
const newContent = rawContent.replace(regex, (match, escaped, quote) => {
// If we matched an escape, and the escaped character is a quote of the
// other type than we intend to enclose the string with, there's no need for
// it to be escaped, so return it _without_ the backslash.
if (escaped === otherQuote) {
return escaped;
}
// If we matched an unescaped quote and it is of the _same_ type as we
// intend to enclose the string with, it must be escaped, so return it with
// a backslash.
if (quote === enclosingQuote) {
return "\\" + quote;
}
Unescape unnecessarily escaped characters in strings (#1575) * Add test with unnecessarily escaped non-quote character In https://github.com/prettier/prettier/pull/1571/commits/0b6d19db18cd64f5d191f8c01dea3ea5525503b3, I noticed that `makeString` doesn't unescape unnecessarily escaped non-quote characters. This change simply adds a test for that. * Fix test with unnecessarily escaped non-quote character Unfortunately, this breaks a couple of other tests... * Revert "Fix test with unnecessarily escaped non-quote character" See https://github.com/prettier/prettier/pull/1575#issuecomment-300490172 This reverts commit d05652518fe7d4e2fb82ce48ffc922b153de5593. * Unescape unnecessarily escaped characters in strings * Add test for unnecessarily escaped character at not-beginning of string * Fix test for unnecessarily escaped character at not-beginning of string * Add test for multiple unnecessary escapes in strings * Pass test for multiple unnecessary escapes in strings * Add test for octal escapes in strings See https://github.com/prettier/prettier/pull/1575#discussion_r115804065 * Pass test for octal escapes in strings See https://github.com/prettier/prettier/pull/1575#discussion_r115804065 * Add test for unnecessarily escaped character preced by escaped backslash See https://github.com/prettier/prettier/pull/1575#discussion_r115804065 * Pass test for unnecessarily escaped character preced by escaped backslash This just allows an even number of backslashes to precede the unnecessary one. See https://github.com/prettier/prettier/pull/1575#discussion_r115804065 * Add test for unescaped character after escaped backslash in strings * Add test for unescaped character preceded by two escaped backslashes in string See https://github.com/prettier/prettier/pull/1575#discussion_r115808571 * Pass test for unescaped character preceded by two escaped backslashes in string This breaks another test though... See https://github.com/prettier/prettier/pull/1575#discussion_r115808571 * Update snapshot It turns out the test wasn't broken, I had just flubbed the escaping in the snapshot. The easiest way to see that this actually works is ```bash $ cat | prettier --stdin "hol\\a (the a is not escaped)" // press Control-D after the newline "hol\\a (the a is not escaped)"; // press Control-D after the newline ``` * Prevent test strings from being parsed as directives See https://github.com/prettier/prettier/pull/1575#discussion_r115820336 (cherry picked from commit 126e56ab2c79801cbf7fee22189212d8c93581df) * Add test for consecutive unnecessarily escaped characters in strings See https://github.com/prettier/prettier/pull/1575#discussion_r115822286 * Pass test for consecutive unnecessarily escaped characters in strings See https://github.com/prettier/prettier/pull/1575#discussion_r115822286 This looping is hacky. We might be able to emulate lookbehind instead. * Optimize (maybe?) string unescaping loop Not sure how expensive string comparison is here... See https://github.com/prettier/prettier/commit/2323c8c025e5302995b2846ed45fd0bd67e66f1b#commitcomment-22092267 * Safeguard against string unescaping loop hanging See https://github.com/prettier/prettier/pull/1575#discussion_r115827531 * Add more comprehensive tests for unnecessary string escapes See https://github.com/prettier/prettier/pull/1575#discussion_r115798155 * Remove superfluous variables from makeString() See https://github.com/prettier/prettier/pull/1575#discussion_r115834468 * Unescape unnecessary strings escapes without looping * Unescape unnecessary string escapes while handling quotes Kudos to @lydell for figuring this out! See https://github.com/prettier/prettier/pull/1575/files#r115860741 * Test that unnecessary escapes remain in directive literals See: * https://github.com/prettier/prettier/pull/1575#discussion_r115820336 * https://github.com/prettier/prettier/pull/1575#issuecomment-300633277
2017-05-11 02:02:49 +03:00
if (quote) {
return quote;
}
// Unescape any unnecessarily escaped character.
// Adapted from https://github.com/eslint/eslint/blob/de0b4ad7bd820ade41b1f606008bea68683dc11a/lib/rules/no-useless-escape.js#L27
return /^[^\\nrvtbfux\r\n\u2028\u2029"'0-7]$/.test(escaped)
? escaped
: "\\" + escaped;
});
return enclosingQuote + newContent + enclosingQuote;
}
function printRegex(node) {
const flags = node.flags.split("").sort().join("");
return `/${node.pattern}/${flags}`;
}
function printNumber(rawNumber) {
return (
rawNumber
.toLowerCase()
// Remove unnecessary plus and zeroes from scientific notation.
.replace(/^([\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3")
// Remove unnecessary scientific notation (1e0).
.replace(/^([\d.]+)e[+-]?0+$/, "$1")
// Make sure numbers always start with a digit.
.replace(/^\./, "0.")
// Remove extraneous trailing decimal zeroes.
.replace(/(\.\d+?)0+(?=e|$)/, "$1")
// Remove trailing dot.
.replace(/\.(?=e|$)/, "")
);
}
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) {
if (node.type === "JSXElement") {
return false;
}
const res =
node.comments &&
2017-03-01 20:37:02 +03:00
node.comments.some(
comment => comment.leading && util.hasNewline(text, util.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 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 exprNeedsASIProtection(node) {
// HACK: node.needsParens is added in `genericPrint()` for the sole purpose
// of being used here. It'd be preferable to find a cleaner way to do this.
const maybeASIProblem =
node.needsParens ||
node.type === "ParenthesizedExpression" ||
node.type === "TypeCastExpression" ||
(node.type === "ArrowFunctionExpression" &&
!canPrintParamsWithoutParens(node)) ||
node.type === "ArrayExpression" ||
node.type === "ArrayPattern" ||
(node.type === "UnaryExpression" &&
node.prefix &&
(node.operator === "+" || node.operator === "-")) ||
node.type === "TemplateLiteral" ||
node.type === "TemplateElement" ||
node.type === "JSXElement" ||
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 exprNeedsASIProtection(getLeftSide(node));
}
function stmtNeedsASIProtection(path) {
const node = path.getNode();
if (node.type !== "ExpressionStatement") {
return false;
}
return exprNeedsASIProtection(node.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.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)) {
return true;
}
if (hasNakedLeftSide(argument)) {
let leftMost = argument;
let newLeftMost;
while ((newLeftMost = getLeftSide(leftMost))) {
leftMost = newLeftMost;
if (hasLeadingOwnLineComment(options.originalText, leftMost)) {
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) {
return (
node.type === "ObjectTypeProperty" &&
node.value.type === "FunctionTypeAnnotation" &&
!node.static &&
!isFunctionNotation(node)
);
}
// TODO: This is a bad hack and we need a better way to distinguish between
// arrow functions and otherwise
function isFunctionNotation(node) {
return isGetterOrSetter(node) || sameLocStart(node, node.value);
}
function isGetterOrSetter(node) {
return node.kind === "get" || node.kind === "set";
}
function sameLocStart(nodeA, nodeB) {
return util.locStart(nodeA) === util.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) {
return (
node.type === "TypeAnnotation" &&
node.typeAnnotation.type === "FunctionTypeAnnotation" &&
!node.static &&
!sameLocStart(node, node.typeAnnotation)
);
}
function isNodeStartingWithDeclare(node, options) {
if (!(options.parser === "flow" || options.parser === "typescript")) {
return false;
}
return (
options.originalText.slice(0, util.locStart(node)).match(/declare\s*$/) ||
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 === "Identifier" &&
fun.params[0].typeAnnotation &&
fun.params[0].typeAnnotation.type === "TypeAnnotation" &&
isObjectType(fun.params[0].typeAnnotation.typeAnnotation)) ||
(fun.params[0].type === "FunctionTypeParam" &&
isObjectType(fun.params[0].typeAnnotation))) &&
!fun.rest
);
}
function templateLiteralHasNewLines(template) {
return template.quasis.some(quasi => quasi.value.raw.includes("\n"));
}
function isTemplateOnItsOwnLine(n, text) {
return (
((n.type === "TemplateLiteral" && templateLiteralHasNewLines(n)) ||
(n.type === "TaggedTemplateExpression" &&
templateLiteralHasNewLines(n.quasi))) &&
!util.hasNewline(text, util.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() &&
util.isNextLineEmpty(options.originalText, childPath.getValue())
) {
separatorParts.push(softline);
}
}, printPath);
return concat(printedElements);
}
function hasDanglingComments(node) {
return (
node.comments &&
node.comments.some(comment => !comment.leading && !comment.trailing)
);
}
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 isStringLiteral(node) {
return (
node.type === "StringLiteral" ||
(node.type === "Literal" && typeof node.value === "string")
);
}
function isObjectType(n) {
return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral";
}
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
function printAstToDoc(ast, options, addAlignmentSize) {
addAlignmentSize = addAlignmentSize || 0;
const cache = new Map();
function printGenerically(path, args) {
const node = path.getValue();
const shouldCache = node && typeof node === "object" && args === undefined;
if (shouldCache && cache.has(node)) {
return cache.get(node);
}
const parent = path.getParentNode(0);
// We let JSXElement print its comments itself because it adds () around
// UnionTypeAnnotation has to align the child without the comments
let res;
if (
(node && node.type === "JSXElement") ||
(parent &&
(parent.type === "UnionTypeAnnotation" ||
parent.type === "TSUnionType"))
) {
res = genericPrint(path, options, printGenerically, args);
} else {
res = comments.printComments(
path,
p => genericPrint(p, options, printGenerically, args),
options,
args && args.needsSemi
);
}
if (shouldCache) {
cache.set(node, res);
}
return res;
}
let doc = printGenerically(new FastPath(ast));
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
if (addAlignmentSize > 0) {
// Add a hardline to make the indents take effect
// It should be removed in index.js format()
doc = addAlignmentToDoc(
docUtils.removeLines(concat([hardline, doc])),
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
addAlignmentSize,
options.tabWidth
);
}
docUtils.propagateBreaks(doc);
if (options.parser === "json") {
doc = concat([doc, hardline]);
}
return doc;
}
2017-01-20 21:12:37 +03:00
module.exports = { printAstToDoc };