TypeScriptの型定義ファイルを作るときに行儀の悪いコードもコンパイルできるようにするべきか?
追記あり。(ついでにタイトルを「TypeScriptの型定義で悩んでいること」から改題) TypeScriptの型定義で悩んでいること。
前提
- 型定義を作ろうとしているライブラリは自分が作ったものではない
- せっかくなのでDefinitelyTypedにプルリクエストを送りたい
悩んでいること
emitter.addListeners({ "click": function(){/* ... */}, "keyup": function(){/* ... */} })
上のようなキーバリューのペアで設定する引数のためにインターフェイスを定義したくなった。
interface EventsListeners { [event: string]: Function }
こうすることでリスナーに関数以外のものを指定して実行時例外になってしまうコードをコンパイル時に検出できる。 しかし、これだとコンパイルが通らないケースがあることに気づいた。
class MyEventListeners { constructor(public click: Function); } var onClick = function(){ console.log("The event was raised!"); }; var listeners = new MyEventListeners(onClick); emitter.addListeners(listeners);
行儀の悪い使い方だと思うけど、内部でfor(i in listeners) + hasOwnProperty
で回しているので動いてしまう。
両方の例をコンパイルに通すためにはObject型を指定しないといけないはず。
型をより厳しく書くために下のケースのようなコードはエラーにしてしまいたいけど、DefinitelyTypedに置くのならJavaScriptでできることは全てコンパイルを通るようにしないといけないのだろうか?
追記
この記事の内容を@vvakameさん(DefinitelyTypedのコラボレータ)に質問したところ、いくつかアドバイスをいただきました。(今回型定義を作ろうとしたファイルはeventemitter)
@ryiwamoto なかなか難儀なライブラリですね…。DefinitelyTypedに置く場合、全てのコンパイルが通るようにする必要は実はありません。お行儀のよい書き方の時だけコンパイルできる…というのでもOKです。問題があったら後から改善すればOKです。
— わかめ@TypeScriptカッコガチ (@vvakame) 2014, 11月 1
@ryiwamoto 僕が型定義ファイルを書く時は、利用側の事は考えず、ライブラリ本体のコードを読みつつそのライブラリが実際に想定している型のみを書いています。
— わかめ@TypeScriptカッコガチ (@vvakame) 2014, 11月 1
@ryiwamoto 極論、今回のブログのような場合は emitter.addListeners(<any>listeners); でOKやろ!というスタンスの型定義ファイルでもOKです。クラスのインスタンス直接渡すような行儀の悪いコード書くなオラァン!でよいかと。
— わかめ@TypeScriptカッコガチ (@vvakame) 2014, 11月 1
これらはあくまでvvkameさんの場合ということで、明確な基準はないそうです。とはいえひとつの方針としてこれから型定義を作るときの参考になりますね。