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

[インデックス 10759] ファイルの概要

このコミットは、Go言語の標準ライブラリであるgo/printergo/docパッケージに、サンプルコード内のコメントを正しく出力する機能を追加するものです。具体的には、go/printerCommentedNodeという新しい構造体をサポートし、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言語の標準パッケージと概念に関する知識が必要です。

  1. go/astパッケージ (Abstract Syntax Tree): Goのソースコードを解析し、その構造を抽象構文木(AST)として表現するためのパッケージです。ASTは、プログラムの構造を木構造で表現したもので、各ノードがコードの要素(変数宣言、関数呼び出し、コメントなど)に対応します。

    • ast.Node: ASTのすべてのノードが実装するインターフェース。
    • ast.BlockStmt: コードブロック({ ... })を表すステートメント。
    • ast.CommentGroup: 複数のコメント(// comment/* comment */)をまとめたグループ。
    • ast.File: 単一のGoソースファイル全体のAST表現。ファイル内のすべての宣言、ステートメント、コメントなどを含みます。
  2. go/tokenパッケージ: Goのソースコードをトークン(キーワード、識別子、演算子など)に分割するためのパッケージです。

    • token.Position: ソースコード内の特定の場所(ファイル名、行番号、列番号、オフセット)を表す構造体。
  3. go/printerパッケージ: go/astパッケージによって生成されたASTを、Goの標準的なフォーマット規則に従って整形し、ソースコードとして出力するためのパッケージです。このパッケージは、go fmtコマンドの基盤となっています。

    • printer.Fprint: ASTノードを整形して指定されたio.Writerに出力する主要な関数。
  4. go/docパッケージ: Goのソースコードからドキュメンテーションを生成するためのパッケージです。godocコマンドはこのパッケージを利用しています。

    • doc.Example構造体: Example関数として記述されたサンプルコードの情報を保持します。これには、サンプルコードの本体(AST)、期待される出力、および説明が含まれます。
  5. Example関数: Goのテストファイル(_test.go)内に記述される特別な関数で、Exampleというプレフィックスを持ちます(例: ExampleInts)。これらの関数は、パッケージや特定の関数の使用例を示すために用いられ、go testコマンドで実行可能であり、godocによってドキュメントとして表示されます。

技術的詳細

このコミットの技術的な核心は、go/printerがASTノードだけでなく、それに関連するコメントも一緒に処理できるように拡張された点と、go/docがサンプルコードのASTを構築する際に、そのコメント情報もgo/printerが理解できる形式で渡すように変更された点にあります。

go/printerの変更点

  1. 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ノードを整形する際に、そのノードに直接関連付けられていないが、そのコードブロック内に存在するコメントも考慮に入れることができるようになります。

  2. printer.printNode関数の変更: printer構造体の内部メソッドであるprintNodeが大幅に修正されました。

    • 入力としてinterface{}を受け取り、それが*CommentedNode型であるかどうかをチェックします。もし*CommentedNodeであれば、そのNodeフィールドとCommentsフィールドを抽出し、printerの内部状態(p.comments)に設定します。
    • コメントが存在する場合、getDoc関数を使用してノードに関連するドキュメンテーションコメントを取得し、コメントの範囲をノードの開始位置と終了位置に基づいてフィルタリングします。これにより、関連するコメントのみが処理対象となります。
    • p.useNodeCommentsというフラグが導入され、ノードに直接関連付けられたコメント(ast.File.Commentsなど)を使用するか、CommentedNodeから提供されたコメントを使用するかを制御します。
  3. getDoc関数の追加: ast.Nodeを受け取り、そのノードに直接関連付けられたドキュメンテーションコメントグループ(*ast.CommentGroup)を返すヘルパー関数getDocが追加されました。これは、ImportSpec, ValueSpec, TypeSpec, GenDecl, FuncDecl, Fileなどの特定のASTノード型に対して機能します。

go/docの変更点

  1. Example.Bodyフィールドの型変更: go/docパッケージのExample構造体のBodyフィールドの型が、*ast.BlockStmtから*printer.CommentedNodeに変更されました。

    // 変更前
    // Body   *ast.BlockStmt // code
    // 変更後
    Body   *printer.CommentedNode // code
    

    これにより、Example構造体は、サンプルコードのASTブロックだけでなく、そのコードブロック全体に関連するコメント情報も保持できるようになりました。

  2. Examples関数の変更: Examples関数内でExample構造体を初期化する際に、f.Body*ast.BlockStmt)を直接Bodyフィールドに代入するのではなく、&printer.CommentedNode{f.Body, src.Comments}という形でCommentedNodeを作成して代入するように変更されました。ここでsrc.Commentsは、元のソースファイル全体から収集されたすべてのコメントのリストです。これにより、go/docはサンプルコードのASTだけでなく、そのコードが属するファイル全体のコメント情報もgo/printerに渡すことができるようになります。go/printerは、このコメントリストの中から、実際にサンプルコードの範囲内にあるコメントを適切にフィルタリングして出力します。

全体的なフロー

  1. go/docがソースファイルを解析し、Example関数を特定します。
  2. Example関数のボディ(*ast.BlockStmt)と、そのファイル全体のコメントリスト(src.Comments)をprinter.CommentedNodeにラップします。
  3. このCommentedNodedoc.Example構造体のBodyフィールドに格納されます。
  4. godocがドキュメントを生成する際に、doc.Example.Body*printer.CommentedNode型)をgo/printer.Fprintに渡します。
  5. go/printer.Fprintは、内部でprinter.printNodeを呼び出し、CommentedNodeからASTノードとコメントリストを抽出します。
  6. 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の議論