AST-анализ в ESLint опирается на систему областей видимости (scope analysis), которая позволяет сопоставлять идентификаторы переменных с их декларациями, отслеживать ссылки на них и определять контекст использования кода. Эта модель критична для правил, связанных с неиспользуемыми переменными, теневыми объявлениями, утечками в глобальную область, корректностью импортов и анализом замыканий.
JavaScript имеет лексическую область видимости, в которой каждая функция, блок или модуль формирует собственный scope. Внутри этой модели выделяются несколько базовых типов:
Каждая из них может содержать объявления переменных
(var, let, const, функции,
классы, импорты), а также ссылки на идентификаторы, определённые выше по
цепочке scopes.
Важный аспект анализа заключается в том, что одно и то же имя может существовать в нескольких вложенных областях, и корректное разрешение зависит от ближайшего объявления по цепочке.
Внутри ESLint используется библиотека eslint-scope, которая строит дерево областей видимости на основе AST, полученного после парсинга кода.
Основные задачи этой системы:
Результатом работы становится объект ScopeManager,
содержащий дерево scope-узлов и набор переменных с их ссылками.
Каждый scope представляет собой объект, включающий:
type — тип области (function, block, global,
module)set — таблица объявленных переменныхreferences — список ссылок на идентификаторыthrough — ссылки, не разрешённые в текущем scopechildScopes — вложенные областиupper — ссылка на родительскую областьПеременная в рамках scope представлена объектом
Variable, который включает:
defs (декларации)referencesСсылки (Reference) связываются либо с переменной, либо
остаются «неразрешёнными», если декларация отсутствует в доступных
scope.
Scope-анализ выполняется на этапе обхода AST. После парсинга ESLint запускает анализатор, который проходит по узлам дерева и создаёт scope-структуру.
Процесс включает несколько фаз:
{} и формирование block-scopeКаждый новый узел AST может инициировать создание нового scope, если он соответствует правилам (например, FunctionDeclaration, ArrowFunctionExpression, BlockStatement с let/const).
Scope-анализ становится доступным через контекст правила. В API правила существует метод:
context.getScope()
Он возвращает текущий scope, связанный с узлом AST, на котором выполняется проверка.
Это позволяет правилам выполнять точные проверки без повторной реализации анализа переменных.
create(context) {
return {
Identifier(node) {
const scope = context.getScope();
const variable = scope.set.get(node.name);
if (!variable) {
return;
}
if (variable.references.length === 0) {
context.report({
node,
message: 'Переменная не используется'
});
}
}
};
}
В реальных правилах анализ более сложный, так как необходимо учитывать shadowing, параметры функций, импорты и глобальные объекты.
Механизм разрешения работает по цепочке scopes:
upperthroughТакой подход позволяет точно определять:
Список through содержит ссылки, которые не были
разрешены ни в одном scope.
Это используется для:
Например:
function test() {
console.log(notDefinedVar);
}
notDefinedVar попадёт в through, так как
отсутствует декларация в доступных scopes.
Shadowing возникает, когда внутренняя область объявляет переменную с тем же именем, что и внешняя.
const value = 1;
function fn() {
const value = 2;
return value;
}
Scope-анализ фиксирует:
valueЭто позволяет правилам ESLint выявлять нежелательное перекрытие переменных, особенно в сложных кодовых базах.
Scope-модель используется для анализа замыканий, где внутренние функции обращаются к внешним переменным.
function outer() {
const data = 10;
return function inner() {
return data;
};
}
Здесь inner scope содержит reference к переменной из
внешнего scope. Такая связь фиксируется через цепочку
upper.
Это позволяет определять:
В модульной системе ES Modules создаётся отдельный module-scope.
import fs from 'fs';
const x = 1;
Импорты регистрируются как переменные в module-scope и участвуют в общем анализе:
Scope-анализ является основой для множества правил ESLint:
Каждое правило использует данные scope-дерева, чтобы избежать повторного обхода AST и повысить точность диагностики.
Scope-анализ выполняется один раз на файл и кешируется внутри процесса линтинга. Оптимизации включают:
При больших проектах эффективность анализа зависит от глубины вложенности scopes и количества идентификаторов.
Несмотря на точность, scope-анализ имеет ограничения:
evalwithТакие конструкции либо частично игнорируются, либо обрабатываются как «неразрешимые» ссылки.
Scope-анализ является промежуточным слоем между парсером и правилами. Он абстрагирует сложность AST и предоставляет единый интерфейс для работы с переменными.
Архитектурно он располагается следующим образом:
Такое разделение позволяет писать правила, не учитывающие детали парсинга и структуры AST на низком уровне, опираясь на семантическое представление кода через scopes.