AST в ESLint рассматривается как основа всей системы анализа кода, поскольку любая проверка, преобразование или диагностика выполняется не над исходным текстом напрямую, а над его структурным представлением. Абстрактное синтаксическое дерево (AST) позволяет разложить JavaScript-код на узлы, каждый из которых описывает конкретную конструкцию языка: выражение, оператор, объявление переменной, вызов функции и так далее. Именно к этим узлам и применяются механизмы посещения (visitors), определяющие поведение правил ESLint.
AST представляет код в виде иерархического дерева, где каждый узел
соответствует синтаксической конструкции. Например, конструкция вызова
функции fn(a, b) будет представлена узлом
CallExpression, внутри которого находятся дочерние узлы
Identifier (имя функции) и список аргументов.
Ключевые особенности AST в контексте ESLint:
Каждый узел содержит поле type, определяющее его
природу, а также дополнительные свойства, зависящие от типа узла.
Для анализа AST ESLint использует обход дерева, при котором каждый узел посещается в определённом порядке. Этот процесс реализуется через паттерн “visitor”, где набор функций соответствует типам узлов.
Существуют два базовых режима обхода:
Такой подход позволяет реализовывать как простые проверки (например, поиск запрещённых идентификаторов), так и сложные анализы, зависящие от контекста.
Каждое правило ESLint реализуется через функцию create,
возвращающую объект посетителей. Ключи этого объекта соответствуют типам
узлов AST или селекторам.
Базовая структура правила:
export default {
create(context) {
return {
Identifier(node) {
// обработка идентификатора
}
};
}
};
Каждая функция-обработчик вызывается автоматически при обходе AST, когда встречается узел соответствующего типа.
Наиболее прямой способ — указание имени типа узла:
IdentifierCallExpressionVariableDeclarationFunctionDeclarationКаждая функция получает объект node, содержащий
структуру узла и его свойства.
Помимо простого указания типа узла, ESLint поддерживает селекторы, основанные на CSS-подобной нотации. Они позволяют описывать более сложные условия.
Примеры селекторов:
CallExpression Identifier — идентификатор внутри вызова
функцииMemberExpression[computed=false] — доступ к свойству
через точкуFunctionDeclaration[id.name="test"] — функция с
конкретным именемСелекторы позволяют существенно сократить необходимость ручной проверки вложенности узлов внутри обработчика.
Каждый посетитель получает доступ к объекту context,
который предоставляет API для взаимодействия с процессом линтинга.
Основные возможности context:
context.report() — регистрация ошибки или
предупрежденияcontext.getSourceCode() — доступ к исходному коду и
утилитам анализаcontext.options — параметры правилаcontext.filename — имя анализируемого файлаПример использования:
Identifier(node) {
if (node.name === "eval") {
context.report({
node,
message: "Использование eval запрещено"
});
}
}
SourceCode предоставляет дополнительные инструменты для
работы с AST и текстом:
Хотя AST описывает структуру, SourceCode связывает её с
реальным текстом, что позволяет делать точные диагностические сообщения
и фиксы.
AST обходится глубоко-первым способом (depth-first traversal). Это означает, что:
Для узла:
function test() {
return 1;
}
последовательность посещений будет включать:
В рамках ESLint AST наиболее часто используются следующие категории узлов:
CallExpressionBinaryExpressionLogicalExpressionAssignmentExpressionЭти узлы представляют операции, вычисления и вызовы.
VariableDeclarationFunctionDeclarationClassDeclarationОни формируют структуру программы.
IdentifierLiteralМинимальные атомарные элементы AST.
ESLint позволяет явно разделять логику входа и выхода с помощью
суффикса :exit.
CallExpression: {
enter(node) {
// до обработки аргументов
},
exit(node) {
// после обработки аргументов
}
}
Использование exit-обработчиков критично при анализе вложенных структур, где требуется агрегировать данные снизу вверх.
При обходе AST ESLint формирует таблицу соответствий между типами узлов и функциями обработчиков. Для каждого узла выполняется:
Определение типа узла
Поиск соответствующих visitor-функций
Вызов обработчиков в порядке приоритета:
Если для одного узла определено несколько селекторов, они вызываются последовательно.
Посетители не ограничены статической структурой. Они могут:
Пример типичного паттерна:
create(context) {
let inFunction = false;
return {
FunctionDeclaration: {
enter() {
inFunction = true;
},
exit() {
inFunction = false;
}
},
ReturnStatement(node) {
if (!inFunction) {
context.report({
node,
message: "return вне функции"
});
}
}
};
}
В сложных правилах часто используется объединение логики нескольких типов узлов. Один и тот же visitor может обрабатывать разные конструкции:
FunctionDeclarationFunctionExpressionArrowFunctionExpressionЭто позволяет абстрагировать поведение от конкретного синтаксиса и работать с семантикой функции как таковой.
Посетители работают уже после этапа парсинга, когда исходный код преобразован в AST. ESLint поддерживает различные парсеры (например, Babel или TypeScript), но модель visitors остаётся неизменной, так как основана на стандарте ESTree.
Таким образом, посетители полностью отделены от синтаксиса конкретной версии JavaScript, работая с унифицированной структурой узлов.
Производительность посетителей зависит от количества обрабатываемых узлов. Для оптимизации применяются:
Особенно важно избегать избыточной логики в часто встречающихся
узлах, таких как Identifier, поскольку они присутствуют
практически в каждом AST.
Некоторые правила требуют учитывать контекст узла:
"use strict"Эта информация не всегда содержится напрямую в AST-узле и требует
дополнительного анализа через scopeManager, предоставляемый
ESLint.
ScopeManager строит дополнительное представление поверх
AST, связывая идентификаторы с их объявлениями. Посетители могут
использовать это для более точного анализа:
Таким образом, AST visitors работают в связке с системой областей видимости, расширяя возможности статического анализа.