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

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

このコミットは、Go言語のドキュメンテーションツールであるgodocの挙動を修正するものです。具体的には、godocが様々なフィルタリングされたビュー(例えば、特定の宣言のみを表示するビュー)において、関連するコメントを表示するように改善しています。

変更されたファイルは以下の2つです。

  • src/cmd/godoc/godoc.go: godocの主要なロジックが含まれるファイル。AST(抽象構文木)の処理とパッケージのエクスポートに関する変更が含まれます。
  • src/cmd/godoc/main.go: godocコマンドのエントリポイントであり、フィルタリングされた出力の表示方法に関する変更が含まれます。

コミット

commit 8140cd9149493cffc797edafc60a230f1c9b5462
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jun 13 13:32:59 2012 -0700

    godoc: show comments in various filtered views
    
    Fixes #3454.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6305069

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

https://github.com/golang/go/commit/8140cd9149493cffc797edafc60a230f1c9b5462

元コミット内容

godoc: show comments in various filtered views

Fixes #3454.

R=rsc
CC=golang-dev
https://golang.org/cl/6305069

変更の背景

godocはGo言語のソースコードからドキュメンテーションを生成し、表示するためのツールです。Go言語では、関数、変数、型などの宣言の直前に書かれたコメントがその宣言のドキュメンテーションとして扱われます。

このコミットが行われる前は、godocが特定のフィルタリングされたビュー(例えば、特定のパッケージ内のエクスポートされたシンボルのみを表示する場合や、正規表現でフィルタリングされた宣言を表示する場合)でドキュメンテーションコメントを適切に表示しないという問題がありました。具体的には、Issue 3454で報告された問題に対応しています。

この問題は、godocがASTを処理する際に、コメントと宣言の関連付けがフィルタリングプロセス中に失われたり、適切に処理されなかったりすることが原因でした。結果として、ユーザーはフィルタリングされた出力で完全なドキュメンテーション情報を得ることができませんでした。このコミットは、このコメント表示の不整合を解消し、godocの有用性を向上させることを目的としています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の標準ライブラリと概念に関する知識が必要です。

  1. go/astパッケージ:

    • Go言語のソースコードを抽象構文木(AST: Abstract Syntax Tree)として表現するためのデータ構造と関数を提供します。
    • ast.File: 単一のGoソースファイルを表すASTのルートノード。
    • ast.Package: 複数のast.Fileから構成されるGoパッケージを表します。
    • ast.Decl: 宣言(関数、変数、型など)を表すインターフェース。
    • ast.CommentGroup: 複数のコメント行をまとめたグループ。
    • ast.NewCommentMap(fset *token.FileSet, node ast.Node): ASTノード内のコメントと、それらが関連付けられているASTノード(宣言など)のマッピングを作成します。これにより、どのコメントがどのコード要素に属しているかを追跡できます。
    • CommentMap.Filter(node ast.Node): CommentMapから、指定されたASTノードに関連するコメントのみをフィルタリングして新しいCommentMapを返します。
    • ast.FileExports(file *ast.File): 指定されたファイルからエクスポートされた(大文字で始まる)宣言のみを残し、それ以外の宣言をASTから削除します。
    • ast.PackageExports(pkg *ast.Package): パッケージ内のすべてのファイルに対してFileExportsを適用し、エクスポートされた宣言のみを残します。
    • ast.MergePackageFiles(pkg *ast.Package, mode ast.MergeMode): 複数のファイルからなるパッケージのASTを単一のファイルASTにマージします。MergeModeはマージ時の挙動を制御します。
      • ast.FilterUnassociatedComments: マージ時に、どの宣言にも関連付けられていないコメントをフィルタリングするモード。
  2. go/tokenパッケージ:

    • Goソースコード内のトークン(キーワード、識別子、演算子など)と、それらのソースコード上の位置(行番号、列番号、オフセット)を管理します。
    • token.FileSet: 複数のファイルにわたるソースコードの位置情報を一元的に管理するためのセット。
  3. go/printerパッケージ:

    • ASTをGoソースコードとして整形して出力するための機能を提供します。
    • printer.CommentedNode: ASTノードと、それに関連付けられたコメントグループを一緒に保持するための構造体。printerパッケージがASTを整形する際に、この情報を使ってコメントを適切な位置に出力します。
  4. godocの動作:

    • godocは、Goのソースファイルを解析してASTを構築します。
    • 構築されたASTからドキュメンテーションコメントを抽出し、HTMLなどの形式で整形して表示します。
    • フィルタリング機能は、ユーザーが特定の条件(例: エクスポートされたシンボルのみ、正規表現にマッチするシンボルのみ)に基づいて表示内容を絞り込むために使用されます。

技術的詳細

このコミットの核心は、godocがASTをフィルタリングする際に、コメントが適切に保持され、最終的な出力に反映されるようにすることです。

以前のgodocの実装では、ASTをフィルタリングする際に、コメントと宣言の関連付けが失われる可能性がありました。特に、ast.PackageExportsast.FilterFileのような関数がASTを変更する際に、コメントが適切に処理されず、結果としてフィルタリングされたビューでコメントが表示されないという問題が発生していました。

このコミットでは、以下の主要なアプローチでこの問題を解決しています。

  1. コメントマップの活用: go/astパッケージのast.NewCommentMapCommentMap.Filterを積極的に利用することで、ASTの変更(フィルタリング)が行われた後でも、各宣言にどのコメントが関連付けられているかを正確に追跡できるようにしました。
  2. ast.PackageExportsのローカル実装: ast.PackageExportsのシグネチャが固定されているため、godocの内部でコメント処理をカスタマイズするためにpackageExportsというローカル関数を実装しました。この関数は、各ファイルのコメントリストを正しく更新しながらエクスポートされた宣言のみを抽出します。
  3. ast.MergePackageFilesの挙動変更: パッケージファイルをマージする際に、以前はast.FilterUnassociatedCommentsというモードを使用していましたが、これを0(つまり、コメントのフィルタリングを行わない)に変更しました。これにより、マージプロセス中にコメントが誤って削除されるのを防ぎます。コメントのフィルタリングは、より制御された方法で後から行われるようになります。
  4. go/printerとの連携: フィルタリングされた宣言を実際に表示する際に、go/printerパッケージのprinter.CommentedNode構造体を使用するように変更しました。これにより、宣言とそれに関連するコメントがペアとしてprinterに渡され、printerがコメントを適切な位置に整形して出力できるようになります。

これらの変更により、ASTのフィルタリングが行われた後でも、コメント情報が保持され、最終的なgodocの出力で正しく表示されるようになりました。

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

src/cmd/godoc/godoc.go

--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -866,6 +866,19 @@ func inList(name string, list []string) bool {
 	return false
 }
 
+// packageExports is a local implementation of ast.PackageExports
+// which correctly updates each package file's comment list.
+// (The ast.PackageExports signature is frozen, hence the local
+// implementation).
+//
+func packageExports(fset *token.FileSet, pkg *ast.Package) {
+	for _, src := range pkg.Files {
+		cmap := ast.NewCommentMap(fset, src)
+		ast.FileExports(src)
+		src.Comments = cmap.Filter(src).Comments()
+	}
+}
+
 // getPageInfo returns the PageInfo for a package directory abspath. If the
 // parameter genAST is set, an AST containing only the package exports is
 // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
@@ -1012,9 +1025,9 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM
 			// TODO(gri) Consider eliminating export filtering in this mode,
 			//           or perhaps eliminating the mode altogether.
 			if mode&noFiltering == 0 {
-				ast.PackageExports(pkg)
+				packageExports(fset, pkg)
 			}
-			past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments)
+			past = ast.MergePackageFiles(pkg, 0)
 		}
 	}
 

src/cmd/godoc/main.go

--- a/src/cmd/godoc/main.go
+++ b/src/cmd/godoc/main.go
@@ -36,6 +36,7 @@ import (
 	"fmt"
 	"go/ast"
 	"go/build"
+	"go/printer"
 	"io"
 	"log"
 	"net/http"
@@ -424,20 +425,24 @@ func main() {
 		filter := func(s string) bool { return rx.MatchString(s) }
 		switch {
 		case info.PAst != nil:
+			cmap := ast.NewCommentMap(info.FSet, info.PAst)
 			ast.FilterFile(info.PAst, filter)
 			// Special case: Don't use templates for printing
 			// so we only get the filtered declarations without
 			// package clause or extra whitespace.
 			for i, d := range info.PAst.Decls {
+				// determine the comments associated with d only
+				comments := cmap.Filter(d).Comments()
+				cn := &printer.CommentedNode{Node: d, Comments: comments}
 				if i > 0 {
 					fmt.Println()
 				}
 				if *html {
 					var buf bytes.Buffer
-					writeNode(&buf, info.FSet, d)
+					writeNode(&buf, info.FSet, cn)
 					FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
 				} else {
-					writeNode(os.Stdout, info.FSet, d)
+					writeNode(os.Stdout, info.FSet, cn)
 				}
 				fmt.Println()
 			}

コアとなるコードの解説

src/cmd/godoc/godoc.goの変更

  1. packageExports関数の追加:

    • この新しい関数は、ast.PackageExportsのローカルな代替実装です。
    • 元のast.PackageExportsは、パッケージ内の各ファイルのASTからエクスポートされた宣言のみを抽出しますが、その過程でコメントの関連付けが失われる可能性がありました。
    • packageExports関数は、各ソースファイル(src)に対して以下の処理を行います。
      • cmap := ast.NewCommentMap(fset, src): まず、現在のファイル(src)のAST全体からコメントマップを作成します。これにより、どのコメントがどのASTノード(宣言など)に関連付けられているかの情報が保持されます。
      • ast.FileExports(src): ファイルからエクスポートされた宣言のみを残します。この操作はASTを変更します。
      • src.Comments = cmap.Filter(src).Comments(): ast.FileExportsによってASTが変更された後、元のコメントマップ(cmap)を使って、変更後のAST (src) に関連するコメントのみをフィルタリングし、その結果をsrc.Commentsに再設定します。これにより、フィルタリング後もコメントが正しくASTに紐付けられます。
    • この変更により、パッケージのエクスポート処理中にコメント情報が失われるのを防ぎます。
  2. getPageInfo関数内の変更:

    • ast.PackageExports(pkg)の呼び出しが、新しく定義されたpackageExports(fset, pkg)に置き換えられました。これにより、コメントの関連付けを維持したままパッケージのエクスポート処理が行われるようになります。
    • ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments)の呼び出しが、ast.MergePackageFiles(pkg, 0)に変更されました。
      • 以前のast.FilterUnassociatedCommentsモードは、マージ時にどの宣言にも関連付けられていないコメントを削除していました。これが、フィルタリングされたビューでコメントが表示されない原因の一つでした。
      • 0を指定することで、マージ時にコメントのフィルタリングを行わないようにします。これにより、すべてのコメントがマージされたASTに保持され、後続の処理で適切に扱えるようになります。

src/cmd/godoc/main.goの変更

  1. go/printerパッケージのインポート:

    • go/printerパッケージが新しくインポートされました。これは、ASTノードとコメントを組み合わせて整形出力するために使用されます。
  2. フィルタリングされた宣言の出力ロジックの変更:

    • case info.PAst != nil:ブロック内で、フィルタリングされた宣言(info.PAst.Decls)を処理するループに大きな変更が加えられました。
    • cmap := ast.NewCommentMap(info.FSet, info.PAst): フィルタリングされたAST (info.PAst) からコメントマップを作成します。これにより、フィルタリング後のASTにおけるコメントと宣言の関連付けを正確に把握できます。
    • ast.FilterFile(info.PAst, filter): ユーザーが指定した正規表現フィルタに基づいてASTをフィルタリングします。
    • ループ内で各宣言dを処理する際に、以下の行が追加されました。
      • comments := cmap.Filter(d).Comments(): 現在の宣言dに関連するコメントのみを、事前に作成したコメントマップ(cmap)からフィルタリングして取得します。
      • cn := &printer.CommentedNode{Node: d, Comments: comments}: 取得した宣言dと、それに関連するコメントcommentsprinter.CommentedNode構造体にラップします。この構造体は、go/printerパッケージがコメントを考慮してASTノードを整形するために使用されます。
    • writeNode関数の呼び出しが変更されました。
      • 以前はwriteNode(&buf, info.FSet, d)のように宣言dを直接渡していましたが、新しいコードではwriteNode(&buf, info.FSet, cn)のようにprinter.CommentedNodeを渡すようになりました。
      • これにより、writeNode(内部でgo/printerを使用していると推測される)が、宣言だけでなく、それに関連するコメントも考慮して整形出力を行うことができるようになります。

これらの変更により、godocはフィルタリングされたビューにおいても、各宣言に紐付けられたドキュメンテーションコメントを正確に抽出し、整形して表示することが可能になりました。

関連リンク

参考にした情報源リンク

  • Go CL 6305069: GODOC: SHOW COMMENTS IN VARIOUS FILTERED VIEWS (このコミットの直接の変更履歴)
  • Go言語のgo/astパッケージのドキュメンテーション (ASTの構造と操作に関する詳細)
  • Go言語のgo/tokenパッケージのドキュメンテーション (ソースコードの位置情報に関する詳細)
  • Go言語のgo/printerパッケージのドキュメンテーション (ASTの整形出力に関する詳細)
  • Go言語のgodocツールに関する一般的な情報