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; }