Gopls: Возможности преобразования кода
В данном документе описаны возможности gopls по преобразованию кода, включая диапазон изменений, сохраняющих поведение (рефакторинг, форматирование, упрощения), исправления кода (фиксы), а также поддержку редактирования (заполнение литералов структур и инструкций switch).
Преобразования кода не являются одной категорией в LSP:
- Некоторые, такие как Formatting (форматирование) и Rename (переименование), являются основными операциями в протоколе.
- Некоторые преобразования доступны через Code Lenses, которые возвращают команды — произвольные операции сервера, вызываемые по их побочным эффектам через запрос
workspace/executeCommand; однако, на данный момент ни одна из существующих code lenses не является преобразованием синтаксиса Go. - Большинство преобразований определяются как code actions.
Code Actions
Code action — это действие, связанное с определённой частью файла.
Каждый раз, когда изменяется выделение, типичный клиент делает запрос textDocument/codeAction
для получения набора доступных действий, а затем обновляет элементы пользовательского интерфейса (меню, значки, всплывающие подсказки), чтобы отразить их.
Руководство VS Code описывает code actions как
“Quick fixes + Refactorings”.
Запрос codeAction предоставляет меню, так сказать, но не заказывает еду. После того как пользователь выбирает действие, происходит одно из двух.
В тривиальных случаях действие само содержит правку, которую клиент может напрямую применить к файлу.
Но в большинстве случаев действие содержит команду,
аналогичную команде, связанной с code lens.
Это позволяет вычислять патч лениво, только тогда, когда он действительно нужен. (Большинство не нуждаются.)
Затем сервер может вычислить правку и отправить клиенту запрос workspace/applyEdit
для внесения изменений в файлы.
Не все команды code actions имеют побочный эффект applyEdit: некоторые могут изменять состояние сервера, например, переключать переменную или заставлять сервер отправлять другие запросы клиенту,
например, запрос showDocument для открытия отчета в веб-браузере.
Основное различие между code lenses и code actions заключается в следующем:
- запрос
codeLensполучает команды для всего файла. Каждая команда указывает свой диапазон исходного кода, и обычно отображается как аннотация в этом диапазоне. - запрос
codeActionполучает команды только для определенного диапазона: текущего выделения. Все команды представлены вместе в меню в этой позиции.
Каждое действие имеет тип,
который представляет собой иерархический идентификатор, такой как refactor.inline.call.
Клиенты могут фильтровать действия на основе их типа.
Например, VS Code имеет:
два меню, «Refactor…» и «Source action…», каждое из которых заполняется
разными типами кодовых действий (refactor и source);
иконку лампочки, которая вызывает меню «quick fixes» (типа quickfix);
и команду «Fix All», которая выполняет все кодовые действия типа
source.fixAll, которые считаются однозначно безопасными для применения.
Gopls поддерживает следующие кодовые действия:
quickfix, которые применяют однозначно безопасные исправленияsource.organizeImportssource.assemblysource.docsource.freesymbolssource.test(не документировано)source.addTestsource.toggleCompilerOptDetailsgopls.doc.features, которое открывает индекс функций gopls в браузереrefactor.extract.constantrefactor.extract.functionrefactor.extract.methodrefactor.extract.toNewFilerefactor.extract.variablerefactor.extract.variable-allrefactor.inline.callrefactor.inline.variablerefactor.rewrite.addTagsrefactor.rewrite.changeQuoterefactor.rewrite.fillStructrefactor.rewrite.fillSwitchrefactor.rewrite.invertIfrefactor.rewrite.joinLinesrefactor.rewrite.moveParamLeftrefactor.rewrite.moveParamRightrefactor.rewrite.removeTagsrefactor.rewrite.removeUnusedParamrefactor.rewrite.splitLines
Gopls сообщает о некоторых действиях с кодом дважды, с двумя разными типами, чтобы они отображались в нескольких элементах пользовательского интерфейса: упрощения, например, из for _ = range m в for range m, имеют типы quickfix и source.fixAll, поэтому они появляются в меню «Быстрое исправление» и активируются командой «Исправить всё».
Многие преобразования вычисляются с помощью анализаторов, которые, в процессе сообщения диагностики о проблеме, также предлагают исправление.
Запрос codeActions вернёт любые исправления, сопровождающие диагностику для текущего выделения.
Особенности:
-
Многие преобразования кода в gopls ограничены представлением синтаксического дерева Go, которое в настоящее время хранит комментарии вне дерева, а в сторонней таблице; следовательно, преобразования, такие как Extract и Inline, склонны терять комментарии. Это проблема https://go.dev/issue/20744, и мы приоритизируем её решение в 2024 году.
-
Сгенерированные файлы, определённые по стандартному комментарию DO NOT EDIT, не предлагают действия с кодом для преобразований.
Поддержка действий с кодом в клиентах:
- VS Code: В зависимости от их типа, действия с кодом находятся в меню «Рефакторинг…» (
^⇧R), в меню «Действие исходного кода…», в меню значка 💡 (лампочки) или в меню «Быстрое исправление» (⌘.). Команда «Исправить всё» применяет все действия типаsource.fixAll. - Emacs + eglot: Действия с кодом невидимы.
Используйте
M-x eglot-code-actions, чтобы выбрать одно из доступных (если их несколько) и выполнить его. Некоторые типы действий имеют ярлыки фильтрации, например,M-x eglot-code-action-{inline,extract,rewrite}. - CLI:
gopls codeaction -exec -kind k,... -diff file.go:#123-#456выполняет действия с кодом указанных типов (например,refactor.inline) на выбранном диапазоне, заданном с использованием смещений в байтах с нулевой базой, и отображает diff.
Форматирование
Запрос LSP
textDocument/formatting
возвращает правки, которые форматируют файл.
Gopls применяет канонический алгоритм форматирования Go,
go fmt.
Параметры форматирования LSP игнорируются.
Большинство клиентов настроены на форматирование файлов и организацию импортов при каждом сохранении файла.
Настройки:
- Настройка
gofumptзаставляет gopls использовать альтернативный форматтер,github.com/mvdan/gofumpt.
Поддержка клиентами:
- VS Code: Форматирует при сохранении по умолчанию. Используйте пункт меню
Format document(⌥⇧F) для ручного вызова. - Emacs + eglot: Используйте
M-x eglot-format-bufferдля форматирования. Привяжите его кbefore-save-hook, чтобы форматировать при сохранении. Для форматирования в сочетании с организацией импортов многие пользователи используют устаревший подход, устанавливая"goimports"какgofmt-commandс помощью go-mode, и добавляяgofmt-before-saveвbefore-save-hook. Решение на основе LSP требует кода, такого как https://github.com/joaotavora/eglot/discussions/1409. - CLI:
gopls format file.go
source.organizeImports: Организация импортов
Запрос codeActions в файле, где импорты не организованы,
возвращает действие стандартного типа source.organizeImports.
Его команда выполняет организацию импортов:
удаляет существующие импорты, которые являются дубликатами или неиспользуемыми,
добавляет новые для неопределённых символов,
и сортирует их в обычном порядке.
Добавление новых импортов основано на эвристике, зависящей от вашего рабочего пространства и содержимого каталога GOMODCACHE; они могут иногда делать неожиданный выбор.
Многие редакторы автоматически организуют импорты и форматируют код перед сохранением любого отредактированного файла.
Некоторые пользователи не любят автоматическое удаление неиспользуемых импортов, потому что, например, единственная строка, ссылающаяся на импорт, временно закомментирована для отладки; см. https://go.dev/issue/54362.
Настройки:
- Настройка
local— это список префиксов путей импортов, которые являются «локальными» для текущего файла и должны отображаться после стандартных и сторонних пакетов в порядке сортировки.
Поддержка клиентов:
- VS Code: автоматически вызывает
source.organizeImportsперед сохранением. Чтобы отключить это поведение, используйте фрагмент ниже и вручную вызывайте команду «Organize Imports» по мере необходимости.<code>"[go]": { "editor.codeActionsOnSave": { "source.organizeImports": false } } </code> - Emacs + eglot: Используйте
M-x eglot-code-action-organize-importsдля ручного вызова. Многие пользователи go-mode используют следующие строки для организации импортов и форматирования каждого изменённого файла перед сохранением, но этот подход основан на устаревшемgoimportsинструменте, а не на gopls:<code class="language-lisp">(setq gofmt-command "goimports") (add-hook 'before-save-hook 'gofmt-before-save) </code>
- CLI:
gopls fix -a file.go:#offset source.organizeImports
source.addTest: Добавить тест для функции или метода
Если выделенный фрагмент кода является частью объявления функции или метода F,
gopls предложит код-действие «Add test for F», которое добавляет новый тест для
выбранной функции в соответствующий файл _test.go. Сгенерированный тест учитывает
её сигнатуру, включая входные параметры и результаты.
Тестовый файл: если файл _test.go не существует, gopls создаёт его, используя
имя текущего файла (a.go → a_test.go), копируя любые комментарии с copyright и
ограничениями сборки из оригинального файла.
Тестовый пакет: для новых файлов, которые тестируют код в пакете p, тестовый файл
использует имя пакета p_test, если это возможно, чтобы поощрить тестирование только экспортируемых
функций. (Если тестовый файл уже существует, новый тест добавляется в этот файл.)
Параметры: каждый из непустых параметров функции становится элементом структуры,
используемой для таблицы тестов. (Для каждого пустого параметра _ значение не имеет эффекта,
поэтому тест предоставляет аргумент с нулевым значением.)
Контексты: если первый параметр — context.Context, тест передаёт
context.Background().
Результаты: результаты функции присваиваются переменным (got, got2,
и так далее) и сравниваются с ожидаемыми значениями (want, want2, и т.д.)
определёнными в структуре тестового случая. Пользователь должен отредактировать логику для выполнения соответствующего сравнения. Если финальный результат — error, тестовый случай определяет булеву переменную wantErr.
Получатели методов: при тестировании метода T.F или (*T).F, тест должен
создать экземпляр T для передачи в качестве получателя. Gopls ищет в пакете подходящую функцию,
которая создаёт значение типа T или *T, возможно с ошибкой, предпочитая функцию с именем NewT.
Импорты: Gopls добавляет недостающие импорты в тестовый файл, используя последний соответствующий спецификатор импорта из оригинального файла. Он избегает дублирования импортов, сохраняя все существующие импорты в тестовом файле.
Переименование
Запрос LSP
textDocument/rename
переименовывает символ.
Переименование происходит в два этапа. Первый этап — это
prepareRename запрос, который возвращает текущее имя идентификатора под курсором (если он действительно существует).
Клиент затем отображает диалог, предлагающий пользователю выбрать новое имя, отредактировав старое. Второй этап — это сам процесс rename, который применяет изменения. (Поддержка простого диалога уникальна среди операций рефакторинга в LSP; см. microsoft/language-server-protocol#1164.)
Алгоритм переименования в Gopls очень тщательно отслеживает ситуации, при которых переименование может привести к ошибке компиляции. Например, изменение имени может привести к тому, что символ станет «затенённым», и некоторые существующие ссылки перестанут быть доступны. Gopls сообщит об ошибке, указав пару символов и затенённую ссылку:
В качестве другого примера рассмотрим переименование метода конкретного типа. Переименование может привести к тому, что тип больше не будет удовлетворять тем же интерфейсам, что и раньше, что может вызвать ошибку компиляции программы. Чтобы избежать этого, gopls проверяет каждое преобразование (явное или неявное) от затронутого типа к типу интерфейса и проверяет, останется ли оно корректным после переименования. Если нет, переименование прерывается с ошибкой.
Если вы намерены переименовать исходный метод и соответствующие методы любых совпадающих типов интерфейсов (а также методы типов, совпадающих с ними), вы можете указать это, вызвав операцию переименования на методе интерфейса.
Аналогично, gopls сообщит об ошибке, если вы переименуете поле структуры, которое является «анонимным» полем, встраиваемым в тип, поскольку это потребовало бы более широкого переименования, включающего и сам тип. Если это именно то, что вы хотите, вы можете снова указать это, вызвав операцию переименования на типе.
Переименование не должно вводить ошибку компиляции, но может привести к динамическим ошибкам. Например, при переименовании метода, если нет прямого преобразования затронутого типа в тип интерфейса, но есть промежуточное преобразование в более широкий тип (например, any), за которым следует утверждение типа к типу интерфейса, то gopls может продолжить переименование метода, что приведёт к сбою утверждения типа во время выполнения.
Подобные проблемы могут возникнуть в пакетах, использующих рефлексию, таких как encoding/json или text/template. Нет замены хорошему здравому смыслу и тестированию.
Особые случаи:
-
При переименовании объявления получателя метода, инструмент также пытается переименовать получателей всех других методов, связанных с тем же именованным типом. Каждый другой получатель, который не может быть полностью переименован, просто пропускается. Переименование любого использования получателя влияет только на эту переменную.
<code class="language-go">type Counter struct { x int } Переименуйте здесь, чтобы повлиять только на этот метод ↓ func (c *Counter) Inc() { c.x++ } func (c *Counter) Dec() { c.x++ } ↑ Переименуйте здесь, чтобы повлиять на все методы </code> -
Переименование объявления пакета дополнительно приводит к переименованию директории пакета.
Некоторые советы для достижения наилучших результатов:
- Проверки безопасности, выполняемые алгоритмом Переименования, требуют информации о типах. Если программа сильно испорчена, может быть недостаточно информации для его выполнения (https://go.dev/issue/41870), и переименование обычно не может использоваться для исправления ошибки типа (https://go.dev/issue/41851). При рефакторинге рекомендуется работать небольшими шагами, исправляя любые проблемы по мере продвижения, чтобы как можно больше кода компилировалось на каждом этапе.
- Иногда может потребоваться изменить структуру ссылок в программе в ходе операции переименования, например, намеренно объединить две переменные x и y, переименовав y в x. Инструмент переименования слишком строг для решения этой задачи (https://go.dev/issue/41852).
Для получения подробностей о работе алгоритма переименования gopls, вы можете обратиться ко второй части выступления на конференции GothamGo 2015: Using go/types for Code Comprehension and Refactoring Tools.
Поддержка клиентов:
- VS Code: Используйте пункт меню “Переименовать символ” (
F2). - Emacs + eglot: Используйте
M-x eglot-rename, илиM-x go-renameиз go-mode. - Vim + coc.nvim: Используйте команду
coc-rename. - CLI:
gopls rename file.go:#offset newname
refactor.extract: Извлечение функции/метода/переменной
Семейство действий с кодом refactor.extract возвращает команды, которые
заменяют выбранные выражения или инструкции ссылкой на новое объявление,
содержащее выбранный код:
-
refactor.extract.functionзаменяет одно или несколько полных инструкций вызовом новой функции с именемnewFunction, тело которой содержит эти инструкции. Выделение должно охватывать меньше инструкций, чем вся функция.

-
refactor.extract.method— вариант операции «Извлечение функции», доступный при выделении инструкций, принадлежащих методу. Новая функция будет методом того же типа получателя. -
refactor.extract.variableзаменяет выражение ссылкой на новую локальную переменную с именемnewVar, инициализированную этим выражением:

-
refactor.extract.constantвыполняет ту же операцию для константного выражения, вводя локальное объявление константы. -
refactor.extract.variable-allзаменяет все вхождения выбранного выражения в функции ссылкой на новую локальную переменную с именемnewVar. Эта операция извлекает выражение один раз и повторно использует его везде, где оно встречается в функции.

refactor.extract.constant-allвыполняет ту же операцию для константного выражения, вводя локальное объявление константы. Если имя по умолчанию для нового объявления уже используется,goplsгенерирует новое имя.
Извлечение — это сложная задача, требующая учета области видимости искажений (shadowing), управления потоком выполнения, например break/continue в цикле или return в функции, количества переменных и даже тонких аспектов стиля.
В каждом случае инструмент будет пытаться обновить извлеченные инструкции, чтобы избежать нарушения сборки или изменения поведения.
К сожалению, алгоритмы Extract в gopls значительно менее строгие, чем операции Переименование и Встраивание, и мы знаем о нескольких случаях, где они не справляются, включая:
- https://github.com/golang/go/issues/66289
- https://github.com/golang/go/issues/65944
- https://github.com/golang/go/issues/63394
- https://github.com/golang/go/issues/61496
Следующие функции Extract запланированы на 2024 год, но пока не поддерживаются:
- Extract parameter struct заменит два или более параметра функции структурой с одним полем для каждого параметра; см. https://go.dev/issue/65552.
- Extract interface for type создаст объявление типа интерфейса со всеми методами выбранного конкретного типа; см. https://go.dev/issue/65721 и https://go.dev/issue/46665.
refactor.extract.toNewFile: Извлечение объявлений в новый файл
(Доступно начиная с gopls/v0.17.0)
Если вы выберете одно или несколько объявлений на верхнем уровне, gopls предложит
действие «Извлечь объявления в новый файл», которое переместит выбранные
объявления в новый файл, имя которого основано на первом объявленном символе.
Объявления импортов создаются по мере необходимости.
Gopls также предлагает это действие, когда выделение состоит только из
первого токена объявления, например func или type.

refactor.inline.call: Встраивание вызова функции
Для запроса codeActions, где выделение (или находится внутри) вызова
функции или метода, gopls вернёт команду типа
refactor.inline.call, действие которой — встроить вызов функции.
Скриншоты ниже показывают вызов sum до и после встраивания:

Встраивание заменяет выражение вызова копией тела функции,
где параметры заменяются аргументами.
Встраивание полезно по ряду причин.
Возможно, вы хотите устранить вызов устаревшей
функции, такой как ioutil.ReadFile, заменив её вызовом
более новой os.ReadFile; встраивание сделает это за вас.
Или, возможно, вы хотите скопировать и изменить существующую функцию каким-либо образом;
встраивание может стать отправной точкой.
Логика встраивания также предоставляет строительный блок для
других рефакторингов, таких как «изменение сигнатуры».
Не каждая функция может быть встроена (inlined). Конечно, инструменту нужно знать, какая функция вызывается, поэтому нельзя встроить динамический вызов через значение функции или метод интерфейса; однако статические вызовы методов допустимы. Также нельзя встроить вызов, если вызываемая функция объявлена в другом пакете и ссылается на неэкспортируемые части этого пакета или на внутренние пакеты, недоступные для вызывающего. Вызовы дженерик-функций пока не поддерживаются (https://go.dev/issue/63352), хотя планируется это исправить.
Когда встраивание возможно, очень важно, чтобы инструмент сохранял исходное поведение программы. Мы не хотим, чтобы рефакторинг ломал сборку, или ещё хуже — вводил скрытые ошибки. Это особенно важно, когда инструменты встраивания используются для автоматической очистки больших кодовых баз; мы должны иметь возможность доверять этим инструментам. Наш инлайнер очень осторожен и не делает предположений или небезопасных допущений о поведении кода. Однако это означает, что он иногда производит изменения, которые отличаются от того, что кто-то с экспертными знаниями того же кода мог бы написать вручную.
В самых сложных случаях, особенно при сложном управлении потоком выполнения, может быть небезопасно вообще устранять вызов функции. Например, поведение инструкции defer тесно связано с вызовом её содержащей функции, и defer — единственная конструкция управления, которая может использоваться для обработки паник, поэтому она не может быть сведена к более простым конструкциям. Таким образом, например, если определена функция f следующим образом:
<code class="language-go">func f(s string) {
defer fmt.Println("goodbye")
fmt.Println(s)
}
</code>
то вызов f("hello") будет встроен в:
<code class="language-go"> func() {
defer fmt.Println("goodbye")
fmt.Println("hello")
}()
</code>
Хотя параметр был устранён, вызов функции остаётся.
Инлайнер похож на оптимизирующий компилятор. Компилятор считается «корректным», если он не изменяет смысл программы при переводе из исходного языка в целевой. Оптимизирующий компилятор использует особенности входных данных для генерации более эффективного кода, где «более эффективный» обычно означает более производительный. По мере того как пользователи сообщают о случаях, при которых компилятор генерирует неоптимальный код, компилятор улучшается, чтобы распознавать больше случаев, больше правил и больше исключений из правил — но этот процесс не имеет конца. Встраивание похоже на это, но «лучший» код означает более чистый и лаконичный. Наиболее консервативное преобразование обеспечивает простую, но (надеемся) корректную основу, на которой можно накладывать бесконечное количество правил и исключений, чтобы улучшить качество вывода.
Вот некоторые технические сложности, связанные с безопасным встраиванием:
-
Эффекты: При замене параметра выражением аргумента необходимо быть осторожным, чтобы не изменить эффекты вызова. Например, если вызвать функцию
func twice(x int) int { return x + x }сtwice(g()), мы не хотим видетьg() + g(), что приведёт к двукратному выполнению эффектов функции g и потенциально каждая функция может вернуть разное значение. Все эффекты должны происходить одинаковое количество раз и в том же порядке. Это требует анализа как аргументов, так и вызываемой функции, чтобы определить, являются ли они «чистыми», читают ли переменные или (и когда) обновляют их. Инлайнер введёт объявление, такое какvar x int = g(), если он не может доказать, что безопасно заменить аргумент повсюду. -
Константы: Если встраивание всегда заменяет параметр его аргументом, когда значение является константой, некоторые программы больше не будут собираться, потому что проверки, ранее выполнявшиеся во время выполнения, будут происходить на этапе компиляции. Например,
func index(s string, i int) byte { return s[i] }— это корректная функция, но если встраивание заменит вызовindex("abc", 3)выражением"abc"[3], компилятор сообщит, что индекс3вне диапазона строки"abc". Инлайнер предотвратит замену параметров проблемными константными аргументами, снова введя объявлениеvar. -
Ссылочная целостность: Когда переменная параметра заменяется выражением аргумента, необходимо убедиться, что любые имена в выражении аргумента продолжают ссылаться на одно и то же — а не на другое объявление в теле вызываемой функции, которое совпадает по имени. Инлайнер должен заменять локальные ссылки, такие как
Printf, на квалифицированные ссылки, такие какfmt.Printf, и при необходимости добавлять импорт пакетаfmt. -
Неявные преобразования: При передаче аргумента функции он неявно преобразуется к типу параметра. Если мы устраняем переменную параметра, мы не хотим потерять преобразование, поскольку оно может быть важно. Например, в
func f(x any) { y := x; fmt.Printf("%T", &y) }тип переменной y —any, поэтому программа выводит"*interface{}". Но если встраивание вызоваf(1)приведёт к инструкцииy := 1, тип y изменится наint, что может вызвать ошибку компиляции или, как в данном случае, баг, поскольку программа теперь выводит"*int". Когда инлайнер заменяет переменную параметра значением аргумента, он может потребовать введения явных преобразований каждого значения к исходному типу параметра, напримерy := any(1). -
Последняя ссылка: Когда выражение аргумента не имеет эффектов, а соответствующий параметр никогда не используется, выражение может быть удалено. Однако, если выражение содержит последнюю ссылку на локальную переменную в вызывающем коде, это может вызвать ошибку компиляции, поскольку переменная теперь становится неиспользуемой. Поэтому инлайнер должен быть осторожным при удалении ссылок на локальные переменные.
Это всего лишь небольшой взгляд на проблемную область. Если интересно, документация для golang.org/x/tools/internal/refactor/inline содержит больше подробностей. Всё это говорит о том, что это сложная задача, и мы стремимся к правильности в первую очередь. Мы уже реализовали целый ряд важных «оптимизаций упорядоченности» и ожидаем, что их будет ещё больше.
refactor.inline.variable: Встроить локальную переменную
Для запроса codeActions, где выделение (или находится внутри) идентификатора, который является использованием локальной переменной, объявление которой имеет выражение инициализации, gopls вернёт код-действие типа refactor.inline.variable, действие которого заключается во встраивании переменной: то есть замене ссылки на выражение инициализации переменной.
Например, если вызвать это действие на идентификаторе s в вызове println(s):
<code class="language-go">func f(x int) {
s := fmt.Sprintf("+%d", x)
println(s)
}
</code>
код-действие преобразует код следующим образом:
<code class="language-go">func f(x int) {
s := fmt.Sprintf("+%d", x)
println(fmt.Sprintf("+%d", x))
}
</code>
(В этом случае s становится неиспользуемой переменной, которую вам нужно будет удалить.)
Код-действие всегда заменяет ссылку выражением инициализации, даже если позже происходит присваивание переменной (например, s = "").
Код-действие сообщает об ошибке, если невозможно выполнить преобразование, потому что один из идентификаторов внутри выражения инициализации (например, x в приведённом выше примере) скрыт объявлениями между ними, как в следующем примере:
<code class="language-go">func f(x int) {
s := fmt.Sprintf("+%d", x)
{
x := 123
println(s, x) // error: cannot replace s with fmt.Sprintf(...) since x is shadowed
}
}
</code>
refactor.rewrite: Различные перезаписи
В этом разделе рассматриваются различные преобразования, доступные как код-действия, типы которых являются дочерними по отношению к refactor.rewrite.
refactor.rewrite.removeUnusedParam: Удалить неиспользуемый параметр
анализатор unusedparams сообщает диагностику для каждого параметра, который не используется внутри тела функции. Например:
<code class="language-go">func f(x, y int) { // "unused parameter: x"
fmt.Println(y)
}
</code>
Он не сообщает диагностику для функций, адрес которых был взят, поскольку они могут требовать всех своих параметров, даже неиспользуемых, чтобы соответствовать конкретной сигнатуре функции.
Также он не сообщает диагностику для экспортированных функций, которые могут быть использованы из другого пакета.
(Функция считается имеющей взятый адрес, если она используется не в позиции вызова, f(...).)
Помимо диагностики, предлагается два возможных исправления:
- переименовать параметр в
_, чтобы подчеркнуть, что он не используется (непосредственное редактирование); или - полностью удалить параметр с помощью команды
ChangeSignature, обновив все вызывающие функции.
Исправление №2 использует ту же самую инфраструктуру, что и «Inline function call» (см. выше), чтобы гарантировать сохранение поведения всех существующих вызовов, даже если выражение аргумента для удалённого параметра имеет побочные эффекты, как показано в примере ниже.

Обратите внимание, что в первом вызове аргумент chargeCreditCard() не был удалён из-за потенциальных побочных эффектов, тогда как во втором вызове аргумент 2, являющийся константой, был безопасно удалён.
refactor.rewrite.moveParam{Left,Right}: Перемещение параметров функции
Когда выделен параметр в сигнатуре функции или метода, gopls предлагает код-действие для перемещения параметра влево или вправо (если это возможно), при этом автоматически обновляются все вызывающие функции.
Например:
<code class="language-go">func Foo(x, y int) int {
return x + y
}
func _() {
_ = Foo(0, 1)
}
</code>
превращается в
<code class="language-go">func Foo(y, x int) int {
return x + y
}
func _() {
_ = Foo(1, 0)
}
</code>
после запроса на перемещение x вправо или y влево.
Это простой строительный блок более обобщённых операций «Change signature» (изменение сигнатуры). Мы планируем расширить его до произвольной перезаписи сигнатуры, но протокол сервера языка в настоящее время не предоставляет хорошей поддержки для ввода данных пользователем в рамках операций рефакторинга (см.
microsoft/language-server-protocol#1164).
Поэтому любая такая операция рефакторинга потребует пользовательской клиентской логики. (Как очень хакерское решение, можно выразить произвольное перемещение параметров, вызвав Rename на ключевом слове func объявления функции, но этот интерфейс — лишь временное решение.)
refactor.rewrite.changeQuote: Преобразование строкового литерала между необработанным и интерпретированным форматами
Когда выделен строковой литерал, gopls предлагает код-действие для преобразования строки между необработанной формой (`abc`) и интерпретированной формой ("abc"), когда это возможно:

Применение действия с кодом во второй раз возвращает исходную форму.
refactor.rewrite.invertIf: Инвертировать условие 'if'
Если выделение находится внутри инструкции if/else, которая не сопровождается else if, то gopls предлагает действие с кодом для инвертирования инструкции, отрицая условие и меняя местами блоки if и else.

refactor.rewrite.{split,join}Lines: Разбить элементы по отдельным строкам
Если выделение находится внутри списка элементов в скобках, такого как:
- элементы составного литерала,
[]T{a, b, c}, - аргументы вызова функции,
f(a, b, c), - группы параметров сигнатуры функции,
func(a, b, c int, d, e bool), или - группы результатов,
func() (x, y string, z rune),
то gopls предлагает действие с кодом “Разбить [элементы] по отдельным строкам”, которое преобразует приведённые выше формы в следующие:
<code class="language-go">[]T{
a,
b,
c,
}
f(
a,
b,
c,
)
func(
a, b, c int,
d, e bool,
)
func() (
x, y string,
z rune,
)
</code>
Обратите внимание, что в последних двух случаях каждая группа параметров или результатов рассматривается как один элемент.
Противоположное действие с кодом, “Объединить [элементы] в одну строку”, отменяет операцию. Ни одно из действий не предлагается, если список уже полностью разбит или объединён, либо является тривиальным (менее двух элементов).
Эти действия с кодом не предлагаются для списков, содержащих комментарии в стиле //, которые простираются до конца строки.
refactor.rewrite.fillStruct: Заполнить литерал структуры
Если курсор находится внутри литерала структуры S{}, то gopls предлагает действие с кодом “Заполнить S”, которое заполняет каждый отсутствующий поле литерала, доступный для записи.
Оно использует следующий эвристический подход для выбора значения, назначенного каждому полю: находит кандидатов в переменные, константы и функции, которым можно назначить поле, и выбирает тот, чьё имя наиболее близко к имени поля.
Если таких нет, то используется нулевое значение (например, 0, "" или nil) типа поля.
В приведённом ниже примере литерал структуры
slog.HandlerOptions
заполняется с использованием двух локальных переменных (level и add) и функции (replace):

Особенности:
- Это действие требует информации о типе структуры, поэтому если она определена в другом пакете, который ещё не импортирован, может потребоваться сначала «organize imports» (например, сохранить файл).
- Кандидаты для заполнения ищутся только в текущем файле и только выше текущей позиции. Символы, объявленные ниже текущей позиции, или в других файлах пакета, не рассматриваются; см. https://go.dev/issue/68224.
refactor.rewrite.fillSwitch: Fill switch
Когда курсор находится внутри инструкции switch, тип операнда которой является
перечислением (конечным набором именованных констант), или внутри инструкции типа switch,
gopls предлагает действие «Add cases for T» (Добавить случаи для T), которое заполняет инструкцию switch
путём добавления случая для каждой доступной именованной константы типа перечисления, или, для инструкции типа switch,
добавления случая для каждого доступного именованного не-интерфейсного типа, реализующего интерфейс.
Добавляются только отсутствующие случаи.
Снимки экрана ниже показывают инструкцию типа switch, операнд которой имеет тип интерфейса
net.Addr. Действие добавляет один случай для каждого конкретного типа сетевого адреса, а также
стандартный случай, вызывающий panic с информативным сообщением, если встречается неожиданный операнд.

А эти снимки экрана иллюстрируют действие, которое добавляет случаи для каждого значения
типа перечисления
html.TokenType,
представляющего различные типы токенов, из которых состоят HTML-документы:

refactor.rewrite.eliminateDotImport: Eliminate dot import
Когда курсор находится на точечном импорте, gopls может предложить действие «Eliminate dot import» (Удалить точечный импорт), которое удаляет точку из импорта и добавляет квалификацию использования пакета во всем файле. Это действие предлагается только в том случае, если каждое использование пакета может быть квалифицировано без конфликтов с существующими именами.
refactor.rewrite.addTags: Добавить теги структуры
Когда курсор находится внутри структуры, это действие рефакторинга добавляет каждому полю json
тег структуры, который указывает имя поля в JSON, используя строчные буквы с подчеркиваниями
(например, LinkTarget превращается в link_target). Для выделенного фрагмента кода, действие добавляет теги только на выделенные поля.
refactor.rewrite.removeTags: Удалить теги структуры
Когда курсор находится внутри структуры, это действие рефакторинга очищает теги структуры у всех полей структуры. Для выделенного фрагмента кода, действие удаляет теги только с выделенных полей.
Исходные файлы для этой документации можно найти в golang.org/x/tools/gopls/doc.