[インデックス 1933] ファイルの概要
このコミットは、Go言語のドキュメンテーション生成ツール(当時のgodoc
の前身、あるいはそれに類するツールの一部と推測される)における機能改善に関するものです。具体的には、型に関連するファクトリ関数(その型を生成する関数)もドキュメントに適切に表示されるように、ドキュメント生成ロジックが変更されています。
変更されたファイルは以下の通りです。
usr/gri/pretty/docprinter.go
: ドキュメントの構造を定義し、Goのソースコードから情報を抽出し、HTML形式で整形して出力する主要なロジックが含まれています。usr/gri/pretty/template.html
: 生成されるドキュメントのHTMLテンプレートです。
コミット
commit 78f7063d9da264f98ccd4c4f4d1ae28b26c510bd
Author: Robert Griesemer <gri@golang.org>
Date: Tue Mar 31 18:46:21 2009 -0700
- also associate factory methods to a type in documentation
R=r
OCL=26974
CL=26976
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/78f7063d9da264f98ccd4c4f4d1ae28b26c510bd
元コミット内容
diff --git a/usr/gri/pretty/docprinter.go b/usr/gri/pretty/docprinter.go
index 87449b3db8..f724f7d5f6 100644
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -56,6 +56,7 @@ type funcDoc struct {
type typeDoc struct {
decl *ast.TypeDecl;
+ factories map[string] *funcDoc;
methods map[string] *funcDoc;
}
@@ -84,6 +85,61 @@ func (doc *PackageDoc) Init(name string) {
}
+func baseTypeName(typ ast.Expr) string {
+ switch t := typ.(type) {
+ case *ast.Ident:
+ return string(t.Lit);
+ case *ast.StarExpr:
+ return baseTypeName(t.X);
+ }
+ return "";
+}
+
+
+func (doc *PackageDoc) lookupTypeDoc(typ ast.Expr) *typeDoc {
+ tdoc, found := doc.types[baseTypeName(typ)];
+ if found {
+ return tdoc;
+ }
+ return nil;
+}
+
+
+func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
+ name := string(fun.Name.Lit);
+ fdoc := &funcDoc{fun};
+
+ // determine if it should be associated with a type
+ var typ *typeDoc;
+ if fun.Recv != nil {
+ // method
+ typ = doc.lookupTypeDoc(fun.Recv.Type);
+ if typ != nil {
+ typ.methods[name] = fdoc;
+ return;
+ }
+ } else {
+ // perhaps a factory function
+ // determine result type, if any
+ if len(fun.Type.Results) >= 1 {
+ res := fun.Type.Results[0];
+ if len(res.Names) <= 1 {
+ // exactly one (named or anonymous) result type
+ typ = doc.lookupTypeDoc(res.Type);
+ if typ != nil {
+ typ.factories[name] = fdoc;
+ return;
+ }
+ }
+ }
+ }
+ // TODO other heuristics (e.g. name is "NewTypename"?)
+
+ // ordinary function
+ doc.funcs[name] = fdoc;
+}
+
+
func (doc *PackageDoc) addDecl(decl ast.Decl) {
switch d := decl.(type) {
case *ast.ImportDecl:
@@ -95,7 +151,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) {
if isExported(d.Name) {
// TODO only add if not there already - or ignore?
name := string(d.Name.Lit);
- tdoc := &typeDoc{d, make(map[string] *funcDoc)};
+ tdoc := &typeDoc{d, make(map[string] *funcDoc), make(map[string] *funcDoc)};
doc.types[name] = tdoc;
}
@@ -105,28 +161,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) {
case *ast.FuncDecl:
if isExported(d.Name) {
- if d.Recv != nil {
- // method
- // determine receiver type name
- var name string;
- switch t := d.Recv.Type.(type) {
- case *ast.Ident:
- name = string(t.Lit);
- case *ast.StarExpr:
- // recv must be of the form *name
- name = string(t.X.(*ast.Ident).Lit)
- }
- typ, found := doc.types[name];
- if found {
- fdoc := &funcDoc{d};
- typ.methods[string(d.Name.Lit)] = fdoc;
- }
- // otherwise ignore
- } else {
- // ordinary function
- fdoc := &funcDoc{d};
- doc.funcs[string(d.Name.Lit)] = fdoc;
- }
+ doc.addFunc(d);
}
case *ast.DeclList:
@@ -236,14 +271,14 @@ func (c *constDoc) printConsts(p *astPrinter.Printer) {
}
-func (f *funcDoc) print(p *astPrinter.Printer) {
+func (f *funcDoc) print(p *astPrinter.Printer, hsize int) {
d := f.decl;
if d.Recv != nil {
- p.Printf("<h3>func (");
+ p.Printf("<h%d>func (", hsize);
p.Expr(d.Recv.Type);
- p.Printf(") %s</h3>\n", d.Name.Lit);
+ p.Printf(") %s</h%d>\n", d.Name.Lit, hsize);
} else {
- p.Printf("<h2>func %s</h2>\n", d.Name.Lit);
+ p.Printf("<h%d>func %s</h%d>\n", hsize, d.Name.Lit, hsize);
}\n p.Printf("<code>\");
p.DoFuncDecl(d);
@@ -265,8 +300,12 @@ func (t *typeDoc) print(p *astPrinter.Printer) {
}
// print associated methods, if any
+ for name, m := range t.factories {
+ m.print(p, 3);
+ }
+
for name, m := range t.methods {
- m.print(p);
+ m.print(p, 3);
}
}
@@ -348,9 +387,9 @@ func (doc *PackageDoc) Print(writer io.Write) {
"FUNCTIONS-->" :
func() {
+ p.Printf("<hr />\n");
for name, f := range doc.funcs {
- p.Printf("<hr />\n");
- f.print(&p);\n+ f.print(&p, 2);\n }\n },\n });\ndiff --git a/usr/gri/pretty/template.html b/usr/gri/pretty/template.html\nindex e4a7550dd7..ab415c9272 100644\n--- a/usr/gri/pretty/template.html\n+++ b/usr/gri/pretty/template.html\n@@ -13,14 +13,6 @@\n \n <!--FUNCTIONS-->\n \n-<hr />\n-<h1>Implementation</h1>\n-<font color=grey>Comments are currently not shown in the source.</font>\n-\n-<pre>\n-<!--PACKAGE_BODY-->\n-</pre>\n-\n </div> <!-- content -->\n </body>\n </html>\n```
## 変更の背景
このコミットの主な目的は、Go言語のドキュメンテーション生成において、特定の型に関連する「ファクトリ関数」もその型のドキュメントの一部として表示されるようにすることです。
Go言語では、特定の型の新しいインスタンスを生成するための関数を「ファクトリ関数」と呼ぶ慣習があります(例: `NewType()`)。これらは厳密にはその型のメソッドではありませんが、その型と密接に関連しており、ドキュメント上で一緒に表示されることで、ユーザーがその型をどのようにインスタンス化すればよいかを理解しやすくなります。
この変更以前は、ドキュメント生成ツールはメソッド(レシーバを持つ関数)のみを型に関連付けていました。このコミットにより、戻り値の型に基づいてファクトリ関数を識別し、関連する型のドキュメントに含めることで、生成されるドキュメントの網羅性と利便性が向上しました。
## 前提知識の解説
### Go言語の基本的な型と関数
Go言語では、`struct`キーワードを使ってカスタムのデータ型を定義できます。関数は`func`キーワードで定義され、引数と戻り値を持つことができます。
### Go言語におけるメソッドとレシーバ
Go言語のメソッドは、特定の型に関連付けられた関数です。メソッドは、関数名の前にレシーバ引数を指定することで定義されます。レシーバは、そのメソッドがどの型の値に対して呼び出されるかを示します。
```go
type MyType struct {
Value int
}
// MyTypeのメソッド
func (m MyType) GetValue() int {
return m.Value
}
ファクトリ関数
Go言語におけるファクトリ関数は、特定の型の新しいインスタンスを生成し、それを返す通常の関数です。メソッドとは異なり、レシーバを持ちません。慣習的に、New
プレフィックスを付けて命名されることが多いです(例: NewMyType()
)。
type MyType struct {
Value int
}
// MyTypeのファクトリ関数
func NewMyType(value int) *MyType {
return &MyType{Value: value}
}
Goのドキュメンテーションツール(godoc
)
Go言語には、ソースコードから自動的にドキュメントを生成するgodoc
というツールがあります。godoc
は、Goのソースコードを解析し、コメントや宣言から情報を抽出し、HTML形式で整形されたドキュメントを生成します。このコミットが行われた2009年当時、godoc
はまだ初期段階にあり、その機能は継続的に改善されていました。usr/gri/pretty
というパスは、Goの初期開発者の一人であるRobert Griesemer氏が開発していた、ドキュメント生成に関連する実験的な、あるいは初期のコードベースの一部であった可能性が高いです。
ast
(Abstract Syntax Tree) パッケージ
Goコンパイラやツールは、ソースコードを直接扱うのではなく、その抽象構文木(AST)を生成して解析します。go/ast
パッケージは、GoのソースコードのASTを表現するためのデータ構造を提供します。ドキュメンテーションツールは、このASTを走査することで、型、関数、メソッドなどの情報をプログラム的に抽出します。
技術的詳細
このコミットは、docprinter.go
内のドキュメント生成ロジックを大幅に拡張しています。
-
typeDoc
構造体の拡張:typeDoc
構造体は、特定の型に関するドキュメント情報を保持します。このコミットにより、factories
という新しいmap[string]*funcDoc
フィールドが追加されました。これにより、型に関連するファクトリ関数を、その型のメソッドとは別に管理できるようになります。type typeDoc struct { decl *ast.TypeDecl; factories map[string] *funcDoc; // 新しく追加 methods map[string] *funcDoc; }
-
ファクトリ関数を識別し関連付けるロジックの追加:
addFunc
という新しい関数が導入されました。この関数は、ast.FuncDecl
(関数のASTノード)を受け取り、それが通常の関数なのか、メソッドなのか、あるいはファクトリ関数なのかを判断し、適切なPackageDoc
またはtypeDoc
に関連付けます。- メソッドの識別:
fun.Recv != nil
(レシーバが存在する)の場合、その関数はメソッドと判断されます。lookupTypeDoc
を使ってレシーバの型に対応するtypeDoc
を探し、見つかればそのmethods
マップに登録します。 - ファクトリ関数の識別:
fun.Recv == nil
(レシーバが存在しない)の場合、通常の関数またはファクトリ関数と判断されます。ファクトリ関数であるかどうかを判断するために、関数の戻り値の型がチェックされます。len(fun.Type.Results) >= 1
:戻り値が1つ以上あるか。len(res.Names) <= 1
:戻り値が1つ(名前付きまたは匿名)であるか。- これらの条件を満たし、かつ戻り値の型が既存の型と一致する場合、その関数はファクトリ関数と見なされ、対応する
typeDoc
のfactories
マップに登録されます。
- 通常の関数: 上記のいずれにも該当しない場合、その関数は通常のパッケージレベルの関数として
PackageDoc
のfuncs
マップに登録されます。
このロジックは、Goのソースコードを解析し、関数のシグネチャ(特にレシーバの有無と戻り値の型)に基づいて、その関数がドキュメント上でどのカテゴリに属するかを自動的に分類するものです。
- メソッドの識別:
-
型名抽出ヘルパー関数の追加:
baseTypeName(typ ast.Expr) string
: ASTの型表現(ast.Expr
)から、その基本となる型名(例:*MyType
からMyType
)を抽出するためのヘルパー関数です。ポインタ型の場合も適切に基底の型名を返します。lookupTypeDoc(typ ast.Expr) *typeDoc
:baseTypeName
を使って型名を取得し、PackageDoc
内のtypes
マップから対応するtypeDoc
を検索します。
-
ドキュメント出力の調整:
funcDoc.print
メソッドにhsize
(HTMLの見出しレベル)パラメータが追加されました。これにより、関数やメソッドの表示において、より柔軟な見出しレベル(<h2>
,<h3>
など)を使用できるようになりました。これにより、ファクトリ関数やメソッドが型のドキュメント内で適切な階層で表示されるようになります。typeDoc.print
メソッド内で、methods
をプリントする前にfactories
マップをイテレートし、それぞれのファクトリ関数をhsize=3
でプリントするロジックが追加されました。PackageDoc.Print
内のFUNCTIONS
セクションの出力ロジックが変更され、不要な<hr />
タグの出力が削除され、関数がhsize=2
でプリントされるようになりました。template.html
から、<h1>Implementation</h1>
などの静的なHTML要素が削除されました。これは、docprinter.go
がより動的にHTML構造を生成するようになったため、テンプレートから冗長な要素を削除したものです。
コアとなるコードの変更箇所
usr/gri/pretty/docprinter.go
-
typeDoc
構造体へのfactories
フィールドの追加:--- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -56,6 +56,7 @@ type funcDoc struct { type typeDoc struct { decl *ast.TypeDecl; + factories map[string] *funcDoc; methods map[string] *funcDoc; }
-
baseTypeName
,lookupTypeDoc
,addFunc
関数の新規追加: これらの関数は、コミットの差分で新規に追加されたブロック全体です。特にaddFunc
は、関数がメソッドかファクトリ関数かを判別する中心的なロジックを含んでいます。 -
PackageDoc.addDecl
におけるast.FuncDecl
の処理の変更: 以前はaddDecl
内で直接メソッドと通常の関数を分類していましたが、新しいaddFunc
関数を呼び出すように変更されました。--- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -105,28 +161,7 @@ func (doc *PackageDoc) addDecl(decl ast.Decl) { case *ast.FuncDecl: if isExported(d.Name) { - if d.Recv != nil { - // method - // determine receiver type name - var name string; - switch t := d.Recv.Type.(type) { - case *ast.Ident: - name = string(t.Lit); - case *ast.StarExpr: - // recv must be of the form *name - name = string(t.X.(*ast.Ident).Lit) - } - typ, found := doc.types[name]; - if found { - fdoc := &funcDoc{d}; - typ.methods[string(d.Name.Lit)] = fdoc; - } - // otherwise ignore - } else { - // ordinary function - fdoc := &funcDoc{d}; - doc.funcs[string(d.Name.Lit)] = fdoc; - } + doc.addFunc(d); }
-
funcDoc.print
メソッドのシグネチャ変更とHTML出力の調整:hsize
パラメータが追加され、見出しタグのレベルが動的に設定されるようになりました。--- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -236,14 +271,14 @@ func (c *constDoc) printConsts(p *astPrinter.Printer) { } -func (f *funcDoc) print(p *astPrinter.Printer) { +func (f *funcDoc) print(p *astPrinter.Printer, hsize int) { d := f.decl; if d.Recv != nil { - p.Printf("<h3>func ("); + p.Printf("<h%d>func (", hsize); p.Expr(d.Recv.Type); - p.Printf(") %s</h3>\n", d.Name.Lit); + p.Printf(") %s</h%d>\n", d.Name.Lit, hsize); } else { - p.Printf("<h2>func %s</h2>\n", d.Name.Lit); + p.Printf("<h%d>func %s</h%d>\n", hsize, d.Name.Lit, hsize); } p.Printf("<code>\"); p.DoFuncDecl(d);
-
typeDoc.print
におけるファクトリ関数の出力ロジックの追加:--- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -265,8 +300,12 @@ func (t *typeDoc) print(p *astPrinter.Printer) { } // print associated methods, if any + for name, m := range t.factories { + m.print(p, 3); + } + for name, m := range t.methods { - m.print(p); + m.print(p, 3); } }
usr/gri/pretty/template.html
- 不要なHTML要素の削除:
--- a/usr/gri/pretty/template.html +++ b/usr/gri/pretty/template.html @@ -13,14 +13,6 @@ <!--FUNCTIONS--> -<hr /> -<h1>Implementation</h1> -<font color=grey>Comments are currently not shown in the source.</font> - -<pre> -<!--PACKAGE_BODY--> -</pre> - </div> <!-- content --> </body> </html>
コアとなるコードの解説
このコミットの核心は、Goのドキュメンテーション生成プロセスが、単にメソッドだけでなく、特定の型を返すファクトリ関数もその型のドキュメントに含めるように賢くなった点にあります。
-
typeDoc
の拡張:typeDoc
にfactories
マップが追加されたことで、ドキュメント生成ツールは、ある型に関連するファクトリ関数を内部的に追跡・管理できるようになりました。これは、メソッド(methods
マップ)と同様に、型と関数の関連性を表現するための重要なデータ構造です。 -
addFunc
によるインテリジェントな分類:addFunc
関数は、GoのASTを解析する際に、関数のシグネチャを詳細に検査します。- レシーバの有無によって、その関数がメソッドであるか、通常の関数であるかを区別します。
- レシーバがない場合(通常の関数である可能性)、さらに戻り値の型をチェックします。もし戻り値が1つだけで、その型が既存の型と一致する場合、その関数を「ファクトリ関数」と見なし、対応する
typeDoc
のfactories
マップに登録します。このロジックにより、NewMyType
のような関数がMyType
のドキュメントに自動的に関連付けられるようになります。
-
ドキュメント出力の改善:
funcDoc.print
にhsize
パラメータが導入されたことで、生成されるHTMLドキュメントの見出し構造がよりセマンティックかつ柔軟になりました。これにより、パッケージレベルの関数は<h2>
、型に属するメソッドやファクトリ関数は<h3>
といったように、適切な階層で表示され、ドキュメントの可読性が向上します。typeDoc.print
内でfactories
マップがイテレートされ、ファクトリ関数がメソッドと同様に型のドキュメント内に表示されるようになったことで、ユーザーは型のインスタンス化方法に関する情報を一箇所で確認できるようになりました。
これらの変更により、Goのドキュメンテーションは、単にコードの構造を反映するだけでなく、Goの慣習(ファクトリ関数など)を理解し、よりユーザーフレンドリーな情報提供を行う方向に進化しました。
関連リンク
- Go言語の公式ドキュメント
- Go言語の
godoc
ツールについて (このコミットより後の記事ですが、godoc
の目的と機能について理解するのに役立ちます) - Go言語のASTパッケージ (
go/ast
)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
go/doc
パッケージやgodoc
コマンドの歴史) - Go言語におけるファクトリ関数の慣習に関する一般的な知識