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 NodePath = types.NodePath; 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 NodePath);
    return printComments(path.node.comments, print(path), options);
}

function print(path, includeComments) {
    if (includeComments)
        return printWithComments(path);

    assert.ok(path instanceof NodePath);

    if (!explicitTabWidth) {
        var oldTabWidth = options.tabWidth;
        var orig = path.node.original;
        var origLoc = orig && orig.loc;
        var origLines = origLoc && origLoc.lines;
        if (origLines) {
            options.tabWidth = origLines.guessTabWidth();
            try {
                return maybeReprint(path);
            } finally {
                options.tabWidth = oldTabWidth;
            }
        }
    }

    return maybeReprint(path);
}

function maybeReprint(path) {
    var reprinter = getReprinter(path);
    if (reprinter)
        return maybeAddParens(path, reprinter(maybeReprint));
    return printRootGenerically(path);
}

// Print the root node generically, but then resume reprinting its
// children non-generically.
function printRootGenerically(path) {
    return 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 path = ast instanceof NodePath ? ast : new NodePath(ast);
    var lines = print(path, 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 = ast instanceof NodePath ? ast : new NodePath(ast);
    var oldReuseWhitespace = options.reuseWhitespace;

    // Do not reuse whitespace (or anything else, for that matter)
    // when printing generically.
    options.reuseWhitespace = false;

    try {
        return new PrintResult(printGenerically(path).toString(options));
    } finally {
        options.reuseWhitespace = oldReuseWhitespace;
    }
};

}

exports.Printer = Printer;

function maybeAddParens(path, lines) {

return path.needsParens() ? concat(["(", lines, ")"]) : lines;

}

function genericPrint(path, options, printPath) {

assert.ok(path instanceof NodePath);
return maybeAddParens(path, genericPrintNoParens(path, options, printPath));

}

function genericPrintNoParens(path, options, print) {

var n = path.value;

if (!n) {
    return fromString("");
}

if (typeof n === "string") {
    return fromString(n, options);
}

namedTypes.Node.assert(n);

switch (n.type) {
case "File":
    path = path.get("program");
    n = path.node;
    namedTypes.Program.assert(n);

    // intentionally fall through...

case "Program":
    return maybeAddSemicolon(
        printStatementSequence(path.get("body"), options, print)
    );

case "EmptyStatement":
    return fromString("");

case "ExpressionStatement":
    return concat([print(path.get("expression")), ";"]);

case "BinaryExpression":
case "LogicalExpression":
case "AssignmentExpression":
    return fromString(" ").join([
        print(path.get("left")),
        n.operator,
        print(path.get("right"))
    ]);

case "MemberExpression":
    var parts = [print(path.get("object"))];

    if (n.computed)
        parts.push("[", print(path.get("property")), "]");
    else
        parts.push(".", print(path.get("property")));

    return concat(parts);

case "Path":
    return fromString(".").join(n.body);

case "Identifier":
    return fromString(n.name, options);

case "SpreadElement":
case "SpreadElementPattern":
case "SpreadProperty":
case "SpreadPropertyPattern":
    return concat(["...", print(path.get("argument"))]);

case "FunctionDeclaration":
case "FunctionExpression":
    var parts = [];

    if (n.async)
        parts.push("async ");

    parts.push("function");

    if (n.generator)
        parts.push("*");

    if (n.id)
        parts.push(" ", print(path.get("id")));

    parts.push(
        "(",
        printFunctionParams(path, options, print),
        ") ",
        print(path.get("body")));

    return concat(parts);

case "ArrowFunctionExpression":
    var parts = [];

    if (n.async)
        parts.push("async ");

    if (n.params.length === 1) {
        parts.push(print(path.get("params", 0)));
    } else {
        parts.push(
            "(",
            printFunctionParams(path, options, print),
            ")"
        );
    }

    parts.push(" => ", print(path.get("body")));

    return concat(parts);

case "MethodDefinition":
    var parts = [];

    if (n.static) {
        parts.push("static ");
    }

    parts.push(printMethod(
        n.kind,
        path.get("key"),
        path.get("value"),
        options,
        print
    ));

    return concat(parts);

case "YieldExpression":
    var parts = ["yield"];

    if (n.delegate)
        parts.push("*");

    if (n.argument)
        parts.push(" ", print(path.get("argument")));

    return concat(parts);

case "AwaitExpression":
    var parts = ["await"];

    if (n.all)
        parts.push("*");

    if (n.argument)
        parts.push(" ", print(path.get("argument")));

    return concat(parts);

case "ModuleDeclaration":
    var parts = ["module", print(path.get("id"))];

    if (n.source) {
        assert.ok(!n.body);
        parts.push("from", print(path.get("source")));
    } else {
        parts.push(print(path.get("body")));
    }

    return fromString(" ").join(parts);

case "ImportSpecifier":
case "ExportSpecifier":
    var parts = [print(path.get("id"))];

    if (n.name)
        parts.push(" as ", print(path.get("name")));

    return concat(parts);

case "ExportBatchSpecifier":
    return fromString("*");

case "ImportNamespaceSpecifier":
    return concat(["* as ", print(path.get("id"))]);

case "ImportDefaultSpecifier":
    return print(path.get("id"));

case "ExportDeclaration":
    var parts = ["export"];

    if (n["default"]) {
        parts.push(" default");

    } else if (n.specifiers &&
               n.specifiers.length > 0) {

        if (n.specifiers.length === 1 &&
            n.specifiers[0].type === "ExportBatchSpecifier") {
            parts.push(" *");
        } else {
            parts.push(
                " { ",
                fromString(", ").join(path.get("specifiers").map(print)),
                " }"
            );
        }

        if (n.source)
            parts.push(" from ", print(path.get("source")));

        parts.push(";");

        return concat(parts);
    }

    if (n.declaration) {
        if (!namedTypes.Node.check(n.declaration)) {
            console.log(JSON.stringify(n, null, 2));
        }
        var decLines = print(path.get("declaration"));
        parts.push(" ", decLines);
        if (lastNonSpaceCharacter(decLines) !== ";") {
            parts.push(";");
        }
    }

    return concat(parts);

case "ImportDeclaration":
    var parts = ["import "];

    if (n.specifiers &&
        n.specifiers.length > 0) {

        var foundImportSpecifier = false;

        path.get("specifiers").each(function(sp) {
            if (sp.name > 0) {
                parts.push(", ");
            }

            if (namedTypes.ImportDefaultSpecifier.check(sp.value) ||
                namedTypes.ImportNamespaceSpecifier.check(sp.value)) {
                assert.strictEqual(foundImportSpecifier, false);
            } else {
                namedTypes.ImportSpecifier.assert(sp.value);
                if (!foundImportSpecifier) {
                    foundImportSpecifier = true;
                    parts.push("{");
                }
            }

            parts.push(print(sp));
        });

        if (foundImportSpecifier) {
            parts.push("}");
        }

        parts.push(" from ");
    }

    parts.push(print(path.get("source")), ";");

    return concat(parts);

case "BlockStatement":
    var naked = printStatementSequence(path.get("body"), options, print);
    if (naked.isEmpty())
        return fromString("{}");

    return concat([
        "{\n",
        naked.indent(options.tabWidth),
        "\n}"
    ]);

case "ReturnStatement":
    var parts = ["return"];

    if (n.argument) {
        var argLines = print(path.get("argument"));
        if (argLines.length > 1 &&
            namedTypes.XJSElement &&
            namedTypes.XJSElement.check(n.argument)) {
            parts.push(
                " (\n",
                argLines.indent(options.tabWidth),
                "\n)"
            );
        } else {
            parts.push(" ", argLines);
        }
    }

    parts.push(";");

    return concat(parts);

case "CallExpression":
    return concat([
        print(path.get("callee")),
        printArgumentsList(path, options, print)
    ]);

case "ObjectExpression":
case "ObjectPattern":
    var allowBreak = false,
        len = n.properties.length,
        parts = [len > 0 ? "{\n" : "{"];

    path.get("properties").map(function(childPath) {
        var prop = childPath.value;
        var i = childPath.name;

        var lines = print(childPath).indent(options.tabWidth);

        var multiLine = 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(multiLine ? ",\n\n" : ",\n");
            allowBreak = !multiLine;
        }
    });

    parts.push(len > 0 ? "\n}" : "}");

    return concat(parts);

case "PropertyPattern":
    return concat([
        print(path.get("key")),
        ": ",
        print(path.get("pattern"))
    ]);

case "Property": // Non-standard AST node type.
    if (n.method || n.kind === "get" || n.kind === "set") {
        return printMethod(
            n.kind,
            path.get("key"),
            path.get("value"),
            options,
            print
        );
    }

    if (path.node.shorthand) {
        return print(path.get("key"));
    } else {
        return concat([
            print(path.get("key")),
            ": ",
            print(path.get("value"))
        ]);
    }

case "ArrayExpression":
case "ArrayPattern":
    var elems = n.elements,
        len = elems.length,
        parts = ["["];

    path.get("elements").each(function(elemPath) {
        var elem = elemPath.value;
        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 i = elemPath.name;
            if (i > 0)
                parts.push(" ");
            parts.push(print(elemPath));
            if (i < len - 1)
                parts.push(",");
        }
    });

    parts.push("]");

    return concat(parts);

case "SequenceExpression":
    return fromString(", ").join(path.get("expressions").map(print));

case "ThisExpression":
    return fromString("this");

case "Literal":
    if (typeof n.value !== "string")
        return fromString(n.value, options);

    // intentionally fall through...

case "ModuleSpecifier":
    // A ModuleSpecifier is a string-valued Literal.
    return fromString(nodeStr(n), options);

case "UnaryExpression":
    var parts = [n.operator];
    if (/[a-z]$/.test(n.operator))
        parts.push(" ");
    parts.push(print(path.get("argument")));
    return concat(parts);

case "UpdateExpression":
    var parts = [
        print(path.get("argument")),
        n.operator
    ];

    if (n.prefix)
        parts.reverse();

    return concat(parts);

case "ConditionalExpression":
    return concat([
        "(", print(path.get("test")),
        " ? ", print(path.get("consequent")),
        " : ", print(path.get("alternate")), ")"
    ]);

case "NewExpression":
    var parts = ["new ", print(path.get("callee"))];
    var args = n.arguments;
    if (args) {
        parts.push(printArgumentsList(path, options, print));
    }

    return concat(parts);

case "VariableDeclaration":
    var parts = [n.kind, " "];
    var maxLen = 0;
    var printed = path.get("declarations").map(function(childPath) {
        var lines = print(childPath);
        maxLen = Math.max(lines.length, maxLen);
        return lines;
    });

    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.parent && path.parent.node;
    if (!namedTypes.ForStatement.check(parentNode) &&
        !namedTypes.ForInStatement.check(parentNode) &&
        !(namedTypes.ForOfStatement &&
          namedTypes.ForOfStatement.check(parentNode))) {
        parts.push(";");
    }

    return concat(parts);

case "VariableDeclarator":
    return n.init ? fromString(" = ").join([
        print(path.get("id")),
        print(path.get("init"))
    ]) : print(path.get("id"));

case "WithStatement":
    return concat([
        "with (",
        print(path.get("object")),
        ") ",
        print(path.get("body"))
    ]);

case "IfStatement":
    var con = adjustClause(print(path.get("consequent")), options),
        parts = ["if (", print(path.get("test")), ")", con];

    if (n.alternate)
        parts.push(
            endsWithBrace(con) ? " else" : "\nelse",
            adjustClause(print(path.get("alternate")), options));

    return concat(parts);

case "ForStatement":
    // TODO Get the for (;;) case right.
    var init = print(path.get("init")),
        sep = init.length > 1 ? ";\n" : "; ",
        forParen = "for (",
        indented = fromString(sep).join([
            init,
            print(path.get("test")),
            print(path.get("update"))
        ]).indentTail(forParen.length),
        head = concat([forParen, indented, ")"]),
        clause = adjustClause(print(path.get("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 (",
        print(path.get("test")),
        ")",
        adjustClause(print(path.get("body")), options)
    ]);

case "ForInStatement":
    // Note: esprima can't actually parse "for each (".
    return concat([
        n.each ? "for each (" : "for (",
        print(path.get("left")),
        " in ",
        print(path.get("right")),
        ")",
        adjustClause(print(path.get("body")), options)
    ]);

case "ForOfStatement":
    return concat([
        "for (",
        print(path.get("left")),
        " of ",
        print(path.get("right")),
        ")",
        adjustClause(print(path.get("body")), options)
    ]);

case "DoWhileStatement":
    var doBody = concat([
        "do",
        adjustClause(print(path.get("body")), options)
    ]), parts = [doBody];

    if (endsWithBrace(doBody))
        parts.push(" while");
    else
        parts.push("\nwhile");

    parts.push(" (", print(path.get("test")), ");");

    return concat(parts);

case "BreakStatement":
    var parts = ["break"];
    if (n.label)
        parts.push(" ", print(path.get("label")));
    parts.push(";");
    return concat(parts);

case "ContinueStatement":
    var parts = ["continue"];
    if (n.label)
        parts.push(" ", print(path.get("label")));
    parts.push(";");
    return concat(parts);

case "LabeledStatement":
    return concat([
        print(path.get("label")),
        ":\n",
        print(path.get("body"))
    ]);

case "TryStatement":
    var parts = [
        "try ",
        print(path.get("block"))
    ];

    path.get("handlers").each(function(handler) {
        parts.push(" ", print(handler));
    });

    if (n.finalizer)
        parts.push(" finally ", print(path.get("finalizer")));

    return concat(parts);

case "CatchClause":
    var parts = ["catch (", print(path.get("param"))];

    if (n.guard)
        // Note: esprima does not recognize conditional catch clauses.
        parts.push(" if ", print(path.get("guard")));

    parts.push(") ", print(path.get("body")));

    return concat(parts);

case "ThrowStatement":
    return concat([
        "throw ",
        print(path.get("argument")),
        ";"
    ]);

case "SwitchStatement":
    return concat([
        "switch (",
        print(path.get("discriminant")),
        ") {\n",
        fromString("\n").join(path.get("cases").map(print)),
        "\n}"
    ]);

    // Note: ignoring n.lexical because it has no printing consequences.

case "SwitchCase":
    var parts = [];

    if (n.test)
        parts.push("case ", print(path.get("test")), ":");
    else
        parts.push("default:");

    if (n.consequent.length > 0) {
        parts.push("\n", printStatementSequence(
            path.get("consequent"),
            options,
            print
        ).indent(options.tabWidth));
    }

    return concat(parts);

case "DebuggerStatement":
    return fromString("debugger;");

// XJS extensions below.

case "XJSAttribute":
    var parts = [print(path.get("name"))];
    if (n.value)
        parts.push("=", print(path.get("value")));
    return concat(parts);

case "XJSIdentifier":
    return fromString(n.name, options);

case "XJSNamespacedName":
    return fromString(":").join([
        print(path.get("namespace")),
        print(path.get("name"))
    ]);

case "XJSMemberExpression":
    return fromString(".").join([
        print(path.get("object")),
        print(path.get("property"))
    ]);

case "XJSSpreadAttribute":
    return concat(["{...", print(path.get("argument")), "}"]);

case "XJSExpressionContainer":
    return concat(["{", print(path.get("expression")), "}"]);

case "XJSElement":
    var openingLines = print(path.get("openingElement"));

    if (n.openingElement.selfClosing) {
        assert.ok(!n.closingElement);
        return openingLines;
    }

    var childLines = concat(
        path.get("children").map(function(childPath) {
            var child = childPath.value;

            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);
        })
    ).indentTail(options.tabWidth);

    var closingLines = print(path.get("closingElement"));

    return concat([
        openingLines,
        childLines,
        closingLines
    ]);

case "XJSOpeningElement":
    var parts = ["<", print(path.get("name"))];
    var attrParts = [];

    path.get("attributes").each(function(attrPath) {
        attrParts.push(" ", print(attrPath));
    });

    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 "XJSClosingElement":
    return concat(["</", print(path.get("name")), ">"]);

case "XJSText":
    return fromString(n.value, options);

case "XJSEmptyExpression":
    return fromString("");

case "TypeAnnotatedIdentifier":
    var parts = [
        print(path.get("annotation")),
        " ",
        print(path.get("identifier"))
    ];

    return concat(parts);

case "ClassBody":
    if (n.body.length === 0) {
        return fromString("{}");
    }

    return concat([
        "{\n",
        printStatementSequence(path.get("body"), options, print)
            .indent(options.tabWidth),
        "\n}"
    ]);

case "ClassPropertyDefinition":
    var parts = ["static ", print(path.get("definition"))];
    if (!namedTypes.MethodDefinition.check(n.definition))
        parts.push(";");
    return concat(parts);

case "ClassProperty":
    return concat([print(path.get("id")), ";"]);

case "ClassDeclaration":
case "ClassExpression":
    var parts = ["class"];

    if (n.id)
        parts.push(" ", print(path.get("id")));

    if (n.superClass)
        parts.push(" extends ", print(path.get("superClass")));

    parts.push(" ", print(path.get("body")));

    return concat(parts);

// These types are unprintable because they serve as abstract
// supertypes for other (printable) types.
case "Node":
case "SourceLocation":
case "Position":
case "Statement":
case "Function":
case "Pattern":
case "Expression":
case "Declaration":
case "Specifier":
case "NamedSpecifier":
    throw new Error("unprintable type: " + JSON.stringify(n.type));

// 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 "TaggedTemplateExpression": // TODO
case "TemplateElement": // TODO
case "TemplateLiteral": // TODO
case "GeneratorExpression": // TODO
case "LetStatement": // TODO
case "LetExpression": // TODO
case "GraphExpression": // TODO
case "GraphIndexExpression": // TODO
case "TypeAnnotation": // 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 = path.parent &&
    namedTypes.ClassBody &&
    namedTypes.ClassBody.check(path.parent.node);

var filtered = path.filter(function(stmtPath) {
    var stmt = stmtPath.value;

    // Just in case the AST has been modified to contain falsy
    // "statements," it's safer simply to skip them.
    if (!stmt)
        return false;

    // Skip printing EmptyStatement nodes to avoid leaving stray
    // semicolons lying around.
    if (stmt.type === "EmptyStatement")
        return false;

    if (!inClassBody) {
        namedTypes.Statement.assert(stmt);
    }

    return true;
});

var prevTrailingSpace = null;
var len = filtered.length;
var parts = [];

filtered.forEach(function(stmtPath, i) {
    var printed = print(stmtPath);
    var stmt = stmtPath.value;
    var needSemicolon = true;
    var multiLine = printed.length > 1;
    var notFirst = i > 0;
    var notLast = i < len - 1;
    var leadingSpace;
    var trailingSpace;

    if (inClassBody) {
        var stmt = stmtPath.value;

        if (namedTypes.MethodDefinition.check(stmt) ||
            (namedTypes.ClassPropertyDefinition.check(stmt) &&
             namedTypes.MethodDefinition.check(stmt.definition))) {
            needSemicolon = false;
        }
    }

    if (needSemicolon) {
        // Try to add a semicolon to anything that isn't a method in a
        // class body.
        printed = maybeAddSemicolon(printed);
    }

    var orig = options.reuseWhitespace && stmt.original;
    var trueLoc = orig && getTrueLoc(orig);
    var lines = trueLoc && trueLoc.lines;

    if (notFirst) {
        if (lines) {
            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 (lines) {
            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 getTrueLoc(node) {

if (!node.comments) {
    // If the node has no comments, regard node.loc as true.
    return node.loc;
}

var start = node.loc.start;
var end = node.loc.end;

// If the node has any comments, their locations might contribute to
// the true start/end positions of the node.
node.comments.forEach(function(comment) {
    if (comment.loc) {
        if (util.comparePos(comment.loc.start, start) < 0) {
            start = comment.loc.start;
        }

        if (util.comparePos(end, comment.loc.end) < 0) {
            end = comment.loc.end;
        }
    }
});

return {
    lines: node.loc.lines,
    start: start,
    end: end
};

}

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(kind, keyPath, valuePath, options, print) {

var parts = [];
var key = keyPath.value;
var value = valuePath.value;

namedTypes.FunctionExpression.assert(value);

if (value.async) {
    parts.push("async ");
}

if (!kind || kind === "init") {
    if (value.generator) {
        parts.push("*");
    }
} else {
    assert.ok(kind === "get" || kind === "set");
    parts.push(kind, " ");
}

parts.push(
    print(keyPath),
    "(",
    printFunctionParams(valuePath, options, print),
    ") ",
    print(valuePath.get("body"))
);

return concat(parts);

}

function printArgumentsList(path, options, print) {

var printed = path.get("arguments").map(print);

var joined = fromString(", ").join(printed);
if (joined.getLineLength(1) > options.wrapColumn) {
    joined = fromString(",\n").join(printed);
    return concat(["(\n", joined.indent(options.tabWidth), "\n)"]);
}

return concat(["(", joined, ")"]);

}

function printFunctionParams(path, options, print) {

var fun = path.node;
namedTypes.Function.assert(fun);

var params = path.get("params");
var defaults = path.get("defaults");
var printed = params.map(defaults.value ? function(param) {
    var p = print(param);
    var d = defaults.get(param.name);
    return d.value ? concat([p, "=", print(d)]) : p;
} : print);

if (fun.rest) {
    printed.push(concat(["...", print(path.get("rest"))]));
}

var joined = fromString(", ").join(printed);
if (joined.length > 1 ||
    joined.getLineLength(1) > options.wrapColumn) {
    joined = fromString(",\n").join(printed);
    return concat(["\n", joined.indent(options.tabWidth)]);
}

return joined;

}

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 nodeStr(n) {

namedTypes.Literal.assert(n);
isString.assert(n.value);
return JSON.stringify(n.value);

}

function maybeAddSemicolon(lines) {

var eoc = lastNonSpaceCharacter(lines);
if (!eoc || "\n};".indexOf(eoc) < 0)
    return concat([lines, ";"]);
return lines;

}