Как создать исходную карту JavaScript на основе преобразования AST?

Предположим, что я AST преобразую содержимое файла JavaScript из состояния A в состояние B.

Как я могу сделать сопроводительную карту источника? я использую esprima а также estravese (estraverse.replace), чтобы пройти AST (у меня есть исходная карта, соответствующая начальной AST) и преобразовать ее в другую AST (но у меня нет полученной исходной карты).

Как я могу получить эту исходную карту?

РЕДАКТИРОВАТЬ: я использую esprima и estraverse, чтобы сделать свое преобразование AST. Моя трансформация выглядит так:

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) {
        //
    }

}

1 ответ

Решение

Для каждого преобразования дерева, которое вы пишете, вы должны написать соответствующее преобразование на исходной карте.

Поскольку преобразования дерева по существу являются произвольными, соответствующие исходные преобразования также будут произвольными. Аналогично, сложные преобразования дерева будут вызывать соответственно сложные преобразования карты источника.

Одним из способов реализации этого может быть совместная (я предполагаю, что они существуют) операций преобразования дерева DeleteNode, ReplaceNode, ReplaceChildWithIdentifier, ReplaceChildWithLiteral, ReplaceChildWithOperator. Используя только эти операции, вы все равно сможете вносить произвольные изменения в дерево. Изменяя эти операции для обновления исходной карты (каждая из которых делает что-то очень специфичное для исходной карты), вы должны получить "бесплатно" обновленную исходную карту. Очевидно, что вы не можете использовать другие операции модификации дерева, если они не реализованы с использованием этих примитивов.

Пара инструментов в сообществе для этой цели:

Другие вопросы по тегам