如何从ESLint SourceCode保留函数名?存储JS方法至数据库遇问题
解决函数表达式存储时丢失名称的问题
这个问题的核心其实不是ESLint的配置问题,而是函数表达式本身的toString()行为和你当前的处理方式导致的——让我拆解下原因,再给你几个可行的解决方案:
为什么会丢失名称?
- 对于函数声明(
function foo() {}),它的toString()方法会完整返回包含函数名的代码,这是ES标准规定的。 - 但对于函数表达式(包括箭头函数和赋值的匿名函数),比如
const foo = (a,b) => {},foo.toString()只会返回箭头函数本身的代码:"(a,b) => {}",不会包含变量名foo——因为变量名是赋值语句的一部分,不属于函数自身的内容。 - ESLint的
SourceCode.splitLines只是把字符串按换行拆分,它不会给你补充原本就不存在的变量名信息。
解决方案
方案1:修改模块导出结构(最省心)
如果这些函数模块是你自己维护的,最简单的方式是让每个模块导出一个包含函数名+函数体的对象,而不是直接导出函数:
// 你的模块文件(比如foo.js) const foo = (a, b, c) => { // 函数逻辑 }; module.exports = { name: 'foo', fn: foo };
然后在读取模块时,直接获取预定义的名称:
let r = require(file); mapObj.set(id, { id, name: r.name, fn: SourceCode.splitLines(r.fn.toString()), });
这种方式完全规避了从函数本身提取名称的问题,还能确保名称准确无误。
方案2:解析原始源代码提取信息(适合无法修改模块的场景)
如果没办法修改目标模块,你需要放弃require()后取函数toString()的方式,改为读取文件原始代码并解析AST,从代码结构中提取函数名和函数体。
ESLint本身依赖espree做AST解析,你可以直接用它来处理:
const fs = require('fs'); const espree = require('espree'); const { generate } = require('@babel/generator'); // 用来从AST节点生成代码 const { SourceCode } = require('eslint'); // 读取文件原始内容 const rawCode = fs.readFileSync(file, 'utf8'); // 解析成AST const ast = espree.parse(rawCode, { ecmaVersion: 'latest', sourceType: 'module' // 如果是CommonJS模块,改成"script" }); let functionName = ''; let functionCode = ''; // 遍历AST,提取函数表达式的名称和代码(这里处理常见的几种情况) ast.body.forEach(node => { // 情况1:顶层变量声明是箭头函数/函数表达式 if (node.type === 'VariableDeclaration') { node.declarations.forEach(decl => { const isTargetFn = ['ArrowFunctionExpression', 'FunctionExpression'].includes(decl.init?.type); if (isTargetFn) { functionName = decl.id.name; functionCode = generate(decl.init).code; } }); } // 情况2:默认导出箭头函数/函数表达式 else if (node.type === 'ExportDefaultDeclaration') { const expr = node.declaration; if (['ArrowFunctionExpression', 'FunctionExpression'].includes(expr.type)) { functionName = 'default'; // 或者自定义默认名称 functionCode = generate(expr).code; } // 情况3:默认导出的是已声明的变量 else if (expr.type === 'Identifier') { const varDecl = ast.body.find(n => n.type === 'VariableDeclaration' && n.declarations.some(d => d.id.name === expr.name) ); if (varDecl) { const targetDecl = varDecl.declarations.find(d => d.id.name === expr.name); functionName = expr.name; functionCode = generate(targetDecl.init).code; } } } }); // 最后存储到mapObj if (functionName && functionCode) { mapObj.set(id, { id, name: functionName, fn: SourceCode.splitLines(functionCode) }); }
这个方案需要你根据实际的模块导出情况扩展AST遍历逻辑,但能从原始代码中准确提取函数名和代码内容。
补充:关于具名函数表达式的特殊情况
如果你使用的是具名函数表达式(比如const foo = function bar() {}),那么foo.toString()会返回"function bar() {}"——这时候你能得到函数名bar,但这是函数自身的名称,不是赋值的变量名foo,如果需要后者还是得用上面的方案。
内容的提问来源于stack exchange,提问作者laujonat
相关产品推荐
相关产品推荐

