Безопасные и небезопасные фиксы

В ESLint система автоматических исправлений основана на способности правил изменять исходный код через API fix, возвращая объект с диапазоном замены и новым текстом. Каждое правило может быть помечено как поддерживающее автофиксы через свойство meta.fixable, принимающее значения code или whitespace.

module.exports = {
  meta: {
    fixable: "code",
  },
  create(context) {
    return {
      Identifier(node) {
        if (node.name === "foo") {
          context.report({
            node,
            message: "Не использовать foo",
            fix(fixer) {
              return fixer.replaceText(node, "bar");
            },
          });
        }
      },
    };
  },
};

Наличие fix не гарантирует корректность преобразования во всех контекстах, поэтому различают безопасные и потенциально небезопасные исправления.


Понятие безопасных исправлений

Безопасное исправление — трансформация кода, при которой сохраняется семантика программы независимо от контекста выполнения. Такие фиксы не зависят от побочных эффектов и структуры окружающего кода.

Типичные категории безопасных фиксов

1. Форматирование и пробельные символы

Изменения, затрагивающие только внешний вид кода:

// было
const a=1

// стало
const a = 1;

Подобные исправления обычно относятся к meta.fixable = "whitespace" или реализуются через eslint --fix совместно с правилами стиля.


2. Простые замены токенов без изменения логики

// было
if (a == 0) {}

// стало
if (a === 0) {}

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


3. Удаление избыточных конструкций

// было
if (flag === true) {}

// стало
if (flag) {}

Безопасность зависит от точного анализа выражения: правило должно учитывать truthy/falsy значения.


4. Упрощение синтаксиса

// было
const obj = {
  a: a,
};

// стало
const obj = {
  a,
};

Такие изменения не влияют на поведение программы.


Небезопасные исправления и их источники

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


1. Изменение операторов с разной семантикой

// потенциально опасно
a == null  →  a === null

Проблема заключается в том, что == null также покрывает undefined, а строгое сравнение меняет поведение:

a == null // true для null и undefined
a === null // true только для null

2. Автоматическая замена логических выражений

// было
if (!a || !b) {}

// попытка "оптимизации"
if (!(a && b)) {}

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


3. Удаление кода без анализа побочных эффектов

// правило может удалить вызов
getValue();

Если функция вызывает побочные эффекты, удаление нарушает поведение программы. Без статического анализа side effects подобные фиксы считаются небезопасными.


4. Изменение порядка вычислений

// было
a() + b()

// стало (теоретическая оптимизация)
b() + a()

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


Архитектура фиксов в ESLint

Механизм исправлений базируется на объекте fixer, предоставляемом контекстом правила.

Основные методы fixer

  • fixer.replaceText(node, text)
  • fixer.remove(node)
  • fixer.insertTextBefore(node, text)
  • fixer.insertTextAfter(node, text)
  • fixer.replaceTextRange(range, text)

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


Проблема пересечения фиксов

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

const a=1
const b=2

Два правила могут одновременно:

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

При конфликте фикс может быть частично применён или отклонён полностью.


Параметры запуска --fix и режимы применения

Полное автоматическое исправление

eslint file.js --fix

Применяет все доступные фиксы, помеченные как безопасные.


Ограничение типов исправлений

eslint file.js --fix-type problem
eslint file.js --fix-type suggestion
eslint file.js --fix-type layout
  • problem — исправления потенциальных ошибок
  • suggestion — улучшения стиля и читаемости
  • layout — форматирование

Режим проверки без записи

eslint file.js --fix-dry-run

Позволяет оценить изменения без модификации файлов.


Критерии безопасности фиксов на уровне правил

Безопасность фикса зависит не от ESLint как инструмента, а от реализации конкретного правила.

Факторы, влияющие на безопасность:

1. Контекст AST-узлов

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

2. Отсутствие побочных эффектов

Правила должны учитывать:

  • вызовы функций
  • геттеры и сеттеры
  • перегрузку операторов через proxy

3. Детектирование типов

Использование TypeScript-информации (через @typescript-eslint) повышает точность фиксов.


Примеры корректных и некорректных фиксов

Корректный фикс

// правило: no-unused-vars
let x;

// фикс
// удаление x

Удаление переменной безопасно при отсутствии ссылок.


Некорректный фикс

// было
const result = fn(a++, b++);

// потенциальный фикс (опасен)
const result = fn(a, b);

Изменение инкрементов нарушает порядок и значение состояния.


Свойство meta.fixable и его роль

meta: {
  fixable: "code"
}

или

meta: {
  fixable: "whitespace"
}
  • code — изменения логики, синтаксиса, структуры
  • whitespace — только форматирование

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


Ограничения системы автоисправлений

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

Сложные случаи: частично безопасные исправления

Некоторые фиксы зависят от контекста.

Пример: приведение типов

// было
if (value == 1)

// фикс
if (Number(value) === 1)

Корректность зависит от предполагаемого типа value. При строковых значениях поведение может измениться.


Пример: изменение строковых кавычек

// было
const s = "text";

// стало
const s = 'text';

Безопасно только при отсутствии экранированных символов.


Влияние ESLint на пайплайн разработки

Автофиксы интегрируются в:

  • pre-commit hooks
  • CI/CD пайплайны
  • редакторы кода (VS Code, WebStorm)
  • форматтеры совместно с Prettier

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