我们的 idea 是在不编辑现有函数的情况下实现这一点,也不必向代码库中的所有函数添加包装器.

理想情况下,console.log应该只应用于我的代码库中的函数,而不是 node 模块函数.

我想在控制台中记录函数名和调用函数所用的参数,并在末尾记录函数名和返回值.

有没有人知道满足这个要求的解决方案?也许和巴别塔一起?

推荐答案

这里有一个巴别塔插件,应该可以做你想做的事情.请注意,对于函数defined,它将匿名地记录"<;匿名&>"作为函数名,而不管它们稍后是否被赋值给变量,这可能是您想要的,也可能不是.

我试图使代码尽可能简单,这样您应该能够根据自己的喜好进行调整.


babel-plugin-autologcalls.mjs

function insertLogBeforeFunctionBody(
  t,
  node,
  // default to using the arguments keyword
  args = [t.identifier("arguments")]
) {
  // node.body is a BlockStatement
  // we want to append a console.log("CALL [<function name>]", arguments) to the body of that block statement
  node.body.body.unshift(
    t.expressionStatement(
      t.callExpression(t.identifier("console.log"), [
        t.stringLiteral(`CALL [${node.id?.name || "<anonymous>"}]`),
        ...args,
      ])
    )
  );
}

// recursively extract the identifiers from any kind of parameter
function getParameterIdentifiers(param) {
  switch (param.type) {
    case "Identifier":
      return [param];
    case "ArrayPattern":
      return param.elements.flatMap(getParameterIdentifiers);
    case "ObjectPattern":
      return param.properties.flatMap((prop) =>
        getParameterIdentifiers(prop.value)
      );
    case "RestElement":
      return [param.argument];
  }
}

function insertLogBeforeArrowFunctionBody(t, node, scope) {
  // rewrite arrow function of form () => ... to () => { return ... } so we can insert a console.log
  if (node.body.type !== "BlockStatement") {
    node.body = t.blockStatement([
      // will later be replaced by the ReturnStatement visitor
      t.returnStatement(node.body),
    ]);
  }

  insertLogBeforeFunctionBody(
    t,
    node,
    // arrow functions don't support the arguments keyword, so we extract the arguments from the parameters
    node.params.flatMap(getParameterIdentifiers)
  );
}

// recursively find the function the return statement was used in
function getFunctionFromReturnStatement(path) {
  return path.type.match(/^(Arrow)?Function(Declaration|Expression)$/)
    ? path.node
    : getFunctionFromReturnStatement(path.parentPath);
}

function insertLogBeforeReturnStatement(t, path) {
  const retId = path.scope.generateUidIdentifier("ret");

  path.replaceWithMultiple([
    // const <retId> = <returned value>
    // console.log("RETURN [<function name>]", <retId>);
    // return <retId>;
    t.variableDeclaration("const", [
      t.variableDeclarator(retId, path.node.argument),
    ]),
    t.expressionStatement(
      t.callExpression(t.identifier("console.log"), [
        t.stringLiteral(
          `RETURN [${
            getFunctionFromReturnStatement(path).id?.name || "<anonymous>"
          }]`
        ),
        retId,
      ])
    ),
    // add extra __isLogged property to prevent an infinite loop where the replacement return statement gets visited
    { ...t.returnStatement(retId), __isLogged: true },
  ]);
}

export default function ({ types: t }) {
  return {
    visitor: {
      // function <name>() { ... }  (statement)
      FunctionDeclaration({ node }) {
        insertLogBeforeFunctionBody(t, node);
      },
      // function <name?>() { ... }  (expression)
      FunctionExpression({ node }) {
        insertLogBeforeFunctionBody(t, node);
      },
      // () => ... or () => { ... }
      ArrowFunctionExpression({ node, scope }) {
        insertLogBeforeArrowFunctionBody(t, node, scope);
      },
      // return ...
      ReturnStatement(path) {
        if (!path.node.__isLogged) {
          insertLogBeforeReturnStatement(t, path);
        }
      },
    },
  };
}

要使用它,请将其注册到babel.config.json.babelrc文件中,如下所示:

{
  "plugins": ["./babel-plugin-autologcalls.mjs"]
}

然后运行npm i -D @babel/core @babel/cli,在项目中安装了Babel之后,使用命令npx babel <source.js> --out-dir <output directory>编译代码.

(请注意,使用巴别塔有不同的方法,请参阅its documentation了解更多选项.)


使用此插件,以下代码...

function emptyfn() {}

function normalfn(s) {
  if (s.length > 1) {
    return s;
  }

  return arguments;
}

const anonymousfn = function () {
  return arguments;
};

const arrowfn = (
  _,
  n,
  [expanded],
  {
    _: {
      nested: [fn],
    },
    d,
  }
) => {
  return fn(n + expanded - d);
};

const lambda = (...args) => args;

const exampleargs = [
  "a",
  10,
  [5, 6, 7],
  { _: { nested: [(a) => `(${a})`] }, d: -11 },
];
normalfn(...exampleargs);
anonymousfn(...exampleargs);
arrowfn(...exampleargs);
lambda(...exampleargs);

...将编译为:

function emptyfn() {
  console.log("CALL [emptyfn]", arguments);
}
function normalfn(s) {
  console.log("CALL [normalfn]", arguments);
  if (s.length > 1) {
    const _ret = s;
    console.log("RETURN [normalfn]", _ret);
    return _ret;
  }
  const _ret2 = arguments;
  console.log("RETURN [normalfn]", _ret2);
  return _ret2;
}
const anonymousfn = function () {
  console.log("CALL [<anonymous>]", arguments);
  const _ret3 = arguments;
  console.log("RETURN [<anonymous>]", _ret3);
  return _ret3;
};
const arrowfn = (_, n, [expanded], {
  _: {
    nested: [fn]
  },
  d
}) => {
  console.log("CALL [<anonymous>]", _, n, expanded, fn, d);
  const _ret4 = fn(n + expanded - d);
  console.log("RETURN [<anonymous>]", _ret4);
  return _ret4;
};
const lambda = (...args) => {
  console.log("CALL [<anonymous>]", args);
  const _ret5 = args;
  console.log("RETURN [<anonymous>]", _ret5);
  return _ret5;
};
const exampleargs = ["a", 10, [5, 6, 7], {
  _: {
    nested: [a => {
      console.log("CALL [<anonymous>]", a);
      const _ret6 = `(${a})`;
      console.log("RETURN [<anonymous>]", _ret6);
      return _ret6;
    }]
  },
  d: -11
}];
normalfn(...exampleargs);
anonymousfn(...exampleargs);
arrowfn(...exampleargs);
lambda(...exampleargs);

希望这个能帮上忙!

Node.js相关问答推荐

链接Inbox类方法,范围在哪里以及为什么发生变化?

node 模块错误:类型参数OT具有循环约束''

从目录中获取所有文件,而不是NodeJS中的单个文件

在Nest.Js中,如何发送带有表单数据的正文请求并应用正文请求验证.附加的文件是可选的

如何在Angular jest测试中调用Nodejs的垃圾收集? node v20的测试速度慢且内存泄漏

Sequelize、postgres和posgis:在n°;公里

Playwright - 无法在 img 标签中使用 file:// 访问本地文件

Node js 处理回调和 Promise

Nest Js - 在 multer 拦截器中如何设置最大文件上传大小并进行可选文件上传

找不到 vue-template-compiler@2.6.14 的匹配版本 | Shopware 6.5 更新后的 node 问题

如何在MongoDB中删除嵌套对象数组中的属性?

如何找到特定文档并更新数组中特定键的值?

Web3.js 脚本在监听 CreatedPairs 时退出

具有多个条件的mongoose 查找

如何在 Node.js 中等待子进程完成

用一级 try ... catch 捕获 JavaScript Promise 中的错误

如何在 MongoDB 中查询引用的对象?

将 Heroku App 连接到 Atlas MongoDB 云服务

node --experimental-modules,请求的模块不提供名为的导出

在 Node.js 上使用 Connect 无法获取 /