如何写编写自定义 ESLint 规则

因为译者还没有写过这个插件, 而且英文水平有限, 所以这篇文章里还存在很多错误, 也有部分看不懂而没法翻译的地方, 读者如果愿意斧正的话, 请发邮件到我的邮箱 olafcheng@gmail.com

后续我在发现文章里的更正确的翻译后, 也会更新这篇译文.

原文地址:https://eslint.org/docs/developer-guide/working-with-rules

注意: 这个文章里,主要覆盖率 ESLint >= 3.0.0 版本的规则,如果想要看已废弃的规则,请点这里.

每个规则,在 ESLint 里都有以这个规则为标识符为开头命名的三个文件(比如, no-extra-semi).

  • lib/rules 目录下: 一个源文件(例如,no-extra-semi.js
  • tests/lib/rules 目录下: 一个测试文件 (例如, no-extra-semi.js)
  • 在 docs/rules 目录: 一个 markdown 文档文件 (例如, no-extra-semi)

需要注意的重点:如果你想要给 ESLint 仓库提交一个核心规则,你必须遵循下列约定.

下面是规则源文件的基本格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @fileoverview Rule to disallow unnecessary semicolons
* @author Nicholas C. Zakas
*/

"use strict";

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

module.exports = {
meta: {
docs: {
description: "disallow unnecessary semicolons",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-extra-semi"
},
fixable: "code",
schema: [] // no options
},
create: function(context) {
return {
// callback functions
};
}
};

规则基础

规则的源文件会 exports 出一个带有下列字段的 object:

meta (object) 包含了规则的元数据:

  • docs (object) 对于 ESLint 的核心规则来说,是必须的:

    • description (string) 提供了会显示在规则目录里的简短描述
    • category (string) 规定了这个规则在规则目录中分数哪个部分
    • recommended (boolean) 是否需要包含在配置文件的 “extends”: “eslint:recommended” 这个属性默认开启的规则里.
    • url (string) 规定了可以在哪个地方可以访问这个规则的详细信息

在自定义规则或者插件里,可以不包含 docs 字段,其实其他字段也是一样的,可以去除掉.

  • fixable (string) 可选值有 “code” 和 “whitespace”, 这个字段决定当在命令行里加上--fix参数的时候, 要不要自动的纠正报错的地方

    注意: 如果没有 fixable 字段, 就算在实现规则的时候写了 fix 函数, ESLint 也不会对报错的地方进行纠正. 如果规则是不可自动纠正的话,请不要写 fixable 这个字段.

  • schema (array) 这个字段可以约束规则的写法, 以便 ESLint 发现有无效的规则配置文件 时进行报错.

    • deprecated (boolean) 用来表明这个规则是否已经被废弃. 如果规则还正在使用的话,请不要写这个字段.

create (function) 返回一个用来遍历(visit) JavaScript 代码的抽象语法树(一个按照ES 树来定义的 AST)节点的对象,这个对象上会挂有一些方法,以便 ESLint 在 AST 上调用:

  • if a key is a node type or a selector, ESLint calls that visitor function while going down the tree
  • if a key is a node type or a selector plus :exit, ESLint calls that visitor function while going up the tree
  • if a key is an event name, ESLint calls that handler function for code path analysis

规则可以利用当前的代码片段和附近的 AST 来报错或者纠正问题.

下面是 array-call-back 规则的一些方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function checkLastSegment (node) {
// report problem for function if last code path segment is reachable
}

module.exports = {
meta: { ... },
create: function(context) {
// declare the state of the rule
return {
ReturnStatement: function(node) {
// at a ReturnStatement node while going down
},
// at a function expression node while going up:
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment,
onCodePathStart: function (codePath, node) {
// at the start of analyzing a code path
},
onCodePathEnd: function(codePath, node) {
// at the end of analyzing a code path
}
};
}
};

上下文 Object

context 对象包含了一些额外的函数,可以帮助规则来完成工作. 顾名思义,context 对象包含了跟规则相关的一些上下文的信息.context 对象包含如下的字段:

  • parserOptions - 在运行的时候,将要用到的解析规则的参数(更多细节看这里)
  • id - 规则 ID.
  • options - 给这个规则传递的配置信息. 这个数组不包含规则本身. 详细信息看这里.
  • settings - 来自配置信息的共享设置.
  • parserPath - 来自配置信息的 parser 的名字
  • parserServices - 一个包含了规则的解析器服务的对象. 默认的解析器并不提供任何服务. 然而, 如果一个规则想要使用自定义的解释器的话, 它就可以通过 parserServices 来访问自定义解释器提供的各种结果. (例如, TypeScript 的解释器可以计算出来一个给定的节点的数据类型)

除此之外, context 对象还含有如下的方法:

  • getAncestors() - 返回一个包含了所有遍历过的父节点的数组, 这些父节点从 AST 的根节点开始, 直到这个节点的直接父节点. 这个数组不包含这个节点本身.
  • getDeclaredVariables(node) - 返回给定节点定义的变量的列表. 这个信息可以用来跟踪变量的引用信息.
    • 如果给定节点是一个 VariableDeclaration, 返回这个变量声明里声明的所有变量.
    • 如果给定节点是一个 VariableDeclarator, 返回这个变量声明里声明的所有变量(译者注: 两者的区别可以看这里).
    • 如果给定节点是一个 FunctionDeclaration 或者 FunctionExpression, 返回给参数传递的参数以及函数的 name 属性的值.
    • 如果给定节点是一个 ArrowFunctionExpression, 会返回传递给这个函数的参数.
    • 如果给定节点是 ClassDeclaration 或者 ClassDeclaration, 返回类的 name 属性的值.
    • 如果给定节点是 CatchClause, 返回异常处理函数里的变量.
    • 如果给定节点是 ImportDeclaration, 返回这个语句定义的变量.
    • 如果给定节点是 ImportSpecifier, ImportDefaultSpecifier 或者 ImportNamespaceSpecifier, 返回定义的变量.
    • 否则, 给定节点什么变量都没有定义的话, 会返回一个空数组.
  • getFilename() - 返回源文件的名称.
  • getScope() - 返回给定节点的作用域. 作用域的信息可以用来跟踪变量.
  • getSourceCode() - 返回基于源文件处理好的, 可以让 ESLint 进行工作的一个源文件对象.
  • markVariableAsUsed(name) - 把给定的作用域中的一个变量标记为已使用过. 这个会影响no-unused-vars规则. 如果这个变量在被标记以前的状态已经是被使用过了, 就返回 true, 否则返回 false.
  • report(descriptor) - 报告代码中存在的问题 (点这里看对应的部分).

注意: 在 ESLint 的早期版本, context 对象还支持额外的一些方法. 这些方法都已经在新的格式里被移除了, 并且不应该再被依赖了.

context.report()

写自定义规则的过程里, 最常用的一个方法是 context.report(), 这个方法可以报告一个 warning 或者 error(取决于你的配置文件怎么用的). 这个方法只接受一个对象作为参数, 这个对象, 包含如下的字段:

  • message - 问题的描述.
  • node - (可选) 与问题相关的 AST. 如果这个选项和 loc 都没有被指定, 这个节点的开始的地方, 就会被被当作问题的定位.
  • loc - (可选) 一个表示问题定位信息的对象. 如果 locnode 都被指定了, ESLint 会用 loc 参数来定位问题, 而不是 node.
    • start - 一个对象, 定位了问题从哪里开始.
      • line - 从 1 开始数的行数, 用于表示问题在第几行开始发生.
      • column - 从 0 开始数的列数, 用于表示问题在第几列开始发生.
    • end - 一个对象, 定位了问题在哪里结束.
      • line - 从 1 开始数的行数, 用于表示问题在第几行结束.
      • column - 从 0 开始数的列数, 用于表示问题在第几列开始结束.
  • data - (可选) 错误信息的占位符数据.
  • fix - (可选) 一个可以用来纠正问题的函数.
    注意, 参数里, nodeloc 至少需要配置一个.

最简单的报错的例子就是直接用 nodemessage:

1
2
3
4
context.report({
node: node,
message: "Unexpected identifier"
});

node 变量包括了查错所需要的必要信息, 包括行列信息, 还有简单的源代码的文本.

用 message 占位符

你也可以用占位符:

1
2
3
4
5
6
7
context.report({
node: node,
message: "Unexpected identifier: {{ identifier }}",
data: {
identifier: node.name
}
});

注意 message 里变量前后的空格是可选的.

node 里包含的信息, 同上.

messageIds

你可以在 context.report() 和测试文件里都打出报告信息, 也可以用一个 messageIds 来代替.

这样你就能避免重复打印信息了. It also prevents errors reported in different sections of your rule from having out-of-date messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// in your rule
module.exports = {
meta: {
messages: {
avoidName: "Avoid using variables named '{{ name }}'"
}
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
messageId: "avoidName",
data: {
name: "foo",
}
});
}
}
};
}
};

// in the file to lint:

var foo = 2;
// ^ error: Avoid using variables named 'foo'

// In your tests:
var rule = require("../../../lib/rules/my-rule");
var RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester();
ruleTester.run("my-rule", rule, {
valid: ["bar", "baz"],

invalid: [
{
code: "foo",
errors: [
{
messageId: "avoidName",
data: {
name: "foo"
}
}
]
}
]
});

进行修正

如果你想让 ESLint 在发现报告问题的时候, 尝试自动修正问题, 可以给 context.report() 指定一个 fix 函数. fix 函数接受一个用来修正的 fixer 对象作为参数, 用来修正问题. 比如:

1
2
3
4
5
6
7
context.report({
node: node,
message: "Missing semicolon",
fix: function(fixer) {
return fixer.insertTextAfter(node, ";");
}
});

这个 fix() 函数可以在给定节点后面插入一个分号. 要注意的是, 修正不是立即就进行的, 而且如果跟其他的修正函数有冲突的话, 这个修正可能不会进行. 在修正完以后, ESLint 会在修正完的代码上重新运行所有启用的校验规则, 所以可能会引发更多的修正. 在所有能被修正的问题都被修正完以前, 这个过程最多会重复 10 次. 在这以后, 问题会和平常一样被报告出来, 而不会自动修正.

Important: Unless the rule exports the meta.fixable property, ESLint does not apply fixes even if the rule implements fix functions.

重要: 即便实现了 fix 函数, ESLint 也不会启用修正功能, 除非规则 exportsmeta.fixable 字段.

fixer 对象包含以下方法:

  • insertTextAfter(nodeOrToken, text) - 在给定节点或者 token 后, 插入文本
  • insertTextAfterRange(range, text) - 在给定的 range 后插入文本
  • insertTextBefore(nodeOrToken, text) - 在给定节点或者 token 前, 插入文本
  • insertTextBeforeRange(range, text) - 在给定的 range 前插入文本
  • remove(nodeOrToken) - 移除给定节点或者 token
  • removeRange(range) - 在指定节点或者 token 中, 移除给定文本
  • replaceText(nodeOrToken, text) - 替换给定节点或者 token 中的文本
  • replaceTextRange(range, text) - 替换给定 range 中的文本

上面这些方法返回了一个 fixing 对象. fix() 函数可以返回如下值:

  • 一个 fixing 对象.
  • 一个包含了 fixing 对象的数组.
  • 一个可迭代枚举 fixing 对象的对象. 也就是说, fix() 对象可以是一个生成器.

如果你让 fix() 函数返回了多个 fixing 对象, 这些 fixing 对象不能是重复的.

有关修正的最佳实践:

  1. 避免任何能改变运行时行为的修正, 以避免代码停止运行.
  2. 作尽可能小范围的修正. 如果有些修正不必要的繁杂, 可能会导致跟其他的修正冲突, 于是这个修正就不会被应用了.
  3. 每个 message 只做一个修正. 这个是强制的, 因为在每个 fix() 函数执行完以后, 你都需要返回修正操作的结果.
  4. 既然在每次修复完成后, 都会重新运行所有的规则进行校验, 那在修正完成后, 就没有单独检查修正是否引起了新的错误的必要了.
      • 比如, 假设修正函数想让一个对象的键被引号包裹起来, 但是这个修正函数并不知道用户的脚本是用单引号还是双引号格式化的.
        1
        2
        3
        4
        5
        6
        7
        8
        9
        ({ foo : 1 })

        // should get fixed to either

        ({ 'foo': 1 })

        // or

        ({ "foo": 1 })
    • 这个修正函数选择的可能是单引号或者双引号里的其中一个. 如果猜错了, 修正后的代码会报引号相关的问题, 并且可以被 quotes 规则自动修正.

context.options

有些规则为了正常的工作, 需要配置选项. 这些选项会出现在配置信息里(.eslintrc, 命令行, 后者注释中). 比如:

1
2
3
{
"quotes": ["error", "double"]
}

quotes 规则在这个例子里有一个选项, "double"(error 是错误级别). 在写修正函数的时候, 可以用context.options 这个属性来得到一个这个规则的配置信息的数组. 在这个例子里, 访问 context.options[0] 会得到 "double":

1
2
3
4
5
6
7
module.exports = {
create: function(context) {
var isDouble = (context.options[0] === "double");

// ...
}
};

由于context.options刚好是一个数组, 你在拿到配置信息的时候, 可以直接得到到底有多少条配置被应用在这个规则上. 记住, 错误级别不属于 context.options, 错误级别在一个规则里也是不可以被获取或者被修改的.

当你的代码要读取配置信息的时候, 确保设置好了一些默认值, 以防使用这个规则的人没有传递任何配置信息.

context.getSourceCode()

如果你想要得到跟你正在 lint 的源代码的更多相关信息, 需要访问 SourceCode 这个对象. 通过调用 getSourceCode() 方法, 你可以随时获得 SourceCode 这个对象:

1
2
3
4
5
6
7
module.exports = {
create: function(context) {
var sourceCode = context.getSourceCode();

// ...
}
};

一旦拿到了 SourceCode 的实例, 就可以调用这个对象上的方法来操作代码了:

  • getText(node) - 返回给定节点的源代码. 如果不传递 node 参数, 会得到整份源代码.
  • getAllComments() - 返回一个包含源代码中所有注释的数组.
  • getCommentsBefore(nodeOrToken) - returns an array of comment tokens that occur directly before the given node or token.
  • getCommentsAfter(nodeOrToken) - returns an array of comment tokens that occur directly after the given node or token.
  • getCommentsInside(node) - 返回给定节点中的所有注释.
  • getJSDocComment(node) - 返回给定节点中的 JSDoc 注释节点, 如果没有的话, 就返回 null.
  • isSpaceBetweenTokens(first, second) - 如果在给定的两个节点之间有空格, 返回 true.
  • getFirstToken(node, skipOptions) - 返回给定节点中包含的第一个分词.
  • getFirstTokens(node, countOptions) - returns the first count tokens representing the given node.
  • getLastToken(node, skipOptions) - 返回给定节点里的最后一个分词.
  • getLastTokens(node, countOptions) - returns the last count tokens representing the given node.
  • getTokenAfter(nodeOrToken, skipOptions) - 返回给定节点或者分词后的第一个分词.
  • getTokensAfter(nodeOrToken, countOptions) - returns count tokens after the given node or token.
  • getTokenBefore(nodeOrToken, skipOptions) - 返回给定节点或者分词前的第一个分词.
  • getTokensBefore(nodeOrToken, countOptions) - returns count tokens before the given node or token.
  • getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions) - 返回两个给定节点或者分词间的第一个分词.
  • getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions) - returns the first count tokens between two nodes or tokens.
  • getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions) - 返回两个给定节点或者分词间的最后一个分词.
  • getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions) - returns the last count tokens between two nodes or tokens.
  • getTokens(node) - 返回给定节点的所有分词.
  • getTokensBetween(nodeOrToken1, nodeOrToken2) - 返回两个给定节点间的所有分词.
  • getTokenByRangeStart(index, rangeOptions) - 返回以给定索引为开始的所有分词.
  • getNodeByRangeIndex(index) - 返回包含给定源代码索引的层次最深的一个节点.
  • getLocFromIndex(index) - 返回给定源代码索引的坐标信息, 包含以 1 开始的行坐标 line与以 0 开始的列坐标 column.
  • getIndexFromLoc(loc) - 返回给定坐标定位到的源代码的坐标对象, loc 是一个坐标对象, 其包含以 1 开始的行坐标 line 与以 0 开始的列坐标 column.
  • commentsExistBetween(nodeOrToken1, nodeOrToken2) - 如果给定节点或分词间, 存在注释, 返回 true.

skipOptions 是一个有 3 个字段的对象, 包括 skip, includeCommentsfilter. 默认值为 {skip: 0, includeComments: false, filter: null}.

  • skip 是一个正整数, 表示跳过的分词的数量. 如果 filter 参数也有数值, 那么 filter 过滤掉的分词不会被计数在需要跳过的分词里.
  • includeComments 是一个布尔值, 是一个标志位, 用来标记返回结果里要不要出现注释分词.
  • filter 是一个第一个参数为分词的函数, 如果函数返回 false, 那么结果里就会排除掉这个分词.

countOptions 是一个拥有 3 个字段的对象; count, includeComments, 以及 filter. 默认值是 {count: 0, includeComments: false, filter: null}.

  • count 是一个正整数, 值为返回的所有分词的数量.
  • includeComments 是一个布尔值, 是一个标志位, 用来标记返回结果里要不要出现注释分词.
  • filter 是一个第一个参数为分词的函数, 如果函数返回 false, 那么结果里就会排除掉这个分词.
    rangeOptions 是包含一个字段的对象: includeComments.
  • includeComments 是一个布尔值, 是一个标志位, 用来标记返回结果里要不要出现注释分词.

还有一些别的你可以访问的字段:

  • hasBOM - 用来表示源代码是否含有 Unicode BOM 头
  • text - 需要被 lint 的完整的文本. Unicode BOM 头已经被去掉了.
  • ast - 需要被 lint 的代码的 AST.
  • scopeManager - 代码的作用域管理对象.
  • visitorKeys - 用于遍历这个 AST 的访问者 key.
  • lines - 代码分成行的数组, 分行是根据指定的换行符来进行的.

通过 SourceCode 对象, 你可以获得更多有关你要 lint 的代码的信息.

Deprecated

下列这些方法已经被废弃了, 并且在未来的版本里, 会从 ESLint 中移除掉:

  • getComments() - 被 getCommentsBefore(), getCommentsAfter(), 以及 getCommentsInside() 方法取代了
  • getTokenOrCommentBefore() - 被带有 { includeComments: true } 参数的 getTokenBefore() 方法取代了
  • getTokenOrCommentAfter() - 被带有 { includeComments: true } 参数的 getTokenAfter() 方法取代了

参数纲要

自定义的规则可能导出 schema 字段, 这个字段是一个基于 JSON 格式的 schema 描述, 这个 schema 会被用于校验用户提供给 ESLint 的参数的有效性, 避免用户给 context.options 传递一些无效或者异常的输入.

有两种导出 schema 的方式. 第一种是, 导出完整的 JSON schema 对象, 这个对象枚举了所有可能的规则参数的情况, 包括了错误级别(error level)第一个参数, 后面再加上几个额外的参数的情况.

然而, 为了简化 schema 的编写, 规则也可以在可选参数的后面跟上一个表示可以作为参数值的数组, ESLint 会首先对需要的错误级别进行校验. 比如, yoda 规则接收主要参数, 以及带有命名好的字段的额外的参数对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// "yoda": [2, "never", { "exceptRange": true }]
module.exports = {
meta: {
schema: [
{
"enum": ["always", "never"]
},
{
"type": "object",
"properties": {
"exceptRange": {
"type": "boolean"
}
},
"additionalProperties": false
}
];
},
};

在上面这个例子里, 错误级别被假定为了第一个参数. 紧随其后的, 是第一个可选参数, 参数的值可能是 "always" 或者 "never". 最后跟着的是一个对象形式的可选的参数, 这个对象表示的是, 这个参数名为 exceptRange, 值为布尔型.

为了进一步了解 JSON schema, 我们建议你先从看一些例子开始, 并且读一下 Understanding JSON Schema 这本书(免费的).

Note: 目前你需要使用完整的 JSON Schema 对象而不是数组, 以免你的 schema 里含有引用($ref), 因为在这种情况下, ESLint 会把数组转换为单个的 schema 而不更新引用, 结果就会导致出错(引用会被忽略掉).

得到源码

如果你需要对 JavaScript 源代码进行操作, 可以调用 sourceCode.getText() 来得到源代码. 这个方法的工作方式如下:

1
2
3
4
5
6
7
8
9
10
11
// get all source
var source = sourceCode.getText();

// get source for just this AST node
var nodeSource = sourceCode.getText(node);

// get source for AST node plus previous two characters
var nodeSourceWithPrev = sourceCode.getText(node, 2);

// get source for AST node plus following two characters
var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2);

如果 AST 没能提供正确的数据的话(比如逗号、分号、圆括号的定位信息等等), 你就可以直接对 JavaScript 文本进行操作来得到这些.

访问注释

因为注释在技术层面上来讲, 并不属于 AST 的一部分, 所以 ESlint 提供了一些访问注释的方法:

sourceCode.getAllComments()
这个方法返回一个数组, 数组里包含了从程序里找到的那些注释. 当开发者不关心注释的定位信息, 而是需要查看一下所有的注释的时候, 这个方法尤为有用.

sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside()
这些方法依次返回在给定节点之前、之后以及节点内部的注释. 当开发者想要查看跟给定节点或者分词相关的注释的时候, 这个方法尤为有用.

记住, 这些方法的结果是需要经过计算才能得到的.

遍历分词的方法

其实, 注释也可以通过很多的 sourceCode 对象的方法, 加上 includeCommecnts 参数来调用, 来进行访问.

访问 Shebangs

Shebangs 是类型为 "Shebang" 的分词. 其会被上面提到的这些方法, 当作注释来识别.

访问代码路径

ESLint 在遍历 AST 的时候会分析代码路径. 你可以通过跟代码路径相关的 5 个事件, 来访问代码路径.
细节看这里

对规则单元测试

在提交 ESLint 的核心规则的时候, 为了被 ESLint 库接受, 必须对每个规则增加一系列的单测文件. 单测文件跟规则的源文件同名, 不过放在 tests/lib/ 文件夹下. 比如, 如果源文件是 lib/rules/foo.js, 那测试文件就是 tests/lib/rules/foo.js.

ESLint 提供了 RuleTester 工具让写单测变得更简单.

性能测试

为了保持 lint 进程的高效性, 以及对开发者造成尽可能小的影响, 测试一下新规则以及对现有规则的修改对性能的影响, 是很有用的.

总体性能

当开发 ESLint 核心库的时候, npm run perf 命令会显示出来所有规则都执行的时候, 运行所需时间的概括情况.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ git checkout master
Switched to branch 'master'

$ npm run perf
CPU Speed is 2200 with multiplier 7500000
Performance Run #1: 1394.689313ms
Performance Run #2: 1423.295351ms
Performance Run #3: 1385.09515ms
Performance Run #4: 1382.406982ms
Performance Run #5: 1409.68566ms
Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms)

$ git checkout my-rule-branch
Switched to branch 'my-rule-branch'

$ npm run perf
CPU Speed is 2200 with multiplier 7500000
Performance Run #1: 1443.736547ms
Performance Run #2: 1419.193291ms
Performance Run #3: 1436.018228ms
Performance Run #4: 1473.605485ms
Performance Run #5: 1457.455283ms
Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms)

单个规则的性能

ESLint 有一个内置的方法来对单个的规则的性能进行跟踪. 设置一下 TIMING 环境变量, 就会触发跟踪结果的显示, 这些内容收集了耗时最长的十个规则里, 每个规则单独的耗时, 并且通过计算占总的规则的时间的百分比, 来标示出对性能的影响的相关性.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ TIMING=1 eslint lib
Rule | Time (ms) | Relative
:-----------------------|----------:|--------:
no-multi-spaces | 52.472 | 6.1%
camelcase | 48.684 | 5.7%
no-irregular-whitespace | 43.847 | 5.1%
valid-jsdoc | 40.346 | 4.7%
handle-callback-err | 39.153 | 4.6%
space-infix-ops | 35.444 | 4.1%
no-undefined | 25.693 | 3.0%
no-shadow | 22.759 | 2.7%
no-empty-class | 21.976 | 2.6%
semi | 19.359 | 2.3%

如果想要明确的测试某个规则, 可以对命令加上 --no-eslintrc--rule 参数:

1
2
3
4
$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib
Rule | Time (ms) | Relative
:------|----------:|--------:
quotes | 18.066 | 100.0%

规则命名约定

ESLint 的规则命名约定, 是相当简单的:

  • 如果你的规则是为了禁用某些东西, 请用 no- 作为前缀, 比如 no-eval 表示禁止使用 eval(), no-debugger 表示禁止使用 debugger.
  • 如果你的规则是为了强制要求包含某些东西, 不要加前缀, 只要用简写来命名就行了.
  • 在单词之间用短破折号(-).

运行时规则

之所以 ESLint 独一无二, 是因为 ESLint 能够自定义运行时规则. 当你需要给你自己的项目或者公司制定某个规则, 但是有不需要发给 ESLint 官方的仓库的时候, 这个特性就很完美. 有了运行时规则, 你就不需要等下个版本的 ESLint 了, 或者因为你自己写的规则并不适合发表到大型 JavaScript 社区而感到沮丧, 只管写就是了, 然后在运行时里引入.

运行时规则写起来和其他格式并无二致. 写的时候就跟写其他规则一样, 写完了以后, 再执行下面这些步骤:

  1. 把你的所有运行时规则都放到同一个目录下(比如, eslint_rules).
  2. 写一个配置文件, 在 rules 字段里, 表明你的规则 ID 以及对应的错误级别. 你的规则如果想要运行, 需要给 1 或者 2 作为值.
  3. 命令行(CLI)加上 --rulesdir 参数, 来指定你的运行时规则所在的文件夹.