4

Suppose I AST transform the content of a JavaScript file from state A to state B.

How might I make an accompanying sourcemap? I'm using esprima and estravese (estraverse.replace) to traverse an AST (I have the sourcemap corresponding to the initial AST) and transform it into another AST (but I don't have the resulting sourcemap).

How might I get that sourcemap?

EDIT: I'm using esprima and estraverse to do my AST transformation. My transformation looks like this:

module.exports = {

    type: 'replace', // or traverse

    enter(node, parent) {

        if (
            node.type == 'ExpressionStatement'
            && parent.type == 'Program'
            && node.expression.type == 'CallExpression'
            && node.expression.callee.name == 'module'
        ) {
            // rename `module` to `define`
            node.expression.callee.name = 'define'

            // The dependency object (the `{a:'./a', b:'./b'}` in `module({a:'./a', b:'./b'}, function(imports) {})`) will be...
            const dependenciesObjectExpression = node.expression.arguments[0]

            // ...converted into an array of paths (the `['./a', './b']` in `define(['./a', './b'], function(a,b) {})`), and...
            const dependencyPathLiterals =
                dependenciesObjectExpression.properties.map(prop => prop.value)

            // ...the dependency names will be converted into parameters of the module body function (the `a,b` in `define(['./a', './b'], function(a,b) {})`).
            const dependencyNameIdentifiers =
                dependenciesObjectExpression.properties.map(prop => prop.key)

            // set the new define call's arguments
            node.expression.arguments[0] = {
                type: 'ArrayExpression',
                elements: dependencyPathLiterals,
            }
            node.expression.arguments[1].params = dependencyNameIdentifiers

            return node
        }

        // if we see `imports.foo`, convert to `foo`
        if (
            node.type == 'MemberExpression'
            && node.object.type == 'Identifier'
            && node.object.name == 'imports'
        ) {
            return {
                type: 'Identifier',
                name: node.property.name,
            }
        }
    },

    leave(node, parent) {
        //
    }

}
trusktr
  • 44,284
  • 53
  • 191
  • 263

1 Answers1

3

For each tree transformation you write, you have write a corresponding transformation on the source map.

Because tree transforms are essentially arbitrary, the corresponding source transforms will be arbitrary, too. Similarly, complex tree transformations will cause correspondingly complex source map transforms.

One way to implement this might be co-opt the (I assume these exist) tree transformation operations DeleteNode, ReplaceNode, ReplaceChildWithIdentifier, ReplaceChildWithLiteral, ReplaceChildWithOperator. Using only these operations you should still be able to make arbitrary tree changes. By modifying these operatios to update the source map (each one does something very specific to the source map), you should get "for free" the updated sourcemap. Obviously you can't use other tree modifying operations unless they are implemented using these primitives.

A couple tools in the community for this purpose:

trusktr
  • 44,284
  • 53
  • 191
  • 263
Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • Hi Ira, thanks for this initial answer. I updated my question to show what my AST transformation looks like. Any suggestions based on that? – trusktr Jan 22 '17 at 19:42
  • I'll be reading that HTML5 article! I was hoping there might be an easy-to-use tool... – trusktr Jan 22 '17 at 20:10
  • You have to replace your node updates with calls on routines like the ones I suggested. Only by tapping in at the most primitive tree operations, can you implement a simple tracing mechanism to update the sourcemap. If you use the the (complex) calls you have, then the sourcemap changes will be very complex and you'll likely have to code them by hand. No easy outs. (Any "easy to use tool" must already be doing this; you want an easy to use tools that is already built inside esprima, and you already can't find that). – Ira Baxter Jan 22 '17 at 20:29
  • Check it out, looks like I found a tool that does just that! https://www.npmjs.com/package/ast-source It takes an AST transform and generates a source maps. Coincidentally, the first example in the readme shows how to do it with esprima/estraverse. Pretty cool! – trusktr Jan 22 '17 at 21:12
  • Found another amazing tool, by a well-known member of the JS community: https://github.com/benjamn/recast – trusktr Jan 22 '17 at 21:42
  • @trusktr: thanks for the edit, and the points :-} I'll be looking over these solutions to see what they actually do, since this is an interesting problem beyond just JavaScript. – Ira Baxter Jan 22 '17 at 23:24