👱‍♀️

TypeScript で JavaScript をパースするときの型

2021/07/05に公開

この記事では TypeScript から既存のパーサーライブラリを使って JavaScript をパースするときにどのように型をつけるか、自分がやっている方法を紹介する。TypeScript を使って1から JavaScript のパーサーを書く話ではないので注意。

仕事で、TypeScript で JavaScript をパースして構文木をゴニョゴニョやるツールを書いていた。

こういうとき、まずどのパーサーを使うかを考える。

パースする対象が絶対に JavaScript であることがわかっている場合、自分は Acorn を使うことが多い(TypeScript や Flow をパースする場合@babel/parserを使うこともあるし、TypeScript Compiler API の AST にもアクセスしたい場合は@typescript-eslint/typescript-estreeを使うことがある)。

Acorn はきちんとメンテナンスされており、@babel/parserに比べてパーサーとして余分な機能がなくサイズが(比較的)小さいなどの特徴がある。なので、特にブラウザ上で動作させるツールの場合は Acorn を使うことが多い。

以前 Zenn のスクラップに ESTree っぽい AST を吐くパーサーをまとめたので、そちらも参照してほしい。

https://zenn.dev/sosukesuzuki/scraps/fa4d48f9098d66

しかし、Acorn の型定義はあまり質が高くない。

ソースコードを実際に見てみるとわかるが、各ノードの型が用意されていない。これでは取得した AST を TypeScript から操作するときに不便である。

https://github.com/acornjs/acorn/blob/master/acorn/dist/acorn.d.ts

なので、自分は型定義だけ @types/estree を使うことが多い。

@types/estree のソースコードは以下にある。

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/estree

acorn のパース結果を as でキャストして ESTree の型を使っている。

import { parse } from "acorn";
import type { Node } from "estree";

const ast = (parser(code, { ecmaVersion: 2020 })) as Node;

もしかしたら実際の AST と型の間で差があるかもしれないが、Acorn は ESTree に準拠するとしており、@types/estree は当然 ESTree に準拠するはずなので、バグってる場合直すことができるはずだ。

もしほかに良い方法がある場合は教えてほしい。

Discussion