babel插件和eslint插件的异同

80 阅读4分钟

babel和eslint都是基于ast转换的,一个是做代码转换,一个是检查代码,错误检查和修复的玩意。babel 插件和 eslint 插件都能够分析和转换代码,那这俩到底有啥不同呢

babel插件

先看babel插件,babel的编译阶段分为parse(解析),transform(转换),generate(生成)3阶段。可以指定插件,在遍历 AST 的时候会合并调用 visitor。 也就是主要做一个代码转换的功能。

eslint插件

eslint也会对代码进行parse,查找要检查的 AST,之后进行检查和报错,但不一定会修复代码,只有指定了 fix 才会进行修复。 我们写一个检查对象格式的 eslint 插件。 需求时把下面的代码格式进行检查和修复:

const  obj = {
    a: 1,b: 2,

    c: 3
}

变成这种:

const obj = {
    a: 1,
    b: 2,
    c: 3
}

eslint 是可以查找某个 AST 关联的 token 的,也就是我们可以拿到对象的每一个属性开始和结束的 token 还有行列号。 我们要校验上一个属性结束的 token 的行号要等于下一个属性开始的 token 的行号(这就是换行的eslint的原理)。 指定对 ObjectExpression 也就是 {} 表达式的每一个属性的开始和结束 token 的行号做检查,如果不是下一个属性是上一个属性的 +1 行,那就报错。 并且,还可以指定如何修复,我们这里的错误的修复方式就是把两个 token 之间的部分替换为换行符(os.EOL) + tab。

const os = require('os');

module.exports = {
     meta: {
         fixable: true
     },
     create(context) {
         const sourceCode = context.getSourceCode();
         return {
            ObjectExpression(node) {
                for (let i = 1; i < node.properties.length; i ++) {
                     const firstToken = sourceCode.getTokenAfter(node.properties[i - 1]);
                     const secondToken = sourceCode.getFirstToken(node.properties[i]);
                     if(firstToken.loc.start.line !== secondToken.loc.start.line - 1) {
                        context.report({
                            node,
                            message: '对象属性之间不能有空行',
                            loc: firstToken.loc,
                            *fix(fixer) {
                                yield fixer.replaceTextRange([firstToken.range[1],secondToken.range[0]], os.EOL + '\t');
                            }
                        });
                     }
                     
                }
             }
         };
     }
 };

这样就完成了对象格式的检查和自动修复。 这个插件文件名命名为 object-property-format,然后我们使用 api 的方式调用下: 首先,引入 eslint 模块,创建 ESLint 对象:

const { ESLint } = require("eslint");

const engine = new ESLint({
    fix: false,
    overrideConfig: {
        parser: '@babel/eslint-parser',
        parserOptions: {
            sourceType: "unambiguous",
            requireConfigFile: false,
        },
        rules: {
            "object-property-format": "error"
        }
    },
    rulePaths: ['./'],
    useEslintrc: false
});

这里把配置文件关掉(useEslintrc: false),只用这里的的配置(overrideConfig) 我们指定用 babel 的 parser(@babel/eslint-parser),并且不需要 babel 配置文件。之后引入刚才我们写的那个 rule,也就是 object-property-format,报错级别设置为 error。 还需要指定 rulePaths,也就是告诉 eslint 去哪里查找 rule。 之后,我们调用 lintText 的 api 进行 lint:

(async function main() {
  const results = await engine.lintText(`
  const  obj = {
        a: 1,b: 2,

        c: 3
    }
  `);
  console.log(results[0].output);
  const formatter = await engine.loadFormatter("stylish");
  const resultText = formatter.format(results);
  console.log(resultText);
})()

eslint和babel插件区别

我们把总结的 babel 插件和 eslint 插件的特点拿到一起对比下。(这里的 eslint 插件严格来说是指的 eslint 的 rule,eslint 插件可以包含多个 rule。) babel 插件:

  • 插件的形式是函数返回一个对象,对象的 visitor 属性声明对什么节点做什么处理

  • visitor 函数可以通过 path 的 api 来对 ast 增删改

  • 修改后的 ast 会打印成目标代码eslint 插件:

  • rule 的形式是对象,create 属性是一个函数,返回一个对象,指定对什么 AST 做什么检查和修复

  • AST 处理函数可以通过 context 的 api 来拿到源码不同位置的 token 来进行格式的检查

  • fix 函数可以拿到 fixer 的 api,来对某个位置的代码进行字符的增删改

  • 默认不会修复代码,需要指定 fix 才会进行修复我们来对比下两者的异同:

  • 从形式上来说,eslint 的 rule 是对象-函数-对象的形式,而 babel 插件是函数-对象的形式,多的部分是 eslint rule 的元信息,也就是 meta 属性。这是两者设计上的不同。

  • babel 插件和 eslint rule 都可以遍历节点,指定对什么节点做处理,但是 babel 插件可以通过 path 的 api 来增删改 AST,而 eslint 则是通过 context.getSourceCode() 拿到 sourceCode,然后通过 sourceCode 的 api 进行格式的检查,最后修复还要通过 fixer 的 api。

  • babel 插件的改动默认就是生效的,最多传入 options 进行控制,而 eslint 的 fix 功能只有开启才生效。其实可以说两个用途完全不一样,但是工作原理又是比较像的。 也就是说理论上基于 babel 完全可以实现 eslint 的功能,只不过两者 api 设计上的不同,导致了两者适合的场景不同。

  • babel 是通过 path api 进行 AST 增删改,适合做代码分析和转换。

  • eslint 是通过 sourceCode 和 fixer api 进行代码格式的检查和 fix,适合做错误检查和修复。但是,从本质上来说,两者编译流程上差别并不大。

​ ​ ​ 参考: 深入对比 eslint 插件 和 babel 插件的异同点(神光大佬)