会社の部内イベントでchatGPTを使ったハッカソンが開催されたので、VSCode拡張機能の勉強がてらソースコードをリファクタリングしてくれるプラグインを自作してみました。いいアイデアがなかったのですが、他のチームはWebアプリを実装してくる読みで、独自性を見出すためにVSCode拡張に手を出しました。
VSCode Extensionsについて
ご存知の方も多いと思いますが、VSCodeは拡張機能を入れることでさらに日々の開発体験を向上することができます。
おすすめプラグインは色々な記事でも紹介されていますが、我々の開発では
- Jest Runner
- Jest Snippets
あたりはよく使っていますね。(最近vitestが台頭してきたので別のプラグインも探さなきゃな、、)
VSCode拡張機能は自作できる
ここからが本題です。VScodeプラグインは自作できます。
今回開発してみて思ったより難しくないことが分かりました。
TypeScriptで記述できるので、みなさんも遊び感覚で開発してみてはいかがでしょうか。
今回最終的に出来上がったもの
開いているエディタで右クリック→「AI refactoring」ボタンを押下すると
ちょっと待った後でchatGPTがリファクタリングしたコードを返却してくるので、比較ウィンドウで開かれる
手順
VScode Extensions開発環境構築
ローカル環境にnode.js実行環境をインストール後、コンソールで
npm install -g yo generator-code
のコマンド実行でvscode拡張機能開発に必要な機能をインストール。インストール完了後
yo code
で雛形プロジェクト作成
雛形プロジェクト作成時に聞かれる質問には適当に答えておきましょう。
こちらの記事を参考にさせていただきました。
雛形プロジェクトの修正
雛形プロジェクトを作成するといくつかファイルが作成されると思いますが、主に編集するファイルは
- extensions.ts
- package.json
の二つです。
extensions.tsには実際の処理内容(今回で言うとテキストエディタ記載の情報をchatGPTのAPIに投げて、返却値を比較ウィンドウで出力する)を記載し、
package.jsonには処理内容をどんなトリガーで呼び出すか、変数の設定(今回で言うとテキストエディタを開いた状態で右クリックしたところに「AI Refactoring」ボタンの設置や、拡張機能の設定からAPIキーの登録など)を記載します。
実際に今回書いたコードは以下の通りです。
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() {}
{
"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の以下記述になります。
"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上では
// APIキーを取得
const configuration = vscode.workspace.getConfiguration('refactorai');
const apiKey = configuration.get('apiKey');
の形で値を取得できます。
今回、chatGPTのAPIにはaxiosでリクエストを送信していますが、openAIのモジュールを使ってもできそうですが未検証です(公式サイト)。
最後に
本拡張機能を作成するにあたり、chatGPT様には大変お世話になりました。