prettier/src/printer.js

3354 lines
94 KiB
JavaScript
Raw Normal View History

2017-01-10 20:18:22 +03:00
"use strict";
2017-01-28 18:50:22 +03:00
var assert = require("assert");
var comments = require("./comments");
var FastPath = require("./fast-path");
var util = require("./util");
2017-01-11 08:48:49 +03:00
var isIdentifierName = require("esutils").keyword.isIdentifierNameES6;
var docBuilders = require("./doc-builders");
var concat = docBuilders.concat;
var join = docBuilders.join;
var line = docBuilders.line;
var hardline = docBuilders.hardline;
var softline = docBuilders.softline;
var literalline = docBuilders.literalline;
var group = docBuilders.group;
var indent = docBuilders.indent;
var align = docBuilders.align;
var conditionalGroup = docBuilders.conditionalGroup;
var ifBreak = docBuilders.ifBreak;
var breakParent = docBuilders.breakParent;
var lineSuffixBoundary = docBuilders.lineSuffixBoundary;
var docUtils = require("./doc-utils");
var willBreak = docUtils.willBreak;
var isLineNext = docUtils.isLineNext;
var getFirstString = docUtils.getFirstString;
var isEmpty = docUtils.isEmpty;
var types = require("ast-types");
var namedTypes = types.namedTypes;
var isString = types.builtInTypes.string;
var isObject = types.builtInTypes.object;
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;
}
case "es5":
2017-02-23 20:57:51 +03:00
if (level === "es5") {
return true;
}
case "none":
default:
return false;
}
}
function genericPrint(path, options, printPath) {
assert.ok(path instanceof FastPath);
2016-12-31 07:10:22 +03:00
var node = path.getValue();
var parts = [];
var needsParens = false;
var linesWithoutParens = genericPrintNoParens(path, options, printPath);
2016-12-31 22:38:58 +03:00
if (!node || isEmpty(linesWithoutParens)) {
return linesWithoutParens;
}
2016-12-31 07:10:22 +03:00
// Escape hatch
2017-02-16 06:56:11 +03:00
if (
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));
}
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)
) {
const separator = node.decorators.length === 1 &&
2017-03-16 23:27:20 +03:00
(node.decorators[0].expression.type === "Identifier" ||
node.decorators[0].expression.type === "MemberExpression")
2017-01-28 18:50:22 +03:00
? " "
: hardline;
path.each(
function(decoratorPath) {
parts.push(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(
function(decoratorPath) {
parts.push(printPath(decoratorPath), line);
},
"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 (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
return concat(parts);
}
function genericPrintNoParens(path, options, print) {
var n = path.getValue();
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
// TODO: Investigate types that return not printable.
// This assert isn't very useful though.
// namedTypes.Printable.assert(n);
var 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(
function(childPath) {
parts.push(print(childPath), ";", hardline);
2017-02-03 19:50:51 +03:00
if (
util.isNextLineEmpty(options.originalText, childPath.getValue())
) {
parts.push(hardline);
}
2017-01-13 23:03:53 +03:00
},
"directives"
);
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(
path.call(
function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
},
"body"
)
);
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":
2017-01-28 18:50:22 +03:00
return concat([path.call(print, "expression"), ";"]); // Babel extension.
case "ParenthesizedExpression":
return concat(["(", path.call(print, "expression"), ")"]);
2017-01-13 23:03:53 +03:00
case "AssignmentExpression":
return printAssignment(
path.call(print, "left"),
n.operator,
n.right,
path.call(print, "right"),
options
2017-01-13 23:03:53 +03:00
);
case "BinaryExpression":
case "LogicalExpression": {
const parts = printBinaryishExpressions(path, print, options);
const parent = path.getParentNode();
// Avoid indenting sub-expressions in if/etc statements.
if (
parent.type === "AssignmentExpression" ||
parent.type === "VariableDeclarator" ||
shouldInlineLogicalExpression(n) ||
parent.type === "ReturnStatement" ||
2017-02-23 20:57:51 +03:00
(n !== parent.body &&
(parent.type === "IfStatement" ||
parent.type === "WhileStatement" ||
parent.type === "DoStatement" ||
parent.type === "ForStatement")) ||
(n === parent.body && parent.type === "ArrowFunctionExpression")
) {
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 "MemberExpression": {
2017-01-09 20:09:04 +03:00
return concat([
path.call(print, "object"),
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":
return concat([
n.name,
n.optional ? "?" : "",
path.call(print, "typeAnnotation")
]);
case "SpreadElement":
case "SpreadElementPattern":
// Babel 6 for ObjectPattern
case "RestProperty":
case "SpreadProperty":
case "SpreadPropertyPattern":
case "RestElement":
case "ObjectTypeSpreadProperty":
2017-01-13 23:03:53 +03:00
return concat([
"...",
path.call(print, "argument"),
path.call(print, "typeAnnotation")
]);
case "FunctionDeclaration":
case "FunctionExpression":
return printFunctionDeclaration(path, print, options)
2017-01-13 23:03:53 +03:00
case "ArrowFunctionExpression":
if (n.async) parts.push("async ");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.typeParameters) {
parts.push(path.call(print, "typeParameters"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (
n.params.length === 1 &&
2017-02-16 06:56:11 +03:00
!n.rest &&
n.params[0].type === "Identifier" &&
!n.params[0].typeAnnotation &&
!n.params[0].leadingComments &&
!n.params[0].trailingComments &&
!n.params[0].optional &&
2017-02-16 06:56:11 +03:00
!n.predicate &&
!n.returnType
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),
printReturnType(path, print)
])
)
);
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(" =>");
2017-01-13 23:03:53 +03:00
const body = path.call(print, "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 (
n.body.type === "ArrayExpression" ||
2017-02-16 06:56:11 +03:00
n.body.type === "ObjectExpression" ||
n.body.type === "JSXElement" ||
n.body.type === "BlockStatement" ||
n.body.type === "TaggedTemplateExpression" ||
n.body.type === "TemplateElement" ||
n.body.type === "ClassExpression" ||
n.body.type === "ArrowFunctionExpression"
) {
2017-01-13 23:03:53 +03:00
return group(collapsed);
}
2017-01-24 21:54:01 +03:00
return group(
concat([
concat(parts),
group(indent(concat([line, body])))
])
2017-01-24 21:54:01 +03:00
);
2017-01-13 23:03:53 +03:00
case "MethodDefinition":
if (n.static) {
parts.push("static ");
}
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("*");
2016-12-31 07:10:22 +03:00
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("*");
2016-12-31 07:10:22 +03:00
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
2017-01-13 23:03:53 +03:00
parts.push(" from ", path.call(print, "source"), ";");
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":
parts.push("import ");
2017-02-04 00:19:14 +03:00
const fromParts = [];
2017-01-13 23:03:53 +03:00
if (n.importKind && n.importKind !== "value") {
parts.push(n.importKind + " ");
}
2016-12-31 07:10:22 +03:00
2017-02-04 00:19:14 +03:00
var standalones = [];
var grouped = [];
2017-01-13 23:03:53 +03:00
if (n.specifiers && n.specifiers.length > 0) {
path.each(
function(specifierPath) {
var value = specifierPath.getValue();
if (
namedTypes.ImportDefaultSpecifier.check(value) ||
2017-02-16 06:56:11 +03:00
namedTypes.ImportNamespaceSpecifier.check(value)
2017-01-13 23:03:53 +03:00
) {
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
2017-01-13 23:03:53 +03:00
if (grouped.length > 0) {
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
2017-02-04 00:19:14 +03:00
fromParts.push(grouped.length === 0 ? line : " ", "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
2017-02-04 00:19:14 +03:00
fromParts.push(path.call(print, "source"), ";");
// If there's a very long import, break the following way:
//
// import veryLong
// from 'verylong'
//
// In case there are grouped elements, they will already break the way
// we want and this break would take precedence instead.
if (grouped.length === 0) {
return group(
concat([concat(parts), indent(concat(fromParts))])
2017-02-04 00:19:14 +03:00
);
}
2016-12-31 07:10:22 +03:00
2017-02-04 00:19:14 +03:00
return concat([concat(parts), concat(fromParts)]);
2017-01-25 18:33:48 +03:00
case "Import": {
return "import";
}
case "BlockStatement": {
2017-01-13 23:03:53 +03:00
var naked = path.call(
function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
},
2017-01-13 23:03:53 +03:00
"body"
);
2016-12-31 07:10:22 +03:00
const hasContent = getFirstString(naked);
const hasDirectives = n.directives && n.directives.length > 0;
var 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 === "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) {
2017-01-13 23:03:53 +03:00
path.each(
function(childPath) {
parts.push(
indent(
2017-01-28 18:50:22 +03:00
concat([hardline, print(childPath), ";"])
2017-01-13 23:03:53 +03:00
)
);
},
"directives"
);
}
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
2017-01-13 23:03:53 +03:00
parts.push(";");
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") ||
// 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"),
path.call(print, "typeParameters"),
2017-01-09 20:09:04 +03:00
printArgumentsList(path, options, print)
]);
}
2017-01-13 23:03:53 +03:00
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation":
case "TSTypeLiteral":
2017-01-13 23:03:53 +03:00
var isTypeAnnotation = n.type === "ObjectTypeAnnotation";
var isTypeScriptTypeAnnotaion = n.type === "TSTypeLiteral";
2017-01-13 23:03:53 +03:00
// Leave this here because we *might* want to make this
2017-04-05 23:28:02 +03:00
// configurable later -- flow accepts ";" for type separators,
// typescript accepts ";" and newlines
2017-01-13 23:03:53 +03:00
var separator = isTypeAnnotation ? "," : ",";
var fields = [];
var leftBrace = n.exact ? "{|" : "{";
var rightBrace = n.exact ? "|}" : "}";
var parent = path.getParentNode(0);
2017-02-16 06:56:11 +03:00
var parentIsUnionTypeAnnotation = parent.type === "UnionTypeAnnotation";
var propertiesField = isTypeScriptTypeAnnotaion ? "members" : "properties";
2017-01-13 23:03:53 +03:00
if (isTypeAnnotation) {
fields.push("indexers", "callProperties");
}
2016-12-31 07:10:22 +03:00
fields.push(propertiesField);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
var props = [];
let separatorParts = [];
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
fields.forEach(function(field) {
path.each(
function(childPath) {
props.push(concat(separatorParts));
2017-01-13 23:03:53 +03:00
props.push(group(print(childPath)));
separatorParts = [separator, line];
2017-02-16 06:56:11 +03:00
if (
util.isNextLineEmpty(options.originalText, childPath.getValue())
) {
separatorParts.push(hardline);
}
2017-01-13 23:03:53 +03:00
},
field
);
});
2016-12-31 07:10:22 +03:00
const lastElem = util.getLast(n[propertiesField]);
const canHaveTrailingComma = !(lastElem &&
lastElem.type === "RestProperty");
const shouldBreak = n.type !== "ObjectPattern" &&
util.hasNewlineInRange(
options.originalText,
util.locStart(n),
util.locEnd(n)
);
2017-01-13 23:03:53 +03:00
if (props.length === 0) {
return group(
concat([
leftBrace,
comments.printDanglingComments(path, options),
softline,
rightBrace
])
);
2017-01-13 23:03:53 +03:00
} else {
2017-01-24 21:54:01 +03:00
return group(
2017-01-13 23:03:53 +03:00
concat([
leftBrace,
indent(
align(
parentIsUnionTypeAnnotation ? 2 : 0,
concat([
options.bracketSpacing ? line : softline,
concat(props)
])
)
2017-01-13 23:03:53 +03:00
),
2017-02-23 20:57:51 +03:00
ifBreak(
canHaveTrailingComma && shouldPrintComma(options) ? "," : ""
),
align(
parentIsUnionTypeAnnotation ? 2 : 0,
concat([options.bracketSpacing ? line : softline, rightBrace])
),
2017-01-13 23:03:53 +03:00
path.call(print, "typeAnnotation")
]),
{ 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 {
if (n.computed) {
parts.push("[", path.call(print, "key"), "]");
} else {
parts.push(printPropertyKey(path, options, print));
2017-01-13 23:03:53 +03:00
}
parts.push(concat([": ", path.call(print, "value")]));
2017-01-13 23:03:53 +03:00
}
2017-01-28 18:50:22 +03:00
return concat(parts); // Babel 6
case "ClassMethod":
2017-01-13 23:03:53 +03:00
if (n.static) {
parts.push("static ");
}
parts = parts.concat(printObjectMethod(path, options, print));
2017-01-28 18:50:22 +03:00
return concat(parts); // Babel 6
case "ObjectMethod":
2017-01-13 23:03:53 +03:00
return printObjectMethod(path, options, print);
case "Decorator":
2017-01-28 18:50:22 +03:00
return concat(["@", path.call(print, "expression")]);
2017-01-13 23:03:53 +03:00
case "ArrayExpression":
case "ArrayPattern":
2017-01-13 23:03:53 +03:00
if (n.elements.length === 0) {
2017-01-28 18:50:22 +03:00
parts.push(
group(
concat([
"[",
comments.printDanglingComments(path, options),
softline,
"]"
])
)
2017-01-28 18:50:22 +03:00
);
2017-01-13 23:03:53 +03:00
} else {
const lastElem = util.getLast(n.elements);
2017-01-23 20:49:46 +03:00
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
2017-01-23 20:49:46 +03:00
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":
return join(", ", path.map(print, "expressions"));
case "ThisExpression":
2017-01-20 21:12:37 +03:00
return "this";
2017-01-13 23:03:53 +03:00
case "Super":
2017-01-28 18:50:22 +03:00
return "super"; // Babel 6 Literal split
case "NullLiteral":
return "null"; // Babel 6 Literal split
case "RegExpLiteral":
return n.extra.raw;
2017-01-13 23:03:53 +03:00
// Babel 6 Literal split
case "NumericLiteral":
return printNumber(n.extra.raw);
// Babel 6 Literal split
case "BooleanLiteral":
2017-01-13 23:03:53 +03:00
// Babel 6 Literal split
case "StringLiteral":
case "Literal":
if (typeof n.value === "number") return printNumber(n.raw);
if (typeof n.value !== "string") return "" + n.value;
2017-01-13 23:03:53 +03:00
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":
return group(
2017-01-09 20:09:04 +03:00
concat([
2017-01-13 23:03:53 +03:00
path.call(print, "test"),
2017-01-09 20:09:04 +03:00
indent(
concat([
2017-01-13 23:03:53 +03:00
line,
"? ",
align(2, path.call(print, "consequent")),
2017-01-13 23:03:53 +03:00
line,
": ",
align(2, path.call(print, "alternate"))
2017-01-09 20:09:04 +03:00
])
2017-01-13 23:03:53 +03:00
)
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"));
2016-12-31 07:10:22 +03:00
if (n.typeParameters) {
parts.push(path.call(print, "typeParameters"))
}
2017-01-13 23:03:53 +03:00
var args = n.arguments;
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (args) {
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":
var printed = path.map(
function(childPath) {
return print(childPath);
},
"declarations"
);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts = [
n.kind,
" ",
printed[0],
2017-01-13 23:03:53 +03:00
indent(
2017-01-28 18:50:22 +03:00
concat(printed.slice(1).map(p => concat([",", line, p])))
2017-01-13 23:03:53 +03:00
)
];
// We generally want to terminate all variable declarations with a
// semicolon, except when they in the () part of for loops.
2017-01-13 23:03:53 +03:00
var parentNode = path.getParentNode();
var isParentForLoop = namedTypes.ForStatement.check(parentNode) ||
2017-01-23 20:49:46 +03:00
namedTypes.ForInStatement.check(parentNode) ||
2017-02-23 20:57:51 +03:00
(namedTypes.ForOfStatement &&
namedTypes.ForOfStatement.check(parentNode)) ||
(namedTypes.ForAwaitStatement &&
namedTypes.ForAwaitStatement.check(parentNode));
if (!(isParentForLoop && parentNode.body !== n)) {
2017-01-13 23:03:53 +03:00
parts.push(";");
}
2017-01-24 21:54:01 +03:00
return group(concat(parts));
2017-01-13 23:03:53 +03:00
case "VariableDeclarator":
return printAssignment(
path.call(print, "id"),
"=",
n.init,
n.init && path.call(print, "init"),
options
);
2017-01-13 23:03:53 +03:00
case "WithStatement":
return concat([
"with (",
path.call(print, "object"),
")",
adjustClause(path.call(print, "body"), options)
2017-01-13 23:03:53 +03:00
]);
case "IfStatement":
const con = adjustClause(path.call(print, "consequent"), options);
const opening = group(concat([
2017-01-13 23:03:53 +03:00
"if (",
group(concat([
indent(
concat([softline, path.call(print, "test")])
),
softline
])),
2017-01-13 23:03:53 +03:00
")",
con
]));
parts.push(opening);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.alternate) {
const hasBraces = isCurlyBracket(con);
const isEmpty = isEmptyBlock(con);
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (hasBraces && !isEmpty) {
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(
2017-01-13 23:03:53 +03:00
path.call(print, "alternate"),
options,
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(path.call(print, "body"), options);
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, "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,
2017-01-13 23:03:53 +03:00
"for (",
group(
2017-01-09 20:09:04 +03:00
concat([
2017-01-13 23:03:53 +03:00
indent(
concat([
softline,
path.call(print, "init"),
";",
line,
path.call(print, "test"),
";",
line,
path.call(print, "update")
])
),
softline
2017-01-09 20:09:04 +03:00
])
2017-01-13 23:03:53 +03:00
),
")",
body
]);
}
2017-01-13 23:03:53 +03:00
case "WhileStatement":
return concat([
"while (",
group(
concat([
indent(
2017-01-28 18:50:22 +03:00
concat([softline, path.call(print, "test")])
),
softline
])
),
2017-01-13 23:03:53 +03:00
")",
adjustClause(path.call(print, "body"), options)
]);
case "ForInStatement":
// Note: esprima can't actually parse "for each (".
return concat([
n.each ? "for each (" : "for (",
path.call(print, "left"),
" in ",
path.call(print, "right"),
")",
adjustClause(path.call(print, "body"), options)
]);
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);
2017-01-13 23:03:53 +03:00
return concat([
"for",
isAwait ? " await" : "",
" (",
2017-01-13 23:03:53 +03:00
path.call(print, "left"),
" of ",
path.call(print, "right"),
")",
adjustClause(path.call(print, "body"), options)
]);
2017-01-13 23:03:53 +03:00
case "DoWhileStatement":
var clause = adjustClause(path.call(print, "body"), options);
2017-01-28 18:50:22 +03:00
var doBody = concat(["do", clause]);
var parts = [doBody];
2017-01-13 23:03:53 +03:00
const hasBraces = isCurlyBracket(clause);
2016-12-31 07:10:22 +03:00
if (hasBraces) parts.push(" while");
2017-01-28 18:50:22 +03:00
else parts.push(concat([line, "while"]));
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
parts.push(" (", path.call(print, "test"), ");");
return concat(parts);
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(";");
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
2017-01-13 23:03:53 +03:00
parts.push(";");
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(
function(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)
// 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":
2017-01-28 18:50:22 +03:00
return concat(["throw ", path.call(print, "argument"), ";"]);
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(
2017-01-28 18:50:22 +03:00
concat([hardline, join(hardline, path.map(print, "cases"))])
)
2017-01-23 20:49:46 +03:00
: "",
2017-01-13 23:03:53 +03:00
hardline,
"}"
]);
2017-01-13 23:03:53 +03:00
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 isFirstCase = path.getNode() === path.getParentNode().cases[0];
if (!isFirstCase && util.isPreviousLineEmpty(options.originalText, path.getValue())) {
parts.unshift(hardline);
}
if (n.consequent.find(node => node.type !== "EmptyStatement")) {
const cons = path.call(consequentPath => {
return join(hardline, consequentPath.map(print));
}, "consequent");
2017-01-13 23:03:53 +03:00
parts.push(
isCurlyBracket(cons)
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);
// JSX extensions below.
case "DebuggerStatement":
2017-01-20 21:12:37 +03:00
return "debugger;";
2017-01-13 23:03:53 +03:00
case "JSXAttribute":
parts.push(path.call(print, "name"));
if (n.value) {
let res;
if (
(n.value.type === "StringLiteral" || n.value.type === "Literal") &&
2017-02-16 06:56:11 +03:00
typeof n.value.value === "string"
2017-01-13 23:03:53 +03:00
) {
res = '"' + util.htmlEscapeInsideDoubleQuote(n.value.value) + '"';
} else {
res = path.call(print, "value");
}
parts.push("=", res);
}
2017-01-13 23:03:53 +03:00
return concat(parts);
case "JSXIdentifier":
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 "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" ||
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(
2017-01-28 18:50:22 +03:00
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 = printJSXElement(path, options, print);
return maybeWrapJSXElementInParens(path, elem, options);
}
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 &&
n.attributes[0].value.type === "Literal" &&
typeof n.attributes[0].value.value === "string"
) {
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.some(
comment => comment.type === "Line" || comment.type === "CommentLine"
2017-03-04 02:39:37 +03:00
);
return concat([
comments.printDanglingComments(
path,
options,
2017-03-16 23:26:44 +03:00
/* sameIndent */ !requiresHardline
),
requiresHardline ? hardline : ""
]);
2017-01-13 23:03:53 +03:00
case "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(
function(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 (!namedTypes.MethodDefinition.check(n.definition)) parts.push(";");
2017-01-13 23:03:53 +03:00
return concat(parts);
case "ClassProperty":
if (n.static) parts.push("static ");
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
var key;
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (n.computed) {
2017-01-28 18:50:22 +03:00
key = concat(["[", path.call(print, "key"), "]"]);
} else {
key = printPropertyKey(path, options, print);
var variance = getFlowVariance(n, options);
if (variance) {
key = concat([variance, key]);
} else if (n.accessibility === "public") {
key = concat(["public ", key]);
} else if (n.accessibility === "protected") {
key = concat(["protected ", key]);
} else if (n.accessibility === "private") {
key = concat(["private ", key]);
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(key);
2016-12-31 07:10:22 +03:00
if (n.typeAnnotation) parts.push(path.call(print, "typeAnnotation"));
2016-12-31 07:10:22 +03:00
if (n.value) parts.push(" = ", path.call(print, "value"));
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);
case "ClassDeclaration":
case "ClassExpression":
return concat(printClass(path, options, print));
2017-01-13 23:03:53 +03:00
case "TemplateElement":
return join(literalline, n.value.raw.split("\n"));
2017-01-13 23:03:53 +03:00
case "TemplateLiteral":
var expressions = path.map(print, "expressions");
2016-12-31 07:10:22 +03:00
2017-02-23 20:00:29 +03:00
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 ? "" : " ";
2017-03-01 20:37:02 +03:00
} else if (d.type === "if-break") {
return d.flatContents || "";
}
2017-02-23 20:00:29 +03:00
return d;
});
}
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
path.each(
function(childPath) {
var i = childPath.getName();
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(print(childPath));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
if (i < expressions.length) {
parts.push(
"${",
2017-02-23 20:57:51 +03:00
removeLines(expressions[i]),
lineSuffixBoundary,
"}"
);
2017-01-13 23:03:53 +03:00
}
},
"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);
// 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":
// Supertype of Block and Line.
case "Comment":
// Flow
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));
2017-01-13 23:03:53 +03:00
// Type Annotations for Facebook Flow, typically stripped out or
// transformed away before printing.
case "TypeAnnotation":
if (n.typeAnnotation) {
if (
n.typeAnnotation.type !== "FunctionTypeAnnotation" &&
!shouldTypeScriptTypeAvoidColon(path) &&
// TypeScript should not have a colon before type parameter constraints
!(path.getParentNode().type === "TypeParameter" &&
path.getParentNode().constraint)
) {
2017-01-13 23:03:53 +03:00
parts.push(": ");
}
2016-12-31 22:38:58 +03:00
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "typeAnnotation"));
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return "";
case "TSTupleType":
2017-01-13 23:03:53 +03:00
case "TupleTypeAnnotation":
let typesField = n.type === "TSTupleType" ? "elementTypes" : "types"
return group(
concat([
"[",
indent(
concat([
softline,
printArrayItems(path, options, typesField, print)
])
),
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"),
";"
]);
case "DeclareModule":
return printFlowDeclaration(path, [
"module ",
path.call(print, "id"),
" ",
path.call(print, "body")
]);
case "DeclareModuleExports":
return printFlowDeclaration(path, [
"module.exports",
path.call(print, "typeAnnotation"),
";"
]);
case "DeclareVariable":
2017-01-28 18:50:22 +03:00
return printFlowDeclaration(path, ["var ", path.call(print, "id"), ";"]);
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;
var parent = path.getParentNode(0);
var isArrowFunctionTypeAnnotation = n.type === "TSFunctionType" || !((!getFlowVariance(parent, options) &&
2017-01-13 23:03:53 +03:00
!parent.optional &&
2017-02-23 20:57:51 +03:00
namedTypes.ObjectTypeProperty.check(parent)) ||
2017-01-13 23:03:53 +03:00
namedTypes.ObjectTypeCallProperty.check(parent) ||
namedTypes.DeclareFunction.check(path.getParentNode(2)));
2017-01-13 23:03:53 +03:00
var needsColon = isArrowFunctionTypeAnnotation &&
namedTypes.TypeAnnotation.check(parent);
if (isObjectTypePropertyAFunction(parent)) {
isArrowFunctionTypeAnnotation = true;
needsColon = true;
}
2017-01-13 23:03:53 +03:00
if (needsColon) {
parts.push(": ");
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
parts.push(path.call(print, "typeParameters"));
2016-12-31 07:10:22 +03:00
parts.push(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
);
}
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" ||
2017-02-16 06:56:11 +03:00
isFlowNodeStartingWithDeclare(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 "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 (
(n.types[i - 1].type === "ObjectTypeAnnotation" &&
n.types[i].type !== "ObjectTypeAnnotation") ||
(n.types[i - 1].type !== "ObjectTypeAnnotation" &&
n.types[i].type === "ObjectTypeAnnotation")
) {
// If you go from object to non-object or vis-versa, then inline it
result.push(" & ", types[i]);
} else {
// Otherwise go to the next line and indent
result.push(indent(concat([" &", line, 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
2017-03-04 02:39:37 +03:00
const shouldIndent = !(parent.type === "TypeAlias" &&
hasLeadingOwnLineComment(options.originalText, n));
//const token = isIntersection ? "&" : "|";
const code = concat([
ifBreak(concat([shouldIndent ? line : "", "| "])),
join(concat([line, "| "]), path.map(print, "types"))
]);
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":
var variance = getFlowVariance(n, options);
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":
var variance = getFlowVariance(n, options);
2017-01-13 23:03:53 +03:00
// TODO: This is a bad hack and we need a better way to know
// when to emit an arrow function or not.
var isFunction = !variance &&
!n.optional &&
2017-01-13 23:03:53 +03:00
n.value.type === "FunctionTypeAnnotation";
if (isObjectTypePropertyAFunction(n)) {
isFunction = true;
}
2017-01-13 23:03:53 +03:00
return concat([
n.static ? "static " : "",
variance || "",
2017-01-13 23:03:53 +03:00
path.call(print, "key"),
n.optional ? "?" : "",
isFunction ? "" : ": ",
path.call(print, "value")
]);
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);
} else {
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" ||
2017-02-16 06:56:11 +03:00
isFlowNodeStartingWithDeclare(n, options)
) {
parts.push("declare ");
}
2016-12-31 07:10:22 +03:00
parts.push(
"type ",
path.call(print, "id"),
path.call(print, "typeParameters"),
" =",
hasLeadingOwnLineComment(options.originalText, n.right)
? indent(
concat([hardline, path.call(print, "right")])
)
: concat([" ", path.call(print, "right")]),
";"
);
2016-12-31 07:10:22 +03:00
return concat(parts);
}
2017-01-13 23:03:53 +03:00
case "TypeCastExpression":
return concat([
"(",
path.call(print, "expression"),
path.call(print, "typeAnnotation"),
")"
]);
case "TypeParameterDeclaration":
case "TypeParameterInstantiation": {
const shouldInline =
n.params.length === 1 &&
n.params[0].type === "ObjectTypeAnnotation";
if (shouldInline) {
return concat(["<", join(", ", path.map(print, "params")), ">"]);
}
return group(concat([
"<",
indent(
concat([
softline,
join(concat([",", line]), path.map(print, "params")),
])
),
softline,
">"
]));
}
2017-01-13 23:03:53 +03:00
case "TypeParameter":
var variance = getFlowVariance(n, options);
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(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"));
}
2016-12-31 07:10:22 +03:00
2017-01-13 23:03:53 +03:00
return concat(parts);
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 "TSAnyKeyword":
return "any";
case "TSBooleanKeyword":
return "boolean";
case "TSNumberKeyword":
return "number";
case "TSObjectKeyword":
return "object";
case "TSStringKeyword":
return "string";
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":
parts.push(path.call(print, "name"));
parts.push(path.call(print, "typeAnnotation"));
return concat(parts);
case "TSTypeReference":
return concat([path.call(print, "typeName")]);
case "TSCallSignature":
return concat([
2017-04-05 23:28:02 +03:00
"(",
join(", ", path.map(print, "parameters")),
"): ",
2017-04-05 23:28:02 +03:00
path.call(print, "typeAnnotation"),
]);
case "TSConstructSignature":
return concat([
2017-04-05 23:28:02 +03:00
"new (",
join(", ", path.map(print, "parameters")),
"): ",
2017-04-05 23:28:02 +03:00
path.call(print, "typeAnnotation"),
]);
case "TSTypeQuery":
return concat(["typeof ", path.call(print, "exprName")]);
case "TSParenthesizedType":
return concat(["(", path.call(print, "typeAnnotation"), ")"]);
case "TSIndexSignature":
return concat([
2017-04-05 23:28:02 +03:00
"[",
// This should only contain a single element, however TypeScript parses
// it using parseDelimitedList that uses commas as delimiter.
2017-04-05 23:28:02 +03:00
join(", ", path.map(print, "parameters")),
"]: ",
2017-04-05 23:28:02 +03:00
path.call(print, "typeAnnotation"),
]);
2017-01-13 23:03:53 +03:00
// TODO
case "ClassHeritage":
// TODO
case "ComprehensionBlock":
// TODO
case "ComprehensionExpression":
// TODO
case "Glob":
// TODO
case "GeneratorExpression":
// TODO
case "LetStatement":
// TODO
case "LetExpression":
// TODO
case "GraphExpression":
// TODO
// XML types that nobody cares about or needs to print.
case "GraphIndexExpression":
case "XMLDefaultDeclaration":
case "XMLAnyName":
case "XMLQualifiedIdentifier":
case "XMLFunctionQualifiedIdentifier":
case "XMLAttributeSelector":
case "XMLFilterExpression":
case "XML":
case "XMLElement":
case "XMLList":
case "XMLEscape":
case "XMLText":
case "XMLStartTag":
case "XMLEndTag":
case "XMLPointTag":
case "XMLName":
case "XMLAttribute":
case "XMLCdata":
case "XMLComment":
case "XMLProcessingInstruction":
default:
debugger;
throw new Error("unknown type: " + JSON.stringify(n.type));
}
}
function printStatementSequence(path, options, print) {
2016-12-31 07:10:22 +03:00
let printed = [];
2016-12-31 22:38:58 +03:00
2017-03-16 23:26:44 +03:00
path.map(stmtPath => {
2017-01-05 06:27:25 +03:00
var 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
2017-01-05 06:27:25 +03:00
parts.push(stmtPrinted);
2016-12-31 07:10:22 +03:00
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 (
(key.type === "StringLiteral" ||
2017-02-23 20:57:51 +03:00
(key.type === "Literal" && typeof key.value === "string")) &&
2017-02-16 06:56:11 +03:00
isIdentifierName(key.value) &&
!node.computed &&
// There's a bug in the flow parser where it throws if there are
// unquoted unicode literals as keys. Let's quote them for now.
(options.parser !== "flow" || key.value.match(/[a-zA-Z0-9$_]/))
) {
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, p => key.value, options),
"key"
);
2017-01-11 08:48:49 +03:00
}
return path.call(print, "key");
}
function printMethod(path, options, print) {
var node = path.getNode();
var kind = node.kind;
var parts = [];
2016-12-31 22:38:58 +03:00
if (node.type === "ObjectMethod" || node.type === "ClassMethod") {
node.value = node;
} else {
namedTypes.FunctionExpression.assert(node.value);
}
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
var 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,
path.call(print, "value", "typeParameters"),
group(
concat([
2017-01-13 23:03:53 +03:00
path.call(
function(valuePath) {
return printFunctionParams(valuePath, print, options);
},
"value"
),
path.call(p => printReturnType(p, print), "value")
2017-02-03 19:50:51 +03:00
])
),
" ",
path.call(print, "value", "body")
);
2016-12-31 07:10:22 +03:00
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) {
var 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);
if (shouldGroupFirst || shouldGroupLastArg(args)) {
const shouldBreak = shouldGroupFirst
? printed.slice(1).some(willBreak)
: printed.slice(0, -1).some(willBreak);
return concat([
printed.some(willBreak) ? breakParent : "",
conditionalGroup(
[
concat(["(", join(concat([", "]), printed), ")"]),
shouldGroupFirst
? concat([
"(",
group(printed[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(printed), { 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([
"(",
2017-01-09 20:09:04 +03:00
indent(
2017-01-28 18:50:22 +03:00
concat([softline, join(concat([",", line]), printed)])
2017-01-09 20:09:04 +03:00
),
ifBreak(shouldPrintComma(options, "all") ? "," : ""),
softline,
")"
]),
{ shouldBreak: printed.some(willBreak) }
);
}
function printFunctionParams(path, print, options) {
var fun = path.getValue();
// namedTypes.Function.assert(fun);
var paramsField = fun.type === "TSFunctionType" ? "parameters" : "params";
var printed = path.map(print, paramsField);
2016-12-31 22:38:58 +03:00
if (fun.defaults) {
path.each(
function(defExprPath) {
var i = defExprPath.getName();
var p = printed[i];
2016-12-31 22:38:58 +03:00
if (p && defExprPath.getValue()) {
2017-01-28 18:50:22 +03:00
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]);
2017-01-20 21:12:37 +03:00
const canHaveTrailingComma = !(lastParam &&
2017-03-04 02:39:37 +03:00
lastParam.type === "RestElement") && !fun.rest;
// 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,
// }) ) => {
// })
const parent = path.getParentNode();
2017-03-04 02:39:37 +03:00
if (
(parent.type === "CallExpression" || parent.type === "NewExpression") &&
((util.getLast(parent.arguments) === path.getValue() &&
shouldGroupLastArg(parent.arguments)) ||
(parent.arguments[0] === path.getValue() &&
shouldGroupFirstArg(parent.arguments)))
2017-03-04 02:39:37 +03:00
) {
return concat(["(", join(", ", printed), ")"]);
}
// Single object destructuring should hug
//
// function({
// a,
// b,
// c
// }) {}
2017-03-22 20:20:42 +03:00
if (fun.params &&
fun.params.length === 1 &&
!fun.params[0].comments &&
(fun.params[0].type === "ObjectPattern" ||
fun.params[0].type === "FunctionTypeParam" &&
fun.params[0].typeAnnotation.type === "ObjectTypeAnnotation") &&
!fun.rest) {
return concat(["(", join(", ", printed), ")"]);
}
const isFlowShorthandWithOneArg = (isObjectTypePropertyAFunction(parent) ||
isTypeAnnotationAFunction(parent) || parent.type === "TypeAlias") &&
fun[paramsField].length === 1 && fun[paramsField][0].name === null && !fun.rest;
return concat([
isFlowShorthandWithOneArg ? "" : "(",
2017-01-09 20:09:04 +03:00
indent(
2017-01-28 18:50:22 +03:00
concat([softline, join(concat([",", line]), printed)])
2017-01-09 20:09:04 +03:00
),
2017-02-23 20:57:51 +03:00
ifBreak(
canHaveTrailingComma && shouldPrintComma(options, "all") ? "," : ""
),
softline,
isFlowShorthandWithOneArg ? "" : ")"
]);
}
function printFunctionDeclaration(path, print, options) {
var n = path.getValue();
var 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(
path.call(print, "typeParameters"),
group(
concat([
printFunctionParams(path, print, options),
printReturnType(path, print)
])
),
" ",
path.call(print, "body")
);
return concat(parts);
}
function printObjectMethod(path, options, print) {
var objMethod = path.getValue();
var parts = [];
2016-12-31 22:38:58 +03:00
if (objMethod.async) parts.push("async ");
2016-12-31 07:10:22 +03:00
if (objMethod.generator) parts.push("*");
2016-12-31 07:10:22 +03:00
if (
objMethod.method || objMethod.kind === "get" || objMethod.kind === "set"
) {
return printMethod(path, options, print);
}
2016-12-31 07:10:22 +03:00
var 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
if (objMethod.typeParameters) {
parts.push(path.call(print, "typeParameters"));
}
parts.push(
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
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 typeIsFunction(type) {
return type === "FunctionExpression" ||
type === "ArrowFunctionExpression" ||
type === "NewExpression";
}
function printExportDeclaration(path, options, print) {
const decl = path.getValue();
let parts = ["export "];
2016-12-31 22:38:58 +03:00
namedTypes.Declaration.assert(decl);
2016-12-31 07:10:22 +03:00
if (decl["default"] || decl.type === "ExportDefaultDeclaration") {
parts.push("default ");
}
2016-12-31 07:10:22 +03:00
2017-02-23 20:57:51 +03:00
parts.push(
comments.printDanglingComments(path, options, /* sameIndent */ true)
);
if (decl.declaration) {
parts.push(path.call(print, "declaration"));
2017-01-13 23:03:53 +03:00
if (
decl.type === "ExportDefaultDeclaration" &&
2017-02-16 06:56:11 +03:00
(decl.declaration.type !== "ClassDeclaration" &&
decl.declaration.type !== "FunctionDeclaration")
2017-01-13 23:03:53 +03:00
) {
parts.push(";");
}
} 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 {
let specifiers = [];
let defaultSpecifiers = [];
let 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"));
}
2017-01-13 23:03:53 +03:00
parts.push(";");
2017-01-09 20:09:04 +03:00
}
2016-12-31 07:10:22 +03:00
return concat(parts);
}
function printFlowDeclaration(path, parts) {
var 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, options) {
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 printClass(path, options, print) {
const n = path.getValue();
2017-01-28 18:50:22 +03:00
const parts = ["class"];
2016-12-31 22:38:58 +03:00
if (n.id) {
parts.push(" ", path.call(print, "id"), path.call(print, "typeParameters"));
}
2016-12-31 07:10:22 +03:00
const partsGroup = [];
if (n.superClass) {
partsGroup.push(
line,
"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) {
partsGroup.push(line, "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 ",
join(", ", path.map(print, "implements"))
);
}
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();
return concat(
n.computed
2017-01-28 18:50:22 +03:00
? [
"[",
group(
concat([
indent(concat([softline, property])),
2017-01-28 18:50:22 +03:00
softline
])
),
"]"
]
: [".", property]
);
}
// 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,
p => 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,
p => printMemberLookup(path, options, print),
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: 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()
var groups = [];
var currentGroup = [printedNodes[0]];
var 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
var hasSeenCallExpression = false;
for (; i < printedNodes.length; ++i) {
2017-02-03 19:50:51 +03:00
if (
hasSeenCallExpression && printedNodes[i].node.type === "MemberExpression"
) {
// [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 (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.
2017-02-16 06:56:11 +03:00
const shouldMerge = 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])|^[_$]+$/))) &&
groups.length >= 2;
function printGroup(printedGroup) {
return concat(printedGroup.map(tuple => tuple.printed));
}
function printIndentedGroup(groups) {
return indent(
group(concat([hardline, join(hardline, groups.map(printGroup))]))
);
}
const printedGroups = groups.map(printGroup);
const oneLine = concat(printedGroups);
const hasComment =
(groups.length >= 2 && groups[1][0].node.comments) ||
(groups.length >= 3 && groups[2][0].node.comments);
// 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 <= (shouldMerge ? 3 : 2) &&
!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 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;
} else {
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.
function printJSXChildren(path, options, print, jsxWhitespace) {
const n = path.getValue();
const children = [];
// using `map` instead of `each` because it provides `i`
path.map(
function(childPath, i) {
const child = childPath.getValue();
const isLiteral = namedTypes.Literal.check(child);
if (isLiteral && typeof child.value === "string") {
// There's a bug in the flow parser where it doesn't unescape the
// value field. To workaround this, we can use rawValue which is
// correctly escaped (since it parsed).
// We really want to use value and re-escape it ourself when possible
// though.
2017-02-02 02:29:42 +03:00
const partiallyEscapedValue = options.parser === "flow"
2017-01-20 21:12:37 +03:00
? child.raw
: util.htmlEscapeInsideAngleBracket(child.value);
2017-02-03 19:50:51 +03:00
const value = partiallyEscapedValue.replace(/\u00a0/g, "&nbsp;");
if (/\S/.test(value)) {
// treat each line of text as its own entity
value.split(/(\n\s*)/).forEach(line => {
const newlines = line.match(/\n/g);
if (newlines) {
children.push(hardline);
// allow one extra newline
if (newlines.length > 1) {
2017-01-28 18:50:22 +03:00
children.push(hardline);
}
return;
}
const beginSpace = /^\s+/.test(line);
if (beginSpace) {
children.push(jsxWhitespace);
children.push(softline);
}
const stripped = line.replace(/^\s+|\s+$/g, "");
if (stripped) {
children.push(stripped);
}
const endSpace = /\s+$/.test(line);
if (endSpace) {
children.push(softline);
children.push(jsxWhitespace);
}
});
if (!isLineNext(util.getLast(children))) {
children.push(softline);
}
} else if (/\n/.test(value)) {
children.push(hardline);
// allow one extra newline
if (value.match(/\n/g).length > 1) {
2017-01-28 18:50:22 +03:00
children.push(hardline);
}
} else if (/\s/.test(value)) {
// whitespace-only without newlines,
// eg; a single space separating two elements
children.push(jsxWhitespace);
children.push(softline);
}
} else {
children.push(print(childPath));
// add a line unless it's followed by a JSX newline
let next = n.children[i + 1];
if (!(next && /^\s*\n/.test(next.value))) {
children.push(softline);
}
}
},
"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;
}
// If no children, just print the opening element
const openingLines = path.call(print, "openingElement");
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 jsxWhitespace = options.singleQuote
? ifBreak("{' '}", " ")
: ifBreak('{" "}', " ");
const children = printJSXChildren(path, options, print, jsxWhitespace);
// Trim trailing lines, recording if there was a hardline
let numTrailingHard = 0;
while (children.length && isLineNext(util.getLast(children))) {
if (willBreak(util.getLast(children))) {
++numTrailingHard;
forcedBreak = true;
}
children.pop();
}
// allow one extra newline
if (numTrailingHard > 1) {
children.push(hardline);
}
// Trim leading lines, recording if there was a hardline
let numLeadingHard = 0;
while (children.length && isLineNext(children[0])) {
if (willBreak(children[0])) {
++numLeadingHard;
forcedBreak = true;
}
children.shift();
}
// allow one extra newline
if (numLeadingHard > 1) {
children.unshift(hardline);
}
// Group by line, recording if there was a hardline.
let groups = [[]]; // Initialize the first line's group
children.forEach((child, i) => {
// leading and trailing JSX whitespace don't go into a group
if (child === jsxWhitespace) {
if (i === 0) {
groups.unshift(child);
return;
} else if (i === children.length - 1) {
groups.push(child);
return;
}
}
let prev = children[i - 1];
if (prev && willBreak(prev)) {
forcedBreak = true;
// On a new line, so create a new group and put this element in it.
groups.push([child]);
} else {
// Not on a newline, so add this element to the current group.
util.getLast(groups).push(child);
}
// Ensure we record hardline of last element.
if (!forcedBreak && i === children.length - 1) {
if (willBreak(child)) forcedBreak = true;
}
});
const childrenGroupedByLine = [
hardline,
// Conditional groups suppress break propagation; we want to output
// hard lines without breaking up the entire jsx element.
// Note that leading and trailing JSX Whitespace don't go into a group.
2017-02-16 06:56:11 +03:00
concat(
groups.map(
contents =>
Array.isArray(contents)
? conditionalGroup([concat(contents)])
: contents
)
)
];
const closingLines = path.call(print, "closingElement");
const multiLineElem = group(
concat([
openingLines,
indent(
group(concat(childrenGroupedByLine), { shouldBreak: true })
),
hardline,
closingLines
])
);
if (forcedBreak) {
return multiLineElem;
}
return conditionalGroup([
2017-01-28 18:50:22 +03:00
group(concat([openingLines, concat(children), closingLines])),
multiLineElem
]);
}
function maybeWrapJSXElementInParens(path, elem, options) {
const parent = path.getParentNode();
if (!parent) return elem;
const NO_WRAP_PARENTS = {
ArrayExpression: true,
JSXElement: true,
JSXExpressionContainer: true,
ExpressionStatement: true,
CallExpression: true,
ConditionalExpression: true,
2017-01-28 18:50:22 +03:00
LogicalExpression: 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) {
2017-02-16 06:56:11 +03:00
return node.type === "LogicalExpression" &&
(node.right.type === "ObjectExpression" ||
node.right.type === "ArrayExpression");
}
// 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) {
let parts = [];
let 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(
2017-01-28 18:50:22 +03:00
left =>
printBinaryishExpressions(
left,
print,
options,
/* isNested */ true
),
"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();
2017-03-04 02:39:37 +03:00
const shouldGroup = 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, p => concat(parts), options);
}
} else {
// Our stopping case. Simply print the node normally.
parts.push(path.call(print));
}
return parts;
}
function printAssignment(printedLeft, operator, rightNode, printedRight, options) {
if (!rightNode) {
return printedLeft;
}
let printed;
if (hasLeadingOwnLineComment(options.originalText, rightNode)) {
printed = indent(
concat([hardline, printedRight])
);
} else if (
(isBinaryish(rightNode) && !shouldInlineLogicalExpression(rightNode)) ||
rightNode.type === "StringLiteral" ||
(rightNode.type === "Literal" &&
typeof rightNode.value === "string")
) {
printed = indent(
concat([line, printedRight])
);
} else {
printed = concat([" ", printedRight]);
}
return group(concat([
printedLeft,
" ",
operator,
printed,
]));
}
function adjustClause(clause, options, forceSpace) {
if (clause === "") {
return ";";
}
if (isCurlyBracket(clause) || forceSpace) {
2017-01-28 18:50:22 +03:00
return concat([" ", clause]);
}
2016-12-31 07:10:22 +03:00
return indent(concat([line, clause]));
}
function isCurlyBracket(doc) {
const str = getFirstString(doc);
return str === "{" || str === "{}";
}
function isEmptyBlock(doc) {
const str = getFirstString(doc);
return str === "{}";
}
function shouldTypeScriptTypeAvoidColon(path) {
// As the special TS nodes isn't returned by the node helpers,
// we use the stack directly to get the parent node.
const parent = path.stack[path.stack.length - 3]
switch (parent.type) {
case "TSFunctionType":
case "TSIndexSignature":
case "TSParenthesizedType":
case "TSCallSignature":
case "TSConstructSignature":
case "TSAsExpression":
return true
default:
return false
}
}
function nodeStr(node, options) {
const str = node.value;
isString.assert(str);
2016-12-31 07:10:22 +03:00
// Workaround a bug in the Javascript version of the flow parser where
// astral unicode characters like \uD801\uDC28 are incorrectly parsed as
// a sequence of \uFFFD.
2017-02-03 19:50:51 +03:00
if (options.parser === "flow" && str.indexOf("\ufffd") !== -1) {
return node.raw;
}
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;
// 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.
if (rawContent.includes(preferred.quote)) {
const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
}
const enclosingQuote = shouldUseAlternateQuote
? alternate.quote
: preferred.quote;
// 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;
}
// Otherwise return the escape or unescaped quote as-is.
return match;
});
return enclosingQuote + newContent + enclosingQuote;
}
function printNumber(rawNumber) {
2017-02-03 19:50:51 +03:00
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 trailing dot.
.replace(/\.(?=e|$)/, "");
}
function isFirstStatement(path) {
const parent = path.getParentNode();
const node = path.getValue();
const body = parent.body;
return body && body[0] === node;
}
function isLastStatement(path) {
const parent = path.getParentNode();
const node = path.getValue();
const body = parent.body;
return body && body[body.length - 1] === node;
}
function hasLeadingOwnLineComment(text, node) {
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;
}
// 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;
}
const hasCommentableLeftSide = argument.type === "BinaryExpression" ||
argument.type === "LogicalExpression" ||
argument.type === "ConditionalExpression" ||
argument.type === "CallExpression" ||
argument.type === "MemberExpression" ||
argument.type === "SequenceExpression" ||
argument.type === "TaggedTemplateExpression";
if (hasCommentableLeftSide) {
const getLeftSide = (node) => {
if (node.expressions) {
return node.expressions[0];
}
return node.left || node.test || node.callee || node.object || node.tag;
}
let leftMost = argument;
let newLeftMost;
while (newLeftMost = getLeftSide(leftMost)) {
leftMost = newLeftMost;
if (hasLeadingOwnLineComment(options.originalText, leftMost)) {
return true;
}
}
}
return false;
}
// 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 &&
util.locStart(node.key) !== util.locStart(node.value);
}
// 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 &&
util.locStart(node) !== util.locStart(node.typeAnnotation)
}
function isFlowNodeStartingWithDeclare(node, options) {
if (options.parser !== "flow") {
return false;
}
return options.originalText
.slice(0, util.locStart(node))
.match(/declare\s*$/);
}
function printArrayItems(path, options, printPath, print) {
const printedElements = [];
let separatorParts = [];
path.each(
function(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 printAstToDoc(ast, options) {
function printGenerically(path) {
return comments.printComments(
path,
p => genericPrint(p, options, printGenerically),
options
);
}
const doc = printGenerically(FastPath.from(ast));
docUtils.propagateBreaks(doc);
return doc;
}
2017-01-20 21:12:37 +03:00
module.exports = { printAstToDoc };