parser.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.parse = 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 b = types.builders;
  8. var isObject = types.builtInTypes.object;
  9. var isArray = types.builtInTypes.array;
  10. var options_1 = require("./options");
  11. var lines_1 = require("./lines");
  12. var comments_1 = require("./comments");
  13. var util = tslib_1.__importStar(require("./util"));
  14. function parse(source, options) {
  15. options = (0, options_1.normalize)(options);
  16. var lines = (0, lines_1.fromString)(source, options);
  17. var sourceWithoutTabs = lines.toString({
  18. tabWidth: options.tabWidth,
  19. reuseWhitespace: false,
  20. useTabs: false,
  21. });
  22. var comments = [];
  23. var ast = options.parser.parse(sourceWithoutTabs, {
  24. jsx: true,
  25. loc: true,
  26. locations: true,
  27. range: options.range,
  28. comment: true,
  29. onComment: comments,
  30. tolerant: util.getOption(options, "tolerant", true),
  31. ecmaVersion: 6,
  32. sourceType: util.getOption(options, "sourceType", "module"),
  33. });
  34. // Use ast.tokens if possible, and otherwise fall back to the Esprima
  35. // tokenizer. All the preconfigured ../parsers/* expose ast.tokens
  36. // automatically, but custom parsers might need additional configuration
  37. // to avoid this fallback.
  38. var tokens = Array.isArray(ast.tokens)
  39. ? ast.tokens
  40. : require("esprima").tokenize(sourceWithoutTabs, {
  41. loc: true,
  42. });
  43. // We will reattach the tokens array to the file object below.
  44. delete ast.tokens;
  45. // Make sure every token has a token.value string.
  46. tokens.forEach(function (token) {
  47. if (typeof token.value !== "string") {
  48. token.value = lines.sliceString(token.loc.start, token.loc.end);
  49. }
  50. });
  51. if (Array.isArray(ast.comments)) {
  52. comments = ast.comments;
  53. delete ast.comments;
  54. }
  55. if (ast.loc) {
  56. // If the source was empty, some parsers give loc.{start,end}.line
  57. // values of 0, instead of the minimum of 1.
  58. util.fixFaultyLocations(ast, lines);
  59. }
  60. else {
  61. ast.loc = {
  62. start: lines.firstPos(),
  63. end: lines.lastPos(),
  64. };
  65. }
  66. ast.loc.lines = lines;
  67. ast.loc.indent = 0;
  68. var file;
  69. var program;
  70. if (ast.type === "Program") {
  71. program = ast;
  72. // In order to ensure we reprint leading and trailing program
  73. // comments, wrap the original Program node with a File node. Only
  74. // ESTree parsers (Acorn and Esprima) return a Program as the root AST
  75. // node. Most other (Babylon-like) parsers return a File.
  76. file = b.file(ast, options.sourceFileName || null);
  77. file.loc = {
  78. start: lines.firstPos(),
  79. end: lines.lastPos(),
  80. lines: lines,
  81. indent: 0,
  82. };
  83. }
  84. else if (ast.type === "File") {
  85. file = ast;
  86. program = file.program;
  87. }
  88. // Expose file.tokens unless the caller passed false for options.tokens.
  89. if (options.tokens) {
  90. file.tokens = tokens;
  91. }
  92. // Expand the Program's .loc to include all comments (not just those
  93. // attached to the Program node, as its children may have comments as
  94. // well), since sometimes program.loc.{start,end} will coincide with the
  95. // .loc.{start,end} of the first and last *statements*, mistakenly
  96. // excluding comments that fall outside that region.
  97. var trueProgramLoc = util.getTrueLoc({
  98. type: program.type,
  99. loc: program.loc,
  100. body: [],
  101. comments: comments,
  102. }, lines);
  103. program.loc.start = trueProgramLoc.start;
  104. program.loc.end = trueProgramLoc.end;
  105. // Passing file.program here instead of just file means that initial
  106. // comments will be attached to program.body[0] instead of program.
  107. (0, comments_1.attach)(comments, program.body.length ? file.program : file, lines);
  108. // Return a copy of the original AST so that any changes made may be
  109. // compared to the original.
  110. return new TreeCopier(lines, tokens).copy(file);
  111. }
  112. exports.parse = parse;
  113. var TreeCopier = function TreeCopier(lines, tokens) {
  114. assert_1.default.ok(this instanceof TreeCopier);
  115. this.lines = lines;
  116. this.tokens = tokens;
  117. this.startTokenIndex = 0;
  118. this.endTokenIndex = tokens.length;
  119. this.indent = 0;
  120. this.seen = new Map();
  121. };
  122. var TCp = TreeCopier.prototype;
  123. TCp.copy = function (node) {
  124. if (this.seen.has(node)) {
  125. return this.seen.get(node);
  126. }
  127. if (isArray.check(node)) {
  128. var copy_1 = new Array(node.length);
  129. this.seen.set(node, copy_1);
  130. node.forEach(function (item, i) {
  131. copy_1[i] = this.copy(item);
  132. }, this);
  133. return copy_1;
  134. }
  135. if (!isObject.check(node)) {
  136. return node;
  137. }
  138. util.fixFaultyLocations(node, this.lines);
  139. var copy = Object.create(Object.getPrototypeOf(node), {
  140. original: {
  141. // Provide a link from the copy to the original.
  142. value: node,
  143. configurable: false,
  144. enumerable: false,
  145. writable: true,
  146. },
  147. });
  148. this.seen.set(node, copy);
  149. var loc = node.loc;
  150. var oldIndent = this.indent;
  151. var newIndent = oldIndent;
  152. var oldStartTokenIndex = this.startTokenIndex;
  153. var oldEndTokenIndex = this.endTokenIndex;
  154. if (loc) {
  155. // When node is a comment, we set node.loc.indent to
  156. // node.loc.start.column so that, when/if we print the comment by
  157. // itself, we can strip that much whitespace from the left margin of
  158. // the comment. This only really matters for multiline Block comments,
  159. // but it doesn't hurt for Line comments.
  160. if (node.type === "Block" ||
  161. node.type === "Line" ||
  162. node.type === "CommentBlock" ||
  163. node.type === "CommentLine" ||
  164. this.lines.isPrecededOnlyByWhitespace(loc.start)) {
  165. newIndent = this.indent = loc.start.column;
  166. }
  167. // Every node.loc has a reference to the original source lines as well
  168. // as a complete list of source tokens.
  169. loc.lines = this.lines;
  170. loc.tokens = this.tokens;
  171. loc.indent = newIndent;
  172. // Set loc.start.token and loc.end.token such that
  173. // loc.tokens.slice(loc.start.token, loc.end.token) returns a list of
  174. // all the tokens that make up this node.
  175. this.findTokenRange(loc);
  176. }
  177. var keys = Object.keys(node);
  178. var keyCount = keys.length;
  179. for (var i = 0; i < keyCount; ++i) {
  180. var key = keys[i];
  181. if (key === "loc") {
  182. copy[key] = node[key];
  183. }
  184. else if (key === "tokens" && node.type === "File") {
  185. // Preserve file.tokens (uncopied) in case client code cares about
  186. // it, even though Recast ignores it when reprinting.
  187. copy[key] = node[key];
  188. }
  189. else {
  190. copy[key] = this.copy(node[key]);
  191. }
  192. }
  193. this.indent = oldIndent;
  194. this.startTokenIndex = oldStartTokenIndex;
  195. this.endTokenIndex = oldEndTokenIndex;
  196. return copy;
  197. };
  198. // If we didn't have any idea where in loc.tokens to look for tokens
  199. // contained by this loc, a binary search would be appropriate, but
  200. // because we maintain this.startTokenIndex and this.endTokenIndex as we
  201. // traverse the AST, we only need to make small (linear) adjustments to
  202. // those indexes with each recursive iteration.
  203. TCp.findTokenRange = function (loc) {
  204. // In the unlikely event that loc.tokens[this.startTokenIndex] starts
  205. // *after* loc.start, we need to rewind this.startTokenIndex first.
  206. while (this.startTokenIndex > 0) {
  207. var token = loc.tokens[this.startTokenIndex];
  208. if (util.comparePos(loc.start, token.loc.start) < 0) {
  209. --this.startTokenIndex;
  210. }
  211. else
  212. break;
  213. }
  214. // In the unlikely event that loc.tokens[this.endTokenIndex - 1] ends
  215. // *before* loc.end, we need to fast-forward this.endTokenIndex first.
  216. while (this.endTokenIndex < loc.tokens.length) {
  217. var token = loc.tokens[this.endTokenIndex];
  218. if (util.comparePos(token.loc.end, loc.end) < 0) {
  219. ++this.endTokenIndex;
  220. }
  221. else
  222. break;
  223. }
  224. // Increment this.startTokenIndex until we've found the first token
  225. // contained by this node.
  226. while (this.startTokenIndex < this.endTokenIndex) {
  227. var token = loc.tokens[this.startTokenIndex];
  228. if (util.comparePos(token.loc.start, loc.start) < 0) {
  229. ++this.startTokenIndex;
  230. }
  231. else
  232. break;
  233. }
  234. // Index into loc.tokens of the first token within this node.
  235. loc.start.token = this.startTokenIndex;
  236. // Decrement this.endTokenIndex until we've found the first token after
  237. // this node (not contained by the node).
  238. while (this.endTokenIndex > this.startTokenIndex) {
  239. var token = loc.tokens[this.endTokenIndex - 1];
  240. if (util.comparePos(loc.end, token.loc.end) < 0) {
  241. --this.endTokenIndex;
  242. }
  243. else
  244. break;
  245. }
  246. // Index into loc.tokens of the first token *after* this node.
  247. // If loc.start.token === loc.end.token, the node contains no tokens,
  248. // and the index is that of the next token following this node.
  249. loc.end.token = this.endTokenIndex;
  250. };