| 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 | |
| 49 | 1. Создаётся экземпляр типа опций (рефлексия). |
| 50 | 2. Если задан `constant_name`, подставляются свойства `Name` / `ConstantName`. |
| 51 | 3. Для каждой пары ключ–значение из `action_options` ищется одноимённое свойство типа опций; если оно доступно для записи и тип совместим, значение присваивается. Поддерживаются строки и массивы строк (для списков имён членов и т.п.). |
| 52 | |
| 53 | Имена свойств у внутренних типов Roslyn можно выяснить только по исходникам или отладкой. Примеры возможных имён (могут отличаться): `InterfaceName`, `MemberNames`, `SelectedMemberNames`, `BaseClassName`, `MakeAbstract`. Если свойство ожидает не строку, а, например, `ImmutableArray<ISymbol>`, передача только строк может не сработать — тогда такой рефакторинг через MCP без доработки Roslyn или копирования внутренних типов поддерживается ограниченно. |
| 54 | |
| 55 | ## Рекомендации |
| 56 | |
| 57 | 1. **Extract method / Extract local function:** всегда передавай **end_line** и **end_column** (выделенный диапазон) и в `get_code_actions`, и в `apply_code_action`. |
| 58 | 2. **Introduce constant:** используй параметр **constant_name**. |
| 59 | 3. **Extract interface / Extract base class:** передавай те же **line/column** (или диапазон по классу), затем при apply — **action_options** с ожидаемыми полями (имя типа, массив имён членов и т.д.), если удаётся подобрать рабочий набор свойств для твоей версии Roslyn. |
| 60 | 4. Для остальных рефакторингов с диалогом — пробуй **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`). |