Внутренняя модель ESLint основана на предположении, что анализируемый файл можно разобрать в абстрактное синтаксическое дерево JavaScript. Однако реальные проекты часто содержат файлы, в которых JavaScript встроен в другие форматы: HTML, Markdown, Vue SFC, шаблонные системы. В таких случаях исходный текст не является чистым JavaScript, и стандартный парсер ESLint не способен работать напрямую.
Механизм процессоров (processors) решает задачу преобразования нетипичных форматов в набор виртуальных JavaScript-артефактов, пригодных для анализа. Процессор выступает промежуточным слоем между исходным файлом и линтером, извлекая фрагменты кода, подготавливая их к разбору и затем сопоставляя диагностические сообщения обратно с оригинальным источником.
Общий конвейер работы ESLint с процессором можно представить как последовательность этапов:
.html)Ключевое свойство процессора заключается в том, что он не изменяет правила ESLint, а изменяет представление входных данных.
Процессор в ESLint реализуется как объект с двумя основными стадиями:
preprocess — разбиение исходного файла на
фрагментыpostprocess — объединение результатов анализаДополнительно используется метаинформация о поддерживаемых расширениях.
module.exports = {
preprocess(text, filename) {
return [];
},
postprocess(messages, filename) {
return [];
},
supportsAutofix: true
};
Функция preprocess возвращает массив строк, каждая из
которых рассматривается ESLint как отдельный виртуальный файл. Функция
postprocess получает массив массивов сообщений и должна
вернуть единый список диагностик.
HTML-файлы могут содержать JavaScript в нескольких местах:
<script> теговonclick,
onchange)Наиболее стабильный и распространённый сценарий — извлечение
содержимого <script>.
Пример HTML:
<html>
<body>
<script>
function test() {
console.log("hello");
}
</script>
</body>
</html>
Процессор должен:
<script>const SCRIPT_REGEX = /<script[^>]*>([\s\S]*?)<\/script>/gi;
function extractScripts(text) {
const scripts = [];
let match;
while ((match = SCRIPT_REGEX.exec(text)) !== null) {
scripts.push(match[1]);
}
return scripts;
}
module.exports = {
preprocess(text) {
return extractScripts(text);
},
postprocess(messages) {
return messages.flat();
}
};
Такой процессор извлекает только код без учета контекста и не выполняет корректное сопоставление строк.
Одна из ключевых сложностей — восстановление точных координат ошибок в исходном HTML. ESLint возвращает сообщения с указанием строки и столбца относительно виртуального файла, однако конечный пользователь ожидает видеть ошибки в HTML.
Для решения требуется:
<script> блока{
index: 0,
startLine: 10,
startColumn: 2
}
При обработке сообщения:
finalLine = startLine + message.line - 1
finalColumn = (message.line === 1)
? startColumn + message.column
: message.column
const SCRIPT_REGEX = /<script[^>]*>([\s\S]*?)<\/script>/gi;
function extractScriptsWithMap(text) {
const result = [];
const maps = [];
let match;
while ((match = SCRIPT_REGEX.exec(text)) !== null) {
const before = text.slice(0, match.index);
const startLine = before.split("\n").length;
const startColumn =
before.length - before.lastIndexOf("\n");
result.push(match[1]);
maps.push({
startLine,
startColumn
});
}
return { result, maps };
}
module.exports = {
preprocess(text) {
const { result, maps } = extractScriptsWithMap(text);
this._maps = maps;
return result;
},
postprocess(messages) {
return messages.flat().map((msg, i) => {
const map = this._maps[i] || { startLine: 1, startColumn: 1 };
return {
...msg,
line: msg.line + map.startLine - 1,
column:
msg.line === 1
? msg.column + map.startColumn
: msg.column
};
});
}
};
Процессоры подключаются через секцию overrides или через
плагины.
module.exports = {
overrides: [
{
files: ["*.html"],
processor: "html/processor"
}
]
};
В более старых версиях использовалась система
eslint-plugin-html, которая автоматически извлекала
<script> блоки и передавала их в ESLint.
Плагин eslint-plugin-html исторически служил для
линтинга JavaScript внутри HTML без необходимости ручной настройки
процессора. Он автоматически:
<script> блокиОднако его подход был ограничен:
В современных архитектурах предпочтение отдаётся явным процессорам или фреймворковым плагинам.
HTML может содержать несколько <script>
блоков:
<script>const a = 1;</script>
<script>const b = 2;</script>
Процессор должен:
Каждый блок становится независимой единицей анализа.
Сложный случай — JavaScript в атрибутах:
<button oncl ick="alert('ok')">OK</button>
Для таких случаев требуется:
parse5)Подобный подход значительно увеличивает сложность процессора, поскольку код не является полноценным скриптом.
Механизм процессоров имеет ряд системных ограничений:
Процессор работает на уровне текста, а не семантики документа.
При использовании процессоров нагрузка распределяется по количеству виртуальных файлов. Для HTML это означает:
Оптимизация достигается через:
В экосистеме JavaScript линтинг HTML-контента часто реализуется через специализированные плагины:
В таких системах процессор ESLint заменяется полноценным синтаксическим анализом на уровне фреймворка.
Процессор выступает адаптером между различными языковыми слоями. Его роль сводится к следующему:
Эта модель позволяет расширять ESLint за пределы JavaScript-файлов без изменения его внутреннего парсера.