deno.com
本页内容

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: 说明符。

deno.json
{
  "lint": {
    "plugins": ["./my-plugin.ts"]
  }
}

插件示例 跳转到标题

插件始终具有相同的形状。它具有默认导出,即您的插件对象。

信息

Deno 为 lint 插件 API 提供了类型声明。

所有类型都可以在 Deno.lint 命名空间下找到。

my-plugin.ts
export default {
  // 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");
                },
              });
            }
          },
        };
      },
    },
  },
} satisfies Deno.lint.Plugin;

使用选择器匹配节点 跳转到标题

如果用纯 JavaScript 编写代码来匹配特定节点,有时会变得有点乏味。有时,这种匹配逻辑通过选择器(类似于 CSS 选择器)更容易表达。使用字符串作为返回的 visitor 对象中的属性名称,我们可以指定自定义选择器。

my-plugin.ts
export default {
  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",
            });
          },
        };
      },
    },
  },
} satisfies Deno.lint.Plugin;

请注意,如果匹配逻辑过于复杂而无法单独表示为选择器,我们始终可以在 JavaScript 中进一步完善我们的匹配。选择器支持的完整语法列表是

语法 描述
Foo + Foo 下一个兄弟选择器
Foo > Bar 子组合符
Foo ~ Bar 后续兄弟组合符
Foo Bar 后代组合符
Foo[attr] 属性存在
Foo[attr.length < 2] 属性值比较
:first-child 第一个子伪类
:last-child 最后一个子伪类
:nth-child(2n + 1) 第 n 个子伪类
:not(> Bar) 否定伪类
:is(> Bar) Is 伪类

应用修复 跳转到标题

自定义 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);
  },
});

运行清理代码 跳转到标题

如果您的插件需要在文件 lint 完成后运行清理代码,您可以通过 destroy() 钩子挂接到 linter。它在文件 lint 完成后以及插件上下文被销毁之前调用。

my-plugin.ts
export default {
  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
      },
    },
  },
} satisfies Deno.lint.Plugin;

注意

假设您的插件代码将针对每个 lint 的文件再次执行是不安全的。

尽量不要保持全局状态,并在 destroy 钩子中进行清理,以防 deno lint 决定重用现有的插件实例。

排除自定义规则 跳转到标题

与内置规则类似,您可以禁用插件提供的自定义规则。为此,请将其添加到 deno.json 中的 lint.rules.exclude 键。自定义 lint 规则的格式始终为 <plugin-name>/<rule-name>

deno.json
{
  "lint": {
    "plugins": ["./my-plugin.ts"],
    "rules": {
      "exclude": ["my-plugin/my-rule"]
    }
  }
}

测试插件 跳转到标题

Deno.lint.runPlugin API 提供了一种方便的方式来测试您的插件。它允许您断言插件针对特定输入产生预期的诊断结果。

让我们使用上面定义的示例插件

my-plugin-test.ts
import { assert, assertEquals } from "jsr:@std/assert";
import myPlugin from "./my-plugin.ts";

Deno.test("my-plugin", () => {
  const diagnostics = Deno.lint.runPlugin(plugin, "main.ts", "const _a = 'a';");

  assertEquals(diagnostics.length, 1);
  const d = diagnostics[0];
  assertEquals(d.id, "my-plugin/my-rule");
  assertEquals(d.message, "should be _b");
  assert(typeof d.fix === "function");
});

信息

Deno.lint.runPlugin API 仅在 deno testdeno bench 子命令中可用。

尝试将其与任何其他子命令一起使用将导致抛出错误。

有关 Deno.lint.runPluginDeno.lint.Diagnostic 的更多信息,请参阅 API 参考

您找到所需的信息了吗?

隐私政策