1 # Rocambole [](https://travis-ci.org/millermedeiros/rocambole)
3 
5 Recursively walk and add extra information/helpers to [Esprima / Mozilla
6 SpiderMonkey Parser API](http://esprima.org/doc/index.html#ast) compatible AST.
8 The main difference between other tools is that it also keeps information about
9 tokens and white spaces and it is meant to be used to transform the tokens and
10 not the string values itself.
12 This library is specially useful for non-destructive AST manipulation.
17 This module was heavily inspired by
18 [node-falafel](https://github.com/substack/node-falafel) and
19 [node-burrito](https://github.com/substack/node-burrito) but I needed more
20 information than what is currently available on falafel (specially about
21 tokens, empty lines and white spaces) and also needed to do the node traversing
22 on the opposite order (start from leaf nodes). The amount of changes required
23 to introduce the new features and the differences on the concept behind the
24 tool justified a new project.
26 It was created mainly to be used on
27 [esformatter](https://github.com/millermedeiros/esformatter/).
33 Besides all the regular tokens returned by `esprima` we also add a few more
34 that are important for non-destructive transformations:
37 - Can store multiple white spaces (tabs are considered white space, line
38 breaks not). Important if you want to do non-destructive replacements that
39 are white-space sensitive.
40 - Multiple subsequent white spaces are treated as a single token.
45 It's way easier to rebuild the JS string if the tokens already have line breaks
46 and comments. It's also easier to identify if previous/next/current token is a
47 LineBreak or Comment (sometimes needed for non-destructive transformations).
49 Rocambole structure might change in the future to keep the extraneous tokens
50 outside the `tokens` array and also add an option to toggle the behavior.
51 ([issue #7](https://github.com/millermedeiros/rocambole/issues/7))
56 Each Node have the following extra properties/methods:
58 - `parent` : Node|undefined
59 - `toString()` : string
60 - `next` : Node|undefined
61 - `prev` : Node|undefined
63 - `startToken` : Token
68 - `prev` : Token|undefined
69 - `next` : Token|undefined
71 BlockComment also have:
73 - `originalIndent`: String|undefined
75 To get a better idea of the generated AST structure try out
76 [rocambole-visualize](http://piuccio.github.io/rocambole-visualize/).
81 You should **treat the tokens as a linked list instead of reading the
82 `ast.tokens` array** (inserting/removing items from a linked list is very cheap
83 and won't break the loop). You should grab a reference to the `node.startToken`
84 and get `token.next` until you find the desired token or reach the end of the
85 program. To loop between all tokens inside a node you can do like this:
88 var token = node.startToken;
89 while (token !== node.endToken.next) {
90 doStuffWithToken(token);
95 The method `toString` loops through all tokens between `node.startToken` and
96 `node.endToken` grabbing the `token.raw` (used by comments) or `token.value`
97 properties. To implement a method similar to falafel `update()` you can do
101 function update(node, str){
103 type : 'Custom', // can be anything (not used internally)
106 // update linked list references
107 if ( node.startToken.prev ) {
108 node.startToken.prev.next = newToken;
109 newToken.prev = node.startToken.prev;
111 if ( node.endToken.next ) {
112 node.endToken.next.prev = newToken;
113 newToken.next = node.endToken.next;
115 node.startToken = node.endToken = newToken;
122 I plan to create helpers as separate projects. For now I'm adding the helpers
123 on the [esformatter util
124 package](https://github.com/millermedeiros/esformatter/tree/master/lib/util)
125 but I plan to extract the generic ones.
127 - [rocambole-token](https://github.com/millermedeiros/rocambole-token): helpers for token manipulation
136 Parses a string and instrument the AST with extra properties/methods.
139 var rocambole = require('rocambole');
140 var ast = rocambole.parse(string);
141 console.log( ast.startToken );
142 // to get a string representation of all tokens call toString()
143 console.log( ast.toString() );
147 ### rocambole.moonwalk
149 The `moonwalk()` starts at the leaf nodes and go down the tree until it reaches
150 the root node (`Program`). Each node will be traversed only once.
153 rocambole.moonwalk(ast, function(node){
154 if (node.type == 'ArrayExpression'){
155 console.log( node.depth +': '+ node.toString() );
164 `-FunctionDeclaration [#16]
165 |-BlockStatement [#14]
166 | |-IfStatement [#12]
167 | | |-BynaryExpression [#9]
168 | | | |-Identifier [#4]
170 | | `-BlockStatement [#10]
171 | | `-ExpressionStatement [#6]
172 | | `-AssignmentExpression [#3]
173 | | |-Identifier [#1 walk starts here]
175 | `-VariableDeclaration [#13]
176 | `-VariableDeclarator [#11]
179 `-ReturnStatement [#17]
183 This behavior is very different from node-falafel and node-burrito.
186 ### rocambole.recursive
188 It loops through all nodes on the AST starting from the root node (`Program`),
189 similar to `node-falafel`.
192 rocambole.recursive(ast, function(node){
193 console.log(node.type);
198 ## Popular Alternatives
200 - [burrito](https://github.com/substack/node-burrito)
201 - [falafel](https://github.com/substack/node-falafel)
207 Besides the regular unit tests we also use
208 [istanbul](https://github.com/yahoo/istanbul) to generate code coverage
209 reports, tests should have at least 95% code coverage for statements, branches
210 and lines and 100% code coverage for functions or travis build will fail.
212 We do not run the coverage test at each call since it slows down the
213 performnace of the tests and it also makes it harder to see the test results.
214 To execute tests and generate coverage report call `npm test --coverage`, for
215 regular tests just do `npm test`.
217 Coverage reports are not committed to the repository since they will change at
218 each `npm test --coverage` call.
230 ### v0.3.6 (2014/06/23)
232 - really handle sparse arrays (eg. `[,]`), fixes moonwalk. (#15)
234 ### v0.3.5 (2014/06/23)
236 - handle sparse arrays (eg. `[,]`). (#15)
238 ### v0.3.4 (2014/06/23)
240 - only add `BlockComment.originalIndent` if `WhiteSpace` is on the start of
243 ### v0.3.3 (2014/04/26)
245 - add `toString` to empty programs AST (#16)
247 ### v0.3.2 (2014/01/17)
249 - exports `BYPASS_RECURSION` (#8)
250 - fix error if input is empty (#12)
251 - support anything that implements `toString()` as input (#13)
253 ### v0.3.1 (2013/12/15)
255 - fix `originalIndent` on `BlockComment` when prev token is not `WhiteSpace`.
257 ### v0.3.0 (2013/12/15)
259 - add `originalIndent` to `BlockComment` (#11)
261 ### v0.2.3 (2013/01/08)
263 - improve `rocambole.parse()` performance by 4500%. (#4)
264 - improve `rocambole.moonwalk()` performance by 11000%.
266 ### v0.2.2 (2012/12/19)
268 - fix consecutive comments before start of program. (#3)
270 ### v0.2.1 (2012/12/13)
272 - fix `loc` info on `WhiteSpace` and `LineBreak` tokens. (#2)
274 ### v0.2.0 (2012/12/09)
282 - avoid recursion over comments.
283 - fix weird bug on esformatter introduced on v0.1.1 related to `token._ast`
286 ### v0.1.1 (2012/12/08)
288 - Improve token manipulation methods behavior (`before`, `after`, `remove`)
290 ### v0.1.0 (2012/12/06)