[インデックス 11781] ファイルの概要
このコミットは、Go言語のgo/printer
パッケージにおいて、フォーマットされたコードが正しくパース可能であることを検証するテストを追加するものです。具体的には、コメントの配置が原因で生成されるコードが不正になるバグ(Issue 1542)を修正し、その回帰テストを含んでいます。
コミット
commit a0acdd210b7052c332926bae9a72e8a4bae642b8
Author: Robert Griesemer <gri@golang.org>
Date: Fri Feb 10 13:28:29 2012 -0800
go/printer: test that formatted code is parseable
- Added test case for issue 1542.
Fixes #1542.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5645080
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0acdd210b7052c332926bae9a72e8a4bae642b8
元コミット内容
このコミットは、go/printer
パッケージのテストスイートに、フォーマット後のコードがGo言語のパーサーによって正しく解析できることを確認する検証ステップを追加します。また、Issue 1542に関連する具体的なテストケースがsrc/pkg/go/printer/testdata/comments.golden
とsrc/pkg/go/printer/testdata/comments.input
に追加されています。これにより、コメントが特定の構文要素(この場合はカンマ)の直前に配置された場合に、go/printer
が不正なコードを生成しないことを保証します。
変更の背景
この変更の背景には、Go言語のツールチェインにおけるgo/printer
の役割と、Issue 1542として報告された具体的なバグがあります。
go/printer
は、Go言語のソースコードを標準的なGoのスタイルガイドに従ってフォーマットするためのパッケージです。gofmt
コマンドの基盤としても利用されており、Go開発者にとって非常に重要なツールです。しかし、コードのフォーマットは単に見た目を整えるだけでなく、そのコードが依然として有効なGoプログラムであることも保証しなければなりません。
Issue 1542は、go/printer
が特定の状況下で、コメントの配置によって不正なGoコードを生成してしまうというバグでした。具体的には、スライスリテラルや配列リテラル内の要素間のカンマの直前にコメントが存在する場合、go/printer
がそのコメントをカンマの前に移動させてしまい、結果として構文エラーを引き起こすコードを出力していました。例えば、[]int{1, /* comment */ 2}
のようなコードが、フォーマット後に[]int{1 /* comment */, 2}
のように不正な形になることが問題でした。これは、go/printer
がコメントの位置情報を正確に処理できていなかったことに起因します。
このコミットは、このようなバグが将来的に再発しないように、フォーマット後のコードが常にパース可能であるという基本的な健全性チェックをテストスイートに組み込むことを目的としています。
前提知識の解説
Go言語のgo/printer
パッケージ
go/printer
パッケージは、Go言語の抽象構文木(AST: Abstract Syntax Tree)を受け取り、それをGoの標準的なフォーマット規則に従って整形されたソースコードとして出力する役割を担います。gofmt
ツールはこのパッケージを利用して、Goのソースコードを自動的に整形します。go/printer
の目的は、一貫性のある読みやすいコードスタイルを強制し、Goコミュニティ全体でのコードの統一性を高めることです。
Go言語のgo/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、そのコードの抽象構文木(AST)を構築する役割を担います。これはコンパイラや各種ツールがGoコードを理解し、操作するための基盤となります。コードが「パース可能である」とは、go/parser
がエラーなくそのコードを解析し、有効なASTを構築できることを意味します。
Go言語のコメントと構文
Go言語では、C++スタイルの一行コメント(//
)と複数行コメント(/* ... */
)がサポートされています。コメントはプログラムの実行には影響しませんが、コードの可読性を高めるために重要です。しかし、コメントの配置は、特に自動フォーマッタのようなツールにとっては複雑な問題となることがあります。コメントが特定の構文要素の直前や直後に来る場合、フォーマッタはそのコメントをどこに配置すべきかを正確に判断する必要があります。Goの構文規則では、カンマは要素の区切りとして機能し、その前後にコメントが挿入されると、構文解析に影響を与える可能性があります。
回帰テスト (Regression Test)
回帰テストとは、ソフトウェアの変更(バグ修正や新機能追加など)が、既存の機能に予期せぬ悪影響(回帰バグ)を与えていないことを確認するために実行されるテストです。このコミットでIssue 1542のテストケースが追加されたのは、このバグが将来の変更によって再発しないようにするための回帰テストとして機能します。
技術的詳細
このコミットの技術的な核心は、go/printer
の出力の健全性をgo/parser
を用いて検証する点にあります。
src/pkg/go/printer/printer_test.go
のruncheck
関数は、go/printer
によってフォーマットされたコード(res
変数に格納される)を受け取ります。このコミットでは、このres
に対して新たにparser.ParseFile(fset, "", res, 0)
を呼び出す行が追加されました。
parser.ParseFile
: Goのソースコードを解析し、ASTを構築する関数です。fset
:token.FileSet
オブジェクトで、ソースファイルの位置情報を管理します。""
: ファイル名。テストなので空文字列で問題ありません。res
:go/printer
によってフォーマットされたコードのバイトスライス。0
:parser.ParseFile
に渡すモードフラグ。0
はデフォルトの動作を意味します。
このparser.ParseFile
の呼び出しがエラーを返した場合(つまり、フォーマットされたコードが不正でパースできなかった場合)、t.Error(err)
とt.Logf("\n%s", res)
が呼び出され、テストが失敗します。これにより、go/printer
が不正なコードを生成した場合に、即座にその問題が検出されるようになります。
また、src/pkg/go/printer/testdata/comments.golden
とsrc/pkg/go/printer/testdata/comments.input
の変更は、Issue 1542の具体的なシナリオをカバーしています。comments.input
には、カンマの直前にコメントがあるGoコードの例が追加されています。comments.golden
は、そのinput
ファイルがgo/printer
によってフォーマットされた後の期待される正しい出力を含んでいます。この変更により、go/printer
がコメントをカンマの前に移動させずに、正しくカンマの後に配置する(または適切な位置に保持する)ことを保証します。
具体的には、comments.input
には以下のようなコードが追加されています。
func _() {
var a = []int{1, 2, /*jasldf*/
}
_ = a
}
func _() {
var a = []int{1, 2, /*jasldf
*/
}
_ = a
}
func _() {
var a = []int{1, 2, // jasldf
}
_ = a
}
これらの入力に対して、comments.golden
ではコメントがカンマの後に適切に配置され、パース可能な状態が維持されていることが示されています。例えば、var a = []int{1, 2, /*jasldf*/}
は、var a = []int{1, 2 /*jasldf*/}
のようにカンマの後にコメントが来るようにフォーマットされることが期待されます。
コアとなるコードの変更箇所
src/pkg/go/printer/printer_test.go
--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -67,6 +67,13 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) {
}\n \tres := buf.Bytes()\n \n+\t// formatted source must be valid\n+\tif _, err := parser.ParseFile(fset, \"\", res, 0); err != nil {\n+\t\tt.Error(err)\n+\t\tt.Logf(\"\\n%s\", res)\n+\t\treturn\n+\t}\n+\n \t// update golden files if necessary\n \tif *update {\n \t\tif err := ioutil.WriteFile(golden, res, 0644); err != nil {\
src/pkg/go/printer/testdata/comments.golden
--- a/src/pkg/go/printer/testdata/comments.golden
+++ b/src/pkg/go/printer/testdata/comments.golden
@@ -404,7 +404,7 @@ func _() {\n \t*/\n }\n \n-// Some interesting interspersed comments\n+// Some interesting interspersed comments.\n func _( /* this */ x /* is */ /* an */ int) {\n }\n \n@@ -428,6 +428,26 @@ func _() {\n \t_ = []int{0, 1 /* don\'t introduce a newline after this comment - was issue 1365 */ }\n }\n \n+// Test cases from issue 1542:\n+// Comments must not be placed before commas and cause invalid programs.\n+func _() {\n+\tvar a = []int{1, 2\t/*jasldf*/}\n+\t_ = a\n+}\n+\n+func _() {\n+\tvar a = []int{1, 2}/*jasldf\n+\t */\n+\n+\t_ = a\n+}\n+\n+func _() {\n+\tvar a = []int{1, 2}// jasldf \n+\n+\t_ = a\n+}\n+\n // Comments immediately adjacent to punctuation (for which the go/printer\n // may only have estimated position information) must remain after the punctuation.\n func _() {\
src/pkg/go/printer/testdata/comments.input
--- a/src/pkg/go/printer/testdata/comments.input
+++ b/src/pkg/go/printer/testdata/comments.input
@@ -410,7 +410,7 @@ func _() {\n }\n \n \n-// Some interesting interspersed comments\n+// Some interesting interspersed comments.\n func _(/* this */x/* is *//* an */ int) {\n }\n \n@@ -432,6 +432,26 @@ func _() {\n \t_ = []int{0, 1 /* don\'t introduce a newline after this comment - was issue 1365 */}\n }\n \n+// Test cases from issue 1542:\n+// Comments must not be placed before commas and cause invalid programs.\n+func _() {\n+\tvar a = []int{1, 2, /*jasldf*/\n+\t}\n+\t_ = a\n+}\n+\n+func _() {\n+\tvar a = []int{1, 2, /*jasldf\n+\t\t\t\t\t\t*/\n+\t}\n+\t_ = a\n+}\n+\n+func _() {\n+\tvar a = []int{1, 2, // jasldf \n+\t}\n+\t_ = a\n+}\n \n // Comments immediately adjacent to punctuation (for which the go/printer\n // may only have estimated position information) must remain after the punctuation.\
コアとなるコードの解説
printer_test.go
の変更
runcheck
関数は、go/printer
のテストヘルパー関数であり、ソースコードをフォーマットし、その結果をゴールデンファイルと比較します。このコミットで追加された以下のコードブロックが最も重要です。
// formatted source must be valid
if _, err := parser.ParseFile(fset, "", res, 0); err != nil {
t.Error(err)
t.Logf("\n%s", res)
return
}
このコードは、go/printer
によって生成されたres
(フォーマット済みコードのバイトスライス)を、go/parser.ParseFile
関数を使って解析しようとします。
- もし
parser.ParseFile
がエラーを返した場合(err != nil
)、それはgo/printer
が不正なGoコードを生成したことを意味します。 - この場合、
t.Error(err)
がテストを失敗させ、t.Logf("\n%s", res)
が生成された不正なコードを出力してデバッグを容易にします。 return
によって、それ以降のゴールデンファイルとの比較などの処理はスキップされます。
この変更により、go/printer
の出力が常にGo言語の構文規則に準拠していることが、自動テストによって保証されるようになりました。これは、フォーマッタが単に見た目を整えるだけでなく、コードの有効性を維持するという重要な側面をカバーします。
comments.golden
とcomments.input
の変更
これらのファイルは、go/printer
のテストデータとして機能します。
comments.input
ファイルには、go/printer
が処理する前の元のGoコードが含まれています。このコミットでは、Issue 1542で問題となった、カンマの直前にコメントが配置されたスライスリテラルの例が複数追加されています。comments.golden
ファイルには、comments.input
がgo/printer
によってフォーマットされた後の期待される正しい出力が含まれています。このファイルは、go/printer
がコメントを適切に処理し、カンマの後に移動させることで、生成されるコードが有効なGo構文を維持していることを示しています。
これらのテストケースの追加により、Issue 1542で報告された特定のバグが修正されただけでなく、将来的に同様のコメント処理の問題が発生した場合に、このテストがそれを捕捉できるようになります。
関連リンク
- Go Issue 1542: https://github.com/golang/go/issues/1542
- Go CL 5645080: https://golang.org/cl/5645080 (このコミットに対応するGoのコードレビューシステム上のチェンジリスト)
go/printer
パッケージのドキュメント: https://pkg.go.dev/go/printergo/parser
パッケージのドキュメント: https://pkg.go.dev/go/parser
参考にした情報源リンク
- Go Issue 1542のGitHubページ
- Go CL 5645080のGo Gerritページ
- Go言語の公式ドキュメント(
go/printer
およびgo/parser
パッケージ) - Go言語の構文に関する一般的な知識
- 回帰テストに関する一般的なソフトウェアテストの知識