util.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. var assert = require("assert");
  2. var types = require("./types");
  3. var getFieldValue = types.getFieldValue;
  4. var n = types.namedTypes;
  5. var sourceMap = require("source-map");
  6. var SourceMapConsumer = sourceMap.SourceMapConsumer;
  7. var SourceMapGenerator = sourceMap.SourceMapGenerator;
  8. var hasOwn = Object.prototype.hasOwnProperty;
  9. var util = exports;
  10. function getUnionOfKeys() {
  11. var result = {};
  12. var argc = arguments.length;
  13. for (var i = 0; i < argc; ++i) {
  14. var keys = Object.keys(arguments[i]);
  15. var keyCount = keys.length;
  16. for (var j = 0; j < keyCount; ++j) {
  17. result[keys[j]] = true;
  18. }
  19. }
  20. return result;
  21. }
  22. util.getUnionOfKeys = getUnionOfKeys;
  23. function comparePos(pos1, pos2) {
  24. return (pos1.line - pos2.line) || (pos1.column - pos2.column);
  25. }
  26. util.comparePos = comparePos;
  27. function copyPos(pos) {
  28. return {
  29. line: pos.line,
  30. column: pos.column
  31. };
  32. }
  33. util.copyPos = copyPos;
  34. util.composeSourceMaps = function(formerMap, latterMap) {
  35. if (formerMap) {
  36. if (!latterMap) {
  37. return formerMap;
  38. }
  39. } else {
  40. return latterMap || null;
  41. }
  42. var smcFormer = new SourceMapConsumer(formerMap);
  43. var smcLatter = new SourceMapConsumer(latterMap);
  44. var smg = new SourceMapGenerator({
  45. file: latterMap.file,
  46. sourceRoot: latterMap.sourceRoot
  47. });
  48. var sourcesToContents = {};
  49. smcLatter.eachMapping(function(mapping) {
  50. var origPos = smcFormer.originalPositionFor({
  51. line: mapping.originalLine,
  52. column: mapping.originalColumn
  53. });
  54. var sourceName = origPos.source;
  55. if (sourceName === null) {
  56. return;
  57. }
  58. smg.addMapping({
  59. source: sourceName,
  60. original: copyPos(origPos),
  61. generated: {
  62. line: mapping.generatedLine,
  63. column: mapping.generatedColumn
  64. },
  65. name: mapping.name
  66. });
  67. var sourceContent = smcFormer.sourceContentFor(sourceName);
  68. if (sourceContent && !hasOwn.call(sourcesToContents, sourceName)) {
  69. sourcesToContents[sourceName] = sourceContent;
  70. smg.setSourceContent(sourceName, sourceContent);
  71. }
  72. });
  73. return smg.toJSON();
  74. };
  75. util.getTrueLoc = function(node, lines) {
  76. // It's possible that node is newly-created (not parsed by Esprima),
  77. // in which case it probably won't have a .loc property (or an
  78. // .original property for that matter). That's fine; we'll just
  79. // pretty-print it as usual.
  80. if (!node.loc) {
  81. return null;
  82. }
  83. var result = {
  84. start: node.loc.start,
  85. end: node.loc.end
  86. };
  87. function include(node) {
  88. expandLoc(result, node.loc);
  89. }
  90. // If the node has any comments, their locations might contribute to
  91. // the true start/end positions of the node.
  92. if (node.comments) {
  93. node.comments.forEach(include);
  94. }
  95. // If the node is an export declaration and its .declaration has any
  96. // decorators, their locations might contribute to the true start/end
  97. // positions of the export declaration node.
  98. if (node.declaration && util.isExportDeclaration(node) &&
  99. node.declaration.decorators) {
  100. node.declaration.decorators.forEach(include);
  101. }
  102. if (comparePos(result.start, result.end) < 0) {
  103. // Trim leading whitespace.
  104. result.start = copyPos(result.start);
  105. lines.skipSpaces(result.start, false, true);
  106. if (comparePos(result.start, result.end) < 0) {
  107. // Trim trailing whitespace, if the end location is not already the
  108. // same as the start location.
  109. result.end = copyPos(result.end);
  110. lines.skipSpaces(result.end, true, true);
  111. }
  112. }
  113. return result;
  114. };
  115. function expandLoc(parentLoc, childLoc) {
  116. if (parentLoc && childLoc) {
  117. if (comparePos(childLoc.start, parentLoc.start) < 0) {
  118. parentLoc.start = childLoc.start;
  119. }
  120. if (comparePos(parentLoc.end, childLoc.end) < 0) {
  121. parentLoc.end = childLoc.end;
  122. }
  123. }
  124. }
  125. util.fixFaultyLocations = function(node, lines) {
  126. var loc = node.loc;
  127. if (loc) {
  128. if (loc.start.line < 1) {
  129. loc.start.line = 1;
  130. }
  131. if (loc.end.line < 1) {
  132. loc.end.line = 1;
  133. }
  134. }
  135. if (node.type === "File") {
  136. // Babylon returns File nodes whose .loc.{start,end} do not include
  137. // leading or trailing whitespace.
  138. loc.start = lines.firstPos();
  139. loc.end = lines.lastPos();
  140. }
  141. if (node.type === "TemplateLiteral") {
  142. fixTemplateLiteral(node, lines);
  143. } else if (loc && node.decorators) {
  144. // Expand the .loc of the node responsible for printing the decorators
  145. // (here, the decorated node) so that it includes node.decorators.
  146. node.decorators.forEach(function (decorator) {
  147. expandLoc(loc, decorator.loc);
  148. });
  149. } else if (node.declaration && util.isExportDeclaration(node)) {
  150. // Nullify .loc information for the child declaration so that we never
  151. // try to reprint it without also reprinting the export declaration.
  152. node.declaration.loc = null;
  153. // Expand the .loc of the node responsible for printing the decorators
  154. // (here, the export declaration) so that it includes node.decorators.
  155. var decorators = node.declaration.decorators;
  156. if (decorators) {
  157. decorators.forEach(function (decorator) {
  158. expandLoc(loc, decorator.loc);
  159. });
  160. }
  161. } else if ((n.MethodDefinition && n.MethodDefinition.check(node)) ||
  162. (n.Property.check(node) && (node.method || node.shorthand))) {
  163. // If the node is a MethodDefinition or a .method or .shorthand
  164. // Property, then the location information stored in
  165. // node.value.loc is very likely untrustworthy (just the {body}
  166. // part of a method, or nothing in the case of shorthand
  167. // properties), so we null out that information to prevent
  168. // accidental reuse of bogus source code during reprinting.
  169. node.value.loc = null;
  170. if (n.FunctionExpression.check(node.value)) {
  171. // FunctionExpression method values should be anonymous,
  172. // because their .id fields are ignored anyway.
  173. node.value.id = null;
  174. }
  175. } else if (node.type === "ObjectTypeProperty") {
  176. var loc = node.loc;
  177. var end = loc && loc.end;
  178. if (end) {
  179. end = copyPos(end);
  180. if (lines.prevPos(end) &&
  181. lines.charAt(end) === ",") {
  182. // Some parsers accidentally include trailing commas in the
  183. // .loc.end information for ObjectTypeProperty nodes.
  184. if ((end = lines.skipSpaces(end, true, true))) {
  185. loc.end = end;
  186. }
  187. }
  188. }
  189. }
  190. };
  191. function fixTemplateLiteral(node, lines) {
  192. assert.strictEqual(node.type, "TemplateLiteral");
  193. if (node.quasis.length === 0) {
  194. // If there are no quasi elements, then there is nothing to fix.
  195. return;
  196. }
  197. // First we need to exclude the opening ` from the .loc of the first
  198. // quasi element, in case the parser accidentally decided to include it.
  199. var afterLeftBackTickPos = copyPos(node.loc.start);
  200. assert.strictEqual(lines.charAt(afterLeftBackTickPos), "`");
  201. assert.ok(lines.nextPos(afterLeftBackTickPos));
  202. var firstQuasi = node.quasis[0];
  203. if (comparePos(firstQuasi.loc.start, afterLeftBackTickPos) < 0) {
  204. firstQuasi.loc.start = afterLeftBackTickPos;
  205. }
  206. // Next we need to exclude the closing ` from the .loc of the last quasi
  207. // element, in case the parser accidentally decided to include it.
  208. var rightBackTickPos = copyPos(node.loc.end);
  209. assert.ok(lines.prevPos(rightBackTickPos));
  210. assert.strictEqual(lines.charAt(rightBackTickPos), "`");
  211. var lastQuasi = node.quasis[node.quasis.length - 1];
  212. if (comparePos(rightBackTickPos, lastQuasi.loc.end) < 0) {
  213. lastQuasi.loc.end = rightBackTickPos;
  214. }
  215. // Now we need to exclude ${ and } characters from the .loc's of all
  216. // quasi elements, since some parsers accidentally include them.
  217. node.expressions.forEach(function (expr, i) {
  218. // Rewind from expr.loc.start over any whitespace and the ${ that
  219. // precedes the expression. The position of the $ should be the same
  220. // as the .loc.end of the preceding quasi element, but some parsers
  221. // accidentally include the ${ in the .loc of the quasi element.
  222. var dollarCurlyPos = lines.skipSpaces(expr.loc.start, true, false);
  223. if (lines.prevPos(dollarCurlyPos) &&
  224. lines.charAt(dollarCurlyPos) === "{" &&
  225. lines.prevPos(dollarCurlyPos) &&
  226. lines.charAt(dollarCurlyPos) === "$") {
  227. var quasiBefore = node.quasis[i];
  228. if (comparePos(dollarCurlyPos, quasiBefore.loc.end) < 0) {
  229. quasiBefore.loc.end = dollarCurlyPos;
  230. }
  231. }
  232. // Likewise, some parsers accidentally include the } that follows
  233. // the expression in the .loc of the following quasi element.
  234. var rightCurlyPos = lines.skipSpaces(expr.loc.end, false, false);
  235. if (lines.charAt(rightCurlyPos) === "}") {
  236. assert.ok(lines.nextPos(rightCurlyPos));
  237. // Now rightCurlyPos is technically the position just after the }.
  238. var quasiAfter = node.quasis[i + 1];
  239. if (comparePos(quasiAfter.loc.start, rightCurlyPos) < 0) {
  240. quasiAfter.loc.start = rightCurlyPos;
  241. }
  242. }
  243. });
  244. }
  245. util.isExportDeclaration = function (node) {
  246. if (node) switch (node.type) {
  247. case "ExportDeclaration":
  248. case "ExportDefaultDeclaration":
  249. case "ExportDefaultSpecifier":
  250. case "DeclareExportDeclaration":
  251. case "ExportNamedDeclaration":
  252. case "ExportAllDeclaration":
  253. return true;
  254. }
  255. return false;
  256. };
  257. util.getParentExportDeclaration = function (path) {
  258. var parentNode = path.getParentNode();
  259. if (path.getName() === "declaration" &&
  260. util.isExportDeclaration(parentNode)) {
  261. return parentNode;
  262. }
  263. return null;
  264. };
  265. util.isTrailingCommaEnabled = function(options, context) {
  266. var trailingComma = options.trailingComma;
  267. if (typeof trailingComma === "object") {
  268. return !!trailingComma[context];
  269. }
  270. return !!trailingComma;
  271. };