recast-harmony
Version:
JavaScript syntax tree transformer, conservative pretty-printer, and automatic source map generator
1,205 lines (953 loc) • 32.6 kB
JavaScript
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);
var properties = {
code: {
value: code,
enumerable: true
}
};
if (sourceMap) {
isObject.assert(sourceMap);
properties.map = {
value: sourceMap,
enumerable: true
};
}
Object.defineProperties(this, properties);
}
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));
}
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 "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);
}
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)) {
parts.push(" ", print(path.get("source")));
} else if (n.kind === "default") {
parts.push(
" ",
print(path.get("specifiers", 0)),
" from ",
print(path.get("source"))
);
} else if (n.kind === "named") {
parts.push(
" { ",
fromString(", ").join(path.get("specifiers").map(print)),
" } from ",
print(path.get("source"))
);
}
parts.push(";");
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
);
}
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("var ".length)
);
} 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":
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 "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);
// 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
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;
}