2016-12-23 21:38:10 +03:00
|
|
|
var assert = require("assert");
|
|
|
|
var types = require("ast-types");
|
|
|
|
var n = types.namedTypes;
|
|
|
|
var isArray = types.builtInTypes.array;
|
|
|
|
var isObject = types.builtInTypes.object;
|
|
|
|
var pp = require("./pp");
|
|
|
|
var fromString = pp.fromString;
|
|
|
|
var concat = pp.concat;
|
2017-01-05 06:26:46 +03:00
|
|
|
var hardline = pp.hardline;
|
2016-12-23 21:38:10 +03:00
|
|
|
var util = require("./util");
|
|
|
|
var comparePos = util.comparePos;
|
|
|
|
var childNodesCacheKey = require("private").makeUniqueKey();
|
2017-01-10 05:24:42 +03:00
|
|
|
var locStart = util.locStart;
|
|
|
|
var locEnd = util.locEnd;
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
// TODO Move a non-caching implementation of this function into ast-types,
|
|
|
|
// and implement a caching wrapper function here.
|
2017-01-10 01:37:45 +03:00
|
|
|
function getSortedChildNodes(node, text, resultArray) {
|
2016-12-23 21:38:10 +03:00
|
|
|
if (!node) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-01-10 01:37:45 +03:00
|
|
|
// The loc checks below are sensitive to some of the problems that
|
|
|
|
// are fixed by this utility function.
|
|
|
|
util.fixFaultyLocations(node, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
if (resultArray) {
|
2017-01-10 01:37:45 +03:00
|
|
|
if (n.Node.check(node)) {
|
2016-12-23 21:38:10 +03:00
|
|
|
// This reverse insertion sort almost always takes constant
|
|
|
|
// time because we almost always (maybe always?) append the
|
|
|
|
// nodes in order anyway.
|
|
|
|
for (var i = resultArray.length - 1; i >= 0; --i) {
|
2017-01-10 05:24:42 +03:00
|
|
|
if (locEnd(resultArray[i]) - locStart(node) <= 0) {
|
2016-12-23 21:38:10 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resultArray.splice(i + 1, 0, node);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (node[childNodesCacheKey]) {
|
|
|
|
return node[childNodesCacheKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
var names;
|
|
|
|
if (isArray.check(node)) {
|
|
|
|
names = Object.keys(node);
|
|
|
|
} else if (isObject.check(node)) {
|
|
|
|
names = types.getFieldNames(node);
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!resultArray) {
|
|
|
|
Object.defineProperty(node, childNodesCacheKey, {
|
|
|
|
value: resultArray = [],
|
|
|
|
enumerable: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0, nameCount = names.length; i < nameCount; ++i) {
|
2017-01-10 01:37:45 +03:00
|
|
|
getSortedChildNodes(node[names[i]], text, resultArray);
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return resultArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As efficiently as possible, decorate the comment object with
|
|
|
|
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
|
|
|
|
// least one of which is guaranteed to be defined.
|
2017-01-10 01:37:45 +03:00
|
|
|
function decorateComment(node, comment, text) {
|
|
|
|
var childNodes = getSortedChildNodes(node, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
// Time to dust off the old binary search robes and wizard hat.
|
|
|
|
var left = 0, right = childNodes.length;
|
|
|
|
while (left < right) {
|
|
|
|
var middle = (left + right) >> 1;
|
|
|
|
var child = childNodes[middle];
|
|
|
|
|
2017-01-10 05:24:42 +03:00
|
|
|
if (locStart(child) - locStart(comment) <= 0 &&
|
|
|
|
locEnd(comment) - locEnd(child) <= 0) {
|
2016-12-23 21:38:10 +03:00
|
|
|
// The comment is completely contained by this child node.
|
2017-01-10 05:49:06 +03:00
|
|
|
comment.enclosingNode = child;
|
|
|
|
decorateComment(child, comment, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
return; // Abandon the binary search at this level.
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:24:42 +03:00
|
|
|
if (locEnd(child) - locStart(comment) <= 0) {
|
2016-12-23 21:38:10 +03:00
|
|
|
// This child node falls completely before the comment.
|
|
|
|
// Because we will never consider this node or any nodes
|
|
|
|
// before it again, this node must be the closest preceding
|
|
|
|
// node we have encountered so far.
|
|
|
|
var precedingNode = child;
|
|
|
|
left = middle + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:24:42 +03:00
|
|
|
if (locEnd(comment) - locStart(child) <= 0) {
|
2016-12-23 21:38:10 +03:00
|
|
|
// This child node falls completely after the comment.
|
|
|
|
// Because we will never consider this node or any nodes after
|
|
|
|
// it again, this node must be the closest following node we
|
|
|
|
// have encountered so far.
|
|
|
|
var followingNode = child;
|
|
|
|
right = middle;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error("Comment location overlaps with node location");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (precedingNode) {
|
|
|
|
comment.precedingNode = precedingNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (followingNode) {
|
|
|
|
comment.followingNode = followingNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-10 01:37:45 +03:00
|
|
|
exports.attach = function(comments, ast, text) {
|
2016-12-23 21:38:10 +03:00
|
|
|
if (!isArray.check(comments)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var tiesToBreak = [];
|
|
|
|
|
|
|
|
comments.forEach(function(comment) {
|
2017-01-10 01:37:45 +03:00
|
|
|
decorateComment(ast, comment, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
var pn = comment.precedingNode;
|
|
|
|
var en = comment.enclosingNode;
|
|
|
|
var fn = comment.followingNode;
|
|
|
|
|
|
|
|
if (pn && fn) {
|
|
|
|
var tieCount = tiesToBreak.length;
|
|
|
|
if (tieCount > 0) {
|
|
|
|
var lastTie = tiesToBreak[tieCount - 1];
|
|
|
|
|
|
|
|
assert.strictEqual(
|
|
|
|
lastTie.precedingNode === comment.precedingNode,
|
|
|
|
lastTie.followingNode === comment.followingNode
|
|
|
|
);
|
|
|
|
|
|
|
|
if (lastTie.followingNode !== comment.followingNode) {
|
2017-01-10 01:37:45 +03:00
|
|
|
breakTies(tiesToBreak, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tiesToBreak.push(comment);
|
|
|
|
|
|
|
|
} else if (pn) {
|
|
|
|
// No contest: we have a trailing comment.
|
2017-01-10 01:37:45 +03:00
|
|
|
breakTies(tiesToBreak, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
addTrailingComment(pn, comment);
|
|
|
|
|
|
|
|
} else if (fn) {
|
|
|
|
// No contest: we have a leading comment.
|
2017-01-10 01:37:45 +03:00
|
|
|
breakTies(tiesToBreak, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
addLeadingComment(fn, comment);
|
|
|
|
|
|
|
|
} else if (en) {
|
|
|
|
// The enclosing node has no child nodes at all, so what we
|
|
|
|
// have here is a dangling comment, e.g. [/* crickets */].
|
2017-01-10 01:37:45 +03:00
|
|
|
breakTies(tiesToBreak, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
addDanglingComment(en, comment);
|
|
|
|
|
|
|
|
} else {
|
2017-01-10 05:49:06 +03:00
|
|
|
// throw new Error("AST contains no nodes at all?");
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-01-10 01:37:45 +03:00
|
|
|
breakTies(tiesToBreak, text);
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
comments.forEach(function(comment) {
|
|
|
|
// These node references were useful for breaking ties, but we
|
|
|
|
// don't need them anymore, and they create cycles in the AST that
|
|
|
|
// may lead to infinite recursion if we don't delete them here.
|
|
|
|
delete comment.precedingNode;
|
|
|
|
delete comment.enclosingNode;
|
|
|
|
delete comment.followingNode;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-01-10 01:37:45 +03:00
|
|
|
function breakTies(tiesToBreak, text) {
|
2016-12-23 21:38:10 +03:00
|
|
|
var tieCount = tiesToBreak.length;
|
|
|
|
if (tieCount === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var pn = tiesToBreak[0].precedingNode;
|
|
|
|
var fn = tiesToBreak[0].followingNode;
|
2017-01-10 05:24:42 +03:00
|
|
|
var gapEndPos = locStart(fn);
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
// Iterate backwards through tiesToBreak, examining the gaps
|
|
|
|
// between the tied comments. In order to qualify as leading, a
|
|
|
|
// comment must be separated from fn by an unbroken series of
|
|
|
|
// whitespace-only gaps (or other comments).
|
|
|
|
for (var indexOfFirstLeadingComment = tieCount;
|
|
|
|
indexOfFirstLeadingComment > 0;
|
|
|
|
--indexOfFirstLeadingComment) {
|
|
|
|
var comment = tiesToBreak[indexOfFirstLeadingComment - 1];
|
|
|
|
assert.strictEqual(comment.precedingNode, pn);
|
|
|
|
assert.strictEqual(comment.followingNode, fn);
|
|
|
|
|
2017-01-10 05:24:42 +03:00
|
|
|
var gap = text.slice(locEnd(comment), gapEndPos);
|
2016-12-23 21:38:10 +03:00
|
|
|
if (/\S/.test(gap)) {
|
|
|
|
// The gap string contained something other than whitespace.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:24:42 +03:00
|
|
|
gapEndPos = locStart(comment);
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
|
2017-01-10 01:37:45 +03:00
|
|
|
// while (indexOfFirstLeadingComment <= tieCount &&
|
|
|
|
// (comment = tiesToBreak[indexOfFirstLeadingComment]) &&
|
|
|
|
// // If the comment is a //-style comment and indented more
|
|
|
|
// // deeply than the node itself, reconsider it as trailing.
|
|
|
|
// (comment.type === "Line" || comment.type === "CommentLine") &&
|
|
|
|
// comment.loc.start.column > fn.loc.start.column) {
|
|
|
|
// ++indexOfFirstLeadingComment;
|
|
|
|
// }
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
tiesToBreak.forEach(function(comment, i) {
|
|
|
|
if (i < indexOfFirstLeadingComment) {
|
|
|
|
addTrailingComment(pn, comment);
|
|
|
|
} else {
|
|
|
|
addLeadingComment(fn, comment);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
tiesToBreak.length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function addCommentHelper(node, comment) {
|
|
|
|
var comments = node.comments || (node.comments = []);
|
|
|
|
comments.push(comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addLeadingComment(node, comment) {
|
|
|
|
comment.leading = true;
|
|
|
|
comment.trailing = false;
|
|
|
|
addCommentHelper(node, comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addDanglingComment(node, comment) {
|
|
|
|
comment.leading = false;
|
|
|
|
comment.trailing = false;
|
|
|
|
addCommentHelper(node, comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addTrailingComment(node, comment) {
|
|
|
|
comment.leading = false;
|
|
|
|
comment.trailing = true;
|
|
|
|
addCommentHelper(node, comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
function printLeadingComment(commentPath, print) {
|
|
|
|
var comment = commentPath.getValue();
|
|
|
|
n.Comment.assert(comment);
|
2017-01-09 21:47:02 +03:00
|
|
|
return concat([print(commentPath), hardline]);
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
|
2017-01-09 21:47:02 +03:00
|
|
|
function printTrailingComment(commentPath, print, options) {
|
|
|
|
const comment = commentPath.getValue(commentPath);
|
2016-12-23 21:38:10 +03:00
|
|
|
n.Comment.assert(comment);
|
2017-01-09 21:47:02 +03:00
|
|
|
const text = options.originalText;
|
2016-12-23 21:38:10 +03:00
|
|
|
|
2017-01-09 21:47:02 +03:00
|
|
|
return concat([
|
2017-01-10 05:24:42 +03:00
|
|
|
util.newlineExistsBefore(text, locStart(comment)) ? hardline : " ",
|
2017-01-09 21:47:02 +03:00
|
|
|
print(commentPath)
|
|
|
|
]);
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
|
2017-01-09 21:47:02 +03:00
|
|
|
exports.printComments = function(path, print, options) {
|
2016-12-23 21:38:10 +03:00
|
|
|
var value = path.getValue();
|
2017-01-10 05:49:06 +03:00
|
|
|
var parent = path.getParentNode();
|
2017-01-09 21:47:02 +03:00
|
|
|
var printed = print(path);
|
2016-12-23 21:38:10 +03:00
|
|
|
var comments = n.Node.check(value) &&
|
|
|
|
types.getFieldValue(value, "comments");
|
2017-01-10 05:49:06 +03:00
|
|
|
var isFirstInProgram = n.Program.check(parent) &&
|
|
|
|
parent.body[0] === value;
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
if (!comments || comments.length === 0) {
|
2017-01-09 21:47:02 +03:00
|
|
|
return printed;
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var leadingParts = [];
|
2017-01-09 21:47:02 +03:00
|
|
|
var trailingParts = [printed];
|
2016-12-23 21:38:10 +03:00
|
|
|
|
|
|
|
path.each(function(commentPath) {
|
|
|
|
var comment = commentPath.getValue();
|
|
|
|
var leading = types.getFieldValue(comment, "leading");
|
|
|
|
var trailing = types.getFieldValue(comment, "trailing");
|
|
|
|
|
|
|
|
if (leading || (trailing && !(n.Statement.check(value) ||
|
|
|
|
comment.type === "Block" ||
|
|
|
|
comment.type === "CommentBlock"))) {
|
|
|
|
leadingParts.push(printLeadingComment(commentPath, print));
|
2017-01-10 05:49:06 +03:00
|
|
|
|
|
|
|
|
|
|
|
// Support a special case where a comment exists at the very top
|
|
|
|
// of the file. Allow the user to add spacing between that file
|
|
|
|
// and any code beneath it.
|
|
|
|
if(isFirstInProgram &&
|
|
|
|
util.newlineExistsAfter(options.originalText, util.locEnd(comment))) {
|
|
|
|
leadingParts.push(hardline);
|
|
|
|
}
|
2016-12-23 21:38:10 +03:00
|
|
|
} else if (trailing) {
|
2017-01-09 21:47:02 +03:00
|
|
|
trailingParts.push(printTrailingComment(commentPath, print, options));
|
2016-12-23 21:38:10 +03:00
|
|
|
}
|
|
|
|
}, "comments");
|
|
|
|
|
|
|
|
leadingParts.push.apply(leadingParts, trailingParts);
|
|
|
|
return concat(leadingParts);
|
|
|
|
};
|