自定义规则
您可以创建自定义规则以与 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 | DeprecatedInfo) 指示规则是否已弃用。如果规则未弃用,您可以省略deprecated属性。
DeprecatedInfo 有一个专门的页面 -
replacedBy: (array, 已弃用 使用meta.deprecated.replacedBy代替。) 在已弃用规则的情况下,指定替换规则。
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 对象
context 对象是规则中 create 方法的唯一参数。例如
// customRule.js
module.exports = {
meta: { ... },
// `context` object is the argument
create(context) {
// ...
}
};
顾名思义,context 对象包含与规则上下文相关的信息。
context 对象具有以下属性
id: (string) 规则 ID。filename: (string) 与源文件关联的文件名。physicalFilename: (string) 当 linting 文件时,它提供磁盘上文件的完整路径,不包含任何代码块信息。当 linting 文本时,它提供传递给—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代替。) 当 linting 文件时,它返回磁盘上文件的完整路径,不包含任何代码块信息。当 linting 文本时,它返回传递给—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,则节点的起始位置用作问题的location。loc: (可选object) 指定问题的 location。如果同时指定了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
}
});
请注意,消息参数中的前导和尾随空格是可选的。
节点包含确定违规文本的行号和列号以及表示节点的源代码文本所需的所有信息。
messageIds
由于以下好处,messageIds 是在 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()返回 fixer 操作的结果。 - 由于在应用初始轮修复后所有规则都会再次运行,因此规则不必检查修复的代码样式是否会导致另一个规则报告错误。
-
例如,假设一个 fixer 想要用引号包围一个对象键,但不确定用户是喜欢单引号还是双引号。
({ foo : 1 }) // should get fixed to either ({ 'foo': 1 }) // or ({ "foo": 1 }) -
这个 fixer 可以随意选择一种引号类型。如果它猜错了,结果代码将被自动报告并由
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 将自动从 linting 输出中删除整个建议。
建议 messageIds
可以使用 messageId 代替使用 desc 键进行建议。这与整体错误的 messageIds 的工作方式相同(请参阅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) 正整数,返回 token 的最大数量。includeComments: (boolean) 将注释令牌包含在结果中的标志。filter(token): 函数,它将一个 token 作为第一个参数获取。如果该函数返回false,则结果会排除该 token。
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 的 Visitor 键。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() 对于需要检查与给定节点或 token 相关的注释的规则非常有用。
请记住,这些方法的结果是按需计算的。
你也可以通过 sourceCode 的许多方法使用 includeComments 选项来访问注释。
选项模式
带有选项的规则必须指定一个 meta.schema 属性,这是一个 JSON Schema 格式的规则选项描述,ESLint 将使用它来验证配置选项,并在将无效或意外输入传递到 context.options 中的规则之前阻止它们。
如果你的规则有选项,强烈建议你指定一个 schema 用于选项验证。然而,可以通过设置 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 }]) - 规则的 schema 验证在
context.options数组上运行。
注意:这意味着规则 schema 无法验证严重程度。规则 schema 仅验证规则配置中严重程度 *之后* 的数组元素。规则无法知道它被配置为什么严重程度。
规则的 schema 有两种格式
- JSON Schema 对象的数组
- 每个元素将根据其在
context.options数组中的相同位置进行检查。 - 如果
context.options数组的元素少于 schema 的数量,则未匹配的 schema 将被忽略。 - 如果
context.options数组的元素多于 schema 的数量,则验证失败。 - 使用此格式有两个重要的后果
- 用户 *始终可以* 不为你的规则提供任何选项(除了严重程度之外)。
- 如果你指定一个空数组,那么用户 *始终会出错*,因为他们为你的规则提供了任何选项(除了严重程度之外)。
- 每个元素将根据其在
- 一个完整的 JSON Schema 对象,它将验证
context.options数组
例如,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 网站上的一些示例,或阅读免费的 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 进行 schema 验证,并启用了其 useDefaults 选项。用户提供和 meta.defaultOptions 选项都将覆盖规则 schema 中指定的任何默认值。ESLint 可能会在未来的主要版本中禁用 Ajv 的 useDefaults。
访问 Shebangs
Shebangs (#!) 由类型为 "Shebang" 的唯一 token 表示。它们被视为注释,可以通过 访问注释 部分中概述的方法(例如 sourceCode.getAllComments())进行访问。
访问变量作用域
SourceCode#getScope(node) 方法返回给定节点的 scope。这是一个有用的方法,用于查找有关给定 scope 中变量的信息以及它们如何在其他 scope 中使用。
作用域类型
下表包含 AST 节点类型及其对应的 scope 类型的列表。有关 scope 类型的更多信息,请参阅 Scope 对象文档。
| AST 节点类型 | Scope 类型 |
|---|---|
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 具有自身 scope 的最近祖先节点的 scope。如果最近的祖先节点有多个 scope,则选择最内层的 scope(例如,如果 Program#sourceType 是 "module",则 Program 节点具有 global scope 和 module scope。最内层的 scope 是 module scope)。
作用域变量
Scope#variables 属性包含一个 Variable 对象数组。这些是在当前 scope 中声明的变量。你可以使用这些 Variable 对象来跟踪整个模块中对变量的引用。
在每个 Variable 内部,Variable#references 属性包含一个 Reference 对象数组。Reference 数组包含变量在模块源代码中被引用的所有位置。
同样在每个 Variable 内部,Variable#defs 属性包含一个 Definition 对象数组。你可以使用 Definitions 来查找变量的定义位置。
全局变量具有以下附加属性
Variable#writeable(布尔值 | undefined) … 如果为true,则可以为该全局变量分配任意值。如果为false,则此全局变量是只读的。Variable#eslintExplicitGlobal(布尔值 | 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()并检查所有子 scope,以确保变量名不会在较低的 scope 中重复使用。( no-shadow 文档) - no-redeclare:在每个 scope 调用
sourceCode.getScope(),以确保变量在同一 scope 中未声明两次。( no-redeclare 文档)
将变量标记为已使用
某些 ESLint 规则,例如 no-unused-vars,检查变量是否已被使用。ESLint 本身只了解变量访问的标准规则,因此自定义的变量访问方式可能不会注册为“已使用”。
为了帮助解决这个问题,你可以使用 sourceCode.markVariableAsUsed() 方法。此方法接受两个参数:要标记为已使用的变量的名称和一个可选的引用节点,指示你正在工作的 scope。这是一个例子
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 将从最接近该节点的 scope 开始搜索。如果省略第二个参数,则使用顶层 scope。(对于 ESM 文件,顶层 scope 是模块 scope;对于 CommonJS 文件,顶层 scope 是第一个函数 scope。)
访问代码路径
ESLint 在遍历 AST 时分析代码路径。你可以使用七个与代码路径相关的事件访问代码路径对象。有关更多信息,请参阅 代码路径分析。
已弃用的 SourceCode 方法
请注意,以下 SourceCode 方法已被弃用,并将在 ESLint 的未来版本中删除
getTokenOrCommentBefore():已替换为带有{ includeComments: true }选项的SourceCode#getTokenBefore()。getTokenOrCommentAfter():已替换为带有{ includeComments: true }选项的SourceCode#getTokenAfter()。isSpaceBetweenTokens():已替换为SourceCode#isSpaceBetween()getJSDocComment()
规则单元测试
ESLint 提供了 RuleTester 实用程序,以便轻松地为规则编写测试。
规则命名约定
虽然你可以为自定义规则指定任何你喜欢的名称,但核心规则有命名约定。将这些相同的命名约定应用于你的自定义规则可能会更清晰。要了解更多信息,请参阅 核心规则命名约定 文档。
运行时规则
使 ESLint 与其他 linters 不同的地方在于它能够在运行时定义自定义规则。这对于特定于你的项目或公司,并且 ESLint 不应附带或包含在插件中的规则非常完美。只需编写你的规则并在运行时包含它们即可。
运行时规则的编写格式与所有其他规则相同。像创建任何其他规则一样创建你的规则,然后按照以下步骤操作
- 将你的所有运行时规则放在同一个目录中(例如,
eslint_rules)。 - 创建一个 配置文件 并在
rules键下指定你的规则 ID 错误级别。除非你的规则在配置文件中的值为"warn"或"error",否则它不会运行。 - 运行 命令行界面,使用
--rulesdir选项来指定你的运行时规则的位置。
分析规则性能
ESLint 有一个内置方法来跟踪单个规则的性能。设置 TIMING 环境变量将在 linting 完成后触发显示运行时间最长的十个规则,以及它们的单个运行时间(规则创建 + 规则执行)和相对于总规则处理时间(规则创建 + 规则执行)的相对性能影响百分比。
$ 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 选项。