util.js 10 KB

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