title: manipulating JavaScript AST without compromising source maps
body: <p>I've been playing with a Rollup plugin to abuse JSX for code generation (I'm aware of <a href="https://www.sweetjs.org" rel="nofollow noreferrer">Sweet.js macros</a>; this is mostly an educational effort), but struggling to find guidance on AST manipulation. After some research, I have cobbled together an approach that sort of works, but seems problematic in various ways, so I wonder what the best practices are here.</p>
  
  <p>Rollup provides an ESTree via Acorn to plugins, so it appears <a href="https://github.com/estools/estraverse" rel="nofollow noreferrer">estraverse</a> is the standard approach here. Let's say I just wanted to turn <code>&lt;div&gt;</code> into <code>"&lt;div&gt;"</code> (<code>rewriteNode</code> is generic AST stuff, the rest is Rollup-specific):</p>
  
  <pre><code>import escodegen from "escodegen"
  import estraverse from "estraverse"
  
  function rewriteNode(node, parent) {
      if(node.type !== "JSXElement") {
          return
      }
  
      const tagName = node.openingElement.name.name
      return {
          type: "Literal",
          value: `&lt;${tagName}&gt;`
      }
  }
  
  function transformModule(code, id) {
      if(!id.endsWith(".jsx")) { // ¯\_(ツ)_/¯
          return null
      }
  
      const ast = this.parse(code)
      ast = estraverse.replace(ast, {
          keys: {
              JSXElement: [] // ¯\_(ツ)_/¯
          },
          enter: rewriteNode
      })
  
      return {
          ast,
          code: escodegen.generate(ast)
      }
  }
  
  export default {
      name: "jsx-macros",
      transform: transformModule
  }
  </code></pre>
  
  <p>There are several related issues here:</p>
  
  <ul>
  <li>Returning a plain <code>{ type, value }</code> object seems insufficient: Proper <code>Node</code> instances also contain <code>start</code> and <code>end</code> properties, among others. I assume these are required to generate proper source maps?</li>
  <li>Instead of using <code>estraverse.replace</code> and returning a new node, I might use <a href="https://github.com/acornjs/acorn/tree/master/acorn-walk" rel="nofollow noreferrer">acorn-walk</a> to traverse and remember the string positions and rewrite the <code>code</code> string afterwards. That seems like awkwardly reinventing the wheel though and would also mess with source maps?</li>
  <li>It appears <a href="https://github.com/estools/escodegen" rel="nofollow noreferrer">escodegen</a> doesn't actually support JSX at the moment, throwing <code>TypeError: this[type] is not a function</code>. Which is why I wonder...</li>
  <li>Why does Rollup require the plugin to return both <code>ast</code> and <code>code</code>; the latter seems redundant?</li>
  </ul>
  
  <p>(Rollup is relevant here because it allows processing an entire module-dependency graph.)</p>