CSharp.Refactor について
CSharp.Refactorは、正確性、パフォーマンス、慣用的なコードを重視したリファクタリング提案を行う、C#開発者向けのオフラインRoslynアナライザー兼コマンドラインツールです。エディターのクイックフィックスと一括変更に対応し、変更をビルドで検証します。提案は情報レベルで、ビルドを妨げません。
主な機能
- 01RoslynによるオフラインC#解析
- 02エディターの電球型修正とコマンドライン一括編集
- 03ドライラン、ルール絞り込み、API変更制御
- 04ビルド検証と最終ビルド失敗時のロールバック
- 05ベースライン対応のSARIF・HTML・CSVレポート
活用シーン
- 01プロジェクトを変更する前にリファクタリング案を確認する
- 02ソリューション全体に選択した修正を適用してビルドを検証する
- 03CIで解析結果をSARIFとしてアップロードする
MCP 対応
`csharp-refactor`コマンドは標準入出力経由でMCPサーバーとして実行でき、`analyze`と`list_rules`を公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
CSharp.Refactor
FSharp.Refactor の C# の妹分です
Roslyn 上で F# により実装された、C# 向けの関数型リファクタリング提案です。 ルールでは、正確性、測定されたパフォーマンス、明快で慣用的な C# を重視し、命名、レイアウト、規約は意図的に対象外としています。
- エディター上の電球型クイックフィックス(Visual Studio、VS Code、Rider など、Roslyn アナライザーをホストするもの)
- 一括適用するコマンドラインツール。適用後にビルドで検証します
提案の重大度は Info です。問題ではなく改善の機会を示すもので、ビルドを妨げることはありません。
使用方法
このツールは完全にオフラインで動作します。作成には AI が使われていますが、ツール自体は AI を使用しません。
クイックスタート
設定は不要です。ツールはプロジェクトを読み取り、変更内容を報告し、指示があった場合にのみ編集します。
dotnet tool install --global csharp-refactor
csharp-refactor Your.csproj --dry-run --api-changes
ファイルと位置を添えて、適用する修正をすべて表示しますが、何も書き込みません。リストに問題がなければ、フラグを外して適用します。
csharp-refactor Your.csproj --api-changes
パブリックメソッド/APIを変更したくない場合は、--api-changes を外します。
csharp-refactor Your.fsproj
すでにビルドが成功しているプロジェクトだけを受け付け、各パスの後にメモリ上で再コンパイルし、最後に実際にプロジェクトをビルドします。ビルドに失敗した場合は修正を元に戻します。
入力中に電球型クイックフィックスを使う方法については、以下の IDE プラグインの手順をご覧ください:VS Code / Ionide および Visual Studio。
エージェントを活用するシナリオ向けに、dotnet ツールは MCP をサポートしています。
ツールの使用方法:
対象は何でも指定できます。種類はパスから判別されます。
Your.csproj | 1 つのプロジェクト |
Thing.cs | 1 つのソースファイル。プロジェクトを特定して解析しますが、編集するのはそのファイルだけです |
Your.sln、Your.slnx | ソリューションに含まれるすべての C# プロジェクト |
src/ | そのディレクトリにあるソリューション、またはその配下のプロジェクト |
"src/**/*.csproj" | glob に一致するすべて |
build.csx | 1 つの C# スクリプト。独自のコンパイル(#r、#load、NuGet キャッシュ)を使用し、MSBuild は一切使いません。ディレクトリを指定すると、その中の単独スクリプトも対象になります |
C:\git | チェックアウトをまとめたワークスペース。個別に解析し、1 つのレポートにまとめます |
エディターと CI のセットアップ
アナライザーは、通常の Roslyn アナライザーパッケージである
CSharp.Refactor.Analyzers
として提供されています。解析対象のプロジェクトから参照すると、dotnet build、Visual Studio、VS Code の C# 拡張機能、Rider など、どのホストでも自動的に読み込まれます。
<PackageReference Include="CSharp.Refactor.Analyzers" Version="*" PrivateAssets="all" />
このパッケージは開発時の依存関係です。ヒントとクイックフィックスを提供するだけで、コンパイル済みの出力や bin に含まれるものはありません。
Visual Studio 2022 / 2026
CSharp.Refactor 拡張機能をインストールすると、IDE で開いたすべての C# プロジェクトに提案と Ctrl+. の修正が表示されます。プロジェクトを変更する必要はなく、ビルドにも影響しません。Tools > CSharp.Refactor メニューでは、開いているソリューションに対して csharp-refactor ツールを実行できます(適用またはドライラン、--api-changes の有無を選択可能)。SARIF レポートを書き出し、ノートのページと .editorconfig ブロックを開きます。Visual Studio で VS Code 拡張機能のコマンドを利用できます。上記のパッケージ参照を使う方法もあります。どちらも動作し、両方を使っているプロジェクトでも各検出結果は一度だけ表示されます。
VS Code
CSharp.Refactor 拡張機能をインストールしてください。アナライザーが同梱されており、許可すると、MSBuild のユーザー向け ImportBefore フックを通じて、このマシン上のすべての C# プロジェクトに設定されます(ユーザープロファイル内の props ファイル 1 つで設定され、コマンドで元に戻せます)。パッケージ参照を使う方法もあります。
CI
アナライザーはコンパイラー内で実行されるため、追加のツールなしでビルドの検出結果を SARIF 形式で出力できます:
dotnet build -p:ErrorLog=findings.sarif
このツール独自のレポートには、提案された変更としての修正、コンテキストの
スニペット、安定したフィンガープリントなど、より多くの情報が含まれます。また、
リンティングのゲートには --dry-run を組み合わせて使用できます:
- run: dotnet tool install --global csharp-refactor
- run: csharp-refactor src/Your.csproj --dry-run --report findings.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: findings.sarif
category: csharp-refactor
ドライランは、検出結果の有無にかかわらず終了コード 0 で終了します。検出結果が
フィルター適用後に残っている場合に終了コード 3 で終了させるには
--fail-on-findings を指定します。--baseline を指定すると、これがラチェットとして
機能します。以前のレポートにすでに含まれている検出結果は報告も修正もされず、
新しい検出結果のみが表示されます。
コマンドラインからの修正の適用
csharp-refactor Your.csproj [--dry-run] [--codes CR0090,CR0103] [--categories correctness,performance] [--api-changes]
| フラグ | |
|---|---|
--dry-run | 報告のみです。適用する修正をすべて一覧表示し、何も書き込みません。 |
--codes CR0090,CR0103 | 実行するルールを指定したものに限定します。それ以外のルールは一切実行されません。ルールを指定すると、そのルールの既定の無効状態や設定の none よりも優先されます。 |
--categories <list> | ルールの種類を限定します。correctness、performance、idiom、cosmetic を指定できます。指定した種類以外のルールは実行されません。管理していないリポジトリでは、プルリクエストを送る価値があるのは correctness,performance です。 |
--define <symbols> | 実行時に定義するプリプロセッサシンボルです。csc -define: や DefineConstants エントリと同様に、繰り返し指定するか、; または , で区切って指定できます(--define:A と -d:A も使用できます)。各プロジェクト独自の DefineConstants に追加されます。DEBUG、TRACE、プロジェクトの定数はそのまま残ります。#if 内のコードを参照してください。 |
--api-changes | 内部または公開シグネチャや構造を変更し、ソリューション全体の呼び出し箇所を書き換える修正も適用します(参照オラクルがすべてのプロジェクトで検出します。VB プロジェクトの呼び出し元が修正を保持し、F# の利用側が公開面を保持します)。このオプションなしでは保留され、件数に含まれます。 |
--report <file> | すべての検出結果を .sarif、.html(自己完結型のページ)、または .csv で書き込みます。 |
--baseline <sarif> | ラチェットです。この以前のレポートにフィンガープリントが含まれる検出結果は、報告も修正もされません。 |
--fail-on-findings | フィルター適用後に検出結果が残っている場合、終了コード 3 で終了します。終了コードの仕様:問題なしは 0、失敗は 1、使用方法の誤りは 2、検出結果ありは 3 です。 |
--notes [on|off|only] | 修正を伴わない助言メモをインラインで一覧表示します。only はレビュー用の実行で、何も書き込みません。既定では、実行結果に修正内容とカテゴリごとのメモの件数が表示されます。レポートと JSON には常にメモが含まれます。 |
--format json | 機械可読な標準出力を使用します。進行状況を示すテキストは標準エラー出力に移ります。 |
--rules | ルールカタログを表示します。 |
--create-config | すべてのルールとキーを既定値に設定したコメント付きブロックを、ディレクトリの .editorconfig に追記します。 |
--mcp | analyze と list_rules を、呼び出し間でウォーム状態のワークスペースを1つ維持する MCP サーバーとして、標準入出力経由で提供します。 |
--parse-only | 参照を使用せず、構文のみを対象とするルールを実行します。通常の実行の代わりにはなりません。 |
--max-passes <n> | 修正後に再解析する反復回数です(既定値は 5)。最初のパス以降は、直前のパスで編集されたファイル、または修正が保留されたファイルのみを再解析します。 |
プロジェクトは MSBuildWorkspace を通じて読み込まれます。net48 を含め、あらゆるターゲットの SDK スタイルのプロジェクトと、プロジェクトに記載されたあらゆるフレームワークの種類に対応します。レガシープロジェクト(明示的な <Compile> 項目を含む <Project ToolsVersion="4.0" …>)は、.NET Framework のビルドホストが応答しない場合、プロジェクトファイルから読み取られます。対象は、コンパイル項目、定義、存在する HintPath 参照、ターゲットフレームワークパックのアセンブリ、プロジェクト参照です。ビルドツリーに含まれないもの(カスタム .targets が追加する参照や、隣接する bin など)は、一部の型が未解決のままになります。実行結果にはその件数が表示され、型が不明な場合は型に依存するルールが適用されず、インメモリ検証ではエラー数が維持されます。ベースラインビルドが成功する場合、最後のビルドは Visual Studio の MSBuild.exe を通じて実行されます。そうでない場合、実行はメモリ内のみで検証され、その旨が表示されます。ソリューション内の別プロジェクト(テストなど)から参照されている実行可能プロジェクトは、末端ではありません。--api-changes を指定しない限り、その公開面は維持され、変更されたプロジェクトに依存するすべてのプロジェクトもビルドされます。
C# スクリプト(.csx)は独立したコンパイル単位です。ファイルはスクリプトとして解析され、参照には実行中のランタイムが使われます。#r はスクリプトのディレクトリ、ランタイム、NuGet キャッシュ(#r "nuget: Name, Version")を基準に解決されます。#load はコンテキスト用にコンパイラーによって読み取られます(読み込まれたファイルは、それ自体が対象として処理される際に編集され、ディレクトリを指定すると、ディレクトリ内のすべての単独のスクリプトが対象になります)。スクリプトはビルドされないため、実行はメモリ内で検証されます。エラー数は増加してはならず、スクリプトホストのグローバル(Args)は検証の前後どちらでもエラーとして数えられます。
#if LOCAL_BUILD 内のコード
プリプロセッサシンボルが定義されていないコードは、構文木にまったく含まれません。つまり、ツールはそのコードを解析せず、別の箇所への修正がそのコードに及ぼす影響も確認できません。また、検証ビルドでもそのコードはコンパイルされません。たとえば、#if LOCAL_BUILD 内にローカル開発用のパスを保持しているリポジトリでは、実行時にシンボルを指定します。
csharp-refactor Your.sln --define LOCAL_BUILD # or --define:LOCAL_BUILD, -d:LOCAL_BUILD
csharp-refactor Your.sln --define "LOCAL_BUILD;CI" # several at once
または、.editorconfig で実行ごとに一度指定します(以下の csharp_refactor.defines = LOCAL_BUILD)。実行時には両方が使用され、最初に有効なシンボルと、それぞれの設定元が表示されます。シンボルは各プロジェクトが定義するものに追加されます。これは、環境変数で DefineConstants を設定したときに dotnet build が認識する動作と同じです。SDK の DEBUG/TRACE、ターゲットフレームワークの NET8_0_OR_GREATER、プロジェクト独自の $(DefineConstants);FOO はすべてそのまま残ります。(-p:DefineConstants=LOCAL_BUILD では動作しません。グローバルプロパティによって、これらすべてが置き換えられてしまうためです。)実行が開始するすべての MSBuild(ワークスペースのデザイン時ビルド、復元、検証ビルド)に、この方法でシンボルが渡されます。また、読み込まれた C# プロジェクト、レガシープロジェクト、.csx スクリプトの解析オプションにも渡されるため、DefineConstants を直接設定しているプロジェクトも、これらのシンボルを使用して解析されます。#r を解決できないスクリプトで、別の #r が #if LOCAL_BUILD 内にある場合は、--define LOCAL_BUILD を渡すよう案内されます。
設定
ルールは、すべての Roslyn アナライザーと同じ方法で .editorconfig に設定します。最も近いセクションが優先されます。
[*.cs]
dotnet_diagnostic.CR0103.severity = none # off
dotnet_diagnostic.CR0006.severity = suggestion # a default-off rule, on
csharp_refactor.CR0006.then_at_least = 30 # a rule's knob
csharp_refactor.public_api = false # nothing links to this assembly
csharp_refactor.api_changes = true # --api-changes as a standing decision
csharp_refactor.suppressions = no-correctness # all | no-correctness | none
csharp_refactor.skip_microsoft_duplicates = true # the Microsoft analyzers run here: skip what they already report
csharp_refactor.ignore_paths = generated;external/imported
csharp_refactor.defines = LOCAL_BUILD # like --define on every run
csharp_refactor.defines は実行単位のキーです。実行ごとに一度、対象に最も近い .editorconfig から読み込まれ(探索は root = true で停止します)、--define に追加されます。置き換えられることはありません。
csharp-refactor --create-config を実行すると、各値をこのビルドのデフォルトに設定したブロックが生成されるため、行を編集するまでは動作に変化はありません。各ルールのオプションは、デフォルト値のまま重大度の行の後に続きます。デフォルトが単一の値ではないオプション(CR0023 の測定された下限や、max_line_length を既定値とするルールの wrap_column など)は、コメントアウトした状態で出力されます。
生成されたファイルは、エディターでもツールでも一切変更されません。対象となるのは、bin、obj、node_modules、.git 内のファイル、*.g.cs、*.designer.cs、*.generated.cs、*.g.i.cs という名前のファイル、ignore_paths に記載されたファイル、およびヘッダーに <auto-generated>(T4 テンプレート、resx デザイナー、SDK の AssemblyInfo など)または「This code was generated by」と記載されたファイルです。
個々の検出結果は、Roslyn 独自の方法(#pragma warning disable CR0090、[SuppressMessage]、重大度の none)で抑制できます。エディターはこれらを標準で認識します。suppressions ポリシーはツールの動作を制御します。no-correctness では、抑制された正確性に関する検出結果も報告されます(自動修正は行われません)。none では、抑制された検出結果もすべて報告されます。--honor-suppressions を指定すると、1 回の実行に限り、いずれのポリシーも上書きできます。実行サマリーには、抑制によって非表示になった検出結果の数が表示されます。
Microsoft のアナライザールール(CA2213、CA1031、CA2000 など)と重複するルールは、ファイルの有効な設定で該当する Microsoft ルールが有効になっている場合、そのルールと同じ形のコードについては報告しません。これにより、同じ内容が二重に報告されたり、結果が揺れ動いたりすることを防ぎます。Rules.md には、各ルールと重複するルールが記載されています。
警告をエラーとして扱う場合:ツールは CI ビルドと同じ設定(TreatWarningsAsErrors、WarningsAsErrors、.editorconfig の重大度)を使用します。修正によってアナライザーエラーが発生する場合、ツールはそのファイルを変更せず、アナライザー名とルールを示したうえで、変更が必要な設定を案内します。修正を適用するには <WarningsNotAsErrors> を使用してください(警告は引き続き表示されます)。または、すべてのユーザーに対してアナライザーを無効にするには <NoWarn> を使用してください。
改善への取り組み
ビルドとテスト
dotnet build
dotnet test
コミットする前に:
dotnet tool restore
dotnet fantomas src tests
dotnet dotnet-fsharplint lint src/CSharp.Refactor.Analyzers/CSharp.Refactor.Analyzers.fsproj
テストハーネスは、実行中のフレームワークに対して C# の文字列リテラルをコンパイルし、純粋なルールを実行して、すべての修正をテキストとして適用したうえで再コンパイルします。ホストテストでは、Roslyn を通じて実際の DiagnosticAnalyzer と CodeFixProvider を実行します。ツールテストでは、合成プロジェクトを MSBuildWorkspace で網羅的にテストします。tests/run-tests.ps1 ですべてを実行できます。benchmarks/PerfClaims には、パフォーマンスに関する各主張の根拠となる変更前後のペアが保存されています。
複数行の入力には、csharp ヘルパー(tests/CSharp.Refactor.Tests/TestSource.fs)を使ってインデントされたブロックを指定します。閉じる引用符のインデントは各行から取り除かれるため、ルールはクラスを列 0 で認識でき、Fantomas はブロックの内容を変えずに移動できます。
let source =
csharp
"""
class C
{
int F(int x) => x + 1;
}
"""
fires 3 "CR0046" source は、ルールの提案を返してその件数をアサートします。assertFired も同様の処理を、手動でフィルタリングしたリストに対して行います。不一致がある場合、各提案が反応したテキストが一覧表示されるため、単なる件数では分からない、見逃した箇所を失敗内容から特定できます。
定数でなければならないリテラル(属性の引数、[<Literal>]、printf の書式文字列)や、タブ、\r、末尾の空白を含むリテラルは、エスケープされた "...\n..." 文字列のままにします。
例ベースのテストスイートに加えて、tests/CSharp.Refactor.PropertyTests は、生成されたプログラムを対象とする FsCheck スイートです。各メンバーがそれぞれのルールに合う形を持つクラスと、3 つの整数に対する真偽値の項を含むメソッド本体を生成し、Roslyn ツリー上のインタープリターをオラクルとして使用します。プロパティは、ルールが保証する不変条件を検証します。エディター専用の代替案を含むすべての修正で、プログラムのコンパイルが維持されること、破損したテキストから復元したコンパイルに対してどのルールも例外をスローしないこと、修正を一つずつ適用すると不動点に達すること、そして真偽値や制御フローを書き換えるルール(CR0001、CR0005、CR0007、CR0008、CR0011)が各ステップでメソッドの真理値表を維持することを確かめます。ジェネレーターは 124 個のルールのうち 121 個を対象にします(形状を読み取る箇所では CR0017 は CR0160 に包含され、CR0156 と CR0158 は C# 15 を必要としますが、ハーネスでは解析できません)。また、カバレッジテストでは各形状を個別に、さらにすべてをまとめて試し、形状が本来対象とするルールに反応しなくなった場合は、そのルールを特定します。さらに 2 つのプロパティでは、実際のファイルのようにプログラムを整えます。メンバーを囲む #region、メンバー内の #if とコメント、CRLF、タブ、#pragma を追加し、すべての修正がコンパイルされ、既存の内容(ディレクティブやコメントが削除されないこと、CRLF ファイルに単独の LF が含まれないこと)が維持されるよう求めます。また、C# 7.3 から 13 までの各言語バージョンでコンパイルし、修正がファイル自体のバージョンより新しい構文を使わないことを求めます。どちらも、ランダムなプログラムだけでなく、すべての形状に対して決定論的に実行されます。コンパイルできても意味が変わる書き換えは、コンパイル不変条件では検出できません。そのため、可能な場合は形状に誤検出用の仕掛けを含めています。CR0100/CR0101 の形状では、FormattableString オーバーロードに [Obsolete(error)] が付いたメソッドを呼び出すため、その呼び出しが別のオーバーロードに束縛されるような補間はコンパイルエラーになります。CR0082 の形状では、タプルを object に渡すため、型の付け替えを行うとコンパイルできなくなります。こうした仕掛けでは表現できないもの(動作を持つ型からなるレコード、例外メッセージに出力されるレコード)は、例ベースのテストスイートで扱います。
設計原則
- ユーザーのコードを壊さない。 修正は、安全性が確実に証明できる場合にのみ提案します。判断が分かれるケースでは提案しません。コンパイルしなくても正しい修正でなければなりません。同じ修正が、何も検証されないエディターでも実行されるためです。
- 削除ではなくガードを使う。 安全でない書き換えには、構造が安全であることを示す型付きの証明を適用します。証明に失敗した場合は適用を見送ります。ルールを削除したり、無条件に無効化したりすることはありません。
- 最小限の編集。 修正には範囲ベースのテキスト編集を使います。編集範囲外のフォーマットには手を加えません。
- 純粋なコアと薄いアダプター。 各ルールは、構文木とセマンティックモデルから提案を生成する純粋関数です。Roslyn アナライザー、修正プロバイダー、ツールは、その関数を呼び出す1行のアダプターです。
- ヒントはイディオマティックな C# への誘導に限る。 元に戻せる好みの問題は、診断ではなく IDE のリファクタリングに任せます。
安全性モデル全体、yields-to ゲート、予定されているカタログ、Microsoft のアナライザーとの関係については、DESIGN.md を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。