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

2014年8月14日木曜日

[Roslyn]SyntaxTreeを探索してみる

CSharpSyntaxTree.ParseText()の戻り値であるSyntaxTreeにはソースコードの様々な情報が格納されています。どんな情報が格納されているのか実際にサンプルソースをパースして確かめてみましょう。

SyntaxTree

今回パースしてみるのは以下のサンプルソースです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoslynSample
{
    /// 
    /// 氏名を表します。
    /// 
    class Name
    {
        private string m_firstName;
        private string m_lastName;

        /// 名を取得します。
        public string FirstName
        { 
            get { return m_firstName; }
        }

        /// 氏を取得します。
        public string LastName
        {
            get { return m_lastName; }
        }

        /// 氏名を取得します。
        public string FullName
        {
            get { return m_firstName + m_lastName; }
        }

        /// 
        /// 氏名を指定してNameクラスの新しいインスタンスを初期化します。
        /// 
        /// 名        /// 氏        public Name(string firstName, string lastName)         {             m_firstName = firstName;             m_lastName = lastName;         }          ///          /// 現在のオブジェクトを表す文字列を返します。         ///          /// 現在のオブジェクトを表す文字列。         public override string ToString()         {             return m_firstName + " " + m_lastName;         }     } } 
なんてことないValueObjectです。これをCSharpSyntaxTree.ParseText()でパースしてSyntaxTreeを取得し、中身を調べてみます。
SyntaxTreeは木構造なのでツリービューで表示してみました。 各ノードにはノードのクラス名とソースコード上の位置を表示しています。 見てのとおり、SyntaxTreeはCompilationUnitSyntaxをルートに様々な種類のクラスによって構成されています。

SyntaxTreeを構成するSyntaxNode

SyntaxTreeを構成するクラスは全てSyntaxNodeの派生クラスとなっています。SyntaxNodeは、ソースコード内におけるノードの位置やノードの文法上の種類を保持しており、またSyntaxTree内を渡り歩くためのメソッドも提供します。
SntaxNodeの主なプロパティとメソッドは以下の通りです。

表1. SyntaxNodeの主なプロパティ
プロパティ名説明
TextSpanFullSpanパースしたテキスト内におけるSyntaxNodeの範囲。FullSpan.Startがテキスト内でのSyntaxNodeの開始位置(0スタートの文字index)、FullSpan.LengthがSyntaxNodeの範囲長(文字数)を表す。
boolHasLeadingTriviaSyntaxNodeの前方にTrivia(コメント、改行文字、空白等)が存在するならtrue、それ以外はfalse。
boolHasLeadingTriviaSyntaxNodeの後方にTrivia(コメント、改行文字、空白等)が存在するならtrue、それ以外はfalse。
SyntaxKindKindSyntaxNodeの文法上の種類。
SyntaxNodeParent親のSyntaxNode
SyntaxTreeSyntaxTreeSyntaxNodeが含まれるツリーへの参照。


表2. SyntaxNodeの主なメソッド
メソッド名説明
Ancestors()先祖のSyntaxNodeのリストを取得。
ChildNodes()のSyntaxNodeのリストを取得。
DescendantNodes()子孫のSyntaxNodeのリストを取得。
GetLocation()SyntaxNodeの位置を取得。GetLocation().GetLineSpan()でSyntaxNodeが含まれるファイルのパスやSyntaxNodeの開始行が取得できる。
GetLeadingTrivia()SyntaxNodeの前方のTriviaを取得。
GetTrailingTrivia()SyntaxNodeの後方のTriviaを取得。
ToFullString()SyntaxNodeを表す文字列を取得(前後のTriviaを含む)。
ToString()SyntaxNodeを表す文字列を取得(前後のTriviaを含まない)。

メソッドには「Ancestors()」や「DescendantNodes()」といった LINQ to XML でお馴染みのメソッドがあります。ソースコード内のメソッド定義にアクセスする場合、これらのメソッドを用いて次のようにアクセスすることができます。
    var tree = CSharpSyntaxTree.ParseText(sourceCode);
    // メソッド定義を全て取得
   var methodDeclarations = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
同様に、クラスの定義には。OfType<Classdeclarationsyntax>()、プロパティの定義にはOfType<Propertydeclarationsyntax>()でアクセスできます。

SyntaxNodeの派生クラス

SyntaxNodeクラスの派生クラスはそれぞれがクラス宣言、メソッド宣言、foreach文、ブロック、式といった文法的な要素を表します。前項で出てきた「MethodDeclarationSyntax」はメソッド宣言、「ClassDeclarationSyntax」はクラス宣言、「Propertydeclarationsyntax」プロパティ宣言を表しています。これらのクラスは(C#の場合)Microsoft.CodeAnalysis.CSharp.Syntax名前空間に***Syntaxクラスとして定義されており、各クラスのプロパティを通して文法的な値(プロパティの型や名前等)を取得できます。また各クラスのメソッドを通して文法的な値を変更することも可能です。

次回はこのSyntaxNodeの派生クラスをいくつか取り上げ、実際に文法的な値を変更してみます。