Конфликты между правилами

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

Основной источник конфликтов — многослойность конфигурации ESLint. Конфигурация формируется из нескольких уровней: базовые настройки проекта, подключаемые пресеты (extends), плагины, локальные переопределения (rules), а также файловые исключения и специальные правила для отдельных директорий.

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

Дополнительный источник противоречий — взаимодействие правил форматирования и правил качества кода. ESLint изначально не является инструментом форматирования, но на практике часто используется совместно с форматирующими наборами правил, что создаёт пересечения.

Типы конфликтов между правилами

Конфликты значений одного и того же правила

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

  • off
  • warn
  • error

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

"no-console": "error"

а более специфичная конфигурация переопределяет:

"no-console": "off"

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

Конфликты между правилами разных плагинов

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

Особенно часто такие конфликты возникают между:

  • eslint:recommended
  • стилевыми плагинами (например, eslint-plugin-import, eslint-plugin-unicorn)
  • TypeScript-плагинами (@typescript-eslint/*)

Типичный пример — конфликт между правилами базового ESLint и TypeScript-расширений. Некоторые правила базового набора становятся неприменимыми в TypeScript-контексте и должны быть отключены в пользу аналогов из @typescript-eslint.

Конфликты форматирования

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

Например, правило:

"indent": ["error", 2]

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

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

Наследование конфигураций и порядок применения

В классической системе ESLint (legacy config) порядок разрешения конфигураций определяется следующим образом: сначала загружаются базовые конфигурации, затем расширения (extends), после чего применяются локальные настройки проекта.

Каждый последующий уровень переопределяет предыдущий. Это означает, что:

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

При этом важно, что объединение объектов конфигурации не является глубоким слиянием для всех типов данных. Для правил ESLint применяется перезапись значений, а не их объединение.

Flat config и изменения модели приоритетов

В новой системе flat config, используемой в современных версиях ESLint, конфигурация представляется как массив объектов. Порядок этих объектов напрямую определяет приоритет: более поздние элементы перекрывают предыдущие.

export default [
  baseConfig,
  pluginConfig,
  projectConfig
]

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

В flat config также становится более прозрачным взаимодействие между различными источниками правил, однако повышается риск неявного переопределения при добавлении новых слоёв конфигурации в середину массива.

Конфликты между TypeScript и базовыми правилами ESLint

При использовании TypeScript через @typescript-eslint/parser возникает специфический класс конфликтов. Базовые правила ESLint часто не учитывают особенности типизированного кода, из-за чего:

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

Типичный конфликт:

  • no-unused-vars (ESLint core)
  • @typescript-eslint/no-unused-vars

Одновременное включение обоих правил приводит к дублированию сообщений и несовместимым результатам анализа. Обычно базовое правило отключается в пользу TypeScript-версии.

Конфликты между стилевыми и семантическими правилами

Семантические правила отвечают за корректность кода, тогда как стилевые — за его внешний вид. Конфликт возникает, когда изменение внешнего вида влияет на интерпретацию семантики правил.

Например:

  • правила, запрещающие использование определённых конструкций (no-restricted-syntax)
  • правила форматирования блоков кода (brace-style, curly)

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

Inline-отключения и локальное переопределение конфликтов

ESLint позволяет разрешать конфликты локально через комментарии:

/* eslint-disable no-console */
console.log("debug");

или точечно:

// eslint-disable-next-line no-unused-vars
const temp = 1;

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

Переопределения через overrides

Механизм overrides позволяет задавать разные наборы правил для разных типов файлов. Это один из ключевых инструментов управления конфликтами в проектах с разнородным кодом.

"overrides": [
  {
    "files": ["*.ts"],
    "rules": {
      "@typescript-eslint/no-explicit-any": "error"
    }
  },
  {
    "files": ["*.test.js"],
    "rules": {
      "no-unused-expressions": "off"
    }
  }
]

Конфликты могут возникать, если файл попадает под несколько пересекающихся шаблонов. В таких случаях применяется порядок следования overrides: более поздние правила имеют приоритет над предыдущими.

Взаимодействие конфигураций из разных источников

Конфигурация ESLint может собираться из:

  • локального eslint.config.js или .eslintrc
  • зависимых пакетов (eslint-config-*)
  • плагинов (eslint-plugin-*)
  • CLI параметров

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

Стратегии уменьшения конфликтности конфигурации

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

  • базовый слой определяет минимальные стандарты качества
  • специализированные плагины отвечают за доменные правила
  • форматирование отделяется от ESLint при использовании внешнего форматтера
  • языковые расширения (TypeScript, JSX) изолируются в отдельных блоках

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

Неявные конфликты и их проявления

Не все конфликты проявляются напрямую. Некоторые из них выражаются косвенно:

  • различия в поведении автофикса (--fix)
  • несогласованность между предупреждениями и ошибками
  • нестабильные результаты при изменении порядка файлов
  • различие между локальным и CI-окружением

Подобные эффекты возникают, когда разные правила воздействуют на один и тот же участок AST (абстрактного синтаксического дерева), но применяют разные стратегии исправления.

Влияние порядка выполнения правил

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

Особенно это заметно в сочетании правил:

  • форматирования строк
  • переноса аргументов функций
  • перестановки пробелов и скобок

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

Взаимодействие конфликтов и масштаб проекта

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

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