README.md 6.96 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# babel-walk

Lightweight AST traversal tools for [Babel] ASTs.

[Babel] supplies the wonderful [babel-traverse] module for walking Babel 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.

This module loosely implements the API of Acorn parser's [walk module], which is a lightweight AST walker for the ESTree AST format.

In my tests, babel-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.

[![Build Status](https://img.shields.io/github/workflow/status/pugjs/babel-walk/Publish%20Canary/master?style=for-the-badge)](https://github.com/pugjs/babel-walk/actions?query=workflow%3A%22Publish+Canary%22)
[![Rolling Versions](https://img.shields.io/badge/Rolling%20Versions-Enabled-brightgreen?style=for-the-badge)](https://rollingversions.com/pugjs/babel-walk)
[![NPM version](https://img.shields.io/npm/v/babel-walk?style=for-the-badge)](https://www.npmjs.com/package/babel-walk)

[babel]: https://babeljs.io/
[babel-traverse]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-babel-traverse
[walk module]: https://github.com/ternjs/acorn#distwalkjs

## Installation

```sh
$ npm install babel-walk
```

## API

```js
var walk = require('babel-walk');
```

### walk.simple(visitors)(node, state)

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`.

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 the result of calling `walk.simple(visitors)` and communicate state leveraging the `state` parameter.

All [babel-types] aliases (e.g. `Expression`) work, but the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) does not.

### walk.ancestor(visitors)(node, state)

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`.

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 the result of calling `walk.ancestor(visitors)` and communicate state leveraging the `state` parameter.

All [babel-types] aliases (e.g. `Expression`) work, but the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) does not.

### walk.recursive(visitors)(node, state)

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.

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 the result of calling `walk.recursive(visitors)` and communicate state leveraging the `state` parameter.

Unlike other babel-walk walkers, `walk.recursive` does not call the `exit` visitor, only the `enter` (the default) visitor, of a specific node type.

All [babel-types] aliases (e.g. `Expression`) work, but the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) does not.

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.

```js
import * as t from 'babel-types';
import {parse} from 'babel';
import * as walk from 'babel-walk';

const visitors = walk.recursive({
  Statement(node, state, c) {
    if (t.isVariableDeclaration(node)) {
      for (let declarator of node.declarations) {
        // Continue walking the declarator
        c(declarator);
      }
    } else if (t.isFunctionDeclaration(node)) {
      state.counter++;
    }
  },

  VariableDeclarator(node, state) {
    if (t.isFunction(node.init)) {
      state.counter++;
    }
  },
});

function countFunctions(node) {
  const state = {
    counter: 0,
  };
  visitors(node, state);
  return state.counter;
}

const ast = parse(`
  // Counts
  var a = () => {};

  // Counts
  function b() {
    // Doesn't count
    function c() {
    }
  }

  // Counts
  const c = function d() {};
`);

countFunctions(ast);
// = 3
```

[babel-types]: https://github.com/babel/babel/tree/master/packages/babel-types
[cache your visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-optimizing-nested-visitors
[visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-visitors

## Caveat

For those of you migrating from Acorn to Babel, there are a few things to be aware of.

1. The visitor caching suggestions do not apply to Acorn's walk module, but do for babel-walk.

2. babel-walk does not provide any of the other functions Acorn's walk module provides (e.g. `make`, `findNode*`).

3. babel-walk does not use a `base` variable. The walker algorithm is the same as what babel-traverse uses.
   - That means certain nodes that are not walked by Acorn, such as the `property` property of a non-computed `MemberExpression`, are walked by babel-walk.

## License

MIT