DartのASTを参照する

背景

Dartのプロジェクトにおいてファイル間の依存関係を明らかにしたいニーズが出てきたため、DartのASTを参照する方法を調べようと思った。ASTを利用すればimport文を解析することで依存関係を明らかにできるだろうと考えた。

analyzerパッケージ

DartのASTを参照するには、analyzerパッケージを使う。このパッケージはIDEなどで使われるAnalysis Serverを提供している他、linterの実装でも利用されている。

使い方としては、parseFile関数でファイルに含まれるDartのソースコードをASTに変換する。

final result = parseFile(
  path: 'path/to/code.dart',
  featureSet: FeatureSet.latestLanguageVersion(),
);
// result.unitからASTを参照できる

ASTを表すクラス群

result.unitDirectiveのリストとDeclarationのリストから成る。DirectiveDeclarationは以下のような継承ツリーの一部に含まれていて、特にDirectiveのサブクラスとしてはImportDirectiveExportDirectiveが存在する。

Node

AstVisitor

ASTのノードの親クラスにあたるAstNodeにはvisitChildrenというメソッドが用意されている。AstVisitor型のvisitorを引数に渡すと、ASTのnodeの種類に対応したvisitorのメソッドが呼び出されるようになっている。AstVisitorは抽象型で、これを実装したクラスがいくつか用意されている。

  • SimpleAstVisitor: メソッドはすべて実装されているが、何もしない。これが他のvisitorのベースになっているようだ。
  • RecursiveAstVisitor: nodeがchildrenを持っているときに再帰的にASTのツリーをたどる。これも実際には何もしないので、これを継承することで、再帰的にnodeをたどってnodeの種類に応じた処理を実装できる。

これらを利用してimportしたファイルを取得するだけのvisitorを実装してみる。

class ImportVisitor<R> extends SimpleAstVisitor<R> {
  final List<String> importedUris = [];

  @override
  R? visitImportDirective(ImportDirective node) {
    importedUris.add(node.uri.stringValue!);
    return super.visitImportDirective(node);
  }
}

void main() {
  final result = parseFile(
    path: 'path/to/code.dart',
    featureSet: FeatureSet.latestLanguageVersion(),
  );

  final visitor = ImportVisitor<void>();
  result.unit.visitChildren(visitor);

  print(visitor.importedUris);
}

AstVisitorvisitImportDirectiveImportDirective型のnodeを見つけたときに呼ばれるメソッドで、今回はこれをリストに追加して、ファイル中のすべてのimportされているファイルを収集している。

このようにAstVisitorを利用することで、特定の種類のnodeのみに対して行う処理を簡潔に実装できるため、アイデア次第でいろんな応用ができるだろう。