Создание собственного форматтера

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

После выполнения анализа ESLint формирует массив сообщений о проблемах. Каждое сообщение содержит:

  • имя файла
  • номер строки и столбца
  • уровень серьёзности (warning или error)
  • идентификатор правила
  • текст сообщения

Форматтер получает этот массив и преобразует его в строку или другой формат вывода (JSON, HTML, таблицы и т.д.).

Ключевой принцип: форматтер не влияет на процесс проверки кода, он работает только с результатом.

Структура пользовательского форматтера

Пользовательский форматтер в ESLint — это JavaScript-модуль, который экспортирует функцию.

Базовый контракт:

module.exports = function (results) {
    return "строка вывода";
};

Параметр results — массив объектов результатов проверки.

Структура одного результата:

{
    filePath: "src/index.js",
    messages: [
        {
            ruleId: "no-console",
            severity: 2,
            message: "Unexpected console statement",
            line: 10,
            column: 5,
            endLine: 10,
            endColumn: 12
        }
    ],
    errorCount: 1,
    warningCount: 0,
    fixableErrorCount: 0,
    fixableWarningCount: 0
}

Подключение собственного форматтера

ESLint позволяет указать путь к кастомному форматтеру через CLI:

eslint . --format ./formatters/simple-formatter.js

Также возможно использование через Node API:

const eslint = require("eslint");

const cli = new eslint.CLIEngine({
    formatter: "./formatters/simple-formatter.js"
});

Простейший форматтер

Минимальный форматтер может просто перечислять ошибки:

module.exports = function (results) {
    let output = "";

    results.forEach(result => {
        if (result.messages.length === 0) return;

        output += `\n${result.filePath}\n`;

        result.messages.forEach(msg => {
            output += `  ${msg.line}:${msg.column}  ${msg.message} (${msg.ruleId})\n`;
        });
    });

    return output;
};

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

Работа с уровнями серьёзности

Сообщения имеют поле severity:

  • 1 — предупреждение (warning)
  • 2 — ошибка (error)

Это позволяет разделять вывод:

const isError = msg.severity === 2;

output += isError ? "ERROR: " : "WARN: ";
output += `${msg.message}\n`;

Разделение уровней полезно для построения цветных терминальных интерфейсов или интеграции с CI.

Агрегация статистики

Форматтер может анализировать весь массив результатов и формировать сводку:

module.exports = function (results) {
    let totalErrors = 0;
    let totalWarnings = 0;

    results.forEach(file => {
        totalErrors += file.errorCount;
        totalWarnings += file.warningCount;
    });

    return `
Errors: ${totalErrors}
Warnings: ${totalWarnings}
`;
};

Такая агрегация используется для построения отчётов уровня проекта.

Сортировка и группировка

Расширенные форматтеры часто группируют сообщения по правилам или файлам:

const grouped = {};

results.forEach(result => {
    result.messages.forEach(msg => {
        if (!grouped[msg.ruleId]) {
            grouped[msg.ruleId] = [];
        }

        grouped[msg.ruleId].push({
            file: result.filePath,
            line: msg.line,
            message: msg.message
        });
    });
});

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

Генерация JSON-формата

Форматтер может возвращать структурированные данные:

module.exports = function (results) {
    return JSON.stringify(results, null, 2);
};

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

Создание HTML-форматтера

Форматтер может генерировать HTML-отчёты:

module.exports = function (results) {
    let html = `<html><body><h1>ESLint Report</h1>`;

    results.forEach(result => {
        html += `<h2>${result.filePath}</h2><ul>`;

        result.messages.forEach(msg => {
            html += `<li>${msg.line}:${msg.column} - ${msg.message}</li>`;
        });

        html += `</ul>`;
    });

    html += `</body></html>`;
    return html;
};

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

Использование вспомогательных библиотек

В форматтерах допустимо использование сторонних пакетов, например:

  • chalk — для цветного вывода в терминал
  • strip-ansi — для очистки управляющих символов
  • lodash — для группировок и агрегации

Пример с цветами:

const chalk = require("chalk");

module.exports = function (results) {
    let output = "";

    results.forEach(result => {
        result.messages.forEach(msg => {
            if (msg.severity === 2) {
                output += chalk.red(msg.message) + "\n";
            } else {
                output += chalk.yellow(msg.message) + "\n";
            }
        });
    });

    return output;
};

Работа с пустыми результатами

При отсутствии ошибок форматтер обязан корректно обрабатывать пустой массив:

if (results.length === 0) {
    return "No linting issues found";
}

Это важно для CI-систем, где отсутствие вывода может интерпретироваться как ошибка.

Потоковая логика форматтера

Несмотря на синхронную природу API, форматтер может имитировать потоковую обработку данных:

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

Такой подход необходим при работе с большими проектами, содержащими тысячи файлов.

Ограничения и особенности

Форматтеры ESLint имеют ряд особенностей:

  • не могут изменять результаты линтинга
  • работают только с финальным массивом данных
  • должны возвращать строку или сериализуемый результат
  • выполняются в контексте Node.js

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

Интеграция с CI/CD

Форматтеры часто адаптируются под системы непрерывной интеграции:

  • GitHub Actions
  • GitLab CI
  • Jenkins

Пример форматтера для CI-логов:

module.exports = function (results) {
    let output = "";

    results.forEach(result => {
        result.messages.forEach(msg => {
            output += `${result.filePath}:${msg.line}:${msg.column} `;
            output += `${msg.severity === 2 ? "error" : "warn"} `;
            output += `${msg.message}\n`;
        });
    });

    return output;
};

Такой формат легко парсится внешними системами.

Расширяемая модель форматирования

Форматтер может быть построен как система плагинов:

const formatters = [
    require("./parts/header"),
    require("./parts/body"),
    require("./parts/footer")
];

module.exports = function (results) {
    return formatters.map(fn => fn(results)).join("\n");
};

Это позволяет разделять ответственность и упрощать поддержку сложных отчётов.

Обработка путей файлов

Часто требуется нормализация путей:

const path = require("path");

const normalized = path.relative(process.cwd(), result.filePath);

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

Форматтер как инструмент анализа качества кода

Хотя основная задача форматтера — представление данных, он фактически становится инструментом аналитики:

  • выявление наиболее проблемных файлов
  • распределение ошибок по типам правил
  • оценка динамики качества кода

За счёт этого форматтеры часто используются в связке с метриками качества и отчётами разработческих команд.