[インデックス 14813] ファイルの概要
このコミットは、Go言語のドキュメンテーションツール (go/doc
パッケージ) における、Example関数の出力コメントの処理に関するバグ修正です。具体的には、Example関数の出力コメントからプレフィックス(Output:
など)を除去した後の空白文字や改行文字のトリミング方法を改善し、strings.TrimSpace
が意図しない挙動を引き起こす問題を解決しています。これにより、Example出力の表示がより正確になります。
コミット
- コミットハッシュ:
a88bbbb771141598e492d123e15e1e9752c134ca
- 作者: Andrew Gerrand adg@golang.org
- コミット日時: Mon Jan 7 13:42:25 2013 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a88bbbb771141598e492d123e15e1e9752c134ca
元コミット内容
go/doc: trim only first space or newline from example output comment
Fixes #4487.
R=rsc
CC=golang-dev
https://golang.org/cl/7057048
変更の背景
この変更は、Go言語のIssue #4487「go doc
output for examples with Output:
comments is sometimes wrong」を修正するために行われました。
Goのドキュメンテーションツール (go doc
) は、Example関数に記述されたコメントから期待される出力を抽出し、ドキュメントに表示する機能を持っています。この機能は、Output:
というプレフィックスで始まるコメント行を解析することで実現されます。
しかし、元の実装では、Output:
プレフィックスの後に続く文字列を strings.TrimSpace
関数でトリミングしていました。strings.TrimSpace
は、文字列の先頭と末尾にあるすべてのUnicode空白文字(スペース、タブ、改行など)を削除します。この挙動が問題を引き起こしていました。
具体的には、Example出力コメントが以下のような形式の場合に問題が発生しました。
// Output:
// Hello, World!
この場合、outputPrefix.FindStringIndex(text)
は Output:
の位置を特定し、その後の文字列 (\nHello, World!
) を strings.TrimSpace
に渡します。strings.TrimSpace
は先頭の改行だけでなく、もし Hello, World!
の後に余分なスペースや改行があった場合、それらもすべて削除してしまいます。
より深刻な問題は、Example出力が複数行にわたる場合や、出力の先頭に意図的に空白や改行を含めたい場合に、strings.TrimSpace
がそれらをすべて削除してしまうことでした。例えば、出力が改行で始まる場合、その改行が削除されてしまい、期待される出力と異なる表示になってしまうというバグがありました。
このコミットは、strings.TrimSpace
の過剰なトリミングを避け、Output:
プレフィックスの直後にある最初の空白または改行のみを削除するように変更することで、この問題を解決することを目的としています。
前提知識の解説
Go言語のドキュメンテーションとExample関数
Go言語は、ソースコードに直接ドキュメンテーションを記述する文化を推奨しています。go doc
コマンドや godoc
ツールは、このドキュメンテーションを解析し、HTML形式やプレーンテキスト形式で表示します。
Example関数は、Goのドキュメンテーションにおける特別な機能です。Example
というプレフィックスで始まる関数は、そのパッケージの利用例として認識されます。これらの関数は、テストの一部として実行され、その標準出力 (stdout) が期待される出力と一致するかどうかを検証できます。
さらに、Example関数のコメントに Output:
というプレフィックスを付けて期待される出力を記述することで、go doc
はその出力をドキュメントに表示します。これにより、ユーザーはコードを実行することなく、そのExampleがどのような結果を生成するのかを視覚的に確認できます。
例:
package main
import "fmt"
func ExampleHello() {
fmt.Println("Hello, World!")
// Output: Hello, World!
}
strings.TrimSpace
関数
Go標準ライブラリの strings
パッケージに含まれる TrimSpace
関数は、文字列の先頭と末尾にあるすべてのUnicode空白文字を削除します。Unicode空白文字には、スペース (' '), タブ ('\t'), 改行 ('\n'), キャリッジリターン ('\r') などが含まれます。
例:
s := " \tHello, World!\n "
trimmed := strings.TrimSpace(s) // trimmed は "Hello, World!" になる
strings.TrimLeft
関数
strings.TrimLeft
関数は、文字列の先頭から、指定された文字セットに含まれる文字をすべて削除します。
例:
s := " Hello"
trimmed := strings.TrimLeft(s, " ") // trimmed は "Hello" になる
s2 := "---Hello---"
trimmed2 := strings.TrimLeft(s2, "-") // trimmed2 は "Hello---" になる
ast
パッケージ (Abstract Syntax Tree)
Goコンパイラやツールは、Goのソースコードを解析して抽象構文木 (AST: Abstract Syntax Tree) を構築します。go/ast
パッケージは、このASTを表現するための型と関数を提供します。
ast.BlockStmt
: コードブロック({ ... }
)を表します。ast.CommentGroup
: 複数のコメント行のグループを表します。
go/doc
パッケージは、ast
パッケージを使用してソースコードのASTを解析し、Example関数のコメントやコードブロックから情報を抽出します。
技術的詳細
このコミットの技術的な核心は、strings.TrimSpace
の代わりに、より制御された方法でExample出力コメントのプレフィックス後の空白を処理することです。
元のコードでは、outputPrefix.FindStringIndex(text)
で Output:
プレフィックスの終了位置 loc[1]
を見つけた後、text[loc[1]:]
でプレフィックス以降の文字列を取得し、それを直接 strings.TrimSpace
に渡していました。
// Old code:
// return strings.TrimSpace(text[loc[1]:])
この strings.TrimSpace
の問題は、Example出力が複数行にわたる場合や、出力の先頭に意図的に改行やスペースを含めたい場合に、それらがすべて削除されてしまうことでした。例えば、Output:\n Hello
のようなコメントがあった場合、TrimSpace
は \n
の部分をすべて削除してしまい、結果として Hello
だけが残ります。しかし、期待されるのは Hello
のように、改行後のインデントが保持されることかもしれません。
新しい実装では、この問題を解決するために、手動で段階的なトリミングを行います。
text = text[loc[1]:]
: まず、Output:
プレフィックスの直後から文字列を取得します。これは変更前と同じです。text = strings.TrimLeft(text, " ")
: 次に、取得した文字列の先頭から、0個以上のスペースを削除します。これは、Output: Hello
のように、プレフィックスの直後にスペースが続く場合に対応します。TrimLeft
は指定された文字セットに含まれる文字のみを削除するため、改行はここでは削除されません。if len(text) > 0 && text[0] == '\n' { text = text[1:] }
: 最後に、文字列の先頭に改行文字 (\n
) が存在し、かつ文字列が空でない場合に、その最初の改行文字を削除します。これは、Output:\nHello
のように、プレフィックスの直後に改行が続く場合に対応します。この条件分岐により、改行は最大で1つだけ削除されることが保証されます。
この新しいロジックにより、Output:
プレフィックスの直後にある「最初のスペースの並び」または「最初の改行」のみが削除され、それ以降の空白や改行は保持されるようになります。これにより、Example出力コメントの意図がより正確に反映されるようになります。
コアとなるコードの変更箇所
変更は src/pkg/go/doc/example.go
ファイルの exampleOutput
関数内で行われました。
--- a/src/pkg/go/doc/example.go
+++ b/src/pkg/go/doc/example.go
@@ -84,7 +84,13 @@ func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) string {
// test that it begins with the correct prefix
text := last.Text()
if loc := outputPrefix.FindStringIndex(text); loc != nil {
- return strings.TrimSpace(text[loc[1]:])
+ text = text[loc[1]:]
+ // Strip zero or more spaces followed by \n or a single space.
+ text = strings.TrimLeft(text, " ")
+ if len(text) > 0 && text[0] == '\n' {
+ text = text[1:]
+ }
+ return text
}
}
return "" // no suitable comment found
コアとなるコードの解説
変更されたコードブロックは exampleOutput
関数内にあります。この関数は、Example関数のコメントグループから期待される出力を抽出する役割を担っています。
text := last.Text() // Exampleコメントの最後の行のテキストを取得
if loc := outputPrefix.FindStringIndex(text); loc != nil { // "Output:" プレフィックスの位置を検索
text = text[loc[1]:] // "Output:" プレフィックス以降の文字列を取得
// Strip zero or more spaces followed by \n or a single space.
// ゼロ個以上のスペースに続く改行、または単一のスペースを削除する。
text = strings.TrimLeft(text, " ") // 先頭のスペースをすべて削除
if len(text) > 0 && text[0] == '\n' { // 文字列が空でなく、かつ先頭が改行の場合
text = text[1:] // 最初の改行文字を削除
}
return text // 処理された文字列を返す
}
この変更により、strings.TrimSpace
が行っていた「先頭と末尾のすべての空白文字の削除」から、「Output:
プレフィックスの直後にある最初の空白または改行のみを削除」という、より限定的で正確な処理に変わりました。これにより、Example出力コメントのフォーマットがより柔軟になり、開発者の意図した通りの出力がドキュメントに反映されるようになります。
関連リンク
- Go Issue #4487: https://github.com/golang/go/issues/4487
- Gerrit Change 7057048: https://golang.org/cl/7057048 (これは古いGerritのURLであり、現在はGitHubのプルリクエストに統合されていますが、コミットメッセージに記載されているため含めます。)
参考にした情報源リンク
- Go言語のドキュメンテーション: https://go.dev/doc/
strings
パッケージのドキュメンテーション: https://pkg.go.dev/strings- Go言語のIssueトラッカー (GitHub): https://github.com/golang/go/issues
- Go言語のExample関数に関する公式ブログ記事など (一般的な知識として)