Lint 插件
这是一项实验性功能,需要 Deno 2.2.0
或更高版本。
插件 API 目前被标记为“不稳定”,因为它将来可能会发生变化。
内置的 linter 可以通过插件扩展,以添加自定义 lint 规则。
虽然 Deno 开箱即用地提供了许多 lint 规则,但在某些情况下,您需要一个特别为您的项目量身定制的自定义规则——无论是为了捕获特定上下文的问题还是强制执行公司范围的约定。
这就是 lint 插件 API 发挥作用的地方。
lint 插件 API 在设计上特意模仿了 ESLint API。虽然此 API 不提供 100% 的兼容性,但如果您过去曾编写过自定义 ESLint 规则,那么编写 ESLint 插件的现有知识大多可以重复使用。
插件通过 deno.json
中的 lint.plugins
设置加载。
该值是一个插件说明符数组。这些可以是路径、npm:
或 jsr:
说明符。
{
"lint": {
"plugins": ["./my-plugin.ts"]
}
}
插件示例 Jump to heading
一个插件总是具有相同的结构。它有一个默认导出,即您的插件对象。
Deno 为 lint 插件 API 提供类型声明。
所有类型都可在 Deno.lint
命名空间下找到。
const plugin: Deno.lint.Plugin = {
// The name of your plugin. Will be shown in error output
name: "my-plugin",
// Object with rules. The property name is the rule name and
// will be shown in the error output as well.
rules: {
"my-rule": {
// Inside the `create(context)` method is where you'll put your logic.
// It's called when a file is being linted.
create(context) {
// Return an AST visitor object
return {
// Here in this example we forbid any identifiers being named `_a`
Identifier(node) {
if (node.name === "_a") {
// Report a lint error with a custom message
context.report({
node,
message: "should be _b",
// Optional: Provide a fix, which can be applied when
// the user runs `deno lint --fix`
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
};
export default plugin;
使用选择器匹配节点 Jump to heading
如果用纯 JavaScript 编写代码来匹配特定节点,有时会变得有点繁琐。有时,这种匹配逻辑通过选择器(类似于 CSS 选择器)更容易表达。通过在返回的访问者对象中使用字符串作为属性名,我们可以指定一个自定义选择器。
const plugin: Deno.lint.Plugin = {
name: "my-plugin",
rules: {
"my-rule": {
create(context) {
return {
// Selectors can be used too. Here we check for
// `require("...") calls.
'CallExpression[callee.name="require"]'(node) {
context.report({
node,
message: "Don't use require() calls to load modules",
});
},
};
},
},
},
};
export default plugin;
请注意,如果匹配逻辑过于复杂而无法单独用选择器表达,我们总是可以在 JavaScript 中进一步完善我们的匹配。支持的选择器语法的完整列表是:
语法 | 描述 |
---|---|
Foo + Foo |
下一个兄弟选择器 |
Foo > Bar |
子组合器 |
Foo ~ Bar |
后续兄弟组合器 |
Foo Bar |
后代组合器 |
Foo[attr] |
属性存在 |
Foo[attr.length < 2] |
属性值比较 |
Foo[attr=/(foo|bar)*/] |
属性值正则表达式检查 |
:first-child |
第一个子元素伪类 |
:last-child |
最后一个子元素伪类 |
:nth-child(2n + 1) |
第 N 个子元素伪类 |
:not(> Bar) |
非伪类 |
:is(> Bar) |
是伪类 |
:where(> Bar) |
Where 伪类(与 :is() 相同) |
:matches(> Bar) |
Matches 伪类(与 :is() 相同) |
:has(> Bar) |
Has 伪类 |
IfStatement.test |
字段选择器 .<field> |
还有一个 :exit
伪类,它只在整个选择器末尾有效。当它存在时,Deno 将在遍历树向上时而不是向下时调用该函数。
应用修复 Jump to heading
自定义 lint 规则可以在报告问题时提供一个函数来应用修复。当运行 deno lint --fix
或通过 Deno LSP 在编辑器内部应用修复时,会调用可选的 fix()
方法。
fix()
方法接收一个 fixer
实例,其中包含辅助方法,使创建修复更容易。一个修复由起始位置、结束位置和应放入此范围的新文本组成。
context.report({
node,
message: "should be _b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
fixer
对象有以下方法:
insertTextAfter(node, text)
: 在给定节点后插入文本。insertTextAfterRange(range, text)
: 在给定范围后插入文本。insertTextBefore(node, text)
: 在给定节点前插入文本。insertTextBeforeRange(range, text)
: 在给定范围前插入文本。remove(node)
: 移除给定节点。removeRange(range)
: 移除给定范围内的文本。replaceText(node, text)
: 替换给定节点中的文本。replaceTextRange(range, text)
: 替换给定范围内的文本。
fix()
方法还可以返回一个修复数组,如果它是生成器函数,则可以生成多个修复。
有时需要节点的原始源代码来创建修复。要获取任何节点的源代码,请使用 context.sourceCode.getText()
context.report({
node,
message: "should be _b",
fix(fixer) {
const original = context.sourceCode.getText(node);
const newText = `{ ${original} }`;
return fixer.replaceText(node, newText);
},
});
运行清理代码 Jump to heading
如果您的插件需要在文件被 lint 后运行清理代码,您可以通过 destroy()
钩子挂接到 linter。它在文件被 lint 后且插件上下文被销毁之前调用。
const plugin: Deno.lint.Plugin = {
name: "my-plugin",
rules: {
"my-rule": {
create(context) {
// ...
},
// Optional: Run code after a linting for a file is completed
// and each rule context is destroyed.
destroy() {
// do some cleanup stuff if you need to
},
},
},
};
export default plugin;
不能安全地假设您的插件代码将为每个被 lint 的文件再次执行。
最好不要保留全局状态,并在 destroy
钩子中进行清理,以防 deno lint
决定重用现有的插件实例。
排除自定义规则 Jump to heading
与内置规则类似,您可以禁用插件提供的自定义规则。为此,请将其添加到 deno.json
中的 lint.rules.exclude
键。自定义 lint 规则的格式始终为 <plugin-name>/<rule-name>
。
{
"lint": {
"plugins": ["./my-plugin.ts"],
"rules": {
"exclude": ["my-plugin/my-rule"]
}
}
}
忽略自定义 lint 报告 Jump to heading
有时您想禁用代码中特定位置报告的 lint 错误。您可以不完全禁用自定义 lint 规则,而是在代码前放置注释来禁用报告的位置。
// deno-lint-ignore my-custom-plugin/no-console
console.log("hey");
这将禁用 lint 插件的 lint 规则,仅适用于此特定行。
忽略注释的语法是:
// deno-lint-ignore <my-plugin>/<my-rule>
测试插件 Jump to heading
Deno.lint.runPlugin
API 提供了一种方便的方式来测试您的插件。它允许您断言插件在给定特定输入时会产生预期的诊断结果。
让我们使用上面定义的示例插件:
import { assertEquals } from "jsr:@std/assert";
import myPlugin from "./my-plugin.ts";
Deno.test("my-plugin", () => {
const diagnostics = Deno.lint.runPlugin(
myPlugin,
"main.ts", // Dummy filename, file doesn't need to exist.
"const _a = 'a';",
);
assertEquals(diagnostics.length, 1);
const d = diagnostics[0];
assertEquals(d.id, "my-plugin/my-rule");
assertEquals(d.message, "should be _b");
assertEquals(d.fix, [{ range: [6, 8], text: "_b" }]);
});
Deno.lint.runPlugin
API 仅在 deno test
和 deno bench
子命令中可用。
尝试在任何其他子命令中使用它将抛出错误。
有关 Deno.lint.runPlugin
和 Deno.lint.Diagnostic
的更多信息,请参阅API 参考。