LoginSignup
3

More than 3 years have passed since last update.

posted at

updated at

Roslynが作るコードツリーを覗く

前書き

.net coreでコンパイル時の自動コード生成をする上で、Roslyn APIに触る必要がある。
その手始めとして、Roslynが吐くASTを除いて見たメモ。

環境

今回の実験環境

% dotnet --info
.NET Core SDK (global.json を反映):
 Version:   3.1.201
 Commit:    b1768b4ae7

ランタイム環境:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

Host (useful for support):
  Version: 3.1.3
  Commit:  4a9f85e9f8

.NET Core SDKs installed:
  3.1.201 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

プロジェクト準備

コンソールアプリとして作成

% dotnet new console -o CodeGenDemo

Roslyn APIのパッケージを加える

% dotnet add CodeGenDemo package Microsoft.CodeAnalysis.CSharp
% dotnet list CodeGenDemo package
プロジェクト 'CodeGenDemo' に次のパッケージ参照が含まれています
   [netcoreapp3.1]: 
   最上位レベル パッケージ                         要求済み    解決済み 
   > Microsoft.CodeAnalysis.CSharp      3.5.0   3.5.0

作成

例として、以下のインターフェースのASTを覗く

using System;

namespace DemoGenDao {
    public interface IColorDao {
        ColorData? FindById(int id);
    }
}

ASTの作成は、Microsoft.CodeAnalysis.CSharp名前空間のCSharpSyntaxTreeクラスのParseTextメソッドを使用する。

see: CSharpSyntaxTree.ParseText Method

ASTのルートツリーは、CSharpSyntaxTreeGetCompilationUnitRootメソッドで取得可能。

see: CSharpSyntaxTree.GetCompilationUnitRoot(CancellationToken) Method

using System;
using Microsoft.CodeAnalysis.CSharp;

// (snip)

static void Main(string[] args) {
    var programText = @"
        using System;

        namespace DemoGenDao {
            public interface IColorDao {
                ColorData? FindById(int id);
            }
        }
        ";

    var tree = CSharpSyntaxTree.ParseText(programText);
    var root = tree.GetCompilationUnitRoot();
}

usingで取り込んだ名前空間の情報は、Usingsプロパティから辿る。
その他クラスユニット等は、Membersプロパティから辿る。

static void Main(string[] args) {
    // 続き

    System.Console.WriteLine($"The tree has {root.Usings.Count} using statements. They are:");

    foreach (var e in root.Usings) {
        System.Console.WriteLine($"\t{e.Name}");
    }

    System.Console.WriteLine($"The tree is {root.Kind()} node.");
    System.Console.WriteLine($"The tree has {root.Members.Count} elements in it.");
}

ASTのノードは、Microsoft.CodeAnalysis.CSharp.Syntax名前空間で定義されている。
適宜キャストして扱う。

see: Microsoft.CodeAnalysis.CSharp.Syntax

今回お世話になったのは、

using Microsoft.CodeAnalysis.CSharp.Syntax; // 追加

// (snip)

static void Main(string[] args) {
    // 続き
    foreach (var m in root.Members) {
        System.Console.WriteLine($"\tThe member in root is {m.Kind()}.");

        var ns = (NamespaceDeclarationSyntax)m; // Microsoft.CodeAnalysis.CSharp.Syntax

        System.Console.WriteLine($"\tThere are {ns.Members.Count} members declared in this namespace.");

        foreach (var t in ns.Members) {
            System.Console.WriteLine($"\t\tThe member in this namespace is {t.Kind()}.");

            if (t is InterfaceDeclarationSyntax) {
                var intf = (InterfaceDeclarationSyntax)t;
                System.Console.WriteLine($"\t\tThis type is {intf.Identifier} interface.");
                System.Console.WriteLine($"\t\tThis type has {intf.Members.Count} members.");

                foreach (var mm in intf.Members) {
                    System.Console.WriteLine($"\t\t\tThe member in this type is {mm.Kind()}.");

                    if (mm is MethodDeclarationSyntax) {
                        var method = (MethodDeclarationSyntax)mm;
                        System.Console.WriteLine($"\t\t\tThis method name is {method.Identifier}.");
                        System.Console.WriteLine($"\t\t\tThis method has {method.Modifiers.Count} modifiers.");
                        System.Console.WriteLine($"\t\t\tThis method returns {method.ReturnType}.");
                        System.Console.WriteLine($"\t\t\t\tThe return type nullable in method is {method.ReturnType.IsNotNull}.");
                        System.Console.WriteLine($"\t\t\tThis method receives {method.ParameterList.Parameters.Count} args.");

                        foreach (ParameterSyntax arg in method.ParameterList.Parameters) {
                            System.Console.WriteLine($"\t\t\t\tThe arg in this method is {arg.Kind()}.");
                            System.Console.WriteLine($"\t\t\t\tThe arg name is {arg.Identifier}.");
                            System.Console.WriteLine($"\t\t\t\tThe arg type is {arg.Type}.");
                            System.Console.WriteLine($"\t\t\t\t\tThe arg nullable is {arg.Type.IsNotNull}.");                                
                        }
                    }
                    else {
                        System.Console.WriteLine($"\t\t???? This member syntax is {t.GetType().FullName}.");
                    }
                }
            }
            else {
                System.Console.WriteLine($"\t\t???? This type syntax is {t.GetType().FullName}.");
            }
        }
    }
}

実行結果

実行結果は以下の通り

% dotnet run -p CodeGenDemo
The tree is CompilationUnit node.
The tree has 1 using statements. They are:
        System
The tree has 1 elements in it.
        The member in root is NamespaceDeclaration.
        There are 1 members declared in this namespace.
                The member in this namespace is InterfaceDeclaration.
                This type is IColorDao interface.
                This type has 1 members.
                        The member in this type is MethodDeclaration.
                        This method name is FindById.
                        This method has 0 modifiers.
                        This method returns ColorData?.
                                The return type nullable in method is False.
                        This method receives 1 args.
                                The arg in this method is Parameter.
                                The arg name is id.
                                The arg type is int.
                                        The arg nullable is False.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3