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

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

このコミットは、Go言語の公式ドキュメンテーションツールである godoc において、生成されるHTMLドキュメント内の「ノート」(TODO, BUG などの特殊なコメント)から、そのノートが記述されているソースコード上の正確な位置へ直接リンクできるようにする機能追加です。これにより、ドキュメントを閲覧している開発者が、関連するソースコードを素早く参照できるようになり、開発効率とドキュメントの有用性が向上します。

コミット

commit f1b7c140ffe42db59b51937037b2af6c48fa94b0
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Mar 28 14:40:59 2013 -0700

    cmd/godoc: provide a link from notes to source location
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/8122043
---
 lib/godoc/package.html   |  4 ++--
 src/cmd/godoc/godoc.go   | 32 +++++++++++++++++++++++---------\
 src/pkg/go/doc/doc.go    |  6 +++---\
 src/pkg/go/doc/reader.go |  1 +
 4 files changed, 29 insertions(+), 14 deletions(-)

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f1b7c140ffe42db59b51937037b2af6c48fa94b0

元コミット内容

cmd/godoc: provide a link from notes to source location

このコミットは、godoc コマンドが生成するドキュメントにおいて、ノート(例: TODO, BUG コメント)から、そのノートが記述されているソースコードの場所へリンクを提供する機能を追加します。

変更の背景

godoc はGo言語のコードから自動的にドキュメントを生成し、Webブラウザで閲覧可能な形式で提供するツールです。Goのソースコード内には、開発者が将来の作業や既知の問題を示すために TODO:BUG: といった特定のマーカーを持つコメント(これらを godoc では「ノート」と呼びます)を記述することがよくあります。

このコミット以前は、godoc が生成するドキュメントにはこれらのノートが表示されていましたが、そのノートがソースコードのどの部分に書かれているのかを直接知る手段がありませんでした。開発者がドキュメントを読んでいて特定のノートに興味を持った場合、手動でソースコードを検索する必要があり、これは非効率的でした。

この変更の背景には、ドキュメントとソースコードの間のナビゲーションをシームレスにし、開発者がより迅速にコードのコンテキストを理解できるようにするという目的があります。ノートから直接ソースコードの該当行へジャンプできる機能は、コードベースの探索や問題の追跡において非常に有用です。

前提知識の解説

このコミットを理解するためには、以下のGo言語のツールや概念に関する知識が必要です。

  • godoc: Go言語のソースコードからドキュメントを生成し、HTTPサーバーとして提供するツールです。Goの標準ライブラリのドキュメントも godoc によって生成されています。コード内のコメントや宣言から情報を抽出し、整形されたHTMLとして表示します。
  • go/doc パッケージ: godoc の中核をなすパッケージの一つで、Goのソースコードを解析し、ドキュメント構造(パッケージ、型、関数、変数、定数、ノートなど)を抽出する役割を担います。このパッケージが ast パッケージと token パッケージを利用して、ソースコードの抽象構文木(AST)を走査し、ドキュメント情報を収集します。
  • go/ast パッケージ: Go言語のソースコードの抽象構文木(AST)を表現するためのデータ構造と関数を提供します。ソースコードはまず字句解析され、次に構文解析されてASTが構築されます。ASTの各ノードは、関数宣言、変数宣言、コメントなど、コードの特定の要素を表します。
  • go/token パッケージ: ソースコード内の位置(ファイル名、行番号、列番号、オフセットなど)を表現するための token.Pos 型を提供します。token.Pos は、ソースコード内の特定のバイトオフセットを抽象的に表現するもので、token.FileSet と組み合わせることで具体的なファイル名や行番号に変換できます。
  • html/template パッケージ: Go言語でHTMLテンプレートを扱うためのパッケージです。godoc はこれを利用して、抽出したドキュメント情報をHTML形式でレンダリングします。テンプレート内でGoの構造体のフィールドやメソッドを呼び出すことができます。
  • ノート (Notes): godoc における「ノート」とは、TODO:, BUG:, FIXME: などの特定のキーワードで始まるコメントのことです。go/doc パッケージがこれらのコメントを特別に扱い、ドキュメントに抽出して表示します。

技術的詳細

このコミットは、godoc が生成するHTMLドキュメント内のノートに、対応するソースコードへのリンクを追加するために、以下の主要な変更を導入しています。

  1. go/doc.Note 構造体の拡張:

    • 以前の doc.Note 構造体は、ノートの開始位置を示す Pos token.Pos フィールドのみを持っていました。
    • このコミットでは、ノートの終了位置を示す End token.Pos フィールドが追加されました。これにより、ノートがコメントブロック全体をカバーする範囲を正確に指定できるようになります。
    • 変更前: Pos token.Pos
    • 変更後: Pos, End token.Pos
  2. go/doc/reader.go での End フィールドの取得:

    • go/doc パッケージ内の reader.go は、ソースコードを読み込み、コメントからノートを抽出するロジックを含んでいます。
    • readNote 関数が、ノートを構成するコメントのリストの最後のコメントの終了位置 (list[len(list)-1].End()) を取得し、新しく追加された doc.NoteEnd フィールドに設定するように変更されました。これにより、ノートの正確な範囲情報が doc.Note オブジェクトに格納されるようになります。
  3. cmd/godoc/godoc.go での posLink_urlFunc の拡張:

    • godoc.go に存在する posLink_urlFunc は、ast.Node(抽象構文木のノード)からソースコードへのリンクURLを生成するためのテンプレート関数です。
    • このコミットでは、posLink_urlFuncast.Node だけでなく、新しく *doc.Note 型の引数も受け取れるように拡張されました。
    • 関数内で型アサーション (switch n := n.(type)) を使用して、引数が ast.Node なのか *doc.Note なのかを判別します。
    • *doc.Note の場合、その PosEnd フィールドを使用して、ソースコード内の正確な範囲(オフセット)を特定し、それに基づいてURLを構築します。このURLは、godoc が提供するソースコードビューアの特定の行や範囲にジャンプするためのものです。
  4. lib/godoc/package.html テンプレートの変更:

    • godoc がパッケージドキュメントを生成する際に使用するHTMLテンプレート (package.html) が変更されました。
    • ノートを表示するセクション ({{with $.Notes}}) 内で、各ノートの表示の隣に新しいリンクが追加されました。
    • 具体的には、<li> タグ内に <a> タグが追加され、その href 属性に posLink_url テンプレート関数が呼び出されます。この関数に現在のノートオブジェクト (.) が渡され、ソースコードへのリンクURLが生成されます。
    • リンクのテキストには、右向きの矢印 (&#x261e;) が使用され、視覚的にソースへのジャンプを示します。
    • また、ノートのリストのスタイルが list-style: none; padding: 0; に変更され、デフォルトのリストマーカーが削除され、よりクリーンな表示になるように調整されています。

これらの変更により、go/doc パッケージがノートの正確なソースコード範囲を抽出し、godoc コマンドがその情報を使ってHTMLテンプレート内でクリック可能なソースコードリンクを生成する、という一連のフローが確立されました。

コアとなるコードの変更箇所

src/pkg/go/doc/doc.go

Note 構造体に End フィールドが追加されました。

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -69,9 +69,9 @@ type Func struct {
 // at least one character is recognized. The ":" following the uid is optional.
 // Notes are collected in the Package.Notes map indexed by the notes marker.
 type Note struct {
-	Pos  token.Pos // position of the comment containing the marker
-	UID  string    // uid found with the marker
-	Body string    // note body text
+	Pos, End token.Pos // position range of the comment containing the marker
+	UID      string    // uid found with the marker
+	Body     string    // note body text
 }
 
 // Mode values control the operation of New.

src/pkg/go/doc/reader.go

readNote 関数で、Note 構造体の End フィールドが設定されるようになりました。

--- a/src/pkg/go/doc/reader.go
+++ b/src/pkg/go/doc/reader.go
@@ -419,6 +419,7 @@ func (r *reader) readNote(list []*ast.Comment) {
 			marker := text[m[2]:m[3]]
 			r.notes[marker] = append(r.notes[marker], &Note{
 				Pos:  list[0].Pos(),
+				End:  list[len(list)-1].End(),
 				UID:  text[m[4]:m[5]],
 				Body: body,
 			})

src/cmd/godoc/godoc.go

posLink_urlFunc*doc.Note 型も処理できるようになり、PosEnd を利用してリンクを生成します。

--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -481,19 +481,33 @@ func pkgLinkFunc(path string) string {
 	return pkgHandler.pattern[1:] + relpath // remove trailing '/' for relative URL
 }
 
-func posLink_urlFunc(info *PageInfo, node ast.Node) string {
+// n must be an ast.Node or a *doc.Note
+func posLink_urlFunc(info *PageInfo, n interface{}) string {
+	var pos, end token.Pos
+
+	switch n := n.(type) {
+	case ast.Node:
+		pos = n.Pos()
+		end = n.End()
+	case *doc.Note:
+		pos = n.Pos
+		end = n.End
+	default:
+		panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
+	}
+
 	var relpath string
 	var line int
-	var low, high int // selection
+	var low, high int // selection offset range
 
-	if p := node.Pos(); p.IsValid() {
-		pos := info.FSet.Position(p)
-		relpath = pos.Filename
-		line = pos.Line
-		low = pos.Offset
+	if pos.IsValid() {
+		p := info.FSet.Position(pos)
+		relpath = p.Filename
+		line = p.Line
+		low = p.Offset
 	}
-	if p := node.End(); p.IsValid() {
-		high = info.FSet.Position(p).Offset
+	if end.IsValid() {
+		high = info.FSet.Position(end).Offset
 	}
 
 	var buf bytes.Buffer

lib/godoc/package.html

ノートの表示部分にソースコードへのリンクが追加されました。

--- a/lib/godoc/package.html
+++ b/lib/godoc/package.html
@@ -169,9 +169,9 @@
 	{{with $.Notes}}
 		{{range $marker, $content := .}}
 			<h2 id="pkg-note-{{$marker}}">{{noteTitle $marker | html}}s</h2>
-			<ul>
+			<ul style="list-style: none; padding: 0;">
 			{{range .}}
-			<li>{{html .Body}}</li>
+			<li><a href="{{posLink_url $ .}}">&#x261e;</a> {{html .Body}}</li>
 			{{end}}
 			</ul>
 		{{end}}

コアとなるコードの解説

このコミットの核心は、go/doc パッケージがノートのソースコード上の範囲を正確に把握し、その情報を godoc コマンドがHTMLリンクとして利用できるようにする点にあります。

  1. doc.Note 構造体の拡張:

    • src/pkg/go/doc/doc.goNote 構造体に End token.Pos が追加されたことで、ノートがソースコードのどこからどこまでを指すのかという「範囲」情報が保持できるようになりました。これは、単一のコメント行だけでなく、複数行にわたるコメントブロック全体をノートとして扱う場合に特に重要です。
  2. reader.go での End の設定:

    • src/pkg/go/doc/reader.goreadNote 関数は、ソースコードからコメントを読み取り、それがノートであると判断した場合に Note オブジェクトを生成します。
    • 以前はコメントリストの最初のコメントの開始位置 (list[0].Pos()) のみを使用していましたが、変更後は list[len(list)-1].End() を使用して、ノートを構成する最後のコメントの終了位置を取得し、Note.End に設定します。これにより、ノートの正確な範囲が go/doc レベルで捕捉されます。
  3. godoc.goposLink_urlFunc の多態性:

    • src/cmd/godoc/godoc.goposLink_urlFunc は、HTMLテンプレートから呼び出される関数で、ソースコード内の位置へのURLを生成します。
    • この関数は元々 ast.Node を引数としていましたが、interface{} を受け取るように変更され、内部で switch n := n.(type) を用いて ast.Node または *doc.Note のどちらが渡されたかを判別します。
    • *doc.Note が渡された場合、その Note オブジェクトの PosEnd フィールドからソースコードのオフセット情報を取得します。このオフセット情報(lowhigh)は、godoc のソースコードビューアが特定の範囲をハイライト表示するために使用されます。
    • 最終的に、ファイルパス、行番号、そしてオフセット範囲を含むURL(例: /src/pkg/foo/bar.go?s=123:456#L789 のような形式)が生成されます。
  4. package.html でのリンクのレンダリング:

    • lib/godoc/package.html テンプレートでは、{{range .}} ループで各ノートを処理する際に、<li> 要素内に <a> タグが追加されました。
    • href="{{posLink_url $ .}}" の部分が重要で、ここで posLink_urlFunc が呼び出され、現在のノートオブジェクト (.) が引数として渡されます。これにより、各ノートに対して個別のソースコードリンクが動的に生成されます。
    • &#x261e; はHTMLエンティティで、右向きの矢印(☛)を表示します。これは、ユーザーがクリックすることでソースコードへジャンプできることを視覚的に示します。

これらの変更が連携することで、godoc はノートの正確なソース位置を把握し、それをユーザーフレンドリーな形でドキュメントに表示できるようになりました。

関連リンク

  • Go Change List 8122043: https://golang.org/cl/8122043
    • このコミットの元となったGoのコードレビューシステム(Gerrit)上の変更リストです。コミットメッセージに記載されているリンクであり、変更の議論や詳細な差分を確認できます。

参考にした情報源リンク

  • GoDoc (Go Documentation) - The Go Programming Language: https://go.dev/blog/godoc
    • godoc の基本的な機能と使い方について解説しているGo公式ブログの記事です。
  • go/doc package documentation: https://pkg.go.dev/go/doc
    • go/doc パッケージの公式ドキュメントです。Note 構造体やドキュメント抽出の仕組みについて詳細が記述されています。
  • go/ast package documentation: https://pkg.go.dev/go/ast
    • Goの抽象構文木(AST)に関する公式ドキュメントです。ast.Node の概念を理解するのに役立ちます。
  • go/token package documentation: https://pkg.go.dev/go/token
    • token.Postoken.FileSet など、ソースコードの位置情報に関する公式ドキュメントです。
  • html/template package documentation: https://pkg.go.dev/html/template
    • GoのHTMLテンプレートに関する公式ドキュメントです。godoc がどのようにHTMLを生成しているかを理解するのに役立ちます。
  • Go言語のソースコードコメントの慣習 (TODO, BUGなど): https://go.dev/doc/effective_go#commentary
    • Go言語におけるコメントの書き方や慣習について説明されており、TODO などのノートの背景を理解するのに役立ちます。