[インデックス 13879] ファイルの概要
このコミットは、Go言語のVimプラグインにおけるインポートの削除(Drop
コマンド)に関するバグ修正です。具体的には、複数のインポートグループが存在する場合に、最初のグループ以降のインポートが正しく削除されない問題を解決します。
コミット
commit adcf0a2aa06ec1424b4d51d2b7ce043a60d29361
Author: David Symonds <dsymonds@golang.org>
Date: Thu Sep 20 08:11:07 2012 +1000
misc/vim: fix Drop for imports after the first group.
Previously, an import block such as
import (
"net"
"stack"
)
would not permit ":Drop stack" to work because we were aborting
the scan early, which is only correct when Import is in operation.
R=golang-dev, franciscossouza
CC=golang-dev
https://golang.org/cl/6532053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/adcf0a2aa06ec1424b4d51d2b7ce043a60d29361
元コミット内容
このコミットは、Go言語のVimプラグイン(misc/vim/ftplugin/go/
以下に存在するファイル群)におけるインポート管理機能の修正を目的としています。特に、Vimのコマンドラインから特定のインポートパスを削除する:Drop
コマンドが、Goのソースコード内でインポートが複数のグループに分かれている場合に正しく機能しないという問題に対処しています。
具体的には、以下のようなインポートブロックがあった場合:
import (
"net"
"stack"
)
:Drop stack
というコマンドを実行しても、「stack」インポートが削除されないという問題がありました。これは、インポートをスキャンする処理が、最初のインポートグループの後に早期に中断されてしまっていたためです。この早期中断のロジックは、インポートを追加する:Import
コマンドの場合には適切でしたが、インポートを削除する:Drop
コマンドの場合には不適切でした。
変更の背景
Go言語のソースコードでは、import
文をグループ化し、グループ間に空行を挿入することで、標準ライブラリ、サードパーティライブラリ、プロジェクト内のローカルパッケージなどを視覚的に区別する慣習があります。VimのGoプラグインは、これらのインポートを効率的に管理するための機能(追加、削除など)を提供しています。
このコミット以前は、:Drop
コマンドがこのグループ化されたインポート構造を正しく扱えず、最初のインポートグループ以降に記述されたインポートパスを認識・削除できないというバグが存在していました。これにより、開発者はVimのインポート管理機能の恩恵を十分に受けられず、手動でインポートを削除する必要がありました。この修正は、VimユーザーのGo開発体験を向上させるために不可欠でした。
前提知識の解説
Vimとftplugin
- Vim: 高機能なテキストエディタであり、プログラミングにおいて広く利用されています。Vimは非常にカスタマイズ性が高く、プラグインによって機能を拡張できます。
- ftplugin (filetype plugin): Vimのプラグインの一種で、特定のファイルタイプ(例: Go言語のファイル)を開いたときに自動的にロードされるスクリプトです。これにより、そのファイルタイプに特化した機能や設定を提供できます。
misc/vim/ftplugin/go/
ディレクトリは、Go言語ファイル用のVimプラグインファイルが格納されている場所です。
Go言語のimport文
Go言語では、外部パッケージの機能を利用するためにimport
文を使用します。
- 単一インポート:
import "fmt"
- グループインポート:
import ( "fmt" "net/http" )
- インポートグループ: Goの慣習として、インポートは関連性に基づいてグループ化され、グループ間に空行を挟むことがあります。例えば、標準ライブラリ、サードパーティライブラリ、プロジェクト内のローカルパッケージなどでグループを分けることがあります。
golang.org/cl
golang.org/cl/
は、GoプロジェクトのコードレビューシステムであるGerritの変更リスト(Change-List)へのリンクです。Goプロジェクトでは、すべてのコード変更はGerritを通じてレビューされ、承認された後にメインリポジトリにマージされます。https://golang.org/cl/6532053
はこのコミットに対応するGerritの変更リストを示しており、詳細な議論やレビューの履歴を確認できます。
技術的詳細
この修正は、misc/vim/ftplugin/go/import.vim
ファイル内のs:SwitchImport
関数に焦点を当てています。この関数は、インポートの追加(Import
)と削除(Drop
)の両方のロジックを処理する汎用的な関数です。
問題の核心は、インポートブロックをスキャンするループ内で、siteprefix == ""
という条件が満たされた場合に早期にループをbreak
していた点にありました。siteprefix
が空であることは、通常、標準ライブラリのインポートや、インポートブロックの最初のグループに属するインポートを示します。
-
既存のロジック(問題点):
if siteprefix == "" " must be in the first group break endif
このロジックは、インポートを追加する
:Import
コマンドの場合には、新しいインポートを適切なグループ(通常は最初のグループ)に挿入するために、最初のグループを見つけたらスキャンを中断するという意味で理にかなっていました。しかし、インポートを削除する:Drop
コマンドの場合、削除対象のインポートが最初のグループ以降のグループに存在しても、このbreak
によってスキャンが中断されてしまい、対象のインポートが見つからずに削除が失敗していました。 -
修正後のロジック:
if siteprefix == "" && a:enabled " must be in the first group break endif
修正では、
break
条件に&& a:enabled
が追加されました。ここで、a:enabled
はs:SwitchImport
関数に渡される引数であり、この関数がインポートを追加するモード(a:enabled
が真)で動作している場合にのみ、早期のbreak
が実行されるように制御します。a:enabled
が真(Import
操作)の場合: 以前と同様に、最初のグループでスキャンが中断され、新しいインポートが挿入されます。a:enabled
が偽(Drop
操作)の場合:siteprefix == ""
の条件が満たされてもbreak
は実行されず、スキャンはインポートブロック全体を継続します。これにより、:Drop
コマンドはすべてのインポートグループを走査し、対象のインポートパスを正しく見つけて削除できるようになります。
また、misc/vim/ftplugin/go/test.sh
のテストスクリプトも更新され、test_one
関数がより汎用的にVimコマンドを実行できるように変更されました。そして、複数のインポートグループを持つbase.go
ファイルを作成し、:Drop
コマンドが正しく機能するかを検証する新しいテストケースが追加されました。これにより、修正が意図通りに動作し、将来のリグレッションを防ぐことが保証されます。
コアとなるコードの変更箇所
misc/vim/ftplugin/go/import.vim
--- a/misc/vim/ftplugin/go/import.vim
+++ b/misc/vim/ftplugin/go/import.vim
@@ -96,7 +96,7 @@ function! s:SwitchImport(enabled, localname, path)\
let linestr = getline(line)
let m = matchlist(getline(line), '^\\()\\|\\(\\s\\+\\)\\(\\S*\\s*\\)"\\(.\\+\\)"\\)\'')
if empty(m)
- if siteprefix == ""
+ if siteprefix == "" && a:enabled
" must be in the first group
break
endif
misc/vim/ftplugin/go/test.sh
--- a/misc/vim/ftplugin/go/test.sh
+++ b/misc/vim/ftplugin/go/test.sh
@@ -22,36 +22,53 @@ EOF
fail=0
-# usage: test_one new_import pattern
+# usage: test_one command pattern
test_one() {
- echo 2>&1 -n "Import $1: "
+ echo 2>&1 -n "$1: "
vim -e -s -u /dev/null -U /dev/null --noplugin -c "source import.vim" \
- -c "Import $1" -c 'wq! test.go' base.go
+ -c "$1" -c 'wq! test.go' base.go
# ensure blank lines are treated correctly
if ! gofmt test.go | cmp test.go; then
echo 2>&1 "gofmt conflict"
- gofmt test.go | diff -u test.go - | sed "s/^/\t/" 2>&1
+ gofmt test.go | diff -u test.go - | sed "s/^/\t/" 2>&1
fail=1
return
fi
if ! grep -P -q "(?s)$2" test.go; then
echo 2>&1 "$2 did not match"
- cat test.go | sed "s/^/\t/" 2>&1
+ cat test.go | sed "s/^/\t/" 2>&1
fail=1
return
fi
echo 2>&1 "ok"
}
-test_one baz '"baz".*"bytes"'
-test_one io/ioutil '"io".*"io/ioutil".*"net"'
-test_one myc '"io".*"myc".*"net"' # prefix of a site prefix
-test_one nat '"io".*"nat".*"net"'
-test_one net/http '"net".*"net/http".*"mycorp/foo"'
-test_one zoo '"net".*"zoo".*"mycorp/foo"'
-test_one mycorp/bar '"net".*"mycorp/bar".*"mycorp/foo"'
-test_one mycorp/goo '"net".*"mycorp/foo".*"mycorp/goo"'
+# Tests for Import
+
+test_one "Import baz" '"baz".*"bytes"'
+test_one "Import io/ioutil" '"io".*"io/ioutil".*"net"'
+test_one "Import myc" '"io".*"myc".*"net"' # prefix of a site prefix
+test_one "Import nat" '"io".*"nat".*"net"'
+test_one "Import net/http" '"net".*"net/http".*"mycorp/foo"'
+test_one "Import zoo" '"net".*"zoo".*"mycorp/foo"'
+test_one "Import mycorp/bar" '"net".*"mycorp/bar".*"mycorp/foo"'
+test_one "Import mycorp/goo" '"net".*"mycorp/foo".*"mycorp/goo"'
+
+# Tests for Drop
+
+cat > base.go <<EOF
+package test
+
+import (
+ "foo"
+
+ "something"
+ "zoo"
+)
+EOF
+
+test_one "Drop something" '([^\"]*\"foo\"[^\"]*\"zoo\"[^\"]*)'
rm -f base.go test.go
if [ $fail -gt 0 ]; then
コアとなるコードの解説
misc/vim/ftplugin/go/import.vim
の変更
s:SwitchImport
関数: この関数は、VimのGoプラグインにおけるインポートの追加(Import
)と削除(Drop
)の主要なロジックを担っています。if siteprefix == "" && a:enabled
:siteprefix == ""
: これは、現在処理しているインポートパスが、Goの標準ライブラリの一部であるか、またはインポートブロックの最初のグループに属していることを示唆する条件です。a:enabled
: これはs:SwitchImport
関数に渡されるブール値の引数で、この関数がインポートを追加するモード(true
)で呼び出されているか、削除するモード(false
)で呼び出されているかを制御します。- 変更前は
if siteprefix == ""
のみでbreak
していました。これにより、Drop
操作時でも最初のインポートグループでスキャンが中断され、それ以降のグループにあるインポートが処理されませんでした。 - 変更後は
&& a:enabled
が追加されたことで、この早期break
はImport
操作(a:enabled
がtrue
)の場合にのみ適用されるようになりました。Drop
操作(a:enabled
がfalse
)の場合、この条件は満たされず、スキャンはインポートブロック全体を継続するため、すべてのインポートグループから対象のインポートを正しく見つけて削除できるようになります。
misc/vim/ftplugin/go/test.sh
の変更
test_one
関数の汎用化:- 以前は
test_one new_import pattern
という形式で、Import
コマンド専用でした。 - 変更後は
test_one command pattern
となり、vim -c "$1"
のように、任意のVimコマンドを第一引数として受け取れるようになりました。これにより、Import
だけでなくDrop
コマンドのテストも同じフレームワークで実行できるようになりました。
- 以前は
Import
テストの更新:test_one
の呼び出しが、新しいtest_one "Import baz"
のような形式に更新されました。Drop
テストの追加:cat > base.go <<EOF ... EOF
ブロックで、複数のインポートグループを持つbase.go
ファイルが動的に作成されます。このファイルには、"foo"
、"something"
、"zoo"
の3つのインポートが含まれ、"something"
と"zoo"
は空行で区切られた別のグループに配置されています。test_one "Drop something" '([^\"]*\"foo\"[^\"]*\"zoo\"[^\"]*)'
という新しいテストが追加されました。"Drop something"
: Vimに対してsomething
インポートを削除するよう指示します。'([^\"]*\"foo\"[^\"]*\"zoo\"[^\"]*)'
: これは正規表現パターンです。テスト実行後、test.go
ファイル(base.go
が変更されたもの)の内容がこのパターンにマッチするかどうかをgrep -P -q
で確認します。このパターンは、"foo"
と"zoo"
がファイル内に存在し、かつ"something"
が存在しないことを検証します。これにより、something
が正しく削除され、他のインポートが影響を受けていないことが確認されます。
これらの変更により、VimのGoプラグインは、Go言語のインポートグループの慣習に沿ったコードに対しても、インポートの追加・削除を正確に実行できるようになりました。
関連リンク
- Go言語の
import
宣言: https://go.dev/ref/spec#Import_declarations - Vimのファイルタイププラグインに関するドキュメント:
:help filetype-plugins
(Vim内で実行) - Gerrit Code Review: https://gerrit-review.googlesource.com/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Vimの公式ドキュメント
- Gerrit Code Reviewのインターフェース
- コミットメッセージと差分情報
- Go言語のコーディングスタイルガイド(インポートのグループ化に関する慣習)
- Vimscriptの構文と関数に関する一般的な知識
- 正規表現(PCRE)に関する一般的な知識