自定义规则
您可以创建自定义规则以与 ESLint 一起使用。如果核心规则未涵盖您的用例,您可能需要创建自定义规则。
以下是自定义规则的基本格式
// customRule.js
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Description of the rule",
},
fixable: "code",
schema: [] // no options
},
create: function(context) {
return {
// callback functions
};
}
};
规则结构
规则的源文件导出一个具有以下属性的对象。自定义规则和核心规则都遵循此格式。
meta
: (object
) 包含规则的元数据
-
type
: (string
) 指示规则的类型,可以是"problem"
、"suggestion"
或"layout"
之一"problem"
: 该规则正在识别可能导致错误或可能导致混淆行为的代码。开发人员应将其视为需要优先解决的问题。"suggestion"
: 该规则正在识别可以以更好方式完成的事情,但如果代码未更改,则不会发生错误。"layout"
: 该规则主要关心空格、分号、逗号和括号,所有决定代码外观而不是代码执行方式的程序部分。这些规则作用于 AST 中未指定的代码部分。
-
docs
: (object
) 通常用于文档生成和工具的属性。核心规则需要,自定义规则可选。自定义规则可以根据需要在此处包含其他属性。description
: (string
) 提供规则的简短描述。对于核心规则,这用于规则索引中。recommended
: (boolean
) 对于核心规则,这指定是否通过来自@eslint/js
的recommended
配置启用该规则。url
: (string
) 指定可以访问完整文档的 URL。代码编辑器通常使用它在突出显示的规则违规行为上提供有用的链接。
-
fixable
: (string
) 如果命令行上的--fix
选项命令行自动修复规则报告的问题,则为"code"
或"whitespace"
之一。重要:
fixable
属性对于可修复规则是必需的。如果未指定此属性,则每当规则尝试生成修复时,ESLint 都会抛出错误。如果规则不可修复,则省略fixable
属性。 -
hasSuggestions
: (boolean
) 指定规则是否可以返回建议(如果省略,则默认为false
)。重要:
hasSuggestions
属性对于提供建议的规则是必需的。如果此属性未设置为true
,则每当规则尝试生成建议时,ESLint 都会抛出错误。如果规则不提供建议,则省略hasSuggestions
属性。 -
schema
: (object | array | false
) 指定选项,以便 ESLint 可以防止无效的规则配置。当规则具有选项时,这是必需的。 -
defaultOptions
: (array
) 指定规则的默认选项。如果存在,则其配置中的任何用户提供的选项都将递归地合并到其之上。 -
deprecated
: (boolean
) 指示规则是否已弃用。如果规则尚未弃用,则可以省略deprecated
属性。 -
replacedBy
: (array
) 在弃用规则的情况下,指定替换规则。
create()
: 返回一个对象,其中包含 ESLint 在遍历 JavaScript 代码的抽象语法树 (AST,由ESTree定义) 时调用以“访问”节点的方法
- 如果键是节点类型或选择器,则 ESLint 在向下遍历树时会调用该访问者函数。
- 如果键是节点类型或选择器加上
:exit
,则 ESLint 在向上遍历树时会调用该访问者函数。 - 如果键是事件名称,则 ESLint 会为代码路径分析调用该处理程序函数。
规则可以使用当前节点及其周围的树来报告或修复问题。
以下是array-callback-return规则的方法
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
}
};
}
};
上下文对象
context
对象是规则中 create
方法的唯一参数。例如
// customRule.js
module.exports = {
meta: { ... },
// `context` object is the argument
create(context) {
// ...
}
};
顾名思义,context
对象包含与规则上下文相关的信息。
context
对象具有以下属性
id
: (string
) 规则 ID。filename
: (string
) 与源关联的文件名。physicalFilename
: (string
) 在 lint 文件时,它提供磁盘上文件的完整路径,没有任何代码块信息。在 lint 文本时,它提供传递给—stdin-filename
或<text>
的值(如果未指定)。cwd
: (string
) 传递给Linter的cwd
选项。它是应被视为当前工作目录的目录的路径。options
: (array
) 此规则的已配置选项的数组。此数组不包括规则严重性(请参阅专用部分)。sourceCode
: (object
) 一个SourceCode
对象,您可以使用它来处理传递给 ESLint 的源代码(请参阅访问源代码)。settings
: (object
) 来自配置的共享设置。languageOptions
: (object
) 每个属性的更多详细信息此处sourceType
: ('script' | 'module' | 'commonjs'
) 当前文件的模式。ecmaVersion
: (number
) 用于解析当前文件的 ECMA 版本。parser
: (object
): 用于解析当前文件的解析器。parserOptions
: (object
) 为此文件配置的解析器选项。globals
: (object
) 指定的全局变量。
parserPath
: (string
, 已删除 请改用context.languageOptions.parser
。) 来自配置的parser
的名称。parserOptions
: (已弃用 请改用context.languageOptions.parserOptions
。) 为此运行配置的解析器选项(更多详细信息此处)。
此外,context
对象具有以下方法
getCwd()
: (已弃用:请改用context.cwd
。) 返回传递给Linter的cwd
选项。它是应被视为当前工作目录的目录的路径。getFilename()
: (已弃用:请改用context.filename
。) 返回与源关联的文件名。getPhysicalFilename()
: (已弃用:请改用context.physicalFilename
。) 在 lint 文件时,它返回磁盘上文件的完整路径,没有任何代码块信息。在 lint 文本时,它返回传递给—stdin-filename
或<text>
的值(如果未指定)。getSourceCode()
: (已弃用:请改用context.sourceCode
。) 返回一个SourceCode
对象,您可以使用它来处理传递给 ESLint 的源代码(请参阅访问源代码)。report(descriptor)
。报告代码中的问题(请参阅专用部分)。
注意:早期版本的 ESLint 支持 context
对象上的其他方法。这些方法在新格式中已被移除,不应依赖它们。
报告问题
编写自定义规则时,您将使用的主要方法是 context.report()
,它会发布警告或错误(取决于使用的配置)。此方法接受一个参数,该参数是一个包含以下属性的对象
messageId
:(string
) 消息的 ID(请参阅messageId)(推荐使用,而非message
)。message
:(string
) 问题消息(messageId
的替代方案)。node
:(可选object
) 与问题相关的 AST 节点。如果存在且未指定loc
,则节点的起始位置将用作问题的位置。loc
:(可选object
) 指定问题的位置。如果同时指定了loc
和node
,则使用loc
中的位置,而不是node
。start
:起始位置的对象。line
:(number
) 发生问题的行号(从 1 开始)。column
:(number
) 发生问题的列号(从 0 开始)。
end
:结束位置的对象。line
:(number
) 发生问题的行号(从 1 开始)。column
:(number
) 发生问题的列号(从 0 开始)。
data
:(可选object
)message
的占位符 数据。fix(fixer)
:(可选function
) 应用修复以解决问题。
请注意,至少需要 node
或 loc
之一。
最简单的示例是仅使用 node
和 message
context.report({
node: node,
message: "Unexpected identifier"
});
该节点包含所有必要的信息,以确定违规文本的行号和列号,以及表示该节点的源文本。
使用消息占位符
您也可以在消息中使用占位符并提供 data
context.report({
node: node,
message: "Unexpected identifier: {{ identifier }}",
data: {
identifier: node.name
}
});
请注意,消息参数中的前导和尾随空格是可选的。
该节点包含所有必要的信息,以确定违规文本的行号和列号,以及表示该节点的源文本。
messageId
messageId
是在 context.report()
调用中报告消息的推荐方法,因为它具有以下优势
- 规则违规消息可以存储在中央
meta.messages
对象中,以便于管理 - 规则违规消息无需在规则文件和规则测试文件中重复
- 因此,更改规则违规消息的门槛更低,鼓励更频繁地贡献以改进和优化它们,以获得最大的清晰度和实用性
规则文件
// avoid-name.js
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",
}
});
}
}
};
}
};
在要 lint 的文件中
// someFile.js
var foo = 2;
// ^ error: Avoid using variables named 'foo'
在您的测试中
// avoid-name.test.js
var rule = require("../../../lib/rules/avoid-name");
var RuleTester = require("eslint").RuleTester;
var ruleTester = new RuleTester();
ruleTester.run("avoid-name", rule, {
valid: ["bar", "baz"],
invalid: [
{
code: "foo",
errors: [
{
messageId: "avoidName"
}
]
}
]
});
应用修复
如果您希望 ESLint 尝试修复您报告的问题,则可以在使用 context.report()
时通过指定 fix
函数来做到这一点。fix
函数接收一个参数,一个 fixer
对象,您可以使用它来应用修复。例如
context.report({
node: node,
message: "Missing semicolon",
fix(fixer) {
return fixer.insertTextAfter(node, ";");
}
});
这里,fix()
函数用于在节点后插入分号。请注意,修复不会立即应用,如果与其他修复存在冲突,则可能根本不会应用。应用修复后,ESLint 将再次对已修复的代码运行所有启用的规则,可能会应用更多修复。此过程将重复最多 10 次,或直到不再发现可修复的问题。之后,任何剩余的问题都将照常报告。
重要:对于可修复规则,meta.fixable
属性是必须的。如果实现 fix
函数的规则未导出 meta.fixable
属性,则 ESLint 将抛出错误。
fixer
对象具有以下方法
insertTextAfter(nodeOrToken, text)
:在给定的节点或标记后插入文本。insertTextAfterRange(range, text)
:在给定范围后插入文本。insertTextBefore(nodeOrToken, text)
:在给定的节点或标记前插入文本。insertTextBeforeRange(range, text)
:在给定范围前插入文本。remove(nodeOrToken)
:移除给定的节点或标记。removeRange(range)
:移除给定范围内的文本。replaceText(nodeOrToken, text)
:替换给定节点或标记中的文本。replaceTextRange(range, text)
:替换给定范围内的文本。
range
是一个包含源代码中字符索引的两个元素数组。第一个元素是范围的开始(包含),第二个元素是范围的结束(不包含)。每个节点和标记都有一个 range
属性来标识它们表示的源代码范围。
上述方法返回一个 fixing
对象。fix()
函数可以返回以下值
- 一个
fixing
对象。 - 一个包含
fixing
对象的数组。 - 一个枚举
fixing
对象的可迭代对象。特别是,fix()
函数可以是一个生成器。
如果您创建了一个返回多个 fixing
对象的 fix()
函数,则这些 fixing
对象不得重叠。
修复的最佳实践
- 避免任何可能更改代码运行时行为并导致其停止工作的修复。
- 使修复尽可能小。过大的修复可能会与其他修复冲突,并阻止它们被应用。
- 每个消息只进行一次修复。这是强制执行的,因为您必须从
fix()
中返回修复程序操作的结果。 - 由于在应用第一轮修复后会再次运行所有规则,因此规则无需检查修复的代码风格是否会导致其他规则报告错误。
-
例如,假设一个修复程序希望用引号括起对象键,但它不确定用户是否更喜欢单引号或双引号。
({ foo : 1 }) // should get fixed to either ({ 'foo': 1 }) // or ({ "foo": 1 })
-
此修复程序可以任意选择一种引号类型。如果猜测错误,则生成的代码将由
quotes
规则自动报告和修复。
-
注意:使修复尽可能小是一种最佳实践,但在某些情况下,扩展修复范围以有意阻止其他规则在同一遍中对周围范围进行修复可能是正确的。例如,如果替换文本声明了一个新变量,则阻止变量作用域中的其他更改可能很有用,因为它们可能会导致名称冲突。
以下示例替换了 node
,并确保在同一遍中不会对 node.parent
的范围内应用其他修复
context.report({
node,
message,
*fix(fixer) {
yield fixer.replaceText(node, replacementText);
// extend range of the fix to the range of `node.parent`
yield fixer.insertTextBefore(node.parent, "");
yield fixer.insertTextAfter(node.parent, "");
}
});
冲突修复
冲突修复是指对源代码的同一部分应用不同更改的修复。无法指定应用哪个冲突修复。
例如,如果两个修复程序想要修改字符 0 到 5,则只应用一个。
提供建议
在某些情况下,修复不适合自动应用,例如,如果修复可能更改功能,或者根据实现意图有多种有效的方法来修复规则(请参阅上面列出的应用修复的最佳实践)。在这些情况下,context.report()
上有一个替代的 suggest
选项,允许其他工具(例如编辑器)为用户公开帮助程序以手动应用建议。
要提供建议,请在报告参数中使用 suggest
键,并使用建议对象的数组。建议对象表示可以应用的单个建议,并且需要一个 desc
键字符串来描述应用建议会做什么,或者一个 messageId
键(请参阅下面),以及一个 fix
键,该键是一个定义建议结果的函数。此 fix
函数遵循与常规修复相同的 API(如上文应用修复中所述)。
context.report({
node: node,
message: "Unnecessary escape character: \\{{character}}.",
data: { character },
suggest: [
{
desc: "Remove the `\\`. This maintains the current functionality.",
fix: function(fixer) {
return fixer.removeRange(range);
}
},
{
desc: "Replace the `\\` with `\\\\` to include the actual backslash character.",
fix: function(fixer) {
return fixer.insertTextBeforeRange(range, "\\");
}
}
]
});
重要:对于提供建议的规则,meta.hasSuggestions
属性是必须的。如果规则尝试生成建议但未导出此属性,则 ESLint 将抛出错误。
注意:建议作为独立的更改应用,不会触发多遍修复。每个建议都应专注于代码中的单个更改,并且不应尝试符合用户定义的样式。例如,如果建议将新的语句添加到代码库中,则不应尝试匹配正确的缩进或符合用户对分号存在/不存在的偏好。所有这些事情都可以在用户触发时通过多遍自动修复来更正。
建议的最佳实践
- 不要尝试做太多事情,并建议可能引入大量重大更改的大型重构。
- 如上所述,不要尝试符合用户定义的样式。
建议旨在提供修复。如果建议的 fix
函数返回 null
或空数组/序列,则 ESLint 将自动从 lint 输出中删除整个建议。
建议 messageId
可以使用 messageId
代替建议的 desc
键。它的工作方式与整体错误的 messageId
相同(请参阅messageId)。以下是如何在规则中使用建议 messageId
的示例
module.exports = {
meta: {
messages: {
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove the `\\`. This maintains the current functionality.",
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
hasSuggestions: true
},
create: function(context) {
// ...
context.report({
node: node,
messageId: 'unnecessaryEscape',
data: { character },
suggest: [
{
messageId: "removeEscape", // suggestion messageId
fix: function(fixer) {
return fixer.removeRange(range);
}
},
{
messageId: "escapeBackslash", // suggestion messageId
fix: function(fixer) {
return fixer.insertTextBeforeRange(range, "\\");
}
}
]
});
}
};
建议消息中的占位符
您也可以在建议消息中使用占位符。它的工作方式与整体错误的占位符相同(请参阅使用消息占位符)。
请注意,您必须在建议的对象上提供 data
。建议消息不能使用整体错误的 data
中的属性。
module.exports = {
meta: {
messages: {
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove `\\` before {{character}}.",
},
hasSuggestions: true
},
create: function(context) {
// ...
context.report({
node: node,
messageId: "unnecessaryEscape",
data: { character }, // data for the unnecessaryEscape overall message
suggest: [
{
messageId: "removeEscape",
data: { character }, // data for the removeEscape suggestion message
fix: function(fixer) {
return fixer.removeRange(range);
}
}
]
});
}
};
访问传递给规则的选项
某些规则需要选项才能正常运行。这些选项出现在配置(.eslintrc
、命令行界面或注释)中。例如
{
"quotes": ["error", "double"]
}
此示例中的 quotes
规则有一个选项 "double"
(error
是错误级别)。您可以使用 context.options
检索规则的选项,它是一个包含规则每个配置选项的数组。在这种情况下,context.options[0]
将包含 "double"
module.exports = {
meta: {
schema: [
{
enum: ["single", "double", "backtick"]
}
]
},
create: function(context) {
var isDouble = (context.options[0] === "double");
// ...
}
};
由于 context.options
只是一个数组,因此您可以使用它来确定传递了多少个选项以及检索实际的选项本身。请记住,错误级别不是 context.options
的一部分,因为错误级别无法从规则内部知道或修改。
使用选项时,请确保您的规则在未提供选项的情况下具有一些逻辑默认值。
带有选项的规则必须指定模式。
访问源代码
SourceCode
对象是获取有关正在 lint 的源代码的更多信息的主要对象。您可以随时使用 context.sourceCode
属性检索 SourceCode
对象
module.exports = {
create: function(context) {
var sourceCode = context.sourceCode;
// ...
}
};
已弃用:context.getSourceCode()
方法已弃用;请确保改用 context.sourceCode
属性。
获得 SourceCode
的实例后,您可以在其上使用以下方法来处理代码
getText(node)
:返回给定节点的源代码。省略node
以获取整个源代码(请参阅专用部分)。getAllComments()
:返回源代码中所有注释的数组(请参阅专用部分)。getCommentsBefore(nodeOrToken)
:返回在给定节点或标记之前直接出现的注释标记数组(请参阅专用部分)。getCommentsAfter(nodeOrToken)
:返回在给定节点或标记之后直接出现的注释标记数组(请参阅专用部分)。getCommentsInside(node)
:返回给定节点内所有注释标记的数组(请参阅专用部分)。isSpaceBetween(nodeOrToken, nodeOrToken)
:如果两个标记之间存在空格字符,则返回 true;或者,如果给定的是节点,则返回第一个节点的最后一个标记和第二个节点的第一个标记之间是否存在空格字符。getFirstToken(node, skipOptions)
:返回表示给定节点的第一个标记。getFirstTokens(node, countOptions)
:返回表示给定节点的前面count
个标记。getLastToken(node, skipOptions)
:返回表示给定节点的最后一个标记。getLastTokens(node, countOptions)
:返回表示给定节点的后面count
个标记。getTokenAfter(nodeOrToken, skipOptions)
:返回给定节点或标记之后的第一个标记。getTokensAfter(nodeOrToken, countOptions)
:返回给定节点或标记之后的count
个标记。getTokenBefore(nodeOrToken, skipOptions)
:返回给定节点或标记之前的第一个标记。getTokensBefore(nodeOrToken, countOptions)
:返回给定节点或标记之前的count
个标记。getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)
:返回两个节点或标记之间的第一个标记。getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)
:返回两个节点或标记之间的前count
个标记。getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)
:返回两个节点或标记之间的最后一个标记。getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)
:返回两个节点或标记之间的后count
个标记。getTokens(node)
:返回给定节点的所有标记。getTokensBetween(nodeOrToken1, nodeOrToken2)
:返回两个节点之间的所有标记。getTokenByRangeStart(index, rangeOptions)
:返回在源代码中范围起始于给定索引的标记。getNodeByRangeIndex(index)
:返回 AST 中包含给定源代码索引的最深节点。getLocFromIndex(index)
:返回一个包含line
和column
属性的对象,分别对应于给定源代码索引的位置。line
从 1 开始,column
从 0 开始。getIndexFromLoc(loc)
:返回给定位置在源代码中的索引,其中loc
是一个对象,包含一个从 1 开始的line
键和一个从 0 开始的column
键。commentsExistBetween(nodeOrToken1, nodeOrToken2)
:如果两个节点之间存在注释,则返回true
。getAncestors(node)
:返回给定节点的祖先节点数组,从 AST 的根节点开始,一直到给定节点的直接父节点。此数组不包含给定节点本身。getDeclaredVariables(node)
:返回给定节点声明的 变量 列表。此信息可用于跟踪对变量的引用。- 如果节点是
VariableDeclaration
,则返回声明中声明的所有变量。 - 如果节点是
VariableDeclarator
,则返回声明器中声明的所有变量。 - 如果节点是
FunctionDeclaration
或FunctionExpression
,则除了函数参数的变量外,还返回函数名称的变量。 - 如果节点是
ArrowFunctionExpression
,则返回参数的变量。 - 如果节点是
ClassDeclaration
或ClassExpression
,则返回类名称的变量。 - 如果节点是
CatchClause
,则返回异常的变量。 - 如果节点是
ImportDeclaration
,则返回其所有说明符的变量。 - 如果节点是
ImportSpecifier
、ImportDefaultSpecifier
或ImportNamespaceSpecifier
,则返回声明的变量。 - 否则,如果节点没有声明任何变量,则返回一个空数组。
- 如果节点是
getScope(node)
:返回给定节点的 作用域。此信息可用于跟踪对变量的引用。markVariableAsUsed(name, refNode)
:将给定引用节点指示的作用域中给定名称的变量标记为已使用。这会影响 no-unused-vars 规则。如果找到给定名称的变量并将其标记为已使用,则返回true
,否则返回false
。
skipOptions
是一个包含 3 个属性的对象:skip
、includeComments
和 filter
。默认值为 {skip: 0, includeComments: false, filter: null}
。
skip
:(number
) 正整数,跳过的标记数。如果同时给定filter
选项,则不将已过滤的标记计入跳过的标记数。includeComments
:(boolean
) 用于将注释标记包含到结果中的标志。filter(token)
:一个函数,它将标记作为第一个参数。如果该函数返回false
,则结果将排除该标记。
countOptions
是一个包含 3 个属性的对象:count
、includeComments
和 filter
。默认值为 {count: 0, includeComments: false, filter: null}
。
count
:(number
) 正整数,返回标记的最大数量。includeComments
:(boolean
) 用于将注释标记包含到结果中的标志。filter(token)
:一个函数,它将标记作为第一个参数,如果该函数返回false
,则结果将排除该标记。
rangeOptions
是一个包含 1 个属性的对象:includeComments
。默认值为 {includeComments: false}
。
includeComments
:(boolean
) 用于将注释标记包含到结果中的标志。
还可以访问一些属性
hasBOM
:(boolean
) 用于指示源代码是否包含 Unicode BOM 的标志。text
:(string
) 要 lint 的代码的完整文本。此文本已去除 Unicode BOM。ast
:(object
) 要 lint 的代码的 AST 的Program
节点。scopeManager
:代码的 ScopeManager 对象。visitorKeys
:(object
) 用于遍历此 AST 的访问者键。parserServices
:(object
) 包含规则的解析器提供的服务。默认解析器不提供任何服务。但是,如果规则打算与自定义解析器一起使用,则可以使用parserServices
访问该解析器提供的任何内容。(例如,TypeScript 解析器可以提供获取给定节点的计算类型的能力。)lines
:(array
) 行数组,根据规范中对换行符的定义进行拆分。
当需要获取有关要 lint 的代码的更多信息时,应使用 SourceCode
对象。
访问源文本
如果规则需要获取实际的 JavaScript 源代码才能使用,则使用 sourceCode.getText()
方法。此方法的工作原理如下
// 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()
和 sourceCode.getCommentsInside()
来访问它们。
sourceCode.getCommentsBefore()
、sourceCode.getCommentsAfter()
和 sourceCode.getCommentsInside()
对于需要检查与给定节点或标记相关的注释的规则很有用。
请记住,这些方法的结果是按需计算的。
还可以通过使用 includeComments
选项,通过 sourceCode
的许多方法访问注释。
选项模式
具有选项的规则必须指定一个 meta.schema
属性,该属性是规则选项的 JSON Schema 格式描述,ESLint 将使用它来验证配置选项,并在将无效或意外的输入传递到 context.options
中的规则之前阻止它们。
如果规则具有选项,则强烈建议您指定选项验证的模式。但是,可以通过设置 schema: false
来选择退出选项验证,但这不建议这样做,因为它会增加出现错误和错误的可能性。
对于未指定 meta.schema
属性的规则,ESLint 会在传递任何选项时抛出错误。如果规则没有选项,请不要设置 schema: false
,只需省略 schema 属性或使用 schema: []
,这两种方法都可以防止传递任何选项。
验证规则配置时,有五个步骤
- 如果规则配置不是数组,则将该值包装到数组中(例如,
"off"
变为["off"]
);如果规则配置是数组,则直接使用它。 - ESLint 将规则配置数组的第一个元素验证为严重性(
"off"
、"warn"
、"error"
、0
、1
、2
) - 如果严重性为
off
或0
,则规则被禁用,验证停止,忽略规则配置数组的任何其他元素。 - 如果规则已启用,则数组中严重性之后的任何元素都将复制到
context.options
数组中(例如,配置["warn", "never", { someOption: 5 }]
会导致context.options = ["never", { someOption: 5 }]
) - 规则的模式验证将在
context.options
数组上运行。
注意:这意味着规则模式无法验证严重性。规则模式仅验证规则配置中严重性之后的数组元素。规则无法知道其配置的严重性。
规则的 schema
有两种格式
- JSON Schema 对象数组
- 每个元素都将针对
context.options
数组中相同的位置进行检查。 - 如果
context.options
数组的元素少于模式的数量,则忽略未匹配的模式 - 如果
context.options
数组的元素多于模式的数量,则验证失败 - 使用此格式有两个重要的后果
- 用户始终可以为规则提供没有选项(除了严重性)的配置。
- 如果指定一个空数组,则用户始终会收到错误,如果他们为规则提供了任何选项(除了严重性)。
- 每个元素都将针对
- 将验证
context.options
数组的完整 JSON Schema 对象
例如,yoda
规则接受 "always"
或 "never"
的主要模式参数,以及一个带有可选属性 exceptRange
的额外选项对象
// Valid configuration:
// "yoda": "warn"
// "yoda": ["error"]
// "yoda": ["error", "always"]
// "yoda": ["error", "never", { "exceptRange": true }]
// Invalid configuration:
// "yoda": ["warn", "never", { "exceptRange": true }, 5]
// "yoda": ["error", { "exceptRange": true }, "never"]
module.exports = {
meta: {
schema: [
{
enum: ["always", "never"]
},
{
type: "object",
properties: {
exceptRange: { type: "boolean" }
},
additionalProperties: false
}
]
}
};
以下是等效的基于对象的模式
// Valid configuration:
// "yoda": "warn"
// "yoda": ["error"]
// "yoda": ["error", "always"]
// "yoda": ["error", "never", { "exceptRange": true }]
// Invalid configuration:
// "yoda": ["warn", "never", { "exceptRange": true }, 5]
// "yoda": ["error", { "exceptRange": true }, "never"]
module.exports = {
meta: {
schema: {
type: "array",
minItems: 0,
maxItems: 2,
items: [
{
enum: ["always", "never"]
},
{
type: "object",
properties: {
exceptRange: { type: "boolean" }
},
additionalProperties: false
}
]
}
}
};
对象模式在允许的内容方面可以更精确和更严格。例如,下面的模式始终要求指定第一个选项(0 到 10 之间的数字),但第二个选项是可选的,可以是设置了一些选项的对象,也可以是 "off"
或 "strict"
。
// Valid configuration:
// "someRule": ["error", 6]
// "someRule": ["error", 5, "strict"]
// "someRule": ["warn", 10, { someNonOptionalProperty: true }]
// Invalid configuration:
// "someRule": "warn"
// "someRule": ["error"]
// "someRule": ["warn", 15]
// "someRule": ["warn", 7, { }]
// "someRule": ["error", 3, "on"]
// "someRule": ["warn", 7, { someOtherProperty: 5 }]
// "someRule": ["warn", 7, { someNonOptionalProperty: false, someOtherProperty: 5 }]
module.exports = {
meta: {
schema: {
type: "array",
minItems: 1, // Can't specify only severity!
maxItems: 2,
items: [
{
type: "number",
minimum: 0,
maximum: 10
},
{
anyOf: [
{
type: "object",
properties: {
someNonOptionalProperty: { type: "boolean" }
},
required: ["someNonOptionalProperty"],
additionalProperties: false
},
{
enum: ["off", "strict"]
}
]
}
]
}
}
}
请记住,规则选项始终是数组,因此请注意不要在顶级指定非数组类型的模式。如果模式在顶级没有指定数组,则用户永远无法启用规则,因为在启用规则时,他们的配置始终无效。
这是一个始终会验证失败的模式示例
// Possibly trying to validate ["error", { someOptionalProperty: true }]
// but when the rule is enabled, config will always fail validation because the options are an array which doesn't match "object"
module.exports = {
meta: {
schema: {
type: "object",
properties: {
someOptionalProperty: {
type: "boolean"
}
},
additionalProperties: false
}
}
}
注意:如果规则模式使用 JSON 模式 $ref
属性,则必须使用完整的 JSON Schema 对象,而不是位置属性模式数组。这是因为 ESLint 会将数组简写转换为单个模式,而不会更新使其不正确的引用(这些引用会被忽略)。
要详细了解 JSON Schema,建议您查看 JSON Schema 网站 上的一些示例,或阅读免费的 Understanding JSON Schema 电子书。
选项默认值
规则可以指定一个 meta.defaultOptions
数组,其中包含任何选项的默认值。当规则在用户配置中启用时,ESLint 将递归地将任何用户提供的选项元素合并到默认元素之上。
例如,给定以下默认值
export default {
meta: {
defaultOptions: [{
alias: "basic",
}],
schema: [{
type: "object",
properties: {
alias: {
type: "string"
}
},
additionalProperties: false
}]
},
create(context) {
const [{ alias }] = context.options;
return { /* ... */ };
}
}
除非用户配置指定了不同的值(例如,使用 ["error", { alias: "complex" }]
),否则该规则的运行时 alias
值将为 "basic"
。
选项数组的每个元素都根据以下规则进行合并
- 任何缺失的值或用户显式提供的
undefined
将回退到默认选项 - 用户提供的数组和除
undefined
之外的原始值会覆盖默认选项 - 用户提供的对象将合并到默认选项对象中,并在其他情况下替换非对象默认值
选项默认值也将根据规则的 meta.schema
进行验证。
注意:ESLint 在内部使用 Ajv 进行模式验证,并启用了其 useDefaults
选项。用户提供的选项和 meta.defaultOptions
选项都将覆盖规则模式中指定的任何默认值。ESLint 可能会在将来的主要版本中禁用 Ajv 的 useDefaults
。
访问 Shebangs
Shebang(#!) 由类型为 "Shebang"
的唯一标记表示。它们被视为注释,可以通过“访问注释”部分中概述的方法访问,例如 sourceCode.getAllComments()
。
访问变量作用域
SourceCode#getScope(node)
方法返回给定节点的作用域。这是一个有用的方法,用于查找有关给定作用域中的变量以及如何在其他作用域中使用它们的信息。
作用域类型
下表包含 AST 节点类型列表及其对应的作用域类型。有关作用域类型的更多信息,请参阅 Scope
对象文档。
AST 节点类型 | 作用域类型 |
---|---|
Program |
global |
FunctionDeclaration |
function |
FunctionExpression |
function |
ArrowFunctionExpression |
function |
ClassDeclaration |
class |
ClassExpression |
class |
BlockStatement ※1 |
block |
SwitchStatement ※1 |
switch |
ForStatement ※2 |
for |
ForInStatement ※2 |
for |
ForOfStatement ※2 |
for |
WithStatement |
with |
CatchClause |
catch |
其他 | ※3 |
※1 仅当配置的解析器提供了块级作用域功能时。如果 parserOptions.ecmaVersion
不小于 6
,则默认解析器提供块级作用域功能。
※2 仅当 for
语句将迭代变量定义为块级作用域变量时(例如,for (let i = 0;;) {}
)。
※3 最近的祖先节点的作用域,该节点具有自己的作用域。如果最近的祖先节点具有多个作用域,则选择最内部的作用域(例如,Program
节点具有 global
作用域和 module
作用域,如果 Program#sourceType
为 "module"
。最内部的作用域是 module
作用域)。
作用域变量
Scope#variables
属性包含一个 Variable
对象 数组。这些是在当前作用域中声明的变量。您可以使用这些 Variable
对象来跟踪整个模块中对变量的引用。
在每个 Variable
内部,Variable#references
属性包含一个 Reference
对象 数组。Reference
数组包含模块源代码中引用变量的所有位置。
同样在每个 Variable
内部,Variable#defs
属性包含一个 Definition
对象 数组。您可以使用 Definitions
查找变量定义的位置。
全局变量具有以下附加属性
Variable#writeable
(boolean | undefined
)… 如果为true
,则此全局变量可以被赋值任意值。如果为false
,则此全局变量是只读的。Variable#eslintExplicitGlobal
(boolean | undefined
)… 如果为true
,则此全局变量由源代码文件中的/* globals */
指令注释定义。Variable#eslintExplicitGlobalComments
(Comment[] | undefined
)… 在源代码文件中定义此全局变量的/* globals */
指令注释数组。如果不存在/* globals */
指令注释,则此属性为undefined
。Variable#eslintImplicitGlobalSetting
("readonly" | "writable" | undefined
)… 配置文件中配置的值。如果存在/* globals */
指令注释,则此值可能与variable.writeable
不同。
有关使用 SourceCode#getScope()
跟踪变量的示例,请参阅以下内置规则的源代码
- no-shadow:在
Program
节点处调用sourceCode.getScope()
并检查所有子作用域以确保在较低的作用域中没有重复使用变量名。(no-shadow 文档) - no-redeclare:在每个作用域中调用
sourceCode.getScope()
以确保在同一作用域中没有两次声明变量。(no-redeclare 文档)
标记变量为已使用
某些 ESLint 规则(例如 no-unused-vars
)会检查变量是否已使用。ESLint 本身只了解变量访问的标准规则,因此自定义的变量访问方式可能不会被注册为“已使用”。
为了帮助解决这个问题,您可以使用 sourceCode.markVariableAsUsed()
方法。此方法接受两个参数:要标记为已使用的变量的名称和一个选项引用节点,指示您正在工作的范围。这是一个例子
module.exports = {
create: function(context) {
var sourceCode = context.sourceCode;
return {
ReturnStatement(node) {
// look in the scope of the function for myCustomVar and mark as used
sourceCode.markVariableAsUsed("myCustomVar", node);
// or: look in the global scope for myCustomVar and mark as used
sourceCode.markVariableAsUsed("myCustomVar");
}
}
// ...
}
};
在这里,myCustomVar
变量相对于 ReturnStatement
节点被标记为已使用,这意味着 ESLint 将从最靠近该节点的作用域开始搜索。如果您省略第二个参数,则使用顶级作用域。(对于 ESM 文件,顶级作用域是模块作用域;对于 CommonJS 文件,顶级作用域是第一个函数作用域。)
访问代码路径
ESLint 在遍历 AST 时分析代码路径。您可以使用与代码路径相关的七个事件来访问代码路径对象。有关更多信息,请参阅 代码路径分析。
已弃用的 SourceCode
方法
请注意,以下 SourceCode
方法已弃用,并将从 ESLint 的未来版本中删除
getTokenOrCommentBefore()
:替换为SourceCode#getTokenBefore()
,并使用{ includeComments: true }
选项。getTokenOrCommentAfter()
:替换为SourceCode#getTokenAfter()
,并使用{ includeComments: true }
选项。isSpaceBetweenTokens()
:替换为SourceCode#isSpaceBetween()
getJSDocComment()
规则单元测试
ESLint 提供了 RuleTester
实用程序,使编写规则测试变得容易。
规则命名约定
虽然您可以为自定义规则指定任何名称,但核心规则具有命名约定。将这些相同的命名约定应用于您的自定义规则可能会更清晰。要了解更多信息,请参阅 核心规则命名约定 文档。
运行时规则
使 ESLint 不同于其他代码风格检查工具的是能够在运行时定义自定义规则。这非常适合特定于您的项目或公司的规则,并且没有必要让 ESLint 附带或包含在插件中。只需编写您的规则并在运行时包含它们即可。
运行时规则的编写格式与所有其他规则相同。像创建任何其他规则一样创建您的规则,然后按照以下步骤操作
- 将所有运行时规则放在同一个目录中(例如,
eslint_rules
)。 - 创建一个 配置文件,并在
rules
键下指定您的规则 ID 错误级别。除非您的规则在配置文件中具有"warn"
或"error"
值,否则它将不会运行。 - 使用
--rulesdir
选项运行 命令行界面 以指定运行时规则的位置。
分析规则性能
ESLint 具有内置方法来跟踪单个规则的性能。设置 TIMING
环境变量将在代码风格检查完成后触发显示,显示运行时间最长的十个规则,以及它们各自的运行时间(规则创建 + 规则执行)和相对于总规则处理时间的相对性能影响(规则创建 + 规则执行)。
$ 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
选项
$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib
Rule | Time (ms) | Relative
:------|----------:|--------:
quotes | 18.066 | 100.0%
要查看更长的结果列表(超过 10 个),请将环境变量设置为其他值,例如 TIMING=50
或 TIMING=all
。
有关更细粒度的计时信息(每个文件每个规则),请改用 stats
选项。