Если вам не приходилось работать в команде, то, возможно, вы еще не используете эти вещи, а кто-то даже не знает про них. Работая один, вы сами себе хозяин.


Как только начали работать в команде? — ?ситуация резко меняется. Если нет договоренностей, то каждый начинает писать код в таком стиле, в каком умеет. И даже если вы все же собрались и обсудили на словах codestyle на проекте и даже записали где-то, это, скорее всего, не поможет решить проблему, и вот почему.



Человек ?не робот, и ему свойственно ошибаться, особенно если нет никаких ограничений?. Пиши код как душе угодно, другие как-нибудь да разберутся. Даже если вы стараетесь соблюдать все правила и тщательно проверяете не только свой код, но и код коллег, это не гарантирует, что получится выявить 100% всех ошибок.


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


Подготовка


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


Большинство инструментов, которые будут рассмотрены далее, можно применить не только к Angular, но в рамках этой статьи будем использовать Angular и начнем с того, что создадим новый проект:


npm install -g @angular/cli # если у вас не установлен @angular/cli
ng new angular-linters-sample

TSLint (deprecated)


На текущий момент в проекте, который сгенерирован через Angular CLI, по умолчанию используется TSLint. Уже сейчас вам будет доступна команда npm run lint, которая запустит встроенную в CLI команду билдера ng lint, тем самым запустив проверку кода на соответствие codestyle.


Теперь давайте проверим: намеренно допустим ошибку и запустим линтер (попробуйте догадаться, какая ошибка была внесена).



TSLint: Запуск линтера в Angular-проекте


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


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


Однако на странице проекта в Гитхабе можно заметить, что TSLint помечен как deprecated, так что вместо него рекомендуют использовать ESLint (о нем расскажем далее).


Codelyzer


Помимо TSLint, который, исходя из названия, работает с TypeScript, в Angular-проекте хочется иметь правила, которые относятся конкретно к Angular.


И такое решение есть? — ?codelyzer, который также по умолчанию устанавливается и настраивается при генерации проекта через Angular CLI. Правила codelyzer тоже применяются при запуске команды npm run lint. Это работает за счет файла конфигурации tslint.json, в котором интеграция TSLint и codelyzer осуществляется через свойство rulesDirectory.


ESLint


Так как TSLint был объявлен как deprecated, Angular-комьюнити начало движение в сторону ESLint. Если хочется узнать подробности, то на Хабре уже была статья, где подробно поясняется, почему стоит мигрировать.


На текущий момент (версия Angular 9.0.5) официальной поддержки ESLint в Angular еще нет, по этой причине CLI генерирует проект с TSLint и codelyzer. Стоит отметить, что codelyzer не совместим с ESLint и в этом случае рекомендуют начать использовать angular-eslint: тут есть builder и в планах — реализация schematic для автоматической миграции.
Также для миграции можно использовать решение от ESLint и написать свои правила с помощью eslint-plugin-typescript.


Если вы используете Nx, то в версии 9.1 появился плагин @nrwl/linter, поддерживающий ESLint.


Еще есть вариант использовать @tinkoff/linters, которые расширяют стандартный набор дополнительными наборами правил от Tinkoff. Последняя версия поддерживает ESLint, а предпоследняя — TSLint.


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


Stylelint


А как же стили? И для стилей тоже есть решение. Встречайте ?— ?stylelint. Активно развивается, много чего поддерживает и в наличии множество готовых правил. Также есть песочница — там можно сформировать необходимый вам набор правил.


Устанавливаем сам stylelint, а также стандартный набор правил:


npm install stylelint stylelint-config-standard --save-dev

Определяем конфигурацию в файле .stylelintrc (другие способы можно найти здесь):


{
 "extends": "stylelint-config-standard",
 "rules": {
   "at-rule-empty-line-before": null
 }
}

Добавляем скрипт в package.json, который можно запускать вручную для проверки стилей:


"scripts": {
 ...
 "lint-css": "stylelint src/**/*.css"
 ...
}

Далее запускаем и сразу ловим ошибку, так как по умолчанию в файле app.component.css стили отсутствуют, и это подпадает под правило no-empty-source, что показано на скриншоте ниже.



Stylelint: Первый запуск линтера стилей в Angular-проекте


Husky


Запускать вручную линтер каждый раз перед коммитом довольно утомительно, да и вообще? можно запросто забыть это сделать. Чтобы делать это автоматически, существует пакет husky, который позволяет удобным образом использовать Git Hooks.
К примеру, если необходимо выполнить какую-нибудь команду до коммита, определяем соответствующее поведения для хука pre-commit.
Помимо вышеописанного хука есть возможность определить поведение для хука pre-push. Обычно не использую его, потому что запуск линтеров на pre-commit помогает делать более качественные коммиты.


Перейдем к практике. ?Как и договаривались, перед каждым коммитом будет автоматически запускаться команда npm run lint.
Для этого установим husky:


npm install husky --save-dev

Определим конфигурацию в файле .huskyrc (можно хранить и в package.json, но попробуем альтернативный вариант):


{
 "hooks": {
   "pre-commit": "npm run lint"
 }
}

После чего достаточно сделать коммит и посмотреть вывод:



Husky: Вывод в терминале при коммите


Как видно из скриншота, перед созданием коммита отработала команда ng lint, которая запустилась скриптом lint, определенным в package.json.
То есть получается следующая последовательность:


Husky > pre-commit hook > npm run lint > ng lint


Если команда вернет ошибку, коммит сделать не получится, придется исправлять найденные недочеты.


Lint-staged


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


Однако, со временем, разработчики начинают замечать, что линтер что-то долго работает и кое-кто даже стал предлагать отключить линтер (ведь все и так уже знают codestyle, зачем ждать!). Отключать линтер, конечно же, не вариант, поэтому давайте определим, в чем проблема.


А проблема в том, что линтер запускается вообще для всех файлов проекта, а не только для тех, которые были изменены в процессе разработки. Так что при запуске линтера логичным будет учитывать только измененные файлы. Кроме этого, нас интересуют только те файлы, которые планируются добавить в коммит, а конкретно? —? файлы в статусе staged (подробнее про статусы файлов в git можно почитать здесь).


Переходим к практике.
Установим lint-staged:


npm install lint-staged --save-dev

Определим конфигурацию в файле .lintstagedrc (другие варианты описания конфигурации можно найти здесь). Далее настроим, что следует запускать скрипт линтера на файлы с расширением js и ts, а также еще один — на файлы с расширением css:


{
 "*.{js,ts}": "npm run lint",
 "*.css": "npm run lint-css"
}

Не забудем про husky, ведь именно благодаря ему запускается хук на pre-commit. Теперь в последовательность добавляется lint-staged, который будет работать в качестве фильтра файлов, не давая запускать линтер на все файлы подряд:


{
 "hooks": {
   "pre-commit": "lint-staged"
 }
}

Теперь последовательность выглядит так:


Husky > pre-commit hook > lint-staged
> tslint
> stylelint


Prettier


Prettier отлично дополняет то, о чем было написано ранее.
Но зачем вообще его использовать, если и так уже есть столько замечательных инструментов?


Из плюсов:


  • Prettier может автоматически исправить любой код (auto fix). Да, тот же TSLint тоже умеет автоматически исправлять код, но не весь. Обратите внимание на пометку Has Fixer в его правилах. То же самое касается и stylelint: он умеет исправлять стили, где это возможно, через использование экспериментального флага --fix. В prettier есть соглашения по умолчанию, так что ваш код будет приведен к единому стилю.
  • Выполняет более “умное” автоисправление. Подробнее можно прочитать здесь.
  • Умеет интегрироваться с некоторыми линтерами, в том числе с ESLint, TSLint, stylelint.
  • Умеет интегрироваться с различными IDE.
  • Поддерживает множество форматов.
  • Расширяется за счет плагинов.

Из минусов:


  • Иногда форматирование кажется очень странным и надо привыкать к этому. Мне и другим разработчикам на проекте хватило пары недель, чтобы привыкнуть и оценить преимущества.
  • На мой взгляд не хватает хорошего форматирования для HTML.

В целом, плюсов больше чем минусов и prettier неплохо выполняет роль связующего звена, объединяя и дополняя вышеперечисленные инструменты. Также стоит отметить, что код, форматированный в одном стиле, удобно читать — уже не ожидаешь каких-то “сюрпризов” при проведении ревью. Поэтому, если в вашей команде вы все еще не используете этот инструмент, обратите на него внимание — возможно, это то, что вам нужно.


Перейдем к практике.
Устанавливаем Prettier и необходимые наборы правил для интеграции с TSLint и stylelint:


npm install prettier tslint-config-prettier tslint-plugin-prettier stylelint-config-prettier stylelint-prettier --save-dev

Добавляем интеграцию для TSLint через файл tslint.json:


{
 "extends": [
   "tslint-config-prettier",
   "tslint-plugin-prettier",
   "codelyzer"
 ],
 "rules": {
   "prettier": true
 }
}

Здесь важно уточнить: поскольку ESLint/TSLint и Prettier частично решают схожие задачи, они могут конфликтовать между собой.
Эти конфиги нужны для устранения конфликта, а именно — для делегирования форматирования полностью в руки Prettier.
Далее добавляем интеграцию для stylelint в файл .stylelintrc:


{
 "extends": ["stylelint-prettier/recommended"]
}

Описываем конфигурацию для Prettier в .prettierrc:


{
 "trailingComma": "es5",
 "tabWidth": 2,
 "semi": true,
 "singleQuote": true
}

Чтобы проверить интеграцию, выполните следующие команды:


npx tslint-config-prettier-check ./tslint.json
npx stylelint-config-prettier-check

Также добавим команду для запуска prettier в package.json:


"scripts": {
 ...
 "prettier": "prettier --write src/**/*.{js,ts,css}",
 ...
}

Не забудем обновить конфиг lint-staged, чтобы Prettier запускался автоматически перед коммитом:


{
 "*.{js,json,md,html}": [
   "prettier --write"
 ],
 "*.css": [
   "prettier --write",
   "npm run lint-css"
 ],
 "*.ts": [
   "prettier --write",
   "npm run lint"
 ]
}

Так как Prettier только что добавлен, необходимо запустить его:


npm run prettier

Итоги


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


Если вы работаете в команде, ведете разработку на Angular, но еще не настроили линтеры — самое время сделать это и эта статья точно для вас. Если же вы не используете Angular, то с небольшими доработками сможете адаптировать все вышеописанное под свой проект.


Код решения, рассмотренного в рамках этой статьи, можно найти в этом репозитории.


Также обратите внимание на @tinkoff/linters, который представляет собой готовое решение с нашими дополнительными наборами правил.
И для быстрого старта рекомендую посмотреть на вот этот репозиторий, где вы найдете готовую конфигурацию, в том числе и по линтерам.