たねやつの木

Photographs, Keyboards and Programming

VSCodeのバグ修正のプルリクを送り、初めてOSSコントリビュートした話

こんにちは、たねやつです。

今日は久々にプログラミング関係の話で、VSCodeのバグ修正をして人生で初めてOSSに貢献した話です。正直修正したソースはたった1行だけ(なんならたった1文字)なのですが、それでも立派な貢献であるといえる(はず)なので、参考になる記事もあまり多く発見できなかったので残しておきます。

作業した環境はWindowsなので手順もWindows用のものとなっています。LinuxやMacOSの場合はHow to Contributeに手順があるのでソチラを参考にしてください(主に環境構築時)

経緯

なんでOSS貢献しようかと思ったのかなのですが、これは単純に最近コーディングする機会が仕事でも仕事以外でめっきりなくなってしまったからです。

自作キーボードに関しても一通り安定した環境になり、自宅IoT関連もなかなか子育てをしながら作業するのも難しい(引っ越して環境がガラッと変わってしまったので今までのも動作しなくなっちゃった)ので今年度は全く趣味でのコーディングというものが進みませんでした。

おまけに仕事の方も上流の仕事やレビューばかりになってしまい、、、ついでに、そもそも自社以外でもちゃんとITエンジニアとして活動できるのか?と思ってきた節もあるのでいろんなコミュニティに参加して自分の能力を確かめてみようと思った次第です。

マージされるまでの流れ

環境構築

次の章と前後するかもしれませんが、開発するためにはまずはローカルの開発環境が必要となってきます。これは普段使用しているようなVSCodeではなくソースからビルドできる開発用のVSCodeです。

流れはすべてVSCodeのGithubリポジトリのHow to Contributeに記載されています。

Prerequisites(必要要件)

開発に必要なアプリやツールとそのバージョンなどが記載されています。

適宜インストール用のリンクも記載されている(やさしい)ので、必要なものはインストールしていきます。Windows Build Toolsに関してもNodeJS本体をインストールする時に一緒にインストールしてくれているはずです。

この記事を書いている時点では使用するNodeJSのバージョンは>=16.14.x and <17となっています。最初自分の環境にあったNodeJSのバージョンは17.xだったのでnvmを使用して古いバージョンを追加でインストールしました。ちなみに適合外のバージョンを使用するとそもそもビルド時にエラーとなりできませんでした。

Enable Commit Signing(コミット署名を有効にする)

VSCode(というかMicrosoft配下のプロジェクト全般?)へのコミットは署名付きにする必要があるそうです。以下のリンクに手順などが記載されています。

Gpg4winをインストールして以下のコマンドを実行してgitから実行ファイルを認識させます。おそらくexeファイルはココにあるはずですが一応自分の環境で確かめてみてください。

git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"

以下でキーを作成します。

// gpgコマンドの確認。(無い場合は再起動か環境変数を設定)
$ where gpg
C:\Program Files (x86)\GnuPG\bin\gpg.exe

$ gpg --full-generate-key
  Kind of key  : RSA and RSA (default)
  Keysize      : 4096
  Expiration   : 0 (無期限)
  Real Name    : 名前
  Email address: Eメールアドレス
  Comment      : コメント(無しでもOK)
  Passphrase   : パスワード
  を入力。

生成出来たら以下のコマンドでチェックして、公開キーを生成します。

$ gpg --list-secret-keys --keyid-format LONG
...\pubring.kbx
------------------------------------------------
sec   rsa4096/XXXXXX 2023-01-24 [SC]
      YYYYYYYYYYYYYYYYYYYYYYYYXXXXXX
uid                 [ultimate] 名前
ssb   rsa4096/ZZZZZZ 2023-01-24 [E]

// sec rsa4096/の右側の文字列かYYYY...の部分の文字列を引数に使用
$ gpg --armor --export XXXXXX
-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----

BEGINからENDの間をどこかにメモしておきます。後でGithubに登録します。

ローカルのgitに鍵の設定を追加します。

$ git config --global user.signingkey XXXXXX
$ git config --global commit.gpgsign true

user.signingkeyでキーを指定して、commit.gpgsignをオンにします。

次にGithubのアカウント設定に移動して、先ほどの公開鍵を登録します。

Settings > SSH and GPG keys > New GPG Keyをクリックします。Titleは適当に指定して、Keyの部分に先ほどのBEGINからENDの内容をペーストしてください。

これで完了です。後でフォークしたブランチでコミット・プッシュするとこんな感じでVerifiedというマークがつきます。

フォークしたブランチをクローンする

ちょっとわき道にそれましたが、本家リポジトリからフォークしてローカル開発環境にクローンしていきます。

上の方のforkというボタンを押して自分のアカウントにフォークします。その後にgit cloneで取得します。

$ git clone https://github.com/(自分のgitアカウント名)/vscode.git
yarnして起動してみる

クローンが完了したらyarnを実行します。

$ cd vscode
$ yarn

ちょっと時間がかかると思いますが、完了したら以下コマンドで起動します。

$ .\scripts\code.bat

Code - OSS Devというスタート画面でVSCode(Code - OSS)が起動すればOKです。

再ビルドするにはVSCodeでクローンしたフォルダを開いて、Ctrl + Shift + Bかターミナルからyarn watchを実行します。

修正したいissueを調べる

開発環境が整ったので、修正するissueを調べていきます。既にとりかかりたいものがあればそれを進めていきます。

私は、何か修正したい箇所があったり自分からバグを見つけてissueをあげるというのではなく、既にissueとして上がっている中から修正したいものを選びました。この時に便利なのがgood first issueというラベルの付いたものでした。

メンテナの方がそこまで要件が難しくない修正に対して付けてくれるラベルで、初めてPJに参加する際にうってつけの内容です!その中から自分はMarkdown関係のものを選んで着手しました。

修正

内容把握・再現

まずはバグの内容把握して再現させる必要があります。今回の内容は、

マークダウンのプレビュー画面(Ctrl + Shift + V)にて、1行目だけハイライトがエディタと一致しない

というような内容でした。

5行目にエディタのカーソルを当てた時に、そのヘッダーがハイライトされますが、1行目にカーソルを当てても最初のヘッダーがハイライトされません。

原因特定

再現出来たら原因特定を行います。VSCodeは中身はHTMLファイルをいろいろ表示しているだけらしく、こういったハイライトの挙動とかもHTMLとCSSによるもののようです。

実際に開発用のVSCodeではChromeのデベロッパーツールを起動することができます。そこからハイライトされるときの挙動を確認します。

正常な挙動をする行を選択したり、解除したりするとHTMLのclassにcode-active-lineというクラスが追加されたり除去されたりしているのを発見しました。

正常な挙動をしない1行目ではこれが追加されないため、正しくハイライトされないようです。ここまでは想定通りでした(何事もこれぐらいすんなり行けば助かるのだけど

ではcode-active-lineというクラスを付与している処理を検索します。プロジェクト内で検索するとmarkdown.cssというファイルとactiveLineMarker.tsというファイルがヒットします。

前者はCSSなので処理には関係ないので名前からも明らかなactiveLineMarker.tsを追っていくと、以下のような処理があります。

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import { getElementsForSourceLine } from './scroll-sync';

export class ActiveLineMarker {
    private _current: any;

    onDidChangeTextEditorSelection(line: number, documentVersion: number) {
        const { previous } = getElementsForSourceLine(line, documentVersion);
        this._update(previous && previous.element);
    }

    _update(before: HTMLElement | undefined) {
        this._unmarkActiveElement(this._current);
        this._markActiveElement(before);
        this._current = before;
    }

    _unmarkActiveElement(element: HTMLElement | undefined) {
        if (!element) {
            return;
        }
        element.className = element.className.replace(/\bcode-active-line\b/g, '');
    }

    _markActiveElement(element: HTMLElement | undefined) {
        if (!element) {
            return;
        }
        element.className += ' code-active-line';
    }
}

_markActiveElementでクラスに追加、_unmarkActiveElementで除去しています。この部分は特に難しい処理をしていないのでですが、おそらく1行目を選択したときにprevious変数がおかしいと思ったのでデバッグ分を追加して再ビルドして確認します。

各行を選択したときのconsole.log(previous)ですが、明らかに1行目(多分配列の添字が0)を選択したときに変な値が取得されています。

他の行は対象のhtml要素が取得できているので、深堀でgetElementsForSourceLine(line, documentVersion)が何をしているか見ます。

import文からscroll-sync.tsというファイルで定義されているので見てみますが、最終的に要素と行番号を格納しているオブジェクトの宣言時に問題があることを発見しました。

cachedElements = [{ element: document.body, line: 0 }];

ここで0行目(エディター上の1行目)の要素としてdocument.bodyをセットしています。

その後に行ごとにこのオブジェクトに値をpushしていってるので、line: 0の値が2個存在して、先頭寄りの方にdocument.bodyが来てしまっています。

他のコードを見てもこのdocument.bodyの値を使用していないので以下のように変更してテストします。

cachedElements = [{ element: document.body, line: -1 }];

テスト

コードを変更したらテストを行います。まずはプロジェクトのデバッグ内にMarkdown Extenstion Testという自動テストがあるのでそれを実行します。

問題なければ実装面のテストを行います。今回修正した変数を呼び出している箇所を全部通るような回帰テストです。この辺りは実際に開発とかをやっていればどのようなテストをすればいいかはわかるのですがなかなか書き記すのは難しいですね。

今回のバグっている部分だけを見れば以下のようなテストでしょう。

  • 1行目を選択時に対象の要素がハイライトされること。
  • 2行目(空行)を選択時に上記の要素がハイライトされること。
  • 3行目(p要素)を選択時に対象の要素がハイライトされること。
  • 1行目と同じ要素(h1)を選択時に、1行目(と対象以外の行)がハイライトされないこと。

等です。

プルリクエストを送る

テストして問題なければコミット・プッシュしてプルリクエストを送ります。

$ git add .
$ git commit -m "fix#171379"
$ git push

コミットコメントには起因のissue番号を含めています。タイトルか修正箇所も簡潔書いた方が見やすかったと後から思いました(実際他のコミッターもそのようにしています。)

プッシュ後におそらくフォーク元と最新コミットが一致しない(別のコミットが取り込まれている)状態になっていると思うので、自分のフォークも合わせておきます。Github画面上から簡単にできることを今回初めて知りました。

Sync fork > Update branchから最新化できます。

プルリクエストはPull requests > New pull requestで、←の右辺(フォークした自分のリポジトリ)のブランチを選択すると自動的に変更箇所をハイライトして表示してくれます。

Create pull requestをクリックするとプルリクエストを作成できます。内容やissueへのリンクを記載しておきます。

承認されてvscode/mainへマージされる

あとは承認されるか否認されて修正 → 再度コミットという流れになると思います。幸い今回は1回で承認されて手戻りなどありませんでした。vscodeの場合は初めてのプルリクに対しては承認者が2人以上必要なようでした。2人から承認されるとあとは自動的にマージまでやってくれます。

マージされてしまえば今回の開発用のブランチは不要なので削除しています。

承認後もいろんなマイルストーン(backlog, 定期リリース対象)に追加されたりします。今回の修正はFebruary 2023(1.76)でリリースされるみたいです。

実際にリリースされる

(February 2023がリリースされたら追記します)

最後に

本業がSEということもあって、バグや改善要望への対応の大まかな流れは大体同じだったためすんなり入り込めました。Githubを使用してプルリクを送るというのは初めての経験だったのですがそこまで難しくないですね。

VSCodeへの貢献はかなり敷居はめちゃくちゃ低いと思います。環境構築手順がしっかり用意されていること、メンテナの方がとっつきやすいissue等にgood-first-issueとラベルを付けてくれているのでそこを足がかりに着手もしやすいです。参加人数もOSSの中でも今はトップクラスに多いし、メンテナの方も活発なのでわからないこと等質問してもすぐに返ってきそうですね。

自分もこれでissueに対してプルリクを送るという一連の流れを体験できたので他のOSS等にも貢献していく勇気が付きました👀 たった1行の修正ですが、自分がいつも使っているエディタの改善に関わることができるというのはとてもうれしいことですね🥰