Use babylon directly and convert recast's comment algorith to use our own API

master
James Long 2017-01-09 17:37:45 -05:00
parent 7ad69b05ca
commit 5c53a2d59c
3 changed files with 105 additions and 176 deletions

View File

@ -1,7 +1,7 @@
const recast = require("recast");
const babylon = require("babylon"); const babylon = require("babylon");
const Printer = require("./src/printer").Printer; const Printer = require("./src/printer").Printer;
const flowParser = require("flow-parser"); const flowParser = require("flow-parser");
const comments = require("./src/comments");
var babylonOptions = { var babylonOptions = {
sourceType: 'module', sourceType: 'module',
@ -40,17 +40,16 @@ module.exports = {
} }
} }
else { else {
ast = recast.parse(text, { ast = babylon.parse(text, babylonOptions);
parser: {
parse: function(source) {
return babylon.parse(source, babylonOptions);
}
}
});
} }
opts.originalText = text; // Interleave comment nodes
if(ast.comments) {
comments.attach(ast.comments, ast, text);
ast.comments = [];
}
ast.tokens = []; ast.tokens = [];
opts.originalText = text;
const printer = new Printer(opts); const printer = new Printer(opts);
return printer.printGenerically(ast).code; return printer.printGenerically(ast).code;

View File

@ -13,26 +13,22 @@ var childNodesCacheKey = require("private").makeUniqueKey();
// TODO Move a non-caching implementation of this function into ast-types, // TODO Move a non-caching implementation of this function into ast-types,
// and implement a caching wrapper function here. // and implement a caching wrapper function here.
function getSortedChildNodes(node, lines, resultArray) { function getSortedChildNodes(node, text, resultArray) {
if (!node) { if (!node) {
return; return;
} }
// The .loc checks below are sensitive to some of the problems that // The loc checks below are sensitive to some of the problems that
// are fixed by this utility function. Specifically, if it decides to // are fixed by this utility function.
// set node.loc to null, indicating that the node's .loc information util.fixFaultyLocations(node, text);
// is unreliable, then we don't want to add node to the resultArray.
util.fixFaultyLocations(node, lines);
if (resultArray) { if (resultArray) {
if (n.Node.check(node) && if (n.Node.check(node)) {
n.SourceLocation.check(node.loc)) {
// This reverse insertion sort almost always takes constant // This reverse insertion sort almost always takes constant
// time because we almost always (maybe always?) append the // time because we almost always (maybe always?) append the
// nodes in order anyway. // nodes in order anyway.
for (var i = resultArray.length - 1; i >= 0; --i) { for (var i = resultArray.length - 1; i >= 0; --i) {
if (comparePos(resultArray[i].loc.end, if (resultArray[i].end - node.start <= 0) {
node.loc.start) <= 0) {
break; break;
} }
} }
@ -60,7 +56,7 @@ function getSortedChildNodes(node, lines, resultArray) {
} }
for (var i = 0, nameCount = names.length; i < nameCount; ++i) { for (var i = 0, nameCount = names.length; i < nameCount; ++i) {
getSortedChildNodes(node[names[i]], lines, resultArray); getSortedChildNodes(node[names[i]], text, resultArray);
} }
return resultArray; return resultArray;
@ -69,8 +65,8 @@ function getSortedChildNodes(node, lines, resultArray) {
// As efficiently as possible, decorate the comment object with // As efficiently as possible, decorate the comment object with
// .precedingNode, .enclosingNode, and/or .followingNode properties, at // .precedingNode, .enclosingNode, and/or .followingNode properties, at
// least one of which is guaranteed to be defined. // least one of which is guaranteed to be defined.
function decorateComment(node, comment, lines) { function decorateComment(node, comment, text) {
var childNodes = getSortedChildNodes(node, lines); var childNodes = getSortedChildNodes(node, text);
// Time to dust off the old binary search robes and wizard hat. // Time to dust off the old binary search robes and wizard hat.
var left = 0, right = childNodes.length; var left = 0, right = childNodes.length;
@ -78,14 +74,13 @@ function decorateComment(node, comment, lines) {
var middle = (left + right) >> 1; var middle = (left + right) >> 1;
var child = childNodes[middle]; var child = childNodes[middle];
if (comparePos(child.loc.start, comment.loc.start) <= 0 && if (child.start - comment.start <= 0 && comment.end - child.end <= 0) {
comparePos(comment.loc.end, child.loc.end) <= 0) {
// The comment is completely contained by this child node. // The comment is completely contained by this child node.
decorateComment(comment.enclosingNode = child, comment, lines); decorateComment(comment.enclosingNode = child, comment, text);
return; // Abandon the binary search at this level. return; // Abandon the binary search at this level.
} }
if (comparePos(child.loc.end, comment.loc.start) <= 0) { if (child.end - comment.start <= 0) {
// This child node falls completely before the comment. // This child node falls completely before the comment.
// Because we will never consider this node or any nodes // Because we will never consider this node or any nodes
// before it again, this node must be the closest preceding // before it again, this node must be the closest preceding
@ -95,7 +90,7 @@ function decorateComment(node, comment, lines) {
continue; continue;
} }
if (comparePos(comment.loc.end, child.loc.start) <= 0) { if (comment.end - child.start <= 0) {
// This child node falls completely after the comment. // This child node falls completely after the comment.
// Because we will never consider this node or any nodes after // Because we will never consider this node or any nodes after
// it again, this node must be the closest following node we // it again, this node must be the closest following node we
@ -117,7 +112,7 @@ function decorateComment(node, comment, lines) {
} }
} }
exports.attach = function(comments, ast, lines) { exports.attach = function(comments, ast, text) {
if (!isArray.check(comments)) { if (!isArray.check(comments)) {
return; return;
} }
@ -125,8 +120,7 @@ exports.attach = function(comments, ast, lines) {
var tiesToBreak = []; var tiesToBreak = [];
comments.forEach(function(comment) { comments.forEach(function(comment) {
comment.loc.lines = lines; decorateComment(ast, comment, text);
decorateComment(ast, comment, lines);
var pn = comment.precedingNode; var pn = comment.precedingNode;
var en = comment.enclosingNode; var en = comment.enclosingNode;
@ -143,7 +137,7 @@ exports.attach = function(comments, ast, lines) {
); );
if (lastTie.followingNode !== comment.followingNode) { if (lastTie.followingNode !== comment.followingNode) {
breakTies(tiesToBreak, lines); breakTies(tiesToBreak, text);
} }
} }
@ -151,18 +145,18 @@ exports.attach = function(comments, ast, lines) {
} else if (pn) { } else if (pn) {
// No contest: we have a trailing comment. // No contest: we have a trailing comment.
breakTies(tiesToBreak, lines); breakTies(tiesToBreak, text);
addTrailingComment(pn, comment); addTrailingComment(pn, comment);
} else if (fn) { } else if (fn) {
// No contest: we have a leading comment. // No contest: we have a leading comment.
breakTies(tiesToBreak, lines); breakTies(tiesToBreak, text);
addLeadingComment(fn, comment); addLeadingComment(fn, comment);
} else if (en) { } else if (en) {
// The enclosing node has no child nodes at all, so what we // The enclosing node has no child nodes at all, so what we
// have here is a dangling comment, e.g. [/* crickets */]. // have here is a dangling comment, e.g. [/* crickets */].
breakTies(tiesToBreak, lines); breakTies(tiesToBreak, text);
addDanglingComment(en, comment); addDanglingComment(en, comment);
} else { } else {
@ -170,7 +164,7 @@ exports.attach = function(comments, ast, lines) {
} }
}); });
breakTies(tiesToBreak, lines); breakTies(tiesToBreak, text);
comments.forEach(function(comment) { comments.forEach(function(comment) {
// These node references were useful for breaking ties, but we // These node references were useful for breaking ties, but we
@ -182,7 +176,7 @@ exports.attach = function(comments, ast, lines) {
}); });
}; };
function breakTies(tiesToBreak, lines) { function breakTies(tiesToBreak, text) {
var tieCount = tiesToBreak.length; var tieCount = tiesToBreak.length;
if (tieCount === 0) { if (tieCount === 0) {
return; return;
@ -190,7 +184,7 @@ function breakTies(tiesToBreak, lines) {
var pn = tiesToBreak[0].precedingNode; var pn = tiesToBreak[0].precedingNode;
var fn = tiesToBreak[0].followingNode; var fn = tiesToBreak[0].followingNode;
var gapEndPos = fn.loc.start; var gapEndPos = fn.start;
// Iterate backwards through tiesToBreak, examining the gaps // Iterate backwards through tiesToBreak, examining the gaps
// between the tied comments. In order to qualify as leading, a // between the tied comments. In order to qualify as leading, a
@ -203,23 +197,23 @@ function breakTies(tiesToBreak, lines) {
assert.strictEqual(comment.precedingNode, pn); assert.strictEqual(comment.precedingNode, pn);
assert.strictEqual(comment.followingNode, fn); assert.strictEqual(comment.followingNode, fn);
var gap = lines.sliceString(comment.loc.end, gapEndPos); var gap = text.slice(comment.end, gapEndPos);
if (/\S/.test(gap)) { if (/\S/.test(gap)) {
// The gap string contained something other than whitespace. // The gap string contained something other than whitespace.
break; break;
} }
gapEndPos = comment.loc.start; gapEndPos = comment.start;
} }
while (indexOfFirstLeadingComment <= tieCount && // while (indexOfFirstLeadingComment <= tieCount &&
(comment = tiesToBreak[indexOfFirstLeadingComment]) && // (comment = tiesToBreak[indexOfFirstLeadingComment]) &&
// If the comment is a //-style comment and indented more // // If the comment is a //-style comment and indented more
// deeply than the node itself, reconsider it as trailing. // // deeply than the node itself, reconsider it as trailing.
(comment.type === "Line" || comment.type === "CommentLine") && // (comment.type === "Line" || comment.type === "CommentLine") &&
comment.loc.start.column > fn.loc.start.column) { // comment.loc.start.column > fn.loc.start.column) {
++indexOfFirstLeadingComment; // ++indexOfFirstLeadingComment;
} // }
tiesToBreak.forEach(function(comment, i) { tiesToBreak.forEach(function(comment, i) {
if (i < indexOfFirstLeadingComment) { if (i < indexOfFirstLeadingComment) {

View File

@ -84,136 +84,55 @@ util.composeSourceMaps = function(formerMap, latterMap) {
return smg.toJSON(); return smg.toJSON();
}; };
util.getTrueLoc = function(node, lines) { function expandLoc(parentNode, childNode) {
// It's possible that node is newly-created (not parsed by Esprima), if (childNode.start - parentNode.start < 0) {
// in which case it probably won't have a .loc property (or an parentNode.start = childNode.start;
// .original property for that matter). That's fine; we'll just
// pretty-print it as usual.
if (!node.loc) {
return null;
} }
var result = { if (parentNode.end - childNode.end < 0) {
start: node.loc.start, parentNode.end = childNode.end;
end: node.loc.end
};
function include(node) {
expandLoc(result, node.loc);
}
// If the node has any comments, their locations might contribute to
// the true start/end positions of the node.
if (node.comments) {
node.comments.forEach(include);
}
// If the node is an export declaration and its .declaration has any
// decorators, their locations might contribute to the true start/end
// positions of the export declaration node.
if (node.declaration && util.isExportDeclaration(node) &&
node.declaration.decorators) {
node.declaration.decorators.forEach(include);
}
if (comparePos(result.start, result.end) < 0) {
// Trim leading whitespace.
result.start = copyPos(result.start);
lines.skipSpaces(result.start, false, true);
if (comparePos(result.start, result.end) < 0) {
// Trim trailing whitespace, if the end location is not already the
// same as the start location.
result.end = copyPos(result.end);
lines.skipSpaces(result.end, true, true);
}
}
return result;
};
function expandLoc(parentLoc, childLoc) {
if (parentLoc && childLoc) {
if (comparePos(childLoc.start, parentLoc.start) < 0) {
parentLoc.start = childLoc.start;
}
if (comparePos(parentLoc.end, childLoc.end) < 0) {
parentLoc.end = childLoc.end;
}
} }
} }
util.fixFaultyLocations = function(node, lines) { util.fixFaultyLocations = function(node, text) {
var loc = node.loc;
if (loc) {
if (loc.start.line < 1) {
loc.start.line = 1;
}
if (loc.end.line < 1) {
loc.end.line = 1;
}
}
if (node.type === "TemplateLiteral") { if (node.type === "TemplateLiteral") {
fixTemplateLiteral(node, lines); fixTemplateLiteral(node, text);
} else if (loc && node.decorators) { } else if (node.decorators) {
// Expand the .loc of the node responsible for printing the decorators // Expand the loc of the node responsible for printing the decorators
// (here, the decorated node) so that it includes node.decorators. // (here, the decorated node) so that it includes node.decorators.
node.decorators.forEach(function (decorator) { node.decorators.forEach(function (decorator) {
expandLoc(loc, decorator.loc); expandLoc(node, decorator);
}); });
} else if (node.declaration && util.isExportDeclaration(node)) { } else if (node.declaration && util.isExportDeclaration(node)) {
// Nullify .loc information for the child declaration so that we never // Expand the loc of the node responsible for printing the decorators
// try to reprint it without also reprinting the export declaration.
node.declaration.loc = null;
// Expand the .loc of the node responsible for printing the decorators
// (here, the export declaration) so that it includes node.decorators. // (here, the export declaration) so that it includes node.decorators.
var decorators = node.declaration.decorators; var decorators = node.declaration.decorators;
if (decorators) { if (decorators) {
decorators.forEach(function (decorator) { decorators.forEach(function (decorator) {
expandLoc(loc, decorator.loc); expandLoc(node, decorator);
}); });
} }
} else if ((n.MethodDefinition && n.MethodDefinition.check(node)) || } else if ((n.MethodDefinition && n.MethodDefinition.check(node)) ||
(n.Property.check(node) && (node.method || node.shorthand))) { (n.Property.check(node) && (node.method || node.shorthand))) {
// If the node is a MethodDefinition or a .method or .shorthand
// Property, then the location information stored in
// node.value.loc is very likely untrustworthy (just the {body}
// part of a method, or nothing in the case of shorthand
// properties), so we null out that information to prevent
// accidental reuse of bogus source code during reprinting.
node.value.loc = null;
if (n.FunctionExpression.check(node.value)) { if (n.FunctionExpression.check(node.value)) {
// FunctionExpression method values should be anonymous, // FunctionExpression method values should be anonymous,
// because their .id fields are ignored anyway. // because their .id fields are ignored anyway.
node.value.id = null; node.value.id = null;
} }
} else if (node.type === "ObjectTypeProperty") { } else if (node.type === "ObjectTypeProperty") {
var loc = node.loc; var end = skipSpaces(text, node.end, true);
var end = loc && loc.end; if (end !== false && text.charAt(end) === ",") {
if (end) { // Some parsers accidentally include trailing commas in the
end = copyPos(end); // .end information for ObjectTypeProperty nodes.
if (lines.prevPos(end) && if ((end = skipSpaces(text, end - 1, true)) !== false) {
lines.charAt(end) === ",") { loc.end = end;
// Some parsers accidentally include trailing commas in the
// .loc.end information for ObjectTypeProperty nodes.
if ((end = lines.skipSpaces(end, true, true))) {
loc.end = end;
}
} }
} }
} }
}; };
function fixTemplateLiteral(node, lines) { function fixTemplateLiteral(node, text) {
assert.strictEqual(node.type, "TemplateLiteral"); assert.strictEqual(node.type, "TemplateLiteral");
if (node.quasis.length === 0) { if (node.quasis.length === 0) {
@ -221,53 +140,53 @@ function fixTemplateLiteral(node, lines) {
return; return;
} }
// First we need to exclude the opening ` from the .loc of the first // First we need to exclude the opening ` from the loc of the first
// quasi element, in case the parser accidentally decided to include it. // quasi element, in case the parser accidentally decided to include it.
var afterLeftBackTickPos = copyPos(node.loc.start); var afterLeftBackTickPos = node.start;
assert.strictEqual(lines.charAt(afterLeftBackTickPos), "`"); assert.strictEqual(text.charAt(afterLeftBackTickPos), "`");
assert.ok(lines.nextPos(afterLeftBackTickPos)); assert.ok(afterLeftBackTickPos < text.length);
var firstQuasi = node.quasis[0]; var firstQuasi = node.quasis[0];
if (comparePos(firstQuasi.loc.start, afterLeftBackTickPos) < 0) { if (firstQuasi.start - afterLeftBackTickPos < 0) {
firstQuasi.loc.start = afterLeftBackTickPos; firstQuasi.start = afterLeftBackTickPos;
} }
// Next we need to exclude the closing ` from the .loc of the last quasi // Next we need to exclude the closing ` from the loc of the last quasi
// element, in case the parser accidentally decided to include it. // element, in case the parser accidentally decided to include it.
var rightBackTickPos = copyPos(node.loc.end); var rightBackTickPos = node.end;
assert.ok(lines.prevPos(rightBackTickPos)); assert.ok(rightBackTickPos >= 0);
assert.strictEqual(lines.charAt(rightBackTickPos), "`"); assert.strictEqual(text.charAt(rightBackTickPos), "`");
var lastQuasi = node.quasis[node.quasis.length - 1]; var lastQuasi = node.quasis[node.quasis.length - 1];
if (comparePos(rightBackTickPos, lastQuasi.loc.end) < 0) { if (rightBackTickPos - lastQuasi.end < 0) {
lastQuasi.loc.end = rightBackTickPos; lastQuasi.end = rightBackTickPos;
} }
// Now we need to exclude ${ and } characters from the .loc's of all // Now we need to exclude ${ and } characters from the loc's of all
// quasi elements, since some parsers accidentally include them. // quasi elements, since some parsers accidentally include them.
node.expressions.forEach(function (expr, i) { node.expressions.forEach(function (expr, i) {
// Rewind from expr.loc.start over any whitespace and the ${ that // Rewind from expr.start over any whitespace and the ${ that
// precedes the expression. The position of the $ should be the same // precedes the expression. The position of the $ should be the same
// as the .loc.end of the preceding quasi element, but some parsers // as the .end of the preceding quasi element, but some parsers
// accidentally include the ${ in the .loc of the quasi element. // accidentally include the ${ in the loc of the quasi element.
var dollarCurlyPos = lines.skipSpaces(expr.loc.start, true, false); var dollarCurlyPos = skipSpaces(text, expr.start - 1, true);
if (lines.prevPos(dollarCurlyPos) && if (dollarCurlyPos - 1 >= 0 &&
lines.charAt(dollarCurlyPos) === "{" && text.charAt(dollarCurlyPos - 1) === "{" &&
lines.prevPos(dollarCurlyPos) && dollarCurlyPos - 2 >= 0 &&
lines.charAt(dollarCurlyPos) === "$") { text.charAt(dollarCurlyPos - 2) === "$") {
var quasiBefore = node.quasis[i]; var quasiBefore = node.quasis[i];
if (comparePos(dollarCurlyPos, quasiBefore.loc.end) < 0) { if (dollarCurlyPos - quasiBefore.end < 0) {
quasiBefore.loc.end = dollarCurlyPos; quasiBefore.end = dollarCurlyPos;
} }
} }
// Likewise, some parsers accidentally include the } that follows // Likewise, some parsers accidentally include the } that follows
// the expression in the .loc of the following quasi element. // the expression in the loc of the following quasi element.
var rightCurlyPos = lines.skipSpaces(expr.loc.end, false, false); var rightCurlyPos = skipSpaces(text, expr.end);
if (lines.charAt(rightCurlyPos) === "}") { if (text.charAt(rightCurlyPos) === "}") {
assert.ok(lines.nextPos(rightCurlyPos)); assert.ok(rightCurlyPos + 1 < text.length);
// Now rightCurlyPos is technically the position just after the }. // Now rightCurlyPos is technically the position just after the }.
var quasiAfter = node.quasis[i + 1]; var quasiAfter = node.quasis[i + 1];
if (comparePos(quasiAfter.loc.start, rightCurlyPos) < 0) { if (quasiAfter.start - rightCurlyPos < 0) {
quasiAfter.loc.start = rightCurlyPos; quasiAfter.start = rightCurlyPos;
} }
} }
}); });
@ -337,3 +256,20 @@ util.newlineExistsBefore = function(text, index) {
util.newlineExistsAfter = function(text, index) { util.newlineExistsAfter = function(text, index) {
return _findNewline(text, index); return _findNewline(text, index);
} }
function skipSpaces(text, index, backwards) {
const length = text.length;
let cursor = backwards ? (index - 1) : (index + 1);
// Look forward and see if there is a newline after/before this code
// by scanning up/back to the next non-indentation character.
while (cursor > 0 && cursor < length) {
const c = text.charAt(cursor);
// Skip any whitespace chars
if (!c.match(/\S/)) {
return cursor;
}
backwards ? cursor-- : cursor++;
}
return false;
}
util.skipSpaces = skipSpaces;