12k
All articles

Повышение качества JavaScript-кода с помощью eslint-plugin-unicorn

eslint-plugin-unicorn для ESLint 10: установка с flat config, пресеты recommended или unopinionated и autofixable-правила для повышения качества JS и TS.

OpenReplay Team
OpenReplay Team
Повышение качества JavaScript-кода с помощью eslint-plugin-unicorn

eslint-plugin-unicorn — это крупный, принципиальный ESLint-плагин с более чем 300 правилами, который продвигает современные идиомы JavaScript и TypeScript, выявляет тонкие баги, влияющие на качество кода, и большинство его правил поддерживают автоматическое исправление. Плагин требует ESLint >=10.4, flat config и ESM, что означает: старая схема с .eslintrc и extends: 'plugin:unicorn/recommended' больше не работает. Если вы уже используете ESLint с базовой конфигурацией и хотите повысить качество кода, не создавая собственных правил, — этот плагин является самым быстрым способом добиться результата.

Загвоздка в том, что «принципиальность» — палка о двух концах. Одни правила предотвращают случайно квадратичные циклы и реальные баги корректности; другие навязывают стилистические предпочтения, которые будут конфликтовать с устоявшейся кодовой базой. Это руководство разграничивает их: что именно плагин контролирует, какие правила оправдывают себя, какие стоит понизить до предупреждений или отключить, а также готовый к копированию eslint.config.mjs, работающий с актуальным инструментарием.

Ключевые выводы

  • eslint-plugin-unicorn поставляется с более чем 300 правилами, большинство из которых поддерживают автоматическое исправление, и начиная с версии v69 (июнь 2026 г.) требует ESLint ≥10.4, flat config и ESM — .eslintrc больше не работает, поскольку ESLint 10 полностью удалил эту систему.
  • Простейший путь к внедрению: расширьте конфигурацию через unicorn/recommended, запустите eslint --fix, затем отключите небольшое количество правил, конфликтующих с вашей кодовой базой — или начните с пресета unopinionated, который заранее отключает спорные правила.
  • Плагин экспортирует три пресета: recommended, unopinionated (recommended без спорных правил) и all.
  • prefer-set-has превращает случайно квадратичные поиски через .includes() внутри цикла в O(n)-проверки с Set.has(), а no-array-for-each переписывает .forEach() в for…of для более точного сужения типов в TypeScript.
  • Используйте плагин совместно с @typescript-eslint/recommended-type-checked, чтобы объединить контроль идиом с типозависимым линтингом.

Что такое eslint-plugin-unicorn и как он устроен

eslint-plugin-unicorn — это набор правил ESLint, направляющих код к современным, менее подверженным ошибкам паттернам JavaScript и TypeScript: предпочтение Set перед сканированием массивов, for…of перед .forEach(), импорт с префиксом node: вместо голых имён модулей и так далее. Плагин требует ESLint >=10.4, flat config и ESM. Репозиторий декларирует более 300 правил, последняя версия — 69.0.0, релизы выходят примерно раз в месяц.

Два принципа дизайна определяют процесс внедрения. Во-первых, большинство правил поддерживают автоматическое исправление, поэтому значительная часть нарушений устраняется через eslint --fix, а не вручную. Во-вторых, плагин поставляется с готовыми пресетами, избавляя от необходимости включать правила по одному. Плагин экспортирует конфигурацию recommended, применяющую лучшие практики, и отдельно конфигурацию all, включающую все правила, кроме устаревших. В таблице правил используются эмодзи для обозначения статуса: ✅ — правило включено в конфигурацию recommended, ☑️ — в конфигурацию unopinionated, 🔧 — правило автоматически исправляется с помощью CLI-опции --fix.

Область применения охватывает преимущественно JavaScript и TypeScript, с одной полезной оговоркой: хотя большинство правил нацелены на JavaScript и TypeScript, некоторые также линтят файлы других типов при использовании соответствующего языкового плагина ESLint, такого как @eslint/css, @eslint/json, @eslint/markdown или @html-eslint/eslint-plugin. Для большинства команд именно правила для JS/TS являются основной причиной установки плагина.

Установка eslint-plugin-unicorn с flat config и ESM

Установите плагин и совместимую версию ESLint, затем расширьте пресет recommended в flat-конфигурации eslint.config.mjs. Текущее требование — ESLint 10.x: ESLint 10 полностью удалил устаревшую систему eslintrc, оставив flat config единственным поддерживаемым форматом, поэтому любое руководство, демонстрирующее .eslintrc.json с extends: 'plugin:unicorn/recommended', устарело. ESLint требует актуальной версии Node.js (Node 20.19+, 22.13+ или 24+ согласно странице ESLint на npm).

npm install --save-dev eslint eslint-plugin-unicorn

Минимальная рабочая конфигурация, ограниченная файлами JavaScript и TypeScript:

// eslint.config.mjs
import unicorn from 'eslint-plugin-unicorn';
import { defineConfig } from 'eslint/config';

export default defineConfig([
  {
    files: ['**/*.{js,mjs,cjs,ts,tsx}'],
    extends: [unicorn.configs.recommended],
    rules: {
      // переопределения здесь
    },
  },
]);

Это соответствует официальному примеру использования: импортируем unicorn из 'eslint-plugin-unicorn', импортируем {defineConfig} из 'eslint/config', затем внутри defineConfig задаём files и extends: [unicorn.configs.recommended]. Ограничение через files важно, когда вы линтите в одной конфигурации файлы разных языков — держите правила Unicorn для JS в отдельном объекте конфигурации.

Для выбора отправной точки доступны три пресета:

  • recommended — тщательно отобранный вариант по умолчанию.
  • unopinionatedrecommended без спорных правил.
  • all — все неустаревшие правила.

Если ваша команда считает спорные правила слишком шумными, замените unicorn.configs.recommended на unicorn.configs.unopinionated и пропустите большую часть ручной настройки, описанной ниже.

Замечание по миграции: если вы всё ещё зависите от плагина, поставляющего только конфигурацию в стиле eslintrc, оберните его с помощью FlatCompat из @eslint/eslintrc. Используйте последнюю версию @eslint/eslintrc с ESLint 10.x.

Наиболее ценные правила eslint-plugin-unicorn: до и после

Наиболее ценные правила Unicorn стоит внедрять в первую очередь, поскольку каждое из них предотвращает проблему производительности, корректности или читаемости, а не просто навязывает стилистику. Все примеры ниже — результат работы eslint --fix, а не идеальный код, написанный вручную.

prefer-set-has — устраняет случайно квадратичные поиски

prefer-set-has помечает массивы, используемые исключительно для проверки принадлежности через .includes(), и направляет к использованию Set. Правило предпочитает Set#has() вместо Array#includes() при проверке наличия или отсутствия элемента. Это особенно важно внутри циклов: Array.prototype.includes имеет сложность O(n) на каждый вызов, поэтому вызов его на каждой итерации по другой коллекции даёт O(n²), тогда как Set.prototype.has работает за примерно константное время.

// ❌ O(n²): includes() заново сканирует весь массив на каждой итерации
const allowed = ['read', 'write', 'admin', /* …сотни значений… */];
const granted = requested.filter((scope) => allowed.includes(scope));

// ✅ O(n): Set.has() работает за примерно константное время на каждый поиск
const allowed = new Set(['read', 'write', 'admin', /* …сотни значений… */]);
const granted = requested.filter((scope) => allowed.has(scope));

Опасность этой проблемы в её незаметности: на малых данных всё работает нормально, а деградация производительности проявляется резко по мере роста объёма. Именно такие регрессии медленного взаимодействия вы потом диагностируете в продакшене, а не на этапе ревью.

no-array-for-each — лучшее сужение типов TypeScript и управление потоком выполнения

no-array-for-each переписывает колбэки .forEach() в циклы for…of. Правило предпочитает for…of методу forEach. Выгода не только стилистическая: for…of обеспечивает более точное сужение типов TypeScript на протяжении всего тела цикла, позволяет использовать await напрямую без акробатики с колбэками, а также поддерживает break/continue — ничего из этого не работает корректно внутри колбэка .forEach().

// ❌
users.forEach((user) => {
  sendEmail(user);
});

// ✅ после автоисправления
for (const user of users) {
  sendEmail(user);
}

prefer-node-protocol — однозначная идентификация встроенных модулей Node

prefer-node-protocol переписывает голые импорты встроенных модулей в явную форму с префиксом node:. Правило предпочитает использование протокола node: при импорте встроенных модулей Node.js. Явный префикс делает импорт однозначно идентифицируемым как встроенный модуль Node и исключает его случайное замещение одноимённым npm-пакетом.

// ❌
import fs from 'fs';
import { join } from 'path';

// ✅ после автоисправления
import fs from 'node:fs';
import { join } from 'node:path';

Ещё несколько полезных автоисправлений

Каждое из следующих правил сопоставляет устаревший паттерн с его современным аналогом и исправляет автоматически:

// throw-new-error — требует new при выбрасывании ошибки
throw Error('boom');        // ❌
throw new Error('boom');    // ✅

// prefer-string-replace-all — глобальная замена без регулярного выражения с /g
str.replace(/-/g, ' ');     // ❌
str.replaceAll('-', ' ');   // ✅

// prefer-array-flat-map — объединяет map().flat()
rows.map((r) => r.cells).flat();   // ❌
rows.flatMap((r) => r.cells);      // ✅

// new-for-builtins — требует new для Map/Set/Array/Date и т.д.
const cache = Map();        // ❌
const cache = new Map();    // ✅

Правило throw-new-error требует new при создании ошибки (охватывая как const e = Error(), так и throw Error()), и оно включено в конфигурацию recommended и поддерживает автоисправление. new-for-builtins обязывает использовать new для всех встроенных объектов, кроме String, Number, Boolean, Symbol и BigInt. Array.prototype.flatMap одновременно нагляднее и избавляет от промежуточного массива, который создаёт .map().flat().

Два правила, ориентированных на читаемость, дополняют набор наиболее ценных. better-regex улучшает регулярные выражения, делая их короче и единообразнее (например, [0-9] заменяется на \d). prefer-ternary сворачивает простые блоки if/else: правило предпочитает тернарные выражения простым операторам if, возвращающим или присваивающим значения. Оператор if-else обычно занимает больше строк, чем однострочный тернарный, и может вынуждать объявлять переменные через let только ради их переприсваивания в блоках — оставляя их излишне изменяемыми и не позволяя prefer-const пометить переменную.

// ❌
function status(ok) {
  if (ok) {
    return 'pass';
  } else {
    return 'fail';
  }
}

// ✅ после автоисправления
function status(ok) {
  return ok ? 'pass' : 'fail';
}

Спорные правила и как принять решение

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

ПравилоРешениеПричина
prevent-abbreviationsWarn или ограничить областьПереименовывает eerror, reqrequest, propsproperties; конфликтует с именами, принятыми в вашем фреймворке и команде.
no-nullОтключить при использовании nullUnicorn предпочитает undefined, но null вполне легитимен, когда его возвращают ORM или API — единственно верного ответа здесь нет.
no-array-reduceОставить или понизить до warnЗапрещает Array#reduce() и Array#reduceRight(); разумно с точки зрения читаемости, но reduce вполне приемлем для многих команд.
filename-caseОграничить через ignoreКонфликтует с именами файлов, диктуемыми фреймворком; оставьте правило, но добавьте исключения.

Случай с no-null наиболее очевиден: Unicorn предпочитает undefined, тогда как многие драйверы баз данных и HTTP API возвращают именно null, поэтому принудительное использование одного варианта создаёт лишние преобразования без какой-либо пользы для корректности. Отключите правило, если null является частью вашего контракта данных.

Отключать эти правила вручную не обязательно. Пресет unopinionated изначально оставляет спорные правила — включая no-null и prevent-abbreviations — выключенными, что делает его встроенным компромиссом между recommended и тщательно настроенной конфигурацией.

Для правил, которые вы оставляете, но хотите скорректировать, лучше ограничить область применения, а не отключать полностью. Типичный паттерн для filename-case — исключение файлов фреймворка:

{
  files: ['**/*.{js,ts,tsx}'],
  extends: [unicorn.configs.recommended],
  rules: {
    'unicorn/no-null': 'off',
    'unicorn/prevent-abbreviations': 'warn',
    'unicorn/filename-case': [
      'error',
      {
        cases: { pascalCase: true, camelCase: true },
        ignore: ['next-env\\.d\\.ts', 'vite\\.config\\.ts'],
      },
    ],
  },
}

Постепенное внедрение в существующую кодовую базу

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

  1. Начните с пресета. Расширьте конфигурацию через unicorn/recommended или через unicorn/unopinionated, если спорные правила слишком шумны для вашей команды.
  2. Сначала запустите автоисправление. Выполните npx eslint . --fix. Поскольку большинство правил поддерживают автоисправление, значительная часть нарушений исчезнет сразу, и вы проверите один механический diff вместо сотен встроенных ошибок.
  3. Понизьте шумные правила до warn. Всё, что продолжает срабатывать в большом объёме и не является проблемой корректности — prevent-abbreviations обычно первый кандидат — переведите в 'warn', чтобы не блокировать CI в процессе миграции.
  4. Ограничивайте область через ignore, а не отключайте полностью. Используйте опцию ignore (как в случае с filename-case) и глобы files, чтобы задавать исключения, а не убивать полезное правило везде.
  5. Добавьте типозависимый линтинг. Подключите @typescript-eslint/recommended-type-checked рядом с Unicorn. Unicorn контролирует идиомы; типозависимая конфигурация typescript-eslint выявляет баги, которые видна только системе типов. Они дополняют друг друга, и включение обоих — сильный выбор по умолчанию для TypeScript-проекта.

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

Самый быстрый путь к улучшению JavaScript с помощью eslint-plugin-unicorn прозаичен: расширьте конфигурацию через unicorn/recommended, запустите eslint --fix и уделите внимание лишь небольшому числу спорных правил, реально конфликтующих с принятым в команде стилем написания кода. Начните с пресета сегодня, дайте автоисправлению выполнить основную работу, а затем настраивайте по мере необходимости — официальная таблица правил является справочником по актуальному статусу и параметрам каждого правила.

Часто задаваемые вопросы

Совместим ли eslint-plugin-unicorn с typescript-eslint, или они конфликтуют?

Они дополняют друг друга и предназначены для совместного использования. Unicorn контролирует современные идиомы JavaScript и TypeScript, тогда как typescript-eslint добавляет типозависимые правила, выявляющие баги, которые видна только системе типов. Добавьте оба плагина как отдельные объекты конфигурации в вашем flat-файле eslint.config.mjs и используйте unicorn/recommended совместно с конфигурацией recommended-type-checked из typescript-eslint. Включение обоих — сильный выбор по умолчанию для TypeScript-проекта, без пересечений правил, требующих разрешения конфликтов.

В чём разница между пресетами recommended и unopinionated?

Пресет recommended — это тщательно отобранный вариант по умолчанию, включающий как правила корректности, так и спорные стилистические правила. Пресет unopinionated — это recommended без спорных правил: он заранее отключает такие противоречивые правила, как no-null и prevent-abbreviations, конфликтующие с устоявшимися кодовыми базами. Выбирайте unopinionated, когда стилистические правила кажутся вашей команде слишком шумными; это встроенный компромисс между recommended и тщательно настроенной конфигурацией. Третий пресет, all, включает все неустаревшие правила.

Почему моя старая конфигурация .eslintrc с extends plugin:unicorn/recommended перестала работать?

ESLint 10 полностью удалил устаревшую систему eslintrc, оставив flat config единственным поддерживаемым форматом, а eslint-plugin-unicorn требует ESLint 10.4 или выше, flat config и ESM. Старый синтаксис .eslintrc с extends 'plugin:unicorn/recommended' больше не загружается. Перейдите на eslint.config.mjs, который импортирует unicorn из 'eslint-plugin-unicorn' и задаёт extends как unicorn.configs.recommended внутри объекта конфигурации, ограниченного через files.

Не сломает ли добавление eslint-plugin-unicorn сборку сотнями ошибок?

Большинство нарушений устраняются автоматически, поскольку большинство правил unicorn поддерживают автоисправление. Сначала запустите eslint с флагом fix — это устранит значительную часть проблем в виде единого механического diff, а не сотен встроенных ошибок. Для правил, которые продолжают срабатывать в большом объёме, но не являются проблемами корректности — например, prevent-abbreviations — установите значение warn, чтобы не блокировать CI в процессе миграции. Такое поэтапное внедрение сохраняет работоспособность сборки в ходе освоения плагина.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.