1 | # babylon-walk
|
2 |
|
3 | Lightweight AST traversal tools for [Babylon] ASTs.
|
4 |
|
5 | Babylon is the parser used by the [Babel] project, which supplies the wonderful [babel-traverse] module for walking Babylon ASTs. Problem is, babel-traverse is very heavyweight, as it is designed to supply utilities to make all sorts of AST transformations possible. For simple AST walking without transformation, babel-traverse brings a lot of overhead.
|
6 |
|
7 | This module loosely implements the API of Acorn parser's [walk module], which is a lightweight AST walker for the ESTree AST format.
|
8 |
|
9 | In my tests, babylon-walk's ancestor walker (the most complex walker provided by this module) is about 8 times faster than babel-traverse, if the visitors are cached and the same AST is used for all runs. It is about 16 times faster if a fresh AST is used every run.
|
10 |
|
11 | [![Dependency Status](https://img.shields.io/david/pugjs/babylon-walk.svg)](https://david-dm.org/pugjs/babylon-walk)
|
12 | [![NPM version](https://img.shields.io/npm/v/babylon-walk.svg)](https://www.npmjs.com/package/babylon-walk)
|
13 |
|
14 | [Babylon]: https://github.com/babel/babylon
|
15 | [Babel]: https://babeljs.io/
|
16 | [babel-traverse]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-babel-traverse
|
17 | [walk module]: https://github.com/ternjs/acorn#distwalkjs
|
18 |
|
19 | ## Installation
|
20 |
|
21 | ```sh
|
22 | $ npm install babylon-walk
|
23 | ```
|
24 |
|
25 | ## API
|
26 |
|
27 | ```js
|
28 | var walk = require('babylon-walk');
|
29 | ```
|
30 |
|
31 | ### walk.simple(node, visitors, state)
|
32 |
|
33 | Do a simple walk over the AST. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state)`, where `node` is the AST node, and `state` is the same `state` passed to `walk.simple`.
|
34 |
|
35 | When `walk.simple` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.)
|
36 |
|
37 | All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work.
|
38 |
|
39 | ### walk.ancestor(node, visitors, state)
|
40 |
|
41 | Do a simple walk over the AST, but memoizing the ancestors of the node and making them available to the visitors. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, ancestors)`, where `node` is the AST node, `state` is the same `state` passed to `walk.ancestor`, and `ancestors` is an array of ancestors to the node (with the outermost node being `[0]` and the current node being `[ancestors.length - 1]`). If `state` is not specified in the call to `walk.ancestor`, the `state` parameter will be set to `ancestors`.
|
42 |
|
43 | When `walk.ancestor` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.)
|
44 |
|
45 | All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work.
|
46 |
|
47 | ### walk.recursive(node, visitors, state)
|
48 |
|
49 | Do a recursive walk over the AST, where the visitors are responsible for continuing the walk on the child nodes of their target node. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, c)`, where `node` is the AST node, `state` is the same `state` passed to `walk.recursive`, and `c` is a function that takes a single node as argument and continues walking _that_ node. If no visitor for a node is provided, the default walker algorithm will still be used.
|
50 |
|
51 | When `walk.recursive` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.)
|
52 |
|
53 | Unlike other babylon-walk walkers, `walk.recursive` does not call the `exit` visitor, only the `enter` (the default) visitor, of a specific node type.
|
54 |
|
55 | All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work.
|
56 |
|
57 | In the following example, we are trying to count the number of functions in the outermost scope. This means, that we can simply walk all the statements and increment a counter if it is a function declaration or expression, and then stop walking. Note that we do not specify a visitor for the `Program` node, and the default algorithm for walking `Program` nodes is used (which is what we want). Also of note is how I bring the `visitors` object outside of `countFunctions` so that the object can be cached to improve performance.
|
58 |
|
59 | ```js
|
60 | import * as t from 'babel-types';
|
61 | import {parse} from 'babylon';
|
62 |
|
63 | const visitors = {
|
64 | Statement(node, state, c) {
|
65 | if (t.isVariableDeclaration(node)) {
|
66 | for (let declarator of node.declarations) {
|
67 | // Continue walking the declarator
|
68 | c(declarator);
|
69 | }
|
70 | } else if (t.isFunctionDeclaration(node)) {
|
71 | state.counter++;
|
72 | }
|
73 | },
|
74 |
|
75 | VariableDeclarator(node, state) {
|
76 | if (t.isFunction(node.init)) {
|
77 | state.counter++;
|
78 | }
|
79 | }
|
80 | };
|
81 |
|
82 | function countFunctions(node) {
|
83 | const state = {
|
84 | counter: 0
|
85 | };
|
86 | walk.recursive(node, visitors, state);
|
87 | return state.counter;
|
88 | }
|
89 |
|
90 | const ast = parse(`
|
91 | // Counts
|
92 | var a = () => {};
|
93 |
|
94 | // Counts
|
95 | function b() {
|
96 | // Doesn't count
|
97 | function c() {
|
98 | }
|
99 | }
|
100 |
|
101 | // Counts
|
102 | const c = function d() {};
|
103 | `);
|
104 |
|
105 | countFunctions(ast);
|
106 | // = 3
|
107 | ```
|
108 |
|
109 | [babel-types]: https://github.com/babel/babel/tree/master/packages/babel-types
|
110 | [cache your visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-optimizing-nested-visitors
|
111 | [visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-visitors
|
112 |
|
113 | ## Caveat
|
114 |
|
115 | For those of you migrating from Acorn to Babylon, there are a few things to be aware of.
|
116 |
|
117 | 1. The visitor caching suggestions do not apply to Acorn's walk module, but do for babylon-walk.
|
118 |
|
119 | 2. babylon-walk does not provide any of the other functions Acorn's walk module provides (e.g. `make`, `findNode*`).
|
120 |
|
121 | 3. babylon-walk does not use a `base` variable. The walker algorithm is the same as what babel-traverse uses.
|
122 | - That means certain nodes that are not walked by Acorn, such as the `property` property of a non-computed `MemberExpression`, are walked by babylon-walk.
|
123 |
|
124 | ## License
|
125 |
|
126 | MIT
|