Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
babel ast literals
var proxyIdentCounter = 0;
// matches ast`anything here`
var IDENTIFIER_NAME = "ast";
module.exports = function (babel) {
var babelParse = require('babel-core').parse;
// temporary
require('babel-core/register');
var cleanAst = require('./cleanAst');
var t = babel.types;
return new babel.Transformer("babel-ast-literal", {
TaggedTemplateExpression: function(node, parent){
var proxyIdentCache = {};
function ProxyIdent(originalExpression){
this.originalExpression = originalExpression;
this.proxyIdent = "__babel_ast_literal_proxyIdent__" + (++proxyIdentCounter);
this.proxyIdentAst = t.identifier(this.proxyIdent);
}
ProxyIdent.prototype.cache = function(){
proxyIdentCache[this.proxyIdent] = this;
};
if (!t.isIdentifier(node.tag, { name: IDENTIFIER_NAME })) {
return node;
}
// not all that different from writing a tagged template literal
// function at runtime
// strings[0], expressions[0], ..., expressions[n-1], strings[n]
var expressions = node.quasi.expressions;
var strings = node.quasi.quasis;
// this is a string of js code to be parsed by babel
var raw = "";
var handleString = function(n){
// should it be .cooked?
// https://github.com/babel/babel/blob/c4a491123e6dfcbc37e970028d9e5c3daa5c5af8/src/acorn/src/expression.js#L515
raw += n.value.raw;
};
var handleExpression = function(n){
cleanAst(n);
var proxy = new ProxyIdent(n);
proxy.cache();
raw += proxy.proxyIdent;
};
for (var i=0; i<expressions.length; i++) {
handleString(strings[i]);
handleExpression(expressions[i]);
}
handleString(strings[strings.length - 1]);
var intermediateAst = babelParse('(' + raw + ')').body[0].expression;
delete intermediateAst.parenthesizedExpression;
// this is the weird part
// we turn the ast for the code "{foo: bar}"
// into the ast for {ObjectExpression: {...}}
// basically it's babel.parse(JSON.stringify(babel.parse(source))
// but it gets better! we're also on the lookout for our proxy identifiers
// which need to be replaced with their original ast
function astToObjectLiteralAst(n){
// proxy idents
var isIdent = t.isIdentifier(n) || t.isJSXIdentifier(n);
if (isIdent && proxyIdentCache[n.name]) {
console.error(proxyIdentCache[n.name].originalExpression);
return proxyIdentCache[n.name].originalExpression;
}
if (typeof n !== "object" || n === null) {
return t.literal(n);
} else if (Array.isArray(n)) {
return t.arrayExpression(n.map(astToObjectLiteralAst));
}
return t.objectExpression(Object.keys(n).map(function(key){
if (key === 'start' || key === 'end') return;
var value = n[key];
return t.property(
'init',
t.identifier(key),
astToObjectLiteralAst(value)
);
}).filter(Boolean));
}
return astToObjectLiteralAst(intermediateAst);
}
});
};
// removes `start` and `end` keys
export default function cleanAst(ast){
Object.keys(ast).forEach(function(key){
if (key === 'start' || key === 'end' || key === 'loc' || key === 'range') {
delete ast[key];
} else if (ast[key] && typeof ast[key] === 'object') {
cleanAst(ast[key]);
}
});
}
var bar = ast`var1`;
var quux = ast`var2`;
var stuff = ast`foo(${bar}, "baz", ${quux})`;
console.log('AST:\n```json');
console.log(JSON.stringify(stuff, null, 2));
console.log('```');
console.log('Code: \n```js');
console.log(require('babel-core').transform.fromAst({
type: "Program",
body: [{type: "ExpressionStatement", expression: stuff}],
sourceType: "module"
}).code);
console.log('```');

AST:

{
  "callee": {
    "name": "foo",
    "type": "Identifier"
  },
  "arguments": [
    {
      "name": "var1",
      "type": "Identifier"
    },
    {
      "value": "baz",
      "raw": "\"baz\"",
      "type": "Literal"
    },
    {
      "name": "var2",
      "type": "Identifier"
    }
  ],
  "type": "CallExpression"
}

Code:

"use strict";

foo(var1, "baz", var2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.