SelectorParserにおける疑似要素セレクタの二重コロン解析バグを修正
::before などの疑似要素セレクタが : と ::before に誤って分割されていたパースバグを修正しました。後続の機能開発を妨げていた問題を、独立したPRとして切り出して解決しています。
背景
別機能の開発中に発覚した SelectorParser のバグが、今回の修正の発端です。.foo::before のような疑似要素セレクタをパースした際、期待される2ノード構成ではなく、余分な : ノードを含む3ノード構成で返されていました。
これまでの実装では ::before の1文字目のコロンが単独の : ノードとして切り出され、残りの ::before が別ノードになる挙動を取っていました。PR本文によれば、この誤りはこれまで実用上の問題にはなっていなかったものの、後続のPRで実際の障害になることが判明したため、独立したPRとして先行修正されています。
技術的な変更
selector-parser.ts の COLON ケース処理に、二重コロンを検出して buffer に結合する早期リターンを追加しました。
変更前は、コロン文字に到達するたびに直前の buffer を新しいセレクタノードとして切り出す処理が走っていました。.foo::before の場合、最初のコロンで .foo が切り出され、次の文字処理で : 単独のノードが生成され、さらに before の手前で ::before が切り出されるという3段階の分割が発生していました。
変更後:
case FULL_STOP:
case COLON:
case NUMBER_SIGN: {
if (currentChar === COLON && buffer === ':') {
buffer += input[i]
break
}
// Handle everything before the combinator as a selector and
// start a new selector
if (buffer.length > 0) {
追加されたガード節は「現在の文字がコロン(:)であり、かつ buffer の内容がすでに : である」という条件を評価します。この条件が真の場合、2文字目のコロンを buffer に追記して即座に break し、ノード分割処理をスキップします。結果として :: がひとまとまりで保持され、続く before と結合されて ::before という正しい単一ノードが生成されます。
テストケースも selector-parser.test.ts に追加され、.foo::before が2ノード構成(.foo と ::before)で返されることが明示的に検証されています。
設計判断
最小限の変更で問題を局所的に修正する方針が取られています。
修正箇所はパーサーの COLON ケースに5行を追加するのみです。buffer の状態を確認するという既存の設計パターンを活かし、二重コロンを特別扱いするロジックを既存の分岐の入口に挿入しています。セレクタ分割の主処理には手を加えず、その手前で二重コロンを透過的に処理することで、変更範囲が最小化されています。
また、PR本文に「後続PRのために分離して修正した」と明記されている点も注目に値します。バグ修正と機能追加を混在させず、変更の目的を単一に保つ方針が読み取れます。
まとめ
バッファの状態チェックという5行の早期リターンで、疑似要素セレクタの誤分割という構造的なバグを解消しました。これまでは問題が顕在化していなかったものの、セレクタノードの構造に依存する処理を追加しようとしたことで問題が発覚した経緯は、パーサー出力の正確性が下流の機能品質に直結することを示す好例といえます。