Slide 1

Slide 1 text

JavaScript パーサーに using 対応をする過 程で与えたエコシステムへの影響
 baseballyama & ota-meshi


Slide 2

Slide 2 text

この発表は
 
 前半: baseballyama
 後半: ota-meshi 
 
 でお送りします。


Slide 3

Slide 3 text

baseballyama 自己紹介
 ● baseballyama (山下 裕一朗)
 ● フライル株式会社
 ○ ソフトウェアエンジニア
 
 ● Svelte コアチームメンバー
 ○ 元々は Svelte コンパイラを担当
 ○ 最近は主に eslint-plugin-svelte や svelte-eslint-parser を担当
 


Slide 4

Slide 4 text

アジェンダ
 ● using とは
 ● acorn に using 対応をした流れと学び
 ● エコシステムに及ぼした影響


Slide 5

Slide 5 text

using とは


Slide 6

Slide 6 text

https://github.com/tc39/proposal-explicit-resource-management 


Slide 7

Slide 7 text

最小のコード例
 {
 using resource = {
 value: 'Hello, using!',
 [Symbol.dispose]() {
 console.log('Resource disposed');
 }
 };
 console.log(resource.value);
 }
 
 // 実行結果:
 // Hello, using!
 // Resource disposed


Slide 8

Slide 8 text

最小のコード例
 {
 using resource = {
 value: 'Hello, using!',
 [Symbol.dispose]() {
 console.log('Resource disposed');
 }
 };
 console.log(resource.value);
 }
 
 // 実行結果:
 // Hello, using!
 // Resource disposed
 【解説】
 1. console.log('Resource disposed') を実行
 2. ブロックを抜ける際に
   Symbol.dispose() が自動実行
 3. リソースのクリーンアップが保証される
 Symbol が 必要 using キーワード

Slide 9

Slide 9 text

もう少し具体的な例
 class AsyncDB {
 constructor(name) {
 console.log(`接続: ${name}`);
 }
 
 async [Symbol.asyncDispose]() {
 console.log('切断処理中...');
 await new Promise(resolve => setTimeout(resolve, 100));
 console.log('切断完了');
 }
 }
 async function main() {
 await using db = new AsyncDB('mydb');
 console.log('クエリ実行');
 }
 
 main().then(() => console.log('完了'));
 
 // 実行結果:
 // 接続: mydb
 // クエリ実行
 // 切断処理中...
 // 切断完了
 // 完了


Slide 10

Slide 10 text

もう少し具体的な例
 class AsyncDB {
 constructor(name) {
 console.log(`接続: ${name}`);
 }
 
 async [Symbol.asyncDispose]() {
 console.log('切断処理中...');
 await new Promise(resolve => setTimeout(resolve, 100));
 console.log('切断完了');
 }
 }
 async function main() {
 await using db = new AsyncDB('mydb');
 console.log('クエリ実行');
 }
 
 main().then(() => console.log('完了'));
 
 // 実行結果:
 // 接続: mydb
 // クエリ実行
 // 切断処理中...
 // 切断完了
 // 完了
 await using キーワード Symbol が 必要

Slide 11

Slide 11 text

using クイズ


Slide 12

Slide 12 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }


Slide 13

Slide 13 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 
 💡 “using” は予約語ではないので変数名に使用可能 ✅

Slide 14

Slide 14 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌

Slide 15

Slide 15 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 予約語の一覧
 ⚠ “using” は予約語ではない
 “let” は strict モードに おいては予約語

Slide 16

Slide 16 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした)

Slide 17

Slide 17 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 関連する構文定義
 ⚠ “instanceof” は予約語であり、 BindingList を満たさないため、
   “await using” として処理してはいけない。


Slide 18

Slide 18 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌

Slide 19

Slide 19 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) ✅ 💡 “using” は分割代入と同時には使用できない ❌ 関連する構文定義
 分割代入は禁止されている ⚠ 分割代入を使用しているため “await using” として処理してはいけない。


Slide 20

Slide 20 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる)

Slide 21

Slide 21 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) ✅ 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる) 関連する構文定義
 改行は禁止されている ⚠ 改行を使用しているため“await using” として処理してはいけない。
   結果、 “using” という名前の Promise を await していると解釈される。


Slide 22

Slide 22 text

正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2. { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる)

Slide 23

Slide 23 text

いろんなケースがあって
 難しい...


Slide 24

Slide 24 text

(最初は構文が難しいことも知らず)
 acorn に using 対応をしようと思った


Slide 25

Slide 25 text

このセクションに行く前に前提を揃えましょう
 ● (復習) 通称 using とは 
 正式には Explicit Resource Management と呼ばれる ES2026 に入る予定の機能。 
 “using” というキーワード (のようなもの) を使用することで、 
 今までは finally に書いていたような処理を宣言的に書ける機能。 
 ● acorn とは 
 代表的な JavaScript パーサーの一種。ESLint / Prettier / Rollup など、 
 我々が普段使っているツールチェインの基礎として使われているライブラリ。 
 ● パーサー とは 
 プログラムを文字列として読み取り構造化された情報に変換するプログラムのこと。 
 構造化した結果は、AST / CST と呼ばれるツリー構造であることが多い。 


Slide 26

Slide 26 text

実際にどう対応したか


Slide 27

Slide 27 text

実際にどう対応したか
 1. とりあえず仕様を読む 
 流石に仕様を理解しないとどうにもならないかなと思い仕様書を眺めた 
 https://github.com/tc39/proposal-explicit-resource-management 
 https://arai-a.github.io/ecma262-compare/?pr=3000 
 2. とりあえず AI に対応させてみる 
 → 結果的に全くうまくいかず、結局全て手書きすることになった 
 3. Babel / Chrome で動かしてみる 
 今回はありがたいことに既に Babel で using 対応がされていたので、Babel のパーサーを 実行してみたり、 Chrome でコードを実行してどうなるかを観察した 


Slide 28

Slide 28 text

実際にどう対応したか
 4. acorn のソースコードを読みなんとなく理解する 
 実はこの時点で初めて acorn のソースコードを読んだ 
 (acorn のパーサーが LL パーサーベースであることもここで知った) 
 (🙏今日は LLパーサー が何かは解説しません)
 5. まずは using の対応をしてみる 
 とにかくテストを追加しながら1個ずつ対応していった 
 変数宣言 → for of → ES2025以下の場合の対応 → module でない場合の対応 
 6. await using の対応をしてみる 
 using と同じ流れで対応
 


Slide 29

Slide 29 text

実際にどう対応したか
 7. 満を持してPRを提出 
 いくつか間違いがあったのを ota-meshi さんが拾ってくれた 
 
 


Slide 30

Slide 30 text

出したPR (GitHub上はクローズだがマージされました🎊)


Slide 31

Slide 31 text

学んだこと
 ● 仕様とBNFはスラスラ読めるようになった方がいい
 僕が普段読むのはせいぜい MDN や RFC なので、プログラミング言語の仕様書 (特に BNF) を読むのに苦労した。数式に慣れる、みたいなのと同じで日々 BNF を読み続けば慣 れるのだとは思うが、僕は普段からパーサーの開発をしているわけではないので、慣れる までに時間がかかるだろうなとは思った。 
 
 ● AIが役に立たない領域があった
 “using” が新しい構文だからなのか、AI が acorn のようなプログラムに対する学習をあま りしていないのかはわからないが、ほとんどAIは役に立たなかった。 
 (テストの不足とかのレビューは少ししてもらったけど) 
 これは ESLint ルールで Scope Manager を多用するルールを実装する時にも同じことが言 えて、学習リソースが足りない分野だとまだまだ人間の出番はあるのかなと感じた。 


Slide 32

Slide 32 text

学んだこと
 ● 大きな課題も複数の小さな課題に分解するとうまくいく
 「“using” 対応をする」というと、大きすぎる課題で何から取り組めばいいのかがわからなく なってしまうが、「まずは変数宣言をサポートしてみよう」といった自分の手に負えそうな単 位に分解して取り組むと最終的に大きな課題を解決できる。 
 
 ● まずは取り組むことが大事
 僕にとって acorn は全く内部を知らないライブラリだったが、一度やるぞと決めたらなんだ かんだ対応できた。(何もわからない状態からでも2日で実装完了した) 
 やるぞと心に決めることにさえ成功すれば、実装が完了したも同然である。 
 


Slide 33

Slide 33 text

めでたし
 めでたし


Slide 34

Slide 34 text

と思いきや...


Slide 35

Slide 35 text

このPRを起点に
 エコシステムに影響を及ぼすこととなった...


Slide 36

Slide 36 text

ota-meshi 自己紹介
 ● ota-meshi (太田 洋介)
 ● フューチャー株式会社
 ○ フロントエンドエンジニア
 
 ● Vueコアチームメンバー
 ● eslint-plugin-vueのメンテナー
 ● eslint-plugin-svelteのメンテナー
 ● ESLintチームAlumni
 ○ ノルマがきつくてコミッターはやめた。
 ○ 最近は Import Attributes とか新構文対応だけ参加してる 


Slide 37

Slide 37 text

using 宣言の構文を
 マスターできましたか?


Slide 38

Slide 38 text

突然ですが問題です


Slide 39

Slide 39 text

// トップレベル
 using x = foo();
 
 1. 有効
 2. エラー
 3. 場合による
 このusing宣言は有効ですか?


Slide 40

Slide 40 text

トップレベルのusing宣言
 https://v8.dev/features/explicit-resource-management#using-and-await-using-declarations 
 ちなみに、V8のブログでは


Slide 41

Slide 41 text

トップレベルのusing宣言
 でも実は、トップレベルで使用できないのはScript(Moduleじゃない)の時だけで す。
 https://github.com/tc39/proposal-explicit-resource-management/blob/main/README.md#semantics 


Slide 42

Slide 42 text

Module? Script?


Slide 43

Slide 43 text

Module? Script?
 ● Module
 ○ よく `import` とか `export`とか書いてるJavaScriptのこと。 
 ○ 別の言い方だと ``で実行されるJavaScriptのこと。 
 ● Script
 ○ ModuleじゃないJavaScriptのこと。 
 ○ 厳密モードにするには`"use strict"`しないといけないあれら。
 ○ 別の言い方だと `<script>`や`<script type="text/javascript">`で実行されるJavaScriptの こと。
 ● 厳密に構文をパースして仕様通りに構文エラーを検知するにはModuleなのかScriptなの かが超重要
 ● Acornは`sourceType`というオプションで判断してパースしている。


Slide 44

Slide 44 text

トップレベルのusing宣言
 つまり
 ● Moduleならトップレベルのusingが使える。
 ● Scriptだとトップレベルのusingは使えない。構文エラー。


Slide 45

Slide 45 text

// トップレベル
 using x = foo();
 
 1. 有効
 2. エラー
 3. 場合による
 このusing宣言は、
 場合によって有効/エラーが変わる


Slide 46

Slide 46 text

もう using 宣言の構文を
 完全にマスターしましたね!


Slide 47

Slide 47 text

…


Slide 48

Slide 48 text

ところで
 CommonJSの場合
 トップレベルのusing宣言は
 どうなるんでしょう?


Slide 49

Slide 49 text

CommonJS × トップレベルのusing宣言?
 ● CommonJSはModuleではないので一般的にはScript扱い。
 ● Scriptだとトップレベルのusingは使えない。って話だった。
 CommonJSでトップレベルのusing宣言は 構文エラーに違いない!

Slide 50

Slide 50 text

● Node.jsのCommonJSは内部的に関数でラップされる。
 ● そのためScriptであっても、トップレベルのusingは、実質的にはトップレベ ルのusingではない。
 ● そのため構文エラーではなく、usingは問題なく使用できる。
 (function (exports, require, module, __filename, __dirname, import) { // トップレベル? using x = foo(); })
 CommonJS × トップレベルのusing宣言?
 でもそれってNode.jsが 仕様に違反してるだけじゃないの?

Slide 51

Slide 51 text

CommonJS × トップレベルのusing宣言?
 いいえ。
 CommonJSではトップレベルのusing宣言を使用できます。
 これはNode.jsの意図的?な動作です。
 https://github.com/nodejs/node/issues/58663 


Slide 52

Slide 52 text

CommonJS × トップレベルのusing宣言
 ● CommonJSではトップレベルのusing宣言が使用できる。
 ESの仕様通りではないが、これは意図的な動作。
 ● CommonJSでトップレベルの`return`が使えるのと同じ理由。
 Acornはどうやって パースすればいいの?

Slide 53

Slide 53 text

AcornのCommonJSサポート
 ● どういうオプションにするかissueで議論された。 
 ○ https://github.com/acornjs/acorn/issues/1373#issuecomment-2955462597 
 ○ https://github.com/acornjs/acorn/issues/1376 
 ○ Babelメンテナーの JLHwungさんも参加
 ● トップレベルの`return`を許可する、`allowReturnOutsideFunction `というオプションがすでにあるので、こ れと同じように`allowTopLevelUsingDeclaration `みたいなオプションはどうか? 
 ● 今後も似たようなオプション増殖させるの辛すぎない?Closure Compilerの` --assume_function_wrapper ` みたいなオプションにするのはどう? 
 ● 実はトップレベルで` new.target`構文も使える(意味はないが)。 
 


Slide 54

Slide 54 text

AcornのCommonJSサポート
 JavaScriptパーサーのCommonJSサポート
 ● Acornは`sourceType`オプションに`"commonjs"`を受け入れられるようにしてCommonJS特有のパースを することにした(これまでは` "module"`と`"script"`のみが使えた)。
 https://github.com/acornjs/acorn/pull/1377 
 ● Espreeは`sourceType: "commonjs"`オプションでAcornの` allowReturnOutsideFunction `オプションを有効 にしていたが、Acornの` sourceType`がそのまま使えるようになる予定 
 https://github.com/eslint/js/issues/662 
 ● BabelメンテナーのJLHwungさんも議論に参加していたので、Babelも同じオプションを追加することに決定 した。
 https://github.com/babel/babel/pull/17390 
 ● 議論を見ていたSWCのメンテナーの方が、SWCでも同様のオプションを受け入れられるようにしてた。 
 https://github.com/swc-project/swc/pull/10600 


Slide 55

Slide 55 text

他のパーサーは?
 ● TypeScriptは?
 ○ トップレベルのusing宣言をエラーにしない
 ● OXCは?
 ○ トップレベルのusing宣言をエラーにしない
 ● 他は分かりません。


Slide 56

Slide 56 text

Conclusion
 ● トップレベルのusing宣言は、実質的にいつも使える
 (CommonJS以外のScript書くことなんてもうないでしょ)
 ● CommonJSでトップレベルusingを書いていて、使ってるJavaScriptパーサー がパースできないなと思ったら、
 `sourceType`オプションに`"commonjs"`が使えるかどうか調べてみよう。


Slide 57

Slide 57 text

Conclusion
 ● それよりも、CommonJS使うのやめましょう(´・ω・`)
 https://x.com/slicknet/status/1932506069797892539

Slide 58

Slide 58 text

Thank you!