• Building Nodes" level="1">Building Nodes

    Building Nodes" class="reference-link">Building Nodes

    When writing transformations you’ll often want to build up some nodes to insert into the AST. As mentioned previously, you can do this using the builder methods in the babel-types package.

    The method name for a builder is simply the name of the node type you want to build except with the first letter lowercased. For example if you wanted to build a MemberExpression you would use t.memberExpression(...).

    The arguments of these builders are decided by the node definition. There’s some work that’s being done to generate easy-to-read documentation on the definitions, but for now they can all be found here.

    A node definition looks like the following:

    1. defineType("MemberExpression", {
    2. builder: ["object", "property", "computed"],
    3. visitor: ["object", "property"],
    4. aliases: ["Expression", "LVal"],
    5. fields: {
    6. object: {
    7. validate: assertNodeType("Expression")
    8. },
    9. property: {
    10. validate(node, key, val) {
    11. let expectedType = node.computed ? "Expression" : "Identifier";
    12. assertNodeType(expectedType)(node, key, val);
    13. }
    14. },
    15. computed: {
    16. default: false
    17. }
    18. }
    19. });

    Here you can see all the information about this particular node type, including how to build it, traverse it, and validate it.

    By looking at the builder property, you can see the 3 arguments that will be needed to call the builder method (t.memberExpression).

    1. builder: ["object", "property", "computed"],

    Note that sometimes there are more properties that you can customize on the node than the builder array contains. This is to keep the builder from having too many arguments. In these cases you need to set the properties manually. An example of this is ClassMethod.

    1. // Example
    2. // because the builder doesn't contain `async` as a property
    3. var node = t.classMethod(
    4. "constructor",
    5. t.identifier("constructor"),
    6. params,
    7. body
    8. )
    9. // set it manually after creation
    10. node.async = true;

    You can see the validation for the builder arguments with the fields object.

    1. fields: {
    2. object: {
    3. validate: assertNodeType("Expression")
    4. },
    5. property: {
    6. validate(node, key, val) {
    7. let expectedType = node.computed ? "Expression" : "Identifier";
    8. assertNodeType(expectedType)(node, key, val);
    9. }
    10. },
    11. computed: {
    12. default: false
    13. }
    14. }

    You can see that object needs to be an Expression, property either needs to be an Expression or an Identifier depending on if the member expression is computed or not and computed is simply a boolean that defaults to false.

    So we can construct a MemberExpression by doing the following:

    1. t.memberExpression(
    2. t.identifier('object'),
    3. t.identifier('property')
    4. // `computed` is optional
    5. );

    Which will result in:

    1. object.property

    However, we said that object needed to be an Expression so why is Identifier valid?

    Well if we look at the definition of Identifier we can see that it has an aliases property which states that it is also an expression.

    1. aliases: ["Expression", "LVal"],

    So since MemberExpression is a type of Expression, we could set it as the object of another MemberExpression:

    1. t.memberExpression(
    2. t.memberExpression(
    3. t.identifier('member'),
    4. t.identifier('expression')
    5. ),
    6. t.identifier('property')
    7. )

    Which will result in:

    1. member.expression.property

    It’s very unlikely that you will ever memorize the builder method signatures for every node type. So you should take some time and understand how they are generated from the node definitions.

    You can find all of the actual definitions here and you can see them documented here