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.

path-visitor.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. "use strict";;
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. var types_1 = __importDefault(require("./types"));
  7. var node_path_1 = __importDefault(require("./node-path"));
  8. var hasOwn = Object.prototype.hasOwnProperty;
  9. function pathVisitorPlugin(fork) {
  10. var types = fork.use(types_1.default);
  11. var NodePath = fork.use(node_path_1.default);
  12. var isArray = types.builtInTypes.array;
  13. var isObject = types.builtInTypes.object;
  14. var isFunction = types.builtInTypes.function;
  15. var undefined;
  16. var PathVisitor = function PathVisitor() {
  17. if (!(this instanceof PathVisitor)) {
  18. throw new Error("PathVisitor constructor cannot be invoked without 'new'");
  19. }
  20. // Permanent state.
  21. this._reusableContextStack = [];
  22. this._methodNameTable = computeMethodNameTable(this);
  23. this._shouldVisitComments =
  24. hasOwn.call(this._methodNameTable, "Block") ||
  25. hasOwn.call(this._methodNameTable, "Line");
  26. this.Context = makeContextConstructor(this);
  27. // State reset every time PathVisitor.prototype.visit is called.
  28. this._visiting = false;
  29. this._changeReported = false;
  30. };
  31. function computeMethodNameTable(visitor) {
  32. var typeNames = Object.create(null);
  33. for (var methodName in visitor) {
  34. if (/^visit[A-Z]/.test(methodName)) {
  35. typeNames[methodName.slice("visit".length)] = true;
  36. }
  37. }
  38. var supertypeTable = types.computeSupertypeLookupTable(typeNames);
  39. var methodNameTable = Object.create(null);
  40. var typeNameKeys = Object.keys(supertypeTable);
  41. var typeNameCount = typeNameKeys.length;
  42. for (var i = 0; i < typeNameCount; ++i) {
  43. var typeName = typeNameKeys[i];
  44. methodName = "visit" + supertypeTable[typeName];
  45. if (isFunction.check(visitor[methodName])) {
  46. methodNameTable[typeName] = methodName;
  47. }
  48. }
  49. return methodNameTable;
  50. }
  51. PathVisitor.fromMethodsObject = function fromMethodsObject(methods) {
  52. if (methods instanceof PathVisitor) {
  53. return methods;
  54. }
  55. if (!isObject.check(methods)) {
  56. // An empty visitor?
  57. return new PathVisitor;
  58. }
  59. var Visitor = function Visitor() {
  60. if (!(this instanceof Visitor)) {
  61. throw new Error("Visitor constructor cannot be invoked without 'new'");
  62. }
  63. PathVisitor.call(this);
  64. };
  65. var Vp = Visitor.prototype = Object.create(PVp);
  66. Vp.constructor = Visitor;
  67. extend(Vp, methods);
  68. extend(Visitor, PathVisitor);
  69. isFunction.assert(Visitor.fromMethodsObject);
  70. isFunction.assert(Visitor.visit);
  71. return new Visitor;
  72. };
  73. function extend(target, source) {
  74. for (var property in source) {
  75. if (hasOwn.call(source, property)) {
  76. target[property] = source[property];
  77. }
  78. }
  79. return target;
  80. }
  81. PathVisitor.visit = function visit(node, methods) {
  82. return PathVisitor.fromMethodsObject(methods).visit(node);
  83. };
  84. var PVp = PathVisitor.prototype;
  85. PVp.visit = function () {
  86. if (this._visiting) {
  87. throw new Error("Recursively calling visitor.visit(path) resets visitor state. " +
  88. "Try this.visit(path) or this.traverse(path) instead.");
  89. }
  90. // Private state that needs to be reset before every traversal.
  91. this._visiting = true;
  92. this._changeReported = false;
  93. this._abortRequested = false;
  94. var argc = arguments.length;
  95. var args = new Array(argc);
  96. for (var i = 0; i < argc; ++i) {
  97. args[i] = arguments[i];
  98. }
  99. if (!(args[0] instanceof NodePath)) {
  100. args[0] = new NodePath({ root: args[0] }).get("root");
  101. }
  102. // Called with the same arguments as .visit.
  103. this.reset.apply(this, args);
  104. var didNotThrow;
  105. try {
  106. var root = this.visitWithoutReset(args[0]);
  107. didNotThrow = true;
  108. }
  109. finally {
  110. this._visiting = false;
  111. if (!didNotThrow && this._abortRequested) {
  112. // If this.visitWithoutReset threw an exception and
  113. // this._abortRequested was set to true, return the root of
  114. // the AST instead of letting the exception propagate, so that
  115. // client code does not have to provide a try-catch block to
  116. // intercept the AbortRequest exception. Other kinds of
  117. // exceptions will propagate without being intercepted and
  118. // rethrown by a catch block, so their stacks will accurately
  119. // reflect the original throwing context.
  120. return args[0].value;
  121. }
  122. }
  123. return root;
  124. };
  125. PVp.AbortRequest = function AbortRequest() { };
  126. PVp.abort = function () {
  127. var visitor = this;
  128. visitor._abortRequested = true;
  129. var request = new visitor.AbortRequest();
  130. // If you decide to catch this exception and stop it from propagating,
  131. // make sure to call its cancel method to avoid silencing other
  132. // exceptions that might be thrown later in the traversal.
  133. request.cancel = function () {
  134. visitor._abortRequested = false;
  135. };
  136. throw request;
  137. };
  138. PVp.reset = function (_path /*, additional arguments */) {
  139. // Empty stub; may be reassigned or overridden by subclasses.
  140. };
  141. PVp.visitWithoutReset = function (path) {
  142. if (this instanceof this.Context) {
  143. // Since this.Context.prototype === this, there's a chance we
  144. // might accidentally call context.visitWithoutReset. If that
  145. // happens, re-invoke the method against context.visitor.
  146. return this.visitor.visitWithoutReset(path);
  147. }
  148. if (!(path instanceof NodePath)) {
  149. throw new Error("");
  150. }
  151. var value = path.value;
  152. var methodName = value &&
  153. typeof value === "object" &&
  154. typeof value.type === "string" &&
  155. this._methodNameTable[value.type];
  156. if (methodName) {
  157. var context = this.acquireContext(path);
  158. try {
  159. return context.invokeVisitorMethod(methodName);
  160. }
  161. finally {
  162. this.releaseContext(context);
  163. }
  164. }
  165. else {
  166. // If there was no visitor method to call, visit the children of
  167. // this node generically.
  168. return visitChildren(path, this);
  169. }
  170. };
  171. function visitChildren(path, visitor) {
  172. if (!(path instanceof NodePath)) {
  173. throw new Error("");
  174. }
  175. if (!(visitor instanceof PathVisitor)) {
  176. throw new Error("");
  177. }
  178. var value = path.value;
  179. if (isArray.check(value)) {
  180. path.each(visitor.visitWithoutReset, visitor);
  181. }
  182. else if (!isObject.check(value)) {
  183. // No children to visit.
  184. }
  185. else {
  186. var childNames = types.getFieldNames(value);
  187. // The .comments field of the Node type is hidden, so we only
  188. // visit it if the visitor defines visitBlock or visitLine, and
  189. // value.comments is defined.
  190. if (visitor._shouldVisitComments &&
  191. value.comments &&
  192. childNames.indexOf("comments") < 0) {
  193. childNames.push("comments");
  194. }
  195. var childCount = childNames.length;
  196. var childPaths = [];
  197. for (var i = 0; i < childCount; ++i) {
  198. var childName = childNames[i];
  199. if (!hasOwn.call(value, childName)) {
  200. value[childName] = types.getFieldValue(value, childName);
  201. }
  202. childPaths.push(path.get(childName));
  203. }
  204. for (var i = 0; i < childCount; ++i) {
  205. visitor.visitWithoutReset(childPaths[i]);
  206. }
  207. }
  208. return path.value;
  209. }
  210. PVp.acquireContext = function (path) {
  211. if (this._reusableContextStack.length === 0) {
  212. return new this.Context(path);
  213. }
  214. return this._reusableContextStack.pop().reset(path);
  215. };
  216. PVp.releaseContext = function (context) {
  217. if (!(context instanceof this.Context)) {
  218. throw new Error("");
  219. }
  220. this._reusableContextStack.push(context);
  221. context.currentPath = null;
  222. };
  223. PVp.reportChanged = function () {
  224. this._changeReported = true;
  225. };
  226. PVp.wasChangeReported = function () {
  227. return this._changeReported;
  228. };
  229. function makeContextConstructor(visitor) {
  230. function Context(path) {
  231. if (!(this instanceof Context)) {
  232. throw new Error("");
  233. }
  234. if (!(this instanceof PathVisitor)) {
  235. throw new Error("");
  236. }
  237. if (!(path instanceof NodePath)) {
  238. throw new Error("");
  239. }
  240. Object.defineProperty(this, "visitor", {
  241. value: visitor,
  242. writable: false,
  243. enumerable: true,
  244. configurable: false
  245. });
  246. this.currentPath = path;
  247. this.needToCallTraverse = true;
  248. Object.seal(this);
  249. }
  250. if (!(visitor instanceof PathVisitor)) {
  251. throw new Error("");
  252. }
  253. // Note that the visitor object is the prototype of Context.prototype,
  254. // so all visitor methods are inherited by context objects.
  255. var Cp = Context.prototype = Object.create(visitor);
  256. Cp.constructor = Context;
  257. extend(Cp, sharedContextProtoMethods);
  258. return Context;
  259. }
  260. // Every PathVisitor has a different this.Context constructor and
  261. // this.Context.prototype object, but those prototypes can all use the
  262. // same reset, invokeVisitorMethod, and traverse function objects.
  263. var sharedContextProtoMethods = Object.create(null);
  264. sharedContextProtoMethods.reset =
  265. function reset(path) {
  266. if (!(this instanceof this.Context)) {
  267. throw new Error("");
  268. }
  269. if (!(path instanceof NodePath)) {
  270. throw new Error("");
  271. }
  272. this.currentPath = path;
  273. this.needToCallTraverse = true;
  274. return this;
  275. };
  276. sharedContextProtoMethods.invokeVisitorMethod =
  277. function invokeVisitorMethod(methodName) {
  278. if (!(this instanceof this.Context)) {
  279. throw new Error("");
  280. }
  281. if (!(this.currentPath instanceof NodePath)) {
  282. throw new Error("");
  283. }
  284. var result = this.visitor[methodName].call(this, this.currentPath);
  285. if (result === false) {
  286. // Visitor methods return false to indicate that they have handled
  287. // their own traversal needs, and we should not complain if
  288. // this.needToCallTraverse is still true.
  289. this.needToCallTraverse = false;
  290. }
  291. else if (result !== undefined) {
  292. // Any other non-undefined value returned from the visitor method
  293. // is interpreted as a replacement value.
  294. this.currentPath = this.currentPath.replace(result)[0];
  295. if (this.needToCallTraverse) {
  296. // If this.traverse still hasn't been called, visit the
  297. // children of the replacement node.
  298. this.traverse(this.currentPath);
  299. }
  300. }
  301. if (this.needToCallTraverse !== false) {
  302. throw new Error("Must either call this.traverse or return false in " + methodName);
  303. }
  304. var path = this.currentPath;
  305. return path && path.value;
  306. };
  307. sharedContextProtoMethods.traverse =
  308. function traverse(path, newVisitor) {
  309. if (!(this instanceof this.Context)) {
  310. throw new Error("");
  311. }
  312. if (!(path instanceof NodePath)) {
  313. throw new Error("");
  314. }
  315. if (!(this.currentPath instanceof NodePath)) {
  316. throw new Error("");
  317. }
  318. this.needToCallTraverse = false;
  319. return visitChildren(path, PathVisitor.fromMethodsObject(newVisitor || this.visitor));
  320. };
  321. sharedContextProtoMethods.visit =
  322. function visit(path, newVisitor) {
  323. if (!(this instanceof this.Context)) {
  324. throw new Error("");
  325. }
  326. if (!(path instanceof NodePath)) {
  327. throw new Error("");
  328. }
  329. if (!(this.currentPath instanceof NodePath)) {
  330. throw new Error("");
  331. }
  332. this.needToCallTraverse = false;
  333. return PathVisitor.fromMethodsObject(newVisitor || this.visitor).visitWithoutReset(path);
  334. };
  335. sharedContextProtoMethods.reportChanged = function reportChanged() {
  336. this.visitor.reportChanged();
  337. };
  338. sharedContextProtoMethods.abort = function abort() {
  339. this.needToCallTraverse = false;
  340. this.visitor.abort();
  341. };
  342. return PathVisitor;
  343. }
  344. exports.default = pathVisitorPlugin;
  345. module.exports = exports["default"];