Поиск циклов
Цикл — группа модулей, импортирующих друг друга по кругу, напрямую или через цепочку. Циклы — самая частая причина «я тронул один файл, и три других фичи отвалились»: порядок загрузки модулей становится load-bearing, type-ошибки распространяются неожиданным образом, тесты начинают зависеть от порядка запуска.
Алгоритм
Archora гоняет алгоритм Тарьяна (strongly-connected-components) по графу импортов. Type-only рёбра по умолчанию исключены — TypeScript стирает их при сборке, и они не участвуют в runtime-циклах.
Каждый SCC с ≥ 2 модулями — цикл. Self-import (SCC длины 1) выводится отдельно как warning self-import, не как цикл.
Сложность — O(V + E), линейная по модулям + импортам. На 5000 модулях — единицы миллисекунд.
Паттерны циклов
После детектирования Archora делает второй проход и классифицирует каждый цикл по типичной форме:
| Паттерн | Выглядит как | Типичное решение |
|---|---|---|
mutual-pair | Ровно 2 модуля импортируют друг друга. | Вынести общий модуль или инвертировать одно направление. |
barrel-cycle | index.ts папки импортирует соседа, который импортирует обратно через barrel. | Обходить barrel изнутри папки. |
hub-feedback | Много модулей указывают на «хаб», который указывает обратно на один или несколько. | Разбить хаб или инвертировать back-edge. |
long-chain | Длинная цепочка, замкнутая 1–2 рёбрами. | Перевести одно замыкающее ребро в import type, если применимо. |
no-shape | Не удалось уверенно классифицировать. | Использовать feedback-arc-set подсказку. |
Распознавание паттернов чисто описательное — оно не меняет цикл, оно даёт словарь, чтобы говорить о нём и выбирать правильный фикс.
Severity
| Severity | Значение |
|---|---|
direct | Длина 2 — два модуля импортируют друг друга. |
indirect | Длина ≥ 3 — есть хотя бы один промежуточный hop. |
Десктоп показывает direct-циклы красным, indirect — оранжевым. CLI считает оба одинаково — оба попадают в --fail-on cycles:N.
Что насчёт type-only циклов?
Если цикл закрывается только type-импортом, такое ребро стирается при сборке, и цикла на runtime фактически нет. Archora находит такие случаи как рекомендацию type-only candidate: меняете import на import type, сохраняете — цикл исчезает.
Ограничения
- По умолчанию мы не моделируем
import.meta.globили кастомные dynamic-loader-ы. Сконфигурируйте их вarchora.config, если они есть в проекте — иначе часть «реальных» runtime-рёбер будет отсутствовать в графе. - Циклические типовые графы (
type Foo = { bar: Bar }и наоборот) не репортятся. TypeScript с ними справляется, и архитектурного долга в них нет.
См. также
- Feedback Arc Set — какое ребро резать.
- Рекомендации — что предлагает Archora.