Source: src/walker.js

  1. /**
  2. * Traversal utilities for ASTs that are compatible with the Mozilla Parser API. Adapted from
  3. * [Acorn](http://marijnhaverbeke.nl/acorn/).
  4. *
  5. * @module jsdoc/src/walker
  6. * @license MIT
  7. */
  8. 'use strict';
  9. var astnode = require('jsdoc/src/astnode');
  10. var doclet = require('jsdoc/doclet');
  11. var Syntax = require('jsdoc/src/syntax').Syntax;
  12. /**
  13. * Check whether an AST node creates a new scope.
  14. *
  15. * @private
  16. * @param {Object} node - The AST node to check.
  17. * @return {Boolean} Set to `true` if the node creates a new scope, or `false` in all other cases.
  18. */
  19. function isScopeNode(node) {
  20. // TODO: handle blocks with "let" declarations
  21. return node && typeof node === 'object' && (node.type === Syntax.CatchClause ||
  22. node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression ||
  23. node.type === Syntax.ArrowFunctionExpression);
  24. }
  25. // TODO: docs
  26. function getCurrentScope(scopes) {
  27. return scopes[scopes.length - 1] || null;
  28. }
  29. // TODO: docs
  30. function moveComments(source, target) {
  31. if (source.leadingComments) {
  32. target.leadingComments = source.leadingComments.slice(0);
  33. source.leadingComments = [];
  34. }
  35. }
  36. function leafNode(node, parent, state, cb) {}
  37. // TODO: docs
  38. var walkers = exports.walkers = {};
  39. walkers[Syntax.ArrayExpression] = function(node, parent, state, cb) {
  40. for (var i = 0, l = node.elements.length; i < l; i++) {
  41. var e = node.elements[i];
  42. if (e) {
  43. cb(e, node, state);
  44. }
  45. }
  46. };
  47. // TODO: verify correctness
  48. walkers[Syntax.ArrayPattern] = function(node, parent, state, cb) {
  49. for (var i = 0, l = node.elements.length; i < l; i++) {
  50. var e = node.elements[i];
  51. // must be an identifier or an expression
  52. if (e && e.type !== Syntax.Identifier) {
  53. cb(e, node, state);
  54. }
  55. }
  56. };
  57. walkers[Syntax.ArrowFunctionExpression] = function(node, parent, state, cb) {
  58. var i;
  59. var l;
  60. if (node.id) {
  61. cb(node.id, node, state);
  62. }
  63. for (i = 0, l = node.params.length; i < l; i++) {
  64. cb(node.params[i], node, state);
  65. }
  66. cb(node.body, node, state);
  67. };
  68. walkers[Syntax.AssignmentExpression] = function(node, parent, state, cb) {
  69. cb(node.left, node, state);
  70. cb(node.right, node, state);
  71. };
  72. walkers[Syntax.AssignmentPattern] = walkers[Syntax.AssignmentExpression];
  73. walkers[Syntax.BinaryExpression] = function(node, parent, state, cb) {
  74. cb(node.left, node, state);
  75. cb(node.right, node, state);
  76. };
  77. walkers[Syntax.BlockStatement] = function(node, parent, state, cb) {
  78. for (var i = 0, l = node.body.length; i < l; i++) {
  79. cb(node.body[i], node, state);
  80. }
  81. };
  82. walkers[Syntax.BreakStatement] = leafNode;
  83. walkers[Syntax.CallExpression] = function(node, parent, state, cb) {
  84. var i;
  85. var l;
  86. cb(node.callee, node, state);
  87. if (node.arguments) {
  88. for (i = 0, l = node.arguments.length; i < l; i++) {
  89. cb(node.arguments[i], node, state);
  90. }
  91. }
  92. };
  93. walkers[Syntax.CatchClause] = leafNode;
  94. walkers[Syntax.ClassBody] = walkers[Syntax.BlockStatement];
  95. walkers[Syntax.ClassDeclaration] = function(node, parent, state, cb) {
  96. if (node.id) {
  97. cb(node.id, node, state);
  98. }
  99. if (node.superClass) {
  100. cb(node.superClass, node, state);
  101. }
  102. if (node.body) {
  103. cb(node.body, node, state);
  104. }
  105. };
  106. walkers[Syntax.ClassExpression] = walkers[Syntax.ClassDeclaration];
  107. // TODO: verify correctness
  108. walkers[Syntax.ComprehensionBlock] = walkers[Syntax.AssignmentExpression];
  109. // TODO: verify correctness
  110. walkers[Syntax.ComprehensionExpression] = function(node, parent, state, cb) {
  111. cb(node.body, node, state);
  112. if (node.filter) {
  113. cb(node.filter, node, state);
  114. }
  115. for (var i = 0, l = node.blocks.length; i < l; i++) {
  116. cb(node.blocks[i], node, state);
  117. }
  118. };
  119. walkers[Syntax.ConditionalExpression] = function(node, parent, state, cb) {
  120. cb(node.test, node, state);
  121. cb(node.consequent, node, state);
  122. cb(node.alternate, node, state);
  123. };
  124. walkers[Syntax.ContinueStatement] = leafNode;
  125. walkers[Syntax.DebuggerStatement] = leafNode;
  126. walkers[Syntax.DoWhileStatement] = function(node, parent, state, cb) {
  127. cb(node.test, node, state);
  128. cb(node.body, node, state);
  129. };
  130. walkers[Syntax.EmptyStatement] = leafNode;
  131. walkers[Syntax.ExperimentalRestProperty] = function(node, parent, state, cb) {
  132. cb(node.argument);
  133. };
  134. walkers[Syntax.ExperimentalSpreadProperty] = walkers[Syntax.ExperimentalRestProperty];
  135. walkers[Syntax.ExportAllDeclaration] = function(node, parent, state, cb) {
  136. if (node.source) {
  137. cb(node.source, node, state);
  138. }
  139. };
  140. walkers[Syntax.ExportDefaultDeclaration] = function(node, parent, state, cb) {
  141. if (node.declaration) {
  142. cb(node.declaration, node, state);
  143. }
  144. };
  145. walkers[Syntax.ExportNamedDeclaration] = function(node, parent, state, cb) {
  146. var i;
  147. var l;
  148. if (node.declaration) {
  149. cb(node.declaration, node, state);
  150. }
  151. for (i = 0, l = node.specifiers.length; i < l; i++) {
  152. cb(node.specifiers[i], node, state);
  153. }
  154. if (node.source) {
  155. cb(node.source, node, state);
  156. }
  157. };
  158. walkers[Syntax.ExportSpecifier] = function(node, parent, state, cb) {
  159. if (node.exported) {
  160. cb(node.exported, node, state);
  161. }
  162. if (node.local) {
  163. cb(node.local, node, state);
  164. }
  165. };
  166. walkers[Syntax.ExpressionStatement] = function(node, parent, state, cb) {
  167. cb(node.expression, node, state);
  168. };
  169. walkers[Syntax.ForInStatement] = function(node, parent, state, cb) {
  170. cb(node.left, node, state);
  171. cb(node.right, node, state);
  172. cb(node.body, node, state);
  173. };
  174. walkers[Syntax.ForOfStatement] = walkers[Syntax.ForInStatement];
  175. walkers[Syntax.ForStatement] = function(node, parent, state, cb) {
  176. if (node.init) {
  177. cb(node.init, node, state);
  178. }
  179. if (node.test) {
  180. cb(node.test, node, state);
  181. }
  182. if (node.update) {
  183. cb(node.update, node, state);
  184. }
  185. cb(node.body, node, state);
  186. };
  187. walkers[Syntax.FunctionDeclaration] = walkers[Syntax.ArrowFunctionExpression];
  188. walkers[Syntax.FunctionExpression] = walkers[Syntax.ArrowFunctionExpression];
  189. walkers[Syntax.Identifier] = leafNode;
  190. walkers[Syntax.IfStatement] = function(node, parent, state, cb) {
  191. cb(node.test, node, state);
  192. cb(node.consequent, node, state);
  193. if (node.alternate) {
  194. cb(node.alternate, node, state);
  195. }
  196. };
  197. walkers[Syntax.ImportDeclaration] = function(node, parent, state, cb) {
  198. if (node.specifiers) {
  199. for (var i = 0, l = node.specifiers.length; i < l; i++) {
  200. cb(node.specifiers[i], node, state);
  201. }
  202. }
  203. if (node.source) {
  204. cb(node.source, node, state);
  205. }
  206. };
  207. walkers[Syntax.ImportDefaultSpecifier] = function(node, parent, state, cb) {
  208. if (node.local) {
  209. cb(node.local, node, state);
  210. }
  211. };
  212. walkers[Syntax.ImportNamespaceSpecifier] = walkers[Syntax.ImportDefaultSpecifier];
  213. walkers[Syntax.ImportSpecifier] = walkers[Syntax.ExportSpecifier];
  214. walkers[Syntax.JSXAttribute] = function(node, parent, state, cb) {
  215. cb(node.name, node, state);
  216. cb(node.value, node, state);
  217. };
  218. walkers[Syntax.JSXClosingElement] = function(node, parent, state, cb) {
  219. cb(node.name, node, state);
  220. };
  221. walkers[Syntax.JSXElement] = function(node, parent, state, cb) {
  222. cb(node.openingElement, node, state);
  223. if (node.closingElement) {
  224. cb(node.closingElement, node, state);
  225. }
  226. for (var i = 0, l = node.children.length; i < l; i++) {
  227. cb(node.children[i], node, state);
  228. }
  229. };
  230. walkers[Syntax.JSXEmptyExpression] = leafNode;
  231. walkers[Syntax.JSXExpressionContainer] = function(node, parent, state, cb) {
  232. cb(node.expression, node, state);
  233. };
  234. walkers[Syntax.JSXIdentifier] = leafNode;
  235. walkers[Syntax.JSXMemberExpression] = function(node, parent, state, cb) {
  236. cb(node.object, node, state);
  237. cb(node.property, node, state);
  238. };
  239. walkers[Syntax.JSXNamespacedName] = function(node, parent, state, cb) {
  240. cb(node.namespace, node, state);
  241. cb(node.name, node, state);
  242. };
  243. walkers[Syntax.JSXOpeningElement] = function(node, parent, state, cb) {
  244. cb(node.name, node, state);
  245. for (var i = 0, l = node.attributes.length; i < l; i++) {
  246. cb(node.attributes[i], node, state);
  247. }
  248. };
  249. walkers[Syntax.JSXSpreadAttribute] = function(node, parent, state, cb) {
  250. cb(node.argument, node, state);
  251. };
  252. walkers[Syntax.JSXText] = leafNode;
  253. walkers[Syntax.LabeledStatement] = function(node, parent, state, cb) {
  254. cb(node.body, node, state);
  255. };
  256. // TODO: add scope info??
  257. walkers[Syntax.LetStatement] = function(node, parent, state, cb) {
  258. for (var i = 0, l = node.head.length; i < l; i++) {
  259. var head = node.head[i];
  260. cb(head.id, node, state);
  261. if (head.init) {
  262. cb(head.init, node, state);
  263. }
  264. }
  265. cb(node.body, node, state);
  266. };
  267. walkers[Syntax.Literal] = leafNode;
  268. walkers[Syntax.LogicalExpression] = walkers[Syntax.BinaryExpression];
  269. walkers[Syntax.MemberExpression] = function(node, parent, state, cb) {
  270. cb(node.object, node, state);
  271. if (node.property) {
  272. cb(node.property, node, state);
  273. }
  274. };
  275. walkers[Syntax.MetaProperty] = leafNode;
  276. walkers[Syntax.MethodDefinition] = function(node, parent, state, cb) {
  277. if (node.key) {
  278. cb(node.key, node, state);
  279. }
  280. if (node.value) {
  281. cb(node.value, node, state);
  282. }
  283. };
  284. walkers[Syntax.ModuleDeclaration] = function(node, parent, state, cb) {
  285. if (node.id) {
  286. cb(node.id, node, state);
  287. }
  288. if (node.source) {
  289. cb(node.source, node, state);
  290. }
  291. if (node.body) {
  292. cb(node.body, node, state);
  293. }
  294. };
  295. walkers[Syntax.NewExpression] = walkers[Syntax.CallExpression];
  296. walkers[Syntax.ObjectExpression] = function(node, parent, state, cb) {
  297. for (var i = 0, l = node.properties.length; i < l; i++) {
  298. cb(node.properties[i], node, state);
  299. }
  300. };
  301. walkers[Syntax.ObjectPattern] = walkers[Syntax.ObjectExpression];
  302. walkers[Syntax.Program] = walkers[Syntax.BlockStatement];
  303. walkers[Syntax.Property] = function(node, parent, state, cb) {
  304. // move leading comments from key to property node
  305. moveComments(node.key, node);
  306. cb(node.value, node, state);
  307. };
  308. walkers[Syntax.RestElement] = function(node, parent, state, cb) {
  309. if (node.argument) {
  310. cb(node.argument, node, state);
  311. }
  312. };
  313. walkers[Syntax.ReturnStatement] = function(node, parent, state, cb) {
  314. if (node.argument) {
  315. cb(node.argument, node, state);
  316. }
  317. };
  318. walkers[Syntax.SequenceExpression] = function(node, parent, state, cb) {
  319. for (var i = 0, l = node.expressions.length; i < l; i++) {
  320. cb(node.expressions[i], node, state);
  321. }
  322. };
  323. walkers[Syntax.SpreadElement] = function(node, parent, state, cb) {
  324. if (node.argument) {
  325. cb(node.argument, node, state);
  326. }
  327. };
  328. walkers[Syntax.Super] = leafNode;
  329. walkers[Syntax.SwitchCase] = function(node, parent, state, cb) {
  330. if (node.test) {
  331. cb(node.test, node, state);
  332. }
  333. for (var i = 0, l = node.consequent.length; i < l; i++) {
  334. cb(node.consequent[i], node, state);
  335. }
  336. };
  337. walkers[Syntax.SwitchStatement] = function(node, parent, state, cb) {
  338. cb(node.discriminant, node, state);
  339. for (var i = 0, l = node.cases.length; i < l; i++) {
  340. cb(node.cases[i], node, state);
  341. }
  342. };
  343. walkers[Syntax.TaggedTemplateExpression] = function(node, parent, state, cb) {
  344. if (node.tag) {
  345. cb(node.tag, node, state);
  346. }
  347. if (node.quasi) {
  348. cb(node.quasi, node, state);
  349. }
  350. };
  351. walkers[Syntax.TemplateElement] = leafNode;
  352. walkers[Syntax.TemplateLiteral] = function(node, parent, state, cb) {
  353. var i;
  354. var l;
  355. if (node.quasis && node.quasis.length) {
  356. for (i = 0, l = node.quasis.length; i < l; i++) {
  357. cb(node.quasis[i], node, state);
  358. }
  359. }
  360. if (node.expressions && node.expressions.length) {
  361. for (i = 0, l = node.expressions.length; i < l; i++) {
  362. cb(node.expressions[i], node, state);
  363. }
  364. }
  365. };
  366. walkers[Syntax.ThisExpression] = leafNode;
  367. walkers[Syntax.ThrowStatement] = function(node, parent, state, cb) {
  368. cb(node.argument, node, state);
  369. };
  370. walkers[Syntax.TryStatement] = function(node, parent, state, cb) {
  371. var i;
  372. var l;
  373. cb(node.block, node, state);
  374. if (node.handler) {
  375. cb(node.handler.body, node, state);
  376. }
  377. if (node.finalizer) {
  378. cb(node.finalizer, node, state);
  379. }
  380. };
  381. walkers[Syntax.UnaryExpression] = function(node, parent, state, cb) {
  382. cb(node.argument, node, state);
  383. };
  384. walkers[Syntax.UpdateExpression] = walkers[Syntax.UnaryExpression];
  385. walkers[Syntax.VariableDeclaration] = function(node, parent, state, cb) {
  386. // move leading comments to first declarator
  387. moveComments(node, node.declarations[0]);
  388. for (var i = 0, l = node.declarations.length; i < l; i++) {
  389. cb(node.declarations[i], node, state);
  390. }
  391. };
  392. walkers[Syntax.VariableDeclarator] = function(node, parent, state, cb) {
  393. cb(node.id, node, state);
  394. if (node.init) {
  395. cb(node.init, node, state);
  396. }
  397. };
  398. walkers[Syntax.WhileStatement] = walkers[Syntax.DoWhileStatement];
  399. walkers[Syntax.WithStatement] = function(node, parent, state, cb) {
  400. cb(node.object, node, state);
  401. cb(node.body, node, state);
  402. };
  403. walkers[Syntax.YieldExpression] = function(node, parent, state, cb) {
  404. if (node.argument) {
  405. cb(node.argument, node, state);
  406. }
  407. };
  408. /**
  409. * Create a walker that can traverse an AST that is consistent with the Mozilla Parser API.
  410. *
  411. * @todo docs
  412. * @memberof module:jsdoc/src/walker
  413. */
  414. var Walker = exports.Walker = function(walkerFuncs) {
  415. this._walkers = walkerFuncs || walkers;
  416. };
  417. // TODO: docs
  418. Walker.prototype._recurse = function(filename, ast) {
  419. var self = this;
  420. var state = {
  421. filename: filename,
  422. nodes: [],
  423. scopes: []
  424. };
  425. function cb(node, parent, cbState) {
  426. var currentScope;
  427. var isScope = astnode.isScope(node);
  428. astnode.addNodeProperties(node);
  429. node.parent = parent || null;
  430. currentScope = getCurrentScope(cbState.scopes);
  431. if (currentScope) {
  432. node.enclosingScope = currentScope;
  433. }
  434. if (isScope) {
  435. cbState.scopes.push(node);
  436. }
  437. cbState.nodes.push(node);
  438. self._walkers[node.type](node, parent, cbState, cb);
  439. if (isScope) {
  440. cbState.scopes.pop();
  441. }
  442. }
  443. cb(ast, null, state);
  444. return state;
  445. };
  446. // TODO: docs
  447. // TODO: skip the AST root node to be consistent with Rhino?
  448. Walker.prototype.recurse = function(ast, visitor, filename) {
  449. var shouldContinue;
  450. var state = this._recurse(filename, ast);
  451. if (visitor) {
  452. for (var i = 0, l = state.nodes.length; i < l; i++) {
  453. shouldContinue = visitor.visit.call(visitor, state.nodes[i], filename);
  454. if (!shouldContinue) {
  455. break;
  456. }
  457. }
  458. }
  459. return ast;
  460. };