blob: 4656edf36c0682cf7e39b40fbb12886efaa65a91 [file] [log] [blame]
var assert = require("assert");
var sourceMap = require("source-map");
var printComments = require("./comments").printComments;
var linesModule = require("./lines");
var fromString = linesModule.fromString;
var concat = linesModule.concat;
var normalizeOptions = require("./options").normalize;
var getReprinter = require("./patcher").getReprinter;
var types = require("./types");
var namedTypes = types.namedTypes;
var isString = types.builtInTypes.string;
var isObject = types.builtInTypes.object;
var FastPath = require("./fast-path");
var util = require("./util");
function PrintResult(code, sourceMap) {
assert.ok(this instanceof PrintResult);
isString.assert(code);
this.code = code;
if (sourceMap) {
isObject.assert(sourceMap);
this.map = sourceMap;
}
}
var PRp = PrintResult.prototype;
var warnedAboutToString = false;
PRp.toString = function() {
if (!warnedAboutToString) {
console.warn(
"Deprecation warning: recast.print now returns an object with " +
"a .code property. You appear to be treating the object as a " +
"string, which might still work but is strongly discouraged."
);
warnedAboutToString = true;
}
return this.code;
};
var emptyPrintResult = new PrintResult("");
function Printer(originalOptions) {
assert.ok(this instanceof Printer);
var explicitTabWidth = originalOptions && originalOptions.tabWidth;
var options = normalizeOptions(originalOptions);
assert.notStrictEqual(options, originalOptions);
// It's common for client code to pass the same options into both
// recast.parse and recast.print, but the Printer doesn't need (and
// can be confused by) options.sourceFileName, so we null it out.
options.sourceFileName = null;
function printWithComments(path) {
assert.ok(path instanceof FastPath);
return printComments(path, print);
}
function print(path, includeComments) {
if (includeComments)
return printWithComments(path);
assert.ok(path instanceof FastPath);
if (!explicitTabWidth) {
var oldTabWidth = options.tabWidth;
var loc = path.getNode().loc;
if (loc && loc.lines && loc.lines.guessTabWidth) {
options.tabWidth = loc.lines.guessTabWidth();
var lines = maybeReprint(path);
options.tabWidth = oldTabWidth;
return lines;
}
}
return maybeReprint(path);
}
function maybeReprint(path) {
var reprinter = getReprinter(path);
if (reprinter) {
// Since the print function that we pass to the reprinter will
// be used to print "new" nodes, it's tempting to think we
// should pass printRootGenerically instead of print, to avoid
// calling maybeReprint again, but that would be a mistake
// because the new nodes might not be entirely new, but merely
// moved from elsewhere in the AST. The print function is the
// right choice because it gives us the opportunity to reprint
// such nodes using their original source.
return maybeAddParens(path, reprinter(print));
}
return printRootGenerically(path);
}
// Print the root node generically, but then resume reprinting its
// children non-generically.
function printRootGenerically(path, includeComments) {
return includeComments
? printComments(path, printRootGenerically)
: genericPrint(path, options, printWithComments);
}
// Print the entire AST generically.
function printGenerically(path) {
return genericPrint(path, options, printGenerically);
}
this.print = function(ast) {
if (!ast) {
return emptyPrintResult;
}
var lines = print(FastPath.from(ast), true);
return new PrintResult(
lines.toString(options),
util.composeSourceMaps(
options.inputSourceMap,
lines.getSourceMap(
options.sourceMapName,
options.sourceRoot
)
)
);
};
this.printGenerically = function(ast) {
if (!ast) {
return emptyPrintResult;
}
var path = FastPath.from(ast);
var oldReuseWhitespace = options.reuseWhitespace;
// Do not reuse whitespace (or anything else, for that matter)
// when printing generically.
options.reuseWhitespace = false;
// TODO Allow printing of comments?
var pr = new PrintResult(printGenerically(path).toString(options));
options.reuseWhitespace = oldReuseWhitespace;
return pr;
};
}
exports.Printer = Printer;
function maybeAddParens(path, lines) {
return path.needsParens() ? concat(["(", lines, ")"]) : lines;
}
function genericPrint(path, options, printPath) {
assert.ok(path instanceof FastPath);
var node = path.getValue();
var parts = [];
var needsParens = false;
var linesWithoutParens =
genericPrintNoParens(path, options, printPath);
if (! node || linesWithoutParens.isEmpty()) {
return linesWithoutParens;
}
if (node.decorators &&
node.decorators.length > 0 &&
// If the parent node is an export declaration, it will be
// responsible for printing node.decorators.
! util.getParentExportDeclaration(path)) {
path.each(function(decoratorPath) {
parts.push(printPath(decoratorPath), "\n");
}, "decorators");
} else if (util.isExportDeclaration(node) &&
node.declaration &&
node.declaration.decorators) {
// Export declarations are responsible for printing any decorators
// that logically apply to node.declaration.
path.each(function(decoratorPath) {
parts.push(printPath(decoratorPath), "\n");
}, "declaration", "decorators");
} else {
// Nodes with decorators can't have parentheses, so we can avoid
// computing path.needsParens() except in this case.
needsParens = path.needsParens();
}
if (needsParens) {
parts.unshift("(");
}
parts.push(linesWithoutParens);
if (needsParens) {
parts.push(")");
}
return concat(parts);
}
function genericPrintNoParens(path, options, print) {
var n = path.getValue();
if (!n) {
return fromString("");
}
if (typeof n === "string") {
return fromString(n, options);
}
namedTypes.Printable.assert(n);
var parts = [];
switch (n.type) {
case "File":
return path.call(print, "program");
case "Program":
// Babel 6
if (n.directives) {
path.each(function(childPath) {
parts.push(print(childPath), ";\n");
}, "directives");
}
parts.push(path.call(function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
}, "body"));
return concat(parts);
case "Noop": // Babel extension.
case "EmptyStatement":
return fromString("");
case "ExpressionStatement":
return concat([path.call(print, "expression"), ";"]);
case "ParenthesizedExpression": // Babel extension.
return concat(["(", path.call(print, "expression"), ")"]);
case "BinaryExpression":
case "LogicalExpression":
case "AssignmentExpression":
return fromString(" ").join([
path.call(print, "left"),
n.operator,
path.call(print, "right")
]);
case "AssignmentPattern":
return concat([
path.call(print, "left"),
" = ",
path.call(print, "right")
]);
case "MemberExpression":
parts.push(path.call(print, "object"));
var property = path.call(print, "property");
if (n.computed) {
parts.push("[", property, "]");
} else {
parts.push(".", property);
}
return concat(parts);
case "MetaProperty":
return concat([
path.call(print, "meta"),
".",
path.call(print, "property")
]);
case "BindExpression":
if (n.object) {
parts.push(path.call(print, "object"));
}
parts.push("::", path.call(print, "callee"));
return concat(parts);
case "Path":
return fromString(".").join(n.body);
case "Identifier":
return concat([
fromString(n.name, options),
path.call(print, "typeAnnotation")
]);
case "SpreadElement":
case "SpreadElementPattern":
case "RestProperty": // Babel 6 for ObjectPattern
case "SpreadProperty":
case "SpreadPropertyPattern":
case "RestElement":
return concat(["...", path.call(print, "argument")]);
case "FunctionDeclaration":
case "FunctionExpression":
if (n.async)
parts.push("async ");
parts.push("function");
if (n.generator)
parts.push("*");
if (n.id) {
parts.push(
" ",
path.call(print, "id"),
path.call(print, "typeParameters")
);
}
parts.push(
"(",
printFunctionParams(path, options, print),
")",
path.call(print, "returnType"),
" ",
path.call(print, "body")
);
return concat(parts);
case "ArrowFunctionExpression":
if (n.async)
parts.push("async ");
if (n.typeParameters) {
parts.push(path.call(print, "typeParameters"));
}
if (
!options.arrowParensAlways &&
n.params.length === 1 &&
!n.rest &&
n.params[0].type === 'Identifier' &&
!n.params[0].typeAnnotation &&
!n.returnType
) {
parts.push(path.call(print, "params", 0));
} else {
parts.push(
"(",
printFunctionParams(path, options, print),
")",
path.call(print, "returnType")
);
}
parts.push(" => ", path.call(print, "body"));
return concat(parts);
case "MethodDefinition":
if (n.static) {
parts.push("static ");
}
parts.push(printMethod(path, options, print));
return concat(parts);
case "YieldExpression":
parts.push("yield");
if (n.delegate)
parts.push("*");
if (n.argument)
parts.push(" ", path.call(print, "argument"));
return concat(parts);
case "AwaitExpression":
parts.push("await");
if (n.all)
parts.push("*");
if (n.argument)
parts.push(" ", path.call(print, "argument"));
return concat(parts);
case "ModuleDeclaration":
parts.push("module", path.call(print, "id"));
if (n.source) {
assert.ok(!n.body);
parts.push("from", path.call(print, "source"));
} else {
parts.push(path.call(print, "body"));
}
return fromString(" ").join(parts);
case "ImportSpecifier":
if (n.imported) {
parts.push(path.call(print, "imported"));
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"));
if (n.name) {
parts.push(" as ", path.call(print, "name"));
}
}
return concat(parts);
case "ExportSpecifier":
if (n.local) {
parts.push(path.call(print, "local"));
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"));
if (n.name) {
parts.push(" as ", path.call(print, "name"));
}
}
return concat(parts);
case "ExportBatchSpecifier":
return fromString("*");
case "ImportNamespaceSpecifier":
parts.push("* as ");
if (n.local) {
parts.push(path.call(print, "local"));
} else if (n.id) {
parts.push(path.call(print, "id"));
}
return concat(parts);
case "ImportDefaultSpecifier":
if (n.local) {
return path.call(print, "local");
}
return path.call(print, "id");
case "ExportDeclaration":
case "ExportDefaultDeclaration":
case "ExportNamedDeclaration":
return printExportDeclaration(path, options, print);
case "ExportAllDeclaration":
parts.push("export *");
if (n.exported) {
parts.push(" as ", path.call(print, "exported"));
}
parts.push(
" from ",
path.call(print, "source")
);
return concat(parts);
case "ExportNamespaceSpecifier":
return concat(["* as ", path.call(print, "exported")]);
case "ExportDefaultSpecifier":
return path.call(print, "exported");
case "Import":
return fromString("import", options);
case "ImportDeclaration":
parts.push("import ");
if (n.importKind && n.importKind !== "value") {
parts.push(n.importKind + " ");
}
if (n.specifiers &&
n.specifiers.length > 0) {
var foundImportSpecifier = false;
path.each(function(specifierPath) {
var i = specifierPath.getName();
if (i > 0) {
parts.push(", ");
}
var value = specifierPath.getValue();
if (namedTypes.ImportDefaultSpecifier.check(value) ||
namedTypes.ImportNamespaceSpecifier.check(value)) {
assert.strictEqual(foundImportSpecifier, false);
} else {
namedTypes.ImportSpecifier.assert(value);
if (!foundImportSpecifier) {
foundImportSpecifier = true;
parts.push(
options.objectCurlySpacing ? "{ " : "{"
);
}
}
parts.push(print(specifierPath));
}, "specifiers");
if (foundImportSpecifier) {
parts.push(
options.objectCurlySpacing ? " }" : "}"
);
}
parts.push(" from ");
}
parts.push(path.call(print, "source"), ";");
return concat(parts);
case "BlockStatement":
var naked = path.call(function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
}, "body");
if (naked.isEmpty()) {
if (!n.directives || n.directives.length === 0) {
return fromString("{}");
}
}
parts.push("{\n");
// Babel 6
if (n.directives) {
path.each(function(childPath) {
parts.push(
print(childPath).indent(options.tabWidth),
";",
n.directives.length > 1 || !naked.isEmpty() ? "\n" : ""
);
}, "directives");
}
parts.push(naked.indent(options.tabWidth));
parts.push("\n}");
return concat(parts);
case "ReturnStatement":
parts.push("return");
if (n.argument) {
var argLines = path.call(print, "argument");
if (argLines.startsWithComment() ||
(argLines.length > 1 &&
namedTypes.JSXElement &&
namedTypes.JSXElement.check(n.argument)
)) {
parts.push(
" (\n",
argLines.indent(options.tabWidth),
"\n)"
);
} else {
parts.push(" ", argLines);
}
}
parts.push(";");
return concat(parts);
case "CallExpression":
return concat([
path.call(print, "callee"),
printArgumentsList(path, options, print)
]);
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation":
var allowBreak = false;
var isTypeAnnotation = n.type === "ObjectTypeAnnotation";
var separator = options.flowObjectCommas ? "," : (isTypeAnnotation ? ";" : ",");
var fields = [];
if (isTypeAnnotation) {
fields.push("indexers", "callProperties");
}
fields.push("properties");
var len = 0;
fields.forEach(function(field) {
len += n[field].length;
});
var oneLine = (isTypeAnnotation && len === 1) || len === 0;
var leftBrace = n.exact ? "{|" : "{";
var rightBrace = n.exact ? "|}" : "}";
parts.push(oneLine ? leftBrace : leftBrace + "\n");
var leftBraceIndex = parts.length - 1;
var i = 0;
fields.forEach(function(field) {
path.each(function(childPath) {
var lines = print(childPath);
if (!oneLine) {
lines = lines.indent(options.tabWidth);
}
var multiLine = !isTypeAnnotation && lines.length > 1;
if (multiLine && allowBreak) {
// Similar to the logic for BlockStatement.
parts.push("\n");
}
parts.push(lines);
if (i < len - 1) {
// Add an extra line break if the previous object property
// had a multi-line value.
parts.push(separator + (multiLine ? "\n\n" : "\n"));
allowBreak = !multiLine;
} else if (len !== 1 && isTypeAnnotation) {
parts.push(separator);
} else if (!oneLine && util.isTrailingCommaEnabled(options, "objects")) {
parts.push(separator);
}
i++;
}, field);
});
parts.push(oneLine ? rightBrace : "\n" + rightBrace);
if (i !== 0 && oneLine && options.objectCurlySpacing) {
parts[leftBraceIndex] = leftBrace + " ";
parts[parts.length - 1] = " " + rightBrace;
}
return concat(parts);
case "PropertyPattern":
return concat([
path.call(print, "key"),
": ",
path.call(print, "pattern")
]);
case "ObjectProperty": // Babel 6
case "Property": // Non-standard AST node type.
if (n.method || n.kind === "get" || n.kind === "set") {
return printMethod(path, options, print);
}
var key = path.call(print, "key");
if (n.computed) {
parts.push("[", key, "]");
} else {
parts.push(key);
}
if (! n.shorthand) {
parts.push(": ", path.call(print, "value"));
}
return concat(parts);
case "ClassMethod": // Babel 6
if (n.static) {
parts.push("static ");
}
return concat([parts, printObjectMethod(path, options, print)]);
case "ObjectMethod": // Babel 6
return printObjectMethod(path, options, print);
case "Decorator":
return concat(["@", path.call(print, "expression")]);
case "ArrayExpression":
case "ArrayPattern":
var elems = n.elements,
len = elems.length;
var printed = path.map(print, "elements");
var joined = fromString(", ").join(printed);
var oneLine = joined.getLineLength(1) <= options.wrapColumn;
if (oneLine) {
if (options.arrayBracketSpacing) {
parts.push("[ ");
} else {
parts.push("[");
}
} else {
parts.push("[\n");
}
path.each(function(elemPath) {
var i = elemPath.getName();
var elem = elemPath.getValue();
if (!elem) {
// If the array expression ends with a hole, that hole
// will be ignored by the interpreter, but if it ends with
// two (or more) holes, we need to write out two (or more)
// commas so that the resulting code is interpreted with
// both (all) of the holes.
parts.push(",");
} else {
var lines = printed[i];
if (oneLine) {
if (i > 0)
parts.push(" ");
} else {
lines = lines.indent(options.tabWidth);
}
parts.push(lines);
if (i < len - 1 || (!oneLine && util.isTrailingCommaEnabled(options, "arrays")))
parts.push(",");
if (!oneLine)
parts.push("\n");
}
}, "elements");
if (oneLine && options.arrayBracketSpacing) {
parts.push(" ]");
} else {
parts.push("]");
}
return concat(parts);
case "SequenceExpression":
return fromString(", ").join(path.map(print, "expressions"));
case "ThisExpression":
return fromString("this");
case "Super":
return fromString("super");
case "NullLiteral": // Babel 6 Literal split
return fromString("null");
case "RegExpLiteral": // Babel 6 Literal split
return fromString(n.extra.raw);
case "BooleanLiteral": // Babel 6 Literal split
case "NumericLiteral": // Babel 6 Literal split
case "StringLiteral": // Babel 6 Literal split
case "Literal":
if (typeof n.value !== "string")
return fromString(n.value, options);
return fromString(nodeStr(n.value, options), options);
case "Directive": // Babel 6
return path.call(print, "value");
case "DirectiveLiteral": // Babel 6
return fromString(nodeStr(n.value, options));
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 fromString(nodeStr(n.value, options), options);
case "UnaryExpression":
parts.push(n.operator);
if (/[a-z]$/.test(n.operator))
parts.push(" ");
parts.push(path.call(print, "argument"));
return concat(parts);
case "UpdateExpression":
parts.push(
path.call(print, "argument"),
n.operator
);
if (n.prefix)
parts.reverse();
return concat(parts);
case "ConditionalExpression":
return concat([
"(", path.call(print, "test"),
" ? ", path.call(print, "consequent"),
" : ", path.call(print, "alternate"), ")"
]);
case "NewExpression":
parts.push("new ", path.call(print, "callee"));
var args = n.arguments;
if (args) {
parts.push(printArgumentsList(path, options, print));
}
return concat(parts);
case "VariableDeclaration":
parts.push(n.kind, " ");
var maxLen = 0;
var printed = path.map(function(childPath) {
var lines = print(childPath);
maxLen = Math.max(lines.length, maxLen);
return lines;
}, "declarations");
if (maxLen === 1) {
parts.push(fromString(", ").join(printed));
} else if (printed.length > 1 ) {
parts.push(
fromString(",\n").join(printed)
.indentTail(n.kind.length + 1)
);
} else {
parts.push(printed[0]);
}
// We generally want to terminate all variable declarations with a
// semicolon, except when they are children of for loops.
var parentNode = path.getParentNode();
if (!namedTypes.ForStatement.check(parentNode) &&
!namedTypes.ForInStatement.check(parentNode) &&
!(namedTypes.ForOfStatement &&
namedTypes.ForOfStatement.check(parentNode)) &&
!(namedTypes.ForAwaitStatement &&
namedTypes.ForAwaitStatement.check(parentNode))) {
parts.push(";");
}
return concat(parts);
case "VariableDeclarator":
return n.init ? fromString(" = ").join([
path.call(print, "id"),
path.call(print, "init")
]) : path.call(print, "id");
case "WithStatement":
return concat([
"with (",
path.call(print, "object"),
") ",
path.call(print, "body")
]);
case "IfStatement":
var con = adjustClause(path.call(print, "consequent"), options),
parts = ["if (", path.call(print, "test"), ")", con];
if (n.alternate)
parts.push(
endsWithBrace(con) ? " else" : "\nelse",
adjustClause(path.call(print, "alternate"), options));
return concat(parts);
case "ForStatement":
// TODO Get the for (;;) case right.
var init = path.call(print, "init"),
sep = init.length > 1 ? ";\n" : "; ",
forParen = "for (",
indented = fromString(sep).join([
init,
path.call(print, "test"),
path.call(print, "update")
]).indentTail(forParen.length),
head = concat([forParen, indented, ")"]),
clause = adjustClause(path.call(print, "body"), options),
parts = [head];
if (head.length > 1) {
parts.push("\n");
clause = clause.trimLeft();
}
parts.push(clause);
return concat(parts);
case "WhileStatement":
return concat([
"while (",
path.call(print, "test"),
")",
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)
]);
case "ForOfStatement":
return concat([
"for (",
path.call(print, "left"),
" of ",
path.call(print, "right"),
")",
adjustClause(path.call(print, "body"), options)
]);
case "ForAwaitStatement":
return concat([
"for await (",
path.call(print, "left"),
" of ",
path.call(print, "right"),
")",
adjustClause(path.call(print, "body"), options)
]);
case "DoWhileStatement":
var doBody = concat([
"do",
adjustClause(path.call(print, "body"), options)
]), parts = [doBody];
if (endsWithBrace(doBody))
parts.push(" while");
else
parts.push("\nwhile");
parts.push(" (", path.call(print, "test"), ");");
return concat(parts);
case "DoExpression":
var statements = path.call(function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
}, "body");
return concat([
"do {\n",
statements.indent(options.tabWidth),
"\n}"
]);
case "BreakStatement":
parts.push("break");
if (n.label)
parts.push(" ", path.call(print, "label"));
parts.push(";");
return concat(parts);
case "ContinueStatement":
parts.push("continue");
if (n.label)
parts.push(" ", path.call(print, "label"));
parts.push(";");
return concat(parts);
case "LabeledStatement":
return concat([
path.call(print, "label"),
":\n",
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");
}
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"));
parts.push(") ", path.call(print, "body"));
return concat(parts);
case "ThrowStatement":
return concat(["throw ", path.call(print, "argument"), ";"]);
case "SwitchStatement":
return concat([
"switch (",
path.call(print, "discriminant"),
") {\n",
fromString("\n").join(path.map(print, "cases")),
"\n}"
]);
// Note: ignoring n.lexical because it has no printing consequences.
case "SwitchCase":
if (n.test)
parts.push("case ", path.call(print, "test"), ":");
else
parts.push("default:");
if (n.consequent.length > 0) {
parts.push("\n", path.call(function(consequentPath) {
return printStatementSequence(consequentPath, options, print);
}, "consequent").indent(options.tabWidth));
}
return concat(parts);
case "DebuggerStatement":
return fromString("debugger;");
// JSX extensions below.
case "JSXAttribute":
parts.push(path.call(print, "name"));
if (n.value)
parts.push("=", path.call(print, "value"));
return concat(parts);
case "JSXIdentifier":
return fromString(n.name, options);
case "JSXNamespacedName":
return fromString(":").join([
path.call(print, "namespace"),
path.call(print, "name")
]);
case "JSXMemberExpression":
return fromString(".").join([
path.call(print, "object"),
path.call(print, "property")
]);
case "JSXSpreadAttribute":
return concat(["{...", path.call(print, "argument"), "}"]);
case "JSXExpressionContainer":
return concat(["{", path.call(print, "expression"), "}"]);
case "JSXElement":
var openingLines = path.call(print, "openingElement");
if (n.openingElement.selfClosing) {
assert.ok(!n.closingElement);
return openingLines;
}
var childLines = concat(
path.map(function(childPath) {
var child = childPath.getValue();
if (namedTypes.Literal.check(child) &&
typeof child.value === "string") {
if (/\S/.test(child.value)) {
return child.value.replace(/^\s+|\s+$/g, "");
} else if (/\n/.test(child.value)) {
return "\n";
}
}
return print(childPath);
}, "children")
).indentTail(options.tabWidth);
var closingLines = path.call(print, "closingElement");
return concat([
openingLines,
childLines,
closingLines
]);
case "JSXOpeningElement":
parts.push("<", path.call(print, "name"));
var attrParts = [];
path.each(function(attrPath) {
attrParts.push(" ", print(attrPath));
}, "attributes");
var attrLines = concat(attrParts);
var needLineWrap = (
attrLines.length > 1 ||
attrLines.getLineLength(1) > options.wrapColumn
);
if (needLineWrap) {
attrParts.forEach(function(part, i) {
if (part === " ") {
assert.strictEqual(i % 2, 0);
attrParts[i] = "\n";
}
});
attrLines = concat(attrParts).indentTail(options.tabWidth);
}
parts.push(attrLines, n.selfClosing ? " />" : ">");
return concat(parts);
case "JSXClosingElement":
return concat(["</", path.call(print, "name"), ">"]);
case "JSXText":
return fromString(n.value, options);
case "JSXEmptyExpression":
return fromString("");
case "TypeAnnotatedIdentifier":
return concat([
path.call(print, "annotation"),
" ",
path.call(print, "identifier")
]);
case "ClassBody":
if (n.body.length === 0) {
return fromString("{}");
}
return concat([
"{\n",
path.call(function(bodyPath) {
return printStatementSequence(bodyPath, options, print);
}, "body").indent(options.tabWidth),
"\n}"
]);
case "ClassPropertyDefinition":
parts.push("static ", path.call(print, "definition"));
if (!namedTypes.MethodDefinition.check(n.definition))
parts.push(";");
return concat(parts);
case "ClassProperty":
if (n.static)
parts.push("static ");
var key = path.call(print, "key");
if (n.computed) {
key = concat(["[", key, "]"]);
} else if (n.variance === "plus") {
key = concat(["+", key]);
} else if (n.variance === "minus") {
key = concat(["-", key]);
}
parts.push(key);
if (n.typeAnnotation)
parts.push(path.call(print, "typeAnnotation"));
if (n.value)
parts.push(" = ", path.call(print, "value"));
parts.push(";");
return concat(parts);
case "ClassDeclaration":
case "ClassExpression":
parts.push("class");
if (n.id) {
parts.push(
" ",
path.call(print, "id"),
path.call(print, "typeParameters")
);
}
if (n.superClass) {
parts.push(
" extends ",
path.call(print, "superClass"),
path.call(print, "superTypeParameters")
);
}
if (n["implements"] && n['implements'].length > 0) {
parts.push(
" implements ",
fromString(", ").join(path.map(print, "implements"))
);
}
parts.push(" ", path.call(print, "body"));
return concat(parts);
case "TemplateElement":
return fromString(n.value.raw, options).lockIndentTail();
case "TemplateLiteral":
var expressions = path.map(print, "expressions");
parts.push("`");
path.each(function(childPath) {
var i = childPath.getName();
parts.push(print(childPath));
if (i < expressions.length) {
parts.push("${", expressions[i], "}");
}
}, "quasis");
parts.push("`");
return concat(parts).lockIndentTail();
case "TaggedTemplateExpression":
return concat([
path.call(print, "tag"),
path.call(print, "quasi")
]);
// These types are unprintable because they serve as abstract
// supertypes for other (printable) types.
case "Node":
case "Printable":
case "SourceLocation":
case "Position":
case "Statement":
case "Function":
case "Pattern":
case "Expression":
case "Declaration":
case "Specifier":
case "NamedSpecifier":
case "Comment": // Supertype of Block and Line.
case "MemberTypeAnnotation": // Flow
case "TupleTypeAnnotation": // Flow
case "Type": // Flow
throw new Error("unprintable type: " + JSON.stringify(n.type));
case "CommentBlock": // Babel block comment.
case "Block": // Esprima block comment.
return concat(["/*", fromString(n.value, options), "*/"]);
case "CommentLine": // Babel line comment.
case "Line": // Esprima line comment.
return concat(["//", fromString(n.value, options)]);
// Type Annotations for Facebook Flow, typically stripped out or
// transformed away before printing.
case "TypeAnnotation":
if (n.typeAnnotation) {
if (n.typeAnnotation.type !== "FunctionTypeAnnotation") {
parts.push(": ");
}
parts.push(path.call(print, "typeAnnotation"));
return concat(parts);
}
return fromString("");
case "ExistentialTypeParam":
case "ExistsTypeAnnotation":
return fromString("*", options);
case "EmptyTypeAnnotation":
return fromString("empty", options);
case "AnyTypeAnnotation":
return fromString("any", options);
case "MixedTypeAnnotation":
return fromString("mixed", options);
case "ArrayTypeAnnotation":
return concat([
path.call(print, "elementType"),
"[]"
]);
case "BooleanTypeAnnotation":
return fromString("boolean", options);
case "BooleanLiteralTypeAnnotation":
assert.strictEqual(typeof n.value, "boolean");
return fromString("" + n.value, options);
case "DeclareClass":
return printFlowDeclaration(path, [
"class ",
path.call(print, "id"),
" ",
path.call(print, "body"),
]);
case "DeclareFunction":
return printFlowDeclaration(path, [
"function ",
path.call(print, "id"),
";"
]);
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":
return printFlowDeclaration(path, [
"var ",
path.call(print, "id"),
";"
]);
case "DeclareExportDeclaration":
case "DeclareExportAllDeclaration":
return concat([
"declare ",
printExportDeclaration(path, options, print)
]);
case "FunctionTypeAnnotation":
// FunctionTypeAnnotation is ambiguous:
// declare function(a: B): void; OR
// var A: (a: B) => void;
var parent = path.getParentNode(0);
var isArrowFunctionTypeAnnotation = !(
namedTypes.ObjectTypeCallProperty.check(parent) ||
namedTypes.DeclareFunction.check(path.getParentNode(2))
);
var needsColon =
isArrowFunctionTypeAnnotation &&
!namedTypes.FunctionTypeParam.check(parent);
if (needsColon) {
parts.push(": ");
}
parts.push(
"(",
fromString(", ").join(path.map(print, "params")),
")"
);
// The returnType is not wrapped in a TypeAnnotation, so the colon
// needs to be added separately.
if (n.returnType) {
parts.push(
isArrowFunctionTypeAnnotation ? " => " : ": ",
path.call(print, "returnType")
);
}
return concat(parts);
case "FunctionTypeParam":
return concat([
path.call(print, "name"),
n.optional ? '?' : '',
": ",
path.call(print, "typeAnnotation"),
]);
case "GenericTypeAnnotation":
return concat([
path.call(print, "id"),
path.call(print, "typeParameters")
]);
case "DeclareInterface":
parts.push("declare ");
case "InterfaceDeclaration":
parts.push(
fromString("interface ", options),
path.call(print, "id"),
path.call(print, "typeParameters"),
" "
);
if (n["extends"]) {
parts.push(
"extends ",
fromString(", ").join(path.map(print, "extends"))
);
}
parts.push(" ", path.call(print, "body"));
return concat(parts);
case "ClassImplements":
case "InterfaceExtends":
return concat([
path.call(print, "id"),
path.call(print, "typeParameters")
]);
case "IntersectionTypeAnnotation":
return fromString(" & ").join(path.map(print, "types"));
case "NullableTypeAnnotation":
return concat([
"?",
path.call(print, "typeAnnotation")
]);
case "NullLiteralTypeAnnotation":
return fromString("null", options);
case "ThisTypeAnnotation":
return fromString("this", options);
case "NumberTypeAnnotation":
return fromString("number", options);
case "ObjectTypeCallProperty":
return path.call(print, "value");
case "ObjectTypeIndexer":
var variance =
n.variance === "plus" ? "+" :
n.variance === "minus" ? "-" : "";
return concat([
variance,
"[",
path.call(print, "id"),
": ",
path.call(print, "key"),
"]: ",
path.call(print, "value")
]);
case "ObjectTypeProperty":
var variance =
n.variance === "plus" ? "+" :
n.variance === "minus" ? "-" : "";
return concat([
variance,
path.call(print, "key"),
n.optional ? "?" : "",
": ",
path.call(print, "value")
]);
case "QualifiedTypeIdentifier":
return concat([
path.call(print, "qualification"),
".",
path.call(print, "id")
]);
case "StringLiteralTypeAnnotation":
return fromString(nodeStr(n.value, options), options);
case "NumberLiteralTypeAnnotation":
case "NumericLiteralTypeAnnotation":
assert.strictEqual(typeof n.value, "number");
return fromString(JSON.stringify(n.value), options);
case "StringTypeAnnotation":
return fromString("string", options);
case "DeclareTypeAlias":
parts.push("declare ");
case "TypeAlias":
return concat([
"type ",
path.call(print, "id"),
path.call(print, "typeParameters"),
" = ",
path.call(print, "right"),
";"
]);
case "TypeCastExpression":
return concat([
"(",
path.call(print, "expression"),
path.call(print, "typeAnnotation"),
")"
]);
case "TypeParameterDeclaration":
case "TypeParameterInstantiation":
return concat([
"<",
fromString(", ").join(path.map(print, "params")),
">"
]);
case "TypeParameter":
switch (n.variance) {
case 'plus':
parts.push('+');
break;
case 'minus':
parts.push('-');
break;
default:
}
parts.push(path.call(print, 'name'));
if (n.bound) {
parts.push(path.call(print, 'bound'));
}
if (n['default']) {
parts.push('=', path.call(print, 'default'));
}
return concat(parts);
case "TypeofTypeAnnotation":
return concat([
fromString("typeof ", options),
path.call(print, "argument")
]);
case "UnionTypeAnnotation":
return fromString(" | ").join(path.map(print, "types"));
case "VoidTypeAnnotation":
return fromString("void", options);
case "NullTypeAnnotation":
return fromString("null", options);
// 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 "ClassHeritage": // TODO
case "ComprehensionBlock": // TODO
case "ComprehensionExpression": // TODO
case "Glob": // TODO
case "GeneratorExpression": // TODO
case "LetStatement": // TODO
case "LetExpression": // TODO
case "GraphExpression": // TODO
case "GraphIndexExpression": // TODO
// XML types that nobody cares about or needs to print.
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));
}
return p;
}
function printStatementSequence(path, options, print) {
var inClassBody =
namedTypes.ClassBody &&
namedTypes.ClassBody.check(path.getParentNode());
var filtered = [];
var sawComment = false;
var sawStatement = false;
path.each(function(stmtPath) {
var i = stmtPath.getName();
var stmt = stmtPath.getValue();
// Just in case the AST has been modified to contain falsy
// "statements," it's safer simply to skip them.
if (!stmt) {
return;
}
// Skip printing EmptyStatement nodes to avoid leaving stray
// semicolons lying around.
if (stmt.type === "EmptyStatement") {
return;
}
if (namedTypes.Comment.check(stmt)) {
// The pretty printer allows a dangling Comment node to act as
// a Statement when the Comment can't be attached to any other
// non-Comment node in the tree.
sawComment = true;
} else if (namedTypes.Statement.check(stmt)) {
sawStatement = true;
} else {
// When the pretty printer encounters a string instead of an
// AST node, it just prints the string. This behavior can be
// useful for fine-grained formatting decisions like inserting
// blank lines.
isString.assert(stmt);
}
// We can't hang onto stmtPath outside of this function, because
// it's just a reference to a mutable FastPath object, so we have
// to go ahead and print it here.
filtered.push({
node: stmt,
printed: print(stmtPath)
});
});
if (sawComment) {
assert.strictEqual(
sawStatement, false,
"Comments may appear as statements in otherwise empty statement " +
"lists, but may not coexist with non-Comment nodes."
);
}
var prevTrailingSpace = null;
var len = filtered.length;
var parts = [];
filtered.forEach(function(info, i) {
var printed = info.printed;
var stmt = info.node;
var multiLine = printed.length > 1;
var notFirst = i > 0;
var notLast = i < len - 1;
var leadingSpace;
var trailingSpace;
var lines = stmt && stmt.loc && stmt.loc.lines;
var trueLoc = lines && options.reuseWhitespace &&
util.getTrueLoc(stmt, lines);
if (notFirst) {
if (trueLoc) {
var beforeStart = lines.skipSpaces(trueLoc.start, true);
var beforeStartLine = beforeStart ? beforeStart.line : 1;
var leadingGap = trueLoc.start.line - beforeStartLine;
leadingSpace = Array(leadingGap + 1).join("\n");
} else {
leadingSpace = multiLine ? "\n\n" : "\n";
}
} else {
leadingSpace = "";
}
if (notLast) {
if (trueLoc) {
var afterEnd = lines.skipSpaces(trueLoc.end);
var afterEndLine = afterEnd ? afterEnd.line : lines.length;
var trailingGap = afterEndLine - trueLoc.end.line;
trailingSpace = Array(trailingGap + 1).join("\n");
} else {
trailingSpace = multiLine ? "\n\n" : "\n";
}
} else {
trailingSpace = "";
}
parts.push(
maxSpace(prevTrailingSpace, leadingSpace),
printed
);
if (notLast) {
prevTrailingSpace = trailingSpace;
} else if (trailingSpace) {
parts.push(trailingSpace);
}
});
return concat(parts);
}
function maxSpace(s1, s2) {
if (!s1 && !s2) {
return fromString("");
}
if (!s1) {
return fromString(s2);
}
if (!s2) {
return fromString(s1);
}
var spaceLines1 = fromString(s1);
var spaceLines2 = fromString(s2);
if (spaceLines2.length > spaceLines1.length) {
return spaceLines2;
}
return spaceLines1;
}
function printMethod(path, options, print) {
var node = path.getNode();
var kind = node.kind;
var parts = [];
if (node.type === "ObjectMethod" || node.type === "ClassMethod") {
node.value = node;
} else {
namedTypes.FunctionExpression.assert(node.value);
}
if (node.value.async) {
parts.push("async ");
}
if (!kind || kind === "init" || kind === "method" || kind === "constructor") {
if (node.value.generator) {
parts.push("*");
}
} else {
assert.ok(kind === "get" || kind === "set");
parts.push(kind, " ");
}
var key = path.call(print, "key");
if (node.computed) {
key = concat(["[", key, "]"]);
}
parts.push(
key,
path.call(print, "value", "typeParameters"),
"(",
path.call(function(valuePath) {
return printFunctionParams(valuePath, options, print);
}, "value"),
")",
path.call(print, "value", "returnType"),
" ",
path.call(print, "value", "body")
);
return concat(parts);
}
function printArgumentsList(path, options, print) {
var printed = path.map(print, "arguments");
var trailingComma = util.isTrailingCommaEnabled(options, "parameters");
var joined = fromString(", ").join(printed);
if (joined.getLineLength(1) > options.wrapColumn) {
joined = fromString(",\n").join(printed);
return concat([
"(\n",
joined.indent(options.tabWidth),
trailingComma ? ",\n)" : "\n)"
]);
}
return concat(["(", joined, ")"]);
}
function printFunctionParams(path, options, print) {
var fun = path.getValue();
namedTypes.Function.assert(fun);
var printed = path.map(print, "params");
if (fun.defaults) {
path.each(function(defExprPath) {
var i = defExprPath.getName();
var p = printed[i];
if (p && defExprPath.getValue()) {
printed[i] = concat([p, " = ", print(defExprPath)]);
}
}, "defaults");
}
if (fun.rest) {
printed.push(concat(["...", path.call(print, "rest")]));
}
var joined = fromString(", ").join(printed);
if (joined.length > 1 ||
joined.getLineLength(1) > options.wrapColumn) {
joined = fromString(",\n").join(printed);
if (util.isTrailingCommaEnabled(options, "parameters") &&
!fun.rest &&
fun.params[fun.params.length - 1].type !== 'RestElement') {
joined = concat([joined, ",\n"]);
} else {
joined = concat([joined, "\n"]);
}
return concat(["\n", joined.indent(options.tabWidth)]);
}
return joined;
}
function printObjectMethod(path, options, print) {
var objMethod = path.getValue();
var parts = [];
if (objMethod.async)
parts.push("async ");
if (objMethod.generator)
parts.push("*");
if (objMethod.method || objMethod.kind === "get" || objMethod.kind === "set") {
return printMethod(path, options, print);
}
var key = path.call(print, "key");
if (objMethod.computed) {
parts.push("[", key, "]");
} else {
parts.push(key);
}
parts.push(
"(",
printFunctionParams(path, options, print),
")",
path.call(print, "returnType"),
" ",
path.call(print, "body")
);
return concat(parts);
}
function printExportDeclaration(path, options, print) {
var decl = path.getValue();
var parts = ["export "];
var shouldPrintSpaces = options.objectCurlySpacing;
namedTypes.Declaration.assert(decl);
if (decl["default"] ||
decl.type === "ExportDefaultDeclaration") {
parts.push("default ");
}
if (decl.declaration) {
parts.push(path.call(print, "declaration"));
} else if (decl.specifiers &&
decl.specifiers.length > 0) {
if (decl.specifiers.length === 1 &&
decl.specifiers[0].type === "ExportBatchSpecifier") {
parts.push("*");
} else {
parts.push(
shouldPrintSpaces ? "{ " : "{",
fromString(", ").join(path.map(print, "specifiers")),
shouldPrintSpaces ? " }" : "}"
);
}
if (decl.source) {
parts.push(" from ", path.call(print, "source"));
}
}
var lines = concat(parts);
if (lastNonSpaceCharacter(lines) !== ";" &&
! (decl.declaration &&
(decl.declaration.type === "FunctionDeclaration" ||
decl.declaration.type === "ClassDeclaration"))) {
lines = concat([lines, ";"]);
}
return lines;
}
function printFlowDeclaration(path, parts) {
var parentExportDecl = util.getParentExportDeclaration(path);
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 ");
}
return concat(parts);
}
function adjustClause(clause, options) {
if (clause.length > 1)
return concat([" ", clause]);
return concat([
"\n",
maybeAddSemicolon(clause).indent(options.tabWidth)
]);
}
function lastNonSpaceCharacter(lines) {
var pos = lines.lastPos();
do {
var ch = lines.charAt(pos);
if (/\S/.test(ch))
return ch;
} while (lines.prevPos(pos));
}
function endsWithBrace(lines) {
return lastNonSpaceCharacter(lines) === "}";
}
function swapQuotes(str) {
return str.replace(/['"]/g, function(m) {
return m === '"' ? '\'' : '"';
});
}
function nodeStr(str, options) {
isString.assert(str);
switch (options.quote) {
case "auto":
var double = JSON.stringify(str);
var single = swapQuotes(JSON.stringify(swapQuotes(str)));
return double.length > single.length ? single : double;
case "single":
return swapQuotes(JSON.stringify(swapQuotes(str)));
case "double":
default:
return JSON.stringify(str);
}
}
function maybeAddSemicolon(lines) {
var eoc = lastNonSpaceCharacter(lines);
if (!eoc || "\n};".indexOf(eoc) < 0)
return concat([lines, ";"]);
return lines;
}