妄想プログラマのらくがき帳 : 11月 2014

2014年11月24日月曜日

[Roslyn]SyntaxRewriterでソースコードを変更してみる。

今回は SyntaxRewriter を用いた方法でソースコードを変更してみます。

SyntaxRewriter を使ってアクセス修飾子を付加する

C#はメンバ変数のアクセス修飾子を指定しないと既定で private になります。そのため private 変数の場合、private を付けても付けなくても良いのですが、コード内で private 有りと private 無しが混在してると統一感に欠けるので統一したいところです。が、既存コードがすでに有り無し混在状態だと手作業で修正するのは大変ですよね。そこで SyntaxRewriter の出番です。

と前置きはこれくらいにしておいて、サンプルコードは以下の通りになります。

using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RoslynSample
{
class ModifierCorrector : CSharpSyntaxRewriter
{
public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
{
var correctedNode = node;
if (!HasAccessModifiers(node))
{
// ノードにアクセス修飾子が付いていない場合、privateを付加する
correctedNode = node.WithModifiers(
node.Modifiers.Add(
SyntaxFactory.Token(SyntaxKind.PrivateKeyword).
WithTrailingTrivia(SyntaxFactory.Whitespace(" "))));
}
// base.VisitFieldDeclaration()には変更後のノードを渡す
return base.VisitFieldDeclaration(correctedNode);
}
private bool HasAccessModifiers(FieldDeclarationSyntax node)
{
var accessModifiersTextList = new List<string>()
{
SyntaxFactory.Token(SyntaxKind.PublicKeyword).Text,
SyntaxFactory.Token(SyntaxKind.ProtectedKeyword).Text,
SyntaxFactory.Token(SyntaxKind.PrivateKeyword).Text
};
var modifiersText = node.Modifiers.Select(x => x.Text);
foreach (var accessModifier in accessModifiersTextList)
{
if (modifiersText.Contains(accessModifier))
{
// 修飾子の中にアクセス修飾子が含まれている場合、trueを返す
return true;
}
}
return false;
}
}
}

まず CSharpSyntaxRewriter を継承したクラスを作成し、 Visit***() メソッドをオーバーライドします。今回はフィールド宣言が変更対象なので、VisitFieldDeclaration() をオーバーライドします。
VisitFieldDeclaration() は SyntaxTree 内の各 FieldDeclarationSyntax ごとに呼び出されるメソッドで、メソッド内で引数で渡された node を変更し、変更後 node を base.VisitFieldDeclaration() に渡してやることで、SyntaxTree 内にある node を変更後のもので置き換えることができます。変更する必要が無い場合は node をそのまま base.VisitFieldDeclaration() に渡します。

このサンプルコードでは、 アクセス修飾子が付いていない FieldDeclarationSyntax に対し、private を付加して base.VisitFieldDeclaration() に渡しています。


次に作成したクラスを使用し、実際にソースコードを変更する方のサンプルコードです。
パース対象のコード内にある RoslynSample. field1 に private を付加しています。

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
namespace RoslynSample
{
class Program
{
static void Main(string[] args)
{
// パース対象のコード
string sourceCode =
@"using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RoslynSample
{
class Program
{
int field1;
private int field2;
protected int field3;
public int field4;
/// <summary>
/// エントリポイントです。
/// </summary>
/// <param name=""args"">コマンドライン引数</param>
static void Main(string[] args)
{
HelloRoslyn();
}
public static HelloRoslyn()
{
System.Console.WriteLine(""Hello Roslyn!"");
}
private static HelloWorld()
{
System.Console.WriteLine(""Hello World!"");
}
public static GoodbyWorld()
{
System.Console.WriteLine(""Goodby World!"");
}
}
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode);
var corrector = new ModifierCorrector();
// VisitorパターンでSyntaxTreeの各ノードを巡り、
// フィールド変数かつアクセス修飾子を持たないフィールドにprivateを付加
var correctedTree = corrector.Visit(tree.GetRoot());
// コードをフォーマット
var workspace = MSBuildWorkspace.Create();
var formattedTree = Formatter.Format(correctedTree, workspace);
System.Console.WriteLine(formattedTree.ToFullString());
}
}
}
こちらで行うことは、先ほど作成したクラスの Visit() に SyntaxTree のルートノードを渡すだけです。Visit() の戻り値として変更後の SyntaxTree を取得できます。

変更後の SyntaxTree が表すソースコードを出力すると以下のようになります。


変更前はアクセス修飾子が無かった field1 に private が付いていますね!

と、まあこんな感じで、ソースコードをコーディング規約に従うように自動修正したりするような用途に SyntaxRewriter は便利です。