在节点js中使用抽象语法树获取行号

Get line number with abstract syntax tree in node js

我正在制作一个程序,通过参数获取一些代码,并转换代码,向代码中添加一些 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”分割文件,这样我就有了总行号,但是我怎么知道我在哪一个呢?

提前致谢。

你的想法很好。首先找到原始代码中每行开始的偏移量。然后将节点的 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 。您可能想让您的代码能够处理该问题,或者跳过这些问题。