おそらく誰も使わない @babel/parser 7.14 新機能 TypeScript プラグイン の dts オプションについて

2021/05/03に公開

先日 Babel 7.14.0 がリリースされました。それに伴って Babel のパーサーである @babel/parser も 7.14.0 がリリースされました。

この記事では、@babel/parser 7.14.0 の TypeScript プラグインに実装された新機能 dts オプションについて解説します。

自分で実装しておいてアレですが、このオプションを実用するひとはおそらくほとんどいないんじゃないかと思います。もしこのオプションが実用上役に立ったひとがいれば興味があるので連絡をください。

また、この記事の前半部分は @babel/parser について知らないひと向けの解説になっています。@babel/parserやプラグイン機能についてはすでに知っていて dts オプションについてのみ知りたいという場合は typescript プラグインの dts オプション のところまで飛ばしてください。

Babel のパーサーを直接使う

多くのウェブ開発者がなんらかの形で Babel を使ったことがあるでしょう。

おそらくそのほとんどがトランスパイラとしての利用だと思います。つまり、新しいプロポーザルを使いたい場合であったり、TypeScript や Flow のような AltJS を使いたい場合であったり、本番環境で古い JavaScript を実行させたい場合に、Babel を使ってウェブブラウザ(や Node.js)が実行できる形に変換するというユースケースです。

それが Babel の主目的です。

しかし、一部のソフトウェアでは Babel のパーサーのみを使っているケースがあります。Babel のパーサーは@babel/parserパッケージとして単独で使用できるようになっています。

たとえば、Prettierでは JavaScript、Flow、TypeScript をパースするために @babel/parserを使用しています。

@babel/parserは次のようなAPIで使うことができます。

const { parse } = require("@babel/parser");
const ast = parse(`const foo = "foo"`);

@babel/parser のプラグイン機能

@babel/parser にはプラグイン機能が存在します。

プラグインを有効にすることで、本来であればパースできない構文をパースできるようになります。

const { parser } = require("@babel/parser");

const code = `const foo: string = "foo"`;

parse(code); // 構文エラー、型注釈の構文をパースできない
parse(code, { plugins: ["typescript"] }); // typescript プラグインを有効にしているため型注釈の構文をパースできる

現在実装されているプラグインは https://babeljs.io/docs/en/babel-parser#plugins で確認できます。

プラグインという呼ばれ方をしていますが、サードパーティがプラグインを実装することはできません。これは、@babel/parserの方針として決まっており、今後もそのような対応をすることはないでしょう。

なので、実際はプラグインというよりはオプションのようなものだと思っていただければいいと思います。

プラグインのオプション

一部の@babel/parserのプラグインにはオプションがあります。

たとえば、pipelineOperatorプラグインにはproposalというオプションがあります。この記事でこれについて詳しく解説することはしませんが、簡単にいえばパイプライン演算子のプロポーザルには3種類あり、どの種類の提案を使うかをオプションで指定できます。

const { parse } = require("@babel/parser");

parse(`x |> await f`, { plugins: [["pipelineOperator", { proposal: "fshap" }]] });

typescript プラグインの dts オプション

さて本題です。

@babel/parser 7.14.0 では、TypeScript の構文に対応するための typescript プラグインに boolean をとる dts という新しいオプションが生えました。

次のようにして使います。

const { parse } = require("@babel/parser");

parse(`const foo: string = "foo"`, { plugins: [["typescript", { dts: true }]] });
parse(`const foo: string = "foo"`, { plugins: [["typescript", { dts: false }]] });

このdtsオプションは、TypeScript の型定義ファイルを正しくパースするためのオプションです。つまり、*.d.tsファイルを正しくパースするためのオプションということです。

*.d.tsファイルと通常の TypeScript の構文は全く同じだと思われていることが多いですが、実際には微妙に違う箇所があり、dtsオプションを使うことでそれを切り替えることができます。

現在 Babel チームが知っている*.d.tsと通常のTypeScriptの構文のちがいはひとつだけです。それは関数のパラメータの最後が Rest Parameters であるとき、末尾カンマが許容されるかどうかという点です。

通常の TypeScript では関数のパラメータの最後が Rest Parameters であるとき、末尾カンマをつけると構文エラーになります。しかし、*.d.tsでは構文エラーが発生しません。

index.ts
// 構文エラーが発生する
function foo(...rest,): string {}
index.d.ts
// 構文エラーは発生しない
function foo(...rest,): string;

https://github.com/microsoft/TypeScript/issues/23070 によるとこの挙動は意図されたものです。

もともと TypeScript では Rest Parameters の末尾にカンマをつけることは構文上問題ありませんでした。しかし、https://github.com/microsoft/TypeScript/pull/22262 によって禁止されるようになりました。しかし、かなり影響範囲が大きくなることが想定されたので *.d.ts ファイルに対しては禁止しないようになっているのです。

まとめると、すくなくとも現時点では @babel/parsertypescript プラグインの dts オプションは、Rest Parameters の末尾カンマを許容するかどうかを決めるオプションということになります(厳密には、その他にももともと Ambient Context のみで発生する構文エラーがいくつかあり、dtsオプション有効時にはそれらのエラーも発生するようになっています)。

詳細が気になるひとはこの機能を追加した PR https://github.com/babel/babel/pull/13113 を参照してください。

Q. なぜ dts オプションは役に立たないのか?

A. *.d.tsファイルを@babel/parserで直接パースしたいひとはいないから。

Discussion