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.

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