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

[インデックス 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 のように、改行後のインデントが保持されることかもしれません。

新しい実装では、この問題を解決するために、手動で段階的なトリミングを行います。

  1. text = text[loc[1]:]: まず、Output: プレフィックスの直後から文字列を取得します。これは変更前と同じです。
  2. text = strings.TrimLeft(text, " "): 次に、取得した文字列の先頭から、0個以上のスペースを削除します。これは、Output: Hello のように、プレフィックスの直後にスペースが続く場合に対応します。TrimLeft は指定された文字セットに含まれる文字のみを削除するため、改行はここでは削除されません。
  3. 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出力コメントのフォーマットがより柔軟になり、開発者の意図した通りの出力がドキュメントに反映されるようになります。

関連リンク

参考にした情報源リンク