VSCodeで.clang-format-ignoreが無視される問題
タイトルの問題にぶち当たって原因を調査しPRを出すまでの記録です。
C/C++ Extensionで問題が出たんですが、同様の方法でclang-formatを起動する他の拡張機能も同様の問題を抱えていると思われます。
TL;DR
- 19.1.0時点のclang-formatは、書式化対象を標準入力から与えファイル名を
-assume-filename
オプションで指定する場合、.clang-format-ignore
を無視します。 - VSCodeのC/C++ Extensionはそのような方法でclang-formatを起動しているので、フォーマットの際に
.clang-format-ignore
が無視されます。 - この問題を修正するPRを出しておきました。
- (2024/10/25追記) 無事マージされたのでLLVM 20の頃には直ってるんじゃないかと思います。
.clang-format-ignore
clang-format 18.1.0から.clang-format-ignore
ファイルのサポートが追加されました。これは.gitignore
に似たフォーマットで1つ以上のパターンを記載しておくと、そのパターンのいずれかにマッチしたファイルについては書式化を行わないというモードです。
例えば以下のような.clang-format-ignore
を用意します。
foo/*.c
a*.c
このとき、それと同じディレクトリにあるabc.c
やfoo
サブディレクトリにあるxyz.c
などといったファイルに対してclang-format
を実行しても、何も起こりません。
$ clang-format abc.c # 何も出力されない(コマンド自体は正常終了する)
$ clang-format -i abc.c # 何も起こらない
$ clang-format -output-replacement-xml abc.c # 何も出力されない(!?)
最後の-output-replacement-xml
オプションを付けた場合であっても何も出てこないのは本当にそれでOKなのかよくわかりませんが…。
なお、すべての階層のサブディレクトリを指定する**
という表記は19.1.0現在まだ使えません。PRは出ているようなのでこれがマージされるのを待ちましょう。
問題: VSCode上で.clang-format-ignoreが効かない
C/C++ Extensionを入れると、VSCodeでclang-formatを使ったCファイルやC++ファイルの自動書式化が可能なのですが、なぜか.clang-format-ignoreが無視されてしまうという問題があります。
C/C++ Extensionにはclang-formatコマンドが同梱されており、システムに入っていない場合にはそれが代わりに使われます。その辺りが悪さをしているのかと疑いましたが、同梱コマンドも18.1.0以降のバージョンになっていたので、バージョンに起因する問題ではなさそうです。
C/C++ Extensionが発行しているコマンドを確認する
拡張機能の設定にLogging Levelというものがあり、これをデフォルトのErrorからDebugに変更することで、書式化の際どのようなコマンドが実行されているのかを確認することができます。
出力パネルからC/C++を選択すると、以下のような出力が確認できます。
ドキュメントの書式設定: file:///foo/bar/baz.c
Formatting Engine: clangFormat
...
/home/hoge/.vscode/extensions/ms-vscode.cpptools-1.22.10-linux-x64/bin/../LLVM/bin/clang-format -style=file -fallback-style=LLVM --Wno-error=unknown -assume-filename=/foo/bar/baz.c
どうやらC/C++ Extensionでは、(たとえファイル全体の書式化であっても)clang-formatコマンドにファイルを直接指定することはせず、標準入力からファイルの内容を与え、標準出力から書式化の結果を受け取っているようです。
-assume-filename
オプションは、標準入力から書式化したい内容を与える際に、書式化対象のファイル名を指定する機能です。これにより、-style=file
オプションを与えた場合に適切な.clang-format
ファイルを探したり、拡張子から言語を推定したりすることができるようになります。
(以下、https://clang.llvm.org/docs/ClangFormat.html より引用)
--assume-filename=<string> - Set filename used to determine the language and to find
.clang-format file.
Only used when reading from stdin.
If this is not passed, the .clang-format file is searched
relative to the current working directory when reading stdin.
...
--style=<string> - Set coding style. <string> can be:
...
2. 'file' to load style configuration from a
.clang-format file in one of the parent directories
of the source file (for stdin, see --assume-filename).
If no .clang-format file is found, falls back to
--fallback-style.
--style=file is the default.
clang-formatの挙動を確認
C/C++ Extensionがどのようなコマンドを実行しているかが分かったので、コマンドラインから同じオプションを指定して手動でclang-formatを実行し、その挙動を確認していきます。
以下のような.clang-format-ignoreファイルを用意しました。
foo.c
同じディレクトリに適当な内容のfoo.cファイルを用意して試します。
$ clang-format foo.c # 何も出ない
$ clang-format -assume-filename=foo.c < foo.c
int main(void) { return 0; }
なんか出た!
どうやらclang-formatは-assume-filename
オプションで指定したファイル名に対して.clang-format-ignore
を適用しないようです。.clang-format
の適用はしてくれるのに…。
ソースコードを確認する
clang-formatコマンドのmain()
を見てみます。
(本記事の著者によるコメントを適宜挿入しています)
// コマンドラインに処理対象のファイル名が指定されなかった場合は、
// 標準入力を対象として書式化を実行し終了する
if (FileNames.empty())
return clang::format::format("-", FailOnIncompleteFormat);
...
// コマンドラインで指定された各ファイルを順次処理していく
for (const auto &FileName : FileNames) {
// .clang-format-ignoreのチェック
const bool Ignored = isIgnored(FileName);
...
// チェックに引っかかった場合は何もせず次のファイルに移る
if (Ignored)
continue;
// --verboseオプションが指定された場合は処理対象ファイルのファイル名を出力する
if (Verbose) {
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
<< FileName << "\n";
}
// 書式化を実行
Error |= clang::format::format(FileName, FailOnIncompleteFormat);
}
ということで、先ほど確認した通り、(コマンドラインから処理対象ファイルを指定せず)書式化対象のコードを標準入力から与えた場合、.clang-format-ignoreのチェックは行われません。
修正案
PR#113100を出しました。
コマンドラインに処理対象のファイル名が指定されず、かつ-assume-filename
が指定されている場合、無視チェックを行い、チェックに引っかかった場合には何も出力せずコマンドを終了します。
(-output-replacements-xml
指定時にこの挙動でよいのか正直自信がありません…)
マージされるまで待ってられない人は自前でclang-formatをビルドしてそれを使ってください(参考)。
$ git clone -b assume-filename-with-clang-format-ignore --filter=blob:none https://github.com/kakkoko/llvm-project.git
$ cd llvm-project
$ cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_BUILD_TYPE=Release
$ cmake --build build
$ cp build/bin/clang-format (適当な場所)
Discussion