Forge
markdown551a3611
1# Code actions и рефакторинги: что чему нужно
2
3В Roslyn MCP код-действия получаются через `roslyn_get_code_actions` и применяются через `roslyn_apply_code_action`. Контекст для рефакторингов — **документ** и **span** (диапазон в тексте). Часть рефакторингов дополнительно требует **опций** (имя, выбор членов и т.д.), которые в IDE задаются диалогом.
4
5## Что передаётся в контекст
6
7- **Позиция:** `file_path`, `line`, `column` (1-based) — одна точка в файле.
8- **Диапазон (опционально):** `end_line`, `end_column` — конец выделения. Если оба заданы, span считается от `(line, column)` до `(end_line, end_column)` включительно. Иначе span берётся как span **одного токена** в позиции.
9
10## Рефакторинги по типу требований
11
12### 1. Только позиция (или один токен)
13
14Достаточно `line` и `column`. Диапазон не обязателен.
15
16- **Rename**, **Go to definition** — отдельные тулы, не code actions.
17- **Add null check**, **Add parameter**, **Add brace**, **Remove unused variable**, **Simplify name**, **Inline variable**, **Introduce local** (для выражения) и т.п. — работают по позиции/токену.
18
19### 2. Нужен диапазон (выделение)
20
21Чтобы рефакторинг «видел» несколько строк или блок кода, нужно передать **end_line** и **end_column** (те же, что при `get_code_actions` — и при `apply_code_action`).
22
23- **Extract method** — выделенный фрагмент метода извлекается в новый метод.
24- **Extract local function** — выделенный фрагмент — в локальную функцию.
25- **Surround with** (try/catch, if, etc.) — выделенный текст оборачивается.
26
27Без диапазона эти действия либо не появятся в списке, либо будут работать только по одному токену (часто бесполезно).
28
29**Имя и параметры** при Extract method / Extract local function **задаёт сам Roslyn**: имя по умолчанию (NewMethod, NewLocalFunction и т.п.), а в параметры попадает всё, что выделенный код использует из внешнего скоупа. Часто после применения вручную переименовывают метод (например через `roslyn_rename`) и упрощают сигнатуру — убирают лишние параметры или переносят логику внутрь метода.
30
31### 3. Дополнительно нужны опции (имя, выбор членов)
32
33В IDE после выбора такого действия показывается диалог. При программном вызове опции нужно передать в `roslyn_apply_code_action`, иначе провайдер может выбросить или вести себя неочевидно.
34
35| Рефакторинг | Что нужно | Поддержка в MCP |
36|-------------|-----------|------------------|
37| **Introduce constant** | Имя константы | Параметр `constant_name`. Для части провайдеров срабатывает; иначе после apply можно переименовать через `roslyn_rename`. |
38| **Extract interface** | Имя интерфейса + **какие члены** (методы, свойства) включить | Опции — внутренний тип Roslyn (нет публичного API). В MCP можно попробовать передать `action_options` с полями, совпадающими с внутренними свойствами (см. ниже). |
39| **Extract base class** | Имя базового класса + **какие члены** поднять в базу + опция «сделать абстрактными» | Аналогично: внутренние опции Roslyn. Через `action_options` — по возможности. |
40| **Rename type to match file** | Имя файла/типа | Обычно одно значение; при наличии опций — через `action_options`. |
41| Другие с диалогом | Зависит от провайдера | Универсальный механизм: `action_options` (JSON object). Свойства объекта по имени подставляются в объект опций через рефлексию (строки и массивы строк). |
42
43Типы опций для Extract Interface / Extract Base Class в Roslyn **внутренние** (internal), поэтому точный контракт может меняться от версии к версии. В IDE диалог заполняет эти опции и вызывает `GetOperationsAsync(options, ct)`.
44
45## Параметр action_options
46
47В `roslyn_apply_code_action` можно передать **action_options** — JSON-объект. При применении действия с опциями (у которого `GetOperationsAsync(options, CancellationToken)` с двумя аргументами):
48
491. Создаётся экземпляр типа опций (рефлексия).
502. Если задан `constant_name`, подставляются свойства `Name` / `ConstantName`.
513. Для каждой пары ключ–значение из `action_options` ищется одноимённое свойство типа опций; если оно доступно для записи и тип совместим, значение присваивается. Поддерживаются строки и массивы строк (для списков имён членов и т.п.).
52
53Имена свойств у внутренних типов Roslyn можно выяснить только по исходникам или отладкой. Примеры возможных имён (могут отличаться): `InterfaceName`, `MemberNames`, `SelectedMemberNames`, `BaseClassName`, `MakeAbstract`. Если свойство ожидает не строку, а, например, `ImmutableArray<ISymbol>`, передача только строк может не сработать — тогда такой рефакторинг через MCP без доработки Roslyn или копирования внутренних типов поддерживается ограниченно.
54
55## Рекомендации
56
571. **Extract method / Extract local function:** всегда передавай **end_line** и **end_column** (выделенный диапазон) и в `get_code_actions`, и в `apply_code_action`.
582. **Introduce constant:** используй параметр **constant_name**.
593. **Extract interface / Extract base class:** передавай те же **line/column** (или диапазон по классу), затем при apply — **action_options** с ожидаемыми полями (имя типа, массив имён членов и т.д.), если удаётся подобрать рабочий набор свойств для твоей версии Roslyn.
604. Для остальных рефакторингов с диалогом — пробуй **action_options** по аналогии; при ошибках смотри сообщение и при необходимости проверяй тип опций в отладчике.
61
62## Стиль кода (.editorconfig)
63
64Обходные тулы, генерирующие код, читают **.editorconfig** от каталога файла вверх по дереву (ближайший переопределяет). Учитываются в `[*.cs]`: `dotnet_style_predefined_type_for_*` (типы), `indent_style`/`indent_size`/`tab_width` (отступ при вставке), `csharp_style_var_*`, `csharp_prefer_braces`, `csharp_new_line_before_open_brace`. Generate constructor не включает backing-поля, если есть свойство с тем же именем.
65
66**Выдача roslyn_get_diagnostics** учитывает .editorconfig целевого решения: для каждого проекта читается .editorconfig от каталога .csproj вверх; диагностики с `dotnet_diagnostic.<Id>.severity = none` не включаются в вывод; диагностики уровня Hidden от анализаторов тоже отфильтровываются — мусора меньше.
67
68**В репозитории roslyn-mcp** в корне лежит свой `.editorconfig`: стиль и **severities** анализаторов (IDE0049, IDE0055, naming, CS0219 и др. — suggestion/warning/none). Сборка с `EnforceCodeStyleInBuild` и `AnalysisLevel` применяет эти правила.
69
70## Обходные тулы (без диалогов)
71
72Рефакторинги вроде Extract Interface / Extract Base Class в Roslyn требуют диалога (выбор членов, имя). MCP-сервер не показывает окна, поэтому добавлены **отдельные тулы**, которые делают то же по параметрам:
73
74- **`roslyn_generate_interface_from_class`** — по позиции на класс генерирует C# интерфейс (все public методы/свойства/события или только из `member_names`), записывает в файл или возвращает текст. Дальше: добавить классу `: IName` и применить code action «Implement interface» (диалога там нет).
75- **`roslyn_generate_base_class_from_class`** — по позиции на класс генерирует абстрактный базовый класс (выбранные public-члены становятся `protected abstract`). Опционально: `base_class_name`, `output_file_path`, `member_names`. Дальше: добавить классу `: BaseName` и проставить `override` у членов.
76- **`roslyn_generate_overrides`** — Generate Overrides без диалога: по позиции на класс собирает виртуальные/абстрактные члены базового типа (ещё не переопределённые), генерирует override-заглушки. Опционально: `member_names`, `insert_into_file` (вставить в тело класса перед `}`).
77- **`roslyn_generate_constructor_from_members`** — Generate constructor from members без диалога: по позиции на класс собирает instance поля и свойства с setter, генерирует конструктор с параметрами и присвоениями. Опционально: `member_names`, `insert_into_file`.
78- **`roslyn_generate_equals_gethashcode`** — Generate Equals and GetHashCode без диалога: по позиции на класс собирает instance поля и свойства, генерирует override Equals(object?) и GetHashCode() (HashCode.Combine). Опционально: `member_names`, `insert_into_file`.
79- **`roslyn_move_members_to_partial_file`** — перенос выбранных членов класса/структуры в новый файл как вторую часть `partial` (те же using/namespace стиль, что в исходнике). Параметры: позиция на тип, `member_names`, `output_file_path`, `apply`. По умолчанию **`add_dependent_upon: true`**: после успешного `apply` в `.csproj` добавляется `Compile Update` с `DependentUpon` на исходный файл (как в Solution Explorer / CascadeIDE). Отключить: `add_dependent_upon: false`.
80- **`roslyn_sync_dependent_upon_partials`** — массовая простановка `DependentUpon` для уже существующих файлов вида `Stem.Rest.cs` → родительский `Stem.cs` в той же папке (эвристика по префиксу имени). Удобно для унаследованного кода. Параметры: `solution_or_project_path`, опционально `project_path`, `dry_run` (как у `roslyn_sync_namespaces`).
View only · write via MCP/CIDE