Переизобретаем IDE — Codegraph Studio: код как интерактивный граф вызовов

Статья будет интересна, в первую очередь, NodeJS разработчикам.

Переизобретаем IDE — Codegraph Studio: код как интерактивный граф вызовов

Большой TypeScript-проект редко выглядит как система. Он выглядит как дерево папок слева и стопка вкладок сверху. Архитектура при этом живёт отдельно: в голове у того, кто дольше всех тут работает, в Confluence двухлетней давности и в фразе «это зовёт вот это, кажется».

Надоело так читать код. Хотелось найти IDE которая сразу показывает всю структуру проекта и дружелюбна к большому количеству одновременно открытых файлов. Но такой IDE я не нашел. По этому сделал расширение для VS Code, которое парсит TypeScript AST, строит граф вызовов между функциями и рисует его на холсте. Карточки — файлы. Дуги — вызовы. Файл открывается прямо на холсте: подсветка синтаксиса, сохранение в воркспейс, граф при этом остается доступен.

Называется Codegraph Studio.

Превью функционала

Проблема, которую не лечит дерево файлов

Файловое дерево отвечает на вопрос «где лежит файл». Grep отвечает на «где встречается строка». TypeScript language server отвечает на «куда прыгнуть из этого символа».

Ни один из них не отвечает на «как устроена система».

Поэтому онбординг выглядит так: открыли services/, потом три соседних файла, потом barrel-реэкспорт, потом конструктор, в который инжектят пять зависимостей. Через сорок минут у человека открыто 18 вкладок, и он всё ещё не знает, это ядро или боковая ветка.

Диаграммы в репозитории гниют в тот же спринт, в который их нарисовали. Dependency graph по import'ам врёт иначе: import есть, вызова нет; вызов есть через new Foo() или поле класса — а на графе модулей этого нет.

Мне нужна была не схема «для слайда», а рабочая поверхность: увидел форму — ткнул — починил, не потеряв место на карте.

Слева — вкладки и дерево. Справа — та же кодовая база как карта.
Слева — вкладки и дерево. Справа — та же кодовая база как карта.

Алгоритм работы через плагин, коротко

Codegraph Studio открывается из Activity Bar в VS Code. Парсит .ts / .tsx текущего воркспейса (или выбранной папки), строит call graph и кладёт его на быстрый холст: зум, пан, перетаскивание карточек.

На скриншоте ниже — реальный прогон: 196 файлов, около 1000 функций, 1814 связей. Это уже не «игрушечный пример из README», и холст на таком размере не замирает: отрисовка режет то, что вне экрана.

Call graph: файлы карточками, вызовы дугами
Call graph: файлы карточками, вызовы дугами

Карточки — файлы. Кривые стрелки — вызовы. Счётчик в углу — масштаб того, что вы смотрите.

Внутри проекта резолвятся вещи, на которых обычно спотыкаются визуализаторы: ESM-импорты со спецификатором .js, new Foo(), barrel-реэкспорты, import * as, алиасы, динамический import(), пути из tsconfig и workspace-пакеты, методы инстанса, DI через конструктор и поля.

Тридцать секунд до первой карты

  1. Ставите Codegraph Studio из Marketplace (или ext install stovberpv.codegraph-studio).
  2. Открываете папку проекта.
  3. Жмёте иконку расширения в Activity Bar — холст открывается сразу, без бокового меню.
  4. На стартовом экране: Analyze current project или Choose folder. Парсинг начинается только после этого клика, редактор не блокируется: разбор идёт в worker'е, прогресс видно и в Notification, и на оверлее.

Дальше как на карте: скролл — зум, фон — пан, карточку можно сдвинуть. Позиции и состояния пишутся в localStorage на проект, после перезапуска раскладка не разъезжается в случайный комок.

Что видно, когда перестаёшь смотреть файл за файлом

Соседи, а не догадка

Навели на карточку — она и её связи остаются яркими, остальное приглушается. Это самый быстрый способ проверить гипотезу «этот файл никого не должен звать» без прыжка по десяти определениям.

Фокус на карточке: видно, кого она зовёт и кто зовёт её. Остальная паутина не мешает.
Фокус на карточке: видно, кого она зовёт и кто зовёт её. Остальная паутина не мешает.

Два режима фокуса.

Follow — кликнули узел, на экране остаются он и прямые соседи; клик по фону возвращает карту.

Lazy observation — все файлы на месте, рёбра появляются по клику. Редакторы на холсте прячутся вместе со своей карточкой: не остаётся «головы без тела».

Острова вместо одного клубка

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

В folder mode кластер — директория, у неё свой фон и шапка. В files mode кластер — сообщество по графу вызовов (Louvain): файлы, которые зовут друг друга, садятся рядом, даже если лежат в разных папках. Утилита на весь репозиторий перестаёт якорить центр вселенной.

Folder islands: файлы сгруппированы по директориям
Folder islands: файлы сгруппированы по директориям

Острова папок. Сразу видно, какой сервисный слой с кем разговаривает, а не только как названы директории.

Файл раскрывается до функций

Карточка по умолчанию свёрнута. Раскрыли — список функций этого файла, всё ещё на карте. Отсюда же: закрепить, спрятать файл, отрезать входящие или исходящие рёбра, свернуть папку.

Раскрытая карточка со списком функций
Раскрытая карточка со списком функций

Шесть функций в одном файле. Карта вокруг не пропадает — пропадает только иллюзия, что файл это атом.

Есть glob-фильтр, поиск, скрытие изолированных узлов. Если смотрите только src/engine/** и не хотите видеть тестовый шум — не надо строить отдельный граф, достаточно фильтра.

Killer feature: починил, не уйдя с карты

Визуализация, из которой нельзя править, быстро становится постером. В VS Code у карточки есть карандаш: поверх файла открывается CodeMirror 6. Редактор ездит вместе с камерой, сохранение — обычный ⌘/Ctrl+S в воркспейс. Можно открыть несколько файлов сразу. Грязный буфер синхронизируется с настоящим документом VS Code.

Редактор прямо на холсте. Zoom позволяет эффективно работать с множеством одновременно открытых файлов
Редактор прямо на холсте. Zoom позволяет эффективно работать с множеством одновременно открытых файлов

Тот же файл, что только что был карточкой: подсветка, сохранение в воркспейс, карта вокруг.

Редактор ездит вместе с камерой. Длинные строки переносятся внутри карточки, соседние файлы разъезжаются, когда вы открываете ещё один.

Новый подход: разработка островами, а не вкладками

Вкладки в редакторе — это очередь. Каждая новая оттесняет предыдущую. Через полчаса у вас открыты handler, сервис, типы, тест и случайный utils.ts, и вы уже не помните, какой из пяти index.ts относится к текущей задаче. Пространства нет: есть только «сейчас на экране» и «где-то в полоске сверху».

На холсте пространство есть. Вы собираете рядом файлы, которые сейчас составляют одну мысль: тот, что правите, и те, без которых логику не удержать. Не потому что они лежат в одной папке — потому что вы их сейчас думаете вместе. Карточки можно сдвинуть, закрепить, открыть редакторы. Получается рабочий стол на карте системы, а не стопка заголовков.

Таких островов может быть несколько. Слева — кусок онбординга, справа — платёжный флоу, где-то в стороне — баг в воркере. Вы не закрываете одно, чтобы открыть другое, и не держите двадцать вкладок «на всякий случай». Переехали камерой — другая часть системы, свой набор файлов, своя логика. Параллельная разработка без путаницы: контекст не в голове и не в именах табов, а в том, как вы разложили карту.

Позиции сохраняются. Завтра острова на тех же местах. Можно вернуться к задаче так же, как возвращаются к столу, а не как к поиску по Cmd+P.

Два рабочих острова: связанные файлы рядом, редакторы открыты, между задачами — пространство карты, а не полоска вкладок.
Два рабочих острова: связанные файлы рядом, редакторы открыты, между задачами — пространство карты, а не полоска вкладок.

Как это устроено

Три шага, один холст.

Парсер → граф → холст с редактором
Парсер → граф → холст с редактором

Парсер (typescript compiler API) обходит .ts / .tsx, собирает объявления, импорты, методы, реэкспорты, поля классов и резолвит вызовы в рёбра. Type-checker не гоняется: на больших деревьях это быстрее, и редактор остаётся живым.

Раскладка на тысячах карточек не может быть O(n²). Отталкивание — Barnes-Hut через quadtree, столкновения — равномерная сетка. Число итераций, угол Barnes-Hut, сила репульсии и гравитация масштабируются с числом карточек. После упаковки островов проходит AABB-проход, чтобы карточки не сидели друг на друге — в том числе когда вы открыли редактор и карточка внезапно выросла.

Хост VS Code не парсит на UI-потоке: worker, отмена, счётчик «parsing K/N files…». Тот же холст умеет крутиться без VS Code (npm run graph) — удобно отлаживать вьюер в браузере. В расширении и в standalone разметка одна, Pug-шаблон один.

Для кого это

Для тех, кто держит TypeScript-сервис или фронт на сотни файлов и уже не видит, кто кого зовёт. Для онбординга: показать форму системы за пять минут, а не за два дня вкладок. Для поиска хабов, висячих островов и странных рёбер через полрепозитория. И для тех, кто хочет поправить файл, не потеряв место на карте.

Где взять

Откройте свой проект, нажмите иконку, дождитесь графа. Если карта совпала с тем, как вы и так представляли систему — хорошо: значит, все в порядке. Если не совпала — ещё лучше: вы только что увидели, где голова и репозиторий разошлись.

2