診断の正確性: 信頼できるエラー
SharpLsp チーム
Nimblesite エンジニアリング
診断は開発者のフィードバックループです。Problems パネルは、コードが正しいかどうかを開発者が確認する場所です。存在しないエラーが表示されれば、開発者はそれを無視し始めます。ビルドを壊すエラーを見逃せば、CI で驚くことになります。どちらの失敗もツールへの信頼を壊します。
SharpLsp の診断アーキテクチャには 1 つの目標があります。正確性です。コンパイラーまたはアナライザーが報告するすべてのエラーはエディターに表示されなければなりません。実際のビルドに現れないエラーは表示されてはいけません。Problems パネルは dotnet build が伝える内容を正確に反映するべきです。多すぎず、少なすぎず。
これは単純に聞こえます。実際には、ワークスペースのライフサイクル、LSP プロトコル、Roslyn の解析パイプラインの動作を慎重に設計する必要があります。
Roslyn における「本物」のエラー
.NET Compiler Platform (Roslyn) は、大きく 2 種類の診断を生成します。それぞれ接頭辞と重大度の扱いが異なります。
コンパイラー診断は、C# では CS、F# では FS の接頭辞を持ちます。型エラー、存在しないメンバー、構文違反、到達不能コードなど、言語コンパイラー自身が生成するエラーと警告です。CS0246 は型名を解決できなかったことを意味します。CS0019 は演算子をそのオペランド型に適用できないことを意味します。これらの診断は権威あるものです。完全なセマンティック解析の後で、コンパイラーがコードについて知っていることを反映します。
Roslyn アナライザー診断は CA(コード品質)または IDE(コードスタイル)の接頭辞を持ち、.NET コード解析の概要に記載されています。Microsoft のドキュメントには次のようにあります。
"Code analysis violations appear with the prefix 'CA' or 'IDE' to differentiate them from compiler errors."
コード解析違反は、コンパイラーエラーと区別するために
CAまたはIDEの接頭辞付きで表示されます。
アナライザー診断は重大度を構成できます。提案、警告、エラーのいずれにもできます。チームが .editorconfig、<TreatWarningsAsErrors> プロパティ、または <AnalysisMode> 構成でアナライザールールを error 重大度に設定した場合、その診断はビルドを失敗させます。これは任意のノイズではありません。ビルドを止めるエラーです。SharpLsp はそれを CS エラーと同じ重要度で表示する必要があります。プロジェクトの観点では同等だからです。
サードパーティのアナライザーパッケージ、たとえば StyleCop、Roslynator、Meziantou.Analyzer、SonarAnalyzer も同じパイプラインに組み込まれます。それらの診断は Roslyn の DiagnosticAnalyzer 基盤で処理され、同じ重大度モデルで報告されます。SharpLsp はこれらを二級扱いしません。StyleCop のルールがプロジェクトでエラーとして構成されていれば、Problems パネルにもエラーとして表示されます。ビルドでもエラーになるからです。
誤ったエラーの発生源
診断の正確性の敵は、早すぎる断定です。正しい診断を計算するために必要な情報をワークスペースが持つ前に、診断を表示してしまうことです。
大規模な .NET ソリューションは一度にセマンティックに完全な状態にはなりません。ソリューションを開くと、Roslyn が権威ある答えを出せるようになる前に、いくつかのことが起きる必要があります。
-
NuGet restore が完了している必要があります。 Roslyn の
MSBuildWorkspaceは、パッケージ参照とその推移閉包を理解するためにproject.assets.jsonに依存します。このファイルが最新でない間、NuGet パッケージから来る型参照は解決できません。この期間に計算された診断は、パッケージ内に存在する型に対してCS0246を報告することがあります。restore が終わった瞬間に消えるエラーです。 -
ソースジェネレーターが実行されている必要があります。 Source generator はコンパイル時に C# ファイルを生成します。ジェネレーターが実行される前にワークスペースが診断を生成すると、生成型への参照は未定義シンボルに見えます。これも本物ではないエラーの一種です。
-
プロジェクト参照が解決されている必要があります。 複数プロジェクトのソリューションでは、あるプロジェクトの型グラフには参照先プロジェクトで定義された型が含まれます。Roslyn が参照先プロジェクトをまだ読み込んでいなければ、プロジェクト間のシンボル参照は未解決に見えます。
パターンは常に同じです。ワークスペースの準備が整う前に診断を急いでプッシュする言語サーバーは、不完全なワークスペース状態から見ると技術的には一貫しているものの、コンパイラーが実際に報告する内容から見ると間違っているエラーを生成します。これはコンパイラーのバグではありません。タイミングの問題です。サーバーが早すぎる断定をしたのです。
LSP 3.17 プル診断モデル
このタイミング問題に対する SharpLsp の解決策は、LSP 3.17 プル診断モデルです。
従来のプッシュモデルでは、言語サーバーが診断が変わったと判断したタイミングで textDocument/publishDiagnostics 通知を送信します。タイミングはサーバーが制御します。ワークスペースが正確な結果を出せる状態かどうかに関係なく、エディターはサーバーが送ったものを受け取ります。
プルモデルはこれを反転します。エディターが必要なときに、次の 2 つの LSP エンドポイントで診断を要求します。
textDocument/diagnostic— 特定ドキュメントの診断をプルしますworkspace/diagnostic— ワークスペース全体の診断をプルし、現在開いていないファイルのエラーもエディターが表示できるようにします
サーバーは各プルに対して、結果識別子を持つ結果を返します。これは現在の診断セットの状態を表すトークンです。エディターが再度プルしたときにワークスペース状態が変わっていなければ、サーバーは DocumentDiagnosticReportKind.Unchanged を返して冗長な計算を避けられます。これにより、大規模ソリューションで変更のないファイルを繰り返しプルしても安く済みます。
ワークスペース状態が変化したとき、たとえばファイルが保存された、パッケージが restore された、プロジェクト参照が追加されたときには、サーバーが workspace/diagnostic/refresh 通知を送信します。これは診断ペイロードではありません。エディターのキャッシュ結果を破棄し、再度プルするべきだという信号です。再プルのタイミングはエディターが制御します。サーバーは古い結果をプッシュできません。
この設計により、SharpLsp は「今が診断を断定するよいタイミングだ」と一方的に決める必要がありません。エディターが尋ねるのを待ちます。尋ねられたとき、信頼できる答えがあれば返します(ワークスペースが準備済み)。まだであれば正直にそう扱います(ワークスペースがロード中)。空白を埋めるためにエラーを作りません。
NuGet Restore ゲート
.NET ツールで偽の CS0246 エラーを生む最大の要因は、古いまたは欠落した project.assets.json です。これはコンパイラーにパッケージアセンブリの場所を伝える restore グラフです。
SharpLsp は MSBuildWorkspace でソリューションを開く前に restore 状態を確認します。パッケージが restore されていなければ、dotnet restore を実行し、完了を待ってからワークスペースのロードを始めます。診断パイプラインは、パッケージ参照を解決できる状態になるまで開きません。
これにより初回オープン体験には遅延が加わります。この環境でまだ restore されていないソリューションでは、通常は数秒です。その代わり、ゲートを通過した後にエディターが受け取る診断は、完全なパッケージグラフを知っているワークスペースに対して計算されます。パッケージ内に存在する型に対する CS0246 エラーはありません。30 秒後に消える赤い波線もありません。
ソリューション全体の診断
開いているファイルだけに対して正確な診断を出すだけでは十分ではありません。実際のビルド失敗は、エディターで開いていないファイルに由来することがよくあります。共有型への破壊的変更は、それを使うすべてのファイルでエラーを引き起こします。その多くは閉じられているかもしれません。
SharpLsp は workspace/diagnostic を使って、ソリューション全体のエラーをカバーします。エディターがワークスペース診断レポートを要求すると、SharpLsp は読み込まれたすべてのプロジェクトのすべてのドキュメントについて Roslyn に診断を問い合わせます。結果には次が含まれます。
- 任意のソースファイルにあるすべての
CS/FSコンパイラーエラー - 組み込み SDK アナライザーと、プロジェクトが参照するサードパーティアナライザーパッケージの両方から来る、
errorまたはwarning重大度のすべてのCAとIDEアナライザー診断 - 出力がコンパイルに失敗するソースジェネレーターが生成した診断
結果には次は含まれません。
- 不完全なワークスペース状態に対して計算された診断
warningより低い重大度に構成され、ビルドには現れないアナライザー提案- プロジェクトグラフから除外されたファイルのエラー
これは dotnet build が報告する内容を反映します。Problems パネルは重要なものを表示します。ノイズは表示しません。
ファイルが変わると、サイドカーはそのドキュメントの更新済み診断を計算し、workspace/diagnostic/refresh を送ります。エディターは更新されたワークスペースレポートをプルします。結果識別子が変わっていないファイルはサーバー側でスキップされます。往復コストは実際に変わったものに比例します。
F# 診断
F# サイドカーは FSharp.Compiler.Service を使い、FSharpChecker 経由で診断を生成します。FCS 診断は FS 接頭辞を持ち、F# 型チェッカーがプロジェクト全体に対して持つ見解を反映します。判別共用体の網羅性、未実装インターフェイス、未使用バインディング(FS0026)、部分アクティブパターンなどが含まれます。
FSharpLint は F# のアナライザー層を提供し、ルールの重大度を構成できます。ビルドを止めるように構成された lint エラーは、Roslyn の CA エラーと同じ扱いで表示されます。Problems パネルに現れ、フィルターされません。
.fsproj における F# ファイル順序はコンパイルに影響します。ファイル順序が間違っているために発生する前方参照について、FCS はエラーを報告します。SharpLsp はこれを本物の診断として表示します。本物のビルドエラーだからです。同時に、開発者が修正を理解できるように順序が原因であることを示します。
Problems パネルが伝えるべきこと
SharpLsp が目指しているのは、ビルドの判定器として扱える Problems パネルです。コミットする前に。プッシュする前に。CI が走る前に。Problems パネルが空なら、ビルドは通ります。エラーがあれば、ビルドはまさにその理由で失敗します。
これには 3 つのものが連携する必要があります。
- 初回表示から正確であること — NuGet restore ゲートにより、診断が流れる前にワークスペースの準備が整います
- 完全なカバレッジ — ワークスペース診断は開いているファイルだけでなくすべてのファイルをカバーし、ビルドを失敗させるすべてのアナライザー重大度を含みます
- 正確な無効化 —
workspace/diagnostic/refreshは、早すぎる断定をせずに、ファイル変更時にエディターを現実と同期させます
信頼を得る診断パネルとは、開発者が疑い直さなくなるパネルです。それが目標です。