VSCode Extensions(拡張機能) 自作 リファクタリングをchatGPTに任せよう

会社の部内イベントでchatGPTを使ったハッカソンが開催されたので、VSCode拡張機能の勉強がてらソースコードをリファクタリングしてくれるプラグインを自作してみました。いいアイデアがなかったのですが、他のチームはWebアプリを実装してくる読みで、独自性を見出すためにVSCode拡張に手を出しました。

## VSCode Extensionsについて

ご存知の方も多いと思いますが、VSCodeは拡張機能を入れることでさらに日々の開発体験を向上することができます。
おすすめプラグインは[色々な記事](https://qiita.com/KNR109/items/5f933df1292564e6dc70)でも紹介されていますが、我々の開発では

- Jest Runner
- Jest Snippets

あたりはよく使っていますね。（最近vitestが台頭してきたので別のプラグインも探さなきゃな、、）

## VSCode拡張機能は自作できる

ここからが本題です。VScodeプラグインは自作できます。
今回開発してみて思ったより難しくないことが分かりました。
TypeScriptで記述できるので、みなさんも遊び感覚で開発してみてはいかがでしょうか。

## 今回最終的に出来上がったもの
開いているエディタで右クリック→「AI refactoring」ボタンを押下すると
![スクリーンショット 2023-05-16 23.27.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166628/d36f9813-a06d-f049-2bf1-a95df3b07863.png)

ちょっと待った後でchatGPTがリファクタリングしたコードを返却してくるので、比較ウィンドウで開かれる
![スクリーンショット 2023-05-16 23.28.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166628/5b8dadf5-f8f1-8516-9b04-1daf1c233c58.png)

## 手順

### VScode Extensions開発環境構築

ローカル環境にnode.js実行環境をインストール後、コンソールで
```bash
npm install -g yo generator-code
```
のコマンド実行でvscode拡張機能開発に必要な機能をインストール。インストール完了後

```bash
yo code
```
で雛形プロジェクト作成
雛形プロジェクト作成時に聞かれる質問には適当に答えておきましょう。

[こちら](https://qiita.com/HelloRusk/items/073b58c1605de224e67e)の記事を参考にさせていただきました。

### 雛形プロジェクトの修正

雛形プロジェクトを作成するといくつかファイルが作成されると思いますが、主に編集するファイルは
- extensions.ts
- package.json

の二つです。
extensions.tsには実際の処理内容（今回で言うとテキストエディタ記載の情報をchatGPTのAPIに投げて、返却値を比較ウィンドウで出力する）を記載し、
package.jsonには処理内容をどんなトリガーで呼び出すか、変数の設定（今回で言うとテキストエディタを開いた状態で右クリックしたところに「AI Refactoring」ボタンの設置や、拡張機能の設定からAPIキーの登録など）を記載します。

実際に今回書いたコードは以下の通りです。
```extensions.ts
import * as vscode from 'vscode';
import axios from 'axios';

export function activate(context: vscode.ExtensionContext) {
  console.log('Your extension "myextension" is now active!');
  
  let disposable = vscode.commands.registerCommand('refactorai.refactoring', async () => {
    // APIキーを取得
    const configuration = vscode.workspace.getConfiguration('refactorai');
    const apiKey = configuration.get('apiKey');
    if(!apiKey){
      vscode.window.showInformationMessage("APIKeyが設定されていません。拡張機能からAPI Keyを設定してください。");
      return;
    }
    const editor = vscode.window.activeTextEditor;
    if (editor) {
      const document = editor.document;
      const text = document.getText();
      console.log("text", text);
      //　chatGPTの初期化メッセージ
      console.log('start chatgpt');
      vscode.window.showInformationMessage('chatGPTにリファクタを依頼します');
      const apiEndpoint = "https://api.openai.com/v1/chat/completions";
      const prompt = `以下のソースコードに対してリファクタリングをお願いします。処理効率やバグの少なさ、可読性、サイクロマチック複雑度を考慮してください。修正内容は記載不要です。本文だけ返却してください。本文以外を返却する場合はコメントアウトしてください。
      ${text}`;
      try{
        const requestBody = {
          "model": "gpt-3.5-turbo",
          "messages": [{
            "role": "user",
            "content": prompt
          }],
          "temperature": 0.7
        };
        const header = {
          // eslint-disable-next-line @typescript-eslint/naming-convention
          Authorization: `Bearer ${apiKey}`,
          // eslint-disable-next-line @typescript-eslint/naming-convention
          "Content-Type": "application/json"
        };
        vscode.window.showInformationMessage('chatGPTによるリファクタ実行中です');
        const data = await axios.post(apiEndpoint, requestBody, { headers: header });
        const refactoredText = data.data.choices[0].message.content;
        vscode.window.showInformationMessage('chatGPTによるリファクタが完了しました');
        console.log(data.data.choices);
        const refactoredDocument = await vscode.workspace.openTextDocument({ language: 'plaintext', content: refactoredText });
        const refactoredEditor =  await vscode.window.showTextDocument(document);
        await vscode.commands.executeCommand('vscode.diff', document.uri, refactoredDocument.uri);

      }catch(e){
        console.error(e);
        vscode.window.showInformationMessage('chatGPTにエラーが発生しました。APIキーが適切に設定されているか確認してください');
      }
    }
  });

  context.subscriptions.push(disposable);
}

export function deactivate() {}

```

```package.json
{
  "name": "refactorai",
  "displayName": "RefactorAI",
  "description": "チャットGPTがリファクタリングしてくれます",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.78.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [],
  "main": "./dist/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "refactorai.refactoring",
        "title": "AI refactoring"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "refactorai.refactoring",
          "group": "2_modification",
          "when": "editorTextFocus"
        }
      ]
    },
    "configuration": {
      "type": "object",
      "title": "RefactorAI",
      "properties": {
        "refactorai.apiKey": {
          "type": "string",
          "default": "",
          "description": "Enter your API key"
        }
      }
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run package",
    "compile": "webpack",
    "watch": "webpack --watch",
    "package": "webpack --mode production --devtool hidden-source-map",
    "compile-tests": "tsc -p . --outDir out",
    "watch-tests": "tsc -p . -w --outDir out",
    "pretest": "npm run compile-tests && npm run compile && npm run lint",
    "lint": "eslint src --ext ts",
    "test": "node ./out/test/runTest.js"
  },
  "devDependencies": {
    "@types/glob": "^8.1.0",
    "@types/mocha": "^10.0.1",
    "@types/node": "16.x",
    "@types/vscode": "^1.78.0",
    "@typescript-eslint/eslint-plugin": "^5.59.1",
    "@typescript-eslint/parser": "^5.59.1",
    "@vscode/test-electron": "^2.3.0",
    "eslint": "^8.39.0",
    "glob": "^8.1.0",
    "mocha": "^10.2.0",
    "ts-loader": "^9.4.2",
    "typescript": "^5.0.4",
    "webpack": "^5.81.0",
    "webpack-cli": "^5.0.2"
  },
  "dependencies": {
    "axios": "^1.4.0",
    "openai": "^3.2.1"
  }
}

```

簡単にコードの内容を解説します。
ファイル内で右クリックした時に今回実装した機能を呼び出す設定はpackage.jsonの以下記述になります。

```package.json
    "menus": {
      "editor/context": [
        {
          "command": "refactorai.refactoring",
          "group": "2_modification",
          "when": "editorTextFocus"
        }
      ]
    },
```

extensions.tsの`vscode.commands.registerCommand`で登録した`refactorai.refactoring`を、`editorTextFocus`なのでeditor上で右クリックした時に呼ぶみたいな感じです。右クリックした時に出てくる名称はpackage.jsonの`commands`セクションに定義しています。
APIキーはソースコードに直接記載するのではなく、ユーザーに設定させたかったので,package.jsonの`configuration`セクションに記載しています。
これにより、ユーザーによるAPIキー設定後はextensions.ts上では
```ts
    // APIキーを取得
    const configuration = vscode.workspace.getConfiguration('refactorai');
    const apiKey = configuration.get('apiKey');
```
の形で値を取得できます。

今回、chatGPTのAPIにはaxiosでリクエストを送信していますが、openAIのモジュールを使ってもできそうですが未検証です（[公式サイト](https://platform.openai.com/docs/api-reference/introduction)）。

### 最後に

本拡張機能を作成するにあたり、chatGPT様には大変お世話になりました。