Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TSKaigi Sendai 2026 - OxcのTypeScript parserを眺める

Avatar for teamLab teamLab PRO
November 01, 2026

TSKaigi Sendai 2026 - OxcのTypeScript parserを眺める

このスライドは TSKaigi Sendai 2026の登壇に使用したスライドのアーカイブになります。

チームラボでは、フロントエンドエンジニアを募集しています。
少しでも、チームラボにご興味をお持ち頂けましたら、採用ページをご覧頂けますと幸いです。
https://www.team-lab.com/recruit/

Avatar for teamLab

teamLab PRO

November 01, 2026

More Decks by teamLab

Other Decks in Technology

Transcript

  1. 先にゴールの確認、Oxcでパースするとどうなるか? a < b > (c); a<b> c; ↓レキサーでトークンに分解 a

    < b > ( ↓レキサーでトークンに分解 c ) ; a < b > c ; ↓パーサーでASTに変換 ↓パーサーでASTに変換 CallExpression BinaryExpression (>) ├─ callee: Identifier "a" ├─ left: BinaryExpression (<) ├─ typeArguments: TSTypeParameterInstantiation │ ├─ left: Identifier "a" │ └─ params[0]: TSTypeReference │ └─ right: Identifier "b" │ └─ typeName: Identifier "b" └─ right: Identifier "c" └─ arguments[0]: Identifier "c" © teamLab Inc. 6
  2. a < b > (c); のパースをトレースする Oxcは再帰下降パーサーで実装されている 「文 → 式

    → より細かい式」と、関数を順に呼び出して読み解いていく 関数(主な呼び出しを抜粋) ファイル:行 この例で解析するもの parse_statement_list_item js/statement.rs:131 文:a < b > (c); └ parse_assignment_expression_or_higher js/expression.rs:1479 式:a < b > (c) └ parse_binary_expression_or_higher js/expression.rs:1304 まず演算の左側になる式を読む └ parse_lhs_expression_or_higher js/expression.rs:748 a とそれに続く構文を読む └ parse_member_expression_rest js/expression.rs:859 aに続く < の解釈を試す © teamLab Inc. 8
  3. parse_member_expression_restから抜粋 「<(LAngle)」「<<(ShiftLeft)」はis_ts(TSとして解析する)のとき 試しに「型引数」として読む loop { … match self.cur_kind() { …

    kind if kind.is_template_start_of_tagged_template() => { … } Kind::Bang if self.is_ts && !self.cur_token().is_on_new_line() => { … } Kind::LAngle | Kind::ShiftLeft if self.is_ts => { if let Some(arguments) = self.parse_type_arguments_in_expression() … } } https://github.com/oxc-project/oxc/blob/a342f7e91ac12d3a3c5d53c49c41288564003a30/crates/oxc_parser/src/js/expression.rs#L913-L929 © teamLab Inc. 9
  4. a < b > (c); のパースをトレースする JSのパーサーから、ts/types.rs の型引数解析を呼び出す 関数(主な呼び出しを抜粋) ファイル:行

    この例で解析するもの parse_statement_list_item js/statement.rs:131 文:a < b > (c); └ parse_assignment_expression_or_higher js/expression.rs:1479 式:a < b > (c) └ parse_binary_expression_or_higher js/expression.rs:1304 まず演算の左側になる式を読む └ parse_lhs_expression_or_higher js/expression.rs:748 a とそれに続く構文を読む └ parse_member_expression_rest js/expression.rs:859 aに続く < の解釈を試す ts/types.rs:914 <b> を型引数として試し読み └ parse_type_arguments_in_expression © teamLab Inc. 10
  5. parse_type_arguments_in_expression の抜粋(a < b > (c);の場合) 型引数として試しに読み、続きも含めて成立しなければ元に戻す <b> を型引数として試しに読み、続くトークンを確認する pub(crate)

    fn parse_type_arguments_in_expression(&mut self,) -> Option<ArenaBox<'a, TSTypeParameterInstantiation<'a>>> { … let checkpoint = self.checkpoint(); // パースを試す前の状態を保存する … // <b> を型引数として試しに読む、失敗したら元の状態に戻す … } if self.fatal_error.is_some() || !self.can_follow_type_arguments_in_expr() { self.rewind(checkpoint); return None; … https://github.com/oxc-project/oxc/blob/a342f7e91ac12d3a3c5d53c49c41288564003a30/crates/oxc_parser/src/ts/types.rs#L945-L949 © teamLab Inc. 11
  6. can_follow_type_arguments_in_expr から抜粋(a < b > (c);の場合) a < b >

    (c); は「>(RAngle)」の次が「((LParen)」なので、trueと判定 fn can_follow_type_arguments_in_expr(&mut self) -> bool { match self.cur_kind() { Kind::LParen | Kind::NoSubstitutionTemplate | Kind::TemplateHead => true, Kind::LAngle | Kind::RAngle | Kind::Plus | Kind::Minus => false, _ => { self.cur_token().is_on_new_line() || self.is_binary_operator() || !self.is_start_of_expression() } } } https://github.com/oxc-project/oxc/blob/a342f7e91ac12d3a3c5d53c49c41288564003a30/crates/oxc_parser/src/ts/types.rs#L958 「>」の先は「( 」で関数呼び出しの形 → 型引数として判定! © teamLab Inc. 12
  7. can_follow_type_arguments_in_expr から抜粋(a < b > c; の場合) a < b

    > c; は「_(その他)」に分岐してfalseと判定される fn can_follow_type_arguments_in_expr(&mut self) -> bool { match self.cur_kind() { Kind::LParen | Kind::NoSubstitutionTemplate | Kind::TemplateHead => true, Kind::LAngle | Kind::RAngle | Kind::Plus | Kind::Minus => false, _ => { self.cur_token().is_on_new_line() // 同じ行 → false || self.is_binary_operator() // 二項演算子ではない → false || !self.is_start_of_expression() // 式を始められる → !true } } } https://github.com/oxc-project/oxc/blob/a342f7e91ac12d3a3c5d53c49c41288564003a30/crates/oxc_parser/src/ts/types.rs#L960-L964 「>」の先は条件に該当しない → 型引数ではない判定! © teamLab Inc. 13
  8. parse_type_arguments_in_expression の抜粋(a < b > c; の場合) 型引数として採用できなければ、保存した状態に戻す(rewind) pub(crate) fn

    parse_type_arguments_in_expression(&mut self,) -> Option<ArenaBox<'a, TSTypeParameterInstantiation<'a>>> { … let checkpoint = self.checkpoint(); // パースを試す前の状態を保存する … // <b> を型引数として試しに読む … } if self.fatal_error.is_some() || !self.can_follow_type_arguments_in_expr() { self.rewind(checkpoint); return None; … https://github.com/oxc-project/oxc/blob/a342f7e91ac12d3a3c5d53c49c41288564003a30/crates/oxc_parser/src/ts/types.rs#L945-L949 © teamLab Inc. 14
  9. a < b > c; のパースをトレースする 比較演算子として、改めて読み直すことでパース完了 関数(主な呼び出しを抜粋) ファイル:行 この例で解析するもの

    parse_statement_list_item js/statement.rs:131 文:a < b > c; └ parse_assignment_expression_or_higher js/expression.rs:1479 式:a < b > c └ parse_binary_expression_or_higher js/expression.rs:1304 まず演算の左側になる式を読む └ parse_lhs_expression_or_higher js/expression.rs:748 a とそれに続く構文を読む └ parse_member_expression_rest js/expression.rs:859 aに続く < の解釈を試す ts/types.rs:914 <b> を型引数として試し読み js/expression.rs:1322 < > を比較演算子として読む └ parse_type_arguments_in_expression └ parse_binary_expression_rest © teamLab Inc. 15
  10. Oxcのパーサーを眺めてわかったこと 1. is_ts フラグで分岐してる箇所が、JSとTSの境界部分 a. ちなみに、50数箇所くらいありました 2. 曖昧な構文では、試しに読んで合わなければ元に戻す checkpoint /

    rewind が使われている a. これはTS固有ではなく、JSが持ってる曖昧な構文を読むための仕組みでもあります コードを読むと、JS/TSの構文も学べて楽しい! © teamLab Inc. 16