[インデックス 17490] ファイルの概要
このコミットは、Go言語の公式フォーマッタであるgofmt
のインポート文のソートと重複排除のロジックを改善するものです。具体的には、インポートパスだけでなく、インポート名やコメントも考慮したより詳細なソート順を導入し、コメントのない重複インポートを削除する機能が追加されました。
コミット
- コミットハッシュ:
08925ce6ee16be5a3b937c0d55c2548bf30c5776
- 作者: Josh Bleecher Snyder josharian@gmail.com
- コミット日時: 2013年9月6日 金曜日 16:25:15 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08925ce6ee16be5a3b937c0d55c2548bf30c5776
元コミット内容
cmd/gofmt: sort more, remove some duplicate imports
* Sort imports by import path, then import name, then comment. Currently, gofmt sorts only by import path.
* If two imports have the same import path and import name, and one of them has no comment, remove the import with no comment. (See the discussion at issue 4414.)
Based on @rsc's https://golang.org/cl/7231070/
Fixes #4414.
R=gri, rsc
CC=golang-dev
https://golang.org/cl/12837044
変更の背景
Go言語では、コードの可読性と一貫性を保つために、gofmt
というツールが広く利用されています。gofmt
は、Goのソースコードを自動的にフォーマットし、Goコミュニティ全体で統一されたコーディングスタイルを強制します。このツールは、インデント、スペース、改行などの基本的なフォーマットだけでなく、インポート文の整理も行います。
このコミット以前のgofmt
は、インポート文をインポートパス(例: "fmt"
, "net/http"
)に基づいてソートしていました。しかし、これだけでは不十分なケースがありました。例えば、同じインポートパスを持つ複数のインポート文が存在する場合(エイリアス付きインポートやブランクインポートなど)、それらの相対的な順序は未定義であり、gofmt
を実行するたびに順序が変わる可能性がありました。これは、バージョン管理システムでの不要な差分を生み出し、コードレビューを複雑にする原因となっていました。
また、開発者が誤って同じパッケージを複数回インポートしてしまうケースも考えられます。特に、エイリアス付きインポートと通常のインポートが混在する場合などです。このような重複は、コードの冗長性を高め、可読性を損ないます。
このコミットは、これらの問題を解決するために提案されました。コミットメッセージで言及されているissue 4414
は、おそらくこれらのインポートのソートと重複排除に関する議論が行われたGoのバグトラッキングシステム上の課題を指していると考えられます。Goのissueトラッカーは進化しており、古いissue番号は直接検索できない場合がありますが、コミットメッセージにその内容が明確に記述されています。
前提知識の解説
gofmt
gofmt
は、Go言語のソースコードを自動的にフォーマットするツールです。Goの標準ライブラリの一部として提供されており、Go開発者にとって不可欠なツールとなっています。gofmt
は、Goのコードベース全体で一貫したスタイルを維持し、コードレビューの負担を軽減し、可読性を向上させることを目的としています。
Goのインポート文
Goのインポート文は、他のパッケージの機能を利用するために使用されます。基本的な形式は以下の通りです。
import "path/to/package"
インポート文には、いくつかのバリエーションがあります。
- エイリアス付きインポート: パッケージ名を変更してインポートします。
import a "path/to/package" // package.Func() ではなく a.Func() でアクセス
- ブランクインポート: パッケージの初期化処理のみを実行し、パッケージ内の識別子を直接使用しない場合に用います。
import _ "path/to/package" // パッケージのinit関数が実行される
- ドットインポート: パッケージの識別子を修飾なしで直接使用できるようにします。非推奨とされることが多いです。
import . "path/to/package" // package.Func() ではなく Func() でアクセス
- コメント付きインポート: インポート文にコメントを付加することができます。
import "fmt" // for printing
go/ast
パッケージ
go/ast
パッケージは、Goのソースコードの抽象構文木(AST: Abstract Syntax Tree)を表現するためのデータ構造と関数を提供します。Goのコンパイラやツール(gofmt
など)は、ソースコードをASTにパースし、ASTを操作することでコードの解析や変換を行います。このコミットでは、インポート文を表すASTノード(ast.ImportSpec
)を操作しています。
go/token
パッケージ
go/token
パッケージは、Goのソースコード内のトークン(キーワード、識別子、演算子など)と、それらのソースコード上の位置(ファイル名、行番号、列番号、オフセット)を管理するためのデータ構造を提供します。gofmt
のようなツールは、コードのフォーマット時に正確な位置情報を必要とします。このコミットでは、token.FileSet
を使用してファイル内の行を削除する機能が追加されています。
技術的詳細
このコミットの主要な変更点は、gofmt
がインポート文をソートおよび重複排除する方法にあります。
インポートのソート順の拡張
以前のgofmt
は、インポートパスのみに基づいてインポート文をソートしていました。このコミットでは、ソートの優先順位が以下のように拡張されました。
- インポートパス (import path): 最も優先されるソートキーです。例えば、
"fmt"
は"net/http"
より前に来ます。 - インポート名 (import name): 同じインポートパスを持つインポート文がある場合、エイリアス名(例:
a "path/to/package"
のa
)やブランクインポート(_
)、ドットインポート(.
)の順でソートされます。エイリアスがない場合は空文字列として扱われます。 - コメント (comment): インポートパスとインポート名が同じ場合、コメントの内容に基づいてソートされます。コメントがない場合は空文字列として扱われます。
この新しいソートロジックは、src/pkg/go/ast/import.go
内のbyImportSpec
型に実装されています。Less
メソッドがこの新しい比較ロジックを定義しています。
重複インポートの削除
このコミットでは、特定の条件を満たす重複インポート文を削除する機能も追加されました。削除の条件は以下の通りです。
- 2つのインポート文が同じインポートパスを持ち、かつ同じインポート名を持つ。
- そのうちの一方のインポート文にコメントがない。
この条件が満たされる場合、コメントのない方のインポート文が削除されます。これは、コメントがインポートの意図や使用方法に関する重要な情報を含んでいる可能性があるため、コメント付きのインポートを優先するという設計思想に基づいています。このロジックは、collapse
関数によって実装されています。
重複排除は、sortSpecs
関数内でソート後に行われます。ソートによって同じインポートパスとインポート名を持つインポート文が隣接するため、効率的に重複を検出できます。
token.File
への行削除機能の追加
重複インポートを削除する際、単にASTからノードを削除するだけでは、ソースコード上の対応する行が残ってしまう可能性があります。これを解決するため、src/pkg/go/token/position.go
にRemoveLine
という新しいメソッドがtoken.File
に追加されました。このメソッドは、指定された行番号の行をファイルから削除し、後続の行を詰めることで、ソースコードの正確な表現を維持します。
コアとなるコードの変更箇所
このコミットによって以下のファイルが変更されました。
src/cmd/gofmt/testdata/import.golden
:gofmt
のテストデータのうち、期待される出力(ゴールデンファイル)が更新されました。新しいソート順と重複排除の結果が反映されています。src/cmd/gofmt/testdata/import.input
:gofmt
のテストデータのうち、入力ファイルが更新されました。新しいソートと重複排除のテストケースが追加されています。src/pkg/go/ast/import.go
: インポートのソートと重複排除の主要なロジックが実装されているファイルです。SortImports
関数が大幅に修正され、sortSpecs
関数が新しいソートロジックと重複排除ロジックを含むように変更されました。また、インポート名とコメントを取得するためのヘルパー関数importName
とimportComment
、そして重複を判定するcollapse
関数が追加されました。src/pkg/go/token/position.go
:token.File
型にRemoveLine
メソッドが追加されました。このメソッドは、ソースコードから特定の行を削除するために使用されます。
コアとなるコードの解説
src/pkg/go/ast/import.go
SortImports
関数
この関数は、ファイルのASTを受け取り、その中のすべてのインポート宣言ブロックを走査します。各インポート宣言ブロック(GenDecl
)に対して、連続するインポート行のまとまり("run")を識別し、それぞれをsortSpecs
関数に渡してソートと重複排除を行います。
変更点として、sortSpecs
の戻り値が[]Spec
となり、重複排除されたスペックのリストを返すようになりました。これにより、d.Specs
が更新され、重複が取り除かれたインポート文のリストが設定されます。
また、重複排除によってインポートブロックの最後に空白行ができてしまう場合があるため、Rparen
の位置を調整して空白行を削除するロジックが追加されました。これは、fset.File(d.Rparen).RemoveLine(rParenLine - 1)
によって行われます。
importName
関数とimportComment
関数
これらのヘルパー関数は、ast.ImportSpec
からそれぞれインポート名(エイリアス名、ブランクインポート、ドットインポート)とコメントのテキストを安全に取得するために追加されました。nil
チェックを行い、存在しない場合は空文字列を返します。
collapse
関数
この関数は、2つのSpec
(インポート文)を受け取り、prev
がnext
によって削除可能かどうかを判定します。以下の条件がすべて真の場合にtrue
を返します。
next
とprev
のインポートパスが同じである。next
とprev
のインポート名が同じである。prev
のインポート文にコメントがない。
この関数は、重複排除ロジックの核心部分を担っています。
sortSpecs
関数
この関数は、インポート文のSpec
のスライスを受け取り、ソートと重複排除を行います。
- ソート:
sort.Sort(byImportSpec(specs))
を呼び出し、byImportSpec
型で定義された新しいソート順(インポートパス、インポート名、コメントの順)でインポート文をソートします。 - 重複排除: ソートされた
specs
スライスを走査し、隣接するインポート文に対してcollapse
関数を適用します。collapse(s, specs[i+1])
がtrue
の場合、つまりs
がspecs[i+1]
によって削除可能である場合、s
に対応するソースコード上の行をfset.File(p).RemoveLine(...)
で削除し、s
をdeduped
スライスに追加しません。これにより、重複するインポート文が取り除かれます。 - コメント位置の修正: インポート文のソートや削除によって、コメントの位置がずれる可能性があるため、コメントのASTノードの開始位置と終了位置を再調整します。
この関数は、ソートと重複排除の両方を担当する重要な部分です。
byImportSpec
型
sort.Interface
インターフェースを実装しており、Len
, Swap
, Less
メソッドを提供します。Less
メソッドが、インポートパス、インポート名、コメントの順でインポート文を比較する新しいロジックを定義しています。
src/pkg/go/token/position.go
RemoveLine
メソッド
File
型に新しく追加されたメソッドです。指定されたline
番号(1-based)の行をファイルから削除します。内部的には、f.lines
スライス(各行の開始オフセットを格納)から該当するオフセットを削除し、後続のオフセットをシフトすることで、行の削除を実現しています。このメソッドは、SortImports
関数内で重複するインポート文の行をソースコードから物理的に削除するために使用されます。
関連リンク
- 元の変更提案 (CL): https://golang.org/cl/12837044
- @rscによる関連する変更提案 (CL): https://golang.org/cl/7231070/
参考にした情報源リンク
- コミットメッセージの内容
- Go言語の
go/ast
パッケージとgo/token
パッケージのドキュメント(Goの公式ドキュメント) - Go言語の
gofmt
ツールの一般的な動作に関する知識
(注: コミットメッセージで言及されているissue 4414
については、直接的なWeb検索では詳細な情報を見つけることができませんでした。これは、Goのissueトラッカーのシステム変更や、非常に古いissueであるため公開されていない可能性があるためです。しかし、コミットメッセージ自体にその内容が明確に記述されているため、その情報に基づいて解説を行いました。)