Обложка статьи «Программируем лучше с ESLint, Prettier и TypeScript»

Программируем лучше с ESLint, Prettier и TypeScript

Перевод статьи «Setting up efficient workflows with ESLint, Prettier and TypeScript»

Олег Бидзан

Олег Бидзан, заместитель технического директора Simtech development

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

Как все начиналось

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

Во-первых, бесконечные споры о том, как писать и форматировать код. Это действительно надоевшая всем тема, по крайней мере, для меня. Личным предпочтениям тут не место. Есть более важные вещи, на которые стоит обратить внимание.

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

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

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

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

Инфо Скорее всего поддержки TSLint в Angular не будет в ближайшее время т.к. TypeScript решили внедрить ESLint вместо TSLint. Команда Angular уже работает нам переездом с TSLint на ESLint. См. issue.

Что такое ESLint, и как это может помочь нам?

ESLint — это утилита, которая может анализировать написанный код. Фактически, это статический анализатор кода, и он может находить синтаксические ошибки, баги или неточности форматирования. В других языках, например, Go, это является неотъемлемой частью языка программирования.

Что я должен сделать, чтобы начать использовать ESLint?

Я рассчитываю, что у вас уже установлены node и npm, и вы знакомы с ними.

Создание рабочей папки

Перейдите в папку, где находится ваш JavaScript или TypeScript проект, или, как я, создайте тестовую папку lint-examples, где мы можем работать по ходу статьи. В операционных системах на основе Linux наберите mkdir lint-examples в командной строке и затем перейдите в созданную папку с помощью команды cd lint-examples.

Установка ESLint

Теперь давайте создадим package.json, чтобы мы могли установить ESLint. Выполнение команды npm init создаст package.json, который необходим для установки eslint в вашу папку.

Добавьте eslint в ваши npm скрипты

{
  "name": "eslint-examples",
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "eslint": "eslint"
  },
  "devDependencies": {
    "eslint": "7.1.0"
  }
}

Подсказка "eslint": "eslint" в скриптах — это сокращение для node_modules/.bin/eslint.

Создайте test.js

Давайте создадим простой JavaScript-файл в папке lint-example, куда мы установили ESLint. Не переживайте о плохом форматировании примера. Нам нужно это для старта.

var foo = 1
console.log(foo)
var bar
bar = 1
function test(


    ) {
  console.log(baz)
}
var baz = 123

Первая попытка в командной строке

Если вы сейчас запустите test.js, используя ESLint, ничего не случится. По умолчанию ESLint проверяет только синтаксические ошибки. Он будет использовать ES5 по умолчанию. Описание опций парсинга можно найти по ссылке.

Если вы использовали const или let в примере выше, ESLint генерирует ошибку, потому что, как уже говорилось, ES5 выбран по умолчанию.

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

npm run eslint -- ./test.js

Становится интереснее!

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

Давайте создадим наш первый .eslintrc.

Существует несколько способов передать конфигурации в ESLint. Я предпочитаю .eslintrc. По ссылке вы найдете другие способы.

{
  "env": {
    "es6": true
  }
}

Инфо env обязателен для глобальных переменных. Когда мы настраиваем параметры env, устанавливая es6 в true, ESLint включит глобальность для всех новых типов, таких как Set. Это так же включит ES6 синтаксис, например, let и const. Смотрите установка опций парсера.

Сейчас мы должны добавить несколько правил в наш .eslintrc

Можем ли мы просто определить правила? Да, потому что установили ESLint, который содержит множество правил из коробки. Для особых правил, таких как TypeScript или новых функций, которые не поддерживаются ESLint, мы должны установить модули eslint-config-xxx или eslint-plugin-xxx. Но мы можем вернуться к этому позже. Вы можете посмотреть правила по ссылке.

{
  "env": {
    "es6": true
  },
  "rules": {
    "no-var": "error",
    "semi": "error",
    "indent": "error",
    "no-multi-spaces": "error",
    "space-in-parens": "error",
    "no-multiple-empty-lines": "error",
    "prefer-const": "error",
    "no-use-before-define": "error"
  }
}

Если вы запустите npm run eslint, то должны получить результат в точности как ниже:

error 'foo' is never reassigned. Use 'const' instead prefer-const
error Missing semicolon semi
error Expected indentation of 0 spaces but found 4 indent
error 'bar' is never reassigned. Use 'const' instead prefer-const
error Multiple spaces found before ')' no-multi-spaces
error There should be no space after this paren space-in-parens
error There should be no space before this paren space-in-parens
error More than 2 blank lines not allowed no-multiple-empty-lines
error 'baz' was used before it was defined no-use-before-define
error 'baz' is never reassigned. Use 'const' instead prefer-const

26 problems (26 errors, 0 warnings)
20 errors and 0 warnings potentially fixable with the `--fix` option.

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

Возможно, вы увидели в выводе результата ESLint, что 20 проблем из 26 могут быть решены автоматически. Мы вернемся к этому в следующем разделе.

ESLint и форматирование кода?

ESLint может автоматически форматировать ваш код до определенной стадии. Как вы возможно видели в выводе лога, дополнительный флаг --fix может быть использован для форматирования написанного кода, основываясь на правилах eslint. Например, пропущенная точка с запятой может быть добавлена автоматически, а несколько пустых строк подряд будут удалены. Это так же работает для других правил.

Давайте исправим код, выполнив npm run eslint -- ./ --fix

var foo = 1;
console.log(foo);
var bar;
var = 1;
function test(

) {
    console.log(baz);
}
var baz = 123;
1:1 error Unexpected var, use let or const instead no-var
3:1 error Unexpected var, use let or const instead no-var
11:1 error Unexpected var, use let or const instead no-var

3 problems (3 errors, 0 warnings)

Вы видите, что ESLint исправляет не все правила. Оставшиеся три ошибки необходимо поправить вручную, однако остальные ошибки из отчета ESLint (такие как «Пропущенная точка с запятой», «Неправильные отступы», «Множественные пробелы») были исправлены автоматически.

Обратите внимание Причина, по которой var не может быть исправлена, — в каких-то действиях с контекстом браузера. Вы можете почитать подробнее по ссылке.

В документации ESLint вы можете найти, какие правила можно активировать с помощью иконки «check mark». Код, который может быть отформатирован автоматически, подсвечивается с помощью иконки гаечного ключа.

  • Правила можно найти по ссылке.
  • Существует примерно 300 правил, и их число постоянно растет.
  • Примерно 100 из этих правил делают автоформатирование.

Всё это становится мощнее, если код форматируется вашей IDE при изменении (сохранении) файла, или если любой инструмент автоматизации, например travis-ci, может взять на себя эту задачу, когда что-то отправляется в Git.

Если ESLint может форматировать ваш код, что тогда делает Prettier?

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

Что надо сделать, чтобы начать использовать Prettier?

Не так много. Просто добавьте в ваши npm-скрипты "prettier": "prettier" и запустите npm install prettier.

Как мы помним, этот код был отформатирован ESLint, и он не очень хорошо оформлен.

// test.js
const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test(

) {
    console.log(baz);
}
const baz = 123;

После выполнения npm run prettier -- --write ./test.js код выглядит опрятнее.

const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test() {
  console.log(baz);
}
const baz = 123;

Так намного лучше. Чем больше кода, тем лучше результат.

Могу ли я настраивать Prettier?

Да. Настроек в парсере Prettier не так много, как в ESLint. С Prettier вы полностью во власти парсера Prettier. Основываясь на небольшом количестве опций, он сам решает, как будет выглядеть ваш код.

Это мои настройки, которые описаны в файле .prettierrc. Полный список опций вы можете найти по ссылке. Давайте создадим .prettierrc-файл с такими опциями.

{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "arrowParens": "avoid"
}

Запускать ли ESLint и Prettier одновременно?

Не рекомендуется запускать ESLint и Prettier по отдельности, чтобы применить правила написания кода и форматирования. Более того, ESLint и Prettier будут мешать друг другу т.к. у них есть пересекающиеся правила, и это может привести к непредсказуемым последствиям. В следующем разделе мы рассмотрим эту проблему и решим ее. Если кратко, то вы просто запускаете eslint в командной строке, а prettier будет уже включаться туда.

Как всё это начиналось!

Как я писал в начале статьи, я никогда не использовал ESLint и Prettier прежде. Следовательно, я не знал, как эти утилиты работают. Как любой разработчик, я копировал наилучший кусок кода из глубин интернета в мой .eslintrc-файл без понимания, что это даст. Главной целью было, чтобы это работало.

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

Если коротко, то там настройки и плагины для ESLint, предоставленные сообществом открытого исходного кода. Мы не должны делать их сами. Главное понимать, как это работает под капотом.

.eslintrc

{
  "plugins": [
    "@typescript-eslint",
    "prettier",
    "unicorn" ,
    "import"
  ],
  "extends": [
    "airbnb-typescript/base",
    "plugin:@typescript-eslint/recommended",
    "plugin:unicorn/recommended",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/@typescript-eslint"
  ],
  "parserOptions": {
    "ecmaVersion": 2020,
    "sourceType": "module"
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  },
  "rules": {
    "no-debugger": "off",
    "no-console": 0
  }
}

Заметка Возможно, вы заметили prettier в плагинах, и вы все еще помните, что я писал выше: «Должны ли мы одновременно запускать ESLint и Prettier для форматирования кода?» Ответ нет, потому что eslint-plulgin-prettier и eslint-config-prettier сделают всю работу за вас.

Что означают эти настройки и опции?

После того, как я заставил систему работать, то задался вопросом, а что это всё значит. Это буквально выбило меня из колеи. Если вы запустите ESLint в вашей консоли с этими опциями, то получите сообщение об ошибке, что конфига (расширения) и плагины не установлены. Но откуда мы можем знать, что устанавливать? Каждый знаком с процессом, вы находите кусок кода на StackOverflow или в каком-то репозитории и потом не знаете, как правильно запустить его.

Вы должны помнить, что все модули внутри extends и plugins могут быть установлены. Но сначала вы должны узнать, как интерпретировать соглашение об именах в свойствах, чтобы иметь возможность устанавливать их через npm.

Что такое опции «плагина»?

Плагины содержат правила написанные с использованием парсера. Это могут быть правила на рассмотрении из TC39, которые еще не поддерживаются ESLint, или специальные рекомендации по написанию кода, которые не представлены в ESLint, например, unicorn/better-regex, import/no-self-import.

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

// :penguin: emoji

Давайте узнаем, как интерпретировать соглашение об именах плагинов

Если имя плагина начинается не с eslint-plugin- или @ или ./, вы просто должны добавить префикс eslint-plugin-.

plugins: [
  "prettier", // npm module "eslint-plugin-prettier"
  "unicorn"   // npm module "eslint-plugin-unicorn"
]

Еще пример, работает так же:

plugins: [
  "eslint-plugin-prettier", // the same as "prettier"
  "eslint-plugin-unicorn"   // the same as "unicorn"
]

Становится немного сложнее, когда вы сталкиваетесь с именами плагинов, которые начинаются с @ (пространство имен). Как видно из приведенного ниже примера, использование / ограничено одним уровнем. Вы должны учитывать, что @mylint и @mylint/foo находятся в одном и том же пространстве имен, но это два разных плагина (npm-модуля).

plugins: [
  "@typescript-eslint", // npm module "@typescript-eslint/eslint-plugin"
  "@mylint",        	// npm module "@mylint/eslint-plugin"
  "@mylint/foo",        // npm module "@mylint/eslint-plugin-foo"
  "./path/to/plugin.js  // Error: cannot includes file paths
]

Код примера ниже такой же, как и сверху.

plugins: [
  "@typescript-eslint/eslint-plugin", // the same as "@typescript-eslint"
  "@mylint/eslint-plugin",            // the same as "@mylint"
  "@mylint/eslint-plugin-foo"         // the same as "@mylint/foo"
]

Подсказка Используйте сокращенную форму (из первого примера) вместо длинной формы (из второго примера). Главное, чтобы вы понимали, как ESLint это конвертирует внутри.

Теперь мы знаем, как работает соглашение об именах для плагинов. Установите следующие плагины ESLint через npm.

npm i eslint-plugin-prettier eslint-plugin-unicorn

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

Для тестирования ваш .eslintrc должен выглядеть следующим образом:

{
  "plugins": [
    "prettier",
    "unicorn"
  ],
  "env": {
    "es6": true
  }
}

Prettier: ESLint плагин для форматирования кода.

Unicorn: дополнительные правила, которые не поддерживаются ESLint.

Теперь, если вы запустите npm run eslint в командной строке, вы не получите сообщение об ошибке, но также не получите и вывод ESLint. Это потому, что мы должны зарегистрировать модуль плагина в свойстве extends нашего .eslintrc-файла или применить его, активировав в разделе rules.

Давайте выясним, как интерпретировать соглашение об именах в расширениях

Прежде всего, если вы считаете, что соглашение об именах в разделе extends такое же, как и у плагинов, я должен вас разочаровать. Есть отличия. Должен честно признать, что мне потребовалось много времени, чтобы заметить разницу. Это было отчасти потому, что ESLint — сложная и обширная тема, по крайней мере, для меня.

Пока вы используете только простое имя (например, foo) без префикса пространства имен (@) или с (./to/my/config.js), принцип соглашений об именах в extends такой же, как и с параметром plugins. Таким образом, foo становится eslint-config-foo

extends: [
  "airbnb-base", // npm module "eslint-config-airbnb-base"
  "prettier"     // npm module "eslint-config-prettier"
]

идентичен

extends: [
  "eslint-config-airbnb-base", // shortform is "airbnb-base"
  "eslint-config-prettier"     // shortform is "prettier"
]

Итак, мы подошли к тому, что существуют различия в соглашении об именах между plugins и extends. Это тот случай, когда вы используете пространства имен (@) в разделе extends. Следующая конфигурация ESLint @mylint все та же, она указывает на модуль npm @mylint/eslint-config, но @mylint/foo может привести к ошибке при использовании в extends из-за отсутствия префикса eslint-config- в @mylint/eslint-congif-foo.

extends: [
  "@bar",                   // npm module "@bar/eslint-config"
  "@bar/eslint-config-foo", // npm module "@bar/eslint-config-foo"
  "@bar/my-config"          // npm module "@bar/eslint-config/my-config"
]

Как я писал в введении к предыдущему разделу, следующий @mylint/my-config немного особенный, поскольку он содержит модуль npm, но в то же время он указывает изнутри ESLint на набор правил (my-config). Мы скоро это выясним. Вот официальная документация по соглашению об именах extends.

Давайте установим остальные модули npm для нашего примера.

npm i eslint-config-airbnb-base eslint-config-prettier

Примечание: возможно, вы заметили, что сначала мы установили eslint-plugin-prettier, а теперь установили eslint-config-prettier. Это разные модули, но работают только вместе. Мы обсудим это позже.

Что конкретно делает extends в .eslintrc?

Конфиг предоставляет предварительно настроенные правила. Эти правила могут состоять из правил ESLint, правил сторонних плагинов или других конфигураций, таких как синтаксический анализатор (babel, esprima, и т.д.), параметров (sourceType, и т.д.), окружений (ES6, и т.д.) и других.

Звучит неплохо? Да, потому что мы не должны делать всё сами. Продвинутые разработчики и команды уже потратили на это много времени. Всё, что нужно сделать, это активировать правила, указав конфиг или набор правил плагинов.

Где я могу найти эти наборы правил?

Есть разные способы их найти.

Во-первых, вы должны посмотреть на README.md соответствующего репозитория и прочитать именно то, что написано. Обычно, эти наборы правил называются «рекомендованными» и должны быть активированы для plugins. Для extends это не всегда необходимо.

Во-вторых, знание того, какой набор правил использовать даже без чтения README.md — вот, что я считаю гораздо более эффективным. Особенно, если файл README.md является неполным или неправильным.

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

eslint-config-airbnb-base

eslint-config-airbnb-base (repository)
| -- index.js
| -- legacy.js
| -- whitespace.js

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

Использование:

"extends": [
  "airbnb-base",            // index.js
  "airbnb-base/whitespace"  // whitespace.js
]

Держите в уме Порядок играет роль, потому что набор правил будет расширять или перезаписывать предыдущие. Так что не переусердствуйте с конфигами и плагинами. Смотрите хорошее объяснение на StackOverflow.

eslint-plugin-prettier

Теперь мы подошли к захватывающей части статьи. Как мы можем использовать Prettier напрямую в ESLint, не запуская его в качестве отдельной службы в нашей командной строке или IDE?

Мы начнём с активации eslint-plugin-prettier в разделе extends, а затем связанного с ним config eslint-config-prettier, который отвечает за деактивацию некоторых наборов правил ESLint, которые могут конфликтовать с Prettier.

eslint-plugin-mylint (repository)
| -- eslint-plugin-prettier.js (because this is specified as entrypoint in package.json)

eslint-plugin-prettier.js

module.exports = {
  configs: {
    recommended: {
      extends: ['prettier'],
      plugins: ['prettier'],
      rules: {
        'prettier/prettier': 'error'
      }
    }
  }
  ...
  ...
  ...

Использование:

"extends": [
  "plugin:prettier/recommended"
]

Подсказка Плагины должны быть зарегистрированы в plugins и активированы в extends с использованием :plugin префикс.

eslint-config-prettier

eslint-config-prettier (repository)
| -- index.js
| -- @typescript-eslint.js
| -- babel.js
| -- flowtype.js
| -- react.js
| -- standard.js
| -- unicorn.js
| -- vue.js

Использование:

"extends": [
  "prettier",                   // index.js
  "prettier/unicorn",           // unicorn.js
  "prettier/@typescript-eslint" // @typescript-eslint.js
]

Примечание Отдельно "prettier" в extends требуется для отключения некоторых правил ядра ESLint. В остальных случаях "prettier." необходимы для отключения правил в unicorn и @typescript-eslint.

Мой личный конфиг ESLint выглядит как приведенный выше пример. Я использую TypeScript и плагин Unicorn. Не хочу, чтобы они конфликтовали с ESLint. Поэтому некоторые правила TypeScript и Unicorn отключены через Prettier.

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

Активировать всё самостоятельно вместо использования набора правил не имеет смысла. Но часто случается так, что вы не согласны с тем или иным правилом или его настройками. В этом случае вы можете отключить одно правило.

.eslintrc

"rules": {
  "unicorn/prevent-abbreviations": "off"
}

Итак, вернёемся к нашему тестовому примеру. Теперь наш .eslintrc-файл должен выглядеть следующим образом:

{
  "plugins": [
    "prettier",
    "unicorn"
  ],
  "extends": [
    "airbnb-base",
    "plugin:unicorn/recommended",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/unicorn"
  ],
  "env": {
    "es6": true,
    "browser": true
  },
  rules: {
    "unicorn/prevent-abbreviations": "off"
  }
}

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

Если вы теперь запустите наш пример через ESLint, используя npm run eslint -- --fix, Prettier будет выполняться через ESLint, так что вам потребуется только одна команда для запуска обоих инструментов.

Как мы можем интегрировать это в IDE?

Все современные IDE (IntelliJ и VS Code) поддерживают ESLint. Важно отметить, что в некоторых случаях вы должны передавать параметр --fix в качестве аргумента в настройках IDE, чтобы всё работало автоматически.

Почему существуют разные типы парсеров “ESLint”?

ESLint поддерживает только новый синтаксис JavaScript, который находится на финальной стадии в TC39. Возможно, не многие знают это, но компилятор Babel поддерживает функции, которые ещё не находятся на финальной стадии. Хорошо известная функция – decorator. От функции, на которой был основан Angular, отказались. Новая же функция имеет другой синтаксис и семантику. Предыдущая функция находится на второй стадии, а новая — на раннем этапе.

В этом случае ESLint вам не поможет. Либо вам нужно найти подходящий плагин для него, либо вы пишете свой собственный плагин eslint, который использует, например, анализатор babel вместо анализатора espree, который является анализатором по умолчанию в ESLint.

Смотрите настройки eslint-parser.

Как насчёт Angular и ESLint?

Команда Angular придерживается мнения, что нам следует подождать с применением ESLint. Это допустимо, потому что они хотят сделать переход как можно более плавным, но если вы всё же хотите попробовать, вот несколько советов.

Производительность и ESLint?

Может случиться, что ESLint не так эффективен, как можно было бы ожидать в некоторых частях кода, но это нормально и может произойти также в TSLint. Для решения проблемы вы можете использовать внутреннее кэширование ESLint или другого демона ESLint. Здесь вы можете найти очень полезные советы, см. пост на StackOverflow.

Prettier существует только для Javascript?

Prettier официально поддерживает несколько других языков. К ним относятся PHP, Ruby, Swift и так далее. Кроме того, существуют плагины сообщества для таких языков как Java, Kotlin, Svelte и многих других.

Как насчет ESLint v7?

Все примеры в нашей статье изначально были основаны на версии ESLint v6, но недавно была выпущена версия ESLint v7. Не волнуйтесь, даже в версии 7 ESLint работает без каких-либо изменений. Если вас интересует, что было изменено или добавлено, вы можете ознакомиться с примечаниями к выпуску ESLint v7.

Призы для программистов — нужно пройти опрос. Больше ответов — больше шансы.

Вакансии в тему: