Процессоры (processors) в ESLint предназначены для предварительной обработки файлов перед запуском механизма анализа. Они позволяют извлекать фрагменты JavaScript-кода из файлов других форматов, разбивать содержимое на виртуальные части или выполнять преобразования, необходимые для корректной проверки.
Наиболее распространённые сценарии использования процессоров:
Плагин ESLint может экспортировать один или несколько процессоров, после чего они становятся доступными для использования в конфигурации линтера.
Современный ESLint ожидает, что плагин будет экспортировать объект
processors. Каждый процессор представляет собой объект с
набором методов, отвечающих за предварительную и последующую обработку
содержимого файла.
Пример структуры плагина:
const plugin = {
processors: {
customProcessor: {
preprocess(text, filename) {
return [text];
},
postprocess(messages, filename) {
return messages[0];
},
supportsAutofix: true
}
}
};
export default plugin;
В этом примере процессор регистрируется под именем
customProcessor, после чего может использоваться в
конфигурации ESLint.
Свойство processors является словарём, где:
Пример:
export default {
processors: {
markdown: {
preprocess() {},
postprocess() {}
},
html: {
preprocess() {},
postprocess() {}
}
}
};
После публикации плагина оба процессора становятся частью его публичного API.
Если плагин называется eslint-plugin-docs, процессор
markdown будет доступен как:
docs/markdown
Метод preprocess() вызывается первым. Он получает
исходное содержимое файла и должен вернуть массив строк или виртуальных
файлов для дальнейшего анализа.
Сигнатура метода:
preprocess(text, filename)
Параметры:
| Параметр | Описание |
|---|---|
text |
Содержимое файла |
filename |
Имя анализируемого файла |
Простейшая реализация:
preprocess(text) {
return [text];
}
В таком случае ESLint получает исходный код без изменений.
Одной из ключевых возможностей процессоров является разбиение одного физического файла на несколько независимых частей.
Пример:
preprocess(text) {
const blocks = extractCodeBlocks(text);
return blocks;
}
Если метод вернёт:
[
"const a = 1;",
"const b = 2;"
]
ESLint выполнит анализ каждого блока отдельно.
Это особенно полезно для:
Классический пример работы процессора — извлечение JavaScript-блоков из Markdown.
Исходный документ:
# Документация
```js
const user = {};
```
```js
console.log(user);
```
Процессор может извлечь оба блока:
preprocess(text) {
const matches = [];
const regexp = /```js([\s\S]*?)```/g;
let match;
while ((match = regexp.exec(text))) {
matches.push(match[1]);
}
return matches;
}
В результате ESLint будет анализировать только содержимое блоков кода.
После завершения проверки ESLint вызывает метод
postprocess().
Сигнатура:
postprocess(messages, filename)
Параметры:
| Параметр | Описание |
|---|---|
messages |
Массив результатов проверки |
filename |
Имя файла |
Если в preprocess() был возвращён массив из нескольких
частей, то messages будет содержать результаты для каждой
из них.
Пример:
postprocess(messages) {
return messages.flat();
}
Такое решение объединяет сообщения всех виртуальных файлов в единый список.
Каждый элемент массива представляет собой объект диагностического сообщения ESLint.
Пример:
{
ruleId: "no-unused-vars",
severity: 2,
message: "'x' is assigned a value but never used.",
line: 3,
column: 7
}
В postprocess() разрешается изменять эти сообщения перед
возвратом в ESLint.
При анализе вложенных фрагментов строки и столбцы часто перестают соответствовать исходному файлу.
Рассмотрим Markdown:
Текст документа
```js
const a = ;
```
Фрагмент JavaScript начинается не с первой строки документа. Поэтому ошибка должна быть смещена относительно оригинального файла.
Пример корректировки:
postprocess(messages) {
return messages[0].map(message => ({
...message,
line: message.line + 2
}));
}
После обработки пользователь увидит правильную позицию ошибки в исходном документе.
Процессор может сообщить ESLint о поддержке автоисправлений через свойство:
supportsAutofix: true
Пример:
export default {
processors: {
markdown: {
preprocess(text) {
return [text];
},
postprocess(messages) {
return messages[0];
},
supportsAutofix: true
}
}
};
Если свойство отсутствует либо имеет значение false,
механизм автоматического исправления для такого процессора считается
неподдерживаемым.
Поддержка автоисправлений требует особой осторожности.
Во многих случаях ESLint генерирует исправление относительно виртуального блока кода, тогда как пользователь редактирует исходный файл.
Например:
```js
var x = 1;
```
Правило может предложить замену:
let x = 1;
Но процессору необходимо корректно перенести изменение обратно в оригинальный Markdown-документ.
Именно поэтому реализация поддержки автофиксов обычно значительно сложнее простой проверки.
Имена процессоров внутри плагина должны быть понятными и отражать назначение.
Удачные варианты:
processors: {
markdown: {},
html: {},
templates: {}
}
Менее удачные:
processors: {
proc1: {},
parser2: {}
}
Поскольку имя становится частью публичного API плагина, его изменение в будущих версиях может привести к несовместимости конфигураций пользователей.
Один плагин может предоставлять сразу несколько процессоров.
Пример:
export default {
processors: {
markdown: {
preprocess() {},
postprocess() {}
},
html: {
preprocess() {},
postprocess() {}
},
snippets: {
preprocess() {},
postprocess() {}
}
}
};
Такой подход позволяет объединять обработку различных форматов файлов в одном пакете.
При наличии нескольких процессоров часто возникает необходимость в повторном использовании логики.
Пример:
function flattenMessages(messages) {
return messages.flat();
}
export default {
processors: {
markdown: {
preprocess() {},
postprocess(messages) {
return flattenMessages(messages);
}
},
html: {
preprocess() {},
postprocess(messages) {
return flattenMessages(messages);
}
}
}
};
Это уменьшает дублирование кода и упрощает поддержку плагина.
Процессор и парсер решают разные задачи.
Парсер отвечает за преобразование JavaScript-кода в AST.
Процессор отвечает за подготовку содержимого файла до передачи его парсеру.
Схема работы выглядит следующим образом:
Файл
↓
Процессор
↓
JavaScript-код
↓
Парсер
↓
AST
↓
Правила ESLint
Поэтому процессор не заменяет парсер и не выполняет его функции.
Каждый элемент массива, возвращённого preprocess(),
рассматривается ESLint как отдельный виртуальный файл.
Пример:
preprocess() {
return [
"const a = 1;",
"const b = 2;",
"const c = 3;"
];
}
ESLint выполнит три независимые проверки.
Результат в postprocess():
[
messagesForFirstBlock,
messagesForSecondBlock,
messagesForThirdBlock
]
Необходимо учитывать это при объединении результатов.
Процессор должен корректно работать даже при отсутствии подходящих фрагментов.
Пример:
preprocess(text) {
const blocks = extractBlocks(text);
return blocks.length ? blocks : [""];
}
Либо:
preprocess(text) {
const blocks = extractBlocks(text);
return blocks;
}
Главное — обеспечить предсказуемое поведение для любых входных данных.
Для крупных проектов удобно выносить процессоры в отдельные модули.
Структура:
plugin/
├── processors/
│ ├── markdown.js
│ ├── html.js
│ └── templates.js
└── index.js
Файл процессора:
export default {
preprocess(text) {
return [text];
},
postprocess(messages) {
return messages.flat();
}
};
Главный файл плагина:
import markdown from "./processors/markdown.js";
import html from "./processors/html.js";
export default {
processors: {
markdown,
html
}
};
Подобная организация значительно упрощает развитие и сопровождение больших ESLint-плагинов.
Минимизировать преобразования данных. Чем меньше изменений вносится между исходным файлом и анализируемым кодом, тем проще поддерживать корректные координаты ошибок.
Сохранять информацию о позициях. При извлечении фрагментов полезно запоминать исходные строки начала и конца блока.
Не смешивать обязанности. Логика поиска блоков, преобразования координат и объединения результатов должна быть разделена на независимые функции.
Проверять работу автофиксов отдельно. Даже корректно работающий анализ может сопровождаться ошибочными исправлениями.
Поддерживать обратную совместимость имён процессоров. После публикации процессор становится частью внешнего API плагина.
Тестировать вложенные структуры. Особенно это важно для Markdown, HTML и шаблонных языков, где код может находиться на различных уровнях вложенности.
Экспорт процессоров является одним из ключевых механизмов расширения
ESLint, позволяющим применять правила линтера к данным, которые
изначально не являются обычными JavaScript-файлами. Благодаря объекту
processors, методам preprocess() и
postprocess(), а также поддержке виртуальных документов,
плагины могут интегрировать ESLint практически в любые текстовые форматы
и специализированные языки разметки.