You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
12 KiB

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