Структура плагина

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

Плагин представляет собой обычный npm-пакет, содержащий набор объектов, которые ESLint способен загрузить и использовать во время анализа файлов. Внутри одного плагина могут находиться:

  • пользовательские правила (rules);
  • конфигурации (configs);
  • процессоры (processors);
  • определения окружений (environments);
  • вспомогательные утилиты.

Благодаря такой архитектуре один пакет может предоставлять полноценную экосистему для работы с конкретным фреймворком, языковым расширением или набором внутренних стандартов разработки.


Общая структура плагина

Минимальная структура проекта плагина обычно выглядит следующим образом:

eslint-plugin-example/
│
├── package.json
├── index.js
│
├── rules/
│   ├── no-console-log.js
│   └── prefer-const-name.js
│
├── configs/
│   ├── recommended.js
│   └── strict.js
│
└── processors/
    └── markdown.js

Каждый элемент отвечает за определённую часть функциональности.

package.json

Файл описывает npm-пакет и позволяет ESLint обнаруживать плагин.

Пример:

{
  "name": "eslint-plugin-example",
  "version": "1.0.0",
  "main": "index.js"
}

По соглашению название плагина начинается с префикса:

eslint-plugin-

Например:

eslint-plugin-react
eslint-plugin-vue
eslint-plugin-import
eslint-plugin-unicorn

При подключении префикс обычно опускается:

{
    plugins: ["react"]
}

ESLint автоматически понимает, что необходимо загрузить пакет:

eslint-plugin-react

Точка входа плагина

Главным файлом обычно является index.js, экспортирующий объект плагина.

Пример:

module.exports = {
    rules: {},
    configs: {},
    processors: {}
};

Современный вариант для Flat Config:

export default {
    rules: {},
    configs: {},
    processors: {}
};

Этот объект становится основным интерфейсом взаимодействия ESLint с плагином.


Раздел rules

Наиболее важной частью большинства плагинов является раздел rules.

Каждое правило представляет собой отдельный модуль.

Структура:

rules/
└── no-console-log.js

Экспорт правила:

module.exports = {
    meta: {},
    create(context) {
        return {};
    }
};

Подключение в основном файле:

module.exports = {
    rules: {
        "no-console-log": require("./rules/no-console-log")
    }
};

После публикации правило будет доступно под именем:

example/no-console-log

Использование:

{
    rules: {
        "example/no-console-log": "error"
    }
}

Внутреннее устройство правила

Каждое правило состоит из двух основных частей:

module.exports = {
    meta: {},
    create(context) {}
};

Объект meta

Содержит метаданные.

Пример:

meta: {
    type: "problem",
    docs: {
        description: "Запрещает использование console.log"
    },
    fixable: "code",
    schema: []
}

Основные свойства:

Свойство Назначение
type Тип правила
docs Документация
schema Описание параметров
fixable Поддержка автоисправления
messages Шаблоны сообщений

Типы правил

Поле type помогает классифицировать нарушение.

type: "problem"

Возможные значения:

problem
suggestion
layout

problem

Ошибки, влияющие на корректность программы.

Пример:

if (value = 10) {
}

suggestion

Рекомендации по улучшению кода.

Пример:

let name = "John";

Вместо:

const name = "John";

layout

Правила форматирования.

Пример:

const a=1;

Вместо:

const a = 1;

Поле docs

Используется для хранения информации о правиле.

docs: {
    description: "Запрещает console.log",
    recommended: true
}

Обычно применяется генераторами документации и инструментами автоматического анализа.


Поле schema

Позволяет описывать параметры правила через JSON Schema.

Без параметров:

schema: []

С одним параметром:

schema: [
    {
        type: "object",
        properties: {
            allowWarn: {
                type: "boolean"
            }
        },
        additionalProperties: false
    }
]

Использование:

{
    rules: {
        "example/no-console-log": [
            "error",
            {
                allowWarn: true
            }
        ]
    }
}

Поле messages

Позволяет хранить шаблоны ошибок централизованно.

messages: {
    forbidden: "Использование console.log запрещено."
}

Вызов сообщения:

context.report({
    node,
    messageId: "forbidden"
});

Такой подход упрощает поддержку и локализацию правил.


Функция create

Метод create() является сердцем любого правила.

Сигнатура:

create(context)

Параметр context содержит информацию о текущем анализе:

create(context) {
    return {};
}

Возвращаемый объект определяет обработчики AST-узлов.


Обход AST

ESLint строит AST (Abstract Syntax Tree) и проходит по каждому узлу.

Пример:

create(context) {
    return {
        Identifier(node) {

        }
    };
}

Обработчик будет вызван для каждого идентификатора программы.

Пример AST:

const userName = "John";

Содержит узел:

Identifier

с именем:

userName

Пример простого правила

Запрет переменной с именем foo.

module.exports = {
    meta: {
        type: "problem",
        schema: []
    },

    create(context) {
        return {
            Identifier(node) {
                if (node.name === "foo") {
                    context.report({
                        node,
                        message: "Имя foo запрещено."
                    });
                }
            }
        };
    }
};

Использование context.report

Для регистрации ошибки применяется метод:

context.report()

Пример:

context.report({
    node,
    message: "Ошибка."
});

Либо через идентификатор сообщения:

context.report({
    node,
    messageId: "forbidden"
});

Возможна передача дополнительных данных:

context.report({
    node,
    messageId: "invalidName",
    data: {
        name: node.name
    }
});

Шаблон:

messages: {
    invalidName: "Имя '{{name}}' запрещено."
}

Автоматическое исправление

Если правило может исправлять код автоматически, используется свойство:

fixable: "code"

В отчёте необходимо вернуть исправление.

Пример:

context.report({
    node,
    message: "Используйте let вместо var.",
    fix(fixer) {
        return fixer.replaceText(node.kind, "let");
    }
});

Объект fixer предоставляет множество методов:

replaceText()
replaceTextRange()
insertTextAfter()
insertTextBefore()
remove()
removeRange()

Раздел configs

Плагин может содержать готовые наборы настроек.

Структура:

configs/
└── recommended.js

Пример конфигурации:

module.exports = {
    rules: {
        "example/no-console-log": "error",
        "example/prefer-const-name": "warn"
    }
};

Экспорт:

module.exports = {
    configs: {
        recommended: require("./configs/recommended")
    }
};

Подключение:

extends: [
    "plugin:example/recommended"
]

Несколько конфигураций

Часто создаются разные уровни строгости.

Пример:

recommended
strict
all

Структура:

configs: {
    recommended,
    strict,
    all
}

Назначение:

Конфигурация Особенности
recommended Базовые проверки
strict Усиленные требования
all Все правила плагина

Раздел processors

Процессоры позволяют анализировать не только JavaScript-файлы.

Примеры:

  • Markdown;
  • HTML;
  • Vue;
  • Astro;

Структура:

module.exports = {
    preprocess(text, filename) {
        return [text];
    },

    postprocess(messages) {
        return messages.flat();
    }
};

Экспорт:

module.exports = {
    processors: {
        markdown: require("./processors/markdown")
    }
};

Метод preprocess

Вызывается до запуска линтера.

Пример:

preprocess(source) {
    return [source];
}

Можно извлечь фрагменты JavaScript из документа:

# Заголовок

```js
console.log("test");

Затем передать найденные блоки в ESLint.

---

## Метод postprocess

Выполняется после проверки.

Пример:

```js
postprocess(messages) {
    return messages.flat();
}

Основная задача — объединение и корректировка сообщений об ошибках.


Раздел environments

Некоторые плагины экспортируют дополнительные окружения.

Пример:

module.exports = {
    environments: {
        browserplus: {
            globals: {
                MyGlobal: true
            }
        }
    }
};

Подключение:

{
    env: {
        "example/browserplus": true
    }
}

После этого глобальная переменная станет известна линтеру.


Организация крупных плагинов

По мере роста проекта структура становится более сложной.

Пример:

eslint-plugin-company/
│
├── src/
│   ├── rules/
│   │   ├── architecture/
│   │   ├── naming/
│   │   ├── security/
│   │   └── performance/
│   │
│   ├── configs/
│   ├── processors/
│   ├── environments/
│   └── utils/
│
├── tests/
│
└── docs/

Подход обеспечивает:

  • логическое разделение правил;
  • удобство сопровождения;
  • масштабируемость;
  • возможность независимой разработки модулей.

Вспомогательные утилиты

Большие плагины часто содержат общие функции.

Пример:

utils/
├── ast.js
├── selectors.js
└── messages.js

Файл:

function isConsoleLog(node) {
    return (
        node.object.name === "console" &&
        node.property.name === "log"
    );
}

module.exports = {
    isConsoleLog
};

Использование:

const { isConsoleLog } = require("../utils/ast");

Такой подход уменьшает дублирование кода между правилами.


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

Для проверки работы правил применяется RuleTester.

Структура:

tests/
└── no-console-log.test.js

Пример:

const { RuleTester } = require("eslint");
const rule = require("../rules/no-console-log");

Создание тестера:

const tester = new RuleTester();

Проверка:

tester.run(
    "no-console-log",
    rule,
    {
        valid: [
            "alert('hello')"
        ],

        invalid: [
            {
                code: "console.log('hello')",
                errors: 1
            }
        ]
    }
);

Тесты являются обязательной частью профессиональных ESLint-плагинов.


Экспорт полного объекта плагина

Итоговая структура главного файла может выглядеть следующим образом:

const noConsoleLog = require("./rules/no-console-log");
const recommended = require("./configs/recommended");
const markdown = require("./processors/markdown");

module.exports = {
    rules: {
        "no-console-log": noConsoleLog
    },

    configs: {
        recommended
    },

    processors: {
        markdown
    }
};

Такой объект представляет полный контракт между ESLint и плагином. Линтер загружает экспортированный модуль, регистрирует правила, конфигурации, процессоры и дополнительные расширения, после чего они становятся доступны для использования в конфигурации проекта.