DartのASTを触ってみる


頓挫したくないでござる。

# 3行でおｋ

* dartの依存解決は`import`と`part`+`part of`でできてる
* `part "path/to/something.dart";` めんどい
* DartのAST系のツールのanalyzer使って自動生成できるようにした

# Dart歴的なの

ここ2週間ほど、Dartをさわさわして楽しんでます。つまりDart歴2週間です。
JSが駆逐されてDartがユーザーのブラウザ上で動く世界が来るかもしれないので(5年後くらいにもしかしたら・・・)、今からさわさわしててもいいんじゃね？的なノリでさわり始めたのですが、普段PHPとかJSとかメインでかいてるゆるふわ系言語エンジニアとしては、中々新鮮で、楽しめてます。

## Dartについて

Dartについての紹介的なのは、きっと[Dart Advent Calendar 2014](http://qiita.com/advent-calendar/2014/dart)に何かしら書かれてたり、書かれたりすると思うのでそちらをご覧ください。
[2013年のアドベントカレンダー](http://www.adventar.org/calendars/85)と比較しても、随分盛り上がってますね！Dart！2015年にはDartが流行るかもですよ！

# `part`なにこれ面倒くさい

ここから若干前振りが長くなりますが、Dartの依存関係の解決方法について説明します。

## Dartの依存関係解決

Dartは非常にモジュール性が高い言語ですが、大きく2つの依存関係の解決方法が用意されています。

* import
* part

簡単にいうと、``import``は外部のライブラリ(package)の読み込みに使い、``part``は自分自身(package)の内部のファイルの解決に使われます。

詳しくは[このへん](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#libraries-and-visibility)とか見てもらえればいいんですが、簡単におさらいしておきます。

## something_libで理解するimport/part

```text:something_libのファイル構造
/packages (依存してる外部パッケージ群)
	/lib1 (外部パッケージその1: lib1)
		/src
			foo.dart
			bar.dart
		lib1.dart
	/lib2 (外部パッケージその2: lib2[以下省略])
/lib (something_lib
    /src
    	something_foo.dart
    	something_bar.dart
    something.dart
```

何がしかライブラリ「something_lib」を作る場合、上記のようなディレクトリ構成になります。
ディレクトリ講師ついては[laco0416](http://qiita.com/laco0416)さんの[こちらの記事](http://qiita.com/laco0416/items/bc5288bb47d47e64a7e9)が大変勉強になります。

上記のようなパッケージの場合、編集するのは`something_foo.dart` `something_bar.dart` `something.dart` の3つになります。
`something.dart` がメインとなるファイルで、`something_foo.dart`と`something_bar.dart`は、その構成要素としてのファイル、パーツとしてのファイルというニュアンスになります。

### import

外部のパッケージは全て`/packages`以下に格納されるので、`lib1`や`lib2`といった外部ライブラリを利用したい場合は、`something.dart`から`lib1`や`lib2`を **`import`** してあげます。

```dart:something.dart
library something; //パッケージ名宣言
import "packages:lib1/lib1.dart"; //lib1をインポート
void main() { //メインの処理
	//importしたライブラリが使えるようになる
	Lib1 lib1 = new Lib1();
}
```

このように、`packages:パッケージ名/公開ライブラリファイル` の形式でimportすると、外部ライブラリを読み込む事ができます。

初めてこれを見たときに気になったのが **「lib1読み込むっていってんだから`/lib1.dart`まで書くのが面倒くさいなりぃ」** だったんですが、これは、ひとつのパッケージの中から、必要なライブラリだけを読み込む事ができるようになっていると考えると、利点になります。
ひとつの``package``の中に複数の``library``があるわけですね。

例えば、GithubクライアントのDartパッケージである[github.dart](https://github.com/DirectMyFile/github.dart)を見ると、以下の画像のように、ひとつのパッケージの中に、「browser.dart（ブラウザ用公開ライブラリ）」と「server.dart（サーバーサイド用公開ライブラリ）」があり、利用用途に合わせて必要なファイルだけを``import``する事ができるようになってます。

[![github.dart_lib_at_master_·_DirectMyFile_github.dart-7.png](https://qiita-image-store.s3.amazonaws.com/0/3865/7d0ef2e3-1fe2-faf8-c0c3-bfec908740e6.png "github.dart_lib_at_master_·_DirectMyFile_github.dart-7.png")](https://github.com/DirectMyFile/github.dart/tree/master/lib "github.dart/lib")

ブラウザ用・サーバーサイド用はちょっと極端だとしても、巨大なパッケージの一部のライブラリだけを使う、みたいな事が比較的簡単にできるようになっている感じです。たぶん。

### part

外部の場合は`import`でしたが、自分自身のパッケージ内のファイルの依存関係の解決は **`part`** と **`part of`** と呼ばれる2つのキーワードで行います。それぞれ日本語に直すと

* **`part`**
	* 私のパーツはこの子です
* **`part of`**
	* 私はこの親のパーツです

こんな感じの事を宣言するシンタックスになってます。
コードを見たほうが分かりやすいと思います。

```dart:something.dart(親)
library something;

part "src/something_foo.dart";

void main () {
	Foo foo = new Foo();
}

```

と

```dart:src/something_foo.dart(子)
part of something;

class Foo() {
	// something
}
```

このように、それぞれが``something``ライブラリに属しながらも、親の方が「こいつが俺の子供だぜ！」って言う感じです。逆にいうと、いくら子の方が「part of!! part of!!」と叫んでも、親のほうで`part`してないとガン無視です。可哀想な子。

また、子のほうが複数の親に属する事もできないので注意です。
`[error] Only one part-of directive may be declared in a file` といった感じで怒られます。

### import/partまとめ

* **`import`** は外部packageを読み込む
* **`part`** と **`part of`** で内部の依存関係を表す

## つまり`part`は面倒臭い

`import`と`part`が理解できたところで、`part`の面倒臭さに気づいた人とはお友達になれそうです。
慣れると対して面倒臭くもなくなってきたんですが、最初はガッツリ面倒くさいオーラを出しながらタイピングしてました。
つまり、子ファイルが増えれば増えるほど、`part`祭りになってくるわけです。

実際のコードを見たほうが理解してもらえそうな気がするので、また先ほどと同じく[github.dart](https://github.com/DirectMyFile/github.dart)さんに登場してもらいましょう。`common.dart`という子ファイルを読み込んでるDartファイルがあるのですが、これを見ると、いかに面倒くさいかが伝わる気がします。

[![github.dart_common.dart_at_master_·_DirectMyFile_github.dart.png](https://qiita-image-store.s3.amazonaws.com/0/3865/1d92de21-dacb-2646-2810-b487a7370f73.png "github.dart_common.dart_at_master_·_DirectMyFile_github.dart.png")](https://github.com/DirectMyFile/github.dart/blob/master/lib/common.dart)

最終的に73行目まで`part 'hoge/hoge.dart';`が続いてます。

ライブラリの規模として適切なのか？といった、設計の適切さみたいな話はひとまず置いておくにして、クラス毎にファイルを切り出すと、こんな感じになってしまいます。

というわけで **「こんなに`part`書くのは面倒くさいなりぃ」** となってしまうわけです。

_※ちなみにDartのパッケージをいくつか見てると、一つのファイルに複数のクラス置くのは別にお作法違反じゃない雰囲気もあります。_

# ならば自動生成だ

前振りが大分長くなってしまいましたが、書くのが面倒くさいなら自動生成すればいいじゃない！ということで、パッケージを書きました。

**part_generator**
[https://pub.dartlang.org/packages/part_generator](https://pub.dartlang.org/packages/part_generator)

Githubは[こちら](https://github.com/takyam-git/part_generator)

使い方はREADMEにも書いてますが、Dartのパッケージ依存管理ツールであるところの [pub]() を使ってインストール＆コマンド実行します。

* install
	* `pub global activate part_generator`
* run
	* `pub global run part_generator -r ライブラリのディレクトリルート -l ライブラリ名`

## 何をやってくれるツールなのか

とりあえず作った程度なので大した機能は無いですが、`-r` で指定したディレクトリ以下の `*.dart` ファイルを探し、それらが `-l` で指定したライブラリ名の `part of` を持ってたら、`part "そのファイルのパス.dart";` をコンソールに出力するだけの簡単なお仕事です。

ためしに `part_generator` 自身に実行してみると、こんな感じ。

![1._takyam_mba-2____IdeaProjects_part_generator__zsh_.png](https://qiita-image-store.s3.amazonaws.com/0/3865/4cde8572-b2d6-d22e-acb6-2ec738d5e9ff.png "1._takyam_mba-2____IdeaProjects_part_generator__zsh_.png")

本当にコンソールに出力するだけなんですね。ええ。

# DartのAST(Abstract Syntax Tree:抽象構文木)

で！ようやくタイトルのASTが出てきました。
前振りが長くてすみませぬ。

`*.dart`ファイルの一覧取得はDart開発チームご謹製の [glob](https://pub.dartlang.org/packages/glob) パッケージを利用してサクッと取得できるのですが、`part of ほげほげ`なファイルを取得するにはどうしたらよいか、といったあたりの話でようやくASTが出てきます。

ASTはご存知のとおり、テキストファイルでいらしゃるところのソースコードを、何かいい感じのアレにしてくれた状態のやつです。詳しくないからggrks。

で、ASTをDartのコードからいい感じに扱えるようにする [analyzer](https://pub.dartlang.org/packages/analyzer) パッケージがこれまたDart開発チームご謹製でありますので、これを使って`glob`で取得した`*.dart`ファイルから、`part of ほげほげ`なものをピックアップしたいと思います。

## analyzerでdirectiveを取得する


実際のコードはGithubを参照してください
[lib/src/part_of_file.dart](https://github.com/takyam-git/part_generator/blob/97f23ac7c2cefb63b78dc181669d80b194e5ea92/lib/src/part_generator/part_of_files.dart#L60-L74)

簡略化したものが以下になります。

```dart:part_of_file.dart
import "package:analyzer/analyzer.dart" as analyzer;
//注: dartFIle は取得した*.dartのFileSystemEntity

//まずanalyzerでDartファイルをパースしてASTに変換
analyzer.CompilationUnit parsed = analyzer.parseDartFile(dartFile.path);

// directive を取得
analyzer.NodeList<analyzer.Directive> directives = parsed.directives;

//ほげほげライブラリのpart of のdirectiveを含むかどうかを確認する
bool isPartOf = directives.any((analyzer.Directive directive) {
  //まずは part of かどうかを確認
  if (!(directive is analyzer.PartOfDirective)) {
    return false;
  }
  //part ofで指定されてるライブラリ名が指定のものかどうか
  if ((directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ") {
    return false;
  }
  return true;
});
```

上から順に見ていきます。

```dart:parseDartFile()
analyzer.CompilationUnit parsed = analyzer.parseDartFile(dartFile.path);
```

まず、``parseDartFile()``メソッドに`*.dart`のファイルパスを渡すと、Dartファイルをパースして、全てオブジェクトにしてくれます。やったね！

このとき、``CompilationUnit``が返ってきます。直訳すると``コンパイル単位``。
この``CompilationUnit``は``directives``と``declarations``の2つの``NodeList``を持っていて、それぞれ、

* **directives**
	* `analyzer`の`Directive`のインスタンスのリストになってる
	* 先頭に書く系のシンタックス `library`/`import`/`part`/`part of` 等が該当
* **declarations**
	* `analyzer`の`CompilationUnit`のインスタンスのリストになってる
	* ``directives``以外のクラスの宣言等が該当

みたいな感じになってます。
また、`PartDirective`/`PartOfDirective`/`LibraryDirective`/`ImportDirective`などそれぞれが別のクラスになっている感じです。

## PartOfDirectiveを確認する

`part of`が含まれるファイルかどうかを見たい場合は、`parseDartFile()`でパースした`CompilationUnit`の`directives`に`PartOfDirective`が含まれているかどうかを確認すれば良いわけです。


```dart:partOfが含まれてるか確認
//anyはListにFuture（クロージャ）の返り値がtrueのものが含まれていればtrueを返すメソッド
bool isPartOf = directives.any((analyzer.Directive directive) {
  //まずは part of かどうかを確認
  if (!(directive is analyzer.PartOfDirective)) {
    return false;
  }
  //part ofで指定されてるライブラリ名が指定のものかどうか
  if ((directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ") {
    return false;
  }
  return true;
});
```

``directive is analyzer.PartOfDirective`` が該当箇所になります。
_(※`is`は`instance of`的なやつです)_

で、``PartOfDirective``は``libraryName``を持ってますので、あとは名前を比較してあげればいいわけです。

``(directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ"`` が該当箇所になります。
ちなみにこの``.libraryName``も``LibraryIdentifier``というクラスのインスタンスです。

以上の事をするだけで、ASTを利用して`part of`が書かれたファイルを取得する事ができました。すばらしい。

あとは、これで対象のファイル群を適当なリストに突っ込んで、パスを出力してあげるだけのツールが`part_generator`になってます。

## AST周りまとめ

* analyzer パッケージを使うと簡単にASTを扱える
* コンパイル単位から、directiveとdeclarationに別れる
* ひとつひとつがクラスになっているので、OO的に扱える

# おわり
* 頓挫せずにすんでよかった
* Dart Teamご謹製パッケージがたくさんあって助かる
* `pub publish` 叩くだけで何も考えずに(Githubすら不要)パッケージ公開できたすごい。
* Dart日本ユーザー会とか無いんですかね