SOURCE

/**
 *
 * ----------------------------------------------------------------------------
 *   Original AST                     |   Transformed AST
 * ----------------------------------------------------------------------------
 *   {                                |   {
 *     type: 'Program',               |     type: 'Program',
 *     body: [{                       |     body: [{
 *       type: 'CallExpression',      |       type: 'ExpressionStatement',
 *       name: 'add',                 |       expression: {
 *       params: [{                   |         type: 'CallExpression',
 *         type: 'NumberLiteral',     |         callee: {
 *         value: '2'                 |           type: 'Identifier',
 *       }, {                         |           name: 'add'
 *         type: 'CallExpression',    |         },
 *         name: 'subtract',          |         arguments: [{
 *         params: [{                 |           type: 'NumberLiteral',
 *           type: 'NumberLiteral',   |           value: '2'
 *           value: '4'               |         }, {
 *         }, {                       |           type: 'CallExpression',
 *           type: 'NumberLiteral',   |           callee: {
 *           value: '2'               |             type: 'Identifier',
 *         }]                         |             name: 'subtract'
 *       }]                           |           },
 *     }]                             |           arguments: [{
 *   }                                |             type: 'NumberLiteral',
 *                                    |             value: '4'
 * ---------------------------------- |           }, {
 *                                    |             type: 'NumberLiteral',
 *                                    |             value: '2'
 *                                    |           }]
 *  (sorry the other one is longer.)  |         }
 *                                    |       }
 *                                    |     }]
 *                                    |   }
 * ----------------------------------------------------------------------------
 */

function traverser(ast,visitor){
    function traverserArray(array, parent){
        array.forEach(node=>{
            traverserNode(node, parent)
        })
    }

    function traverserNode(node,parent){
        const method = visitor[node.type]
        if(method&&method.enter){
            method.enter(node, parent)
        }
        switch(node.type){
            case 'Program': 
                traverserArray(node.body,node);
                break;
            case 'CallExpression':
                traverserArray(node.params,node);
                break;
            case 'NumberLiteral':
            case 'NumberLiteral':
                break;
            default:
                throw new TypeError(node.type)
            
        }

        if(method&&method.exit){
            method.exit(node, parent)
        }
        return 
    }
    traverserNode(ast, null)
}

function transformer(ast) {
    let newAst = {
        type:'Program',
        body:[]
    }

    ast._context = newAst.body

    traverser(ast, {
        NumberLiteral:{
            enter(node, parent){
                 parent._context.push({
                    type: 'NumberLiteral',
                    value:node.value 
                })
            }
        },
        StringLiteral:{
            enter(node, parent){
                 parent._context.push({
                    type: 'StringLiteral',
                    value:node.value 
                })
            }
        },
        CallExpression:{
            enter(node,parent){
                let expression = {
                    type: 'CallExpression',
                    callee:{
                        type: 'Identifier',
                        name: node.name
                    },
                    arguments:[]
                }

                node._context = expression.arguments

                if(parent.type!=="CallExpression"){
                    expression = {
                        type: "ExpressionStatement",
                        expression: expression
                    }
                }
                parent._context.push(expression)

            }
        }
    })

    console.log(newAst)
}

const ast1 = {
    "type": "Program",
    "body": [
        {
            "type": "CallExpression",
            "name": "add",
            "params": [
                {
                    "type": "NumberLiteral",
                    "value": "2"
                },
                {
                    "type": "CallExpression",
                    "name": "subtract",
                    "params": [
                        {
                            "type": "NumberLiteral",
                            "value": "4"
                        },
                        {
                            "type": "NumberLiteral",
                            "value": "2"
                        }
                    ]
                }
            ]
        }
    ]
}
transformer(ast1)
console 命令行工具 X clear

                    
>
console