parser.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. "use strict";
  2. var assert = require("assert");
  3. var types = require("./types");
  4. var n = types.namedTypes;
  5. var b = types.builders;
  6. var isObject = types.builtInTypes.object;
  7. var isArray = types.builtInTypes.array;
  8. var isFunction = types.builtInTypes.function;
  9. var Patcher = require("./patcher").Patcher;
  10. var normalizeOptions = require("./options").normalize;
  11. var fromString = require("./lines").fromString;
  12. var attachComments = require("./comments").attach;
  13. var util = require("./util");
  14. exports.parse = function parse(source, options) {
  15. options = normalizeOptions(options);
  16. const lines = fromString(source, options);
  17. const sourceWithoutTabs = lines.toString({
  18. tabWidth: options.tabWidth,
  19. reuseWhitespace: false,
  20. useTabs: false
  21. });
  22. let comments = [];
  23. const 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. if (Array.isArray(ast.comments)) {
  35. comments = ast.comments;
  36. delete ast.comments;
  37. }
  38. if (ast.loc) {
  39. // If the source was empty, some parsers give loc.{start,end}.line
  40. // values of 0, instead of the minimum of 1.
  41. util.fixFaultyLocations(ast, lines);
  42. } else {
  43. ast.loc = {
  44. start: lines.firstPos(),
  45. end: lines.lastPos()
  46. };
  47. }
  48. ast.loc.lines = lines;
  49. ast.loc.indent = 0;
  50. let file;
  51. let program;
  52. if (ast.type === "Program") {
  53. program = ast;
  54. // In order to ensure we reprint leading and trailing program
  55. // comments, wrap the original Program node with a File node. Only
  56. // ESTree parsers (Acorn and Esprima) return a Program as the root AST
  57. // node. Most other (Babylon-like) parsers return a File.
  58. file = b.file(ast, options.sourceFileName || null);
  59. file.loc = {
  60. start: lines.firstPos(),
  61. end: lines.lastPos(),
  62. lines: lines,
  63. indent: 0
  64. };
  65. } else if (ast.type === "File") {
  66. file = ast;
  67. program = file.program;
  68. }
  69. // Expand the Program's .loc to include all comments (not just those
  70. // attached to the Program node, as its children may have comments as
  71. // well), since sometimes program.loc.{start,end} will coincide with the
  72. // .loc.{start,end} of the first and last *statements*, mistakenly
  73. // excluding comments that fall outside that region.
  74. var trueProgramLoc = util.getTrueLoc({
  75. type: program.type,
  76. loc: program.loc,
  77. body: [],
  78. comments
  79. }, lines);
  80. program.loc.start = trueProgramLoc.start;
  81. program.loc.end = trueProgramLoc.end;
  82. // Passing file.program here instead of just file means that initial
  83. // comments will be attached to program.body[0] instead of program.
  84. attachComments(
  85. comments,
  86. program.body.length ? file.program : file,
  87. lines
  88. );
  89. // Return a copy of the original AST so that any changes made may be
  90. // compared to the original.
  91. return new TreeCopier(lines).copy(file);
  92. };
  93. function TreeCopier(lines) {
  94. assert.ok(this instanceof TreeCopier);
  95. this.lines = lines;
  96. this.indent = 0;
  97. this.seen = new Map;
  98. }
  99. var TCp = TreeCopier.prototype;
  100. TCp.copy = function(node) {
  101. if (this.seen.has(node)) {
  102. return this.seen.get(node);
  103. }
  104. if (isArray.check(node)) {
  105. var copy = new Array(node.length);
  106. this.seen.set(node, copy);
  107. node.forEach(function (item, i) {
  108. copy[i] = this.copy(item);
  109. }, this);
  110. return copy;
  111. }
  112. if (!isObject.check(node)) {
  113. return node;
  114. }
  115. util.fixFaultyLocations(node, this.lines);
  116. var copy = Object.create(Object.getPrototypeOf(node), {
  117. original: { // Provide a link from the copy to the original.
  118. value: node,
  119. configurable: false,
  120. enumerable: false,
  121. writable: true
  122. }
  123. });
  124. this.seen.set(node, copy);
  125. var loc = node.loc;
  126. var oldIndent = this.indent;
  127. var newIndent = oldIndent;
  128. if (loc) {
  129. // When node is a comment, we set node.loc.indent to
  130. // node.loc.start.column so that, when/if we print the comment by
  131. // itself, we can strip that much whitespace from the left margin of
  132. // the comment. This only really matters for multiline Block comments,
  133. // but it doesn't hurt for Line comments.
  134. if (node.type === "Block" || node.type === "Line" ||
  135. node.type === "CommentBlock" || node.type === "CommentLine" ||
  136. this.lines.isPrecededOnlyByWhitespace(loc.start)) {
  137. newIndent = this.indent = loc.start.column;
  138. }
  139. loc.lines = this.lines;
  140. loc.indent = newIndent;
  141. }
  142. var keys = Object.keys(node);
  143. var keyCount = keys.length;
  144. for (var i = 0; i < keyCount; ++i) {
  145. var key = keys[i];
  146. if (key === "loc") {
  147. copy[key] = node[key];
  148. } else if (key === "tokens" &&
  149. node.type === "File") {
  150. // Preserve file.tokens (uncopied) in case client code cares about
  151. // it, even though Recast ignores it when reprinting.
  152. copy[key] = node[key];
  153. } else {
  154. copy[key] = this.copy(node[key]);
  155. }
  156. }
  157. this.indent = oldIndent;
  158. return copy;
  159. };