Gopls: コード変換機能
このドキュメントでは、動作を維持する変更(リファクタリング、フォーマット、簡略化)、コードの修正(修正)、編集サポート(構造体リテラルやswitchステートメントの埋め込み)を含む、goplsのコード変換機能について説明します。
コード変換はLSPにおいて単一のカテゴリではありません。
- フォーマットや名前変更など、いくつかプロトコルの主要な操作です。
- いくつかの変換はCode Lensesを通じて公開されており、これらは
workspace/executeCommandリクエストを通じて副作用のために呼び出される任意のサーバー操作であるコマンドを返します。ただし、現在のコードレンズにGo構文の変換はありません。 - ほとんどの変換はコードアクションとして定義されます。
コードアクション
コードアクションは、ファイルの一部に関連付けられたアクションです。選択が変更されるたびに、一般的なクライアントは利用可能なアクションのセットに対してtextDocument/codeActionリクエストを行い、UI要素(メニュー、アイコン、ツールチップ)を更新してそれを反映させます。VS Codeのマニュアルでは、コードアクションを「クイックフィックス + リファクタリング」と表現しています。
codeActionリクエストは、いわばメニューを届けますが、食事を注文するわけではありません。ユーザーがアクションを選択すると、次の2つのうちのいずれかが起こります。簡単なケースでは、アクション自体にクライアントがファイルに直接適用できる編集が含まれています。しかし、ほとんどのケースでは、アクションにはコードレンズに関連付けられたコマンドと同様のコマンドが含まれています。これにより、パッチを計算する作業は、実際に必要になったときにのみ遅延して実行されます(ほとんどはそうではありません)。サーバーは編集を計算し、クライアントにworkspace/applyEditリクエストを送信してファイルをパッチすることができます。すべてのコードアクションのコマンドがapplyEditの副作用を持つわけではありません。例えば、変数を切り替えたり、サーバーが他のリクエスト(Webブラウザでレポートを開くためのshowDocumentリクエストなど)をクライアントに送信させたりするなど、サーバーの状態を変更するものもあります。
コードレンズとコードアクションの主な違いは次のとおりです。
codeLensリクエストは、ファイル全体に対してコマンドを取得します。各コマンドは適用可能なソース範囲を指定し、通常はそのソース範囲に注釈として表示されます。codeActionリクエストは、特定の範囲(現在の選択範囲)に対してのみコマンドを取得します。すべてのコマンドは、その場所のメニューにまとめて表示されます。
各アクションには、refactor.inline.callのような階層識別子である種類があります。クライアントは、種類に基づいてアクションをフィルタリングできます。例えば、VS Codeには、「Refactor…」と「Source action…」の2つのメニューがあり、それぞれ異なる種類のコードアクション(refactorとsource)が入力されています。また、「クイックフィックス」(quickfixの種類)のメニューをトリガーする電球アイコンと、source.fixAllの種類のアクション(適用しても安全と明確に判断されたもの)をすべて実行する「Fix All」コマンドがあります。
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は、一部のコードアクションを2種類の異なる種類で2回報告し、複数のUI要素に表示されるようにしています。例えば、for _ = range mからfor range mへの簡略化は、quickfixとsource.fixAllの両方の種類を持つため、「クイックフィックス」メニューに表示され、「Fix All」コマンドで有効になります。
多くの変換は、問題に関する診断を報告する際に修正も提案するアナライザーによって計算されます。codeActionsリクエストは、現在の選択範囲に対する診断に付随するすべての修正を返します。
注意点
-
goplsのコード変換の多くは、Goの構文ツリー表現によって制限されています。Goの構文ツリー表現は現在、コメントをツリー内ではなくサイドテーブルに記録しています。そのため、ExtractやInlineなどの変換ではコメントが失われがちです。これはhttps://go.dokyumento.jp/issue/20744という問題であり、2024年に修正することが私たちの優先事項です。
-
慣習的なDO NOT EDITコメントによって識別される生成されたファイルには、変換のためのコードアクションは提供されません。
コードアクションのクライアントサポート
- VS Code: 種類に応じて、コードアクションは「Refactor…」メニュー(
^⇧R)、「Source action…」メニュー、💡(電球)アイコンのメニュー、または「Quick fix」(⌘.)メニューにあります。「Fix All」コマンドは、source.fixAllの種類のアクションをすべて適用します。 - Emacs + eglot: コードアクションは非表示です。利用可能なものから1つ選択し(複数ある場合)、実行するには
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)を、0ベースのバイトオフセットで指定された選択範囲で実行し、差分を表示します。
書式設定
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にアタッチします。フォーマットとインポート整理を組み合わせる場合、多くのユーザーはgo-modeを使用して"goimports"をgofmt-commandとして設定し、gofmt-before-saveをbefore-save-hookに追加するという従来のAアプローチを取っています。LSPベースのソリューションには、https://github.com/joaotavora/eglot/discussions/1409のようなコードが必要です。 - CLI:
gopls format file.go
source.organizeImports: インポートの整理
インポートが整理されていないファイルでのcodeActionsリクエストは、標準の種類source.organizeImportsのアクションを返します。このコマンドは、インポートを整理する効果があります。つまり、重複または未使用の既存のインポートを削除し、未定義のシンボルの新しいインポートを追加し、従来の順序に並べ替えます。
新しいインポートの追加は、ワークスペースとGOMODCACHEディレクトリの内容に依存するヒューリスティックに基づいて行われます。これにより、時に驚くような選択がなされる場合があります。
多くのエディタは、編集したファイルを保存する前に、自動的にインポートを整理し、コードをフォーマットします。
一部のユーザーは、例えば、インポートを参照する唯一の行がデバッグのために一時的にコメントアウトされている場合などに、参照されていないインポートが自動的に削除されることを好みません。詳細については、https://go.dokyumento.jp/issue/54362を参照してください。
設定
local設定は、現在のファイルにとって「ローカル」であり、標準パッケージおよびサードパーティパッケージの後にソート順で表示されるインポートパスのプレフィックスをコンマ区切りでリストしたものです。
クライアントサポート
- VS Code: 保存前に自動的に
source.organizeImportsを呼び出します。無効にするには、以下のスニペットを使用し、必要に応じて「Organize Imports」コマンドを手動で呼び出します。"[go]": { "editor.codeActionsOnSave": { "source.organizeImports": false } } - Emacs + eglot: 手動で呼び出すには
M-x eglot-code-action-organize-importsを使用します。go-modeの多くのユーザーは、以下の行を使用して、編集したファイルを保存する前にインポートを整理し、再フォーマットしています。しかし、このアプローチはgoplsではなく、従来のgoimportsツールに基づいています。(setq gofmt-command "goimports") (add-hook 'before-save-hook 'gofmt-before-save) - 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)に基づいてファイルを作成し、元のファイルから著作権およびビルド制約コメントをコピーします。
テストパッケージ: パッケージ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リクエストはシンボル名を変更します。
名前変更は2段階のプロセスです。最初のステップであるprepareRenameクエリは、カーソル下の識別子の現在の名前(存在する場合)を返します。その後、クライアントは、古い名前を編集して新しい名前を選択するようユーザーに促すダイアログを表示します。2番目のステップである実際のrenameは、変更を適用します。(このシンプルなダイアログサポートは、LSPリファクタリング操作の中でもユニークです。microsoft/language-server-protocol#1164を参照してください。)
Goplsの名前変更アルゴリズムは、名前変更がコンパイルエラーを引き起こす可能性がある状況を検出するために細心の注意を払います。例えば、名前を変更すると、シンボルが「シャドウ」され、既存の参照の一部がスコープ外になることがあります。Goplsはエラーを報告し、シンボルのペアとシャドウされた参照を通知します。
別の例として、具体的な型のメソッド名を変更することを考えてみましょう。名前変更により、その型が以前と同じインターフェースを満たさなくなる可能性があり、プログラムのコンパイルが失敗する可能性があります。これを回避するため、goplsは影響を受ける型からインターフェース型への各変換(明示的または暗黙的)を検査し、名前変更後も有効であるかどうかを確認します。有効でない場合、エラーを伴う名前変更を中止します。
元のメソッドと、一致するインターフェイス型の対応するメソッド(およびそれらに一致する型のメソッド)の両方を変更する意図がある場合は、インターフェイスメソッドで名前変更操作を呼び出すことでこれを示すことができます。
同様に、構造体のフィールドの名前を変更する場合、そのフィールドが型を埋め込む「匿名」フィールドである場合、goplsはエラーを報告します。これは、型も含むより大きな名前変更が必要となるためです。その意図がある場合は、再度、型で名前変更操作を呼び出すことでこれを示すことができます。
名前変更は決してコンパイルエラーを引き起こすべきではありませんが、動的なエラーを引き起こす可能性があります。例えば、メソッド名の変更において、影響を受ける型からインターフェース型への直接変換がない場合でも、より広範な型(anyなど)への中間変換があり、その後インターフェース型への型アサーションがある場合、goplsはメソッドの名前変更を続行し、実行時に型アサーションが失敗する原因となることがあります。encoding/jsonやtext/templateなど、リフレクションを使用するパッケージでも同様の問題が発生する可能性があります。優れた判断とテストに代わるものはありません。
特殊ケース
-
メソッドレシーバの宣言名を変更する場合、ツールは同じ名前付き型に関連付けられた他のすべてのメソッドのレシーバの名前も変更しようとします。完全に名前を変更できない他のレシーバは、黙ってスキップされます。レシーバの使用の名前を変更しても、その変数のみが影響を受けます。
type Counter struct { x int } Rename here to affect only this method ↓ func (c *Counter) Inc() { c.x++ } func (c *Counter) Dec() { c.x++ } ↑ Rename here to affect all methods -
パッケージ宣言の名前を変更すると、パッケージのディレクトリも名前変更されます。
最良の結果を得るためのヒント
- Renameアルゴリズムによって実行される安全性チェックには型情報が必要です。プログラムが著しく不正な形式である場合、実行するための情報が不足している可能性があり(https://go.dokyumento.jp/issue/41870)、型エラーを修正するために名前変更を一般的に使用することはできません(https://go.dokyumento.jp/issue/41851)。リファクタリングを行う際には、小さなステップで作業し、途中で問題を修正して、各ステップでできるだけ多くのプログラムがコンパイルされるようにすることをお勧めします。
- プログラムのリファレンス構造を変更したい場合があります。例えば、yをxにリネームすることで、2つの変数xとyを意図的に結合したい場合です。リネームツールはこのケースでは厳しすぎます(https://go.dokyumento.jp/issue/41852)。
gopls のリネームアルゴリズムの詳細については、2015 年の GothamGo の講演の後半をご覧ください。Using go/types for Code Comprehension and Refactoring Tools。
クライアントサポート
- VS Code: 「シンボル名の変更」メニュー項目(
F2)を使用します。 - Emacs + eglot:
M-x eglot-renameを使用するか、go-modeからM-x go-renameを使用します。 - Vim + coc.nvim:
coc-renameコマンドを使用します。 - CLI:
gopls rename file.go:#offset newname
refactor.extract: 関数/メソッド/変数の抽出
refactor.extractファミリーのコードアクションはすべて、選択した式またはステートメントを、選択したコードを含む新しく作成された宣言への参照に置き換えるコマンドを返します。
-
refactor.extract.functionは、1つ以上の完全なステートメントを、本体にステートメントを含むnewFunctionという名前の新しい関数への呼び出しに置き換えます。選択範囲は、既存の関数本体全体よりも少ないステートメントを囲む必要があります。

-
refactor.extract.methodは、「関数を抽出」の変種で、選択されたステートメントがメソッドに属する場合に提供されます。新しく作成された関数は、同じレシーバ型のメソッドになります。 -
refactor.extract.variableは、式を、式によって初期化されたnewVarという名前の新しいローカル変数への参照に置き換えます。

-
`refactor.extract.constant は、定数式に対して同様のことを行い、ローカルの定数宣言を導入します。
-
refactor.extract.variable-allは、関数内の選択された式のすべての出現箇所を、newVarという名前の新しいローカル変数への参照に置き換えます。これにより、式が一度抽出され、関数内のどこに現れても再利用されます。

- `refactor.extract.constant-allは、定数式に対して同様のことを行い、ローカルの定数宣言を導入します。新しい宣言のデフォルト名がすでに使用されている場合、goplsは新しい名前を生成します。
抽出は、識別子のスコープとシャドウイング、ループ内のbreak/continueや関数内のreturnなどの制御フロー、変数のカーディナリティ、さらにはスタイルの微妙な問題も考慮する必要がある、困難な問題です。それぞれの場合において、ツールはビルドの破損や動作の変更を避けるために、抽出されたステートメントを必要に応じて更新しようとします。残念ながら、goplsのExtractアルゴリズムはRenameおよびInline操作よりもかなり厳密ではなく、以下を含むいくつかの欠点があることを認識しています。
- 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
以下の抽出機能は2024年に計画されていますが、まだサポートされていません。
- パラメータ構造体の抽出は、関数の2つ以上のパラメータを、パラメータごとに1つのフィールドを持つ構造体型に置き換えます。https://go.dokyumento.jp/issue/65552を参照してください。
- 型に対するインターフェースの抽出は、選択された具象型のすべてのメソッドを持つインターフェース型の宣言を作成します。https://go.dokyumento.jp/issue/65721およびhttps://go.dokyumento.jp/issue/46665を参照してください。
refactor.extract.toNewFile: 宣言を新しいファイルに抽出
(gopls/v0.17.0 から利用可能)
1つ以上のトップレベル宣言を選択すると、goplsは「Extract declarations to new file」コードアクションを提供します。このアクションは、選択された宣言を、最初に宣言されたシンボルに基づいて名前が付けられた新しいファイルに移動します。必要に応じて、インポート宣言が作成されます。goplsは、選択がfuncやtypeなどの宣言の最初のトークンのみである場合にもこのコードアクションを提供します。

refactor.inline.call: 関数呼び出しのインライン化
選択範囲が関数またはメソッドの呼び出し(またはその内部)であるcodeActionsリクエストの場合、goplsは種類refactor.inline.callのコマンドを返します。その効果は、関数呼び出しをインライン化することです。
以下のスクリーンショットは、インライン化前後のsumの呼び出しを示しています

インライン化は、呼び出し式を関数本体のコピーで置き換え、パラメータを引数に置き換えます。インライン化は、いくつかの理由で役立ちます。例えば、非推奨の関数(ioutil.ReadFileなど)の呼び出しを、より新しいos.ReadFileへの呼び出しに置き換えることで、その呼び出しを排除したいとします。インライン化はそれを自動的に行います。あるいは、既存の関数を何らかの方法でコピーして修正したい場合、インライン化は出発点を提供できます。インライン化ロジックは、「シグネチャの変更」などの他のリファクタリングのための構成要素も提供します。
すべての呼び出しをインライン化できるわけではありません。もちろん、ツールはどの関数が呼び出されているかを知る必要がありますので、関数値やインターフェースメソッドを介した動的な呼び出しはインライン化できません。しかし、メソッドへの静的な呼び出しは問題ありません。また、呼び出される関数が別のパッケージで宣言されており、そのパッケージの非エクスポート部分や、呼び出し元からアクセスできない内部パッケージを参照している場合も、呼び出しをインライン化できません。ジェネリック関数の呼び出しはまだサポートされていませんが(https://go.dokyumento.jp/issue/63352)、修正を予定しています。
インライン化が可能である場合、ツールがプログラムの元の動作を保持することは非常に重要です。リファクタリングによってビルドが壊れたり、さらに悪いことに、微妙な潜在的なバグが導入されたりすることを望みません。これは、インライン化ツールが大規模なコードベースで自動的なクリーンアップを実行するために使用される場合に特に重要です。私たちはツールを信頼できる必要があります。私たちのインライナーは、コードの動作について推測したり、不健全な仮定をしたりしないように非常に注意しています。ただし、それは、同じコードの専門知識を持つ人が手書きで書いたものとは異なる変更を生成することがあることを意味します。
最も困難なケース、特に複雑な制御フローでは、関数呼び出しを完全に削除することは安全ではない場合があります。例えば、deferステートメントの動作はその囲む関数呼び出しと密接に関連しており、deferはパニックを処理するために使用できる唯一の制御構造であるため、より単純な構造に還元することはできません。したがって、例えば、以下のように定義された関数fが与えられた場合、
func f(s string) {
defer fmt.Println("goodbye")
fmt.Println(s)
}
f("hello")の呼び出しは以下のようにインライン化されます。
func() {
defer fmt.Println("goodbye")
fmt.Println("hello")
}()
パラメーターは削除されましたが、関数呼び出しは残っています。
インライナーは最適化コンパイラのようなものです。コンパイラは、ソース言語からターゲット言語への翻訳においてプログラムの意味を変更しない場合、「正しい」と見なされます。最適化コンパイラは、入力の詳細を利用してより良いコードを生成します。「より良い」とは通常、より効率的であることを意味します。ユーザーがコンパイラに最適とは言えないコードを生成させる入力があると報告するにつれて、コンパイラはより多くのケース、より多くのルール、そしてルールの例外を認識するように改善されます。しかし、このプロセスに終わりはありません。インライン化も同様ですが、「より良い」コードとはより簡潔なコードを意味します。最も保守的な翻訳は、シンプルですが(うまくいけば)正しい基盤を提供し、その上に無限のルールとルールの例外が、出力の品質を飾り立てて向上させることができます。
ここに健全なインライン化に関わる技術的な課題をいくつか挙げます。
-
副作用: パラメータを引数式に置き換える際には、呼び出しの副作用を変更しないように注意する必要があります。例えば、
func twice(x int) int { return x + x }という関数をtwice(g())で呼び出す場合、g() + g()となるとgの副作用が2回発生し、それぞれの呼び出しが異なる値を返す可能性があるため、これは望ましくありません。すべての副作用は同じ回数、同じ順序で発生しなければなりません。そのためには、引数と呼び出し先の関数の両方を分析して、それらが「純粋」であるかどうか、変数を読み取るかどうか、そして(いつ)変数を更新するかどうかを判断する必要があります。インライナーは、引数を全体にわたって安全に置換できると証明できない場合、var x int = g()のような宣言を導入します。 -
定数: インライン化が値が定数の場合に常にパラメータを引数に置き換えると、以前は実行時に行われていたチェックがコンパイル時に行われるため、一部のプログラムはビルドできなくなります。例えば、
func index(s string, i int) byte { return s[i] }は有効な関数ですが、インライン化によってindex("abc", 3)の呼び出しが式"abc"[3]に置き換えられた場合、コンパイラは文字列"abc"に対してインデックス3が範囲外であると報告します。インライナーは問題のある定数引数によるパラメータの置換を防ぎ、代わりに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のコードアクションを返します。その効果は変数をインライン化すること、つまり参照を変数の初期化式に置き換えることです。
例えば、println(s)の呼び出しで識別子sに対して呼び出された場合、
func f(x int) {
s := fmt.Sprintf("+%d", x)
println(s)
}
コードアクションはコードを次のように変換します。
func f(x int) {
s := fmt.Sprintf("+%d", x)
println(fmt.Sprintf("+%d", x))
}
(この場合、sは参照されない変数になるため、削除する必要があります。)
このコードアクションは、たとえ変数に後続の代入(例: s = "")があっても、常に参照を初期化式に置き換えます。
コードアクションは、初期化式内の識別子(例: 上の例のx)のいずれかが、この例のように、間に挟まれた宣言によってシャドウされているため、変換ができない場合にエラーを報告します。
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
}
}
refactor.rewrite: その他の書き換え
このセクションでは、refactor.rewriteの子である種類を持つコードアクションとしてアクセスできる、いくつかの変換について説明します。
refactor.rewrite.removeUnusedParam: 未使用パラメータの削除
unusedparamsアナライザーは、関数本体内で使用されていない各パラメータに対して診断を報告します。例えば、
func f(x, y int) { // "unused parameter: x"
fmt.Println(y)
}
特定の関数シグネチャに準拠するために、未使用のパラメーターであってもすべて必要とする可能性のあるアドレス取得関数については、診断は報告されません。また、別パッケージによってアドレス取得される可能性のあるエクスポートされた関数についても診断は報告されません。(関数は、呼び出し位置f(...)以外で使用されている場合、アドレス取得されます。)
診断に加え、2つの修正案を提案します。
- パラメータを
_にリネームして、それが参照されていないことを強調する(即時編集)。または ChangeSignatureコマンドを使用してパラメータを完全に削除し、すべての呼び出し元を更新します。
修正 #2 は、「関数呼び出しのインライン化」(上記参照)と同じ仕組みを使用して、削除されたパラメーターの引数式に副作用がある場合でも、既存のすべての呼び出しの動作が保持されるようにします。

最初の呼び出しでは、引数chargeCreditCard()は潜在的な副作用のために削除されなかったのに対し、2番目の呼び出しでは、定数である引数2は安全に削除されたことに注目してください。
refactor.rewrite.moveParam{Left,Right}: 関数パラメーターの移動
関数またはメソッドのシグネチャ内のパラメータが選択されている場合、goplsは、パラメータを左または右に(可能であれば)移動するコードアクションを提供し、それに従ってすべての呼び出し元を更新します。
例
func Foo(x, y int) int {
return x + y
}
func _() {
_ = Foo(0, 1)
}
次になります。
func Foo(y, x int) int {
return x + y
}
func _() {
_ = Foo(1, 0)
}
xを右に、またはyを左に移動する要求に従います。
これは、より一般的な「シグネチャ変更」操作の基本的な構成要素です。これを任意のシグネチャ書き換えに一般化する予定ですが、言語サーバープロトコルは現在、リファクタリング操作へのユーザー入力を適切にサポートしていません(microsoft/language-server-protocol#1164を参照)。したがって、そのようなリファクタリングにはカスタムのクライアント側ロジックが必要です。(非常にハックな回避策として、関数宣言のfuncキーワードに対してRenameを呼び出すことで任意のパラメータ移動を表現できますが、このインターフェースは一時的な応急処置にすぎません。)
refactor.rewrite.changeQuote: 文字列リテラルを生形式と解釈形式の間で変換
選択が文字列リテラルである場合、gopls は、可能であれば文字列を生形式(`abc`)と解釈形式("abc")の間で変換するコードアクションを提供します。

コードアクションをもう一度適用すると、元の形式に戻ります。
refactor.rewrite.invertIf: 「if」条件の反転
選択がelse ifが続かないif/elseステートメント内にある場合、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は「Split [items] into separate lines」コードアクションを提供し、上記の形式を以下の形式に変換します。
[]T{
a,
b,
c,
}
f(
a,
b,
c,
)
func(
a, b, c int,
d, e bool,
)
func() (
x, y string,
z rune,
)
最後の2つのケースでは、各パラメーターまたは結果のグループが単一のアイテムとして扱われることに注目してください。
反対のコードアクション「Join [items] into one line」は、この操作を取り消します。リストが既に完全に分割または結合されている場合、または些細な場合(項目が2つ未満)は、どちらのアクションも提供されません。
これらのコードアクションは、行末まで続く//形式のコメントを含むリストには提供されません。
refactor.rewrite.fillStruct: 構造体リテラルの補完
カーソルが構造体リテラルS{}内にある場合、goplsは「Fill S」コードアクションを提供し、アクセス可能なリテラルの欠落している各フィールドを埋めます。
各フィールドに割り当てる値を選択する際に、以下のヒューリスティックを使用します。フィールドに代入可能な候補変数、定数、関数を見つけ、フィールド名に最も一致する名前のものを選びます。見つからない場合は、フィールドの型のゼロ値(0、""、nilなど)を使用します。
以下の例では、slog.HandlerOptions構造体リテラルが2つのローカル変数(levelとadd)と関数(replace)を使用して埋められています。

注意点
- このコードアクションには構造体型の型情報が必要なため、別のまだインポートされていないパッケージで定義されている場合は、ファイルを保存するなどして、まず「インポートを整理」する必要があるかもしれません。
- 候補となる宣言は、現在のファイル内、かつ現在のポイントより上の位置でのみ検索されます。現在のポイントより下、またはパッケージ内の他のファイルで宣言されたシンボルは考慮されません。https://go.dokyumento.jp/issue/68224を参照してください。
refactor.rewrite.fillSwitch: switchの補完
カーソルがオペランド型がenum(名前付き定数の有限集合)であるswitchステートメント内、または型switchステートメント内にある場合、goplsは「Add cases for T」コードアクションを提供します。このアクションは、enum型のアクセス可能な各名前付き定数に対してケースを追加するか、型switchの場合は、インターフェースを実装するアクセス可能な各名前付き非インターフェース型に対してケースを追加することで、switchステートメントを埋めます。欠落しているケースのみが追加されます。
以下のスクリーンショットは、オペランドがnet.Addrインターフェース型を持つ型スイッチを示しています。このコードアクションは、具象ネットワークアドレス型ごとに1つのケースと、予期しないオペランドが検出された場合に有益なメッセージでパニックを起こすデフォルトケースを追加します。

そして、これらのスクリーンショットは、HTMLドキュメントを構成するさまざまな種類のトークンを表すhtml.TokenType enum型の各値に対してケースを追加するコードアクションを示しています。

refactor.rewrite.eliminateDotImport: ドットインポートの削除
カーソルがドットインポートにある場合、goplsは「ドットインポートの削除」コードアクションを提供できます。これはインポートからドットを削除し、ファイル全体でパッケージの使用を修飾します。このコードアクションは、パッケージの各使用が既存の名前との衝突なしに修飾できる場合にのみ提供されます。
refactor.rewrite.addTags: 構造体タグの追加
カーソルが構造体内にある場合、このコードアクションは各フィールドに、JSON名を指定するjson構造体タグを、アンダースコア付きの小文字(例: LinkTargetはlink_target)を使用して追加します。ハイライトされた選択範囲の場合は、選択されたフィールドにのみタグを追加します。
refactor.rewrite.removeTags: 構造体タグの削除
カーソルが構造体内にある場合、このコードアクションはすべての構造体フィールドの構造体タグをクリアします。ハイライトされた選択範囲の場合は、選択されたフィールドからのみタグを削除します。
このドキュメントのソースファイルは、golang.org/x/tools/gopls/doc の下にあります。