javascript

ΠŸΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹ΠΉ статичСский Π°Π½Π°Π»ΠΈΠ· Π² TypeScript-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°Ρ…: Π²Ρ‹Ρ…ΠΎΠ΄ΠΈΠΌ Π·Π° Ρ€Π°ΠΌΠΊΠΈ tsc ΠΈ ESLint

  • пятница, 2 октября 2026β€―Π³. Π² 00:00:11
https://habr.com/ru/companies/megafon/articles/1088486/

πŸ‘‹ ΠŸΡ€ΠΈΠ²Π΅Ρ‚! МСня Π·ΠΎΠ²ΡƒΡ‚ АлСксандр, я Ρ€Π°Π±ΠΎΡ‚Π°ΡŽ Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠΌ Π² ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ «МСгаЀон». БСгодня Ρ…ΠΎΡ‡Ρƒ ΠΏΠΎΠ³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ΡŒ ΠΎ статичСском Π°Π½Π°Π»ΠΈΠ·Π΅ TypeScript-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ΠΎΠ² Π·Π° ΠΏΡ€Π΅Π΄Π΅Π»Π°ΠΌΠΈ ΠΏΡ€ΠΈΠ²Ρ‹Ρ‡Π½ΠΎΠΉ связки tsc ΠΈ ESLint.

tsc ΠΏΡ€Π΅ΠΆΠ΄Π΅ всСго ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅Ρ‚ Π·Π° ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ Ρ‚ΠΈΠΏΠΎΠ², Π° ESLint Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ ΡˆΠΈΡ€ΠΎΠΊΠΈΠΉ ΠΊΡ€ΡƒΠ³ ошибок ΠΈ Π½Π°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ принятых Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ ΠΏΡ€Π°Π²ΠΈΠ». ВмСстС ΠΎΠ½ΠΈ Π·Π°ΠΊΡ€Ρ‹Π²Π°ΡŽΡ‚ Π±ΠΎΠ»ΡŒΡˆΠΈΠ½ΡΡ‚Π²ΠΎ повсСднСвных Π·Π°Π΄Π°Ρ‡, Π½ΠΎ Π½Π΅ всС ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ ΡƒΠ΄ΠΎΠ±Π½ΠΎ Π²Ρ‹Ρ€Π°ΠΆΠ°Ρ‚ΡŒ Ρ‡Π΅Ρ€Π΅Π· Ρ‚ΠΈΠΏΡ‹ ΠΈ ΠΏΡ€Π°Π²ΠΈΠ»Π° Π»ΠΈΠ½Ρ‚Π΅Ρ€Π°. НапримСр, Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π·Π°ΠΏΡ€Π΅Ρ‚ΠΈΡ‚ΡŒ зависимости ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹ΠΌΠΈ слоями, Π½Π°ΠΉΡ‚ΠΈ экспортируСмый ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ большС Π½ΠΈΠ³Π΄Π΅ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ, ΠΈΠ»ΠΈ ΠΏΡ€ΠΎΡΠ»Π΅Π΄ΠΈΡ‚ΡŒ, ΠΊΠ°ΠΊ Π½Π΅ΠΏΡ€ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· HTTP-запроса доходят Π΄ΠΎ ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасной ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ. Π­Ρ‚ΠΎ ΡƒΠΆΠ΅ Ρ€Π°Π·Π½Ρ‹Π΅ Π·Π°Π΄Π°Ρ‡ΠΈ статичСского Π°Π½Π°Π»ΠΈΠ·Π°, ΠΈ для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΈΠ· Π½ΠΈΡ… Π΅ΡΡ‚ΡŒ свой ΠΏΠΎΠ΄Ρ…ΠΎΠ΄.

Одни инструмСнты строят Π³Ρ€Π°Ρ„ зависимостСй ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ связи ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ ΠΈ модулями. Π”Ρ€ΡƒΠ³ΠΈΠ΅ Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΡŽΡ‚ использованиС экспортов, слСдят Π·Π° распространСниСм any ΠΈΠ»ΠΈ ΠΎΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°ΡŽΡ‚ Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅. Π’ ΡΡ‚Π°Ρ‚ΡŒΠ΅ Ρ€Π°Π·Π±Π΅Ρ€Ρ‘ΠΌ Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ инструмСнты для этих Π·Π°Π΄Π°Ρ‡: ΠΎΡ‚ контроля Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹ ΠΈ поиска Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° Π΄ΠΎ Π°Π½Π°Π»ΠΈΠ·Π° ΠΏΠΎΡ‚ΠΎΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ собствСнных ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ.

1. ΠŸΠΎΡ‡Π΅ΠΌΡƒ tsc ΠΈ ESLint нСдостаточно?

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ½ΡΡ‚ΡŒ, Π·Π°Ρ‡Π΅ΠΌ Π½ΡƒΠΆΠ½Ρ‹ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ инструмСнты, посмотрим Π½Π° ограничСния tscΒ ΠΈΒ ESLint.

ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ tsc

ΠŸΡ€ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ΅ TypeScript создаёт Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π΅ прСдставлСниС всСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° β€” ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Program. Π’ Π½Ρ‘ΠΌ собраны исходныС Ρ„Π°ΠΉΠ»Ρ‹, ΠΈΠΌΠΏΠΎΡ€Ρ‚Ρ‹ ΠΈ информация ΠΎ Ρ‚ΠΈΠΏΠ°Ρ…. На основС Program компилятор Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ ΠΈΠΌΠΏΠΎΡ€Ρ‚Ρ‹ ΠΈ сопоставляСт Ρ‚ΠΈΠΏΡ‹ ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ tsc нСльзя ΡΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ инструмСнтом Ρ‚ΠΎΠ»ΡŒΠΊΠΎ локальной ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ. Π•Π³ΠΎ Π·Π°Π΄Π°Ρ‡Π° ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ»Π° языка ΠΈ систСмы Ρ‚ΠΈΠΏΠΎΠ², Π° Π½Π΅ Π»ΡŽΠ±Ρ‹Π΅ свойства ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°. Π—Π° Ρ€Π°ΠΌΠΊΠ°ΠΌΠΈ ΠΎΡΡ‚Π°ΡŽΡ‚ΡΡ:

  • АрхитСктурныС ограничСния: tsc Π½Π΅ провСряСт ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ»ΡŒΠ½Ρ‹Π΅ ΠΏΡ€Π°Π²ΠΈΠ»Π° слоёв, ΠΌΠΎΠ΄ΡƒΠ»ΡŒΠ½Ρ‹Ρ… Π³Ρ€Π°Π½ΠΈΡ† ΠΈΠ»ΠΈ допустимых Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ зависимостСй.

  • ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ экспорты ΠΈ Ρ„Π°ΠΉΠ»Ρ‹: ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ noUnusedLocals ΠΈ noUnusedParameters Π½Π΅ Π·Π°ΠΌΠ΅Π½ΡΡŽΡ‚ поиск экспортируСмых Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ, Ρ‚ΠΈΠΏΠΎΠ² ΠΈ классов, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ большС Π½ΠΈΠ³Π΄Π΅ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ.

  • ΠŸΠ΅Ρ€Π΅Π΄Π°Ρ‡Π° ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ…: Ρ…ΠΎΡ‚ΡŒ tsc ΠΈ ΡƒΠΌΠ΅Π΅Ρ‚ ΡΡƒΠΆΠ°Ρ‚ΡŒ Ρ‚ΠΈΠΏΡ‹ Π²Π½ΡƒΡ‚Ρ€ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ, ΠΎΠ½ Π½Π΅ отслСТиваСт, ΠΊΠ°ΠΊ Π½Π΅ΠΏΡ€ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Π΅ проходят Ρ‡Π΅Ρ€Π΅Π· Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΈ Π²Ρ‹Π·ΠΎΠ²ΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρƒ модулями.

ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ ESLint

ESLint Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ абстрактноС синтаксичСскоС Π΄Π΅Ρ€Π΅Π²ΠΎ (AST) ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ провСряСмого Ρ„Π°ΠΉΠ»Π°. Π­Ρ‚ΠΎ Π΄Π΅Ρ€Π΅Π²ΠΎ описываСт структуру ΠΊΠΎΠ΄Π°: объявлСния, Π²Ρ‹Π·ΠΎΠ²Ρ‹, условия ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠ΅ элСмСнты. ΠŸΡ€ΠΈ этом ΠΏΡ€Π°Π²ΠΈΠ»Π°ΠΌ typescript-eslint ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ… ΠΈΠ· typescript. Вакая настройка позволяСт Π²Ρ‹ΡΠ²Π»ΡΡ‚ΡŒ Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ стилистичСскиС ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹, Π½ΠΎ ΠΈ ошибки, для поиска ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½ΡƒΠΆΠ½Ρ‹ Ρ‚ΠΈΠΏΡ‹. НапримСр, ΠΏΡ€Π°Π²ΠΈΠ»Π° no-floating-promises ΠΈ no-misused-promises ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡŽΡ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΡƒΡŽ Ρ€Π°Π±ΠΎΡ‚Ρƒ с Promise, Π° сСмСйство no-unsafe-* Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ опасныС ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ с any. ΠŸΡ€Π°Π²ΠΈΠ»ΠΎ ESLint вызываСтся для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°, Π΄Π°ΠΆΠ΅ Ссли Π΅ΠΌΡƒ доступны Ρ‚ΠΈΠΏΡ‹ ΠΈ символы ΠΈΠ· typescript.

ESLint Π½Π΅ ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½ для ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΠΎΠ³ΠΎ Π°Π½Π°Π»ΠΈΠ·Π° Π³Ρ€Π°Ρ„Π° зависимостСй всСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°. Он Ρ‚Π°ΠΊΠΆΠ΅ Π½Π΅ ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½ для слоТного отслСТивания Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠ΅ΠΆΠ΄Ρƒ нСсколькими функциями ΠΈ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ. Для Ρ‚Π°ΠΊΠΈΡ… Π·Π°Π΄Π°Ρ‡ ΡƒΠ΄ΠΎΠ±Π½Π΅Π΅ спСциализированныС инструмСнты.

НиТС прСдставлСна Ρ‚Π°Π±Π»ΠΈΡ†Π° сравнСния ΡƒΡ€ΠΎΠ²Π½Π΅ΠΉ статичСского Π°Π½Π°Π»ΠΈΠ·Π°:

Π£Ρ€ΠΎΠ²Π΅Π½ΡŒ Π°Π½Π°Π»ΠΈΠ·Π°

Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚

Π§Ρ‚ΠΎ Π²ΠΈΠ΄ΠΈΡ‚

Π§Ρ‚ΠΎ упускаСт

АбстрактноС синтаксичСскоС Π΄Π΅Ρ€Π΅Π²ΠΎ (AST)

ESLint

Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ Ρ„Π°ΠΉΠ»Π°, стилистика, синтаксичСскиС конструкции.

Π‘Π΅Π· Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΉ настройки Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ….

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ‚ΠΈΠΏΠΎΠ²

tsc

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ‚ΠΈΠΏΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ, Π²Ρ‹Π²Π΅Π΄Π΅Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠΎΠ², сигнатуры.

ΠŸΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Π΅ ΠΏΡ€Π°Π²ΠΈΠ»Π°, Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ экспорты, отслСТиваниС опасных Π΄Π°Π½Π½Ρ‹Ρ….

ESLint с ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠ΅ΠΉ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ…

ESLint + typescript-eslint

Π’ΠΈΠΏΡ‹ ΠΈ символы ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°, ошибки Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Promise ΠΈ any.

Π“Ρ€Π°Ρ„ зависимостСй всСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° ΠΈ слоТныС Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ….

Π“Ρ€Π°Ρ„ зависимостСй ΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π°

dependency-cruiser, knip

Π“Ρ€Π°Ρ„ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ²/экспортов, Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ слоёв, Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ.

ΠŸΠΎΡ‚ΠΎΠΊ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ Π²Π½ΡƒΡ‚Ρ€ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ.

ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ…

Semgrep

Π”Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ ΠΎΡ‚ мСста Π²Π²ΠΎΠ΄Π° Π΄ΠΎ ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасной ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ.

Анализ ΠΌΠ΅ΠΆΠ΄Ρƒ функциями ΠΈ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ Π² Community Edition.

2. АрхитСктурный ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ ΠΈ Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ

ΠŸΡ€ΠΈ Ρ€Π°Π·Π²ΠΈΡ‚ΠΈΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ ΠΌΠ΅ΠΆΠ΄Ρƒ слоями часто Ρ€Π°Π·ΠΌΡ‹Π²Π°ΡŽΡ‚ΡΡ, особСнно ΠΊΠΎΠ³Π΄Π° Π²Π½Π΅Π΄Ρ€ΡΡŽΡ‚ Feature-Sliced Design (FSD), Clean Architecture ΠΈΠ»ΠΈ DDD (Domain-Driven Design). Π’ спСшкС Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ React‑компонСнт Π² бизнСс‑сСрвис ΠΈΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ инфраструктурный ΠΊΠ»ΠΈΠ΅Π½Ρ‚ прямо Π²Π½ΡƒΡ‚Ρ€ΠΈ Π΄ΠΎΠΌΠ΅Π½Π½ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ.

ΠžΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ ΠΈΠΌΠΏΠΎΡ€Ρ‚Ρ‹ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΡ‚ΡŒ ΠΈ ΠΏΡ€Π°Π²ΠΈΠ»Π°ΠΌΠΈ ESLint, Π½ΠΎ для Π΄Π΅ΠΊΠ»Π°Ρ€Π°Ρ‚ΠΈΠ²Π½ΠΎΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Π³Ρ€Π°Ρ„Π° зависимостСй ΡƒΠ΄ΠΎΠ±Π½Π΅Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ спСциализированный инструмСнт.

ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ связСй с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ dependency-cruiser

Начнём с dependency-cruiser. Π­Ρ‚ΠΎ Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ CLI-инструмСнт. Он Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ import, require ΠΈ export, строит Π³Ρ€Π°Ρ„ зависимостСй ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° ΠΈ свСряСт Π΅Π³ΠΎ с ΠΏΡ€Π°Π²ΠΈΠ»Π°ΠΌΠΈ ΠΈΠ· ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ.

Установим инструмСнт локально:

npm install --save-dev dependency-cruiser

Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» .dependency-cruiser.cjs. Рассмотрим ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Тёсткой настройки ΠΏΡ€Π°Π²ΠΈΠ» для Clean Architecture. Аналогичный ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΈ Π² FSD, Π°Π΄Π°ΠΏΡ‚ΠΈΡ€ΠΎΠ²Π°Π² ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΏΠΎΠ΄ Π΅Ρ‘ слои:

/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
  forbidden: [
    {
      name: 'no-circular',
      severity: 'error',
      comment:
        'ЦикличСскиС зависимости Π·Π°ΠΏΡ€Π΅Ρ‰Π΅Π½Ρ‹, Ρ‚Π°ΠΊ ΠΊΠ°ΠΊ ΠΎΠ½ΠΈ ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΊ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°ΠΌ с ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠ΅ΠΉ ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ ΠΈ ΡƒΡΠ»ΠΎΠΆΠ½ΡΡŽΡ‚ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ.',
      from: {},
      to: {
        circular: true
      }
    },
    {
      name: 'domain-isolation',
      severity: 'error',
      comment:
        'Π‘Π»ΠΎΠΉ Domain Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π·Π°Π²ΠΈΡΠ΅Ρ‚ΡŒ ΠΎΡ‚ Infrastructure ΠΈΠ»ΠΈ Presentation!',
      from: {
        path: '^src/domain(?:/|$)'
      },
      to: {
        path: [
          '^src/infrastructure(?:/|$)',
          '^src/presentation(?:/|$)',
          '^src/ui(?:/|$)'
        ]
      }
    },
    {
      name: 'no-orphans',
      severity: 'warn',
      comment:
        'ΠžΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½ Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ Ρ„Π°ΠΉΠ» Π±Π΅Π· входящих ΠΈ исходящих зависимостСй.',
      from: {
        orphan: true,
        path: '^src(?:/|$)',
        pathNot: [
          '\\.d\\.ts$',
          '(^|/)index\\.(?:[cm]?ts|tsx|[cm]?js|jsx)$'
        ]
      },
      to: {}
    },
    {
      name: 'restrict-node-builtins-in-ui',
      severity: 'error',
      comment:
        'UI-ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ систСмныС ΠΌΠΎΠ΄ΡƒΠ»ΠΈ Node.js (fs, path ΠΈ Ρ‚.Π΄.)',
      from: {
        path: '^src/(?:ui|presentation)(?:/|$)'
      },
      to: {
        dependencyTypes: ['core']
      }
    }
  ],
  options: {
    tsPreCompilationDeps: true,
    tsConfig: {
      fileName: 'tsconfig.json'
    },
    enhancedResolveOptions: {
      exportsFields: ['exports'],
      conditionNames: ['import', 'require', 'node', 'default']
    }
  }
};

Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ ΠΌΠΎΠΆΠ½ΠΎ Ρ‚Π°ΠΊ:

npx depcruise --config .dependency-cruiser.cjs src

Если ΠΊΡ‚ΠΎ-Ρ‚ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ Π² src/domain/user.ts ΠΈΠΌΠΏΠΎΡ€Ρ‚ Π²ΠΈΠ΄Π° import { HttpClient } from '../infrastructure/http', dependency-cruiser Π·Π°Π²Π΅Ρ€ΡˆΠΈΡ‚ Ρ€Π°Π±ΠΎΡ‚Ρƒ с Π½Π΅Π½ΡƒΠ»Π΅Π²Ρ‹ΠΌ ΠΊΠΎΠ΄ΠΎΠΌ Π²Ρ‹Ρ…ΠΎΠ΄Π° ΠΈ Π²Ρ‹Π²Π΅Π΄Π΅Ρ‚ Π½Π°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠ΅ ΠΏΡ€Π°Π²ΠΈΠ»Π° domain-isolation.

Π—Π°Ρ‰ΠΈΡ‚Π° инкапсуляции ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ Ρ‡Π΅Ρ€Π΅Π· good-fences

Если Π½ΡƒΠΆΠ½ΠΎ Ρ€Π°Π·Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠΎΠ½ΠΎΠ»ΠΈΡ‚ Π½Π° логичСски ΠΈΠ·ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ ΠΈ статичСски ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΡ… ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΉ API Π±Π΅Π· выдСлСния ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ модуля Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΠ°ΠΊΠ΅Ρ‚, ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ good-fences.

Установим ΠΏΠ°ΠΊΠ΅Ρ‚:

npm install --save-dev @good-fences/api

ΠŸΠ°ΠΊΠ΅Ρ‚ рСгистрируСт CLI-ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ good-fences, поэтому Π΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ Ρ‡Π΅Ρ€Π΅Π· npx ΠΈΠ»ΠΈ npm-скрипт.

Π‘ ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ good-fences ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Π² дирСкториях ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° Ρ„Π°ΠΉΠ»Ρ‹ fence.json, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΎΠΏΠΈΡΡ‹Π²Π°ΡŽΡ‚ Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ. ПолС exports опрСдСляСт, ΠΊΠ°ΠΊΠΈΠ΅ Ρ„Π°ΠΉΠ»Ρ‹ модуля доступны для ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° ΠΈΠ·Π²Π½Π΅, Π° imports ΠΌΠΎΠ΄ΡƒΠ»ΠΈ с ΠΊΠ°ΠΊΠΈΠΌΠΈ Ρ‚Π΅Π³Π°ΠΌΠΈ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΎ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠ·Π½ΡƒΡ‚Ρ€ΠΈ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ модуля.

НапримСр, ΠΌΠΎΠ΄ΡƒΠ»ΡŒ common ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΌ Ρ‚Π΅Π³ΠΎΠΌ Π² src/modules/common/fence.json:

{ 
  "tags": ["common"]
}

А ΠΌΠΎΠ΄ΡƒΠ»ΡŒ auth Π² src/modules/auth/fence.json:

{
  "tags": ["auth"]
}

Π’ΠΎΠ³Π΄Π° src/modules/billing/fence.json ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹Π³Π»ΡΠ΄Π΅Ρ‚ΡŒ Ρ‚Π°ΠΊ:

{
 "tags": ["billing"],
 "exports": [ "index", "types" ],
 "imports": [ "common", "auth" ]
}

Вакая конфигурация Ρ€Π°Π·Ρ€Π΅ΡˆΠ°Π΅Ρ‚ ΠΊΠΎΠ΄Ρƒ Π²Π½ΡƒΡ‚Ρ€ΠΈ billing ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ, ΠΏΠΎΠΌΠ΅Ρ‡Π΅Π½Π½Ρ‹Π΅ Ρ‚Π΅Π³Π°ΠΌΠΈ common ΠΈ auth, Π° снаруТи billing доступны Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π΅Π³ΠΎ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ index ΠΈ types.

ЗапускаСтся ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ:

npx good-fences src

Если Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ попытаСтся Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½ΠΈΠΉ Ρ„Π°ΠΉΠ» src/modules/billing/internal/calculator.ts ΠΈΠ· Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ модуля, Ρ‚Π°ΠΊΠΎΠΉ ΠΈΠΌΠΏΠΎΡ€Ρ‚ Π½Π°Ρ€ΡƒΡˆΠΈΡ‚ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ exports, ΠΈ good-fences Π·Π°Π²Π΅Ρ€ΡˆΠΈΡ‚ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ с ошибкой.

3. ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ ΠΊΠΎΠ΄ ΠΈ распространСниС any

Π‘ΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½Π΅ΠΌ кодовая Π±Π°Π·Π° обрастаСт мусором: ΡƒΡΡ‚Π°Ρ€Π΅Π²ΡˆΠΈΠ΅ экспортныС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ, Π½Π΅Π½ΡƒΠΆΠ½Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹, Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ зависимости Π² package.json. ΠŸΠΎΡΡ‚Π΅ΠΏΠ΅Π½Π½Ρ‹Π΅ ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ ΠΈ Π½Π΅Π±Ρ€Π΅ΠΆΠ½Ρ‹ΠΉ ΠΊΠΎΠ΄ приводят ΠΊ Ρ‚ΠΎΠΌΡƒ, Ρ‡Ρ‚ΠΎ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ распространяСтся any, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ сниТаСт ΠΏΠΎΠ»ΡŒΠ·Ρƒ ΠΎΡ‚ статичСской Ρ‚ΠΈΠΏΠΈΠ·Π°Ρ†ΠΈΠΈ.

Поиск Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° с Knip

ΠŸΡ€Π°Π²ΠΈΠ»ΠΎ ESLint no-unused-vars Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ использованиС ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…, Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ ΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π°. Оно Π½Π΅ строит ΠΏΠΎΠ»Π½Ρ‹ΠΉ Π³Ρ€Π°Ρ„ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ экспортируСмая функция ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ Π½Π΅ считаСтся Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠΉ, Π΄Π°ΠΆΠ΅ Ссли Π΅Ρ‘ Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚.

Для поиска Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° ΡƒΠ΄ΠΎΠ±Π½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Knip. Он строит Π³Ρ€Π°Ρ„ экспортов, ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ² ΠΈ зависимостСй ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°, Π²ΠΊΠ»ΡŽΡ‡Π°Ρ Ρ‚ΠΈΠΏΡ‹, ΠΈ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ элСмСнты с ΡƒΡ‡Ρ‘Ρ‚ΠΎΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ популярных инструмСнтов ΠΈ Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠΎΠ² (Next.js, Vite ΠΈ Ρ‚. Π΄.).

Установка:

npm install -D knip typescript @types/node

Или Ρ‡Π΅Ρ€Π΅Π· ΠΈΠ½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½ΡƒΡŽ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ:

npm init @knip/config

ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ knip.json:

{
  "$schema": "https://unpkg.com/knip@6/schema.json",
  "entry": ["src/index.ts", "src/bin/*.ts"],
  "project": ["src/**/*.{ts,tsx}"]
}

Π’ ΠΏΠΎΠ»Π΅ entry ΡƒΠΊΠ°Π·Π°Π½Ρ‹ Ρ‚ΠΎΡ‡ΠΊΠΈ Π²Ρ…ΠΎΠ΄Π° β€” Ρ„Π°ΠΉΠ»Ρ‹, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Knip Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΠΎΠ±Ρ…ΠΎΠ΄ Π³Ρ€Π°Ρ„Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°. ΠžΡ‚ ΠΈΡ… ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ³ΠΎ Π²Ρ‹Π±ΠΎΡ€Π° зависит Ρ‚ΠΎΡ‡Π½ΠΎΡΡ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°.

Команда для запуска:

npx knip

Π§Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π°ΠΉΡ‚ΠΈ Knip, Ρ‡Π΅Π³ΠΎ Π½Π΅ Π²ΠΈΠ΄ΠΈΡ‚ ESLint:

  1. ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ (Unused files): Ρ„Π°ΠΉΠ»Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ входят Π² Π³Ρ€Π°Ρ„ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ² ΠΎΡ‚ Π·Π°Π΄Π°Π½Π½Ρ‹Ρ… Ρ‚ΠΎΡ‡Π΅ΠΊ Π²Ρ…ΠΎΠ΄Π°.

  2. ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ экспорты (Unused exports): ЭкспортируСмыС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ, классы, константы ΠΈ интСрфСйсы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚.

  3. ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ экспортируСмыС Ρ‚ΠΈΠΏΡ‹ (Unused export types): Π’ΠΈΠΏΡ‹ ΠΈ интСрфСйсы, ΠΎΡ‚Π΄Π°Π²Π°Π΅ΠΌΡ‹Π΅ Π½Π°Ρ€ΡƒΠΆΡƒ, Π½ΠΎ Π½Π΅ вострСбованныС Π΄Ρ€ΡƒΠ³ΠΈΠΌΠΈ модулями.

  4. ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ зависимости (Unused dependencies / devDependencies): ΠŸΠ°ΠΊΠ΅Ρ‚Ρ‹ ΠΈΠ· package.json, использованиС ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Knip Π½Π΅ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΠ» Π² ΠΊΠΎΠ΄Π΅, конфигурациях, скриптах ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹Ρ… Ρ‚ΠΎΡ‡ΠΊΠ°Ρ… Π²Ρ…ΠΎΠ΄Π°.

  5. Π”ΡƒΠ±Π»ΠΈΡ€ΡƒΡŽΡ‰ΠΈΠ΅ΡΡ экспорты (Duplicate exports): ΠΎΠ΄ΠΈΠ½ ΠΈ Ρ‚ΠΎΡ‚ ΠΆΠ΅ элСмСнт экспортируСтся Π±ΠΎΠ»Π΅Π΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ€Π°Π·Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΠ΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ ΠΊΠ°ΠΊ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ ΠΈ экспорт ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ ΠΈΠ»ΠΈ Ρ‡Π΅Ρ€Π΅Π· ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰ΠΈΠ΅ΡΡ Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΈ рСэкспортов.

Π˜Π·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ ΠΈ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ строгости Ρ‚ΠΈΠΏΠΎΠ² с type-coverage

ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΊΠΎΠΌΠΏΠΈΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π±Π΅Π· ошибок, Π½ΠΎ ΠΏΡ€ΠΈ этом ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ сотни явных any, Π° ΠΏΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Ρ‘Π½Π½ΠΎΠΌ noImplicitAny ΠΈ нСявных. Они ΠΏΠΎΡΠ²Π»ΡΡŽΡ‚ΡΡ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΠΈΠ·-Π·Π° Π²Π½Π΅ΡˆΠ½ΠΈΡ… Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ Π±Π΅Π· Ρ‚ΠΈΠΏΠΎΠ² ΠΈΠ»ΠΈ злоупотрСблСния as any.

Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚ type-coverage ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ… ΠΈΠ· TypeScript-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°. Он считаСт, какая доля ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΎΠ² ΠΈΠΌΠ΅Π΅Ρ‚ Ρ‚ΠΈΠΏ, ΠΎΡ‚Π»ΠΈΡ‡Π½Ρ‹ΠΉ ΠΎΡ‚ any, ΠΈ Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚ покрытия Ρ‚ΠΈΠΏΠ°ΠΌΠΈ.

Установка ΠΈ запуск:

npm install -D type-coverage
npx type-coverage --at-least 95 --detail --strict

Π€Π»Π°Π³ΠΈ:

  • --at-least 95 β€” Π·Π°Π²Π΅Ρ€ΡˆΠ°Π΅Ρ‚ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ с ошибкой, Ссли ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ Ρ‚ΠΈΠΏΠ°ΠΌΠΈ Π½ΠΈΠΆΠ΅ 95%. Благодаря этому ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² CI.

  • --detail β€” Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ Ρ„Π°ΠΉΠ»Ρ‹, строки ΠΈ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ инструмСнт считаСт Π½Π΅ΠΏΠΎΠΊΡ€Ρ‹Ρ‚Ρ‹ΠΌΠΈ Ρ‚ΠΈΠΏΠ°ΠΌΠΈ.

  • --strict β€” Ρ€Π°ΡΡˆΠΈΡ€ΡΠ΅Ρ‚ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ: ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°Π΅Ρ‚ Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Π΅ any, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Promise<any> ΠΈ any[], Π° Ρ‚Π°ΠΊΠΆΠ΅ ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ нСбСзопасныС утвСрТдСния Ρ‚ΠΈΠΏΠΎΠ², утвСрТдСния ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π΅ Ρ€Π°Π²Π½ΠΎ null ΠΈΠ»ΠΈ undefined (!), ΠΈ слишком ΡˆΠΈΡ€ΠΎΠΊΠΈΠ΅ Ρ‚ΠΈΠΏΡ‹ Object ΠΈ {}.

ИспользованиС type-coverage позволяСт Π·Π°Ρ„ΠΈΠΊΡΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ минимально допустимый ΡƒΡ€ΠΎΠ²Π΅Π½ΡŒ покрытия Ρ‚ΠΈΠΏΠ°ΠΌΠΈ ΠΏΡ€ΠΈ Ρ€Π°Π±ΠΎΡ‚Π΅ с унаслСдованным ΠΊΠΎΠ΄ΠΎΠΌ ΠΈ Π½Π΅ Π΄ΠΎΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ Π΅Π³ΠΎ сниТСния.

4. Поиск ΠΏΠΎ структурС ΠΊΠΎΠ΄Π° ΠΈ отслСТиваниС ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ…

ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΠΌ, Ρ‡Ρ‚ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ ΠΈΠ· req.body, ΠΏΡ€ΠΎΡ…ΠΎΠ΄ΠΈΡ‚ Ρ‡Π΅Ρ€Π΅Π· нСсколько ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… ΠΈ ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ‚ Π² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ, Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‰ΡƒΡŽ ΡΠΈΡΡ‚Π΅ΠΌΠ½ΡƒΡŽ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ. ΠŸΡ€ΠΎΡΡ‚ΠΎΠ΅ сопоставлСниС синтаксичСских конструкций ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ опасный Π²Ρ‹Π·ΠΎΠ², Π½ΠΎ Π½Π΅ всСгда ΠΏΠΎΠΊΠ°ΠΆΠ΅Ρ‚, ΠΎΡ‚ΠΊΡƒΠ΄Π° ΠΏΡ€ΠΈΡˆΠ»ΠΈ Π΄Π°Π½Π½Ρ‹Π΅.

Анализ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ отслСТиваСт Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ ΠΏΠΎ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅, называСтся Π°Π½Π°Π»ΠΈΠ·ΠΎΠΌ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ… (Data Flow Analysis, DFA). Один ΠΈΠ· Π΅Π³ΠΎ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ² β€” Taint Analysis, ΠΈΠ»ΠΈ Π°Π½Π°Π»ΠΈΠ· ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ…: ΠΎΠ½ прослСТиваСт, ΠΊΠ°ΠΊ Π½Π΅ΠΏΡ€ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ значСния доходят Π΄ΠΎ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΠΌΠΎΠ³ΡƒΡ‚ ΡΡ‚Π°Ρ‚ΡŒ опасными.

ИспользованиС Semgrep для поиска Π°Π½Ρ‚ΠΈΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ΠΎΠ²

Semgrep β€” ΠΌΡƒΠ»ΡŒΡ‚ΠΈΡΠ·Ρ‹Ρ‡Π½Ρ‹ΠΉ инструмСнт статичСского Π°Π½Π°Π»ΠΈΠ·Π° с ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΌΠΈ ΠΏΡ€Π°Π²ΠΈΠ»Π°ΠΌΠΈ Π½Π° YAML, синтаксис ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΠΏΠΎΡ…ΠΎΠΆ Π½Π° провСряСмый ΠΊΠΎΠ΄. Π­Ρ‚ΠΈ ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ локально Ρ‡Π΅Ρ€Π΅Π· CLI ΠΈ ΠΎΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ с ΠΈΡ… ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Π°Π½Π°Π»ΠΈΠ· ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ….

Π’ Ρ‚Π°ΠΊΠΈΡ… ΠΏΡ€Π°Π²ΠΈΠ»Π°Ρ… source β€” мСсто, ΠΎΡ‚ΠΊΡƒΠ΄Π° приходят ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасныС Π΄Π°Π½Π½Ρ‹Π΅; sink β€” опСрация, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ ΠΎΠ½ΠΈ ΠΌΠΎΠ³ΡƒΡ‚ ΡΡ‚Π°Ρ‚ΡŒ опасными; sanitizer β€” ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΈΠ»ΠΈ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠ΅, послС ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ Π΄Π°Π½Π½Ρ‹Π΅ ΡΡ‡ΠΈΡ‚Π°ΡŽΡ‚ΡΡ бСзопасными.

Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠ΅ΠΌΡ‹ΠΉ способ установки:

pipx install semgrep

На macOS Ρ‚Π°ΠΊΠΆΠ΅ доступСн Homebrew:

brew install semgrep

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ 1: Поиск пустых Π±Π»ΠΎΠΊΠΎΠ² catch

ΠŸΡƒΡΡ‚ΠΎΠΉ catch ΠΏΠ΅Ρ€Π΅Ρ…Π²Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅, Π½ΠΎ Π½ΠΈΠΊΠ°ΠΊ Π΅Π³ΠΎ Π½Π΅ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚:

async function loadData() {
	try {
		await requestData();
	} catch (error) {
		// Ошибка Π½Π΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½Π°
	}
}

Π’Π°ΠΊΠΎΠΉ ΠΊΠΎΠ΄ Π½Π΅ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ Π½Π΅ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½Π½ΠΎΠΌΡƒ ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΈΡŽ Promise, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ошибка Π±Ρ‹Π»Π° ΠΏΠ΅Ρ€Π΅Ρ…Π²Π°Ρ‡Π΅Π½Π°. Однако это скрываСт сбои, услоТняСт ΠΎΡ‚Π»Π°Π΄ΠΊΡƒ ΠΈ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Π²ΠΏΠ΅Ρ‡Π°Ρ‚Π»Π΅Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ опСрация Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ.

Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ Π² Ρ„Π°ΠΉΠ»Π΅ .semgrep/rules/no-empty-catch.yaml:

rules:
  - id: javascript-no-empty-catch
    languages:
      - javascript
      - typescript
    message: >-
      ΠŸΡƒΡΡ‚ΠΎΠΉ Π±Π»ΠΎΠΊ catch подавляСт ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅.
      ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Π°ΠΉΡ‚Π΅ ΠΎΡˆΠΈΠ±ΠΊΡƒ, Π·Π°Π»ΠΎΠ³ΠΈΡ€ΡƒΠΉΡ‚Π΅ Π΅Ρ‘ ΠΈΠ»ΠΈ явно ΠΏΠ΅Ρ€Π΅Π΄Π°ΠΉΡ‚Π΅ дальшС.
    severity: MEDIUM
    pattern-either:
      - pattern: |
          try {
            ...
          } catch ($ERR) {}
      - pattern: |
          try {
            ...
          } catch {}

ΠŸΡ€Π°Π²ΠΈΠ»ΠΎ провСряСт пустыС Π±Π»ΠΎΠΊΠΈ catch ΠΊΠ°ΠΊ Π² синхронном, Ρ‚Π°ΠΊ ΠΈ Π² асинхронном ΠΊΠΎΠ΄Π΅. Π‘Π»ΠΎΠΊΠΈ, содСрТащиС Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ, Ρ‚Π°ΠΊΠΆΠ΅ ΡΡ‡ΠΈΡ‚Π°ΡŽΡ‚ΡΡ пустыми, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ Π½Π΅ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅.

Запуск ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ:

semgrep scan --config .semgrep/rules/no-empty-catch.yaml .

Для поиска Π½Π΅ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½Π½Ρ‹Ρ… Promise, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Π²Ρ‹Π·ΠΎΠ²ΠΎΠ² async-Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ Π±Π΅Π· await, return ΠΈΠ»ΠΈ явной ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π° Π² TypeScript-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°Ρ… Π»ΡƒΡ‡ΡˆΠ΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΈΠΏΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠ΅ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ ESLint:

'@typescript-eslint/no-floating-promises': 'error'

Semgrep Π² Ρ‚Π°ΠΊΠΎΠΌ случаС ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ для Π±ΠΎΠ»Π΅Π΅ спСцифичных ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Ρ… соглашСний, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ для Π·Π°ΠΏΡ€Π΅Ρ‚Π° пустых catch, ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ использования ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³Π³Π΅Ρ€Π° ΠΈΠ»ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ структуры ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² ошибок.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ 2: Taint Analysis β€” отслСТиваниС ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ…

Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ для ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° опасной ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ: ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… Π΄Π°Π½Π½Ρ‹Ρ… Π² unsafeExec(...) Π±Π΅Π· ΠΏΡ€Π΅Π΄Π²Π°Ρ€ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΉ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ.

.semgrep/rules/taint-user-input.yaml:

rules:
  - id: taint-user-input-to-command
    languages: [typescript, javascript]
    severity: ERROR
    message: >-
      НСпровСрСнныС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Π΅ ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ΡΡ Π² ΠΎΠΏΠ°ΡΠ½ΡƒΡŽ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΡŽ.
    mode: taint
    pattern-sources:
      - pattern: req.body
      - pattern: req.query
      - pattern: req.params
    pattern-sanitizers:
      - pattern: validateCommandArgument(...)
    pattern-sinks:
      - pattern: unsafeExec(...)

Запуск ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Semgrep локально:

semgrep --config=.semgrep/rules/

Если Π² ΠΊΠΎΠ΄Π΅ появится Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠ°:

function handleRequest(req, res) {
    const rawData = req.body.data;
    // ... ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½Ρ‹ΠΉ ΠΊΠΎΠ΄ ...
    const formatted = `${rawData}`;
    // ...
    unsafeExec(formatted);
}

Semgrep отслСдит распространСниС значСния ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½Ρ‹ΠΌΠΈ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌΠΈ Π²Π½ΡƒΡ‚Ρ€ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΈ ΡƒΠΊΠ°ΠΆΠ΅Ρ‚ Π½Π° Π²Ρ‹Π·ΠΎΠ² unsafeExec(formatted).

Бпособ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΈΠ»ΠΈ очистки всСгда зависит ΠΎΡ‚ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ опасной ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ: HTML, SQL ΠΈ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ ΠΎΠ±ΠΎΠ»ΠΎΡ‡ΠΊΠΈ Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Ρ… стратСгий.

5. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ собствСнных Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€ΠΎΠ² Ρ‡Π΅Ρ€Π΅Π· TypeScript Compiler API ΠΈ ts-morph

Π“ΠΎΡ‚ΠΎΠ²Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ» Π»ΠΈΠ½Ρ‚Π΅Ρ€ΠΎΠ² ΠΈΠ½ΠΎΠ³Π΄Π° Π½Π΅ Ρ…Π²Π°Ρ‚Π°Π΅Ρ‚. НапримСр, Π² ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ свои Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Π΅ соглашСния: ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ класс с Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ΠΎΠΌ @Injectable() обязан ΠΈΠΌΠ΅Ρ‚ΡŒ явно Π·Π°Π΄Π°Π½Π½Ρ‹ΠΉ Π»ΠΎΠ³Π³Π΅Ρ€ Π² конструкторС, Π° всС классы для ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ… (Data Transfer Object, DTO) ΠΈΠ· ΠΏΠ°ΠΏΠΊΠΈ src/dto Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΠΎΡΡ‚ΠΎΡΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΈΠ· readonly ΠΏΠΎΠ»Π΅ΠΉ ΠΈ Π·Π°ΠΊΠ°Π½Ρ‡ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π½Π° Dto.

Π Π΅ΡˆΠ°Ρ‚ΡŒ Ρ‚Π°ΠΊΠΈΠ΅ Π·Π°Π΄Π°Ρ‡ΠΈ рСгулярными выраТСниями Π½Π΅Π½Π°Π΄Ρ‘ΠΆΠ½ΠΎ, Π° ΠΏΠΈΡΠ°Ρ‚ΡŒ ΠΏΠ»Π°Π³ΠΈΠ½ для ESLint Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ AST Π³Ρ€ΠΎΠΌΠΎΠ·Π΄ΠΊΠΎ. Π—Π΄Π΅ΡΡŒ ΠΏΠΎΠ΄ΠΎΠΉΠ΄Ρ‘Ρ‚ TypeScript Compiler API β€” ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½Ρ‹ΠΉ интСрфСйс ΠΊ Ρ‚Π΅ΠΌ ΠΆΠ΅ синтаксичСским Π΄Π΅Ρ€Π΅Π²ΡŒΡΠΌ, символам ΠΈ Ρ‚ΠΈΠΏΠ°ΠΌ, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌΠΈ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ компилятор. Для удобства ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΅Π³ΠΎ ΠΎΠ±Ρ‘Ρ€Ρ‚ΠΊΡƒ ts-morph.

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠ°Ρ рСализация собствСнного Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€Π° Π½Π° ts-morph

ts-morph ΡƒΠΏΡ€ΠΎΡ‰Π°Π΅Ρ‚ ΠΎΠ±Ρ…ΠΎΠ΄ AST β€” ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΡƒΡŽ Ρ€Π°Π±ΠΎΡ‚Ρƒ с ΡƒΠ·Π»Π°ΠΌΠΈ синтаксичСского Π΄Π΅Ρ€Π΅Π²Π°, Π° Ρ‚Π°ΠΊΠΆΠ΅ Ρ€Π°Π±ΠΎΡ‚Ρƒ с символами, Ρ‚ΠΈΠΏΠ°ΠΌΠΈ ΠΈ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.

Установка:

npm install --save-dev ts-morph ts-node

Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ скрипт scripts/analyze-architecture.ts. Он Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡ‚ΡŒ Π΄Π²Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ»Π°:

  1. ИмСна всСх DTO-классов Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΈΠΌΠ΅Ρ‚ΡŒ суффикс Dto, Π° всС ΠΈΡ… свойства Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ readonly.

  2. Ни ΠΎΠ΄ΠΈΠ½ ΠΌΠ΅Ρ‚ΠΎΠ΄ сСрвиса (*Service.ts) Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ any ΠΈΠ»ΠΈ нСявный Promise<any>.

Π’ этом дСмонстрационном ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ getProperties() провСряСт свойства, ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Π΅ нСпосрСдствСнно Π² классС. Бвойства, ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Π΅ Ρ‡Π΅Ρ€Π΅Π· ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ конструктора, ΠΈ унаслСдованныС свойства Π½Π΅ ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°ΡŽΡ‚ΡΡ. Для простоты Promise<any> Π½ΠΈΠΆΠ΅ опрСдСляСтся Ρ‡Π΅Ρ€Π΅Π· строковоС прСдставлСниС Ρ‚ΠΈΠΏΠ°. Π’ Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€Π΅ для Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° Π»ΡƒΡ‡ΡˆΠ΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡ‚ΡŒ структуру Ρ‚ΠΈΠΏΠ° ΠΈ Π΅Π³ΠΎ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ Ρ‡Π΅Ρ€Π΅Π· TypeScript Compiler API.

import { Project, ClassDeclaration, PropertyDeclaration } from 'ts-morph';
import * as path from 'path';

// Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ с привязкой ΠΊ tsconfig.json
const project = new Project({
  tsConfigFilePath: path.join(__dirname, '../tsconfig.json'),
});

let hasErrors = false;

function logError(filePath: string, line: number, message: string) {
  console.error(`\x1b[31m[ARCH ERROR]\x1b[0m ${filePath}:${line} - ${message}`);
  hasErrors = true;
}

console.log('Запуск Π°Π½Π°Π»ΠΈΠ·Π° Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ»...');

const sourceFiles = project.getSourceFiles();

sourceFiles.forEach((sourceFile) => {
  const filePath = sourceFile.getFilePath();

  // -------------------------------------------------------------------
  // ΠŸΡ€Π°Π²ΠΈΠ»ΠΎ 1: ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° DTO Π² Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΎΡ€ΠΈΠΈ src/dto
  // -------------------------------------------------------------------
  if (filePath.includes('/dto/')) {
    const classes = sourceFile.getClasses();

    classes.forEach((cls: ClassDeclaration) => {
      const className = cls.getName();

      if (!className || !className.endsWith('Dto')) {
        const line = cls.getStartLineNumber();
        logError(filePath, line, `Имя класса DTO "${className || 'Anonymous'}" Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΎΠΊΠ°Π½Ρ‡ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π½Π° "Dto".`);
      }

      // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ свойства класса
      cls.getProperties().forEach((prop: PropertyDeclaration) => {
        if (!prop.isReadonly()) {
          const line = prop.getStartLineNumber();
          logError(
            filePath,
            line,
            `Бвойство "${prop.getName()}" Π² DTO "${className}" Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΈΠΌΠ΅Ρ‚ΡŒ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ readonly!`
          );
        }
      });
    });
  }

  // -------------------------------------------------------------------
  // ΠŸΡ€Π°Π²ΠΈΠ»ΠΎ 2: ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² сСрвисов (*Service.ts) Π½Π° Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ `any`
  // -------------------------------------------------------------------
  if (filePath.endsWith('Service.ts')) {
    const classes = sourceFile.getClasses();

    classes.forEach((cls) => {
      cls.getMethods().forEach((method) => {
        const returnType = method.getReturnType();
        const returnTypeString = returnType.getText();
        const methodName = method.getName();
        const line = method.getStartLineNumber();
        
        if (returnType.isAny()) {
          logError( filePath, line, `ΠœΠ΅Ρ‚ΠΎΠ΄ "${methodName}" сСрвиса "${cls.getName()}" Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠ° "any". Π£ΠΊΠ°ΠΆΠΈΡ‚Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚ΠΈΠΏ!` );
          }

        if (returnTypeString === 'Promise<any>') {
          logError(
            filePath,
            line,
            `ΠœΠ΅Ρ‚ΠΎΠ΄ "${methodName}" сСрвиса "${cls.getName()}" Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ "${returnTypeString}" с Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹ΠΌ "any". Π£ΠΊΠ°ΠΆΠΈΡ‚Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚ΠΈΠΏ!`
          );
        }
      });
    });
  }
});

if (hasErrors) {
  console.error('ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π° с ошибками Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹.');
  process.exit(1);
} else {
  console.log('АрхитСктурный Π°Π½Π°Π»ΠΈΠ· ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ ΠΏΡ€ΠΎΠΉΠ΄Π΅Π½!');
  process.exit(0);
}

Для запуска Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€Π° достаточно Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ Π² package.json:

{
  "scripts": {
    "lint:arch": "ts-node scripts/analyze-architecture.ts"
  }
}

Π’Π°ΠΊΠΎΠΉ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ ΡƒΠ΄ΠΎΠ±Π΅Π½ для ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ», ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ слоТно ΠΈΠ»ΠΈ Π½Π΅ΡƒΠ΄ΠΎΠ±Π½ΠΎ Π²Ρ‹Ρ€Π°Π·ΠΈΡ‚ΡŒ Π³ΠΎΡ‚ΠΎΠ²Ρ‹ΠΌΠΈ Π»ΠΈΠ½Ρ‚Π΅Ρ€Π°ΠΌΠΈ: ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡ‚ΡŒ Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€Ρ‹, JSDoc-ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π½Π΅ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π²Ρ‹Π·ΠΎΠ²Ρ‹ Π² CI ΠΈΠ»ΠΈ Π½Π° этапС сборки.

6. Π—Π° ΠΏΡ€Π΅Π΄Π΅Π»Π°ΠΌΠΈ статичСского Π°Π½Π°Π»ΠΈΠ·Π°: ΠΌΡƒΡ‚Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠ΅ тСстированиС с StrykerJS

StrykerJS Ρ„ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ Π½Π΅ относится ΠΊ статичСскому Π°Π½Π°Π»ΠΈΠ·Ρƒ, Π½ΠΎ Ρ…ΠΎΡ€ΠΎΡˆΠΎ дополняСт описанныС Π²Ρ‹ΡˆΠ΅ автоматичСскиС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ. Π‘ Π΅Π³ΠΎ ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡ†Π΅Π½ΠΈΡ‚ΡŒ, Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π»ΠΈ тСсты ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΠ²Π°ΡŽΡ‚ измСнСния повСдСния ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹. ΠžΠ±Ρ‹Ρ‡Π½ΠΎΠ΅ ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ ΠΊΠΎΠ΄Π° (Code Coverage) ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ„Π°ΠΊΡ‚ выполнСния строк Π²ΠΎ врСмя тСстов, Π½ΠΎ Π½Π΅ Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ качСства самих тСстов.

ΠœΡƒΡ‚Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠ΅ тСстированиС ΠΏΡ€Π΅ΠΆΠ΄Π΅ всСго ΠΎΡ†Π΅Π½ΠΈΠ²Π°Π΅Ρ‚ качСство тСстов. Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚ вносит Π½Π°ΠΌΠ΅Ρ€Π΅Π½Π½Ρ‹Π΅ измСнСния (ΠΌΡƒΡ‚Π°Ρ†ΠΈΠΈ) Π² исходный ΠΊΠΎΠ΄ ΠΈ запускаСт тСсты. Если хотя Π±Ρ‹ ΠΎΠ΄ΠΈΠ½ тСст ΡƒΠΏΠ°Π», ΠΌΡƒΡ‚Π°Π½Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ статус Killed: тСсты ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅. Если всС тСсты ΠΏΡ€ΠΎΡˆΠ»ΠΈ, ΠΌΡƒΡ‚Π°Π½Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ статус Survived, Ρ‡Ρ‚ΠΎ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π½Π° ΠΏΡ€ΠΎΠ±Π΅Π» Π² тСстах.

Настройка StrykerJS локально

Установим StrykerJS ΠΈ ΠΏΠ°ΠΊΠ΅Ρ‚ ΠΈΠ½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΈ с Vitest:

npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runner

Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ stryker.config.json:

{
  "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
  "mutate": [
    "src/**/*.ts",
    "!src/**/*.spec.ts",
    "!src/**/*.dto.ts"
  ],
  "testRunner": "vitest",
  "reporters": ["html", "clear-text", "progress"],
  "coverageAnalysis": "perTest"
}

ΠŸΡ€ΠΈ Π·Π°ΠΌΠ΅Π½Π΅ условий (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, if (a > b) Π½Π° if (a >= b) ΠΈΠ»ΠΈ return true Π½Π° return false) Stryker ΠΏΠΎΠΊΠ°ΠΆΠ΅Ρ‚, ΠΊΠ°ΠΊΠΈΠ΅ измСнСния Π² Π»ΠΎΠ³ΠΈΠΊΠ΅ Π½Π΅ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΌΠΈ тСстами.

7. Настройка локального окруТСния для Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΈ интСграция ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ Π² CI/CD

Π§Π΅ΠΌ большС Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€ΠΎΠ², Ρ‚Π΅ΠΌ Π²Π°ΠΆΠ½Π΅Π΅ ΡΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ ΠΈΡ… Ρ€Π°Π±ΠΎΡ‚Ρ‹. Анализ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ»ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° всСго Π³Ρ€Π°Ρ„Π° зависимостСй ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π½ΠΈΠΌΠ°Ρ‚ΡŒ Π·Π°ΠΌΠ΅Ρ‚Π½ΠΎΠ΅ врСмя Π² зависимости ΠΎΡ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΊΠΎΠ΄ΠΎΠ²ΠΎΠΉ Π±Π°Π·Ρ‹ ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ инструмСнта.

ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΡƒΠ΄ΠΎΠ±Π½ΠΎ Ρ€Π°Π·Π΄Π΅Π»ΠΈΡ‚ΡŒ Π½Π° нСсколько ΡƒΡ€ΠΎΠ²Π½Π΅ΠΉ Π² зависимости ΠΎΡ‚ стоимости ΠΈΡ… выполнСния:

  1. ΠŸΠ΅Ρ€Π΅Π΄ ΠΊΠΎΠΌΠΌΠΈΡ‚ΠΎΠΌ β€” быстрыС Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ: ESLint для Ρ„Π°ΠΉΠ»ΠΎΠ², Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Ρ… Π² ΠΊΠΎΠΌΠΌΠΈΡ‚, ΠΈ ΠΈΠ½ΠΊΡ€Π΅ΠΌΠ΅Π½Ρ‚Π°Π»ΡŒΠ½Π°Ρ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° tsc.

  2. ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΈ Π² CI для Pull Request β€” ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ: Knip, dependency-cruiser, Semgrep, ts-morph, type-coverage.

  3. По Ρ€Π°ΡΠΏΠΈΡΠ°Π½ΠΈΡŽ β€” рСсурсоёмкиС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ: StrykerJS ΠΈ Π±ΠΎΠ»Π΅Π΅ тяТёлыС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ бСзопасности.

Настройка Git-Ρ…ΡƒΠΊΠΎΠ² Ρ‡Π΅Ρ€Π΅Π· Husky ΠΈ lint-staged

Git-Ρ…ΡƒΠΊ β€” ΠΊΠΎΠΌΠ°Π½Π΄Π°, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Git автоматичСски запускаСт ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½Ρ‹ΠΌ событиСм. Π—Π΄Π΅ΡΡŒ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Husky ΠΈ lint-staged ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΠΌ быстрыС Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΊ этапу ΠΊΠΎΠΌΠΌΠΈΡ‚Π°:

1 - Установка Husky ΠΈ lint-staged:

npm install -D husky lint-staged
npx husky init

2 - Настроим .husky/pre-commit:

npx lint-staged

3 - Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ lint-staged.config.mjs:

export default {
  '*.{ts,tsx}': [
    'eslint --fix',
    () => 'tsc --noEmit'
  ]
};

ESLint Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΏΡƒΡΠΊΠ°Ρ‚ΡŒΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для TypeScript-Ρ„Π°ΠΉΠ»ΠΎΠ², Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Ρ… Π² ΠΊΠΎΠΌΠΌΠΈΡ‚. tsc --noEmit здСсь провСряСт вСсь TypeScript-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚, Π° Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ эти Ρ„Π°ΠΉΠ»Ρ‹, Π½ΠΎ запускаСтся, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π² ΠΊΠΎΠΌΠΌΠΈΡ‚ ΠΏΠΎΠΏΠ°Π»ΠΈ Ρ„Π°ΠΉΠ»Ρ‹ .ts ΠΈΠ»ΠΈ .tsx. На большом ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ Ρ‚ΠΈΠΏΠΎΠ² ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·ΡƒΠΌΠ½Π΅Π΅ пСрСнСсти Π½Π° этап ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΈΠ»ΠΈ Π² CI.

Запуск ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Ρ… ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ Π² CI

Π‘Π»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ ΡƒΡ€ΠΎΠ²Π΅Π½ΡŒ β€” ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‚ΡΡ ΠΏΡ€ΠΈ создании ΠΈΠ»ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ Pull Request.

НапримСр, Π² package.json ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΈΡ… Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹:

{
  "scripts": {
    "lint:arch": "depcruise --config .dependency-cruiser.cjs src && ts-node scripts/analyze-architecture.ts",
    "lint:semgrep": "semgrep scan --config .semgrep/rules/ .",
    "lint:knip": "knip",
    "type-coverage": "type-coverage --at-least 95 --detail --strict",
    "check:deep": "npm run lint:arch && npm run lint:semgrep && npm run lint:knip && npm run type-coverage"
  }
}

Π’ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎΠΌ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ CI ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ:

npm ci
npm run check:deep

Π’Π°ΠΊ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Π½Π΅ Π·Π°ΠΌΠ΅Π΄Π»ΡΡŽΡ‚ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠΌΠΌΠΈΡ‚, Π½ΠΎ ΠΏΡ€ΠΈ этом ΠΌΠΎΠ³ΡƒΡ‚ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Pull Request ΠΏΡ€ΠΈ Π½Π°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ», появлСнии Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° ΠΈΠ»ΠΈ сниТСнии покрытия Ρ‚ΠΈΠΏΠΎΠ².

ВяТёлыС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΏΠΎ Ρ€Π°ΡΠΏΠΈΡΠ°Π½ΠΈΡŽ

НаиболСС рСсурсоёмкиС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ StrykerJS ΠΈ Π±ΠΎΠ»Π΅Π΅ тяТёлыС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ бСзопасности, ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ ΠΏΠΎ Ρ€Π°ΡΠΏΠΈΡΠ°Π½ΠΈΡŽ β€” Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Ρ€Π°Π· Π² сутки. Π’Π°ΠΊ рСсурсоёмкиС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‚ΡΡ рСгулярно, Π½ΠΎ Π½Π΅ Π²Π»ΠΈΡΡŽΡ‚ Π½Π° ΡΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ повсСднСвной Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ.

8. Π—Π°ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ сводная ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Π° инструмСнтов

Π‘Π²Π΅Π΄Ρ‘ΠΌ всС Ρ€Π°Π·ΠΎΠ±Ρ€Π°Π½Π½Ρ‹Π΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ Π² Π΅Π΄ΠΈΠ½ΡƒΡŽ ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρƒ Π²Ρ‹Π±ΠΎΡ€Π°:

Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚

ΠšΠ°ΠΊΡƒΡŽ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡƒ Ρ€Π΅ΡˆΠ°Π΅Ρ‚

На ΠΊΠ°ΠΊΠΎΠΌ этапС ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ

Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ Ρ€Π°Π±ΠΎΡ‚Ρ‹

tsc

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ‚ΠΈΠΏΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ

ΠŸΠ΅Ρ€Π΅Π΄ ΠΊΠΎΠΌΠΌΠΈΡ‚ΠΎΠΌ / Π² IDE

⚑⚑⚑ Высокая (ΠΏΡ€ΠΈ incremental)

ESLint + @typescript-eslint

ΠŸΡ€Π°Π²ΠΈΠ»Π° ΠΏΠΎ синтаксичСскому Π΄Π΅Ρ€Π΅Π²Ρƒ ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ с ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠ΅ΠΉ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ…

ΠŸΠ΅Ρ€Π΅Π΄ ΠΊΠΎΠΌΠΌΠΈΡ‚ΠΎΠΌ / Π² IDE

⚑⚑⚑ Высокая

dependency-cruiser

ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Ρ… слоёв ΠΈ цикличСских зависимостСй

ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ / Π² CI для PR

⚑⚑ БрСдняя

good-fences

ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Ρ… Π³Ρ€Π°Π½ΠΈΡ† ΠΈ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρƒ модулями

ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ / Π² CI для PR

⚑⚑ БрСдняя

Knip

Поиск Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°, экспортов ΠΈ Π»ΠΈΡˆΠ½ΠΈΡ… ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ²

ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ / Π² CI для PR

⚑⚑ БрСдняя

type-coverage

ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ распространСния any ΠΈ покрытия Ρ‚ΠΈΠΏΠ°ΠΌΠΈ

Π’ CI для PR

⚑⚑ БрСдняя

Semgrep (CLI)

Поиск Π°Π½Ρ‚ΠΈΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ΠΎΠ² ΠΈ отслСТиваниС ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ…

Π’ CI для PR

⚑⚑ БрСдняя

ts-morph (собствСнныС ΠΏΡ€Π°Π²ΠΈΠ»Π°)

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° узкоспСциализированных бизнСс- ΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π½Ρ‹Ρ… ΠΏΡ€Π°Π²ΠΈΠ»

Π’ CI для PR

⚑⚑ БрСдняя

StrykerJS

ДинамичСская ΠΎΡ†Π΅Π½ΠΊΠ° эффСктивности тСстов ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠΌ ΠΌΡƒΡ‚Π°Ρ†ΠΈΠΉ

По Ρ€Π°ΡΠΏΠΈΡΠ°Π½ΠΈΡŽ

🐒 Низкая

Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ ΡƒΠΊΠ°Π·Π°Π½Π° ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΈ ΠΌΠΎΠΆΠ΅Ρ‚ сущСствСнно Π·Π°Π²ΠΈΡΠ΅Ρ‚ΡŒ ΠΎΡ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°, ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€Π° ΠΈ использования кэша.

Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π£Π½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½ΠΎΠ³ΠΎ Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€Π° Π½Π΅Ρ‚: ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ‚ΠΈΠΏΠΎΠ², Π³Ρ€Π°Ρ„ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΎΠ², поиск Π½Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°, отслСТиваниС ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ опасных Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ качСство тСстов β€” Ρ€Π°Π·Π½Ρ‹Π΅ классы Π·Π°Π΄Π°Ρ‡. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Π½Π°Π±ΠΎΡ€ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ стоит ΠΏΠΎΠ΄Π±ΠΈΡ€Π°Ρ‚ΡŒ ΠΏΠΎΠ΄ риски ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.

Π”Π΅ΡˆΡ‘Π²Ρ‹Π΅ ΠΈ быстрыС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ Ρ€Π°Π·ΡƒΠΌΠ½ΠΎ ΠΎΡΡ‚Π°Π²Π»ΡΡ‚ΡŒ Π² локальном Ρ†ΠΈΠΊΠ»Π΅, ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ β€” Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ Π² CI, Π° рСсурсоёмкиС β€” ΠΏΠΎ Ρ€Π°ΡΠΏΠΈΡΠ°Π½ΠΈΡŽ. Π’Π°ΠΊ Ρ‡Π°ΡΡ‚ΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰ΠΈΡ…ΡΡ Π·Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠΉ пСрСносится ΠΈΠ· Ρ€ΡƒΡ‡Π½ΠΎΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΊΠΎΠ΄Π° Π² автоматичСскиС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ, Π° ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ Π΄ΠΎ слияния ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ.