Написание fixera

Механизм автоисправлений в ESLint строится вокруг функции fixer, которая передаётся в объект контекста правила и используется внутри context.report. Фиксер формирует набор операций над исходным кодом, которые ESLint способен применить без участия разработчика. Каждая такая операция описывает минимальное и точное изменение текстового представления программы, сохраняя её синтаксическую корректность и поведение.

Фиксеры являются частью контрактной модели правила: правило не только обнаруживает проблему, но и формирует корректное преобразование кода, если оно возможно. Важным аспектом является строгая локальность изменений — фиксер не выполняет произвольную трансформацию AST, а оперирует диапазонами текста и узлами.

Базовая структура фикса в ESLint-правиле

Автоисправление определяется внутри вызова context.report через поле fix:

context.report({
  node,
  message: "Использование var запрещено",
  fix(fixer) {
    return fixer.replaceText(node, "let");
  }
});

Функция fixer предоставляет API для генерации операций изменения исходного кода. Возвращаемое значение может быть:

  • одиночным фиксом
  • массивом фиксов

ESLint применяет их последовательно, если они не конфликтуют по диапазонам.

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

Основные операции fixer API

replaceText

Наиболее распространённая операция:

fixer.replaceText(node, "newText");

Замещает текст узла полностью. Используется для простых замен идентификаторов, литералов и выражений, когда структура кода сохраняется.

Типичный пример:

fixer.replaceText(node, "const");

Применяется только если замена не нарушает синтаксис и семантику окружающего кода.


replaceTextRange

fixer.replaceTextRange([start, end], "value");

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

Пример применения — удаление части выражения или замена оператора:

fixer.replaceTextRange([node.start, node.end], "0");

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


insertTextBefore / insertTextAfter

fixer.insertTextBefore(node, "text");
fixer.insertTextAfter(node, "text");

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

Частые сценарии:

  • добавление ключевого слова
  • вставка аргумента
  • добавление импортов

Пример:

fixer.insertTextBefore(node, "/* eslint-disable */\n");

remove / removeRange

Удаление элементов кода:

fixer.remove(node);
fixer.removeRange([start, end]);

Применяется для удаления лишних выражений, импортов, параметров.

Удаление требует особой осторожности, поскольку может изменить синтаксическую структуру.

Работа с SourceCode и текстовыми границами

Для построения надёжных фиксов используется объект sourceCode, доступный через контекст:

const sourceCode = context.getSourceCode();
const text = sourceCode.getText(node);

Этот объект позволяет:

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

Фиксеры часто комбинируются с sourceCode.getFirstToken и getLastToken для точной привязки к синтаксису.

Пример:

const firstToken = sourceCode.getFirstToken(node);
const lastToken = sourceCode.getLastToken(node);

return fixer.replaceTextRange(
  [firstToken.range[0], lastToken.range[1]],
  "replacement"
);

Ограничения и требования к безопасным фиксам

Фиксеры должны быть безопасными, то есть гарантировать, что после применения код остаётся:

  • синтаксически корректным
  • предсказуемым по поведению
  • без побочных эффектов

ESLint не проверяет семантику кода после фикса автоматически, поэтому ответственность за корректность лежит на разработчике правила.

Основные ограничения:

  1. Запрет на перекрывающиеся диапазоны Два фикса не могут изменять один и тот же участок кода.

  2. Недопустимость зависимых изменений Фиксы должны быть независимыми, если они возвращаются массивом.

  3. Стабильность результата Повторное применение фикса не должно менять код дальше (идемпотентность).

  4. Сохранение форматирования (по возможности) Не всегда обязательно, но желательно избегать разрушения читаемости.

Структура правила с fixers

Полная структура кастомного правила включает meta и create:

export default {
  meta: {
    type: "problem",
    fixable: "code",
    messages: {
      noVar: "Использование var запрещено"
    }
  },

  create(context) {
    return {
      VariableDeclaration(node) {
        if (node.kind === "var") {
          context.report({
            node,
            messageId: "noVar",
            fix(fixer) {
              return fixer.replaceTextRange(
                [node.start, node.start + 3],
                "let"
              );
            }
          });
        }
      }
    };
  }
};

Поле fixable в meta обязательно указывает, что правило поддерживает автоисправления. Возможные значения:

  • "code" — исправление кода
  • "whitespace" — только форматирование

Работа с токенами и контекстом синтаксиса

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

const token = sourceCode.getTokenAfter(node);

Это особенно важно, когда фиксер зависит от точного положения символов, например при вставке запятых или скобок.

Пример безопасного добавления запятой:

fixer.insertTextAfter(node, ",");

Однако более надёжный подход — проверка следующего токена:

const nextToken = sourceCode.getTokenAfter(node);

if (nextToken.value !== ",") {
  return fixer.insertTextAfter(node, ",");
}

Множественные фиксы

Фиксер может возвращать массив операций:

fix(fixer) {
  return [
    fixer.remove(node),
    fixer.insertTextAfter(parent, ";")
  ];
}

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

Важно учитывать порядок применения: ESLint применяет фиксы в последовательности, но конфликтующие диапазоны приведут к отказу применения.

Типичные ошибки при реализации фиксеров

Неверное использование диапазонов

Ошибки в range приводят к повреждению кода:

  • случайное удаление пробелов
  • захват лишних символов
  • обрезка комментариев

Игнорирование комментариев

AST-узлы не включают комментарии, поэтому:

fixer.replaceText(node, "x");

может удалить комментарии вокруг. Для сохранения комментариев требуется работа через токены.


Несоответствие синтаксису

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

fixer.replaceText(node, "return;");

если узел находится внутри выражения.


Нестабильные фиксы

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

Продвинутые техники

Контекстно-зависимые фиксы

Иногда фиксер зависит от окружения:

const scope = context.getScope();

Это позволяет проверять, не конфликтует ли изменение с другими переменными.


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

В сложных случаях применяется частичная реконструкция кода:

fixer.replaceText(node, `${left} + ${right}`);

Минимизация изменений

Лучшие фиксы изменяют минимально возможный участок кода. Это снижает риск конфликтов с форматированием и другими правилами.

Взаимодействие с автоформатированием

Фиксеры ESLint часто работают вместе с форматерами (Prettier или встроенные правила стилизации). Важно учитывать:

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

Безопасные паттерны проектирования фиксов

Наиболее устойчивые подходы:

  • замена одного токена
  • удаление узла без вложенных структур
  • вставка небольших фрагментов
  • использование replaceText вместо диапазонов

Менее надёжные:

  • сложные диапазонные трансформации
  • многократные вставки в одном месте
  • изменения с зависимостью от форматирования

Поведение ESLint при конфликте фиксов

Если несколько правил предлагают пересекающиеся изменения:

  • фиксы могут быть отклонены
  • ESLint применяет только непересекающиеся изменения
  • при конфликте правило автофиксации может быть проигнорировано

Поэтому проектирование фиксеров требует учёта возможных взаимодействий между правилами.