[インデックス 10759] ファイルの概要
このコミットは、Go言語の標準ライブラリであるgo/printer
とgo/doc
パッケージに、サンプルコード内のコメントを正しく出力する機能を追加するものです。具体的には、go/printer
がCommentedNode
という新しい構造体をサポートし、go/doc
がサンプルコードからコメントを収集するように変更されています。これにより、go doc
コマンドなどで表示されるサンプルコードが、元のソースコードに含まれるコメントを保持したまま整形・表示されるようになります。
コミット
commit 5fb7e5b482eba62a78738866c536ef04f0696809
Author: Robert Griesemer <gri@golang.org>
Date: Tue Dec 13 14:05:05 2011 -0800
go/printer, godoc: print comments in example code
- go/printer: support for printing CommentedNodes
- go/doc: collect comments from examples
Fixes #2429.
R=adg, rsc
CC=golang-dev
https://golang.org/cl/5482052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5fb7e5b482eba62a78738866c536ef04f0696809
元コミット内容
このコミットの元の内容は以下の通りです。
- コミットメッセージ:
go/printer, godoc: print comments in example code
- 詳細:
go/printer
:CommentedNode
の出力に対応。go/doc
: サンプルコードからコメントを収集。
- 関連Issue:
Fixes #2429.
- レビュー担当者:
adg
,rsc
- CC:
golang-dev
- Gerrit変更リスト:
https://golang.org/cl/5482052
変更の背景
この変更の背景には、Go言語のドキュメンテーションツール(godoc
)が、パッケージのサンプルコード(Example
関数)を表示する際に、そのコード内に記述されたコメントを適切に表示できていなかったという問題があります。Issue #2429("godoc: example code comments are not printed")がこの問題を提起しており、このコミットはその修正を目的としています。
Goのドキュメンテーションシステムでは、Example
関数として記述されたコードは、go test
コマンドで実行可能なテストとしても機能し、同時にgodoc
によって生成されるドキュメントの一部として表示されます。しかし、これまでの実装では、go/printer
パッケージがAST(抽象構文木)を整形して出力する際に、ASTノードに直接関連付けられていないコメント(例えば、コードブロック内の行コメントなど)を適切に扱えず、結果としてサンプルコードのコメントが失われていました。
このコミットは、サンプルコードの可読性と理解度を高めるために、コメントがドキュメントの一部として不可欠であるという認識に基づいています。コメントはコードの意図や特定の挙動を説明するために非常に重要であり、それらが失われることはドキュメントの品質を著しく低下させます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の標準パッケージと概念に関する知識が必要です。
-
go/ast
パッケージ (Abstract Syntax Tree): Goのソースコードを解析し、その構造を抽象構文木(AST)として表現するためのパッケージです。ASTは、プログラムの構造を木構造で表現したもので、各ノードがコードの要素(変数宣言、関数呼び出し、コメントなど)に対応します。ast.Node
: ASTのすべてのノードが実装するインターフェース。ast.BlockStmt
: コードブロック({ ... }
)を表すステートメント。ast.CommentGroup
: 複数のコメント(// comment
や/* comment */
)をまとめたグループ。ast.File
: 単一のGoソースファイル全体のAST表現。ファイル内のすべての宣言、ステートメント、コメントなどを含みます。
-
go/token
パッケージ: Goのソースコードをトークン(キーワード、識別子、演算子など)に分割するためのパッケージです。token.Position
: ソースコード内の特定の場所(ファイル名、行番号、列番号、オフセット)を表す構造体。
-
go/printer
パッケージ:go/ast
パッケージによって生成されたASTを、Goの標準的なフォーマット規則に従って整形し、ソースコードとして出力するためのパッケージです。このパッケージは、go fmt
コマンドの基盤となっています。printer.Fprint
: ASTノードを整形して指定されたio.Writer
に出力する主要な関数。
-
go/doc
パッケージ: Goのソースコードからドキュメンテーションを生成するためのパッケージです。godoc
コマンドはこのパッケージを利用しています。doc.Example
構造体:Example
関数として記述されたサンプルコードの情報を保持します。これには、サンプルコードの本体(AST)、期待される出力、および説明が含まれます。
-
Example
関数: Goのテストファイル(_test.go
)内に記述される特別な関数で、Example
というプレフィックスを持ちます(例:ExampleInts
)。これらの関数は、パッケージや特定の関数の使用例を示すために用いられ、go test
コマンドで実行可能であり、godoc
によってドキュメントとして表示されます。
技術的詳細
このコミットの技術的な核心は、go/printer
がASTノードだけでなく、それに関連するコメントも一緒に処理できるように拡張された点と、go/doc
がサンプルコードのASTを構築する際に、そのコメント情報もgo/printer
が理解できる形式で渡すように変更された点にあります。
go/printer
の変更点
-
CommentedNode
構造体の導入:go/printer
パッケージにCommentedNode
という新しい公開構造体が追加されました。type CommentedNode struct { Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt Comments []*ast.CommentGroup }
この構造体は、整形対象のASTノード(
Node
フィールド)と、そのノードに関連するコメントのリスト(Comments
フィールド)をバンドルします。これにより、go/printer
はASTノードを整形する際に、そのノードに直接関連付けられていないが、そのコードブロック内に存在するコメントも考慮に入れることができるようになります。 -
printer.printNode
関数の変更:printer
構造体の内部メソッドであるprintNode
が大幅に修正されました。- 入力として
interface{}
を受け取り、それが*CommentedNode
型であるかどうかをチェックします。もし*CommentedNode
であれば、そのNode
フィールドとComments
フィールドを抽出し、printer
の内部状態(p.comments
)に設定します。 - コメントが存在する場合、
getDoc
関数を使用してノードに関連するドキュメンテーションコメントを取得し、コメントの範囲をノードの開始位置と終了位置に基づいてフィルタリングします。これにより、関連するコメントのみが処理対象となります。 p.useNodeComments
というフラグが導入され、ノードに直接関連付けられたコメント(ast.File.Comments
など)を使用するか、CommentedNode
から提供されたコメントを使用するかを制御します。
- 入力として
-
getDoc
関数の追加:ast.Node
を受け取り、そのノードに直接関連付けられたドキュメンテーションコメントグループ(*ast.CommentGroup
)を返すヘルパー関数getDoc
が追加されました。これは、ImportSpec
,ValueSpec
,TypeSpec
,GenDecl
,FuncDecl
,File
などの特定のASTノード型に対して機能します。
go/doc
の変更点
-
Example.Body
フィールドの型変更:go/doc
パッケージのExample
構造体のBody
フィールドの型が、*ast.BlockStmt
から*printer.CommentedNode
に変更されました。// 変更前 // Body *ast.BlockStmt // code // 変更後 Body *printer.CommentedNode // code
これにより、
Example
構造体は、サンプルコードのASTブロックだけでなく、そのコードブロック全体に関連するコメント情報も保持できるようになりました。 -
Examples
関数の変更:Examples
関数内でExample
構造体を初期化する際に、f.Body
(*ast.BlockStmt
)を直接Body
フィールドに代入するのではなく、&printer.CommentedNode{f.Body, src.Comments}
という形でCommentedNode
を作成して代入するように変更されました。ここでsrc.Comments
は、元のソースファイル全体から収集されたすべてのコメントのリストです。これにより、go/doc
はサンプルコードのASTだけでなく、そのコードが属するファイル全体のコメント情報もgo/printer
に渡すことができるようになります。go/printer
は、このコメントリストの中から、実際にサンプルコードの範囲内にあるコメントを適切にフィルタリングして出力します。
全体的なフロー
go/doc
がソースファイルを解析し、Example
関数を特定します。Example
関数のボディ(*ast.BlockStmt
)と、そのファイル全体のコメントリスト(src.Comments
)をprinter.CommentedNode
にラップします。- この
CommentedNode
がdoc.Example
構造体のBody
フィールドに格納されます。 godoc
がドキュメントを生成する際に、doc.Example.Body
(*printer.CommentedNode
型)をgo/printer.Fprint
に渡します。go/printer.Fprint
は、内部でprinter.printNode
を呼び出し、CommentedNode
からASTノードとコメントリストを抽出します。printer.printNode
は、抽出されたコメントリストの中から、現在のASTノードの範囲内にあるコメントを特定し、整形されたコードと共に出力します。
この一連の変更により、サンプルコード内のコメントが、godoc
によって生成されるドキュメントに正確に反映されるようになりました。
コアとなるコードの変更箇所
src/pkg/go/doc/example.go
--- a/src/pkg/go/doc/example.go
+++ b/src/pkg/go/doc/example.go
@@ -8,15 +8,16 @@ package doc
import (
"go/ast"
+ "go/printer"
"strings"
"unicode"
"unicode/utf8"
)
type Example struct {
- Name string // name of the item being demonstrated
- Body *ast.BlockStmt // code
- Output string // expected output
+ Name string // name of the item being demonstrated
+ Body *printer.CommentedNode // code
+ Output string // expected output
}
func Examples(pkg *ast.Package) []*Example {
@@ -33,7 +34,7 @@ func Examples(pkg *ast.Package) []*Example {
}
examples = append(examples, &Example{
Name: name[len("Example"):],
- Body: f.Body,
+ Body: &printer.CommentedNode{f.Body, src.Comments},
Output: CommentText(f.Doc),
})
}
src/pkg/go/printer/printer.go
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -807,13 +807,75 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
return
}
+// getNode returns the ast.CommentGroup associated with n, if any.
+func getDoc(n ast.Node) *ast.CommentGroup {
+ switch n := n.(type) {
+ // *ast.Fields cannot be printed separately - ignore for now
+ case *ast.ImportSpec:
+ return n.Doc
+ case *ast.ValueSpec:
+ return n.Doc
+ case *ast.TypeSpec:
+ return n.Doc
+ case *ast.GenDecl:
+ return n.Doc
+ case *ast.FuncDecl:
+ return n.Doc
+ case *ast.File:
+ return n.Doc
+ }
+ return nil
+}
+
func (p *printer) printNode(node interface{}) error {
+\t// unpack *CommentedNode, if any
+\tvar comments []*ast.CommentGroup
+\tif cnode, ok := node.(*CommentedNode); ok {
+\t\tnode = cnode.Node
+\t\tcomments = cnode.Comments
+\t}\n\n+\tif comments != nil {\n+\t\t// commented node - restrict comment list to relevant range
+\t\tn, ok := node.(ast.Node)\n+\t\tif !ok {\n+\t\t\tgoto unsupported\n+\t\t}\n+\t\tbeg := n.Pos()\n+\t\tend := n.End()\n+\t\t// if the node has associated documentation,
+\t\t// include that commentgroup in the range
+\t\t// (the comment list is sorted in the order
+\t\t// of the comment appearance in the source code)
+\t\tif doc := getDoc(n); doc != nil {\n+\t\t\tbeg = doc.Pos()\n+\t\t}\n+\t\t// token.Pos values are global offsets, we can
+\t\t// compare them directly
+\t\ti := 0\n+\t\tfor i < len(comments) && comments[i].End() < beg {\n+\t\t\ti++\n+\t\t}\n+\t\tj := i\n+\t\tfor j < len(comments) && comments[j].Pos() < end {\n+\t\t\tj++\n+\t\t}\n+\t\tif i < j {\n+\t\t\tp.comments = comments[i:j]\n+\t\t}\n+\t} else if n, ok := node.(*ast.File); ok {\n+\t\t// use ast.File comments, if any
+\t\tp.comments = n.Comments
+\t}\n\n+\t// if there are no comments, use node comments
+\tp.useNodeComments = p.comments == nil
+\n+\t// format node
\tswitch n := node.(type) {\n \tcase ast.Expr:\n-\t\tp.useNodeComments = true
\t\tp.expr(n, ignoreMultiLine)\n \tcase ast.Stmt:\n-\t\tp.useNodeComments = true
\t\t// A labeled statement will un-indent to position the\n \t\t// label. Set indent to 1 so we don't get indent "underflow".\n \t\tif _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt {\n@@ -821,19 +883,19 @@ func (p *printer) printNode(node interface{}) error {\n \t\t}\n \t\tp.stmt(n, false, ignoreMultiLine)\n \tcase ast.Decl:\n-\t\tp.useNodeComments = true
\t\tp.decl(n, ignoreMultiLine)\n \tcase ast.Spec:\n-\t\tp.useNodeComments = true
\t\tp.spec(n, 1, false, ignoreMultiLine)\n \tcase *ast.File:\n-\t\tp.comments = n.Comments
-\t\tp.useNodeComments = n.Comments == nil
\t\tp.file(n)\n \tdefault:\n-\t\treturn fmt.Errorf("go/printer: unsupported node type %T", n)\n+\t\tgoto unsupported
\t}\n+\n \treturn nil\n+\n+unsupported:\n+\treturn fmt.Errorf("go/printer: unsupported node type %T", node)\n }\n \n // ----------------------------------------------------------------------------\n@@ -1001,10 +1063,18 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{\n \treturn\n }\n \n+// A CommentedNode bundles an AST node and corresponding comments.\n+// It may be provided as argument to any of the FPrint functions.\n+//\n+type CommentedNode struct {\n+\tNode interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt\n+\tComments []*ast.CommentGroup\n+}\n+\n // Fprint "pretty-prints" an AST node to output for a given configuration cfg.\n // Position information is interpreted relative to the file set fset.\n-// The node type must be *ast.File, or assignment-compatible to ast.Expr,\n-// ast.Decl, ast.Spec, or ast.Stmt.\n+// The node type must be *ast.File, *CommentedNode, or assignment-compatible\n+// to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.\n //\n func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {\n \treturn cfg.fprint(output, fset, node, make(map[ast.Node]int))\n```
### `src/pkg/sort/example_test.go`
```diff
--- a/src/pkg/sort/example_test.go
+++ b/src/pkg/sort/example_test.go
@@ -11,7 +11,7 @@ import (
// [1 2 3 4 5 6]
func ExampleInts() {
-\ts := []int{5, 2, 6, 3, 1, 4}\n+\ts := []int{5, 2, 6, 3, 1, 4} // unsorted
\tsort.Ints(s)\n \tfmt.Println(s)\n }\n```
## コアとなるコードの解説
### `src/pkg/go/doc/example.go`の変更
- **`Example`構造体の変更**: `Body`フィールドの型が`*ast.BlockStmt`から`*printer.CommentedNode`に変更されました。これにより、サンプルコードのASTブロックだけでなく、そのブロックに関連するコメントも`Example`構造体の一部として保持できるようになります。
- **`Examples`関数の変更**: `Example`構造体を生成する際に、`f.Body`(`*ast.BlockStmt`)と`src.Comments`(ソースファイル全体のコメントリスト)を`printer.CommentedNode`にラップして`Body`フィールドに代入しています。これは、`go/printer`がコメントを処理するために必要な情報を`go/doc`から受け取れるようにするための重要な変更です。
### `src/pkg/go/printer/printer.go`の変更
- **`getDoc`関数の追加**: この関数は、特定のASTノード(`ImportSpec`, `ValueSpec`, `TypeSpec`, `GenDecl`, `FuncDecl`, `File`)に付随するドキュメンテーションコメント(`Doc`フィールド)を安全に取得するためのヘルパーです。これは、`printNode`内でコメントの範囲を決定する際に利用されます。
- **`printNode`関数の変更**:
- まず、入力`node`が`*CommentedNode`型であるかをチェックします。もしそうであれば、その`Node`フィールドと`Comments`フィールドを抽出し、`printer`の内部状態(`p.comments`)に設定します。これにより、`printer`は整形対象のASTノードだけでなく、そのノードに関連するコメントのリストも利用できるようになります。
- コメントリストが存在する場合、ノードの開始位置(`n.Pos()`)と終了位置(`n.End()`)に基づいて、関連するコメントのみをフィルタリングします。`getDoc(n)`が返すドキュメンテーションコメントの開始位置も考慮に入れ、コメントの範囲を正確に特定します。
- `p.useNodeComments`という新しいフラグが導入され、`p.comments`が設定されていない場合にのみ、ノードに直接関連付けられたコメント(例: `ast.File.Comments`)を使用するように制御します。
- `switch n := node.(type)`ブロックの各ケースから`p.useNodeComments = true`の行が削除されました。これは、コメントの処理ロジックが`printNode`の冒頭で一元的に行われるようになったためです。
- サポートされていないノードタイプの場合のエラーハンドリングが`goto unsupported`を使って統一されました。
- **`CommentedNode`構造体の定義**: `go/printer`パッケージの公開型として`CommentedNode`が定義されました。これは、ASTノードとそれに関連するコメントグループのリストを保持するためのコンテナです。
- **`Config.Fprint`関数のシグネチャ変更**: `node interface{}`のコメントが更新され、`*CommentedNode`も有効な入力タイプとして明示されました。
### `src/pkg/sort/example_test.go`の変更
- これはテストファイルであり、既存の`ExampleInts`関数に`// unsorted`というコメントが追加されています。この変更は、このコミットによって`go/printer`と`go/doc`がコメントを正しく処理できるようになったことを示すための具体的なテストケース(またはその準備)として機能します。このコメントが`godoc`で表示されることを確認するためのものです。
これらの変更により、Goのドキュメンテーションシステムは、サンプルコードのコメントを正確に抽出し、整形して表示できるようになり、ドキュメントの品質と可読性が向上しました。
## 関連リンク
- Go Issue #2429: [https://github.com/golang/go/issues/2429](https://github.com/golang/go/issues/2429) (このコミットが修正したIssue)
- Go Gerrit Change-ID: `5482052` ([https://golang.org/cl/5482052](https://golang.org/cl/5482052))
## 参考にした情報源リンク
- Go言語の公式ドキュメント: `go/ast`, `go/token`, `go/printer`, `go/doc`パッケージのドキュメント
- Go言語のソースコード: 特に`src/pkg/go/doc/example.go`と`src/pkg/go/printer/printer.go`の変更履歴
- Go言語のIssueトラッカー: Issue #2429の議論