Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.goldensrc/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.goruncheck関数は、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.goldensrc/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.goldencomments.inputの変更

これらのファイルは、go/printerのテストデータとして機能します。

  • comments.inputファイルには、go/printerが処理する前の元のGoコードが含まれています。このコミットでは、Issue 1542で問題となった、カンマの直前にコメントが配置されたスライスリテラルの例が複数追加されています。
  • comments.goldenファイルには、comments.inputgo/printerによってフォーマットされた後の期待される正しい出力が含まれています。このファイルは、go/printerがコメントを適切に処理し、カンマの後に移動させることで、生成されるコードが有効なGo構文を維持していることを示しています。

これらのテストケースの追加により、Issue 1542で報告された特定のバグが修正されただけでなく、将来的に同様のコメント処理の問題が発生した場合に、このテストがそれを捕捉できるようになります。

関連リンク

参考にした情報源リンク

  • Go Issue 1542のGitHubページ
  • Go CL 5645080のGo Gerritページ
  • Go言語の公式ドキュメント(go/printerおよびgo/parserパッケージ)
  • Go言語の構文に関する一般的な知識
  • 回帰テストに関する一般的なソフトウェアテストの知識