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を覗く

```csharp
using System;
                
namespace DemoGenDao {
    public interface IColorDao {
        ColorData? FindById(int id);
    }
}
```

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

see: [CSharpSyntaxTree.ParseText Method](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.csharpsyntaxtree.parsetext?view=roslyn-dotnet)

`AST`のルートツリーは、**CSharpSyntaxTree**の**GetCompilationUnitRoot**メソッドで取得可能。

see: [CSharpSyntaxTree.GetCompilationUnitRoot(CancellationToken) Method](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.csharpsyntaxtree.getcompilationunitroot?view=roslyn-dotnet)

```csharp
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**プロパティから辿る。

```csharp
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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax?view=roslyn-dotnet)


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

* [NamespaceDeclarationSyntax](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.namespacedeclarationsyntax?view=roslyn-dotnet)
* [InterfaceDeclarationSyntax](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.interfacedeclarationsyntax?view=roslyn-dotnet)
* [MethodDeclarationSyntax](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.methoddeclarationsyntax?view=roslyn-dotnet)
* [ParameterSyntax](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.parametersyntax?view=roslyn-dotnet)
* [TypeSyntax](https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.typesyntax?view=roslyn-dotnet)

```csharp
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.
```