var assert = require(“assert”); var sourceMap = require(“source-map”); var printComments = require(“./comments”).printComments; var linesModule = require(“./lines”); var fromString = linesModule.fromString; var concat = linesModule.concat; var normalizeOptions = require(“./options”).normalize; var getReprinter = require(“./patcher”).getReprinter; var types = require(“./types”); var namedTypes = types.namedTypes; var isString = types.builtInTypes.string; var isObject = types.builtInTypes.object; var NodePath = types.NodePath; var util = require(“./util”);
function PrintResult(code, sourceMap) {
assert.ok(this instanceof PrintResult); isString.assert(code); this.code = code; if (sourceMap) { isObject.assert(sourceMap); this.map = sourceMap; }
}
var PRp = PrintResult.prototype; var warnedAboutToString = false;
PRp.toString = function() {
if (!warnedAboutToString) { console.warn( "Deprecation warning: recast.print now returns an object with " + "a .code property. You appear to be treating the object as a " + "string, which might still work but is strongly discouraged." ); warnedAboutToString = true; } return this.code;
};
var emptyPrintResult = new PrintResult(“”);
function Printer(originalOptions) {
assert.ok(this instanceof Printer); var explicitTabWidth = originalOptions && originalOptions.tabWidth; var options = normalizeOptions(originalOptions); assert.notStrictEqual(options, originalOptions); // It's common for client code to pass the same options into both // recast.parse and recast.print, but the Printer doesn't need (and // can be confused by) options.sourceFileName, so we null it out. options.sourceFileName = null; function printWithComments(path) { assert.ok(path instanceof NodePath); return printComments(path.node.comments, print(path), options); } function print(path, includeComments) { if (includeComments) return printWithComments(path); assert.ok(path instanceof NodePath); if (!explicitTabWidth) { var oldTabWidth = options.tabWidth; var orig = path.node.original; var origLoc = orig && orig.loc; var origLines = origLoc && origLoc.lines; if (origLines) { options.tabWidth = origLines.guessTabWidth(); try { return maybeReprint(path); } finally { options.tabWidth = oldTabWidth; } } } return maybeReprint(path); } function maybeReprint(path) { var reprinter = getReprinter(path); if (reprinter) return maybeAddParens(path, reprinter(maybeReprint)); return printRootGenerically(path); } // Print the root node generically, but then resume reprinting its // children non-generically. function printRootGenerically(path) { return genericPrint(path, options, printWithComments); } // Print the entire AST generically. function printGenerically(path) { return genericPrint(path, options, printGenerically); } this.print = function(ast) { if (!ast) { return emptyPrintResult; } var path = ast instanceof NodePath ? ast : new NodePath(ast); var lines = print(path, true); return new PrintResult( lines.toString(options), util.composeSourceMaps( options.inputSourceMap, lines.getSourceMap( options.sourceMapName, options.sourceRoot ) ) ); }; this.printGenerically = function(ast) { if (!ast) { return emptyPrintResult; } var path = ast instanceof NodePath ? ast : new NodePath(ast); var oldReuseWhitespace = options.reuseWhitespace; // Do not reuse whitespace (or anything else, for that matter) // when printing generically. options.reuseWhitespace = false; try { return new PrintResult(printGenerically(path).toString(options)); } finally { options.reuseWhitespace = oldReuseWhitespace; } };
}
exports.Printer = Printer;
function maybeAddParens(path, lines) {
return path.needsParens() ? concat(["(", lines, ")"]) : lines;
}
function genericPrint(path, options, printPath) {
assert.ok(path instanceof NodePath); return maybeAddParens(path, genericPrintNoParens(path, options, printPath));
}
function genericPrintNoParens(path, options, print) {
var n = path.value; if (!n) { return fromString(""); } if (typeof n === "string") { return fromString(n, options); } namedTypes.Node.assert(n); switch (n.type) { case "File": path = path.get("program"); n = path.node; namedTypes.Program.assert(n); // intentionally fall through... case "Program": return maybeAddSemicolon( printStatementSequence(path.get("body"), options, print) ); case "EmptyStatement": return fromString(""); case "ExpressionStatement": return concat([print(path.get("expression")), ";"]); case "BinaryExpression": case "LogicalExpression": case "AssignmentExpression": return fromString(" ").join([ print(path.get("left")), n.operator, print(path.get("right")) ]); case "MemberExpression": var parts = [print(path.get("object"))]; if (n.computed) parts.push("[", print(path.get("property")), "]"); else parts.push(".", print(path.get("property"))); return concat(parts); case "Path": return fromString(".").join(n.body); case "Identifier": return fromString(n.name, options); case "SpreadElement": case "SpreadElementPattern": case "SpreadProperty": case "SpreadPropertyPattern": return concat(["...", print(path.get("argument"))]); case "FunctionDeclaration": case "FunctionExpression": var parts = []; if (n.async) parts.push("async "); parts.push("function"); if (n.generator) parts.push("*"); if (n.id) parts.push(" ", print(path.get("id"))); parts.push( "(", printFunctionParams(path, options, print), ") ", print(path.get("body"))); return concat(parts); case "ArrowFunctionExpression": var parts = []; if (n.async) parts.push("async "); if (n.params.length === 1) { parts.push(print(path.get("params", 0))); } else { parts.push( "(", printFunctionParams(path, options, print), ")" ); } parts.push(" => ", print(path.get("body"))); return concat(parts); case "MethodDefinition": var parts = []; if (n.static) { parts.push("static "); } parts.push(printMethod( n.kind, path.get("key"), path.get("value"), options, print )); return concat(parts); case "YieldExpression": var parts = ["yield"]; if (n.delegate) parts.push("*"); if (n.argument) parts.push(" ", print(path.get("argument"))); return concat(parts); case "AwaitExpression": var parts = ["await"]; if (n.all) parts.push("*"); if (n.argument) parts.push(" ", print(path.get("argument"))); return concat(parts); case "ModuleDeclaration": var parts = ["module", print(path.get("id"))]; if (n.source) { assert.ok(!n.body); parts.push("from", print(path.get("source"))); } else { parts.push(print(path.get("body"))); } return fromString(" ").join(parts); case "ImportSpecifier": case "ExportSpecifier": var parts = [print(path.get("id"))]; if (n.name) parts.push(" as ", print(path.get("name"))); return concat(parts); case "ExportBatchSpecifier": return fromString("*"); case "ImportNamespaceSpecifier": return concat(["* as ", print(path.get("id"))]); case "ImportDefaultSpecifier": return print(path.get("id")); case "ExportDeclaration": var parts = ["export"]; if (n["default"]) { parts.push(" default"); } else if (n.specifiers && n.specifiers.length > 0) { if (n.specifiers.length === 1 && n.specifiers[0].type === "ExportBatchSpecifier") { parts.push(" *"); } else { parts.push( " { ", fromString(", ").join(path.get("specifiers").map(print)), " }" ); } if (n.source) parts.push(" from ", print(path.get("source"))); parts.push(";"); return concat(parts); } if (n.declaration) { if (!namedTypes.Node.check(n.declaration)) { console.log(JSON.stringify(n, null, 2)); } var decLines = print(path.get("declaration")); parts.push(" ", decLines); if (lastNonSpaceCharacter(decLines) !== ";") { parts.push(";"); } } return concat(parts); case "ImportDeclaration": var parts = ["import "]; if (n.specifiers && n.specifiers.length > 0) { var foundImportSpecifier = false; path.get("specifiers").each(function(sp) { if (sp.name > 0) { parts.push(", "); } if (namedTypes.ImportDefaultSpecifier.check(sp.value) || namedTypes.ImportNamespaceSpecifier.check(sp.value)) { assert.strictEqual(foundImportSpecifier, false); } else { namedTypes.ImportSpecifier.assert(sp.value); if (!foundImportSpecifier) { foundImportSpecifier = true; parts.push("{"); } } parts.push(print(sp)); }); if (foundImportSpecifier) { parts.push("}"); } parts.push(" from "); } parts.push(print(path.get("source")), ";"); return concat(parts); case "BlockStatement": var naked = printStatementSequence(path.get("body"), options, print); if (naked.isEmpty()) return fromString("{}"); return concat([ "{\n", naked.indent(options.tabWidth), "\n}" ]); case "ReturnStatement": var parts = ["return"]; if (n.argument) { var argLines = print(path.get("argument")); if (argLines.length > 1 && namedTypes.XJSElement && namedTypes.XJSElement.check(n.argument)) { parts.push( " (\n", argLines.indent(options.tabWidth), "\n)" ); } else { parts.push(" ", argLines); } } parts.push(";"); return concat(parts); case "CallExpression": return concat([ print(path.get("callee")), printArgumentsList(path, options, print) ]); case "ObjectExpression": case "ObjectPattern": var allowBreak = false, len = n.properties.length, parts = [len > 0 ? "{\n" : "{"]; path.get("properties").map(function(childPath) { var prop = childPath.value; var i = childPath.name; var lines = print(childPath).indent(options.tabWidth); var multiLine = lines.length > 1; if (multiLine && allowBreak) { // Similar to the logic for BlockStatement. parts.push("\n"); } parts.push(lines); if (i < len - 1) { // Add an extra line break if the previous object property // had a multi-line value. parts.push(multiLine ? ",\n\n" : ",\n"); allowBreak = !multiLine; } }); parts.push(len > 0 ? "\n}" : "}"); return concat(parts); case "PropertyPattern": return concat([ print(path.get("key")), ": ", print(path.get("pattern")) ]); case "Property": // Non-standard AST node type. if (n.method || n.kind === "get" || n.kind === "set") { return printMethod( n.kind, path.get("key"), path.get("value"), options, print ); } if (path.node.shorthand) { return print(path.get("key")); } else { return concat([ print(path.get("key")), ": ", print(path.get("value")) ]); } case "ArrayExpression": case "ArrayPattern": var elems = n.elements, len = elems.length, parts = ["["]; path.get("elements").each(function(elemPath) { var elem = elemPath.value; if (!elem) { // If the array expression ends with a hole, that hole // will be ignored by the interpreter, but if it ends with // two (or more) holes, we need to write out two (or more) // commas so that the resulting code is interpreted with // both (all) of the holes. parts.push(","); } else { var i = elemPath.name; if (i > 0) parts.push(" "); parts.push(print(elemPath)); if (i < len - 1) parts.push(","); } }); parts.push("]"); return concat(parts); case "SequenceExpression": return fromString(", ").join(path.get("expressions").map(print)); case "ThisExpression": return fromString("this"); case "Literal": if (typeof n.value !== "string") return fromString(n.value, options); // intentionally fall through... case "ModuleSpecifier": // A ModuleSpecifier is a string-valued Literal. return fromString(nodeStr(n), options); case "UnaryExpression": var parts = [n.operator]; if (/[a-z]$/.test(n.operator)) parts.push(" "); parts.push(print(path.get("argument"))); return concat(parts); case "UpdateExpression": var parts = [ print(path.get("argument")), n.operator ]; if (n.prefix) parts.reverse(); return concat(parts); case "ConditionalExpression": return concat([ "(", print(path.get("test")), " ? ", print(path.get("consequent")), " : ", print(path.get("alternate")), ")" ]); case "NewExpression": var parts = ["new ", print(path.get("callee"))]; var args = n.arguments; if (args) { parts.push(printArgumentsList(path, options, print)); } return concat(parts); case "VariableDeclaration": var parts = [n.kind, " "]; var maxLen = 0; var printed = path.get("declarations").map(function(childPath) { var lines = print(childPath); maxLen = Math.max(lines.length, maxLen); return lines; }); if (maxLen === 1) { parts.push(fromString(", ").join(printed)); } else if (printed.length > 1 ) { parts.push( fromString(",\n").join(printed) .indentTail(n.kind.length + 1) ); } else { parts.push(printed[0]); } // We generally want to terminate all variable declarations with a // semicolon, except when they are children of for loops. var parentNode = path.parent && path.parent.node; if (!namedTypes.ForStatement.check(parentNode) && !namedTypes.ForInStatement.check(parentNode) && !(namedTypes.ForOfStatement && namedTypes.ForOfStatement.check(parentNode))) { parts.push(";"); } return concat(parts); case "VariableDeclarator": return n.init ? fromString(" = ").join([ print(path.get("id")), print(path.get("init")) ]) : print(path.get("id")); case "WithStatement": return concat([ "with (", print(path.get("object")), ") ", print(path.get("body")) ]); case "IfStatement": var con = adjustClause(print(path.get("consequent")), options), parts = ["if (", print(path.get("test")), ")", con]; if (n.alternate) parts.push( endsWithBrace(con) ? " else" : "\nelse", adjustClause(print(path.get("alternate")), options)); return concat(parts); case "ForStatement": // TODO Get the for (;;) case right. var init = print(path.get("init")), sep = init.length > 1 ? ";\n" : "; ", forParen = "for (", indented = fromString(sep).join([ init, print(path.get("test")), print(path.get("update")) ]).indentTail(forParen.length), head = concat([forParen, indented, ")"]), clause = adjustClause(print(path.get("body")), options), parts = [head]; if (head.length > 1) { parts.push("\n"); clause = clause.trimLeft(); } parts.push(clause); return concat(parts); case "WhileStatement": return concat([ "while (", print(path.get("test")), ")", adjustClause(print(path.get("body")), options) ]); case "ForInStatement": // Note: esprima can't actually parse "for each (". return concat([ n.each ? "for each (" : "for (", print(path.get("left")), " in ", print(path.get("right")), ")", adjustClause(print(path.get("body")), options) ]); case "ForOfStatement": return concat([ "for (", print(path.get("left")), " of ", print(path.get("right")), ")", adjustClause(print(path.get("body")), options) ]); case "DoWhileStatement": var doBody = concat([ "do", adjustClause(print(path.get("body")), options) ]), parts = [doBody]; if (endsWithBrace(doBody)) parts.push(" while"); else parts.push("\nwhile"); parts.push(" (", print(path.get("test")), ");"); return concat(parts); case "BreakStatement": var parts = ["break"]; if (n.label) parts.push(" ", print(path.get("label"))); parts.push(";"); return concat(parts); case "ContinueStatement": var parts = ["continue"]; if (n.label) parts.push(" ", print(path.get("label"))); parts.push(";"); return concat(parts); case "LabeledStatement": return concat([ print(path.get("label")), ":\n", print(path.get("body")) ]); case "TryStatement": var parts = [ "try ", print(path.get("block")) ]; path.get("handlers").each(function(handler) { parts.push(" ", print(handler)); }); if (n.finalizer) parts.push(" finally ", print(path.get("finalizer"))); return concat(parts); case "CatchClause": var parts = ["catch (", print(path.get("param"))]; if (n.guard) // Note: esprima does not recognize conditional catch clauses. parts.push(" if ", print(path.get("guard"))); parts.push(") ", print(path.get("body"))); return concat(parts); case "ThrowStatement": return concat([ "throw ", print(path.get("argument")), ";" ]); case "SwitchStatement": return concat([ "switch (", print(path.get("discriminant")), ") {\n", fromString("\n").join(path.get("cases").map(print)), "\n}" ]); // Note: ignoring n.lexical because it has no printing consequences. case "SwitchCase": var parts = []; if (n.test) parts.push("case ", print(path.get("test")), ":"); else parts.push("default:"); if (n.consequent.length > 0) { parts.push("\n", printStatementSequence( path.get("consequent"), options, print ).indent(options.tabWidth)); } return concat(parts); case "DebuggerStatement": return fromString("debugger;"); // XJS extensions below. case "XJSAttribute": var parts = [print(path.get("name"))]; if (n.value) parts.push("=", print(path.get("value"))); return concat(parts); case "XJSIdentifier": return fromString(n.name, options); case "XJSNamespacedName": return fromString(":").join([ print(path.get("namespace")), print(path.get("name")) ]); case "XJSMemberExpression": return fromString(".").join([ print(path.get("object")), print(path.get("property")) ]); case "XJSSpreadAttribute": return concat(["{...", print(path.get("argument")), "}"]); case "XJSExpressionContainer": return concat(["{", print(path.get("expression")), "}"]); case "XJSElement": var openingLines = print(path.get("openingElement")); if (n.openingElement.selfClosing) { assert.ok(!n.closingElement); return openingLines; } var childLines = concat( path.get("children").map(function(childPath) { var child = childPath.value; if (namedTypes.Literal.check(child) && typeof child.value === "string") { if (/\S/.test(child.value)) { return child.value.replace(/^\s+|\s+$/g, ""); } else if (/\n/.test(child.value)) { return "\n"; } } return print(childPath); }) ).indentTail(options.tabWidth); var closingLines = print(path.get("closingElement")); return concat([ openingLines, childLines, closingLines ]); case "XJSOpeningElement": var parts = ["<", print(path.get("name"))]; var attrParts = []; path.get("attributes").each(function(attrPath) { attrParts.push(" ", print(attrPath)); }); var attrLines = concat(attrParts); var needLineWrap = ( attrLines.length > 1 || attrLines.getLineLength(1) > options.wrapColumn ); if (needLineWrap) { attrParts.forEach(function(part, i) { if (part === " ") { assert.strictEqual(i % 2, 0); attrParts[i] = "\n"; } }); attrLines = concat(attrParts).indentTail(options.tabWidth); } parts.push(attrLines, n.selfClosing ? " />" : ">"); return concat(parts); case "XJSClosingElement": return concat(["</", print(path.get("name")), ">"]); case "XJSText": return fromString(n.value, options); case "XJSEmptyExpression": return fromString(""); case "TypeAnnotatedIdentifier": var parts = [ print(path.get("annotation")), " ", print(path.get("identifier")) ]; return concat(parts); case "ClassBody": if (n.body.length === 0) { return fromString("{}"); } return concat([ "{\n", printStatementSequence(path.get("body"), options, print) .indent(options.tabWidth), "\n}" ]); case "ClassPropertyDefinition": var parts = ["static ", print(path.get("definition"))]; if (!namedTypes.MethodDefinition.check(n.definition)) parts.push(";"); return concat(parts); case "ClassProperty": return concat([print(path.get("id")), ";"]); case "ClassDeclaration": case "ClassExpression": var parts = ["class"]; if (n.id) parts.push(" ", print(path.get("id"))); if (n.superClass) parts.push(" extends ", print(path.get("superClass"))); parts.push(" ", print(path.get("body"))); return concat(parts); // These types are unprintable because they serve as abstract // supertypes for other (printable) types. case "Node": case "SourceLocation": case "Position": case "Statement": case "Function": case "Pattern": case "Expression": case "Declaration": case "Specifier": case "NamedSpecifier": throw new Error("unprintable type: " + JSON.stringify(n.type)); // Unhandled types below. If encountered, nodes of these types should // be either left alone or desugared into AST types that are fully // supported by the pretty-printer. case "ClassHeritage": // TODO case "ComprehensionBlock": // TODO case "ComprehensionExpression": // TODO case "Glob": // TODO case "TaggedTemplateExpression": // TODO case "TemplateElement": // TODO case "TemplateLiteral": // TODO case "GeneratorExpression": // TODO case "LetStatement": // TODO case "LetExpression": // TODO case "GraphExpression": // TODO case "GraphIndexExpression": // TODO case "TypeAnnotation": // TODO // XML types that nobody cares about or needs to print. case "XMLDefaultDeclaration": case "XMLAnyName": case "XMLQualifiedIdentifier": case "XMLFunctionQualifiedIdentifier": case "XMLAttributeSelector": case "XMLFilterExpression": case "XML": case "XMLElement": case "XMLList": case "XMLEscape": case "XMLText": case "XMLStartTag": case "XMLEndTag": case "XMLPointTag": case "XMLName": case "XMLAttribute": case "XMLCdata": case "XMLComment": case "XMLProcessingInstruction": default: debugger; throw new Error("unknown type: " + JSON.stringify(n.type)); } return p;
}
function printStatementSequence(path, options, print) {
var inClassBody = path.parent && namedTypes.ClassBody && namedTypes.ClassBody.check(path.parent.node); var filtered = path.filter(function(stmtPath) { var stmt = stmtPath.value; // Just in case the AST has been modified to contain falsy // "statements," it's safer simply to skip them. if (!stmt) return false; // Skip printing EmptyStatement nodes to avoid leaving stray // semicolons lying around. if (stmt.type === "EmptyStatement") return false; if (!inClassBody) { namedTypes.Statement.assert(stmt); } return true; }); var prevTrailingSpace = null; var len = filtered.length; var parts = []; filtered.forEach(function(stmtPath, i) { var printed = print(stmtPath); var stmt = stmtPath.value; var needSemicolon = true; var multiLine = printed.length > 1; var notFirst = i > 0; var notLast = i < len - 1; var leadingSpace; var trailingSpace; if (inClassBody) { var stmt = stmtPath.value; if (namedTypes.MethodDefinition.check(stmt) || (namedTypes.ClassPropertyDefinition.check(stmt) && namedTypes.MethodDefinition.check(stmt.definition))) { needSemicolon = false; } } if (needSemicolon) { // Try to add a semicolon to anything that isn't a method in a // class body. printed = maybeAddSemicolon(printed); } var orig = options.reuseWhitespace && stmt.original; var trueLoc = orig && getTrueLoc(orig); var lines = trueLoc && trueLoc.lines; if (notFirst) { if (lines) { var beforeStart = lines.skipSpaces(trueLoc.start, true); var beforeStartLine = beforeStart ? beforeStart.line : 1; var leadingGap = trueLoc.start.line - beforeStartLine; leadingSpace = Array(leadingGap + 1).join("\n"); } else { leadingSpace = multiLine ? "\n\n" : "\n"; } } else { leadingSpace = ""; } if (notLast) { if (lines) { var afterEnd = lines.skipSpaces(trueLoc.end); var afterEndLine = afterEnd ? afterEnd.line : lines.length; var trailingGap = afterEndLine - trueLoc.end.line; trailingSpace = Array(trailingGap + 1).join("\n"); } else { trailingSpace = multiLine ? "\n\n" : "\n"; } } else { trailingSpace = ""; } parts.push( maxSpace(prevTrailingSpace, leadingSpace), printed ); if (notLast) { prevTrailingSpace = trailingSpace; } else if (trailingSpace) { parts.push(trailingSpace); } }); return concat(parts);
}
function getTrueLoc(node) {
if (!node.comments) { // If the node has no comments, regard node.loc as true. return node.loc; } var start = node.loc.start; var end = node.loc.end; // If the node has any comments, their locations might contribute to // the true start/end positions of the node. node.comments.forEach(function(comment) { if (comment.loc) { if (util.comparePos(comment.loc.start, start) < 0) { start = comment.loc.start; } if (util.comparePos(end, comment.loc.end) < 0) { end = comment.loc.end; } } }); return { lines: node.loc.lines, start: start, end: end };
}
function maxSpace(s1, s2) {
if (!s1 && !s2) { return fromString(""); } if (!s1) { return fromString(s2); } if (!s2) { return fromString(s1); } var spaceLines1 = fromString(s1); var spaceLines2 = fromString(s2); if (spaceLines2.length > spaceLines1.length) { return spaceLines2; } return spaceLines1;
}
function printMethod(kind, keyPath, valuePath, options, print) {
var parts = []; var key = keyPath.value; var value = valuePath.value; namedTypes.FunctionExpression.assert(value); if (value.async) { parts.push("async "); } if (!kind || kind === "init") { if (value.generator) { parts.push("*"); } } else { assert.ok(kind === "get" || kind === "set"); parts.push(kind, " "); } parts.push( print(keyPath), "(", printFunctionParams(valuePath, options, print), ") ", print(valuePath.get("body")) ); return concat(parts);
}
function printArgumentsList(path, options, print) {
var printed = path.get("arguments").map(print); var joined = fromString(", ").join(printed); if (joined.getLineLength(1) > options.wrapColumn) { joined = fromString(",\n").join(printed); return concat(["(\n", joined.indent(options.tabWidth), "\n)"]); } return concat(["(", joined, ")"]);
}
function printFunctionParams(path, options, print) {
var fun = path.node; namedTypes.Function.assert(fun); var params = path.get("params"); var defaults = path.get("defaults"); var printed = params.map(defaults.value ? function(param) { var p = print(param); var d = defaults.get(param.name); return d.value ? concat([p, "=", print(d)]) : p; } : print); if (fun.rest) { printed.push(concat(["...", print(path.get("rest"))])); } var joined = fromString(", ").join(printed); if (joined.length > 1 || joined.getLineLength(1) > options.wrapColumn) { joined = fromString(",\n").join(printed); return concat(["\n", joined.indent(options.tabWidth)]); } return joined;
}
function adjustClause(clause, options) {
if (clause.length > 1) return concat([" ", clause]); return concat([ "\n", maybeAddSemicolon(clause).indent(options.tabWidth) ]);
}
function lastNonSpaceCharacter(lines) {
var pos = lines.lastPos(); do { var ch = lines.charAt(pos); if (/\S/.test(ch)) return ch; } while (lines.prevPos(pos));
}
function endsWithBrace(lines) {
return lastNonSpaceCharacter(lines) === "}";
}
function nodeStr(n) {
namedTypes.Literal.assert(n); isString.assert(n.value); return JSON.stringify(n.value);
}
function maybeAddSemicolon(lines) {
var eoc = lastNonSpaceCharacter(lines); if (!eoc || "\n};".indexOf(eoc) < 0) return concat([lines, ";"]); return lines;
}