ESlint 介绍

848 阅读9分钟

Lint介绍

Lint 是什么

C语言诞生之初,程序员编写的代码风格各异,在移植时会出现一些因为不严谨的代码段导致无法被编译器执行的问题。于是在 1979年,一款叫lint的程序被开发出来,能够通过扫描源代码检测潜在的错误。此后 lint 的功能不断完善,类似的工具相继出现。不仅可以检测代码中的潜在 Bug,还能做一些类型检查。

Lint 发展

  • 2002 年,Douglas Crockford 开发了是第一款针对 JavaScript 的语法检测工具 —— JSLint,并于 2010 年开源。
  • 2011 年,Anton Kovalyov Fork JSLint 项目开发了 JSHint
  • 2013 年的 6 月份,Zakas 发布了全新的 lint 工具——ESLint
  • 2015 年 6 月,ES2015 规范正式发布,但是发布后,市面上浏览器对最新标准的支持情况极其有限。如果想要提前体验最新标准的语法,就得靠 Babel 之类的工具将代码编译成 ES5 甚至更低的版本,同时一些实验性的特性也能靠 Babel 转换。
  • 2015 年React 的应用越来越广泛,诞生不久的 JSX 也愈加流行。ESLint 本身也不支持 JSX 语法。还是因为可扩展性,eslint-plugin-react的出现让 ESLint 也能支持当时 React 特有的规则。

Lint对比

在ESLint诞生初期,有许多编辑器对 JSHint 支持完善,生态足够强大。地位无法撼动,而 ECMAScript 6的出现让ESLint弯道超车了,ES2015 变化很大,JSHint 就略尴尬短期内无法完全支持。ESLint 可扩展的优势一下就体现出来了,不仅可以扩展规则,甚至连解析器也能替换。Babel 团队就为 ESLint 开发了 babel-eslint替换默认解析器,让 ESLint 率先支持 ES2015 语法。

ESLint作用

  • 避免错误
  • 写出最佳实践的代码
  • 规范变量的使用方式
  • 规范代码格式
  • 更合适的使用新的语法

ESLint 配置项

"extends": [
    "plugin:@typescript-eslint/recommended",
    "prettier",
    "prettier/@typescript-eslint"
  ],
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "parserOptions": {
    "ecmaFeatures": {
      "generators": true,
      "classes": true
    }
  },
  "env": {
    "browser": true,
    "node": true,
    "mocha": true,
    "jest": true
  },
  "rules": {}
  • env

    • 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
  • globals

    • 脚本在执行期间访问的额外的全局变量。也就是 env 中未预定义,但我们又需要使用的全局变量。
  • extends

    • 检测中使用的预定义的规则集合。
  • rules

    • 启用的规则及其各自的错误级别,会合并 extends 中的同名规则,定义冲突时优先级更高。
  • plugins

    • plugins 是一个 npm 包,通常输出 eslint 内部未定义的规则实现。rules 和 extends 中定义的规则,并不都在 eslint 内部中有实现。 比如 extends 中的plugin:react/recommended,其中定义了规则开关和等级,但是这些规则如何生效的逻辑是在其对应的插件 ‘react’ 中实现的。
  • overrides

    • 外侧配置的 rule 一般都是全局生效,通过 overrides ,我们可以针对一些文件覆盖一些规则。
  • settings

    • 通过 setting 我们可以向每条 rule 传入一些自定义的配置内容
  • parserOptions

    • ESLint 允许你指定你想要支持的 JavaScript 语言选项。ecmaFeatures 是个对象,表示你想使用的额外的语言特性,ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持 es5,你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。上面的 env 中启用了 es6,自动设置了ecmaVersion 解析器选项为 6。

ESLint 原理

AST

  • AST是: Abstract Syntax Tree的简称,中文叫做:抽象语法树 将代码抽象成树状数据结构,方便后续分析检测代码。 astexplorer.net/ 直观转义后的内容

  • AST概念比较常见,babel webpack 中都会涉及,在涉及编译原理的地方可能都会涉及到它。 ESLint的实现原理抽象出代码的共性再进行分析,对代码的抽象即为AST。它利用 AST 处理规则,用 Esprima 解析代码。

Espree

  • ESLint使用JavaScript解析器Espree把JS代码解析成AST
  • 解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的
  • ESLint 默认使用Espree作为其解析器,你可以在配置文件中指定一个不同的解析器,只要该解析器符合下列要求:
    • 它必须是本地安装的一个 npm 模块。
    • 它必须有兼容 Esprima 的接口(它必须输出一个 parse() 方法)
    • 它必须产出兼容 Esprima 的 AST 和 token 对象;

校验过程

  • 将代码解析成AST。
  • 深度遍历AST,监听匹配过程, 在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。
  • 触发监听选择器的rule回调,在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。
  • 检测规则。

Rule写法

module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "disallow the use of `debugger`",
            category: "Possible Errors",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-debugger"
        },

        fixable: null,
        schema: [],

        messages: {
            unexpected: "Unexpected 'debugger' statement."
        }
    },

    create(context) {

        return {
            DebuggerStatement(node) {
                context.report({
                    node,
                    messageId: "unexpected"
                });
            }
        };

    }
};

no-debugger

  • Meta
    • 代表了这条规则的元数据,如其类别,文档,可接收的参数的schema 等等,官方文档对其有详细的描述。
  • Create
    • create 表示这条 rule 具体会怎么分析代码;

多Rule校验

nodeQueue.forEach(traversalInfo => {
        currentNode = traversalInfo.node;

        try {
            if (traversalInfo.isEntering) {
                eventGenerator.enterNode(currentNode);
            } else {
                eventGenerator.leaveNode(currentNode);
            }
        } catch (err) {
            err.currentNode = currentNode;
            throw err;
        }
    });

    return lintingProblems;
  • 遍历依据源码生成的 AST;
  • 将每一个 node 传入 nodeQueue 队列中;
  • 遍历所有将被应用的规则;
  • 为规则中所有的选择器添加监听事件;
  • 在触发时执行,将问题 push 到 lintingProblems 中;
  • 遍历第一步获取到的 nodeQueue,触发其中包含的事件;
  • 返回 lintingProblems。

ESLint用法

  • 安装

    • npm install eslint --save-dev
    • yarn add eslint –dev
  • 初始化

    • npx eslint --init
    • yarn run eslint --init 执行--init 前默认当前目录下已有package.json。
  • 配置

  • 运行完init后会生成eslintrc文件,在这个文件中添加配置

Usage

npx eslint src/**/*.js

Usage with VSCode

在vscode中安装插件,修改vs code配置

  "eslint.options": {
    "eslint.autoFixOnSave": true,
    "extensions": [
      ".js",
      ".vue",
      ".ts",
      ".tsx"
    ],
    // An array of language ids which should be validated by ESLint
    "eslint.validate": [
      "javascript",
      // jsx
      "javascriptreact",
      // vue
      {
        "language": "vue",
        "autoFix": true
      },
      // ts
      {
        "language": "typescript",
        "autoFix": true
      },
      // tsx
      {
        "language": "typescriptreact",
        "autoFix": true
      },
      "html"
    ],
    "eslint.alwaysShowStatus": true,
  },

Usage with webpack

  • 不一定每个 ide 都有插件,如果不想使用插件,又要实时提示报错,我们可以结合 webpack 的打包编译功能来实现
  • 在devserver 配置中添加 overlay: true
  • overlay的作用是可以在浏览器打开的页面显示终端编译时产生的错误。通过配置该属性,以后在写代码的时候,编译如果出错了,我们就不需要打开终端看到底是什么报错了,可以直接在页面里看到错误,对于开发而言确实很方便。在项目中,很少有使用 eslint-loader 的,因为不可避免会降低打包速度

Usage with git hooks

Git Hooks 就是在 Git 执行特定事件(如commit、push、receive等)时触发运行的脚本,类似于“钩子函数”

Git Hooks 就是在 Git 执行特定事件(如commit、push、receive等)时触发运行的脚本,类似于“钩子函数”

  • Husky

    • Husky 是一个让配置 Git 钩子变得更简单的工具,Husky 的原理是让我们在项目根目录中写一个配置文件,然后在安装 Husky的时候把配置文件和 Git Hook 关联起来,这样我们就能在项目中使用 Git Hook 了
    • 安装 npm install husky -D
    • 配置 支持多种配置文件类型,也可以写在package.json文件中
    "husky": {
      "hooks": {
        "pre-commit": "npm run lint"
      }
    },
    

Usage with typescript

{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "prettier",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
      "no-var": 2,
      "no-console": 1
    }
}

       早期TS项目大多使用TSLint做代码检查,TSLint 和 TypeScript 采用同样的 AST 格式进行编译,但对于 JavaScript 生态的项目支持不够友好,因此 TypeScript 团队在 2019 年宣布全面转向 ESLint。

  • ESLint 通过解析器把代码转换成AST数据格式,这种数据格式被插件所使用,用来创建规则,来规范代码格式及行为。
  • TypeScript 通过一种解析器把代码转换成AST数据格式,这种数据格式被TS编译器使用,实现给用户反馈问题,让编码更准确。
           TypeScript 和 ESLint 使用不同的 AST 进行解析,因此为了在 ESLint 中支持 TypeScript 代码检查需要制作额外的自定义解析器,目的是为了能够解析 TypeScript 语法并转成与 ESLint 兼容的 AST。
           @typescript-eslint/parser由此产生,它会处理所有 ESLint 特定的配置并调用 @typescript-eslint/typescript-estree 生成 ESTree-compatible AST。

       @typescript-eslint/eslint-plugin配合 @typescript-eslint/parser一起使用的 ESLint 插件,可以设置 TypeScript 的校验规则。
typescript-eslint与babel-eslint 不可以同时使用,这样会导致typescript-eslint很难解析出非标准js语法代码的AST。

Usage with prettier

Prettier 是一个统一代码格式风格的工具

       ESLint 的规则校验同时包含了 格式规则 和 质量规则,但是大部分情况下只有 格式规则 可以通过 --fix 或 VS Code 插件的 Sava Auto Fix 功能一键修复,而 质量规则 更多的是发现代码可能出现的 Bug 从而防止代码出错,这类规则往往需要手动修复。因此 格式规则 并不是必须的,而 质量规则 则是必须的。 Prettier 与 ESLint 的区别在于 Prettier 专注于统一的格式规则,从而减轻 ESLint 在格式规则上的校验,而对于质量规则 则交给专业的 ESLint 进行处理。

  • 格式规则(Formatting rules):例如 max-len、keyword-spacing、 no-mixed-spaces-and-tabs等
  • 质量规则(Code-quality rules):例如 no-unused-vars、no-implicit-globals、prefer-promise-reject-errors等

总结

ESLint + prettier + husky + lint-staged 规范项目编码规范

  • ESLint 代码质量规范
  • Prettier 代码格式规范
  • Husky 代码提交规范
  • lint-staged 代码暂存区校验

参考资料