"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var assert_1 = __importDefault(require("assert"));
var comments_1 = require("./comments");
var lines_1 = require("./lines");
var options_1 = require("./options");
var patcher_1 = require("./patcher");
var types_1 = __importDefault(require("./types"));
var namedTypes = types_1.default.namedTypes;
var isString = types_1.default.builtInTypes.string;
var isObject = types_1.default.builtInTypes.object;
var fast_path_1 = __importDefault(require("./fast-path"));
var util = __importStar(require("./util"));
var PrintResult = function PrintResult(code, sourceMap) {
    assert_1.default.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("");
var Printer = function Printer(config) {
    assert_1.default.ok(this instanceof Printer);
    var explicitTabWidth = config && config.tabWidth;
    config = options_1.normalize(config);
    // 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) config.sourceFileName, so we null it out.
    config.sourceFileName = null;
    // Non-destructively modifies options with overrides, and returns a
    // new print function that uses the modified options.
    function makePrintFunctionWith(options, overrides) {
        options = Object.assign({}, options, overrides);
        return function (path) {
            return print(path, options);
        };
    }
    function print(path, options) {
        assert_1.default.ok(path instanceof fast_path_1.default);
        options = options || {};
        if (options.includeComments) {
            return comments_1.printComments(path, makePrintFunctionWith(options, {
                includeComments: false
            }));
        }
        var oldTabWidth = config.tabWidth;
        if (!explicitTabWidth) {
            var loc = path.getNode().loc;
            if (loc && loc.lines && loc.lines.guessTabWidth) {
                config.tabWidth = loc.lines.guessTabWidth();
            }
        }
        var reprinter = patcher_1.getReprinter(path);
        var lines = 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.
            ? reprinter(print)
            : genericPrint(path, config, options, makePrintFunctionWith(options, {
                includeComments: true,
                avoidRootParens: false
            }));
        config.tabWidth = oldTabWidth;
        return lines;
    }
    this.print = function (ast) {
        if (!ast) {
            return emptyPrintResult;
        }
        var lines = print(fast_path_1.default.from(ast), {
            includeComments: true,
            avoidRootParens: false
        });
        return new PrintResult(lines.toString(config), util.composeSourceMaps(config.inputSourceMap, lines.getSourceMap(config.sourceMapName, config.sourceRoot)));
    };
    this.printGenerically = function (ast) {
        if (!ast) {
            return emptyPrintResult;
        }
        // Print the entire AST generically.
        function printGenerically(path) {
            return comments_1.printComments(path, function (path) {
                return genericPrint(path, config, {
                    includeComments: true,
                    avoidRootParens: false
                }, printGenerically);
            });
        }
        var path = fast_path_1.default.from(ast);
        var oldReuseWhitespace = config.reuseWhitespace;
        // Do not reuse whitespace (or anything else, for that matter)
        // when printing generically.
        config.reuseWhitespace = false;
        // TODO Allow printing of comments?
        var pr = new PrintResult(printGenerically(path).toString(config));
        config.reuseWhitespace = oldReuseWhitespace;
        return pr;
    };
};
exports.Printer = Printer;
function genericPrint(path, config, options, printPath) {
    assert_1.default.ok(path instanceof fast_path_1.default);
    var node = path.getValue();
    var parts = [];
    var linesWithoutParens = genericPrintNoParens(path, config, printPath);
    if (!node || linesWithoutParens.isEmpty()) {
        return linesWithoutParens;
    }
    var shouldAddParens = false;
    var decoratorsLines = printDecorators(path, printPath);
    if (decoratorsLines.isEmpty()) {
        // Nodes with decorators can't have parentheses, so we can avoid
        // computing path.needsParens() except in this case.
        if (!options.avoidRootParens) {
            shouldAddParens = path.needsParens();
        }
    }
    else {
        parts.push(decoratorsLines);
    }
    if (shouldAddParens) {
        parts.unshift("(");
    }
    parts.push(linesWithoutParens);
    if (shouldAddParens) {
        parts.push(")");
    }
    return lines_1.concat(parts);
}
// Note that the `options` parameter of this function is what other
// functions in this file call the `config` object (that is, the
// configuration object originally passed into the Printer constructor).
// Its properties are documented in lib/options.js.
function genericPrintNoParens(path, options, print) {
    var n = path.getValue();
    if (!n) {
        return lines_1.fromString("");
    }
    if (typeof n === "string") {
        return lines_1.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");
            }
            if (n.interpreter) {
                parts.push(path.call(print, "interpreter"));
            }
            parts.push(path.call(function (bodyPath) {
                return printStatementSequence(bodyPath, options, print);
            }, "body"));
            return lines_1.concat(parts);
        case "Noop": // Babel extension.
        case "EmptyStatement":
            return lines_1.fromString("");
        case "ExpressionStatement":
            return lines_1.concat([path.call(print, "expression"), ";"]);
        case "ParenthesizedExpression": // Babel extension.
            return lines_1.concat(["(", path.call(print, "expression"), ")"]);
        case "BinaryExpression":
        case "LogicalExpression":
        case "AssignmentExpression":
            return lines_1.fromString(" ").join([
                path.call(print, "left"),
                n.operator,
                path.call(print, "right")
            ]);
        case "AssignmentPattern":
            return lines_1.concat([
                path.call(print, "left"),
                " = ",
                path.call(print, "right")
            ]);
        case "MemberExpression":
        case "OptionalMemberExpression":
            parts.push(path.call(print, "object"));
            var property = path.call(print, "property");
            var optional = n.type === "OptionalMemberExpression";
            if (n.computed) {
                parts.push(optional ? "?.[" : "[", property, "]");
            }
            else {
                parts.push(optional ? "?." : ".", property);
            }
            return lines_1.concat(parts);
        case "MetaProperty":
            return lines_1.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 lines_1.concat(parts);
        case "Path":
            return lines_1.fromString(".").join(n.body);
        case "Identifier":
            return lines_1.concat([
                lines_1.fromString(n.name, options),
                n.optional ? "?" : "",
                path.call(print, "typeAnnotation")
            ]);
        case "SpreadElement":
        case "SpreadElementPattern":
        case "RestProperty": // Babel 6 for ObjectPattern
        case "SpreadProperty":
        case "SpreadPropertyPattern":
        case "ObjectTypeSpreadProperty":
        case "RestElement":
            return lines_1.concat([
                "...",
                path.call(print, "argument"),
                path.call(print, "typeAnnotation")
            ]);
        case "FunctionDeclaration":
        case "FunctionExpression":
        case "TSDeclareFunction":
            if (n.declare) {
                parts.push("declare ");
            }
            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"));
            if (n.body) {
                parts.push(" ", path.call(print, "body"));
            }
            return lines_1.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 lines_1.concat(parts);
        case "MethodDefinition":
            return printMethod(path, options, print);
        case "YieldExpression":
            parts.push("yield");
            if (n.delegate)
                parts.push("*");
            if (n.argument)
                parts.push(" ", path.call(print, "argument"));
            return lines_1.concat(parts);
        case "AwaitExpression":
            parts.push("await");
            if (n.all)
                parts.push("*");
            if (n.argument)
                parts.push(" ", path.call(print, "argument"));
            return lines_1.concat(parts);
        case "ModuleDeclaration":
            parts.push("module", path.call(print, "id"));
            if (n.source) {
                assert_1.default.ok(!n.body);
                parts.push("from", path.call(print, "source"));
            }
            else {
                parts.push(path.call(print, "body"));
            }
            return lines_1.fromString(" ").join(parts);
        case "ImportSpecifier":
            if (n.importKind && n.importKind !== "value") {
                parts.push(n.importKind + " ");
            }
            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 lines_1.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 lines_1.concat(parts);
        case "ExportBatchSpecifier":
            return lines_1.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 lines_1.concat(parts);
        case "ImportDefaultSpecifier":
            if (n.local) {
                return path.call(print, "local");
            }
            return path.call(print, "id");
        case "TSExportAssignment":
            return lines_1.concat(["export = ", path.call(print, "expression")]);
        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 lines_1.concat(parts);
        case "TSNamespaceExportDeclaration":
            parts.push("export as namespace ", path.call(print, "id"));
            return maybeAddSemicolon(lines_1.concat(parts));
        case "ExportNamespaceSpecifier":
            return lines_1.concat(["* as ", path.call(print, "exported")]);
        case "ExportDefaultSpecifier":
            return path.call(print, "exported");
        case "Import":
            return lines_1.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 unbracedSpecifiers_1 = [];
                var bracedSpecifiers_1 = [];
                path.each(function (specifierPath) {
                    var spec = specifierPath.getValue();
                    if (spec.type === "ImportSpecifier") {
                        bracedSpecifiers_1.push(print(specifierPath));
                    }
                    else if (spec.type === "ImportDefaultSpecifier" ||
                        spec.type === "ImportNamespaceSpecifier") {
                        unbracedSpecifiers_1.push(print(specifierPath));
                    }
                }, "specifiers");
                unbracedSpecifiers_1.forEach(function (lines, i) {
                    if (i > 0) {
                        parts.push(", ");
                    }
                    parts.push(lines);
                });
                if (bracedSpecifiers_1.length > 0) {
                    var lines_2 = lines_1.fromString(", ").join(bracedSpecifiers_1);
                    if (lines_2.getLineLength(1) > options.wrapColumn) {
                        lines_2 = lines_1.concat([
                            lines_1.fromString(",\n").join(bracedSpecifiers_1).indent(options.tabWidth),
                            ","
                        ]);
                    }
                    if (unbracedSpecifiers_1.length > 0) {
                        parts.push(", ");
                    }
                    if (lines_2.length > 1) {
                        parts.push("{\n", lines_2, "\n}");
                    }
                    else if (options.objectCurlySpacing) {
                        parts.push("{ ", lines_2, " }");
                    }
                    else {
                        parts.push("{", lines_2, "}");
                    }
                }
                parts.push(" from ");
            }
            parts.push(path.call(print, "source"), ";");
            return lines_1.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 lines_1.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 lines_1.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 lines_1.concat(parts);
        case "CallExpression":
        case "OptionalCallExpression":
            parts.push(path.call(print, "callee"));
            if (n.type === "OptionalCallExpression" &&
                n.callee.type !== "OptionalMemberExpression") {
                parts.push("?.");
            }
            parts.push(printArgumentsList(path, options, print));
            return lines_1.concat(parts);
        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");
                if (n.internalSlots != null) {
                    fields.push("internalSlots");
                }
            }
            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;
            }
            if (n.typeAnnotation) {
                parts.push(path.call(print, "typeAnnotation"));
            }
            return lines_1.concat(parts);
        case "PropertyPattern":
            return lines_1.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 lines_1.concat(parts);
        case "ClassMethod": // Babel 6
        case "ObjectMethod": // Babel 6
        case "ClassPrivateMethod":
        case "TSDeclareMethod":
            return printMethod(path, options, print);
        case "PrivateName":
            return lines_1.concat(["#", path.call(print, "id")]);
        case "Decorator":
            return lines_1.concat(["@", path.call(print, "expression")]);
        case "ArrayExpression":
        case "ArrayPattern":
            var elems = n.elements, len = elems.length;
            var printed = path.map(print, "elements");
            var joined = lines_1.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 lines_1.concat(parts);
        case "SequenceExpression":
            return lines_1.fromString(", ").join(path.map(print, "expressions"));
        case "ThisExpression":
            return lines_1.fromString("this");
        case "Super":
            return lines_1.fromString("super");
        case "NullLiteral": // Babel 6 Literal split
            return lines_1.fromString("null");
        case "RegExpLiteral": // Babel 6 Literal split
            return lines_1.fromString(n.extra.raw);
        case "BigIntLiteral": // Babel 7 Literal split
            return lines_1.fromString(n.value + "n");
        case "NumericLiteral": // Babel 6 Literal Split
            // Keep original representation for numeric values not in base 10.
            if (n.extra &&
                typeof n.extra.raw === "string" &&
                Number(n.extra.raw) === n.value) {
                return lines_1.fromString(n.extra.raw, options);
            }
            return lines_1.fromString(n.value, options);
        case "BooleanLiteral": // Babel 6 Literal split
        case "StringLiteral": // Babel 6 Literal split
        case "Literal":
            // Numeric values may be in bases other than 10. Use their raw
            // representation if equivalent.
            if (typeof n.value === "number" &&
                typeof n.raw === "string" &&
                Number(n.raw) === n.value) {
                return lines_1.fromString(n.raw, options);
            }
            if (typeof n.value !== "string") {
                return lines_1.fromString(n.value, options);
            }
            return lines_1.fromString(nodeStr(n.value, options), options);
        case "Directive": // Babel 6
            return path.call(print, "value");
        case "DirectiveLiteral": // Babel 6
            return lines_1.fromString(nodeStr(n.value, options));
        case "InterpreterDirective":
            return lines_1.fromString("#!" + n.value + "\n", 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 lines_1.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 lines_1.concat(parts);
        case "UpdateExpression":
            parts.push(path.call(print, "argument"), n.operator);
            if (n.prefix)
                parts.reverse();
            return lines_1.concat(parts);
        case "ConditionalExpression":
            return lines_1.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 lines_1.concat(parts);
        case "VariableDeclaration":
            if (n.declare) {
                parts.push("declare ");
            }
            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(lines_1.fromString(", ").join(printed));
            }
            else if (printed.length > 1) {
                parts.push(lines_1.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 lines_1.concat(parts);
        case "VariableDeclarator":
            return n.init ? lines_1.fromString(" = ").join([
                path.call(print, "id"),
                path.call(print, "init")
            ]) : path.call(print, "id");
        case "WithStatement":
            return lines_1.concat([
                "with (",
                path.call(print, "object"),
                ") ",
                path.call(print, "body")
            ]);
        case "IfStatement":
            var con = adjustClause(path.call(print, "consequent"), options);
            parts.push("if (", path.call(print, "test"), ")", con);
            if (n.alternate)
                parts.push(endsWithBrace(con) ? " else" : "\nelse", adjustClause(path.call(print, "alternate"), options));
            return lines_1.concat(parts);
        case "ForStatement":
            // TODO Get the for (;;) case right.
            var init = path.call(print, "init"), sep = init.length > 1 ? ";\n" : "; ", forParen = "for (", indented = lines_1.fromString(sep).join([
                init,
                path.call(print, "test"),
                path.call(print, "update")
            ]).indentTail(forParen.length), head = lines_1.concat([forParen, indented, ")"]), clause = adjustClause(path.call(print, "body"), options);
            parts.push(head);
            if (head.length > 1) {
                parts.push("\n");
                clause = clause.trimLeft();
            }
            parts.push(clause);
            return lines_1.concat(parts);
        case "WhileStatement":
            return lines_1.concat([
                "while (",
                path.call(print, "test"),
                ")",
                adjustClause(path.call(print, "body"), options)
            ]);
        case "ForInStatement":
            // Note: esprima can't actually parse "for each (".
            return lines_1.concat([
                n.each ? "for each (" : "for (",
                path.call(print, "left"),
                " in ",
                path.call(print, "right"),
                ")",
                adjustClause(path.call(print, "body"), options)
            ]);
        case "ForOfStatement":
        case "ForAwaitStatement":
            parts.push("for ");
            if (n.await || n.type === "ForAwaitStatement") {
                parts.push("await ");
            }
            parts.push("(", path.call(print, "left"), " of ", path.call(print, "right"), ")", adjustClause(path.call(print, "body"), options));
            return lines_1.concat(parts);
        case "DoWhileStatement":
            var doBody = lines_1.concat([
                "do",
                adjustClause(path.call(print, "body"), options)
            ]);
            parts.push(doBody);
            if (endsWithBrace(doBody))
                parts.push(" while");
            else
                parts.push("\nwhile");
            parts.push(" (", path.call(print, "test"), ");");
            return lines_1.concat(parts);
        case "DoExpression":
            var statements = path.call(function (bodyPath) {
                return printStatementSequence(bodyPath, options, print);
            }, "body");
            return lines_1.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 lines_1.concat(parts);
        case "ContinueStatement":
            parts.push("continue");
            if (n.label)
                parts.push(" ", path.call(print, "label"));
            parts.push(";");
            return lines_1.concat(parts);
        case "LabeledStatement":
            return lines_1.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 lines_1.concat(parts);
        case "CatchClause":
            parts.push("catch ");
            if (n.param) {
                parts.push("(", path.call(print, "param"));
            }
            if (n.guard) {
                // Note: esprima does not recognize conditional catch clauses.
                parts.push(" if ", path.call(print, "guard"));
            }
            if (n.param) {
                parts.push(") ");
            }
            parts.push(path.call(print, "body"));
            return lines_1.concat(parts);
        case "ThrowStatement":
            return lines_1.concat(["throw ", path.call(print, "argument"), ";"]);
        case "SwitchStatement":
            return lines_1.concat([
                "switch (",
                path.call(print, "discriminant"),
                ") {\n",
                lines_1.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 lines_1.concat(parts);
        case "DebuggerStatement":
            return lines_1.fromString("debugger;");
        // JSX extensions below.
        case "JSXAttribute":
            parts.push(path.call(print, "name"));
            if (n.value)
                parts.push("=", path.call(print, "value"));
            return lines_1.concat(parts);
        case "JSXIdentifier":
            return lines_1.fromString(n.name, options);
        case "JSXNamespacedName":
            return lines_1.fromString(":").join([
                path.call(print, "namespace"),
                path.call(print, "name")
            ]);
        case "JSXMemberExpression":
            return lines_1.fromString(".").join([
                path.call(print, "object"),
                path.call(print, "property")
            ]);
        case "JSXSpreadAttribute":
            return lines_1.concat(["{...", path.call(print, "argument"), "}"]);
        case "JSXSpreadChild":
            return lines_1.concat(["{...", path.call(print, "expression"), "}"]);
        case "JSXExpressionContainer":
            return lines_1.concat(["{", path.call(print, "expression"), "}"]);
        case "JSXElement":
        case "JSXFragment":
            var openingPropName = "opening" + (n.type === "JSXElement" ? "Element" : "Fragment");
            var closingPropName = "closing" + (n.type === "JSXElement" ? "Element" : "Fragment");
            var openingLines = path.call(print, openingPropName);
            if (n[openingPropName].selfClosing) {
                assert_1.default.ok(!n[closingPropName], "unexpected " + closingPropName + " element in self-closing " + n.type);
                return openingLines;
            }
            var childLines = lines_1.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, closingPropName);
            return lines_1.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 = lines_1.concat(attrParts);
            var needLineWrap = (attrLines.length > 1 ||
                attrLines.getLineLength(1) > options.wrapColumn);
            if (needLineWrap) {
                attrParts.forEach(function (part, i) {
                    if (part === " ") {
                        assert_1.default.strictEqual(i % 2, 0);
                        attrParts[i] = "\n";
                    }
                });
                attrLines = lines_1.concat(attrParts).indentTail(options.tabWidth);
            }
            parts.push(attrLines, n.selfClosing ? " />" : ">");
            return lines_1.concat(parts);
        case "JSXClosingElement":
            return lines_1.concat(["</", path.call(print, "name"), ">"]);
        case "JSXOpeningFragment":
            return lines_1.fromString("<>");
        case "JSXClosingFragment":
            return lines_1.fromString("</>");
        case "JSXText":
            return lines_1.fromString(n.value, options);
        case "JSXEmptyExpression":
            return lines_1.fromString("");
        case "TypeAnnotatedIdentifier":
            return lines_1.concat([
                path.call(print, "annotation"),
                " ",
                path.call(print, "identifier")
            ]);
        case "ClassBody":
            if (n.body.length === 0) {
                return lines_1.fromString("{}");
            }
            return lines_1.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 lines_1.concat(parts);
        case "ClassProperty":
            if (typeof n.accessibility === "string") {
                parts.push(n.accessibility, " ");
            }
            if (n.static) {
                parts.push("static ");
            }
            if (n.abstract) {
                parts.push("abstract ");
            }
            if (n.readonly) {
                parts.push("readonly ");
            }
            var key = path.call(print, "key");
            if (n.computed) {
                key = lines_1.concat(["[", key, "]"]);
            }
            if (n.variance) {
                key = lines_1.concat([printVariance(path, print), key]);
            }
            parts.push(key);
            if (n.optional) {
                parts.push("?");
            }
            if (n.typeAnnotation) {
                parts.push(path.call(print, "typeAnnotation"));
            }
            if (n.value) {
                parts.push(" = ", path.call(print, "value"));
            }
            parts.push(";");
            return lines_1.concat(parts);
        case "ClassPrivateProperty":
            if (n.static) {
                parts.push("static ");
            }
            parts.push(path.call(print, "key"));
            if (n.typeAnnotation) {
                parts.push(path.call(print, "typeAnnotation"));
            }
            if (n.value) {
                parts.push(" = ", path.call(print, "value"));
            }
            parts.push(";");
            return lines_1.concat(parts);
        case "ClassDeclaration":
        case "ClassExpression":
            if (n.declare) {
                parts.push("declare ");
            }
            if (n.abstract) {
                parts.push("abstract ");
            }
            parts.push("class");
            if (n.id) {
                parts.push(" ", path.call(print, "id"));
            }
            if (n.typeParameters) {
                parts.push(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 ", lines_1.fromString(", ").join(path.map(print, "implements")));
            }
            parts.push(" ", path.call(print, "body"));
            return lines_1.concat(parts);
        case "TemplateElement":
            return lines_1.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 lines_1.concat(parts).lockIndentTail();
        case "TaggedTemplateExpression":
            return lines_1.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 "Flow": // Supertype of all Flow AST node types
        case "FlowType": // Supertype of all Flow types
        case "FlowPredicate": // Supertype of InferredPredicate and DeclaredPredicate
        case "MemberTypeAnnotation": // Flow
        case "Type": // Flow
        case "TSHasOptionalTypeParameters":
        case "TSHasOptionalTypeAnnotation":
            throw new Error("unprintable type: " + JSON.stringify(n.type));
        case "CommentBlock": // Babel block comment.
        case "Block": // Esprima block comment.
            return lines_1.concat(["/*", lines_1.fromString(n.value, options), "*/"]);
        case "CommentLine": // Babel line comment.
        case "Line": // Esprima line comment.
            return lines_1.concat(["//", lines_1.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 lines_1.concat(parts);
            }
            return lines_1.fromString("");
        case "ExistentialTypeParam":
        case "ExistsTypeAnnotation":
            return lines_1.fromString("*", options);
        case "EmptyTypeAnnotation":
            return lines_1.fromString("empty", options);
        case "AnyTypeAnnotation":
            return lines_1.fromString("any", options);
        case "MixedTypeAnnotation":
            return lines_1.fromString("mixed", options);
        case "ArrayTypeAnnotation":
            return lines_1.concat([
                path.call(print, "elementType"),
                "[]"
            ]);
        case "TupleTypeAnnotation":
            var printed = path.map(print, "types");
            var joined = lines_1.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 < n.types.length - 1 || (!oneLine && util.isTrailingCommaEnabled(options, "arrays")))
                        parts.push(",");
                    if (!oneLine)
                        parts.push("\n");
                }
            }, "types");
            if (oneLine && options.arrayBracketSpacing) {
                parts.push(" ]");
            }
            else {
                parts.push("]");
            }
            return lines_1.concat(parts);
        case "BooleanTypeAnnotation":
            return lines_1.fromString("boolean", options);
        case "BooleanLiteralTypeAnnotation":
            assert_1.default.strictEqual(typeof n.value, "boolean");
            return lines_1.fromString("" + n.value, options);
        case "InterfaceTypeAnnotation":
            parts.push("interface");
            if (n.extends && n.extends.length > 0) {
                parts.push(" extends ", lines_1.fromString(", ").join(path.map(print, "extends")));
            }
            parts.push(" ", path.call(print, "body"));
            return lines_1.concat(parts);
        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 lines_1.concat([
                "declare ",
                printExportDeclaration(path, options, print)
            ]);
        case "InferredPredicate":
            return lines_1.fromString("%checks", options);
        case "DeclaredPredicate":
            return lines_1.concat([
                "%checks(",
                path.call(print, "value"),
                ")"
            ]);
        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.ObjectTypeInternalSlot.check(parent) && parent.method) ||
                namedTypes.DeclareFunction.check(path.getParentNode(2)));
            var needsColon = isArrowFunctionTypeAnnotation &&
                !namedTypes.FunctionTypeParam.check(parent);
            if (needsColon) {
                parts.push(": ");
            }
            parts.push("(", printFunctionParams(path, options, print), ")");
            // 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 lines_1.concat(parts);
        case "FunctionTypeParam":
            return lines_1.concat([
                path.call(print, "name"),
                n.optional ? '?' : '',
                ": ",
                path.call(print, "typeAnnotation"),
            ]);
        case "GenericTypeAnnotation":
            return lines_1.concat([
                path.call(print, "id"),
                path.call(print, "typeParameters")
            ]);
        case "DeclareInterface":
            parts.push("declare ");
        // Fall through to InterfaceDeclaration...
        case "InterfaceDeclaration":
        case "TSInterfaceDeclaration":
            if (n.declare) {
                parts.push("declare ");
            }
            parts.push("interface ", path.call(print, "id"), path.call(print, "typeParameters"), " ");
            if (n["extends"] && n["extends"].length > 0) {
                parts.push("extends ", lines_1.fromString(", ").join(path.map(print, "extends")), " ");
            }
            if (n.body) {
                parts.push(path.call(print, "body"));
            }
            return lines_1.concat(parts);
        case "ClassImplements":
        case "InterfaceExtends":
            return lines_1.concat([
                path.call(print, "id"),
                path.call(print, "typeParameters")
            ]);
        case "IntersectionTypeAnnotation":
            return lines_1.fromString(" & ").join(path.map(print, "types"));
        case "NullableTypeAnnotation":
            return lines_1.concat([
                "?",
                path.call(print, "typeAnnotation")
            ]);
        case "NullLiteralTypeAnnotation":
            return lines_1.fromString("null", options);
        case "ThisTypeAnnotation":
            return lines_1.fromString("this", options);
        case "NumberTypeAnnotation":
            return lines_1.fromString("number", options);
        case "ObjectTypeCallProperty":
            return path.call(print, "value");
        case "ObjectTypeIndexer":
            return lines_1.concat([
                printVariance(path, print),
                "[",
                path.call(print, "id"),
                ": ",
                path.call(print, "key"),
                "]: ",
                path.call(print, "value")
            ]);
        case "ObjectTypeProperty":
            return lines_1.concat([
                printVariance(path, print),
                path.call(print, "key"),
                n.optional ? "?" : "",
                ": ",
                path.call(print, "value")
            ]);
        case "ObjectTypeInternalSlot":
            return lines_1.concat([
                n.static ? "static " : "",
                "[[",
                path.call(print, "id"),
                "]]",
                n.optional ? "?" : "",
                n.value.type !== "FunctionTypeAnnotation" ? ": " : "",
                path.call(print, "value")
            ]);
        case "QualifiedTypeIdentifier":
            return lines_1.concat([
                path.call(print, "qualification"),
                ".",
                path.call(print, "id")
            ]);
        case "StringLiteralTypeAnnotation":
            return lines_1.fromString(nodeStr(n.value, options), options);
        case "NumberLiteralTypeAnnotation":
        case "NumericLiteralTypeAnnotation":
            assert_1.default.strictEqual(typeof n.value, "number");
            return lines_1.fromString(JSON.stringify(n.value), options);
        case "StringTypeAnnotation":
            return lines_1.fromString("string", options);
        case "DeclareTypeAlias":
            parts.push("declare ");
        // Fall through to TypeAlias...
        case "TypeAlias":
            return lines_1.concat([
                "type ",
                path.call(print, "id"),
                path.call(print, "typeParameters"),
                " = ",
                path.call(print, "right"),
                ";"
            ]);
        case "DeclareOpaqueType":
            parts.push("declare ");
        // Fall through to OpaqueType...
        case "OpaqueType":
            parts.push("opaque type ", path.call(print, "id"), path.call(print, "typeParameters"));
            if (n["supertype"]) {
                parts.push(": ", path.call(print, "supertype"));
            }
            if (n["impltype"]) {
                parts.push(" = ", path.call(print, "impltype"));
            }
            parts.push(";");
            return lines_1.concat(parts);
        case "TypeCastExpression":
            return lines_1.concat([
                "(",
                path.call(print, "expression"),
                path.call(print, "typeAnnotation"),
                ")"
            ]);
        case "TypeParameterDeclaration":
        case "TypeParameterInstantiation":
            return lines_1.concat([
                "<",
                lines_1.fromString(", ").join(path.map(print, "params")),
                ">"
            ]);
        case "Variance":
            if (n.kind === "plus") {
                return lines_1.fromString("+");
            }
            if (n.kind === "minus") {
                return lines_1.fromString("-");
            }
            return lines_1.fromString("");
        case "TypeParameter":
            if (n.variance) {
                parts.push(printVariance(path, print));
            }
            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 lines_1.concat(parts);
        case "TypeofTypeAnnotation":
            return lines_1.concat([
                lines_1.fromString("typeof ", options),
                path.call(print, "argument")
            ]);
        case "UnionTypeAnnotation":
            return lines_1.fromString(" | ").join(path.map(print, "types"));
        case "VoidTypeAnnotation":
            return lines_1.fromString("void", options);
        case "NullTypeAnnotation":
            return lines_1.fromString("null", options);
        // Type Annotations for TypeScript (when using Babylon as parser)
        case "TSType":
            throw new Error("unprintable type: " + JSON.stringify(n.type));
        case "TSNumberKeyword":
            return lines_1.fromString("number", options);
        case "TSObjectKeyword":
            return lines_1.fromString("object", options);
        case "TSBooleanKeyword":
            return lines_1.fromString("boolean", options);
        case "TSStringKeyword":
            return lines_1.fromString("string", options);
        case "TSSymbolKeyword":
            return lines_1.fromString("symbol", options);
        case "TSAnyKeyword":
            return lines_1.fromString("any", options);
        case "TSVoidKeyword":
            return lines_1.fromString("void", options);
        case "TSThisType":
            return lines_1.fromString("this", options);
        case "TSNullKeyword":
            return lines_1.fromString("null", options);
        case "TSUndefinedKeyword":
            return lines_1.fromString("undefined", options);
        case "TSUnknownKeyword":
            return lines_1.fromString("unknown", options);
        case "TSNeverKeyword":
            return lines_1.fromString("never", options);
        case "TSArrayType":
            return lines_1.concat([
                path.call(print, "elementType"),
                "[]"
            ]);
        case "TSLiteralType":
            return path.call(print, "literal");
        case "TSUnionType":
            return lines_1.fromString(" | ").join(path.map(print, "types"));
        case "TSIntersectionType":
            return lines_1.fromString(" & ").join(path.map(print, "types"));
        case "TSConditionalType":
            parts.push(path.call(print, "checkType"), " extends ", path.call(print, "extendsType"), " ? ", path.call(print, "trueType"), " : ", path.call(print, "falseType"));
            return lines_1.concat(parts);
        case "TSInferType":
            parts.push("infer ", path.call(print, "typeParameter"));
            return lines_1.concat(parts);
        case "TSParenthesizedType":
            return lines_1.concat([
                "(",
                path.call(print, "typeAnnotation"),
                ")"
            ]);
        case "TSFunctionType":
        case "TSConstructorType":
            return lines_1.concat([
                path.call(print, "typeParameters"),
                "(",
                printFunctionParams(path, options, print),
                ")",
                path.call(print, "typeAnnotation")
            ]);
        case "TSMappedType": {
            parts.push(n.readonly ? "readonly " : "", "[", path.call(print, "typeParameter"), "]", n.optional ? "?" : "");
            if (n.typeAnnotation) {
                parts.push(": ", path.call(print, "typeAnnotation"), ";");
            }
            return lines_1.concat([
                "{\n",
                lines_1.concat(parts).indent(options.tabWidth),
                "\n}",
            ]);
        }
        case "TSTupleType":
            return lines_1.concat([
                "[",
                lines_1.fromString(", ").join(path.map(print, "elementTypes")),
                "]"
            ]);
        case "TSRestType":
            return lines_1.concat([
                "...",
                path.call(print, "typeAnnotation"),
                "[]"
            ]);
        case "TSOptionalType":
            return lines_1.concat([
                path.call(print, "typeAnnotation"),
                "?"
            ]);
        case "TSIndexedAccessType":
            return lines_1.concat([
                path.call(print, "objectType"),
                "[",
                path.call(print, "indexType"),
                "]"
            ]);
        case "TSTypeOperator":
            return lines_1.concat([
                path.call(print, "operator"),
                " ",
                path.call(print, "typeAnnotation")
            ]);
        case "TSTypeLiteral": {
            var memberLines_1 = lines_1.fromString(",\n").join(path.map(print, "members"));
            if (memberLines_1.isEmpty()) {
                return lines_1.fromString("{}", options);
            }
            parts.push("{\n", memberLines_1.indent(options.tabWidth), "\n}");
            return lines_1.concat(parts);
        }
        case "TSEnumMember":
            parts.push(path.call(print, "id"));
            if (n.initializer) {
                parts.push(" = ", path.call(print, "initializer"));
            }
            return lines_1.concat(parts);
        case "TSTypeQuery":
            return lines_1.concat([
                "typeof ",
                path.call(print, "exprName"),
            ]);
        case "TSParameterProperty":
            if (n.accessibility) {
                parts.push(n.accessibility, " ");
            }
            if (n.export) {
                parts.push("export ");
            }
            if (n.static) {
                parts.push("static ");
            }
            if (n.readonly) {
                parts.push("readonly ");
            }
            parts.push(path.call(print, "parameter"));
            return lines_1.concat(parts);
        case "TSTypeReference":
            return lines_1.concat([
                path.call(print, "typeName"),
                path.call(print, "typeParameters")
            ]);
        case "TSQualifiedName":
            return lines_1.concat([
                path.call(print, "left"),
                ".",
                path.call(print, "right")
            ]);
        case "TSAsExpression": {
            var withParens = n.extra && n.extra.parenthesized === true;
            if (withParens)
                parts.push("(");
            parts.push(path.call(print, "expression"), lines_1.fromString(" as "), path.call(print, "typeAnnotation"));
            if (withParens)
                parts.push(")");
            return lines_1.concat(parts);
        }
        case "TSNonNullExpression":
            return lines_1.concat([
                path.call(print, "expression"),
                "!"
            ]);
        case "TSTypeAnnotation": {
            // similar to flow's FunctionTypeAnnotation, this can be
            // ambiguous: it can be prefixed by => or :
            // in a type predicate, it takes the for u is U
            var parent = path.getParentNode(0);
            var prefix = ": ";
            if (namedTypes.TSFunctionType.check(parent)) {
                prefix = " => ";
            }
            if (namedTypes.TSTypePredicate.check(parent)) {
                prefix = " is ";
            }
            return lines_1.concat([
                prefix,
                path.call(print, "typeAnnotation")
            ]);
        }
        case "TSIndexSignature":
            return lines_1.concat([
                n.readonly ? "readonly " : "",
                "[",
                path.map(print, "parameters"),
                "]",
                path.call(print, "typeAnnotation")
            ]);
        case "TSPropertySignature":
            parts.push(printVariance(path, print), n.readonly ? "readonly " : "");
            if (n.computed) {
                parts.push("[", path.call(print, "key"), "]");
            }
            else {
                parts.push(path.call(print, "key"));
            }
            parts.push(n.optional ? "?" : "", path.call(print, "typeAnnotation"));
            return lines_1.concat(parts);
        case "TSMethodSignature":
            if (n.computed) {
                parts.push("[", path.call(print, "key"), "]");
            }
            else {
                parts.push(path.call(print, "key"));
            }
            if (n.optional) {
                parts.push("?");
            }
            parts.push(path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation"));
            return lines_1.concat(parts);
        case "TSTypePredicate":
            return lines_1.concat([
                path.call(print, "parameterName"),
                path.call(print, "typeAnnotation")
            ]);
        case "TSCallSignatureDeclaration":
            return lines_1.concat([
                path.call(print, "typeParameters"),
                "(",
                printFunctionParams(path, options, print),
                ")",
                path.call(print, "typeAnnotation")
            ]);
        case "TSConstructSignatureDeclaration":
            if (n.typeParameters) {
                parts.push("new", path.call(print, "typeParameters"));
            }
            else {
                parts.push("new ");
            }
            parts.push("(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation"));
            return lines_1.concat(parts);
        case "TSTypeAliasDeclaration":
            return lines_1.concat([
                n.declare ? "declare " : "",
                "type ",
                path.call(print, "id"),
                path.call(print, "typeParameters"),
                " = ",
                path.call(print, "typeAnnotation"),
                ";"
            ]);
        case "TSTypeParameter":
            parts.push(path.call(print, "name"));
            // ambiguous because of TSMappedType
            var parent = path.getParentNode(0);
            var isInMappedType = namedTypes.TSMappedType.check(parent);
            if (n.constraint) {
                parts.push(isInMappedType ? " in " : " extends ", path.call(print, "constraint"));
            }
            if (n["default"]) {
                parts.push(" = ", path.call(print, "default"));
            }
            return lines_1.concat(parts);
        case "TSTypeAssertion":
            var withParens = n.extra && n.extra.parenthesized === true;
            if (withParens) {
                parts.push("(");
            }
            parts.push("<", path.call(print, "typeAnnotation"), "> ", path.call(print, "expression"));
            if (withParens) {
                parts.push(")");
            }
            return lines_1.concat(parts);
        case "TSTypeParameterDeclaration":
        case "TSTypeParameterInstantiation":
            return lines_1.concat([
                "<",
                lines_1.fromString(", ").join(path.map(print, "params")),
                ">"
            ]);
        case "TSEnumDeclaration":
            parts.push(n.declare ? "declare " : "", n.const ? "const " : "", "enum ", path.call(print, "id"));
            var memberLines = lines_1.fromString(",\n").join(path.map(print, "members"));
            if (memberLines.isEmpty()) {
                parts.push(" {}");
            }
            else {
                parts.push(" {\n", memberLines.indent(options.tabWidth), "\n}");
            }
            return lines_1.concat(parts);
        case "TSExpressionWithTypeArguments":
            return lines_1.concat([
                path.call(print, "expression"),
                path.call(print, "typeParameters")
            ]);
        case "TSInterfaceBody":
            var lines = lines_1.fromString(";\n").join(path.map(print, "body"));
            if (lines.isEmpty()) {
                return lines_1.fromString("{}", options);
            }
            return lines_1.concat([
                "{\n",
                lines.indent(options.tabWidth), ";",
                "\n}",
            ]);
        case "TSImportEqualsDeclaration":
            if (n.isExport) {
                parts.push("export ");
            }
            parts.push("import ", path.call(print, "id"), " = ", path.call(print, "moduleReference"));
            return maybeAddSemicolon(lines_1.concat(parts));
        case "TSExternalModuleReference":
            return lines_1.concat(["require(", path.call(print, "expression"), ")"]);
        case "TSModuleDeclaration": {
            var parent_1 = path.getParentNode();
            if (parent_1.type === "TSModuleDeclaration") {
                parts.push(".");
            }
            else {
                if (n.declare) {
                    parts.push("declare ");
                }
                if (!n.global) {
                    var isExternal = n.id.type === "StringLiteral" ||
                        (n.id.type === "Literal" &&
                            typeof n.id.value === "string");
                    if (isExternal) {
                        parts.push("module ");
                    }
                    else if (n.loc &&
                        n.loc.lines &&
                        n.id.loc) {
                        var prefix_1 = n.loc.lines.sliceString(n.loc.start, n.id.loc.start);
                        // These keywords are fundamentally ambiguous in the
                        // Babylon parser, and not reflected in the AST, so
                        // the best we can do is to match the original code,
                        // when possible.
                        if (prefix_1.indexOf("module") >= 0) {
                            parts.push("module ");
                        }
                        else {
                            parts.push("namespace ");
                        }
                    }
                    else {
                        parts.push("namespace ");
                    }
                }
            }
            parts.push(path.call(print, "id"));
            if (n.body && n.body.type === "TSModuleDeclaration") {
                parts.push(path.call(print, "body"));
            }
            else if (n.body) {
                var bodyLines = path.call(print, "body");
                if (bodyLines.isEmpty()) {
                    parts.push(" {}");
                }
                else {
                    parts.push(" {\n", bodyLines.indent(options.tabWidth), "\n}");
                }
            }
            return lines_1.concat(parts);
        }
        case "TSModuleBlock":
            return path.call(function (bodyPath) {
                return printStatementSequence(bodyPath, options, print);
            }, "body");
        // 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));
    }
}
function printDecorators(path, printPath) {
    var parts = [];
    var node = path.getValue();
    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");
    }
    return lines_1.concat(parts);
}
function printStatementSequence(path, options, print) {
    var filtered = [];
    var sawComment = false;
    var sawStatement = false;
    path.each(function (stmtPath) {
        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" &&
            !(stmt.comments && stmt.comments.length > 0)) {
            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_1.default.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 lines_1.concat(parts);
}
function maxSpace(s1, s2) {
    if (!s1 && !s2) {
        return lines_1.fromString("");
    }
    if (!s1) {
        return lines_1.fromString(s2);
    }
    if (!s2) {
        return lines_1.fromString(s1);
    }
    var spaceLines1 = lines_1.fromString(s1);
    var spaceLines2 = lines_1.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 = [];
    var nodeValue = node.value;
    if (!namedTypes.FunctionExpression.check(nodeValue)) {
        nodeValue = node;
    }
    var access = node.accessibility || node.access;
    if (typeof access === "string") {
        parts.push(access, " ");
    }
    if (node.static) {
        parts.push("static ");
    }
    if (node.abstract) {
        parts.push("abstract ");
    }
    if (node.readonly) {
        parts.push("readonly ");
    }
    if (nodeValue.async) {
        parts.push("async ");
    }
    if (nodeValue.generator) {
        parts.push("*");
    }
    if (kind === "get" || kind === "set") {
        parts.push(kind, " ");
    }
    var key = path.call(print, "key");
    if (node.computed) {
        key = lines_1.concat(["[", key, "]"]);
    }
    parts.push(key);
    if (node.optional) {
        parts.push("?");
    }
    if (node === nodeValue) {
        parts.push(path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "returnType"));
        if (node.body) {
            parts.push(" ", path.call(print, "body"));
        }
        else {
            parts.push(";");
        }
    }
    else {
        parts.push(path.call(print, "value", "typeParameters"), "(", path.call(function (valuePath) {
            return printFunctionParams(valuePath, options, print);
        }, "value"), ")", path.call(print, "value", "returnType"));
        if (nodeValue.body) {
            parts.push(" ", path.call(print, "value", "body"));
        }
        else {
            parts.push(";");
        }
    }
    return lines_1.concat(parts);
}
function printArgumentsList(path, options, print) {
    var printed = path.map(print, "arguments");
    var trailingComma = util.isTrailingCommaEnabled(options, "parameters");
    var joined = lines_1.fromString(", ").join(printed);
    if (joined.getLineLength(1) > options.wrapColumn) {
        joined = lines_1.fromString(",\n").join(printed);
        return lines_1.concat([
            "(\n",
            joined.indent(options.tabWidth),
            trailingComma ? ",\n)" : "\n)"
        ]);
    }
    return lines_1.concat(["(", joined, ")"]);
}
function printFunctionParams(path, options, print) {
    var fun = path.getValue();
    if (fun.params) {
        var params = fun.params;
        var printed = path.map(print, "params");
    }
    else if (fun.parameters) {
        params = fun.parameters;
        printed = path.map(print, "parameters");
    }
    if (fun.defaults) {
        path.each(function (defExprPath) {
            var i = defExprPath.getName();
            var p = printed[i];
            if (p && defExprPath.getValue()) {
                printed[i] = lines_1.concat([p, " = ", print(defExprPath)]);
            }
        }, "defaults");
    }
    if (fun.rest) {
        printed.push(lines_1.concat(["...", path.call(print, "rest")]));
    }
    var joined = lines_1.fromString(", ").join(printed);
    if (joined.length > 1 ||
        joined.getLineLength(1) > options.wrapColumn) {
        joined = lines_1.fromString(",\n").join(printed);
        if (util.isTrailingCommaEnabled(options, "parameters") &&
            !fun.rest &&
            params[params.length - 1].type !== 'RestElement') {
            joined = lines_1.concat([joined, ",\n"]);
        }
        else {
            joined = lines_1.concat([joined, "\n"]);
        }
        return lines_1.concat(["\n", joined.indent(options.tabWidth)]);
    }
    return joined;
}
function printExportDeclaration(path, options, print) {
    var decl = path.getValue();
    var parts = ["export "];
    if (decl.exportKind && decl.exportKind !== "value") {
        parts.push(decl.exportKind + " ");
    }
    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) {
        if (decl.specifiers.length === 1 &&
            decl.specifiers[0].type === "ExportBatchSpecifier") {
            parts.push("*");
        }
        else if (decl.specifiers.length === 0) {
            parts.push("{}");
        }
        else if (decl.specifiers[0].type === 'ExportDefaultSpecifier') {
            var unbracedSpecifiers_2 = [];
            var bracedSpecifiers_2 = [];
            path.each(function (specifierPath) {
                var spec = specifierPath.getValue();
                if (spec.type === "ExportDefaultSpecifier") {
                    unbracedSpecifiers_2.push(print(specifierPath));
                }
                else {
                    bracedSpecifiers_2.push(print(specifierPath));
                }
            }, "specifiers");
            unbracedSpecifiers_2.forEach(function (lines, i) {
                if (i > 0) {
                    parts.push(", ");
                }
                parts.push(lines);
            });
            if (bracedSpecifiers_2.length > 0) {
                var lines_3 = lines_1.fromString(", ").join(bracedSpecifiers_2);
                if (lines_3.getLineLength(1) > options.wrapColumn) {
                    lines_3 = lines_1.concat([
                        lines_1.fromString(",\n").join(bracedSpecifiers_2).indent(options.tabWidth),
                        ","
                    ]);
                }
                if (unbracedSpecifiers_2.length > 0) {
                    parts.push(", ");
                }
                if (lines_3.length > 1) {
                    parts.push("{\n", lines_3, "\n}");
                }
                else if (options.objectCurlySpacing) {
                    parts.push("{ ", lines_3, " }");
                }
                else {
                    parts.push("{", lines_3, "}");
                }
            }
        }
        else {
            parts.push(shouldPrintSpaces ? "{ " : "{", lines_1.fromString(", ").join(path.map(print, "specifiers")), shouldPrintSpaces ? " }" : "}");
        }
        if (decl.source) {
            parts.push(" from ", path.call(print, "source"));
        }
    }
    var lines = lines_1.concat(parts);
    if (lastNonSpaceCharacter(lines) !== ";" &&
        !(decl.declaration &&
            (decl.declaration.type === "FunctionDeclaration" ||
                decl.declaration.type === "ClassDeclaration" ||
                decl.declaration.type === "TSModuleDeclaration" ||
                decl.declaration.type === "TSInterfaceDeclaration" ||
                decl.declaration.type === "TSEnumDeclaration"))) {
        lines = lines_1.concat([lines, ";"]);
    }
    return lines;
}
function printFlowDeclaration(path, parts) {
    var parentExportDecl = util.getParentExportDeclaration(path);
    if (parentExportDecl) {
        assert_1.default.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 lines_1.concat(parts);
}
function printVariance(path, print) {
    return path.call(function (variancePath) {
        var value = variancePath.getValue();
        if (value) {
            if (value === "plus") {
                return lines_1.fromString("+");
            }
            if (value === "minus") {
                return lines_1.fromString("-");
            }
            return print(variancePath);
        }
        return lines_1.fromString("");
    }, "variance");
}
function adjustClause(clause, options) {
    if (clause.length > 1)
        return lines_1.concat([" ", clause]);
    return lines_1.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 lines_1.concat([lines, ";"]);
    return lines;
}
