自定义规则
您可以创建自定义规则与 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 可以防止无效的规则配置。当规则具有选项时,此属性为必需。 -
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(请参阅messageIds)(建议使用此属性而不是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
s
messageId
s 是在 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
s
可以使用 messageId
而不是使用建议的 desc
键。这与整体错误的 messageId
s 的工作方式相同(请参阅messageIds)。以下是如何在规则中使用建议 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
) 正在检查的代码的完整文本。此文本已去除 Unicode BOM。ast
:(object
) 正在检查的代码的 AST 的Program
节点。scopeManager
:代码的ScopeManager对象。visitorKeys
:(object
) 用于遍历此 AST 的访问者键。parserServices
:(object
) 包含解析器为规则提供的服务。默认解析器不提供任何服务。但是,如果规则旨在与自定义解析器一起使用,则可以使用parserServices
访问该解析器提供的任何内容。(例如,TypeScript 解析器可以提供获取给定节点的计算类型的能力。)lines
:(array
) 行数组,根据规范中对换行符的定义进行拆分。
当需要获取有关正在检查的代码的更多信息时,应使用 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
}
]
}
};
以下是等效的基于对象的 Schema
// 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
}
]
}
}
};
对象 Schema 可以更精确和严格地限制允许的内容。例如,下面的 Schema 始终要求指定第一个选项(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"]
}
]
}
]
}
}
}
请记住,规则选项始终是数组,因此请注意不要在顶层为非数组类型指定 Schema。如果您的 Schema 在顶层没有指定数组,则用户将*永远*无法启用您的规则,因为当规则启用时,他们的配置将始终无效。
这是一个始终导致验证失败的 Schema 示例
// 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
}
}
}
注意:如果您的规则 Schema 使用 JSON Schema $ref
属性,则必须使用完整的 JSON Schema 对象而不是位置属性 Schema 的数组。这是因为 ESLint 会将数组简写转换为单个 Schema,而不会更新引用,这会导致引用不正确(它们会被忽略)。
要了解有关 JSON Schema 的更多信息,我们建议您查看JSON Schema 网站上的一些示例,或阅读免费的理解 JSON Schema 电子书。
访问 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 与其他 linter 不同的因素是在运行时定义自定义规则的能力。这非常适合特定于您的项目或公司的规则,这些规则对于 ESLint 来说没有意义,或者不应该包含在插件中。只需编写您的规则并在运行时包含它们即可。
运行时规则的编写格式与所有其他规则相同。像创建其他规则一样创建您的规则,然后按照以下步骤操作
- 将所有运行时规则放在同一个目录中(例如,
eslint_rules
)。 - 创建一个配置文件,并在
rules
键下指定规则 ID 错误级别。除非在配置文件中将规则的值设置为"warn"
或"error"
,否则您的规则将不会运行。 - 使用
--rulesdir
选项运行命令行界面以指定运行时规则的位置。
分析规则性能
ESLint 具有内置方法来跟踪各个规则的性能。设置 TIMING
环境变量将在 lint 完成后触发显示十个运行时间最长的规则,以及它们各自的运行时间(规则创建 + 规则执行)和相对于总规则处理时间的相对性能影响(规则创建 + 规则执行)。
$ 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
选项。