TS4.5 の type Modifiers on Import Names のパースがおもしろい
TypeScript 4.5 のベータ版がアナウンスされました。
たくさんの新機能があり、大きなバージョンになりそうです。
それらの新機能の中に type
Modifiers on Import Names というものがあります。
これは、TypeScript 3.8 から追加された Type-only imports and exports を specifier 単位で指定できるようにしたものです。
今までは、
import { Foo1 } from "foo";
import type { Foo2 } from "foo";
とかいていたものを、これからは
import { Foo1, type Foo2 } from "foo";
と書くことができます。
便利ですね。
パース処理がおもしろい
最近私はこの構文のパーサーを Babel のために書いています。
挙動を TypeScript と一貫させるために、本家(microsoft/TypeScript)のコードを読んでいたのですがその処理がおもしろかったので紹介します。
本家コードはここにあります。
一部抜粋して掲載します。
コード
let isTypeOnly = false;
let propertyName: Identifier | undefined;
let canParseAsKeyword = true;
let name = parseIdentifierName();
if (name.escapedText === "type") {
// If the first token of an import specifier is 'type', there are a lot of possibilities,
// especially if we see 'as' afterwards:
//
// import { type } from "mod"; - isTypeOnly: false, name: type
// import { type as } from "mod"; - isTypeOnly: true, name: as
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
if (token() === SyntaxKind.AsKeyword) {
// { type as ...? }
const firstAs = parseIdentifierName();
if (token() === SyntaxKind.AsKeyword) {
// { type as as ...? }
const secondAs = parseIdentifierName();
if (tokenIsIdentifierOrKeyword(token())) {
// { type as as something }
isTypeOnly = true;
propertyName = firstAs;
name = parseNameWithKeywordCheck();
canParseAsKeyword = false;
} else {
// { type as as }
propertyName = name;
name = secondAs;
canParseAsKeyword = false;
}
} else if (tokenIsIdentifierOrKeyword(token())) {
// { type as something }
propertyName = name;
canParseAsKeyword = false;
name = parseNameWithKeywordCheck();
} else {
// { type as }
isTypeOnly = true;
name = firstAs;
}
} else if (tokenIsIdentifierOrKeyword(token())) {
// { type something ...? }
isTypeOnly = true;
name = parseNameWithKeywordCheck();
}
}
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
propertyName = name;
parseExpected(SyntaxKind.AsKeyword);
name = parseNameWithKeywordCheck();
}
まあ具体的なところは無視して読み飛ばしてほしいんですが、注目してほしいのはこのコメントです。
// import { type } from "mod"; - isTypeOnly: false, name: type
// import { type as } from "mod"; - isTypeOnly: true, name: as
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
name
と propertyName
という変数に、as
の左右の specifier を代入していくわけですが、そのエッジケースが書かれています。
1:
import { type } from "mod";
は mod
というモジュールから type
という識別子を named import します。これは type-only ではありません。
2:
import { type as } from "mod";
は mod
というモジュールから as
という識別子を type-only import します。
3:
import { type as as } from "mod";
は mod
というモジュールから、type
という識別子を as
という名前で import します。type-only import ではありません。
4:
import { type as as as } from "mod";
は mod
というモジュールから、as
という識別子を as
という名前で type-only import します。
おわりに
Babel のためのこのロジックを書きましたが、めんどうくさかったです。
みなさんはこういうコードを書かない方が良いと思います。
Discussion