comments.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.printComments = exports.attach = void 0;
  4. var tslib_1 = require("tslib");
  5. var assert_1 = tslib_1.__importDefault(require("assert"));
  6. var types = tslib_1.__importStar(require("ast-types"));
  7. var n = types.namedTypes;
  8. var isArray = types.builtInTypes.array;
  9. var isObject = types.builtInTypes.object;
  10. var lines_1 = require("./lines");
  11. var util_1 = require("./util");
  12. var childNodesCache = new WeakMap();
  13. // TODO Move a non-caching implementation of this function into ast-types,
  14. // and implement a caching wrapper function here.
  15. function getSortedChildNodes(node, lines, resultArray) {
  16. if (!node) {
  17. return resultArray;
  18. }
  19. // The .loc checks below are sensitive to some of the problems that
  20. // are fixed by this utility function. Specifically, if it decides to
  21. // set node.loc to null, indicating that the node's .loc information
  22. // is unreliable, then we don't want to add node to the resultArray.
  23. (0, util_1.fixFaultyLocations)(node, lines);
  24. if (resultArray) {
  25. if (n.Node.check(node) && n.SourceLocation.check(node.loc)) {
  26. // This reverse insertion sort almost always takes constant
  27. // time because we almost always (maybe always?) append the
  28. // nodes in order anyway.
  29. var i = resultArray.length - 1;
  30. for (; i >= 0; --i) {
  31. var child = resultArray[i];
  32. if (child &&
  33. child.loc &&
  34. (0, util_1.comparePos)(child.loc.end, node.loc.start) <= 0) {
  35. break;
  36. }
  37. }
  38. resultArray.splice(i + 1, 0, node);
  39. return resultArray;
  40. }
  41. }
  42. else {
  43. var childNodes = childNodesCache.get(node);
  44. if (childNodes) {
  45. return childNodes;
  46. }
  47. }
  48. var names;
  49. if (isArray.check(node)) {
  50. names = Object.keys(node);
  51. }
  52. else if (isObject.check(node)) {
  53. names = types.getFieldNames(node);
  54. }
  55. else {
  56. return resultArray;
  57. }
  58. if (!resultArray) {
  59. childNodesCache.set(node, (resultArray = []));
  60. }
  61. for (var i = 0, nameCount = names.length; i < nameCount; ++i) {
  62. getSortedChildNodes(node[names[i]], lines, resultArray);
  63. }
  64. return resultArray;
  65. }
  66. // As efficiently as possible, decorate the comment object with
  67. // .precedingNode, .enclosingNode, and/or .followingNode properties, at
  68. // least one of which is guaranteed to be defined.
  69. function decorateComment(node, comment, lines) {
  70. var childNodes = getSortedChildNodes(node, lines);
  71. // Time to dust off the old binary search robes and wizard hat.
  72. var left = 0;
  73. var right = childNodes && childNodes.length;
  74. var precedingNode;
  75. var followingNode;
  76. while (typeof right === "number" && left < right) {
  77. var middle = (left + right) >> 1;
  78. var child = childNodes[middle];
  79. if ((0, util_1.comparePos)(child.loc.start, comment.loc.start) <= 0 &&
  80. (0, util_1.comparePos)(comment.loc.end, child.loc.end) <= 0) {
  81. // The comment is completely contained by this child node.
  82. decorateComment((comment.enclosingNode = child), comment, lines);
  83. return; // Abandon the binary search at this level.
  84. }
  85. if ((0, util_1.comparePos)(child.loc.end, comment.loc.start) <= 0) {
  86. // This child node falls completely before the comment.
  87. // Because we will never consider this node or any nodes
  88. // before it again, this node must be the closest preceding
  89. // node we have encountered so far.
  90. precedingNode = child;
  91. left = middle + 1;
  92. continue;
  93. }
  94. if ((0, util_1.comparePos)(comment.loc.end, child.loc.start) <= 0) {
  95. // This child node falls completely after the comment.
  96. // Because we will never consider this node or any nodes after
  97. // it again, this node must be the closest following node we
  98. // have encountered so far.
  99. followingNode = child;
  100. right = middle;
  101. continue;
  102. }
  103. throw new Error("Comment location overlaps with node location");
  104. }
  105. if (precedingNode) {
  106. comment.precedingNode = precedingNode;
  107. }
  108. if (followingNode) {
  109. comment.followingNode = followingNode;
  110. }
  111. }
  112. function attach(comments, ast, lines) {
  113. if (!isArray.check(comments)) {
  114. return;
  115. }
  116. var tiesToBreak = [];
  117. comments.forEach(function (comment) {
  118. comment.loc.lines = lines;
  119. decorateComment(ast, comment, lines);
  120. var pn = comment.precedingNode;
  121. var en = comment.enclosingNode;
  122. var fn = comment.followingNode;
  123. if (pn && fn) {
  124. var tieCount = tiesToBreak.length;
  125. if (tieCount > 0) {
  126. var lastTie = tiesToBreak[tieCount - 1];
  127. assert_1.default.strictEqual(lastTie.precedingNode === comment.precedingNode, lastTie.followingNode === comment.followingNode);
  128. if (lastTie.followingNode !== comment.followingNode) {
  129. breakTies(tiesToBreak, lines);
  130. }
  131. }
  132. tiesToBreak.push(comment);
  133. }
  134. else if (pn) {
  135. // No contest: we have a trailing comment.
  136. breakTies(tiesToBreak, lines);
  137. addTrailingComment(pn, comment);
  138. }
  139. else if (fn) {
  140. // No contest: we have a leading comment.
  141. breakTies(tiesToBreak, lines);
  142. addLeadingComment(fn, comment);
  143. }
  144. else if (en) {
  145. // The enclosing node has no child nodes at all, so what we
  146. // have here is a dangling comment, e.g. [/* crickets */].
  147. breakTies(tiesToBreak, lines);
  148. addDanglingComment(en, comment);
  149. }
  150. else {
  151. throw new Error("AST contains no nodes at all?");
  152. }
  153. });
  154. breakTies(tiesToBreak, lines);
  155. comments.forEach(function (comment) {
  156. // These node references were useful for breaking ties, but we
  157. // don't need them anymore, and they create cycles in the AST that
  158. // may lead to infinite recursion if we don't delete them here.
  159. delete comment.precedingNode;
  160. delete comment.enclosingNode;
  161. delete comment.followingNode;
  162. });
  163. }
  164. exports.attach = attach;
  165. function breakTies(tiesToBreak, lines) {
  166. var tieCount = tiesToBreak.length;
  167. if (tieCount === 0) {
  168. return;
  169. }
  170. var pn = tiesToBreak[0].precedingNode;
  171. var fn = tiesToBreak[0].followingNode;
  172. var gapEndPos = fn.loc.start;
  173. // Iterate backwards through tiesToBreak, examining the gaps
  174. // between the tied comments. In order to qualify as leading, a
  175. // comment must be separated from fn by an unbroken series of
  176. // whitespace-only gaps (or other comments).
  177. var indexOfFirstLeadingComment = tieCount;
  178. var comment;
  179. for (; indexOfFirstLeadingComment > 0; --indexOfFirstLeadingComment) {
  180. comment = tiesToBreak[indexOfFirstLeadingComment - 1];
  181. assert_1.default.strictEqual(comment.precedingNode, pn);
  182. assert_1.default.strictEqual(comment.followingNode, fn);
  183. var gap = lines.sliceString(comment.loc.end, gapEndPos);
  184. if (/\S/.test(gap)) {
  185. // The gap string contained something other than whitespace.
  186. break;
  187. }
  188. gapEndPos = comment.loc.start;
  189. }
  190. while (indexOfFirstLeadingComment <= tieCount &&
  191. (comment = tiesToBreak[indexOfFirstLeadingComment]) &&
  192. // If the comment is a //-style comment and indented more
  193. // deeply than the node itself, reconsider it as trailing.
  194. (comment.type === "Line" || comment.type === "CommentLine") &&
  195. comment.loc.start.column > fn.loc.start.column) {
  196. ++indexOfFirstLeadingComment;
  197. }
  198. if (indexOfFirstLeadingComment) {
  199. var enclosingNode = tiesToBreak[indexOfFirstLeadingComment - 1].enclosingNode;
  200. if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.type) === "CallExpression") {
  201. --indexOfFirstLeadingComment;
  202. }
  203. }
  204. tiesToBreak.forEach(function (comment, i) {
  205. if (i < indexOfFirstLeadingComment) {
  206. addTrailingComment(pn, comment);
  207. }
  208. else {
  209. addLeadingComment(fn, comment);
  210. }
  211. });
  212. tiesToBreak.length = 0;
  213. }
  214. function addCommentHelper(node, comment) {
  215. var comments = node.comments || (node.comments = []);
  216. comments.push(comment);
  217. }
  218. function addLeadingComment(node, comment) {
  219. comment.leading = true;
  220. comment.trailing = false;
  221. addCommentHelper(node, comment);
  222. }
  223. function addDanglingComment(node, comment) {
  224. comment.leading = false;
  225. comment.trailing = false;
  226. addCommentHelper(node, comment);
  227. }
  228. function addTrailingComment(node, comment) {
  229. comment.leading = false;
  230. comment.trailing = true;
  231. addCommentHelper(node, comment);
  232. }
  233. function printLeadingComment(commentPath, print) {
  234. var comment = commentPath.getValue();
  235. n.Comment.assert(comment);
  236. var loc = comment.loc;
  237. var lines = loc && loc.lines;
  238. var parts = [print(commentPath)];
  239. if (comment.trailing) {
  240. // When we print trailing comments as leading comments, we don't
  241. // want to bring any trailing spaces along.
  242. parts.push("\n");
  243. }
  244. else if (lines instanceof lines_1.Lines) {
  245. var trailingSpace = lines.slice(loc.end, lines.skipSpaces(loc.end) || lines.lastPos());
  246. if (trailingSpace.length === 1) {
  247. // If the trailing space contains no newlines, then we want to
  248. // preserve it exactly as we found it.
  249. parts.push(trailingSpace);
  250. }
  251. else {
  252. // If the trailing space contains newlines, then replace it
  253. // with just that many newlines, with all other spaces removed.
  254. parts.push(new Array(trailingSpace.length).join("\n"));
  255. }
  256. }
  257. else {
  258. parts.push("\n");
  259. }
  260. return (0, lines_1.concat)(parts);
  261. }
  262. function printTrailingComment(commentPath, print) {
  263. var comment = commentPath.getValue(commentPath);
  264. n.Comment.assert(comment);
  265. var loc = comment.loc;
  266. var lines = loc && loc.lines;
  267. var parts = [];
  268. if (lines instanceof lines_1.Lines) {
  269. var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos();
  270. var leadingSpace = lines.slice(fromPos, loc.start);
  271. if (leadingSpace.length === 1) {
  272. // If the leading space contains no newlines, then we want to
  273. // preserve it exactly as we found it.
  274. parts.push(leadingSpace);
  275. }
  276. else {
  277. // If the leading space contains newlines, then replace it
  278. // with just that many newlines, sans all other spaces.
  279. parts.push(new Array(leadingSpace.length).join("\n"));
  280. }
  281. }
  282. parts.push(print(commentPath));
  283. return (0, lines_1.concat)(parts);
  284. }
  285. function printComments(path, print) {
  286. var value = path.getValue();
  287. var innerLines = print(path);
  288. var comments = n.Node.check(value) && types.getFieldValue(value, "comments");
  289. if (!comments || comments.length === 0) {
  290. return innerLines;
  291. }
  292. var leadingParts = [];
  293. var trailingParts = [innerLines];
  294. path.each(function (commentPath) {
  295. var comment = commentPath.getValue();
  296. var leading = types.getFieldValue(comment, "leading");
  297. var trailing = types.getFieldValue(comment, "trailing");
  298. if (leading ||
  299. (trailing &&
  300. !(n.Statement.check(value) ||
  301. comment.type === "Block" ||
  302. comment.type === "CommentBlock"))) {
  303. leadingParts.push(printLeadingComment(commentPath, print));
  304. }
  305. else if (trailing) {
  306. trailingParts.push(printTrailingComment(commentPath, print));
  307. }
  308. }, "comments");
  309. leadingParts.push.apply(leadingParts, trailingParts);
  310. return (0, lines_1.concat)(leadingParts);
  311. }
  312. exports.printComments = printComments;