[インデックス 11892] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールであるgodoc
と、ドキュメンテーション生成ライブラリであるgo/doc
における、Example関数の表示方法に関する改善です。具体的には、単一のExample関数しか含まないファイルが、そのファイル全体をExampleとして表示する「whole file example」として扱われるように変更されました。
コミット
commit e11632ee0044474d3e767192d6f61e6ab010c48d
Author: Andrew Gerrand <adg@golang.org>
Date: Tue Feb 14 17:19:59 2012 +1100
go/doc, godoc: regard lone examples as "whole file" examples
Fixes #2930.
R=r, gri, rsc
CC=golang-dev
https://golang.org/cl/5657048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e11632ee0044474d3e767192d6f61e6ab010c48d
元コミット内容
このコミットの目的は、「単一のExample関数しか含まないファイル」を「whole file example」として扱うことです。これにより、godoc
が生成するドキュメンテーションにおいて、Example関数だけでなく、そのExampleが依存する型、変数、定数などの宣言もExampleコードの一部として表示されるようになります。これは、GoのIssue #2930を解決するための変更です。
変更の背景
Go言語のドキュメンテーションツールgodoc
は、コード内のExample関数(ExampleF
のような命名規則に従う関数)を抽出し、そのコードと出力をドキュメンテーションに含める機能を持っています。しかし、これまでの実装では、Example関数が定義されているファイル内に他の関数や型、変数などの宣言があっても、Example関数本体のコードブロックのみが抽出されて表示されていました。
これにより、Exampleが依存する補助的なコード(例えば、Example内で使用されるカスタム型やヘルパー関数)がドキュメンテーションに表示されず、Exampleが単独では理解しにくい、あるいは不完全に見えるという問題がありました。特に、Exampleがそのファイル内で定義された特定の型や関数に密接に結合している場合、Example関数本体だけを見ても、そのExampleが何を示しているのか、どのように動作するのかが不明瞭になることがありました。
このコミットは、このような問題を解決するために、特定の条件を満たすExample関数(ファイル内に単一のExample関数しかなく、かつ他のトップレベル宣言が存在し、テストやベンチマーク関数がない場合)に対して、Example関数本体だけでなく、ファイル全体をExampleコードとして表示するようにgodoc
の振る舞いを変更することを目的としています。これにより、Exampleの完全性と理解しやすさが向上します。
前提知識の解説
Go言語のExample関数
Go言語には、コードの利用例を示すための特別な関数である「Example関数」があります。これらはExample
というプレフィックスで始まり、ExamplePackage
、ExampleType
、ExampleFunction
、ExampleType_Method
といった命名規則に従います。Example関数は、go test
コマンドで実行され、その出力が期待される出力と一致するかどうかを検証することができます。また、godoc
ツールによって自動的に抽出され、生成されるドキュメンテーションにコードスニペットとして表示されます。
Example関数は、Goのドキュメンテーションにおいて非常に重要な役割を果たします。単なるAPIリファレンスだけでなく、実際にコードがどのように使われるかを示すことで、ライブラリやパッケージの理解を深める手助けとなります。
godoc
ツールとgo/doc
パッケージ
godoc
: Go言語のソースコードからドキュメンテーションを生成し、HTTPサーバーとして提供するツールです。開発者がローカルでGoの標準ライブラリや自身のプロジェクトのドキュメンテーションを閲覧する際に利用されます。godoc
は、コードコメント、関数シグネチャ、Example関数などを解析して、整形されたHTMLドキュメントを生成します。go/doc
パッケージ:godoc
ツールが内部的に利用するGoの標準ライブラリです。Goのソースコード(AST: Abstract Syntax Tree)を解析し、パッケージ、型、関数、Example関数などのドキュメンテーション情報を抽出するための機能を提供します。このパッケージが、Example関数をどのように解釈し、どの範囲のコードをExampleとして扱うかを決定する中心的な役割を担っています。
AST (Abstract Syntax Tree)
ASTは、プログラムのソースコードの抽象的な構文構造をツリー形式で表現したものです。コンパイラやリンター、ドキュメンテーションツールなどがソースコードを解析する際に利用します。Go言語のgo/ast
パッケージは、GoのソースコードをASTとしてパースする機能を提供します。このコミットでは、go/doc
パッケージがASTを操作して、Example関数のボディをファイル全体に置き換えることで、「whole file example」を実現しています。
技術的詳細
このコミットの核心は、src/pkg/go/doc/example.go
内のExamples
関数のロジック変更にあります。
変更前は、Examples
関数は各ソースファイルからExample
プレフィックスを持つ関数を抽出し、その関数のボディ(f.Body
)をExampleのコードとして扱っていました。
変更後は、以下の条件がすべて満たされた場合に、Example関数のボディをファイル全体(file
ASTノード)に置き換えるロジックが追加されました。
- ファイル内にテスト関数やベンチマーク関数が存在しないこと (
!hasTests
): Example関数とテスト/ベンチマーク関数が混在しているファイルでは、この「whole file example」のロジックは適用されません。これは、テストやベンチマークのコードがExampleとして表示されることを避けるためです。 - ファイル内にインポート宣言以外のトップレベル宣言が複数存在すること (
numDecl > 1
): Example関数以外にも、型、変数、定数、または他の関数などの宣言がファイル内に存在する場合に適用されます。これにより、Exampleが単独で存在する場合(例えば、Example関数しか含まないファイル)は、これまで通りExample関数本体のみが表示され、Exampleが他の補助的な宣言に依存している場合にのみ「whole file example」が適用されるようになります。 - ファイル内に単一のExample関数のみが存在すること (
len(flist) == 1
): 複数のExample関数が存在するファイルでは、どのExampleを「whole file example」として扱うべきか不明確になるため、このロジックは適用されません。
これらの条件が満たされた場合、抽出された単一のExampleオブジェクトのBody.Node
フィールドが、Example関数本体のASTノードから、そのExample関数が定義されているファイル全体のASTノードに置き換えられます。これにより、godoc
がExampleコードをレンダリングする際に、Example関数本体だけでなく、ファイル全体のコードが表示されるようになります。
また、src/cmd/godoc/godoc.go
では、HTML出力時にExampleコードの整形方法が調整されています。これまでの実装では、Exampleコードの先頭と末尾の波括弧を無条件に削除し、インデントを解除していました。この変更により、コードが波括弧で始まる場合にのみこれらの整形処理を行うようになり、「whole file example」のようにファイル全体がExampleとして扱われる場合に、不要な波括弧の削除やインデント解除が行われないように修正されました。
コアとなるコードの変更箇所
src/pkg/go/doc/example.go
--- a/src/pkg/go/doc/example.go
+++ b/src/pkg/go/doc/example.go
@@ -9,6 +9,7 @@ package doc
import (
"go/ast"
"go/printer"
+ "go/token"
"strings"
"unicode"
"unicode/utf8"
@@ -21,28 +22,47 @@ type Example struct {
}
func Examples(pkg *ast.Package) []*Example {
- var examples []*Example
- for _, src := range pkg.Files {
- for _, decl := range src.Decls {
+ var list []*Example
+ for _, file := range pkg.Files {
+ hasTests := false // file contains tests or benchmarks
+ numDecl := 0 // number of non-import declarations in the file
+ var flist []*Example
+ for _, decl := range file.Decls {
+ if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
+ numDecl++
+ continue
+ }
f, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
+ numDecl++
name := f.Name.Name
+ if isTest(name, "Test") || isTest(name, "Benchmark") {
+ hasTests = true
+ continue
+ }
if !isTest(name, "Example") {
continue
}
- examples = append(examples, &Example{
+ flist = append(flist, &Example{
Name: name[len("Example"):],
Body: &printer.CommentedNode{
Node: f.Body,
- Comments: src.Comments,
+ Comments: file.Comments,
},
Output: f.Doc.Text(),
})
}
+ if !hasTests && numDecl > 1 && len(flist) == 1 {
+ // If this file only has one example function, some
+ // other top-level declarations, and no tests or
+ // benchmarks, use the whole file as the example.
+ flist[0].Body.Node = file
+ }
+ list = append(list, flist...)
}
- return examples
+ return list
}
// isTest tells whether name looks like a test, example, or benchmark.
src/cmd/godoc/godoc.go
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -516,10 +516,13 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
continue
}
- // print code, unindent and remove surrounding braces
+ // print code
code := node_htmlFunc(eg.Body, fset)
- code = strings.Replace(code, "\n ", "\n", -1)
- code = code[2 : len(code)-2]
+ if len(code) > 0 && code[0] == '{' {
+ // unindent and remove surrounding braces
+ code = strings.Replace(code, "\n ", "\n", -1)
+ code = code[2 : len(code)-2]
+ }
err := exampleHTML.Execute(&buf, struct {
Name, Code, Output string
コアとなるコードの解説
src/pkg/go/doc/example.go
の変更
token
パッケージのインポート:token.IMPORT
定数を使用するためにgo/token
パッケージがインポートされました。- ファイルごとの処理の導入:
Examples
関数は、パッケージ内の各ファイル(pkg.Files
)を個別にループ処理するようになりました。 hasTests
とnumDecl
の追跡:hasTests
は、現在のファイルにテスト関数(Test
プレフィックス)またはベンチマーク関数(Benchmark
プレフィックス)が含まれているかどうかを示すフラグです。numDecl
は、現在のファイル内のインポート宣言以外のトップレベル宣言(関数、型、変数、定数)の数をカウントします。
- Example関数の収集と条件付き「whole file」化:
- 各ファイル内で見つかったExample関数は一時的なリスト
flist
に収集されます。 - Example関数が見つかるたびに
numDecl
もインクリメントされます。 - テスト関数やベンチマーク関数が見つかった場合、
hasTests
がtrue
に設定されます。 - ループの最後に、以下の条件がチェックされます。
!hasTests
: ファイルにテスト/ベンチマーク関数がない。numDecl > 1
: インポート以外のトップレベル宣言が複数ある。len(flist) == 1
: Example関数がちょうど1つだけある。
- これらの条件がすべて満たされた場合、
flist
内の唯一のExampleオブジェクトのBody.Node
が、Example関数本体のASTノードから、ファイル全体のASTノード(file
)に置き換えられます。これが「whole file example」を実現する核心部分です。
- 各ファイル内で見つかったExample関数は一時的なリスト
- 最終的なExampleリストの構築: 各ファイルから抽出されたExample(「whole file example」を含む)が、最終的な
list
に結合されて返されます。
src/cmd/godoc/godoc.go
の変更
- 条件付きコード整形:
example_htmlFunc
関数内で、ExampleコードをHTMLとして表示する際の整形ロジックが変更されました。 - 変更前は、Exampleコードの先頭と末尾の波括弧を無条件に削除し、インデントを解除していました。
- 変更後は、
if len(code) > 0 && code[0] == '{'
という条件が追加されました。これにより、Exampleコードが波括弧で始まる場合にのみ、波括弧の削除とインデント解除が行われるようになりました。 - この変更は、「whole file example」が導入されたことによるものです。「whole file example」の場合、Exampleのボディはファイル全体になるため、先頭に波括弧がない可能性があります。無条件に波括弧を削除しようとすると、予期せぬ結果になるため、この条件付きの整形が必要となりました。
関連リンク
- Go Issue #2930: https://github.com/golang/go/issues/2930
- Go CL 5657048: https://golang.org/cl/5657048
参考にした情報源リンク
- Go言語の公式ドキュメンテーション (Example関数に関する記述): https://go.dev/blog/examples
go/doc
パッケージのドキュメンテーション: https://pkg.go.dev/go/docgodoc
ツールのドキュメンテーション: https://pkg.go.dev/cmd/godoc- Go言語のASTに関する情報 (例:
go/ast
パッケージ): https://pkg.go.dev/go/ast