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.

539 lines
19 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 n = types.namedTypes;
  16. var isArray = types.builtInTypes.array;
  17. var isNumber = types.builtInTypes.number;
  18. var util = __importStar(require("./util"));
  19. var FastPath = function FastPath(value) {
  20. assert_1.default.ok(this instanceof FastPath);
  21. this.stack = [value];
  22. };
  23. var FPp = FastPath.prototype;
  24. // Static convenience function for coercing a value to a FastPath.
  25. FastPath.from = function (obj) {
  26. if (obj instanceof FastPath) {
  27. // Return a defensive copy of any existing FastPath instances.
  28. return obj.copy();
  29. }
  30. if (obj instanceof types.NodePath) {
  31. // For backwards compatibility, unroll NodePath instances into
  32. // lightweight FastPath [..., name, value] stacks.
  33. var copy = Object.create(FastPath.prototype);
  34. var stack = [obj.value];
  35. for (var pp; (pp = obj.parentPath); obj = pp)
  36. stack.push(obj.name, pp.value);
  37. copy.stack = stack.reverse();
  38. return copy;
  39. }
  40. // Otherwise use obj as the value of the new FastPath instance.
  41. return new FastPath(obj);
  42. };
  43. FPp.copy = function copy() {
  44. var copy = Object.create(FastPath.prototype);
  45. copy.stack = this.stack.slice(0);
  46. return copy;
  47. };
  48. // The name of the current property is always the penultimate element of
  49. // this.stack, and always a String.
  50. FPp.getName = function getName() {
  51. var s = this.stack;
  52. var len = s.length;
  53. if (len > 1) {
  54. return s[len - 2];
  55. }
  56. // Since the name is always a string, null is a safe sentinel value to
  57. // return if we do not know the name of the (root) value.
  58. return null;
  59. };
  60. // The value of the current property is always the final element of
  61. // this.stack.
  62. FPp.getValue = function getValue() {
  63. var s = this.stack;
  64. return s[s.length - 1];
  65. };
  66. FPp.valueIsDuplicate = function () {
  67. var s = this.stack;
  68. var valueIndex = s.length - 1;
  69. return s.lastIndexOf(s[valueIndex], valueIndex - 1) >= 0;
  70. };
  71. function getNodeHelper(path, count) {
  72. var s = path.stack;
  73. for (var i = s.length - 1; i >= 0; i -= 2) {
  74. var value = s[i];
  75. if (n.Node.check(value) && --count < 0) {
  76. return value;
  77. }
  78. }
  79. return null;
  80. }
  81. FPp.getNode = function getNode(count) {
  82. if (count === void 0) { count = 0; }
  83. return getNodeHelper(this, ~~count);
  84. };
  85. FPp.getParentNode = function getParentNode(count) {
  86. if (count === void 0) { count = 0; }
  87. return getNodeHelper(this, ~~count + 1);
  88. };
  89. // The length of the stack can be either even or odd, depending on whether
  90. // or not we have a name for the root value. The difference between the
  91. // index of the root value and the index of the final value is always
  92. // even, though, which allows us to return the root value in constant time
  93. // (i.e. without iterating backwards through the stack).
  94. FPp.getRootValue = function getRootValue() {
  95. var s = this.stack;
  96. if (s.length % 2 === 0) {
  97. return s[1];
  98. }
  99. return s[0];
  100. };
  101. // Temporarily push properties named by string arguments given after the
  102. // callback function onto this.stack, then call the callback with a
  103. // reference to this (modified) FastPath object. Note that the stack will
  104. // be restored to its original state after the callback is finished, so it
  105. // is probably a mistake to retain a reference to the path.
  106. FPp.call = function call(callback /*, name1, name2, ... */) {
  107. var s = this.stack;
  108. var origLen = s.length;
  109. var value = s[origLen - 1];
  110. var argc = arguments.length;
  111. for (var i = 1; i < argc; ++i) {
  112. var name = arguments[i];
  113. value = value[name];
  114. s.push(name, value);
  115. }
  116. var result = callback(this);
  117. s.length = origLen;
  118. return result;
  119. };
  120. // Similar to FastPath.prototype.call, except that the value obtained by
  121. // accessing this.getValue()[name1][name2]... should be array-like. The
  122. // callback will be called with a reference to this path object for each
  123. // element of the array.
  124. FPp.each = function each(callback /*, name1, name2, ... */) {
  125. var s = this.stack;
  126. var origLen = s.length;
  127. var value = s[origLen - 1];
  128. var argc = arguments.length;
  129. for (var i = 1; i < argc; ++i) {
  130. var name = arguments[i];
  131. value = value[name];
  132. s.push(name, value);
  133. }
  134. for (var i = 0; i < value.length; ++i) {
  135. if (i in value) {
  136. s.push(i, value[i]);
  137. // If the callback needs to know the value of i, call
  138. // path.getName(), assuming path is the parameter name.
  139. callback(this);
  140. s.length -= 2;
  141. }
  142. }
  143. s.length = origLen;
  144. };
  145. // Similar to FastPath.prototype.each, except that the results of the
  146. // callback function invocations are stored in an array and returned at
  147. // the end of the iteration.
  148. FPp.map = function map(callback /*, name1, name2, ... */) {
  149. var s = this.stack;
  150. var origLen = s.length;
  151. var value = s[origLen - 1];
  152. var argc = arguments.length;
  153. for (var i = 1; i < argc; ++i) {
  154. var name = arguments[i];
  155. value = value[name];
  156. s.push(name, value);
  157. }
  158. var result = new Array(value.length);
  159. for (var i = 0; i < value.length; ++i) {
  160. if (i in value) {
  161. s.push(i, value[i]);
  162. result[i] = callback(this, i);
  163. s.length -= 2;
  164. }
  165. }
  166. s.length = origLen;
  167. return result;
  168. };
  169. // Returns true if the node at the tip of the path is wrapped with
  170. // parentheses, OR if the only reason the node needed parentheses was that
  171. // it couldn't be the first expression in the enclosing statement (see
  172. // FastPath#canBeFirstInStatement), and it has an opening `(` character.
  173. // For example, the FunctionExpression in `(function(){}())` appears to
  174. // need parentheses only because it's the first expression in the AST, but
  175. // since it happens to be preceded by a `(` (which is not apparent from
  176. // the AST but can be determined using FastPath#getPrevToken), there is no
  177. // ambiguity about how to parse it, so it counts as having parentheses,
  178. // even though it is not immediately followed by a `)`.
  179. FPp.hasParens = function () {
  180. var node = this.getNode();
  181. var prevToken = this.getPrevToken(node);
  182. if (!prevToken) {
  183. return false;
  184. }
  185. var nextToken = this.getNextToken(node);
  186. if (!nextToken) {
  187. return false;
  188. }
  189. if (prevToken.value === "(") {
  190. if (nextToken.value === ")") {
  191. // If the node preceded by a `(` token and followed by a `)` token,
  192. // then of course it has parentheses.
  193. return true;
  194. }
  195. // If this is one of the few Expression types that can't come first in
  196. // the enclosing statement because of parsing ambiguities (namely,
  197. // FunctionExpression, ObjectExpression, and ClassExpression) and
  198. // this.firstInStatement() returns true, and the node would not need
  199. // parentheses in an expression context because this.needsParens(true)
  200. // returns false, then it just needs an opening parenthesis to resolve
  201. // the parsing ambiguity that made it appear to need parentheses.
  202. var justNeedsOpeningParen = !this.canBeFirstInStatement() &&
  203. this.firstInStatement() &&
  204. !this.needsParens(true);
  205. if (justNeedsOpeningParen) {
  206. return true;
  207. }
  208. }
  209. return false;
  210. };
  211. FPp.getPrevToken = function (node) {
  212. node = node || this.getNode();
  213. var loc = node && node.loc;
  214. var tokens = loc && loc.tokens;
  215. if (tokens && loc.start.token > 0) {
  216. var token = tokens[loc.start.token - 1];
  217. if (token) {
  218. // Do not return tokens that fall outside the root subtree.
  219. var rootLoc = this.getRootValue().loc;
  220. if (util.comparePos(rootLoc.start, token.loc.start) <= 0) {
  221. return token;
  222. }
  223. }
  224. }
  225. return null;
  226. };
  227. FPp.getNextToken = function (node) {
  228. node = node || this.getNode();
  229. var loc = node && node.loc;
  230. var tokens = loc && loc.tokens;
  231. if (tokens && loc.end.token < tokens.length) {
  232. var token = tokens[loc.end.token];
  233. if (token) {
  234. // Do not return tokens that fall outside the root subtree.
  235. var rootLoc = this.getRootValue().loc;
  236. if (util.comparePos(token.loc.end, rootLoc.end) <= 0) {
  237. return token;
  238. }
  239. }
  240. }
  241. return null;
  242. };
  243. // Inspired by require("ast-types").NodePath.prototype.needsParens, but
  244. // more efficient because we're iterating backwards through a stack.
  245. FPp.needsParens = function (assumeExpressionContext) {
  246. var node = this.getNode();
  247. // This needs to come before `if (!parent) { return false }` because
  248. // an object destructuring assignment requires parens for
  249. // correctness even when it's the topmost expression.
  250. if (node.type === "AssignmentExpression" && node.left.type === 'ObjectPattern') {
  251. return true;
  252. }
  253. var parent = this.getParentNode();
  254. if (!parent) {
  255. return false;
  256. }
  257. var name = this.getName();
  258. // If the value of this path is some child of a Node and not a Node
  259. // itself, then it doesn't need parentheses. Only Node objects (in fact,
  260. // only Expression nodes) need parentheses.
  261. if (this.getValue() !== node) {
  262. return false;
  263. }
  264. // Only statements don't need parentheses.
  265. if (n.Statement.check(node)) {
  266. return false;
  267. }
  268. // Identifiers never need parentheses.
  269. if (node.type === "Identifier") {
  270. return false;
  271. }
  272. if (parent.type === "ParenthesizedExpression") {
  273. return false;
  274. }
  275. switch (node.type) {
  276. case "UnaryExpression":
  277. case "SpreadElement":
  278. case "SpreadProperty":
  279. return parent.type === "MemberExpression"
  280. && name === "object"
  281. && parent.object === node;
  282. case "BinaryExpression":
  283. case "LogicalExpression":
  284. switch (parent.type) {
  285. case "CallExpression":
  286. return name === "callee"
  287. && parent.callee === node;
  288. case "UnaryExpression":
  289. case "SpreadElement":
  290. case "SpreadProperty":
  291. return true;
  292. case "MemberExpression":
  293. return name === "object"
  294. && parent.object === node;
  295. case "BinaryExpression":
  296. case "LogicalExpression":
  297. var po = parent.operator;
  298. var pp = PRECEDENCE[po];
  299. var no = node.operator;
  300. var np = PRECEDENCE[no];
  301. if (pp > np) {
  302. return true;
  303. }
  304. if (pp === np && name === "right") {
  305. assert_1.default.strictEqual(parent.right, node);
  306. return true;
  307. }
  308. default:
  309. return false;
  310. }
  311. case "SequenceExpression":
  312. switch (parent.type) {
  313. case "ReturnStatement":
  314. return false;
  315. case "ForStatement":
  316. // Although parentheses wouldn't hurt around sequence expressions in
  317. // the head of for loops, traditional style dictates that e.g. i++,
  318. // j++ should not be wrapped with parentheses.
  319. return false;
  320. case "ExpressionStatement":
  321. return name !== "expression";
  322. default:
  323. // Otherwise err on the side of overparenthesization, adding
  324. // explicit exceptions above if this proves overzealous.
  325. return true;
  326. }
  327. case "YieldExpression":
  328. switch (parent.type) {
  329. case "BinaryExpression":
  330. case "LogicalExpression":
  331. case "UnaryExpression":
  332. case "SpreadElement":
  333. case "SpreadProperty":
  334. case "CallExpression":
  335. case "MemberExpression":
  336. case "NewExpression":
  337. case "ConditionalExpression":
  338. case "YieldExpression":
  339. return true;
  340. default:
  341. return false;
  342. }
  343. case "IntersectionTypeAnnotation":
  344. case "UnionTypeAnnotation":
  345. return parent.type === "NullableTypeAnnotation";
  346. case "Literal":
  347. return parent.type === "MemberExpression"
  348. && isNumber.check(node.value)
  349. && name === "object"
  350. && parent.object === node;
  351. // Babel 6 Literal split
  352. case "NumericLiteral":
  353. return parent.type === "MemberExpression"
  354. && name === "object"
  355. && parent.object === node;
  356. case "AssignmentExpression":
  357. case "ConditionalExpression":
  358. switch (parent.type) {
  359. case "UnaryExpression":
  360. case "SpreadElement":
  361. case "SpreadProperty":
  362. case "BinaryExpression":
  363. case "LogicalExpression":
  364. return true;
  365. case "CallExpression":
  366. case "NewExpression":
  367. return name === "callee"
  368. && parent.callee === node;
  369. case "ConditionalExpression":
  370. return name === "test"
  371. && parent.test === node;
  372. case "MemberExpression":
  373. return name === "object"
  374. && parent.object === node;
  375. default:
  376. return false;
  377. }
  378. case "ArrowFunctionExpression":
  379. if (n.CallExpression.check(parent) &&
  380. name === 'callee') {
  381. return true;
  382. }
  383. if (n.MemberExpression.check(parent) &&
  384. name === 'object') {
  385. return true;
  386. }
  387. return isBinary(parent);
  388. case "ObjectExpression":
  389. if (parent.type === "ArrowFunctionExpression" &&
  390. name === "body") {
  391. return true;
  392. }
  393. break;
  394. case "CallExpression":
  395. if (name === "declaration" &&
  396. n.ExportDefaultDeclaration.check(parent) &&
  397. n.FunctionExpression.check(node.callee)) {
  398. return true;
  399. }
  400. }
  401. if (parent.type === "NewExpression" &&
  402. name === "callee" &&
  403. parent.callee === node) {
  404. return containsCallExpression(node);
  405. }
  406. if (assumeExpressionContext !== true &&
  407. !this.canBeFirstInStatement() &&
  408. this.firstInStatement()) {
  409. return true;
  410. }
  411. return false;
  412. };
  413. function isBinary(node) {
  414. return n.BinaryExpression.check(node)
  415. || n.LogicalExpression.check(node);
  416. }
  417. // @ts-ignore 'isUnaryLike' is declared but its value is never read. [6133]
  418. function isUnaryLike(node) {
  419. return n.UnaryExpression.check(node)
  420. // I considered making SpreadElement and SpreadProperty subtypes of
  421. // UnaryExpression, but they're not really Expression nodes.
  422. || (n.SpreadElement && n.SpreadElement.check(node))
  423. || (n.SpreadProperty && n.SpreadProperty.check(node));
  424. }
  425. var PRECEDENCE = {};
  426. [["||"],
  427. ["&&"],
  428. ["|"],
  429. ["^"],
  430. ["&"],
  431. ["==", "===", "!=", "!=="],
  432. ["<", ">", "<=", ">=", "in", "instanceof"],
  433. [">>", "<<", ">>>"],
  434. ["+", "-"],
  435. ["*", "/", "%", "**"]
  436. ].forEach(function (tier, i) {
  437. tier.forEach(function (op) {
  438. PRECEDENCE[op] = i;
  439. });
  440. });
  441. function containsCallExpression(node) {
  442. if (n.CallExpression.check(node)) {
  443. return true;
  444. }
  445. if (isArray.check(node)) {
  446. return node.some(containsCallExpression);
  447. }
  448. if (n.Node.check(node)) {
  449. return types.someField(node, function (_name, child) {
  450. return containsCallExpression(child);
  451. });
  452. }
  453. return false;
  454. }
  455. FPp.canBeFirstInStatement = function () {
  456. var node = this.getNode();
  457. if (n.FunctionExpression.check(node)) {
  458. return false;
  459. }
  460. if (n.ObjectExpression.check(node)) {
  461. return false;
  462. }
  463. if (n.ClassExpression.check(node)) {
  464. return false;
  465. }
  466. return true;
  467. };
  468. FPp.firstInStatement = function () {
  469. var s = this.stack;
  470. var parentName, parent;
  471. var childName, child;
  472. for (var i = s.length - 1; i >= 0; i -= 2) {
  473. if (n.Node.check(s[i])) {
  474. childName = parentName;
  475. child = parent;
  476. parentName = s[i - 1];
  477. parent = s[i];
  478. }
  479. if (!parent || !child) {
  480. continue;
  481. }
  482. if (n.BlockStatement.check(parent) &&
  483. parentName === "body" &&
  484. childName === 0) {
  485. assert_1.default.strictEqual(parent.body[0], child);
  486. return true;
  487. }
  488. if (n.ExpressionStatement.check(parent) &&
  489. childName === "expression") {
  490. assert_1.default.strictEqual(parent.expression, child);
  491. return true;
  492. }
  493. if (n.AssignmentExpression.check(parent) &&
  494. childName === "left") {
  495. assert_1.default.strictEqual(parent.left, child);
  496. return true;
  497. }
  498. if (n.ArrowFunctionExpression.check(parent) &&
  499. childName === "body") {
  500. assert_1.default.strictEqual(parent.body, child);
  501. return true;
  502. }
  503. if (n.SequenceExpression.check(parent) &&
  504. parentName === "expressions" &&
  505. childName === 0) {
  506. assert_1.default.strictEqual(parent.expressions[0], child);
  507. continue;
  508. }
  509. if (n.CallExpression.check(parent) &&
  510. childName === "callee") {
  511. assert_1.default.strictEqual(parent.callee, child);
  512. continue;
  513. }
  514. if (n.MemberExpression.check(parent) &&
  515. childName === "object") {
  516. assert_1.default.strictEqual(parent.object, child);
  517. continue;
  518. }
  519. if (n.ConditionalExpression.check(parent) &&
  520. childName === "test") {
  521. assert_1.default.strictEqual(parent.test, child);
  522. continue;
  523. }
  524. if (isBinary(parent) &&
  525. childName === "left") {
  526. assert_1.default.strictEqual(parent.left, child);
  527. continue;
  528. }
  529. if (n.UnaryExpression.check(parent) &&
  530. !parent.prefix &&
  531. childName === "argument") {
  532. assert_1.default.strictEqual(parent.argument, child);
  533. continue;
  534. }
  535. return false;
  536. }
  537. return true;
  538. };
  539. exports.default = FastPath;