前端向架构突围系列 - 编译原理 [6 - 3]:ESLint 原理、自定义规则与 Codemod

91 阅读4分钟

写在前面

很多团队面临这样的困境: 架构师制定了规范:“所有业务组件禁止直接引用 lodash,必须引用 src/utils。” 结果呢?文档写在 Wiki 里吃灰,新同事照样写 import _ from 'lodash'。Code Review 时如果你没看出来,这代码就溜上线了。

口头规范是软弱的,代码规范才是强硬的。

真正的高手,会把架构规范写成 ESLint 插件。这一节,我们将把“文档里的规范”变成“编辑器里的红色波浪线”。

image.png


一、 ESLint 的原理:找茬的艺术

ESLint 的工作流程和 Babel 惊人地相似,只有最后一步不同。

1.1 流程对比

  • Babel: Parse -> Transform (修改 AST) -> Generate (生成新代码)
  • ESLint: Parse -> Traverse (遍历 AST) -> Report (报告错误)

ESLint 默认使用 Espree 作为解析器(Parser)。它遍历 AST,当遇到不符合规则的节点时,不是去修改它,而是记录一个“错误对象”(包含行号、列号、错误信息)。

1.2 Fix 的原理

你一定用过 eslint --fix。既然 ESLint 不生成新代码,它是怎么修复错误的? 其实,ESLint 的规则在报错时,可以提供一个 fixer 对象。

context.report({
  node: node,
  message: "缺少分号",
  fix: function(fixer) {
    // 告诉 ESLint:在当前节点后面插入一个 ";"
    return fixer.insertTextAfter(node, ";");
  }
});

ESLint 收集所有的 fix 操作,最后在源码字符串上进行字符串拼接(而不是重新 Generate),从而保留原本的格式(空格、注释)。


二、 实战:编写你的第一条 ESLint 规则

假设你的团队有一个死规定:代码中禁止使用 var,必须用 letconst 虽然现有的规则集里有 no-var,但为了学习,我们自己写一个。

2.1 规则结构

一个 ESLint 规则就是一个导出的对象,包含 meta(元数据)和 create(访问者)。

// eslint-plugin-no-var-custom.js
module.exports = {
  meta: {
    type: "suggestion",
    docs: {
      description: "禁止使用 var",
    },
    fixable: "code", // 表示这个规则支持自动修复
  },
  create(context) {
    return {
      // 监听 VariableDeclaration 节点
      VariableDeclaration(node) {
        // 如果声明类型是 "var"
        if (node.kind === "var") {
          // 报警!
          context.report({
            node,
            message: "大清亡了,别用 var 了!",
            // 自动修复逻辑
            fix(fixer) {
              // 把 "var" 替换成 "let"
              // sourceCode.getFirstToken(node) 获取到的就是 "var" 这个关键词
              const varToken = context.getSourceCode().getFirstToken(node);
              return fixer.replaceText(varToken, "let");
            }
          });
        }
      }
    };
  }
};

2.2 架构级应用:防腐层治理

架构师可以利用自定义规则做更高级的事情。 场景: 项目中分层架构,UI 层(src/components)严禁直接导入数据库层(src/db)。

// rule: no-ui-import-db.js
create(context) {
  return {
    ImportDeclaration(node) {
      const importPath = node.source.value; // e.g., '@/db/user'
      const currentFilename = context.getFilename(); // 当前正在检查的文件

      // 如果当前文件在 components 目录下,且引用了 db 目录
      if (currentFilename.includes('/src/components/') && importPath.includes('/db/')) {
        context.report({
          node,
          message: "架构报警:UI 组件禁止直接触碰数据库层!请通过 Service 层调用。"
        });
      }
    }
  };
}

把这个规则加入 CI/CD,你的架构分层就有了强制力


三、 Codemod:自动化重构的核武器

ESLint 的 fix 适合修补小问题。但如果你面临的是大规模破坏性重构,比如:

  • 把项目中 5000 个文件的 React.createClass 全部重写为 class extends React.Component
  • 把所有的 import { Button } from 'my-ui' 变成 import Button from 'my-ui/button'

这时候,你需要 Codemod。最著名的工具是 Facebook 推出的 jscodeshift

3.1 jscodeshift 的优势

它不仅仅是 AST 解析器,它提供了一套类似 jQuery 的 API 来操作 AST。你不需要关心复杂的节点结构,只需要链式调用。

3.2 实战:API 签名变更

需求: 旧的 API myApi.get(id, type) 升级了,参数变了,必须改成对象传参 myApi.get({ id, type })

Codemod 脚本:

// transformer.js
export default function(file, api) {
  const j = api.jscodeshift; // 获取 jscodeshift 实例
  
  return j(file.source) // 1. 解析源码
    .find(j.CallExpression, { // 2. 查找所有的函数调用
      callee: {
        object: { name: 'myApi' },
        property: { name: 'get' }
      }
    })
    .forEach(path => { // 3. 遍历找到的节点
      const args = path.node.arguments;
      
      // 如果参数数量是 2 个,说明是旧代码
      if (args.length === 2) {
        // 创建一个新的对象表达式 { id: arg0, type: arg1 }
        const newObjArg = j.objectExpression([
            j.property('init', j.identifier('id'), args[0]),
            j.property('init', j.identifier('type'), args[1])
        ]);
        
        // 替换参数
        path.node.arguments = [newObjArg];
      }
    })
    .toSource(); // 4. 生成新代码
}

运行:

npx jscodeshift -t transformer.js src/**/*.js

瞬间,你完成了全项目几千个文件的 API 升级。这就是架构师的效率。


四、 总结:架构师的“法治”思维

这一节我们从“写代码”进阶到了“管代码”。

  1. ESLint 是日常执勤的警察,通过 Linting(检查)和 Fixing(微修补)维持代码风格和架构边界。
  2. Codemod 是特种部队,通过 AST Transformation 解决大规模的技术债务和破坏性升级。

架构师不应该仅仅是那个“写文档告诉大家怎么做”的人,而应该是那个“提供工具让大家没法做错”的人。

Next Step: 我们已经把 AST 在工具链(Babel, ESLint)中的应用学完了。 最后,我们要看看 AST 是如何在现代前端框架中发挥作用的。Vue 的 <template> 是怎么变成 JS 的?React 的 JSX 到底是怎么回事? 下一节,我们将揭秘**《第四篇:应用——框架的魔法:Vue 模板编译与 React JSX 转换背后的编译艺术》**。