prettier/src/printer.js

4602 lines
128 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 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;
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 "postcss":
return require("./printer-postcss");
default:
return genericPrintNoParens;
}
}
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 &&
2017-02-16 06:56:11 +03:00
node.comments &&
node.comments.length > 0 &&
node.comments.some(comment => comment.value.trim() === "prettier-ignore")
2017-02-16 06:56:11 +03:00
) {
return options.originalText.slice(util.locStart(node), util.locEnd(node));
}
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" ||
decorator.type === "TSDecorator"
? ""
: "@";
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();
}
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 "Noop":
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 === "DoStatement");
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")
) {
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
2017-01-13 23:03:53 +03:00
parts.push("::", path.call(print, "callee"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "Path":
return join(".", n.body);
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
parts.push(printFunctionTypeParameters(path, options, print));
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,
args && (args.expandLastArg || args.expandFirstArg)
),
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-28 18:50:22 +03:00
const collapsed = concat([concat(parts), " ", 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 === "SequenceExpression" ||
isTemplateOnItsOwnLine(n.body, options.originalText) ||
n.body.type === "ArrowFunctionExpression")
) {
2017-01-13 23:03:53 +03:00
return group(collapsed);
}
// 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":
parts.push("await");
2016-12-31 07:10:22 +03:00
if (n.all) {
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 "ModuleDeclaration":
parts.push("module", path.call(print, "id"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.source) {
assert.ok(!n.body);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push("from", path.call(print, "source"));
} else {
parts.push(path.call(print, "body"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return join(" ", parts);
case "ImportSpecifier":
if (n.imported) {
2017-02-15 23:56:34 +03:00
if (n.importKind) {
parts.push(path.call(print, "importKind"), " ");
}
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "imported"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.local && n.local.name !== n.imported.name) {
parts.push(" as ", 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
if (n.name) {
parts.push(" as ", path.call(print, "name"));
}
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ExportSpecifier":
if (n.local) {
parts.push(path.call(print, "local"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.exported && n.exported.name !== n.local.name) {
parts.push(" as ", path.call(print, "exported"));
}
} 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
if (n.name) {
parts.push(" as ", path.call(print, "name"));
}
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ExportBatchSpecifier":
2017-01-20 21:12:37 +03:00
return "*";
2017-01-13 23:03:53 +03:00
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 "ExportDeclaration":
case "ExportDefaultDeclaration":
case "ExportNamedDeclaration":
return printExportDeclaration(path, options, print);
case "ExportAllDeclaration":
parts.push("export *");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.exported) {
parts.push(" as ", path.call(print, "exported"));
}
2016-12-31 07:10:22 +03:00
parts.push(" from ", path.call(print, "source"), semi);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
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 &&
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") {
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])));
}, "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"
) {
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 "CallExpression": {
if (
// We want to keep require calls as a unit
2017-03-04 02:39:37 +03:00
(n.callee.type === "Identifier" && n.callee.name === "require") ||
// 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', () => {`
2017-03-04 02:39:37 +03:00
(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([
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 (n.callee.type === "MemberExpression") {
return printMemberChain(path, options, print);
}
2017-01-09 20:09:04 +03:00
return concat([
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("extends ", join(", ", 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 !== "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
])
);
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.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
case "PropertyPattern":
return concat([
path.call(print, "key"),
": ",
path.call(print, "pattern")
]);
// 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);
2017-05-03 02:03:23 +03:00
case "TSDecorator":
2017-01-13 23:03:53 +03:00
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 &&
2017-01-23 20:49:46 +03:00
!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.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
}
2017-01-13 23:03:53 +03:00
return concat(parts);
case "SequenceExpression": {
const parent = path.getParentNode();
const shouldInline =
parent.type === "ReturnStatement" ||
parent.type === "ForStatement" ||
parent.type === "ExpressionStatement";
if (shouldInline) {
return join(", ", path.map(print, "expressions"));
}
return group(
concat([
indent(
concat([
softline,
join(concat([",", line]), path.map(print, "expressions"))
])
),
softline
])
);
}
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
2017-01-13 23:03:53 +03:00
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;
}
2017-01-28 18:50:22 +03:00
return nodeStr(n, options); // Babel 6
case "Directive":
return path.call(print, "value"); // Babel 6
case "DirectiveLiteral":
return nodeStr(n, options);
2017-01-13 23:03:53 +03:00
case "ModuleSpecifier":
if (n.local) {
throw new Error("The ESTree ModuleSpecifier type should be abstract");
}
// The Esprima ModuleSpecifier type is just a string-valued
// Literal identifying the imported-from module.
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": {
const parent = path.getParentNode();
const printed = concat([
line,
"? ",
n.consequent.type === "ConditionalExpression" ? ifBreak("", "(") : "",
align(2, path.call(print, "consequent")),
n.consequent.type === "ConditionalExpression" ? ifBreak("", ")") : "",
line,
": ",
align(2, path.call(print, "alternate"))
]);
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, "test"),
parent.type === "ConditionalExpression" ? printed : indent(printed)
2017-01-09 20:09:04 +03:00
])
);
}
2017-01-13 23:03:53 +03:00
case "NewExpression":
parts.push(
"new ",
path.call(print, "callee"),
printFunctionTypeParameters(path, options, print)
);
2016-12-31 07:10:22 +03:00
if (n.arguments) {
2017-01-13 23:03:53 +03:00
parts.push(printArgumentsList(path, options, print));
2017-01-11 08:48:49 +03:00
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
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);
2017-01-13 23:03:53 +03:00
parts = [
isNodeStartingWithDeclare(n, options) ? "declare " : "",
2017-01-13 23:03:53 +03:00
n.kind,
printed.length ? concat([" ", printed[0]]) : "",
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(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":
parts.push("try ", path.call(print, "block"));
if (n.handler) {
parts.push(" ", path.call(print, "handler"));
} else if (n.handlers) {
path.each(handlerPath => {
parts.push(" ", print(handlerPath));
}, "handlers");
}
2017-01-13 23:03:53 +03:00
if (n.finalizer) {
parts.push(" finally ", path.call(print, "finalizer"));
}
return concat(parts);
case "CatchClause":
parts.push("catch (", path.call(print, "param"));
if (n.guard) {
2017-01-13 23:03:53 +03:00
// Note: esprima does not recognize conditional catch clauses.
parts.push(" if ", path.call(print, "guard"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(") ", path.call(print, "body"));
2017-01-13 23:03:53 +03:00
return concat(parts);
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 = n.value.extra ? n.value.extra.raw : n.value.raw;
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/307
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":
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 : ""
]);
}
case "Keyword": {
return n.name;
}
2017-01-13 23:03:53 +03:00
case "TypeAnnotatedIdentifier":
return concat([
path.call(print, "annotation"),
" ",
path.call(print, "identifier")
]);
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 "ClassPropertyDefinition":
parts.push("static ", path.call(print, "definition"));
2016-12-31 07:10:22 +03:00
if (
n.definition.type !== "MethodDefinition" &&
n.definition.type !== "TSAbstractMethodDefinition"
) {
parts.push(semi);
}
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ClassProperty":
case "TSAbstractClassProperty": {
const variance = getFlowVariance(n);
if (variance) {
parts.push(variance);
}
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 ");
}
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);
case "TSHeritageClause":
return join(", ", path.map(print, "types"));
case "TSExpressionWithTypeArguments":
return concat([
path.call(print, "expression"),
printTypeParameters(path, options, print, "typeArguments")
]);
2017-01-13 23:03:53 +03:00
case "TemplateElement":
return join(literalline, n.value.raw.split(/\r?\n/g));
case "TemplateLiteral": {
const parent = path.getParentNode();
const parentParent = path.getParentNode(1);
const isCSS =
n.quasis &&
n.quasis.length === 1 &&
parent.type === "JSXExpressionContainer" &&
parentParent.type === "JSXElement" &&
parentParent.openingElement.name.name === "style" &&
parentParent.openingElement.attributes.some(
attribute => attribute.name.name === "jsx"
);
if (isCSS) {
const parseCss = eval("require")("./parser-postcss");
const newOptions = Object.assign({}, options, { parser: "postcss" });
const text = n.quasis[0].value.raw;
try {
const ast = parseCss(text, newOptions);
let subtree = printAstToDoc(ast, newOptions);
// HACK remove ending hardline
assert.ok(
subtree.type === "concat" &&
subtree.parts[0].type === "concat" &&
subtree.parts[0].parts.length === 2 &&
subtree.parts[0].parts[1] === hardline
);
subtree = subtree.parts[0].parts[0];
parts.push("`", indent(concat([line, subtree])), line, "`");
return group(concat(parts));
} catch (error) {
// If CSS parsing (or printing) failed
// we give up and just print the TemplateElement as usual
}
}
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":
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
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(
printFunctionTypeParameters(path, options, print),
printFunctionParams(path, print, options)
);
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")]);
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 "NullTypeAnnotation":
return "null";
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.accessibility) {
parts.push(n.accessibility + " ");
}
if (n.export) {
parts.push("export ");
}
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 concat(["(", path.call(print, "typeAnnotation"), ")"]);
case "TSIndexSignature": {
const parent = path.getParentNode();
let printedParams = [];
if (n.params) {
printedParams = path.map(print, "params");
}
if (n.parameters) {
printedParams = path.map(print, "parameters");
}
return concat([
n.accessibility ? concat([n.accessibility, " "]) : "",
n.export ? "export " : "",
n.static ? "static " : "",
n.readonly ? "readonly " : "",
2017-04-05 23:28:02 +03:00
"[",
path.call(print, "index"),
// This should only contain a single element, however TypeScript parses
// it using parseDelimitedList that uses commas as delimiter.
join(", ", printedParams),
"]: ",
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 ");
}
const isType = n.type === "TSConstructorType";
if (n.typeParameters) {
parts.push(printTypeParameters(path, options, print, "typeParameters"));
}
const params = n.params
? path.map(print, "params")
: path.map(print, "parameters");
parts.push("(", join(", ", params), ")");
if (n.typeAnnotation) {
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 ? "?" : "",
printFunctionTypeParameters(path, options, print),
printFunctionParams(path, print, options)
);
if (n.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
}
return group(concat(parts));
case "TSNamespaceExportDeclaration":
if (n.declaration) {
// Temporary fix until https://github.com/eslint/typescript-eslint-parser/issues/263
const isDefault = options.originalText
.slice(util.locStart(n), util.locStart(n.declaration))
.match(/\bdefault\b/);
parts.push(
"export ",
isDefault ? "default " : "",
path.call(print, "declaration")
);
} else {
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" &&
n.modifiers &&
n.modifiers.some(modifier => modifier.type === "TSDeclareKeyword");
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");
2017-01-13 23:03:53 +03:00
default:
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.
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 === "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();
const paramsFieldIsArray = Array.isArray(fun["typeParameters"]);
2017-05-13 01:33:03 +03:00
if (fun.typeParameters) {
// for TSFunctionType typeParameters is an array
// for FunctionTypeAnnotation it's a single node
if (paramsFieldIsArray) {
return concat("<", join(", ", path.map(print, "typeParameters")), ">");
}
return path.call(print, "typeParameters");
}
return "";
}
function printFunctionParams(path, print, options, expandArg) {
const fun = path.getValue();
const paramsField = fun.parameters ? "parameters" : "params";
let printed = [];
if (fun[paramsField]) {
printed = path.map(print, paramsField);
}
2016-12-31 22:38:58 +03:00
if (fun.defaults) {
path.each(defExprPath => {
const i = defExprPath.getName();
const p = printed[i];
2016-12-31 22:38:58 +03:00
if (p && defExprPath.getValue()) {
printed[i] = concat([p, " = ", print(defExprPath)]);
}
}, "defaults");
}
2016-12-31 07:10:22 +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([
"(",
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) {
return group(concat(["(", join(", ", printed.map(removeLines)), ")"]));
}
// Single object destructuring should hug
//
// function({
// a,
// b,
// c
// }) {}
if (shouldHugArguments(fun)) {
return concat(["(", join(", ", printed), ")"]);
}
const parent = path.getParentNode();
2017-04-14 03:54:20 +03:00
const flowTypeAnnotations = [
"AnyTypeAnnotation",
"NullLiteralTypeAnnotation",
"GenericTypeAnnotation",
"ThisTypeAnnotation",
"NumberTypeAnnotation",
"VoidTypeAnnotation",
"NullTypeAnnotation",
"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 &&
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([
"(",
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.params[0].type === "Identifier" &&
!node.params[0].typeAnnotation &&
!util.hasBlockComments(node.params[0]) &&
!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") {
// Temp fix, delete after https://github.com/eslint/typescript-eslint-parser/issues/304
if (
decl.declaration &&
/=/.test(
options.originalText.slice(
util.locStart(decl),
util.locStart(decl.declaration)
)
)
) {
parts.push("= ");
} else {
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")
2017-01-13 23:03:53 +03:00
) {
parts.push(semi);
}
} else {
if (decl.specifiers && decl.specifiers.length > 0) {
if (
decl.specifiers.length === 1 &&
2017-02-16 06:56:11 +03:00
decl.specifiers[0].type === "ExportBatchSpecifier"
) {
parts.push("*");
} else {
const specifiers = [];
const defaultSpecifiers = [];
const namespaceSpecifiers = [];
path.map(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:
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.accessibility) {
parts.push(n.accessibility + " ");
}
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
);
}
// We detect calls on member expressions specially to format a
// comman 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") {
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 (node.type === "MemberExpression") {
printedNodes.unshift({
node: node,
2017-02-03 19:50:51 +03:00
printed: comments.printComments(
path,
() => printMemberLookup(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 (
printedNodes[i].node.type === "MemberExpression" &&
2017-02-16 06:56:11 +03:00
printedNodes[i + 1].node.type === "MemberExpression"
) {
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) {
2017-02-03 19:50:51 +03:00
if (
hasSeenCallExpression &&
printedNodes[i].node.type === "MemberExpression"
2017-02-03 19:50:51 +03:00
) {
// [0] should be appended at the end of the group instead of the
// beginning of the next one
if (printedNodes[i].node.computed) {
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 child but it's just a newline, treat as empty
const value = node.children[0].value;
if (!/\S/.test(value) && /\n/.test(value)) {
return true;
}
return false;
}
// 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.
//
// So for one thing, '\n' needs to be parsed out of string literals
// and turned into hardlines (with string boundaries otherwise using softline)
//
// For another, leading, trailing, and lone whitespace all need to
// turn themselves into the rather ugly `{' '}` when breaking.
//
// Finally 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) && typeof child.value === "string") {
const value = child.raw || child.extra.raw;
// Contains a non-whitespace character
if (/[^ \n\r\t]/.test(value)) {
// treat each line of text as its own entity
value.split(/(\r?\n\s*)/).forEach(textLine => {
const newlines = textLine.match(/\n/g);
if (newlines) {
children.push("");
children.push(hardline);
// allow one extra newline
if (newlines.length > 1) {
children.push("");
children.push(hardline);
}
return;
}
if (textLine.length === 0) {
return;
}
const beginSpace = /^[ \n\r\t]+/.test(textLine);
if (beginSpace) {
children.push("");
children.push(jsxWhitespace);
}
const stripped = textLine.replace(/^[ \n\r\t]+|[ \n\r\t]+$/g, "");
// Split text into words separated by "line"s.
stripped.split(/([ \n\r\t]+)/).forEach(word => {
const space = /[ \n\r\t]+/.test(word);
if (space) {
children.push(line);
} else {
children.push(word);
}
});
const endSpace = /[ \n\r\t]+$/.test(textLine);
if (endSpace) {
children.push(jsxWhitespace);
} else {
// Ideally this would be a `softline` 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(value)) {
children.push("");
children.push(hardline);
// allow one extra newline
if (value.match(/\n/g).length > 1) {
children.push("");
children.push(hardline);
}
} else if (/[ \n\r\t]/.test(value)) {
// whitespace(s)-only without newlines,
// eg; one or more spaces separating two elements
for (let i = 0; i < value.length; ++i) {
// Because fill expects alternating content and whitespace parts
// we need to include an empty content part before each JSX
// whitespace.
children.push("");
children.push(jsxWhitespace);
}
}
} else {
children.push(print(childPath));
const next = n.children[i + 1];
const followedByJSXElement = next && !isLiteral(next);
const followedByJSXWhitespace =
next &&
next.type === "JSXExpressionContainer" &&
isLiteral(next.expression) &&
next.expression.value === " ";
if (followedByJSXElement && !followedByJSXWhitespace) {
children.push(softline);
} else {
// Ideally this would be a softline as well.
// See the comment above about the Facebook translation pipeline as
// to why this is an empty string.
children.push("");
}
}
}, "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;
}
// Record any breaks. Should never go from true to false, only false to true.
let forcedBreak = willBreak(openingLines);
const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}';
const jsxWhitespace = ifBreak(concat([rawJsxWhitespace, softline]), " ");
const children = printJSXChildren(path, options, print, jsxWhitespace);
// Remove multiple filler empty strings
// These can occur when a text element is followed by a newline.
for (let i = children.length - 2; i >= 0; i--) {
if (children[i] === "" && children[i + 1] === "") {
children.splice(i, 2);
}
}
// Trim trailing lines (or empty strings), recording if there was a hardline
let numTrailingHard = 0;
while (
children.length &&
(isLineNext(util.getLast(children)) || isEmpty(util.getLast(children)))
) {
if (willBreak(util.getLast(children))) {
++numTrailingHard;
forcedBreak = true;
}
children.pop();
}
// allow one extra newline
if (numTrailingHard > 1) {
children.push("");
children.push(hardline);
}
// Trim leading lines (or empty strings), recording if there was a hardline
let numLeadingHard = 0;
while (
children.length &&
(isLineNext(children[0]) || isEmpty(children[0])) &&
(isLineNext(children[1]) || isEmpty(children[1]))
) {
if (willBreak(children[0]) || willBreak(children[1])) {
++numLeadingHard;
forcedBreak = true;
}
children.shift();
children.shift();
}
// allow one extra newline
if (numLeadingHard > 1) {
children.unshift(hardline);
children.unshift("");
}
// 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) => {
// Ensure that we display leading, trailing, and solitary whitespace as
// `{" "}` when outputting this element over multiple lines.
if (child === jsxWhitespace) {
if (i === 1 && children[i - 1] === "") {
multilineChildren.push(rawJsxWhitespace);
return;
} else if (i === children.length - 1) {
multilineChildren.push(rawJsxWhitespace);
return;
}
}
multilineChildren.push(child);
if (willBreak(child)) {
forcedBreak = true;
}
});
const multiLineElem = group(
concat([
openingLines,
indent(concat([hardline, fill(multilineChildren)])),
hardline,
closingLines
])
);
if (forcedBreak) {
return multiLineElem;
}
return conditionalGroup([
group(concat([openingLines, fill(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,
LogicalExpression: true,
ArrowFunctionExpression: 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 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;
}
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.getPrecedence(node.left.operator) ===
util.getPrecedence(node.operator) &&
node.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)) ||
((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]));
}
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
function nodeStr(node, options, isFlowDirectiveLiteral) {
const raw = node.extra ? node.extra.raw : node.raw;
// `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 =
isFlowDirectiveLiteral || node.type === "DirectiveLiteral";
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 = 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 === "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) {
if (!path) {
return false;
}
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;
if (!name) {
return false;
}
// 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:
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 (node.type === "ObjectTypeAnnotation" || node.type === "TSTypeLiteral") {
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 === "Literal" && n.value === null)
).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" &&
shouldHugType(fun.params[0].typeAnnotation.typeAnnotation)) ||
(fun.params[0].type === "FunctionTypeParam" &&
shouldHugType(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 removeLines(doc) {
// Force this doc into flat mode by statically converting all
// lines into spaces (or soft lines into nothing). Hard lines
// should still output because there's too great of a chance
// of breaking existing assumptions otherwise.
return docUtils.mapDoc(doc, d => {
if (d.type === "line" && !d.hard) {
return d.soft ? "" : " ";
} else if (d.type === "if-break") {
return d.flatContents || "";
}
return d;
});
}
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;
function printGenerically(path, args) {
const node = path.getValue();
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
if (
(node && node.type === "JSXElement") ||
(parent &&
(parent.type === "UnionTypeAnnotation" ||
parent.type === "TSUnionType"))
) {
return genericPrint(path, options, printGenerically, args);
}
return comments.printComments(
path,
p => genericPrint(p, options, printGenerically, args),
options,
args && args.needsSemi
);
}
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(
removeLines(concat([hardline, doc])),
addAlignmentSize,
options.tabWidth
);
}
docUtils.propagateBreaks(doc);
return doc;
}
2017-01-20 21:12:37 +03:00
module.exports = { printAstToDoc };