eslint从入门到放弃(七)eslint 之自定义规则

489 阅读8分钟

上文说了eslint配置之plugins(插件),今天说下ESLint之自定义规则,前文传送门,

eslint从入门到放弃(一)eslint入门

eslint从入门到放弃(二)esLint配置之globals

eslint从入门到放弃(三)ESLint配置之env(三)

eslint从入门到放弃(四)eslint配置之规则

eslint从入门到放弃(五)eslint配置之extends(共享配置)

eslint从入门到放弃(六)eslint 配置之plugins(插件)

大家好我是【小枫学幽默】,这是我eslint从入门到放弃系列教程的第七篇,欢迎关注后续更新。接下来步入正题:

ESLint配置之自定义规则

讲在前面

在开发过程中,我们可能会遇到一些代码规范,函数命名不能使用驼峰命名,函数命名不能使用下划线命名等等,这些规范我们都可以通过eslint来约束,但是eslint提供的规则可能并不能满足我们的需求,所以我们需要自定义规则。

今天做什么?

今天,我们创建一个叫no-forbidden-var的自定义规则,该规则用于禁止使用特定的变量名(例如forbiddenVar

准备工作

让我们先新建一个demo目录,文件结构如下

demo
├── .eslintrc.js # eslint配置文件
├── package.json # 项目配置文件
└── index.js # 入口文件

安装 eslint

npm install eslint --save-dev
# 当前demo 使用的 eslint 版本为 3.14.1

index.js中编写测试代码

// 既然今天是要禁止使用forbiddenVar作为变量名,
// 那我们就故意使用forbiddenVar作为变量名 【坏笑】
var forbiddenVar = 'forbiddenVar';

创建自定义规则

按照今天的计划,我们应该要创建一个叫no-forbidden-var的自定义规则,该规则用于禁止使用特定的变量名(例如forbiddenVar)。

Let's go...

demo根目录下创建一个名为rules的文件夹,用于存放我们自定义的no-forbidden-var规则。

  • 1、 在rules文件夹中创建一个名为no-forbidden-var.js的文件,

no-forbidden-var.js文件创建后目录结构如下

demo
├── .eslintrc.js # eslint配置文件
├── package.json # 项目配置文件
└── index.js # 测试文件
└── rules # 规则目录
└──-- no-forbidden-var.js # 规则
  • 2、 在no-forbidden-var.js编写以下代码:
module.exports = {
  meta: {
    // 规则类型
    /*
    "problem" 意味着该规则正在识别将导致错误或可能导致混乱行为的代码。开发人员应该把它作为一个高度优先事项来解决。
    "suggestion" 意味着该规则确定了一些可以用更好的方式完成的事情,但如果不改变代码,就不会发生错误。
    "layout" 意味着该规则主要关心的是空白、分号、逗号和括号,所有决定代码外观的部分,而不是代码的执行方式。这些规则对代码中没有在 AST 中指定的部分起作用。
    */
    type: "suggestion",
    // 规则文档
    docs: {
      description: "禁止使用特定的变量名",
    },
  },
  // 规则逻辑 实际编写规则逻辑的地方
  create(context) {
    // context 对象有哪些方法和属性
    // 点这个链接去看 => https://zh-hans.eslint.org/docs/latest/extend/custom-rules
    // 检查变量声明
    return {
      VariableDeclaration(node) {
        // 遍历这个节点的是所有变量声明
        node.declarations.forEach((declaration) => {
          // 判断变量名是否为forbiddenVar
          if (declaration.id.name === 'forbiddenVar') {
            // 如果为forbiddenVar则报告错误
            // 使用context.report方法上报发现的错误,用户就可以感知到这里有错误了
            context.report({
              node,
              message: '禁止使用forbiddenVar作为变量名',
            });
          }
        });
      },
    };
  },
};

嗯?create 函数是干啥的?

【熟悉 AST 语法树(Abstract Syntax Tree 即抽象语法树)同学可跳过这段, 不要问我为什么,问就是你们技术太牛X了】

create是插件的核心处理函数,它接收一个 context 对象作为参数,并返回一个对象,该对象包含一个或多个钩子函数。这些钩子函数会在特定类型节点被访问时被调用。 create函数的返回值是一个对象,该对象的属性名是AST节点类型,属性值是一个函数,该函数会在ESLint遍历到该类型的节点时被调用。

例如,如果create函数返回以下对象:

module.exports = {
  create(context) {
    return {
      VariableDeclaration(node) {
        // 当遍历到VariableDeclaration节点时调用此函数
        node.declarations.forEach((declaration) => {
          console.log(declaration.id.name);
        });
      }
    }
  }
}

那么,当ESLint遍历到VariableDeclaration节点时,会调用VariableDeclaration函数。这个函数可以用来检查节点的属性或报告错误。上面的代码会在遍历到VariableDeclaration节点时,在控制台中输出当前节点所有变量定义的变量名。

啥?这都是啥?AST ? 节点 ?节点类型...脑袋嗡嗡的吧...

别着急,你看AST语法树长这个样子,像不像 DOM 树,节点和节点类型都可以类比 DOM中的节点和节点类型

ast语法树.png

<看不懂看看下面这个例子>

module.exports = {
  create(context) {
    return {
      div(node) {
        // 当遍历到div节点时调用此函数
        node.childNodes.forEach((child) => {
          console.log(child.textContent);
        });
      }
    }
  }
}

module.exports = {
  create(context) {
    return {
      VariableDeclaration(node) {
        // 当遍历到VariableDeclaration节点时调用此函数
        node.declarations.forEach((declaration) => {
          console.log(declaration.id.name);
        });
      }
    }
  }
}

Are we clear ?

我怎么知道create函数的返回值应该包含哪些钩子函数?

看我们今天开发的规则要规范的代码是什么样子,从代码的 AST 语法树中判断哪些节点类型是需要处理的,然后去ESLint的官方文档中查找对应的钩子函数。

例如,如果我们要处理的代码是:

// 既然今天是要禁止使用forbiddenVar作为变量名,
// 那我们就故意使用forbiddenVar作为变量名 【坏笑】
var forbiddenVar = 'forbiddenVar';

它的AST语法树如下:(是不是很像 DOM 树?)

ast语法树.png

可以看到,forbiddenVar是一个VariableDeclaration节点(即节点类型为VariableDeclaration),它的id属性是一个Identifier节点,name属性就是forbiddenVar。所以,我们可以通过遍历VariableDeclaration节点,然后检查它的id.name属性是否为forbiddenVar,如果是,则报告一个错误。 所以 create函数的返回值应该包含一个名为 VariableDeclaration 的钩子函数,代码如下:

create(context) {
    // 检查变量声明
    return {
      VariableDeclaration(node) {
        // 遍历这个节点的是所有变量声明
        node.declarations.forEach((declaration) => {
          // 判断变量名是否为forbiddenVar
          if (declaration.id.name === 'forbiddenVar') {
            // 如果为forbiddenVar则报告错误
            // 使用context.report方法上报发现的错误,用户就可以感知到这里有错误了
            context.report({
              node,
              message: '禁止使用forbiddenVar作为变量名',
            });
          }
        });
      },
    };
}

如何查看AST语法树?

你可以使用ESLint提供的SourceCode对象的getAST方法来获取AST语法树。例如:

create(context) {
    const sourceCode = context.getSourceCode();
    const ast = sourceCode.ast;
    console.log(ast);
    return {
      VariableDeclaration(node) {
        // 遍历这个节点的是所有变量声明
      },
    };
}

这样就可以在控制台中看到AST语法树了。 你也可以使用AST Explorer工具来查看AST语法树。 AST Explorer工具的网址是:点我去look look

ESLint提供了许多内置的钩子函数,例如:

  • Program:在解析整个程序时调用。
  • VariableDeclaration:在解析变量声明时调用。
  • FunctionDeclaration:在解析函数声明时调用。
  • CallExpression:在解析函数调用时调用。
  • ...

你可以查看ESLint的官方文档,了解所有可用的钩子函数。你也可以查看其他ESLint插件的源代码,了解如何使用这些钩子函数。

.eslintrc.js中引用no-forbidden-var规则

module.exports = {
    "rules": {
        // 禁止使用未定义的变量
        "no-forbidden-var": ['error'],
    },
}

使用 eslint 检查代码

cd demo
# --rulesdir ./rules 指定本地自己开发的规则所在的目录
npx eslint index.js -c .eslintrc.js --rulesdir ./rules

检查结果

# demo
1:1  error  禁止使用forbiddenVar作为变量名  no-forbidden-var

等等,这个规则只能禁用forbiddenVar,不通用啊!!

规则支持使用时传入禁止使用的变量名数组

module.exports = {
  meta: {
    // 规则类型
    /*
    "problem" 意味着该规则正在识别将导致错误或可能导致混乱行为的代码。开发人员应该把它作为一个高度优先事项来解决。
    "suggestion" 意味着该规则确定了一些可以用更好的方式完成的事情,但如果不改变代码,就不会发生错误。
    "layout" 意味着该规则主要关心的是空白、分号、逗号和括号,所有决定代码外观的部分,而不是代码的执行方式。这些规则对代码中没有在 AST 中指定的部分起作用。
    */
    type: "suggestion",
    // 规则文档
    docs: {
      description: "禁止使用特定的变量名",
    },
    // 规则配置项
    schema: [
      // 定义参数为一个数组(array),没一个数组条目类型为 字符串(string)
      {
        type: "array",
        items: {
          type: "string",
        },
      },
    ],
  },
  // 规则逻辑 实际编写规则逻辑的地方
  create(context) {
    // context 对象有哪些方法和属性
    // 点这个链接去看 => https://zh-hans.eslint.org/docs/latest/extend/custom-rules

    // 获取用户提供的禁止使用的变量名数组
    const forbiddenVars = context.options[0] || [];

    // 检查变量名是否被禁止
    function isForbiddenVar(name) {
        return forbiddenVars.includes(name);
    }

    // 检查变量声明
    return {
      VariableDeclaration(node) {
        node.declarations.forEach((declaration) => {
          if (isForbiddenVar(declaration.id.name)) {
            // 如果为forbiddenVar则报告错误
            // 使用context.report方法上报发现的错误,用户就可以感知到这里有错误了
            context.report({
              node,
              message: `禁止使用${declaration.id.name}作为变量名`,
            });
          }
        });
      },
    };
  },
};

.eslintrc.js中配置no-forbidden-var规则,并传入禁止使用的变量名列表

module.exports = {
    "rules": {
        // 禁止使用未定义的变量
        "no-forbidden-var": ['error', ['aaa', 'bbb', 'ccc', 'a']],
    },
}

index.js中编写测试代码

var a = 'I am a';
var aaa = 'I am aaa';
var bbb = 'I am bbb';
var ccc = 'I am ccc';

使用 eslint 检查代码

cd demo
# --rulesdir ./rules 指定自定义规则目录
npx eslint index.js -c .eslintrc.js --rulesdir ./rules

检查结果

# demo
  1:1  error  禁止使用a作为变量名    no-forbidden-var
  2:1  error  禁止使用aaa作为变量名  no-forbidden-var
  3:1  error  禁止使用bbb作为变量名  no-forbidden-var
  4:1  error  禁止使用ccc作为变量名  no-forbidden-var

官方文档

点我查看官方文档

欢迎关注我的个人公众号「「小枫学幽默」」一起成长,一起分享生活!!