Fixers в собственных правилах

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

Основной контракт фикса в пользовательском правиле строится вокруг объекта отчёта:

context.report({
  node,
  messageId: 'unexpectedSemicolon',
  fix(fixer) {
    return fixer.remove(node);
  }
});

Функция fix принимает объект fixer, предоставляемый ESLint, и обязана вернуть одно или несколько изменений текста либо null. Любое исправление трактуется как операция над исходной строкой, а не над AST-узлом как структурой.


Автоисправление в ESLint считается корректным только в случае, если трансформация:

  • детерминирована;
  • не зависит от внешнего состояния;
  • не меняет поведение программы за пределами локального контекста;
  • применима к конкретному диапазону текста без неоднозначности.

Функция fix должна быть чистой: вызовы побочных эффектов, асинхронность, использование внешних переменных приводят к неопределённому поведению механизма --fix.


Базовые операции fixer

Объект fixer предоставляет набор атомарных операций над текстом.

Удаление фрагмента

fixer.remove(node)

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


Замена текста узла

fixer.replaceText(node, 'newValue')

Операция заменяет текстовое содержимое узла на новую строку. AST при этом не перестраивается вручную — ESLint повторно анализирует итоговый код.

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

context.report({
  node,
  messageId: 'useConst',
  fix(fixer) {
    return fixer.replaceText(node.kind, 'const');
  }
});

Вставка текста до узла

fixer.insertTextBefore(node, 'debugger;\n')

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


Вставка текста после узла

fixer.insertTextAfter(node, ';')

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


Замена диапазона символов

fixer.replaceTextRange([start, end], 'replacement')

Наиболее низкоуровневая операция. Позволяет точно управлять изменениями, опираясь на индексы символов, полученные через sourceCode.getIndexFromLoc.

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


Получение исходного кода

Для точного определения границ исправлений используется SourceCode:

const sourceCode = context.getSourceCode();

Через него доступны:

  • getText(node)
  • getFirstToken(node)
  • getLastToken(node)
  • getTokenAfter(node)
  • getIndexFromLoc(loc)

Эти методы позволяют строить корректные диапазоны для replaceTextRange и избегать разрушения синтаксиса.


Множественные исправления

Функция fix может возвращать массив фиксов:

fix(fixer) {
  return [
    fixer.remove(node.left),
    fixer.insertTextAfter(node.right, ';')
  ];
}

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


Ограничения пересекающихся фиксов

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

Типичная проблема возникает при:

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

Свойство fixable в метаданных правила

Для включения автоисправлений правило должно явно объявлять поддержку фиксов:

meta: {
  fixable: 'code'
}

Возможные значения:

  • code — исправления, изменяющие логику кода;
  • whitespace — изменения, касающиеся только форматирования.

Это влияет на режимы работы ESLint и интеграцию с форматирующими инструментами.


Ограничения корректности фиксов

Автоисправления должны учитывать синтаксическую целостность. Ошибки часто возникают при:

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

Например, удаление аргумента функции требует корректного управления запятыми:

fixer.remove(argNode)

в чистом виде может оставить висячую запятую, поэтому часто требуется комбинированный фикс:

fix(fixer) {
  return [
    fixer.remove(argNode),
    fixer.remove(commaToken)
  ];
}

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

Во многих случаях надёжнее работать не с узлами, а с токенами:

const token = sourceCode.getTokenAfter(node);

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


Идемпотентность исправлений

Корректный фикс должен быть идемпотентным: повторное применение не должно изменять результат после первого запуска.

Пример некорректного подхода:

fix(fixer) {
  return fixer.replaceText(node, node.name + ' ');
}

Такое исправление может добавлять пробелы при каждом запуске.


Сложные сценарии трансформации

В случаях многошаговых изменений используется стратегия композиции:

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

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


Совместимость с suggestions

Помимо fix, ESLint поддерживает механизм предложений:

context.report({
  node,
  messageId: 'preferConst',
  suggest: [
    {
      messageId: 'convertToConst',
      fix(fixer) {
        return fixer.replaceText(varToken, 'const');
      }
    }
  ]
});

Разделение на fix и suggest позволяет разграничивать автоматические исправления и опциональные изменения, требующие подтверждения.


Практика построения устойчивых фиксов

Устойчивые автоисправления обычно следуют нескольким принципам:

  • работа через SourceCode вместо строковых эвристик;
  • минимизация диапазона изменений;
  • избегание предположений о форматировании;
  • использование токенов для учёта синтаксических границ;
  • разбиение сложных трансформаций на несколько простых операций.

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


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

Автоисправления ESLint не заменяют форматтеры. При совместной работе с инструментами форматирования изменения должны быть либо:

  • минимальными (логические правки);
  • либо строго ограниченными whitespace-операциями.

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


Диапазоны и точность изменений

При использовании replaceTextRange критически важно учитывать:

  • границы строк и символов;
  • кодировки и переносы строк;
  • положение комментариев;
  • вложенность выражений.

Ошибки в диапазонах приводят к синтаксическим сбоям, которые ESLint не всегда способен диагностировать до повторного парсинга.


Итоговая архитектурная роль фиксеров

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