Spec-Zone.ru › Deno 2

Плагины линтера

Примечание

API плагинов линтера доступен в Deno 2.2.0 и более поздних версиях; нестабильный флаг не требуется. API всё ещё развивается и может измениться в будущем выпуске.

Встроенный линтер можно расширить с помощью плагинов, добавляющих пользовательские правила линтера.

Хотя Deno поставляется со множеством правил линтера из коробки, бывают случаи, когда требуется пользовательское правило, специально адаптированное под проект, — например, чтобы обнаруживать проблему, характерную для определённого контекста, или обеспечивать соблюдение единых для компании соглашений.

Именно здесь пригодится API плагинов линтера.

API плагинов линтера намеренно смоделирован по образцу API ESLint. Хотя этот API не обеспечивает 100-процентную совместимость, существующие знания о создании плагинов ESLint можно в основном применить повторно, если вы ранее писали пользовательские правила ESLint.

Плагины загружаются через настройку lint.plugins в deno.json.

Значение представляет собой массив спецификаторов плагинов. Это могут быть пути, npm: или спецификаторы jsr:.

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

Пример плагина

У плагина всегда одна и та же структура. Он содержит экспорт по умолчанию — объект вашего плагина.

Информация

Deno предоставляет объявления типов для API плагинов линтера.

Все определения типов доступны в пространстве имён Deno.lint.

my-plugin.ts
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;

Использование селекторов для сопоставления узлов

Написание кода для сопоставления с определённым узлом иногда может быть утомительным, если использовать обычный JavaScript. Иногда эту логику сопоставления проще выразить с помощью селектора, подобного селекторам CSS. Указав строку в качестве имени свойства в возвращаемом объекте посетителя, можно задать пользовательский селектор.

my-plugin.ts
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) Псевдокласс дочернего элемента с указанным номером
:not(> Bar) Псевдокласс отрицания
:is(> Bar) Псевдокласс соответствия
:where(> Bar) Псевдокласс where (то же, что и :is())
:matches(> Bar) Псевдокласс matches (то же, что и :is())
:has(> Bar) Псевдокласс has
IfStatement.test Селектор поля .<field>

Также существует псевдокласс :exit, который допустим только в конце всего селектора. Если он присутствует, Deno будет вызывать функцию при движении по дереву вверх, а не вниз.

Совет

При разработке правил линтера мы настоятельно рекомендуем использовать песочницу typescript-eslint. Она позволяет изучать код и результирующий формат AST. Так проще увидеть, каким узлам соответствуют те или иные селекторы.

Применение исправлений

Пользовательское правило линтера может предоставлять функцию для применения исправления при сообщении о проблеме. Необязательный метод fix() вызывается при запуске deno lint --fix или применении исправления из редактора через Deno LSP.

Метод 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);
  },
});

Выполнение кода очистки

Если плагину требуется выполнить код очистки после проверки файла линтером, можно подключиться к линтеру через хук destroy(). Он вызывается после проверки файла линтером и непосредственно перед уничтожением контекста плагина.

my-plugin.ts
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;
Внимание

Нельзя считать, что код плагина будет выполняться повторно для каждого проверяемого линтером файла.

Старайтесь не хранить глобальное состояние и выполняйте очистку в хуке destroy на случай, если deno lint решит повторно использовать существующий экземпляр плагина.

Исключение пользовательских правил

Как и встроенные правила, пользовательские правила, предоставляемые плагином, можно отключить. Для этого добавьте правило в ключ lint.rules.exclude файла deno.json. Формат пользовательского правила линтера всегда имеет вид <plugin-name>/<rule-name>.

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

Игнорирование сообщений пользовательских правил линтера

Иногда требуется отключить сообщение об ошибке линтера в определённом месте кода. Вместо полного отключения пользовательского правила линтера можно отключить сообщение для конкретного места, поместив перед ним комментарий в коде.

// deno-lint-ignore my-custom-plugin/no-console
console.log("hey");

Это отключит правило линтера из плагина для данной строки.

Синтаксис комментария для игнорирования:

// deno-lint-ignore <my-plugin>/<my-rule>

Тестирование плагинов

API Deno.lint.runPlugin предоставляет удобный способ тестирования плагинов. С его помощью можно проверить, что плагин выдаёт ожидаемые диагностические сообщения для заданного входного кода.

Воспользуемся приведённым выше примером плагина:

my-plugin_test.ts
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" }]);
});
Информация

API Deno.lint.runPlugin доступен только в подкомандах deno test и deno bench.

Попытка использовать его с любой другой подкомандой приведёт к ошибке.

Дополнительную информацию о справочнике API, Deno.lint.runPlugin и Deno.lint.Diagnostic см. в документации.

Последнее обновление: 2 апреля 2025 г.

© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/reference/lint_plugins

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API