[インデックス 10498] ファイルの概要
このコミットは、Go言語のgofix
ツールに新しいルールを追加するものです。具体的には、text/template
およびhtml/template
パッケージにおけるAPIの変更に対応するため、template.ParseFile
の呼び出しをtemplate.ParseFiles
に自動的に書き換える機能と、非推奨となったtemplate.Set
型およびその関連メソッドの使用に対して警告を発する機能が導入されています。これにより、古いAPIを使用しているGoプログラムを新しいAPIに適合させ、将来の互換性の問題を回避することを目的としています。
コミット
- コミットハッシュ:
da62104169c7b31f8b2917b24232dd349b769c8f
- 作者: Rob Pike r@golang.org
- コミット日時: 2011年11月23日(水) 20:17:41 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da62104169c7b31f8b2917b24232dd349b769c8f
元コミット内容
gofix: trivial rewrite for template.ParseFiles
Also warn about uses of Set.
R=rsc
CC=golang-dev
https://golang.org/cl/5436051
変更の背景
この変更の背景には、Go言語の標準ライブラリであるtext/template
およびhtml/template
パッケージのAPI進化があります。初期のGo言語開発段階では、APIが頻繁に変更されることがありました。特に、テンプレートファイルのパースに関する関数は、単一ファイルをパースするParseFile
から、複数のファイルを一度にパースできるParseFiles
へと変更されました。これは、複数のテンプレートファイルを扱う際の利便性と効率性を向上させるための変更です。
また、template.Set
型は、テンプレートのセットを管理するためのものでしたが、その後のAPI設計の見直しにより、template.Template
型自体が複数のテンプレートを管理する機能を内包するようになり、template.Set
は冗長または非推奨となりました。
gofix
ツールは、このようなGo言語のAPI変更や慣習の進化に対応するために開発された自動コード修正ツールです。APIの変更に伴い、既存のコードベースを新しいAPIに手動で移行するのは手間がかかり、エラーの元となる可能性があります。gofix
は、このような移行作業を自動化し、開発者がGo言語の進化に追従しやすくすることを目的としています。
このコミットは、上記のtemplate
パッケージのAPI変更(ParseFile
からParseFiles
への変更、およびtemplate.Set
の非推奨化)に対応するために、gofix
に新しい修正ルールを追加するものです。これにより、ユーザーはgofix
を実行するだけで、古いtemplate
パッケージのAPIを使用しているコードを自動的に更新できるようになります。
前提知識の解説
gofix
ツール
gofix
は、Go言語のソースコードを自動的に修正するためのコマンドラインツールです。Go言語のバージョンアップに伴うAPIの変更や、言語仕様の変更、あるいは推奨されるコーディングスタイルへの準拠など、様々な理由で既存のコードベースを更新する必要がある場合に利用されます。gofix
は、Goの抽象構文木(AST: Abstract Syntax Tree)を解析し、定義されたルールに基づいてコードを変換します。これにより、手動での修正に比べて、はるかに効率的かつ正確にコードの移行を行うことができます。
text/template
およびhtml/template
パッケージ
これらはGo言語の標準ライブラリで提供されるテンプレートエンジンです。
text/template
: テキストベースの出力を生成するための汎用テンプレートエンジンです。html/template
:text/template
をベースにしており、HTML出力に特化しています。クロスサイトスクリプティング(XSS)攻撃を防ぐための自動エスケープ機能など、Webアプリケーション開発に不可欠なセキュリティ機能を提供します。
これらのパッケージは、Webアプリケーションのビュー層や、設定ファイルの生成、コード生成など、様々な場面で利用されます。
抽象構文木 (AST: Abstract Syntax Tree)
ASTは、プログラミング言語のソースコードを抽象的な構文構造で表現したツリー構造のデータです。コンパイラやリンター、コード分析ツール、そしてgofix
のようなコード変換ツールは、ソースコードを直接操作するのではなく、一度ASTに変換してから処理を行います。ASTは、コードの構造を明確に表現するため、プログラムの意味を理解し、変更を加えるのに適しています。
go/ast
パッケージ
Go言語の標準ライブラリの一部であり、GoのソースコードのASTを表現するための型と関数を提供します。gofix
のようなツールは、このパッケージを利用してGoのソースコードを解析し、ASTを構築し、そのASTを操作することでコードの変換を行います。
技術的詳細
このコミットで追加されたgofix
の修正は、主にsrc/cmd/gofix/template.go
に実装されています。
-
template.ParseFile
からtemplate.ParseFiles
への書き換え:gofix
は、GoのソースファイルをASTとして読み込みます。- ASTを走査し、
template.ParseFile
という関数呼び出し、または*template.Template
型のレシーバーを持つParseFile
メソッドの呼び出しを特定します。 - これらの識別子(
ParseFile
)をParseFiles
に書き換えます。 - この変更は、
template.ParseFile
が単一のファイルパスを引数にとるのに対し、template.ParseFiles
が可変長引数で複数のファイルパスを受け取るように変更されたことに対応しています。gofix
は、引数の数や型を変更するわけではなく、単にメソッド/関数名を変更するだけです。これは、ParseFile
がParseFiles
の単一ファイル版として機能するため、名前の変更だけで互換性が保たれるケースを想定しています。
-
template.Set
の使用に対する警告:gofix
は、template.Set
型、またはその関連するグローバル関数(ParseSetFiles
,ParseSetGlob
,ParseTemplateFiles
,ParseTemplateGlob
,Set
,SetMust
)やメソッドの使用を検出します。- これらの使用箇所が見つかった場合、
gofix
は警告メッセージを出力し、手動での修正が必要であることをユーザーに通知します。これは、template.Set
が非推奨となり、その機能がtemplate.Template
に統合されたため、単純な自動修正では対応できない複雑な変更が必要となる可能性があるためです。警告は、コードのどの位置で問題が検出されたかを示す行番号情報を含みます。
template.go
のコード構造
init()
関数でtemplateFix
をgofix
のレジストリに登録します。templateFix
構造体は、修正の名前、適用日、修正関数(template
)、および説明を含みます。templateSetGlobals
とtemplateSetMethods
は、template.Set
に関連するグローバル関数名とメソッド名を定義した文字列スライスです。これらは警告を生成する際に使用されます。templateTypeConfig
は、go/types
パッケージのような型チェックを行うための設定です。template.Template
とtemplate.Set
のメソッドシグネチャを定義しており、gofix
がASTを走査する際に、セレクタ式(sel.X.sel.Name
のような形式)の型を特定するために利用されます。template(f *ast.File) bool
関数が実際の修正ロジックを実装しています。- まず、ファイルが
text/template
またはhtml/template
パッケージをインポートしているかを確認します。インポートしていない場合は修正対象外です。 typecheck
関数(gofix
の内部関数)を使用して、AST内の各ノードの型情報を取得します。walk
関数(gofix
の内部関数)を使用してASTを再帰的に走査します。- 走査中に
ast.SelectorExpr
(例:template.ParseFile
やt.ParseFile
)が見つかった場合、以下のチェックを行います。template.ParseFile
というグローバル関数呼び出しの場合、ParseFile
をParseFiles
に書き換えます。*template.Template
型のレシーバーを持つParseFile
メソッド呼び出しの場合、ParseFile
をParseFiles
に書き換えます。templateSetGlobals
に含まれるグローバル関数(例:template.Set
)が呼び出されている場合、警告を発します。*template.Set
型のレシーバーを持つtemplateSetMethods
に含まれるメソッドが呼び出されている場合、警告を発します。
- まず、ファイルが
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
src/cmd/gofix/Makefile
:--- a/src/cmd/gofix/Makefile +++ b/src/cmd/gofix/Makefile @@ -31,6 +31,7 @@ GOFILES=\ sorthelpers.go\ sortslice.go\ stringssplit.go\ +\ttemplate.go\ typecheck.go\ url.go\
gofix
のビルドプロセスにtemplate.go
を追加しています。 -
src/cmd/gofix/template.go
: (新規ファイル) このファイル全体が新規追加されており、template.ParseFile
からtemplate.ParseFiles
への書き換えロジックと、template.Set
の使用に対する警告ロジックが含まれています。 -
src/cmd/gofix/template_test.go
: (新規ファイル) このファイル全体が新規追加されており、template.go
で実装された修正ロジックのテストケースが含まれています。template.ParseFile
がtemplate.ParseFiles
に書き換えられること、およびtemplate.Set
関連の呼び出しが警告の対象となることを検証しています。
コアとなるコードの解説
src/cmd/gofix/template.go
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"go/ast"
)
func init() {
register(templateFix)
}
var templateFix = fix{
"template",
"2011-11-22",
template,
`Rewrite calls to template.ParseFile to template.ParseFiles
http://codereview.appspot.com/5433048
`,
}
var templateSetGlobals = []string{
"ParseSetFiles",
"ParseSetGlob",
"ParseTemplateFiles",
"ParseTemplateGlob",
"Set",
"SetMust",
}
var templateSetMethods = []string{
"ParseSetFiles",
"ParseSetGlob",
"ParseTemplateFiles",
"ParseTemplateGlob",
}
var templateTypeConfig = &TypeConfig{
Type: map[string]*Type{
"template.Template": &Type{
Method: map[string]string{
"Funcs": "func() *template.Template",
"Delims": "func() *template.Template",
"Parse": "func() (*template.Template, error)",
"ParseFile": "func() (*template.Template, error)",
"ParseInSet": "func() (*template.Template, error)",
},
},
"template.Set": &Type{
Method: map[string]string{
"ParseSetFiles": "func() (*template.Set, error)",
"ParseSetGlob": "func() (*template.Set, error)",
"ParseTemplateFiles": "func() (*template.Set, error)",
"ParseTemplateGlob": "func() (*template.Set, error)",
},
},
},
Func: map[string]string{
"template.New": "*template.Template",
"template.Must": "(*template.Template, error)",
"template.SetMust": "(*template.Set, error)",
},
}
func template(f *ast.File) bool {
// text/template または html/template をインポートしているか確認
if !imports(f, "text/template") && !imports(f, "html/template") {
return false
}
fixed := false
// 型情報を取得
typeof, _ := typecheck(templateTypeConfig, f)
// ASTを走査して名前を更新
walk(f, func(n interface{}) {
if sel, ok := n.(*ast.SelectorExpr); ok {
// トップレベル関数 template.ParseFile の参照
if isPkgDot(sel, "template", "ParseFile") {
sel.Sel.Name = "ParseFiles" // ParseFile を ParseFiles に書き換え
fixed = true
return
}
// ParseFile メソッドの参照 (template.Template 型のレシーバー)
if typeof[sel.X] == "*template.Template" && sel.Sel.Name == "ParseFile" {
sel.Sel.Name = "ParseFiles" // ParseFile を ParseFiles に書き換え
fixed = true
return
}
// Set 型とその関数は現在廃止されているため警告
for _, name := range templateSetGlobals {
if isPkgDot(sel, "template", name) {
warn(sel.Pos(), "reference to template.%s must be fixed manually", name)
return
}
}
// Set のメソッドも現在廃止されているため警告
for _, name := range templateSetMethods {
if typeof[sel.X] == "*template.Set" && sel.Sel.Name == name {
warn(sel.Pos(), "reference to template.*Set.%s must be fixed manually", name)
return
}
}
}
})
return fixed
}
template(f *ast.File) bool
関数が、このgofix
ルールの主要なロジックを含んでいます。imports(f, "text/template")
とimports(f, "html/template")
は、現在のファイルがtext/template
またはhtml/template
パッケージをインポートしているかどうかをチェックするgofix
のヘルパー関数です。これにより、関連のないファイルでの処理をスキップし、パフォーマンスを向上させます。typecheck(templateTypeConfig, f)
は、templateTypeConfig
で定義された型情報に基づいて、AST内の各式の型を推論します。これにより、例えばsel.X
が*template.Template
型であるかどうかを正確に判断できます。walk(f, func(n interface{}) { ... })
は、ASTを深さ優先で走査するためのgofix
のヘルパー関数です。匿名関数内で、各ASTノードn
が処理されます。if sel, ok := n.(*ast.SelectorExpr); ok
は、現在のノードがast.SelectorExpr
(例:pkg.Name
やexpr.Method
のような形式)であるかをチェックします。isPkgDot(sel, "template", "ParseFile")
は、セレクタ式がtemplate.ParseFile
のような形式であるかをチェックするgofix
のヘルパー関数です。これが真の場合、sel.Sel.Name = "ParseFiles"
によってParseFile
がParseFiles
に書き換えられます。typeof[sel.X] == "*template.Template" && sel.Sel.Name == "ParseFile"
は、セレクタ式のレシーバー(sel.X
)が*template.Template
型であり、かつ選択された名前がParseFile
であるかをチェックします。これも真の場合、ParseFile
がParseFiles
に書き換えられます。templateSetGlobals
とtemplateSetMethods
のループでは、template.Set
に関連するグローバル関数やメソッドの使用を検出し、warn()
関数を使って警告メッセージを出力します。warn()
は、指定された位置(sel.Pos()
)に警告メッセージを表示するgofix
のヘルパー関数です。
src/cmd/gofix/template_test.go
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func init() {
addTestCases(templateTests, template)
}
var templateTests = []testCase{
{
Name: "template.0",
In: `package main
import (
"text/template"
)
func f() {
template.ParseFile(a)
var t template.Template
x, y := template.ParseFile()
template.New("x").Funcs(m).ParseFile(a) // chained method
// Output should complain about these as functions or methods.
var s *template.Set
s.ParseSetFiles(a)
template.ParseSetGlob(a)
s.ParseTemplateFiles(a)
template.ParseTemplateGlob(a)
x := template.SetMust(a())
}
`,
Out: `package main
import (
"text/template"
)
func f() {
template.ParseFiles(a)
var t template.Template
x, y := template.ParseFiles()
template.New("x").Funcs(m).ParseFiles(a) // chained method
// Output should complain about these as functions or methods.
var s *template.Set
s.ParseSetFiles(a)
template.ParseSetGlob(a)
s.ParseTemplateFiles(a)
template.ParseTemplateGlob(a)
x := template.SetMust(a())
}
`,
},
}
addTestCases(templateTests, template)
は、templateTests
で定義されたテストケースを、template
関数(修正ロジック)に対して実行するように登録します。templateTests
はtestCase
型のスライスで、各要素が1つのテストケースを表します。In
フィールドは修正前の入力コード、Out
フィールドはgofix
による修正後の期待される出力コードです。- このテストケースでは、
template.ParseFile(a)
がtemplate.ParseFiles(a)
に、x, y := template.ParseFile()
がx, y := template.ParseFiles()
に、そしてチェーンされたメソッド呼び出しtemplate.New("x").Funcs(m).ParseFile(a)
がtemplate.New("x").Funcs(m).ParseFiles(a)
にそれぞれ書き換えられることを確認しています。 - また、
template.Set
に関連する呼び出し(s.ParseSetFiles(a)
など)はOut
コードでは変更されていませんが、gofix
の実行時にはこれらの行に対して警告が出力されることが期待されます。これは、gofix
のテストフレームワークが、コードの変更だけでなく、警告メッセージの出力も検証する能力を持っていることを示唆しています。
関連リンク
- Go Code Review: https://golang.org/cl/5436051
- Go Issue Tracker (関連する可能性のあるIssue): https://codereview.appspot.com/5433048 (コミットメッセージに記載されているリンク)
参考にした情報源リンク
- Go言語の
gofix
ツールに関する一般的な情報:- https://go.dev/blog/gofix (Go Blog: Gofix: A tool for updating Go programs)
- Go言語の
text/template
およびhtml/template
パッケージに関する情報: - Go言語のASTに関する情報:
- https://pkg.go.dev/go/ast
- https://go.dev/blog/go-ast-package (Go Blog: The Go AST Package)
- Go言語の歴史的なAPI変更に関する情報 (一般的な知識として):
- Go言語のリリースノートや、Goの公式ブログの過去記事などが参考になりますが、特定の
ParseFile
からParseFiles
への変更に関する詳細なブログ記事は特定できませんでした。これは、Goの初期開発段階でのAPI変更はドキュメント化が追いつかない場合があったためと考えられます。
- Go言語のリリースノートや、Goの公式ブログの過去記事などが参考になりますが、特定の