Регистрация правил

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

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

Регистрация правила представляет собой процесс объявления правила внутри плагина ESLint и предоставления его линтеру для дальнейшего использования в конфигурации.


Архитектура пользовательского правила

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

  1. Метаданные (meta).
  2. Логика анализа (create).

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

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

    create(context) {
        return {
            CallEx * pression(node) {
                // логика проверки
            }
        };
    }
};

После регистрации правило становится доступным для использования в конфигурации ESLint.


Роль объекта meta

Секция meta содержит служебную информацию о правиле.

Пример:

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

Наиболее важные поля:

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

Типы правил

ESLint выделяет три категории:

type: "problem"

Поиск потенциальных ошибок.

type: "suggestion"

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

type: "layout"

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

Пример:

meta: {
    type: "layout"
}

Такое правило относится исключительно к оформлению кода.


Метод create

Метод create() вызывается при запуске анализа файла.

Он получает объект контекста:

create(context) {
    return {};
}

Через context правило взаимодействует с ESLint:

create(context) {
    console.log(context.filename);
}

На практике используется набор методов объекта контекста.

Например:

context.report(...)

или

context.getSourceCode()

Регистрация обработчиков узлов AST

ESLint анализирует код через абстрактное синтаксическое дерево (AST).

Каждому типу узла соответствует обработчик:

create(context) {
    return {
        VariableDeclaration(node) {

        },

        FunctionDeclaration(node) {

        },

        CallEx * pression(node) {

        }
    };
}

При обходе дерева ESLint автоматически вызывает нужный обработчик.

Код:

const user = "Alex";

console.log(user);

Создаёт узлы:

  • VariableDeclaration
  • VariableDeclarator
  • Identifier
  • CallExpression
  • MemberExpression

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


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

Рассмотрим правило, запрещающее использование console.log.

Реализация

module.exports = {
    meta: {
        type: "problem",
        schema: [],
        messages: {
            forbiddenConsole:
                "Использование console.log запрещено."
        }
    },

    create(context) {
        return {
            CallEx * pression(node) {
                if (
                    node.callee.type === "MemberExpression" &&
                    node.callee.object.name === "console" &&
                    node.callee.property.name === "log"
                ) {
                    context.report({
                        node,
                        messageId: "forbiddenConsole"
                    });
                }
            }
        };
    }
};

Теперь правило способно обнаруживать:

console.log("debug");

и выдавать диагностическое сообщение.


Регистрация через индексный файл

В плагине ESLint все правила обычно собираются в одном месте.

Структура:

eslint-plugin-company/
│
├── rules/
│   ├── no-console-log.js
│   ├── no-debugger.js
│   └── no-var.js
│
└── index.js

Файл регистрации:

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

        "no-debugger":
            require("./rules/no-debugger"),

        "no-var":
            require("./rules/no-var")
    }
};

После этого ESLint получает доступ ко всем правилам плагина.


Пространство имён правила

Каждое зарегистрированное правило используется через имя плагина.

Плагин:

eslint-plugin-company

Правило:

no-console-log

Полное имя:

company/no-console-log

Конфигурация:

module.exports = {
    rules: {
        "company/no-console-log": "error"
    }
};

Регистрация набора правил

Плагин может предоставлять не только отдельные правила, но и готовые конфигурации.

Пример:

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

    configs: {
        recommended: {
            rules: {
                "company/no-console-log": "error"
            }
        }
    }
};

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

extends: [
    "plugin:company/recommended"
]

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


Использование schema при регистрации

Многие правила принимают настройки.

Например:

{
    "company/max-function-lines": [
        "error",
        50
    ]
}

Для описания параметров используется schema.

Пример:

meta: {
    schema: [
        {
            type: "integer",
            minimum: 1
        }
    ]
}

Теперь ESLint сможет валидировать настройки до выполнения правила.

Некорректная конфигурация:

{
    "company/max-function-lines": [
        "error",
        "many"
    ]
}

будет отклонена ещё на этапе загрузки конфигурации.


Регистрация сообщений через messages

Сообщения рекомендуется хранить отдельно.

Плохо:

context.report({
    node,
    message:
        "Использование console.log запрещено"
});

Лучше:

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

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

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

Преимущества:

  • централизованное хранение сообщений;
  • повторное использование;
  • упрощение локализации;
  • единый стиль оформления.

Регистрация параметризованных сообщений

Сообщение может содержать переменные.

Описание:

messages: {
    forbiddenMethod:
        "Метод '{{name}}' запрещён."
}

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

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

Результат:

Метод 'alert' запрещён.

Регистрация автоисправляемого правила

ESLint поддерживает автоматическое исправление ошибок.

Для этого необходимо указать:

meta: {
    fixable: "code"
}

или

meta: {
    fixable: "whitespace"
}

Пример:

module.exports = {
    meta: {
        fixable: "code"
    },

    create(context) {
        return {
            Identifier(node) {
                if (node.name === "foo") {
                    context.report({
                        node,
                        message: "foo запрещён",

                        fix(fixer) {
                            return fixer.replaceText(
                                node,
                                "bar"
                            );
                        }
                    });
                }
            }
        };
    }
};

Запуск:

eslint . --fix

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


Регистрация правил с подсказками

Иногда автоматическая замена неоднозначна.

Для таких случаев используются suggestions.

Регистрация:

meta: {
    hasSuggestions: true
}

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

context.report({
    node,

    message: "Использование var нежелательно",

    suggest: [
        {
            desc: "Заменить на let",

            fix(fixer) {
                return fixer.replaceText(
                    node,
                    "let"
                );
            }
        }
    ]
});

Редактор может предложить исправление без автоматического применения.


Получение исходного кода

Во многих правилах требуется доступ к тексту программы.

Для этого используется:

const sourceCode =
    context.getSourceCode();

Получение фрагмента:

sourceCode.getText(node);

Пример:

const text =
    sourceCode.getText(node);

Результат:

user.profile.name

Подобные возможности активно используются при регистрации сложных правил.


Регистрация нескольких обработчиков

Одно правило может анализировать множество типов узлов.

Пример:

create(context) {
    return {
        FunctionDeclaration(node) {

        },

        ArrowFunctionEx * pression(node) {

        },

        FunctionEx * pression(node) {

        }
    };
}

Такой подход позволяет реализовать единую проверку для всех вариантов объявления функций.


Использование селекторов AST

ESLint поддерживает регистрацию обработчиков через селекторы.

Пример:

create(context) {
    return {
        "CallExpression[callee.name='eval']"(node) {
            context.report({
                node,
                message: "eval запрещён"
            });
        }
    };
}

Вместо ручных проверок используется декларативный фильтр.

Это делает код правила значительно компактнее.


Регистрация обработчиков входа и выхода

Для некоторых задач требуется отслеживать момент входа и выхода из узла.

Синтаксис:

create(context) {
    return {
        FunctionDeclaration(node) {

        },

        "FunctionDeclaration:exit"(node) {

        }
    };
}

Вход выполняется до обработки дочерних элементов.

Выход выполняется после завершения обхода поддерева.

Подобный механизм часто используется для анализа областей видимости и стеков состояний.


Регистрация правила внутри плагина ESLint

Полный пример структуры:

eslint-plugin-company/
│
├── index.js
│
└── rules/
    └── no-console-log.js

rules/no-console-log.js

module.exports = {
    meta: {
        type: "problem",

        schema: [],

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

    create(context) {
        return {
            CallEx * pression(node) {
                if (
                    node.callee.type === "MemberExpression" &&
                    node.callee.object.name === "console" &&
                    node.callee.property.name === "log"
                ) {
                    context.report({
                        node,
                        messageId: "forbidden"
                    });
                }
            }
        };
    }
};

index.js

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

Конфигурация проекта

module.exports = {
    plugins: [
        "company"
    ],

    rules: {
        "company/no-console-log":
            "error"
    }
};

После загрузки ESLint регистрирует правило, подключает его к процессу анализа и начинает применять ко всем проверяемым файлам.


Типичные ошибки при регистрации правил

Отсутствие schema

Неправильно:

meta: {}

Правильно:

meta: {
    schema: []
}

Даже если правило не принимает параметров, схема должна быть определена явно.

Отсутствие messages

Неправильно:

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

Предпочтительно:

messages: {
    error: "Ошибка"
}

Неверное имя правила

Регистрация:

rules: {
    "no-console-log": rule
}

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

"company/no-console": "error"

ESLint не сможет найти правило из-за несовпадения имени.

Отсутствие экспорта

Неправильно:

const rule = {};

Правильно:

module.exports = rule;

Без экспорта правило не будет зарегистрировано внутри плагина.


Жизненный цикл зарегистрированного правила

После подключения ESLint выполняет последовательность действий:

  1. Загружает плагин.
  2. Находит зарегистрированное правило.
  3. Считывает объект meta.
  4. Проверяет конфигурацию через schema.
  5. Вызывает функцию create.
  6. Формирует набор обработчиков AST.
  7. Выполняет обход синтаксического дерева.
  8. Вызывает соответствующие обработчики.
  9. Формирует диагностические сообщения.
  10. Применяет исправления и подсказки при необходимости.

Именно регистрация связывает реализацию правила с инфраструктурой ESLint, превращая обычный JavaScript-модуль в полноценный механизм статического анализа кода.