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

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

このコミットは、Go言語のtext/template/parseパッケージにおけるコメントのパース処理を改善し、不正なコメント構文に対するエラー診断をより分かりやすくするための変更です。具体的には、コメントの終了デリミタが正しくない場合に、より適切なエラーメッセージを生成するように修正されています。また、text/templateパッケージのドキュメントも更新され、コメント構文がより明確に説明されています。

コミット

commit b7eb0e5990b45afc10ccc3c91edbd226793843b1
Author: Rob Pike <r@golang.org>
Date:   Fri Aug 9 12:57:21 2013 +1000

    text/template/parse: nicer error when comment ends before final delimiter
    By separating finding the end of the comment from the end of the action,
    we can diagnose malformed comments better.
    Also tweak the documentation to make the comment syntax clearer.
    
    Fixes #6022.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/12570044

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

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

元コミット内容

text/template/parse: nicer error when comment ends before final delimiter
By separating finding the end of the comment from the end of the action,
we can diagnose malformed comments better.
Also tweak the documentation to make the comment syntax clearer.

Fixes #6022.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/12570044

変更の背景

Go言語のtext/templateパッケージは、テキストベースのテンプレートを生成するための機能を提供します。このテンプレート言語では、{{/* ... */}}のような形式でコメントを記述できます。しかし、以前の実装では、コメントの終了デリミタ(*/)とアクションの終了デリミタ(}})が連続している場合に、コメントが正しく閉じられていない状況を適切に診断できない問題がありました。

具体的には、{{/* comment */}}-worldのようなテンプレートで、コメントの終了デリミタ*/の直後にアクションの終了デリミタ}}が続かない場合、パーサーは「コメントが閉じられていない」という一般的なエラーを返していました。これは、コメントの終了とアクションの終了を同時にチェックしていたためです。

このコミットの目的は、コメントの終了デリミタの検出と、その後のアクションの終了デリミタの検出を分離することで、より具体的なエラーメッセージ(「コメントが閉じデリミタの前に終了している」)を提供し、開発者がテンプレートの構文エラーを特定しやすくすることです。また、ドキュメントの記述も改善し、コメント構文の理解を深めることも目的としています。

コミットメッセージに記載されているFixes #6022は、この変更が特定の課題を解決することを示唆していますが、現在のところ、GoのGitHubリポジトリで直接この番号のIssueは見つかりませんでした。これは、Issue番号が内部的なものか、あるいは時間の経過とともに変更された可能性も考えられます。

前提知識の解説

  • Go言語のtext/templateパッケージ: Go言語に組み込まれている、テキストベースのテンプレートを処理するためのパッケージです。HTML、XML、プレーンテキストなどの動的なコンテンツ生成に利用されます。テンプレートは、プレリミタ({{)とポストリミタ(}})で囲まれた「アクション」と呼ばれる特殊な構文を含みます。
  • テンプレートのアクション: text/templateにおけるアクションは、データ処理、条件分岐、ループ、関数呼び出しなど、テンプレートの動的な部分を定義します。コメントもアクションの一種です。
  • 字句解析(Lexing/Tokenizing): プログラミング言語のコンパイラやインタプリタの最初の段階で、ソースコードを意味のある最小単位(トークン)に分解するプロセスです。このコミットでは、テンプレート文字列をトークンに分解する字句解析器(lexer)のロジックが変更されています。
  • lexer構造体: text/template/parseパッケージ内のlexer構造体は、テンプレート文字列の字句解析の状態を管理します。現在の位置(pos)、入力文字列(input)、左右のデリミタ(leftDelim, rightDelim)などの情報を含みます。
  • stateFn: Goの字句解析器でよく使われるパターンで、字句解析の状態を表す関数です。各stateFnは、次の状態関数を返します。
  • lexComment関数: コメントの字句解析を担当する関数です。テンプレート内の{{/*から*/}}までのコメント部分を処理します。
  • strings.Index: Goの標準ライブラリstringsパッケージの関数で、文字列内で指定された部分文字列が最初に出現するインデックスを返します。
  • strings.HasPrefix: Goの標準ライブラリstringsパッケージの関数で、文字列が指定されたプレフィックスで始まるかどうかを判定します。

技術的詳細

このコミットの主要な変更は、src/pkg/text/template/parse/lex.goファイル内のlexComment関数のロジックにあります。

変更前は、lexComment関数内でコメントの終了デリミタ(*/)とアクションの終了デリミタ(}})を結合した文字列(rightComment+l.rightDelim)を一度に検索していました。

// 変更前
i := strings.Index(l.input[l.pos:], rightComment+l.rightDelim)
if i < 0 {
    return l.errorf("unclosed comment")
}
l.pos += Pos(i + len(rightComment) + len(l.rightDelim))

このアプローチでは、{{/* comment */}}-worldのようなケースで、*/は存在するものの、その直後に}}が続かない場合に、strings.Index-1を返し、「unclosed comment」(コメントが閉じられていない)という一般的なエラーになっていました。これは、コメント自体は閉じられているにもかかわらず、パーサーがその後の構文を期待していたためです。

変更後は、この処理が2段階に分けられました。

  1. まず、コメントの終了デリミタ*/のみを検索します。
  2. */が見つかった場合、その直後にアクションの終了デリミタ}}が続くかどうかをstrings.HasPrefixで確認します。
// 変更後
i := strings.Index(l.input[l.pos:], rightComment) // まずコメントの終了デリミタ '*/' を探す
if i < 0 {
    return l.errorf("unclosed comment") // '*/' が見つからない場合は、コメントが閉じられていない
}
l.pos += Pos(Pos(i + len(rightComment))) // '*/' の位置まで進む
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) { // '*/' の直後にアクションの終了デリミタ '}}' が続くか確認
    return l.errorf("comment ends before closing delimiter") // 続かない場合は、より具体的なエラー
}
l.pos += Pos(len(l.rightDelim)) // '}}' の位置まで進む

この変更により、{{/* comment */}}-worldのようなケースでは、*/が見つかった後に}}が続かないため、comment ends before closing delimiter(コメントが閉じデリミタの前に終了している)という、より正確なエラーメッセージが返されるようになります。

また、src/pkg/text/template/doc.goのドキュメントも更新され、コメントの構文に関する説明がより明確になりました。特に、「Comments do not nest and must start and end at the delimiters, as shown here.」(コメントはネストせず、ここに示されているようにデリミタで開始および終了する必要があります。)という記述が追加され、コメントの正しい使用法が強調されています。

src/pkg/text/template/parse/lex_test.goには、この新しいエラーケースをテストするための新しいテストケースが追加されています。

// 新しいテストケース
{"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{
    {itemText, 0, "hello-"},
    {itemError, 0, `comment ends before closing delimiter`},
}},

このテストケースは、{{/* */ }}のようにコメントの終了デリミタ*/とアクションの終了デリミタ}}の間にスペースがある場合に、期待通りにcomment ends before closing delimiterエラーが発生することを確認しています。

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

src/pkg/text/template/doc.go

--- a/src/pkg/text/template/doc.go
+++ b/src/pkg/text/template/doc.go
@@ -44,7 +44,8 @@ data, defined in detail below.
 */
 //	{{/* a comment */}}
 //		A comment; discarded. May contain newlines.
-//		Comments do not nest.
+//		Comments do not nest and must start and end at the
+//		delimiters, as shown here.
 /*
 
 	{{pipeline}}

src/pkg/text/template/parse/lex.go

--- a/src/pkg/text/template/parse/lex.go
+++ b/src/pkg/text/template/parse/lex.go
@@ -243,11 +243,16 @@ func lexLeftDelim(l *lexer) stateFn {
 // lexComment scans a comment. The left comment marker is known to be present.
 func lexComment(l *lexer) stateFn {
 	l.pos += Pos(len(leftComment))
-	i := strings.Index(l.input[l.pos:], rightComment+l.rightDelim)
+	i := strings.Index(l.input[l.pos:], rightComment)
 	if i < 0 {
 		return l.errorf("unclosed comment")
 	}
-	l.pos += Pos(i + len(rightComment) + len(l.rightDelim))
+	l.pos += Pos(i + len(rightComment))
+	if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+		return l.errorf("comment ends before closing delimiter")
+
+	}
+	l.pos += Pos(len(l.rightDelim))
 	l.ignore()
 	return lexText
 }

src/pkg/text/template/parse/lex_test.go

--- a/src/pkg/text/template/parse/lex_test.go
+++ b/src/pkg/text/template/parse/lex_test.go
@@ -336,6 +336,10 @@ var lexTests = []lexTest{\n 		{itemText, 0, "hello-"},\n 		{itemError, 0, `unclosed comment`},\n 	}},\n+\t{"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{\n+\t\t{itemText, 0, "hello-"},\n+\t\t{itemError, 0, `comment ends before closing delimiter`},\n+\t}},\n }\n \n // collect gathers the emitted items into a slice.\n```

## コアとなるコードの解説

### `src/pkg/text/template/parse/lex.go`の変更

`lexComment`関数は、テンプレート内のコメントブロック(`{{/* ... */}}`)を解析する役割を担っています。

*   **変更前**:
    `strings.Index(l.input[l.pos:], rightComment+l.rightDelim)`
    この行では、現在の字句解析器の位置(`l.pos`)から、コメントの終了デリミタ`*/`とアクションの終了デリミタ`}}`を連結した文字列(例: `*/}}`)を一度に検索していました。もしこの連結文字列が見つからない場合、コメントが閉じられていないと判断し、一般的なエラーメッセージ`unclosed comment`を返していました。

*   **変更後**:
    1.  `i := strings.Index(l.input[l.pos:], rightComment)`
        まず、コメントの終了デリミタである`*/`のみを検索します。これにより、コメント自体が閉じられているかどうかを独立して判断できます。
    2.  `if i < 0 { return l.errorf("unclosed comment") }`
        もし`*/`が見つからない場合、コメントは実際に閉じられていないため、以前と同じ`unclosed comment`エラーを返します。
    3.  `l.pos += Pos(i + len(rightComment))`
        `*/`が見つかった場合、字句解析器の現在位置を`*/`の直後に進めます。
    4.  `if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) { return l.errorf("comment ends before closing delimiter") }`
        `*/`の直後に、アクションの終了デリミタ`}}`が続くかどうかを`strings.HasPrefix`で確認します。もし`}}`が続かない場合(例: `{{/* comment */}}-world`のように`*/`の後に別の文字がある場合)、コメントは閉じられているものの、テンプレートのアクションが正しく終了していないと判断し、より具体的なエラーメッセージ`comment ends before closing delimiter`を返します。
    5.  `l.pos += Pos(len(l.rightDelim))`
        `}}`が正しく続いた場合、字句解析器の現在位置を`}}`の直後に進めます。

この二段階のチェックにより、エラーの診断がより精密になり、開発者はテンプレートの構文エラーの原因を特定しやすくなります。

### `src/pkg/text/template/doc.go`の変更

ドキュメントの変更は、コメントの構文に関する説明をより明確にするためのものです。特に、コメントがネストしないことと、デリミタで正確に開始および終了する必要があることを強調しています。これは、字句解析器の変更と合わせて、ユーザーが正しいコメント構文を理解し、エラーを回避するのに役立ちます。

### `src/pkg/text/template/parse/lex_test.go`の変更

新しいテストケースは、`{{/* */ }}-world`のような、コメントの終了デリミタとアクションの終了デリミタの間にスペースがある不正な構文をカバーしています。このテストは、変更された`lexComment`関数が期待通りに`comment ends before closing delimiter`エラーを生成することを確認し、コードの修正が意図した通りに機能していることを保証します。

## 関連リンク

*   Go言語の`text/template`パッケージのドキュメント: [https://pkg.go.dev/text/template](https://pkg.go.dev/text/template)
*   Go言語の`strings`パッケージのドキュメント: [https://pkg.go.dev/strings](https://pkg.go.dev/strings)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント
*   Go言語のGitHubリポジトリのコミット履歴
*   Go言語の字句解析器の実装パターンに関する一般的な知識
*   `strings.Index`および`strings.HasPrefix`関数のGo標準ライブラリドキュメント