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

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

このコミットは、Go言語の標準ライブラリにおけるドキュメンテーション生成に関連する重要なリファクタリングを含んでいます。具体的には、go/docパッケージにあったコメントテキスト抽出機能CommentTextを、抽象構文木(AST)を扱うgo/astパッケージ内のast.CommentGroup型にTextメソッドとして移動させる変更です。これにより、go/docパッケージの依存関係が整理され、godocツールのみがgo/docをインポートする形になります。

コミット

  • コミットハッシュ: f0f6aa59cce7c4b59c259c536ef4d1223f127683
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2012年1月12日 木曜日 11:34:02 -0800

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

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

元コミット内容

go/doc: move CommentText to ast.CommentGroup's Text method

Now only godoc imports go/doc.

R=gri
CC=golang-dev
https://golang.org/cl/5541045

変更の背景

この変更の主な背景は、Go言語の標準ライブラリにおけるパッケージ間の依存関係の整理と、API設計の改善にあります。

元々、Goのソースコードからドキュメンテーションを生成するgo/docパッケージには、CommentTextという関数が存在し、これはgo/astパッケージのast.CommentGroup型(コメントのグループを表すASTノード)を受け取り、そのコメントからマーカー(///* */など)を取り除いた純粋なテキストを抽出する役割を担っていました。

しかし、この設計には以下の問題がありました。

  1. 不必要な依存関係: go/docパッケージがast.CommentGroupを処理するためにgo/astパッケージに依存していました。コメントテキストの抽出は、ast.CommentGroup自身の振る舞いとして自然に属する機能であり、ドキュメンテーション生成というよりはASTノードの基本的な操作に近いものでした。
  2. APIの整合性: ast.CommentGroupはASTの一部であり、そのテキスト表現を取得する機能は、その型自身が提供すべきであるという考え方です。外部のパッケージ(go/doc)がその内部構造を操作するような形は、APIの整合性やカプセル化の観点から望ましくありませんでした。
  3. godoc以外の利用: コミットメッセージにある「Now only godoc imports go/doc.」という記述が示すように、この変更の目的の一つは、go/docパッケージの利用をgodocツールに限定することでした。CommentTextgo/docにある限り、godoc以外のツールやライブラリもコメントテキストを抽出するためにgo/docをインポートする必要があり、これがgo/docの利用範囲を広げていました。CommentTextgo/astに移動することで、go/docの役割をより純粋なドキュメンテーション生成に特化させ、他のパッケージがコメントテキストを必要とする場合は直接go/astを利用するように促すことができます。

これらの理由から、CommentText関数をast.CommentGroupのメソッドとして再配置することで、よりクリーンで論理的なAPI設計を目指しました。

前提知識の解説

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

  1. Go言語の抽象構文木 (AST):

    • Go言語のコンパイラやツールは、ソースコードを直接解析するのではなく、まずそのコードの抽象構文木(Abstract Syntax Tree, AST)を構築します。ASTは、プログラムの構造を木構造で表現したものです。
    • go/astパッケージは、GoのソースコードのASTを表現するためのデータ構造と、それらを操作するための関数を提供します。例えば、関数宣言、変数宣言、式、コメントなどもASTノードとして表現されます。
    • go/tokenパッケージは、ソースコード内の位置(ファイル、行、列)やトークン(キーワード、識別子、演算子など)を扱うための基本的な型を提供し、go/astパッケージと密接に連携します。
  2. go/ast.CommentGroup:

    • go/astパッケージには、CommentGroupという構造体があります。これは、Goのソースコード内のコメントのグループ(例えば、関数や型の宣言の直前にあるドキュメンテーションコメントなど)を表します。
    • CommentGroupは、複数の*Comment(個々のコメント行やブロックコメント)のリストを保持しています。
  3. go/docパッケージ:

    • go/docパッケージは、Goのソースコードから抽出されたASTを解析し、人間が読める形式のドキュメンテーションを生成するための機能を提供します。これは、godocツールの中核をなすパッケージです。
    • このパッケージは、パッケージ、型、関数、変数などのドキュメンテーションコメントを抽出し、整形する役割を担います。
  4. godocツール:

    • godocは、Go言語のソースコードからドキュメンテーションを生成し、Webブラウザで表示したり、プレーンテキストとして出力したりする公式ツールです。go/docパッケージを内部的に利用しています。
  5. ビルドスクリプト (buildscript_*.sh):

    • Go言語の初期のビルドシステムでは、各プラットフォーム(darwin, freebsd, linux, netbsd, openbsd, plan9, windows)およびアーキテクチャ(386, amd64, arm)ごとにシェルスクリプト(buildscript_*.sh)が存在し、Goの標準ライブラリやツールをコンパイル・インストールする手順が記述されていました。
    • これらのスクリプトは、Goのコンパイラ(8g, 6g, 5gなど)やアーカイバ(gopack)を呼び出し、各パッケージのオブジェクトファイルやアーカイブファイルを生成し、適切なディレクトリに配置していました。
    • パッケージ間の依存関係の変更は、これらのビルドスクリプトにおけるコンパイル順序や依存パッケージの指定に影響を与える可能性があります。

技術的詳細

このコミットの技術的な核心は、go/docパッケージからCommentText関数を削除し、その機能をgo/astパッケージのast.CommentGroup型にText()メソッドとして実装し直した点にあります。

変更前: go/docパッケージには、以下のようなシグネチャを持つCommentText関数がありました。

func CommentText(comment *ast.CommentGroup) string

この関数は、go/astパッケージのast.CommentGroup型のポインタを受け取り、コメントマーカー(//, /*, */)や余分な空白を取り除いた整形済みのコメントテキストを文字列として返していました。

変更後:

  1. go/ast.CommentGroupへのText()メソッドの追加: src/pkg/go/ast/ast.goファイルに、ast.CommentGroup型に対するText()メソッドが追加されました。このメソッドは、以前go/doc.CommentTextが持っていたロジック(コメントマーカーの除去、行頭・行末の空白のトリミング、連続する空行の単一化など)を内包しています。 これにより、ast.CommentGroupオブジェクト自体が、そのコメントの整形済みテキスト表現を提供する責任を持つようになりました。

  2. go/doc.CommentTextの削除: src/pkg/go/doc/comment.goファイルから、CommentText関数が完全に削除されました。

  3. 関連パッケージの更新: go/doc.CommentTextを利用していたGoの様々なツールやパッケージが、新しく追加されたast.CommentGroup.Text()メソッドを使用するように変更されました。これには、cmd/cgo, cmd/go, cmd/godoc, cmd/gotest, go/build, go/doc/example, go/doc/readerなどが含まれます。これらのファイルでは、doc.CommentText(cg)のような呼び出しがcg.Text()に置き換えられています。

  4. ビルドスクリプトの変更: Goのビルドスクリプト(src/buildscript_*.sh)も多数変更されています。これは、パッケージ間の依存関係の変更(特にgo/docgo/astに依存しなくなったこと、またはその逆)に伴い、コンパイル順序やパッケージのビルド方法が調整されたためと考えられます。具体的には、go/docのビルドセクションが削除されたり、他のパッケージのビルド順序が調整されたりしています。これは、go/docCommentTextのロジックを持たなくなったことで、そのビルドに必要な依存関係が減少したことを示唆しています。

このリファクタリングにより、go/astパッケージはASTノードの基本的な操作(コメントテキストの抽出も含む)に特化し、go/docパッケージは純粋にドキュメンテーションの構造化と生成に特化するという、より明確な役割分担が実現されました。これにより、Goのツールチェインのモジュール性が向上し、将来的なメンテナンスや機能拡張が容易になります。

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

このコミットのコアとなる変更は、主に以下の2つのファイルに集約されます。

  1. src/pkg/go/ast/ast.go: ast.CommentGroup型にText()メソッドが追加されました。

    --- a/src/pkg/go/ast/ast.go
    +++ b/src/pkg/go/ast/ast.go
    @@ -9,6 +9,7 @@ package ast
    
     import (
     	"go/token"
    +	"strings"
     	"unicode"
     	"unicode/utf8"
     )
    @@ -76,6 +77,74 @@ type CommentGroup struct {
     func (g *CommentGroup) Pos() token.Pos { return g.List[0].Pos() }
     func (g *CommentGroup) End() token.Pos { return g.List[len(g.List)-1].End() }
    
    +func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
    +
    +func stripTrailingWhitespace(s string) string {
    +	i := len(s)
    +	for i > 0 && isWhitespace(s[i-1]) {
    +		i--
    +	}
    +	return s[0:i]
    +}
    +
    +// Text returns the text of the comment,
    +// with the comment markers - //, /*, and */ - removed.
    +func (g *CommentGroup) Text() string {
    +	if g == nil {
    +		return ""
    +	}
    +	comments := make([]string, len(g.List))
    +	for i, c := range g.List {
    +		comments[i] = string(c.Text)
    +	}
    +
    +	lines := make([]string, 0, 10) // most comments are less than 10 lines
    +	for _, c := range comments {
    +		// Remove comment markers.
    +		// The parser has given us exactly the comment text.
    +		switch c[1] {
    +		case '/':
    +			//-style comment
    +			c = c[2:]
    +			// Remove leading space after //, if there is one.
    +			// TODO(gri) This appears to be necessary in isolated
    +			//           cases (bignum.RatFromString) - why?
    +			if len(c) > 0 && c[0] == ' ' {
    +				c = c[1:]
    +			}
    +		case '*':
    +			/*-style comment */
    +			c = c[2 : len(c)-2]
    +		}
    +
    +		// Split on newlines.
    +		cl := strings.Split(c, "\n")
    +
    +		// Walk lines, stripping trailing white space and adding to list.
    +		for _, l := range cl {
    +			lines = append(lines, stripTrailingWhitespace(l))
    +		}
    +	}
    +
    +	// Remove leading blank lines; convert runs of
    +	// interior blank lines to a single blank line.
    +	n := 0
    +	for _, line := range lines {
    +		if line != "" || n > 0 && lines[n-1] != "" {
    +			lines[n] = line
    +			n++
    +		}
    +	}
    +	lines = lines[0:n]
    +
    +	// Add final "" entry to get trailing newline from Join.
    +	if n > 0 && lines[n-1] != "" {
    +		lines = append(lines, "")
    +	}
    +
    +	return strings.Join(lines, "\n")
    +}
    +
     // ----------------------------------------------------------------------------
     // Expressions and types
    
  2. src/pkg/go/doc/comment.go: CommentText関数が削除されました。

    --- a/src/pkg/go/doc/comment.go
    +++ b/src/pkg/go/doc/comment.go
    @@ -7,7 +7,6 @@
     package doc
    
     import (
    -	"go/ast"
     	"io"
     	"regexp"
     	"strings"
    @@ -16,74 +15,6 @@ import (
     	"unicode/utf8"
     )
    
    -func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
    -
    -func stripTrailingWhitespace(s string) string {
    -	i := len(s)
    -	for i > 0 && isWhitespace(s[i-1]) {
    -		i--
    -	}
    -	return s[0:i]
    -}
    -
    -// CommentText returns the text of comment,
    -// with the comment markers - //, /*, and */ - removed.
    -func CommentText(comment *ast.CommentGroup) string {
    -	if comment == nil {
    -		return ""
    -	}
    -	comments := make([]string, len(comment.List))
    -	for i, c := range comment.List {
    -		comments[i] = string(c.Text)
    -	}
    -
    -	lines := make([]string, 0, 10) // most comments are less than 10 lines
    -	for _, c := range comments {
    -		// Remove comment markers.
    -		// The parser has given us exactly the comment text.
    -		switch c[1] {
    -		case '/':
    -			//-style comment
    -			c = c[2:]
    -			// Remove leading space after //, if there is one.
    -			// TODO(gri) This appears to be necessary in isolated
    -			//           cases (bignum.RatFromString) - why?
    -			if len(c) > 0 && c[0] == ' ' {
    -				c = c[1:]
    -			}
    -		case '*':
    -			/*-style comment */
    -			c = c[2 : len(c)-2]
    -		}
    -
    -		// Split on newlines.
    -		cl := strings.Split(c, "\n")
    -
    -		// Walk lines, stripping trailing white space and adding to list.
    -		for _, l := range cl {
    -			lines = append(lines, stripTrailingWhitespace(l))
    -		}
    -	}
    -
    -	// Remove leading blank lines; convert runs of
    -	// interior blank lines to a single blank line.
    -	n := 0
    -	for _, line := range lines {
    -		if line != "" || n > 0 && lines[n-1] != "" {
    -			lines[n] = line
    -			n++
    -		}
    -	}
    -	lines = lines[0:n]
    -
    -	// Add final "" entry to get trailing newline from Join.
    -	if n > 0 && lines[n-1] != "" {
    -		lines = append(lines, "")
    -	}
    -
    -	return strings.Join(lines, "\n")
    -}
    -
     var (
     	ldquo = []byte("“")
     	rdquo = []byte("”")
    

コアとなるコードの解説

src/pkg/go/ast/ast.go の変更

ast.CommentGroup構造体にText()メソッドが追加されました。このメソッドは、コメントグループが保持する個々のコメント(*ast.Comment)のテキストを結合し、Goのドキュメンテーションコメントの慣習に従って整形します。

  • isWhitespacestripTrailingWhitespace: これらのヘルパー関数は、コメントテキストから不要な空白を除去するために導入されました。isWhitespaceは特定のバイトが空白文字であるかを判定し、stripTrailingWhitespaceは文字列の末尾から空白を削除します。
  • Text() メソッドのロジック:
    1. CommentGroupnilの場合は空文字列を返します。
    2. CommentGroup内の各*ast.Commentの生テキストを取得します。
    3. 各コメントテキストからコメントマーカー(///* */*/)を削除します。//コメントの場合は、その後の先頭の空白も除去します。
    4. コメントテキストを改行で分割し、各行の末尾の空白をstripTrailingWhitespaceで除去します。
    5. 整形された行のリストから、先頭の空行を除去し、連続する空行を単一の空行にまとめます。
    6. 最後に、整形された行を改行文字で結合して返します。

この変更により、ast.CommentGroupは自身のテキスト表現を生成する自己完結型のエンティティとなり、go/docパッケージがこの低レベルな整形ロジックを持つ必要がなくなりました。

src/pkg/go/doc/comment.go の変更

このファイルからは、以前go/docパッケージが提供していたCommentText関数が完全に削除されました。これにより、go/docパッケージはgo/astパッケージへの直接的な依存を解消し、より高レベルなドキュメンテーション構造の構築に専念できるようになりました。

その他のファイルの変更

src/cmd/cgo/ast.go, src/cmd/go/pkg.go, src/cmd/go/test.go, src/cmd/godoc/dirtrees.go, src/cmd/gotest/gotest.go, src/pkg/go/build/dir.go, src/pkg/go/doc/example.go, src/pkg/go/doc/reader.goなどのファイルでは、go/doc.CommentTextの呼び出しが、新しく導入されたast.CommentGroup.Text()メソッドの呼び出しに置き換えられています。

例 (src/cmd/cgo/ast.go):

--- a/src/cmd/cgo/ast.go
+++ b/src/cmd/cgo/ast.go
@@ -9,7 +9,6 @@ package main
 import (
 	"fmt"
 	"go/ast"
-	"go/doc"
 	"go/parser"
 	"go/scanner"
 	"go/token"
@@ -79,7 +78,7 @@ func (f *File) ReadGo(name string) {
 			}
 			if cg != nil {
 				f.Preamble += fmt.Sprintf("#line %d %q\\n", sourceLine(cg), name)
-				f.Preamble += doc.CommentText(cg) + "\\n"
+				f.Preamble += cg.Text() + "\\n"
 			}
 		}
 	}

この変更は、go/docパッケージへの依存を削除し、ast.CommentGroupが提供する新しいメソッドを直接利用することで、コードの簡潔性と依存関係の明確化を実現しています。

ビルドスクリプトの変更

多数のsrc/buildscript_*.shファイルが変更されていますが、これは主にgo/docパッケージのビルドプロセスからCommentTextに関連するステップが削除されたこと、およびパッケージ間の依存関係の変更に伴うビルド順序の調整を反映したものです。これらの変更は、Goのビルドシステムが、リファクタリングされたパッケージ構造に適応したことを示しています。

関連リンク

参考にした情報源リンク