ESLint Handbook

ESLint 是什么

ESLint 是一种用于 ECMAScript/JavaScript 的检查工具,其目标是使代码更加一致并避免错误,有以下特性:

  • 使用 Espress 作为解析器;
  • 在代码中基于 AST 来执行操作
  • 完全可插拔(每一条规则都是一个插件)

总结起来就是:ESLint 是一个基于 AST 来实现 ECMAScript/JavaScript 的检查工具,主要用来保证代码质量,且有非常多的可配置项。

ESLint 的黄金搭档 Prettier

在团队开发中,保证代码质量是必须的,但是代码的风格也需要统一,解决方案就是 Prettier

Prettier 认为代码风格很重要,但是呢,你们也别定制了,用我就完事儿了,其他的不用管了。

哪些是代码风格:

因为 Prettier 和 ESLint 其实是存在一部分重叠的功能,因此必然存在冲突项,想要让 ESLint 和 Prettier 和谐的跑起来,就需要使用 eslint-config-prettier 插件,来解决搭档间的小冲突。

npm install --save-dev eslint-config-prettier
复制代码
{
  "extends": [
    "some-other-config-you-use",
    "prettier" // 一定要将 prettier 声明在最后,才能有效的覆盖有冲突的配置项
  ]
}
复制代码

如果想让所有的报错信息都由 ESLint 来处理,那么可以使用 eslint-plugin-prettier 这个插件,将 prettier 当做 ESLint 的插件来使用。

怎么配置 ESLint

两种方式配置:

  1. 在文件中通过注释配置
  2. 通过特定的配置文件
  • .eslintrc.* (js|json|YAML)格式的配置文件
  • package.json 中的 eslintConfig

配置文件中典型的配置项:

  • Environments 指定 js 运行的环境,以自动注入全局变量,比如 nodejs es5 es6
  • Globals 指定自定义的全局变量
  • Rules 所有的规则配置项
  • Plugins 插件配置项目

微信小程序中使用的 .eslintrc.js 文件 DEMO :

module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
  },
  extends: ['eslint:recommended'],
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 12
  },
  rules: {
    indent: ['error', 'tab', { SwitchCase: 1 }],
    'linebreak-style': ['error', 'unix'],
    quotes: ['error', 'single'],
    semi: ['error', 'always'],
    'object-shorthand': ['error', 'always'],
    'one-var': [
      2,
      {
        var: 'never',
        let: 'never',
        const: 'never',
      },
    ],
    'no-invalid-regexp': 'error'
  },
  globals: {
    getApp: false,
    wx: false,
    Page: false,
    getCurrentPages: false,
    Component: false,
    App: false,
    Behavior: false,
    requirePlugin: false,
  },
};
复制代码

因为 ESLint 的可配置项非常多,最佳实践就是继承通用的校验规则,比如 "extends": ["eslint:recommended"],然后再进行团队定制。

这个地方需要知道的小知识:

.eslintrc.* 会从检查的文件路径开始逐级向上搜索配置文件,直到根目录或则配置中包含有 root: true 才会停止搜索,途中所有的 .eslintrc.* 文件配置会被合并,根据就近优先原则,对冲突配置进行覆盖。

如果项目的 package.json 文件中指定了 eslintConfig 项,那么这个配置会被生效于项目的所有子目录,根据就近优先原则对冲突配置进行覆盖。

怎么基于 ESLint 进行创作

官方为了简化开发操作,已经基于 yo 定制了脚手架 generator-eslint,因此我们只需要几行命令就可以轻松搭建好开发环境。

# 全局安装 yo
npm install -g yo

# 安装 eslint 官方脚手架
npm install -g generator-eslint
复制代码

创建插件

# 创建一个项目文件夹比如
mkdir eslint-wd

# 进入目录
cd eslint-wd

# 通过脚手架创建基础文件,根据引导选择包含 rules

yo eslint:plugin

# 最后会生成如下结构的目录
# .
# ├── README.md
# ├── lib
# │   ├── index.js
# │   ├── processors
# │   └── rules
# ├── package-lock.json
# ├── package.json
# └── tests
#     └── lib
#        ├── processors
#        └── rules
复制代码

创建 rule

# 创建自定义 rule 我们依然使用脚手架,在 eslint-wd 目录中执行
# 我们创建一个文件中不允许使用 require 引入模块变量为例子

yo eslint:rule

# 根据引导完成配置
#? What is your name? amin
#? Where will this rule be published? ESLint Plugin
#? What is the rule ID? no-require
#? Type a short description of this rule: 禁止项目中使用 require
#? Type a short example of the code that will fail: const fs = require('fs');
#   create docs/rules/no-require.md
#   create lib/rules/no-require.js
#   create tests/lib/rules/no-require.js
复制代码

根据脚手架引导之后,会生成三个文件:

  • docs/rules/no-require.md 文档描述
  • lib/rules/no-require.js 核心逻辑
  • tests/lib/rules/no-require.js 单元测试

进入 lib/rules/no-require.js 文件中,主要有两个核心部分:

  • meta 元数据
    • type 指定规则的类型

      • "problem" 可能会导致错误或者异常行为
      • "suggestion" 一些优化建议,不修改也不会导致错误
      • "layout" 代码风格
    • doc 对规则的一些文字描述还有文档地址

      • description(string)
      • category(string)
      • recommended(boolean) "extends": "eslint:recommended" 是否启用该规则
      • url(string)
    • fixable

      • "code"
      • "whitespace"
    • schema 配置说明

  • create(context) {} 函数返回一个对象,对象中定义各种在 AST 深度遍历中的回调函数,简单的遍历可以直接以语法树中的节点类型作为函数名即可,更多用法可查看文档

下面我以创建一个 禁用 require() 的插件为例,讲解过程:

首先,到 语法树在线预览网站 查看 AST

const fs = require('fs');
const { exec } = require('child_process');
复制代码

将代码贴进去后,可以在右侧看到 AST 的结构:

WechatIMG328.png

可以看到,我们要识别出 require ,我们只需要关注 CallExpression 类型的节点,并且这个节点的 callee 属性是一个 Identifier, 且 name="require",那么代码就很容易实现了:

/**
 * @fileoverview 禁止项目中使用 require
 * @author amin`
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/**
 * @type {import('eslint').Rule.RuleModule}
 */
module.exports = {
  meta: {
    type: "suggestion", // `problem`, `suggestion`, or `layout`
    docs: {
      description: "禁止项目中使用 require",
      category: "Fill me in",
      recommended: false,
      url: null, // URL to the documentation page for this rule
    },
    fixable: null, // Or `code` or `whitespace`
    schema: [], // Add a schema if the rule has options
    messages: {
      avoidRequire: 'avoid use {{name}}',
    }
  },

  create(context) {
    // variables should be defined here

    //----------------------------------------------------------------------
    // Helpers
    //----------------------------------------------------------------------

    // any helper functions should go here or else delete this section

    //----------------------------------------------------------------------
    // Public
    //----------------------------------------------------------------------

    return {
      // visitor functions for different types of nodes
      CallExpression(node) {
        const callee = node.callee;
        if (callee && callee.type === 'Identifier' && callee.name === 'require') {
          context.report({
            node,
            messageId: 'avoidRequire', // 对应 meta 中定义的 messages 部分
            data: {
              name: callee.name,
            },
          });
        }
      }
    };
  },
};
复制代码

完善 Jest 测试:

/**
 * @fileoverview 禁止项目中使用 require
 * @author fumin
 */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-require"),
  RuleTester = require("eslint").RuleTester;


//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module' } });
ruleTester.run("no-require", rule, {
  valid: [
    'import fs from "fs"',
    'import { exec } from "child_process"'
  ],

  invalid: [
    {
      code: "const fs = require('fs');",
      // output: 'avoid use require',
      errors: [{ messageId: 'avoidRequire' }],
    },
  ],
});
复制代码

写完测试用例后,可以执行 npm run test 来校验我们写的功能是否正确,校验通过后,我们可以到实际项目中来验证。

在插件根目录,通过 npm link 命令,将插件软链到全局目录中

在需要使用项目中,执行 npm link eslint-plugin-wd ,项目的 node_modules 中会创建出该插件软链

.eslintrc.* 文件中引入插件,在规则中配置规则:

{
  plugins: ['eslint-plugin-wd'],
  rules: {
    'eslint-plugin-wd/no-require': 'error',
  },
}
复制代码

这样项目中就启用了,刚创建的规则。

猜你喜欢

转载自juejin.im/post/7086102578916196366