【问题标题】:Get line number with abstract syntax tree in node js在节点js中使用抽象语法树获取行号
【发布时间】:2020-02-17 15:30:18
【问题描述】:

我正在制作一个程序,该程序通过参数获取一些代码,并转换代码,将一些 console.logs 添加到代码中。这是程序:

const escodegen = require('escodegen');
const espree = require('espree');
const estraverse = require('estraverse');

function addLogging(code) {
    const ast = espree.parse(code);
    estraverse.traverse(ast, {
        enter: function(node, parent) {
            if (node.type === 'FunctionDeclaration' ||
                node.type === 'FunctionExpression') {
                addBeforeCode(node);
            }
        }
    });
    return escodegen.generate(ast);
}

function addBeforeCode(node) {
    const name = node.id ? node.id.name : '<anonymous function>';
    const beforeCode = "console.log('Entering " + name + "()');";
    const beforeNodes = espree.parse(beforeCode).body;
    node.body.body = beforeNodes.concat(node.body.body);
}

所以如果我们将这段代码传递给函数:

console.log(addLogging(`
function foo(a, b) {   
  var x = 'blah';   
  var y = (function () {
    return 3;
  })();
}
foo(1, 'wut', 3);
`));

这是这个程序的输出:

function foo(a, b) {
    console.log('Entering foo()');
    var x = 'blah';
    var y = function () {
        console.log('Entering <anonymous function>()');
        return 3;
    }();
}
foo(1, 'wut', 3);

这是传递给 addLoggin 的最后一个函数的 AST(抽象语法树):

https://astexplorer.net/#/gist/b5826862c47dfb7dbb54cec15079b430/latest

所以我想在控制台日志中添加更多信息,例如我们所在的行号。据我所知,在 ast 中,节点有一个名为“start”和“end”的值,它指示该节点从哪个字符开始以及在哪里结束。我如何使用它来获取我们所在的行号?老实说,这对我来说似乎很困惑。我正在考虑通过“\n”对文件进行拆分,这样我就有了总行号,但是我怎么知道我在哪一个呢?

提前谢谢你。

【问题讨论】:

  • 由于最后会重新生成代码,所以源码中的行号与结果中的行号不一样。删除空行,可以添加换行符,console.log 添加添加行。那么:行号应该参考哪个版本的代码?源代码还是返回代码?

标签: javascript node.js abstract-syntax-tree


【解决方案1】:

你的想法很好。首先在每行开始的原始代码中找到偏移量。然后将节点的start索引与收集到的索引进行比较,以确定行号。

我在这里假设您希望报告的行号引用原始代码,而不是函数返回的代码。

因此,自下而上,进行以下更改。首先期望行号作为addBeforeCode的参数:

function addBeforeCode(node, lineNum) {
    const name = node.id ? node.id.name : '<anonymous function>';
    const beforeCode = `console.log("${lineNum}: Entering ${name}()");`;
    const beforeNodes = espree.parse(beforeCode).body;
    node.body.body = beforeNodes.concat(node.body.body);
}

定义一个函数来收集原始代码中对应于行首的偏移量:

function getLineOffsets(str) {
    const regex = /\r?\n/g;
    const offsets = [0];
    while (regex.exec(str)) offsets.push(regex.lastIndex);
    offsets.push(str.length);
    return offsets;
}

注意:如果你支持matchAll,那么上面可以写得更简洁一些。

然后在你的主函数中使用上面的:

function addLogging(code) {
    const lineStarts = getLineOffsets(code);   // <---
    let lineNum = 0;                           // <---
    const ast = espree.parse(code);
    estraverse.traverse(ast, {
        enter: function(node, parent) {
            if (node.type === 'FunctionDeclaration' ||
                     node.type === 'FunctionExpression') {
                // Look for the corresponding line number in the source code:
                while (lineStarts[lineNum] < node.body.body[0].start) lineNum++;
                // Actually we now went one line too far, so pass one less:
                addBeforeCode(node, lineNum-1);
            }
        }
    });
    return escodegen.generate(ast);
}

与您的问题无关,但请注意,函数可以是具有表达式语法的箭头函数。所以他们不会有阻塞,你也不能以同样的方式注入console.log。你可能想让你的代码能够处理这个问题,或者跳过这些。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多