プレーンテキスト貼り付け時の誤った自動リンク変換を修正
Foo::Bar::Baz や port:8080 のようなコロンを含むプレーンテキストが誤ってリンクとして変換されていた問題を修正しました。URL コンストラクタを用いた判定を廃止し、scheme:// または www. で始まる文字列のみを対象とする正規表現に置き換えています。
背景
これまでの実装では、isUrl() 関数が new URL(string) の成否でURL判定を行っていました。しかし URL コンストラクタは RFC 3986 に基づく広範な構文を受け入れるため、Foo::Bar::Baz(Rubyの名前空間表記)や port:8080、time:9:00 のようなコロンを含む文字列も有効なURLとして解釈してしまいます。これらの文字列は scheme:path 形式のURLとして構文上は合法であり、コンストラクタが例外を投げないためです。
その結果、エディタへのプレーンテキスト貼り付け時に意図しないリンクノードが生成されるという問題が発生していました。
技術的な変更
isUrl() を廃止し、より厳格な判定を行う isAutolinkableURL() に置き換えました。判定ロジックは new URL() によるパースから正規表現マッチに変わっています。
変更前:
export function isUrl(string) {
try {
new URL(string)
return true
} catch {
return false
}
}
変更後:
export function isAutolinkableURL(string) {
return /^(?:[a-z0-9]+:\/\/|www\.)[^\s]+$/i.test(string)
}
新しい正規表現は以下の2つの条件を必要とします:
- 文字列が
scheme://(英数字スキームの後に://)またはwww.で始まること - 空白文字を含まないこと
この正規表現はBasecampの url_pastes.js を元にしており、本番環境で長年使用されてきた実績があります。clipboard.js 内の呼び出し箇所も isUrl から isAutolinkableURL へ変更されています。
設計判断
関数名を isAutolinkableURL に変更したことで、この判定が「RFC 3986 準拠の完全なURL検証」ではなく「自動リンク化の対象かどうか」という、より狭い意図を持つものであることが明示されています。
正規表現の選択も意図的です。scheme:// の形式を要求することで、mailto:user@example.com のような :// を持たないURIスキームは対象外となります。これはエディタの自動リンク機能として実用的なスコープを定義したものであり、URLの完全な検証を目的としていません。また、大文字小文字を区別しない i フラグにより、HTTPS://example.com や WWW.example.com のような入力も正しく処理されます。
テストには thingsThatMightBeURIs として18パターンのケースが追加されており、リンク化すべきURL(https://、http://、www. 形式)と、リンク化すべきでない文字列(:: 区切りの名前空間、key:value 形式)の両方を網羅しています。
まとめ
本PRは、汎用的なURLパーサーをエディタの自動リンク機能に流用していた設計上の不一致を解消しています。「何がURLか」という問いへの答えを RFC 3986 の定義からユーザーが貼り付けてリンク化したいと想定される表現に絞り込むことで、意図しない変換が発生しない実装に置き換えました。