ChatGPTフル活用したらたったの2時間でTS初心者がサクッとVSCode機能拡張を作れた件


### 1.ChatGPTでひとりハッカソン
    
ここ最近、ChatGPTがソフトウェア開発者に非常に有用だということが、特に個人開発者やソフトウェアベンチャーを中心によく言われています。

その理由というのは、アイデアをコードに移す際のハードルがChatGPTのおかげで非常に低くなったのです。

これまでは、アイデアがあっても、調査の段階で挫折したり、新しい言語を学ぶ時間がなくなって、アイデアを実現することができなかったことがありました。

しかし、ChatGPTの登場によって、これらの問題が一気に解決されてしまいました。ChatGPTは、自然言語処理を行うAIで、開発者を様々な形でサポートしています。質問に答えたり、コードの提案をしたり、エラー解決策を提供したりしてくれ、私にとってはまさに救世主のようなAIツールです。

- アイデアが湧いてきたけど、これって実現可能？→ChatGPTに聞いてみる
- 参考にできそうな作品、記事、文献はある？→ChatGPTに聞いてみる
- 新しい言語や環境が必要みたいだけど何が必要？→ChatGPTに聞いてみる
- 環境構築の方法が分からない→ChatGPTに聞いてみる
- とりあえず単機能でプロトタイプ作ってみたい→ChatGPTに聞いてみる
- こんな機能追加したいけど→ChatGPTに聞いてみる
- 動かして見たけどエラーになる→ChatGPTに聞いてみる

ChatGPTに質問すると、動くコードを提案してくれますし、分からないところは答えてくれます。また、エラーが出た場合は、エラーコードを貼り付けるだけで解決策を提案してくれます。

と、こんな感じで今までは色々なツールで調査したり精査したり分析したり悩んだりという時間がChatGPTによって100倍速で解決していき、いつでも思いついたら即ひとりハッカソンができるようになりました。

ChatGPTにはGPT-3.5が使える無料版と、GPT-4が使える有料版(月額$20)があります。人によっては月額2600円(1ドル130円として)は高いと思うかもしれません。しかし、GPT-4は日本語や抽象的な質問に対する回答の精度がGPT-3.5とは段違いで、私は有料版には断然その価値があると考えています。

さて、上でも少し触れましたが、ChatGPTの登場により新しい言語に取り組むハードルが恐ろしく低くなったので、この機会にVSCodeの機能拡張に挑戦してみることにしました。題材としては、ちょうどChatGPTを使って[料理のレシピを提案するLINEチャットbot]([https://qiita.com/__DASHi__/items/2c974e90f900a376f766](https://qiita.com/__DASHi__/items/2c974e90f900a376f766))を作っていた時に思いついたアイデア―Pythonのdocstring(関数コメント)をChatGPTを使って自動で生成する―を実現してみることにします。

VSCodeの既存の機能拡張には既に似たものがありますが、私はDescription(関数や変数の説明)をいちいち考えるのが非常に面倒だったので、ChatGPTを使えばそこまで一気に自動化できるのでは？と思ったのです。試しにWeb版のChatGPTで、こんなプロンプトを試してみると、かなりいい感じでDescriptionも含めて自動生成してくれることが分かりました。

**質問**

> Pythonコードについて、docstringを提案してください。docstringはGoogleスタイルで、DescriptionとArgsとReturnsのみ出力してください。
> 

**回答(GPT-3.5)**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664305/179920f3-152e-06f9-8b21-36af58a82c32.png)

GPT-3.5でも実用に耐えるレベルであることが分かったので、API版でも問題なく実装できそうです。さっそく開発に取り掛かってみます。

さて、さっそく開発に取り掛かってみますと言っても、私はTypeScriptについては「JavaScriptの型指定が付いたやつ？」ぐらいの知識しかありません。そもそもJavaScriptについても自分用のツールを作るぐらいの多少の知識しかありませんので、初心者と大差ないと言ってもいいレベルです。（職業プログラマとして一応25年ほど経験がありますが、恥ずかしいのであまり言いたくない）

ということでこの記事では、TypeScript初心者の私が、VSCodeの機能拡張の作り方からコードの提案、バグ修正、パッケージの作成方法まで、ChatGPTに頼りっきりで1つの機能拡張を作り上げようとするとどうなったかを、お見せしたいと思います。

**結論から言うと、めっちゃ楽で、めっちゃサクサク進んで、気がついたらとりあえず動くものが2時間強でできてしまいました**。控えめに言ってChatGPTとのひとりハッカソン、楽しすぎです。

ここからは、私が実際に取り組んだ手順や、どのようにしてChatGPTを使って機能拡張を作成したか、また、開発中に遭遇した問題や、それをどのように解決したか、さらに、機能拡張をパッケージ化して実際に動くようになった様子まで具体的に紹介していきます。

TypeScript初心者の方でも、この記事を読めばVSCodeの機能拡張を作成することができるようになると思いますし、ChatGPTを使えば、初心者でも簡単に新しい言語に取り組むことができることが分かっていただけると思います。
    

## 2. 必要なツールと環境をセットアップする

まずはVSCode機能拡張を作るにあたり、必要なツールと環境を揃えなくてはいけません。

ChatGPTにとりあえず聞いてみることにしました。

**質問**

> windowsでのVSCode拡張機能の作り方を教えてください。
> 

**回答(GPT-4)**

> Visual Studio Code (VSCode) の拡張機能を Windows で作成する方法を以下の手順で説明します。
> 
> 1. 必要なツールをインストールする:
>     - Node.js: **[https://nodejs.org/ja/download/](https://nodejs.org/ja/download/)** からダウンロードしてインストールしてください。LTS版が推奨されます。
>     - Visual Studio Code: **[https://code.visualstudio.com/download](https://code.visualstudio.com/download)** からダウンロードしてインストールしてください。
> 2. Yeoman と VS Code Extension Generator をインストールする:
> コマンドプロンプトまたは PowerShell を開いて、以下のコマンドを実行してください。
>     
>     ```powershell
>     npm install -g yo generator-code
>     ```
>     
> 3. 拡張機能のプロジェクトを作成する:
> 適当なディレクトリを作成し、そのディレクトリでコマンドプロンプトまたは PowerShell を開いて、以下のコマンドを実行してください。
>     
>     ```powershell
>     yo code
>     ```
>     
> 4. プロジェクト設定を行う:
> 生成されたプロンプトに従って、拡張機能の設定を行います。基本的にデフォルトの設定で問題ありませんが、拡張機能名、説明、言語などを入力します。
> 5. 生成されたプロジェクトを VSCode で開く:
>     
>     ```powershell
>     code .
>     ```
>     
> 6. 拡張機能のコードを編集する:
> 生成されたプロジェクトには、サンプルコードが含まれています。`src/extension.ts` ファイルを開いて、拡張機能の実装を行ってください。拡張機能のAPIについては、公式ドキュメントを参照してください: **[https://code.visualstudio.com/api](https://code.visualstudio.com/api)**
> 7. 拡張機能をデバッグする:
> VSCode のデバッグビューを開き、デバッグボタン（緑色の三角アイコン）をクリックして拡張機能をデバッグ実行します。デバッグが開始されると、新しい VSCode ウィンドウが開き、開発中の拡張機能が読み込まれます。
> 8. テストを書く (オプション):
> 拡張機能の動作を保証するために、テストを書くことが推奨されます。`test/extension.test.ts` ファイルを開いて、拡張機能のテストを実装してください。公式ドキュメントを参照して、テスト方法を学んでください: **[https://code.visualstudio.com/api/working-with-extensions/testing-extension](https://code.visualstudio.com/api/working-with-extensions/testing-extension)**
> 9. 拡張機能をパッケージ化する:
> 拡張

ChatGPT、めっちゃ親切に教えてくれます。まずはNode.jsをインストールして、と。VSCodeは私の場合は既にインストールしてあるのでスキップします。次にVS Code Extension Generatorというのが必要なので指示された通りにコマンドを打ち込んで（実際はコピーアンドペースト）インストールします。あとはディレクトリを作成したりサンプルコードを眺めたり、と。悩む間もなくあっという間に環境構築が完了しました。びっくりするほどあっけなかった。。。

Node.jsとかnpmとかyoとかWindows系業務システム開発者のワタシには正直ちんぷんかんぷん（恥）なのですが、とりあえずサクサクと進めていけるのがいいですね。（後でおさらいしました）

いつもだと、自分の環境に合った手順の記事を見つけるまでが一苦労、とか、参考にした記事が古かった、とかでこの辺りで早々にハマることが多いのですが、ChatGPTの回答は自分の環境に合った手順をまとめてくれるのでメチャクチャ時短になります。

ChatGPTの弱点は最新の情報に弱いことですが、GPT-4になってほとんど問題にならなくなりました。GPT-4でもここ1年ぐらいの情報は含まれていません（GPT-4は2021年9月までの情報しか学習していない）が、超最新情報が欲しいときはそこは適宜Bing AIなどで補足すれば良いとしましょう。

## 3.  VSCode拡張機能のサンプルコードを動かしてみる

では、次は環境構築が正しくできていることの確認も含めて、サンプルコードをそのまま実行して問題なく動作することを確認してみます。

上のChatGPTが提示してくれた手順から、いったん**手順7**にジャンプして、未編集のサンプルコードを実行してみます。ソースコードをざっと眺めてみると、こんなコードが見つかります。

```tsx
export function activate(context: vscode.ExtensionContext) {
	console.log('Congratulations, your extension "test-extension" is now active!');
	let disposable = vscode.commands.registerCommand('test-extension.helloWorld', () => {
		vscode.window.showInformationMessage('Hello World from test-extension!');
	});
	context.subscriptions.push(disposable);
}
```

どうやらHello, WorldがどこかのWindowに表示されるようです。

さて、緑色のデバッグボタン（F5キーでも可）で実行してみると、`拡張機能開発ホスト`という新しいWindowが開かれましたが、特にそれ以上のことは起きないようです。コンソールにも`Congratulations, your extension "test-extension" is now active!`というメッセージが表示されるはずですがその気配がしません。もう一回ChatGPTに聞いてみましょう。

**質問**

> VSCode拡張機能をデバッグ実行して、`拡張機能開発ホスト`ウィンドウが開かれました。その後なにをしたら拡張機能が実行されますか。
> 

**回答(GPT-4)**

> Visual Studio Codeの拡張機能開発をデバッグ実行し、拡張機能開発ホストウィンドウが開いた状態で、拡張機能を実行する方法は次の通りです。
> 
> 1. コマンドパレットを開く: コマンドパレットは`Ctrl+Shift+P`（Windows/Linux）または`Cmd+Shift+P`（Mac）のショートカットキーで開くことができます。
> 2. 拡張機能のコマンドを実行する: コマンドパレットが開いたら、拡張機能で定義されたコマンドを入力して実行します。これにより、拡張機能の機能が実行されます。拡張機能によっては、特定のファイルタイプを開いている状態でコマンドを実行する必要がある場合もあります。
> 3. 以下略

なるほど、コマンドパレットから機能拡張を入力して実行するようです。

まあ言われればそれしかないんですが、一人でやってる時ってこういうどうでもいい事でつまずいたりするものです。そして手がパタリと止まってしまったりする。でも、ChatGPTがあればざっくりと聞けばだいたい答えてくれるし、答えが抽象的ならもう一段階具体的に質問すればいいだけです。この **「手が止まらない」というのが飽きっぽい私には非常に大きな強み** になります。

実際にコマンドパレットに入力してみると、`Hello World`がありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664305/20ff784b-6c46-42f0-d6fd-1018492d69de.png)

実行してみると、画面右下のメッセージボックスに`Hello World from test-extension!`という文字が表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664305/184667c6-43ce-07cd-4ff3-b4a60d96cfe7.png)

これで、開発環境が正しくインストールできたことが検証できました。

ここまで正味15分程度、このスピード感は病みつきになります。

## 4. サンプルコードを少しだけ改造してみる

次に、サンプルコードに慣れる意味も含めて、少しだけ改造してみることにします。改造ポイントは以下の2点。あんまり一気にやるとつまづきポイントが分かりにくくなるので、スモールステップで行きましょう。

- ショートカットキーで起動できるようにする
- カーソルのある位置のテキストを取得する

環境構築のおさらいも兼ねて、新しいプロジェクトを作ります。そして、ショートカットキーで起動できるようにコードを追加します。はて、ショートカットキーの設定はどのようにしてコーディングすればいいのか。。。そうだ、ChatGPTに聞こう。

**質問**

> VSCodeの拡張機能を開発しています。ショートカットキーで拡張機能を起動できるようにしたいのですが、設定はどこに記述したらいいですか。
> 

**回答(GPT-4)**

> Visual Studio Codeの拡張機能でショートカットキーを設定するには、`package.json`ファイルの`contributes`セクションに`keybindings`を追加して記述します。
> 
> 
> 以下は、`package.json`ファイルでショートカットキーを設定する例です:
> 
> 中略
> 
> "keybindings": [
>   {
>     "command": "your-extension-name.yourCommand",
>     "key": "ctrl+shift+t",
>     "mac": "cmd+shift+t",
>     "when": "editorTextFocus"
>   }
> ]
> 

ふむふむ、`package.json`ファイルに`keybindings`という項目を追加して、`command`には[拡張機能名].[コマンド名]、`key`にショートカットキーを設定するようです。

ChatGPTを使うようになって、手詰まりになった時の解決方法が「ググる→検索結果から自分の環境にあった記事を探す→記事の中から必要な箇所を探す→実装する」という作業が「ChatGPTに質問する→答えをコピペする」と段違いにスピードアップしました。

今回は既存のAutoDocString拡張機能と同じCtrl-Shift-2で起動したいと思い、こんな感じに設定してみました。

```json
"keybindings": [
      {
        "command": "autodocstring-chatgpt.helloWorld",
        "key": "ctrl+shift+2",
        "mac": "cmd+shift+2",
        "when": "editorTextFocus"
      }
    ]
```

次に、カーソル位置のテキストを取得するコードを書いていきます。これもChatGPTに丸投げしましょう。

**質問**

> VSCodeの拡張機能を開発しています。拡張機能が起動された時に、カーソルのある行のテキストを取得するTypeScriptのコードを教えてください。> 

**回答(GPT-4)**

> Visual Studio Code拡張機能で、カーソルのある行のテキストを取得するには、以下のTypeScriptコードを使用できます。このコードは、拡張機能の`activate`関数内やコマンドの実行処理部分に追加してください。
> 
> ```
> function getCurrentLineText() {
>   const editor = vscode.window.activeTextEditor;
> 
>   if (editor) {
>     const document = editor.document;
>     const position = editor.selection.active;
>     const line = document.lineAt(position.line);
>     const lineText = line.text;
> 
>     // カーソルのある行のテキストを取得
>     console.log(lineText);
>   }
> }
> ```

なるほど、`エディタインスタンス→ドキュメントインスタンス→カーソル位置→カーソル位置から行番号→行番号の示す位置のテキストを取得`、とこういう順番で実装できるんですね。しかも関数化してくれているので、そのままコピペで動いてしまうという、なんて気の利いたAIなんだ。。。

実際にそのまま貼り付けてみました。activate関数内でこの関数を呼び出すようにして、デバッグ実行してみると、エディタ内でCtrl-Shift-2を押したタイミングで無事にデバッグコンソールにカーソル位置の行のテキストが表示されました。どんどん動くコードが書けているぞ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664305/ab2a6744-78e2-02a8-8c3c-8c5569a3b7c9.png)

## 5. 機能を実装する

さて、機能拡張開発の雰囲気はなんとなくつかめたので、欲しい機能を実装していきます。最終的に実現したいことをブレイクダウンすると、

- カーソル位置のPython関数の全体を取得する（カーソルの上下を見る必要がある）
- OpenAI APIに指示と関数を投げる
- 返ってきた結果からdocstring部分を抽出
- Python関数の定義直下の行にdocstringを流し込む

こんな感じでできそうです。

### 5-1. 下書きをChatGPTに書いてもらう

まずはChatGPTに下書きを書いてもらいます。

**質問**

> 「カーソル位置のPython関数をOpenAI APIに送信して、受信したメッセージからdocstring部分を取り出して関数定義直下に挿入する」VSCode機能拡張のTypeScriptコードを教えてください。
> 

**回答(GPT-4)**

> まず、OpenAI APIを利用するために、`@openai/api`パッケージをインストールしてください。ターミナルで以下のコマンドを実行してインストールします。
> 
> 
> ```
> npm install @openai/api
> ```
> 
> 次に、以下のTypeScriptコードを使って、指定された機能を実装するVSCode拡張機能を作成します。このコードは、カーソル位置のPython関数を取得し、OpenAI APIを使用してdocstringを生成し、関数定義の直下に挿入します。
> 
> ```tsx
> import * as vscode from 'vscode';
> import { createApi, CreateCompletionRequest } from '@openai/api';
> 
> const openai = createApi({ apiKey: 'your_openai_api_key' });
> 
> async function generateDocstring(functionText: string): Promise<string> {
>   const prompt = `Generate a docstring for the following Python function:\n\n${functionText}\n\nDocstring:`;
> 
>   const completionRequest: CreateCompletionRequest = {
>     engine: 'davinci-codex',
>     prompt: prompt,
>     max_tokens: 100,
>     n: 1,
>     stop: null,
>     temperature: 0.5,
>   };
> 
>   const result = await openai.completions.create(CreateCompletionRequest);
>   const docstring = result.choices[0].text.trim();
>   return docstring;
> }
> 
> async function insertDocstring() {
>   const editor = vscode.window.activeTextEditor;
> 
>   if (editor) {
>     const document = editor.document;
>     const position = editor.selection.active;
>     const line = document.lineAt(position.line);
>     const functionText = line.text;
> 
>     try {
>       const docstring = await generateDocstring(functionText);
>       const newPosition = position.with(position.line + 1, 0);
>       editor.edit((editBuilder) => {
>         editBuilder.insert(newPosition, `\n${docstring}\n`);
>       });
>     } catch (error) {
>       vscode.window.showErrorMessage('Error generating docstring: ' + error.message);
>     }
>   }
> }
> 
> export function activate(context: vscode.ExtensionContext) {
>   let disposable = vscode.commands.registerCommand('extension.insertDocstring', () => {
>     insertDocstring();
>   });
> 
>   context.subscriptions.push(disposable);
> }
> 
> export function deactivate() {}
> 
> ```
> 
> このコードでは、`generateDocstring`関数がOpenAI APIを使用してdocstringを生成し、`insertDocstring`関数がカーソル位置のPython関数を取得し、生成されたdocstringを関数定義の直下に挿入します。
> 
> **注意**: `your_openai_api_key`を実際のOpenAI APIキーに置き換えてください。APIキーは、**[OpenAIのダッシュボード](https://beta.openai.com/signup/)**で取得できます。
> 
> また、このコードはPython関数の取得と解析を簡略化しています。実際の状況では、より堅牢な方法で関数を検出・解析する必要があります。
> 

ものの数十秒でコードを書いてきました。私がど初心者なことを見抜いて注意事項まで付記してくれてます。なんて思いやりのあるAIだ。。。

コードをざっと見た感じそのままでは希望する動作とはちょっと異なるのでいくらか修正は必要そうです。でも、それぞれを追加の質問で追い込んでいけばあっという間に完成しそうです。

### 5-2. 最新情報を確認する

まずは、`@openai/api`パッケージがGPT-3.5に対応しているかを確認します。最新情報を見たい場合はBing AIに聞くのがいいですね。

**質問**

> openai/apiパッケージはgpt-3.5に対応していますか。
> 

**回答(Bing AI:厳密)**

> はい、node.jsのopenai/apiパッケージはgpt-3.5に対応しています。OpenAI公式のAPI Referenceには、Node.js用のサンプルが掲載されています。また、gpt-3.5-turboというモデルがあり、チャットに最適化されていますが、従来の完了タスクにも適しています。
> 

参照元に指定されたWeb記事やopenaiパッケージのマニュアルを参考に、若干の変更で実装できそうです。

修正**前**

```tsx
const completionRequest: CreateCompletionRequest = {
    engine: 'davinci-codex',
    prompt: prompt,
    max_tokens: 100,
    n: 1,
    stop: null,
    temperature: 0.5,
  };

  const result = await openai.completions.create(CreateCompletionRequest);
  const docstring = result.choices[0].text.trim();
```

**修正後**

```tsx
const result = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages: [{ role: "user", content: prompt }],
		temperature: 0.0
	});
	const docstring = result.data.choices[0].message.content.trim();
  return docstring;
```

あと、temperature（ランダム性）はツールとして使う時は0.0がいいと思います。

### 5-3. 指示が曖昧だった部分を修正する

次に、カーソル位置のPython関数を取得する部分のコードの修正が必要そうです。私の指示があまりにもざっくりすぎてChatGPTは`カーソル行の位置にPython関数の定義(def)があると決めつけたコード`を書いてきました。

そこで、もうちょっと詳しく指示を与えてみます。

**質問**

> VSCode拡張機能を開発しています。以下の機能を実現するTypeScriptのコードを教えてください。
> 
> - カーソル位置を検出する
> - カーソルはPython関数内の任意の位置に存在すると仮定する
> - カーソル位置にあるPython関数の関数全体を取得して変数functionTextに格納する

**回答からコード部分を抜粋(GPT-4)**

```tsx
const pythonFunctionPattern = /(def)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*:/g;

const cursorPosition = editor.selection.active;

let functionText: string | null = null;

for (let lineNumber = cursorPosition.line; lineNumber >= 0; lineNumber--) {
  const line = document.lineAt(lineNumber).text;
  const match = pythonFunctionPattern.exec(line);

  if (match) {
    const startPosition = new vscode.Position(lineNumber, match.index);
    const endPosition = document.positionAt(document.offsetAt(new vscode.Position(lineNumber, line.length)) + 1);
    const functionRange = new vscode.Range(startPosition, endPosition);
    functionText= document.getText(functionRange);
    break;
  }
}
```

おお、なんか正規表現を使ったそれっぽいコードが返ってきました。変数名などちょっとの変更で使えそうです。

**修正前**

```tsx
const position = editor.selection.active;
const line = document.lineAt(position.line);
const functionText = line.text;
```

**修正後**

```tsx
const pythonFunctionPattern = /(def)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*:/g;

const position = editor.selection.active;

let functionText: string | null = null;

for (let lineNumber = position.line; lineNumber >= 0; lineNumber--) {
  const line = document.lineAt(lineNumber).text;
  const match = pythonFunctionPattern.exec(line);

  if (match) {
    const startPosition = new vscode.Position(lineNumber, match.index);
    const endPosition = document.positionAt(document.offsetAt(new vscode.Position(lineNumber, line.length)) + 1);
    const functionRange = new vscode.Range(startPosition, endPosition);
    functionText= document.getText(functionRange);
    break;
  }
}
```

### 5-4. 想定外のパターンにも対応できるようにする

さらに、OpenAIからのレスポンスに、docstring以外の解説が含まれる可能性があるので、それを排除したいと思います。

docstringは`’’’`もしくは`”””`を使った正規表現で抽出できそうです。ChatGPTではダブルクォーテーションで返ってくるようですが、とりあえずシングルクォーテーションにも対応しておきましょう。

**質問**

> TypeScriptで、文字列変数functionTextからPythonのdocstringを抽出するコードを教えてください。docstringを囲んでいるトリプルクォーテーションも含めて抽出してください。
> 

**回答からコード部分を抜粋(GPT-4)**

```tsx
function extractPythonDocstring(functionText: string): string | null {
  const regex = /('''[\s\S]*?'''|"""[\s\S]*?""")/;
  const match = functionText.match(regex);

  if (match === null) {
    return null;
  }

  return match[0];
}
```

（何度かプロンプトは微調整しましたが）この関数で抽出できそうです。とりあえず採用としましょう。

**修正前**

```tsx
const docstring = result.choices[0].text.trim();
```

**修正後**

```tsx
const docstring = extractPythonDocstring(result.choices[0].text);
(中略)
function extractPythonDocstring(functionText: string): string | null {
  const regex = /('''[\s\S]*?'''|"""[\s\S]*?""")/;
  const match = functionText.match(regex);

  if (match === null) {
    return null;
  }

  return match[0];
}
```

## 6. 拡張機能をデバッグする

とりあえず必要な機能は揃った様子なので、コードを組み上げてデバッグしてみます。

デバッグしていて、エラーが出たらそのままChatGPTに貼り付ければ、大抵のエラーの解決法は教えてくれます。

デバッグ中に気づいた点についても、その都度ChatGPTに相談しながら、じゃんじゃん改善していけるのがいいですね。結果として、当初は考えていなかった以下のような実装を含めることにしました。

- 動作中のメッセージを表示（OpenAI APIからの返信に10数秒かかるため）
- docstringにインデントを追加する処理（ChatGPTから返ってきたdocstringにインデントが含まれていないケースがあった）
- 細かな修正

### コード全容

package.json

```tsx
{
  "name": "autodocstring-chatgpt",
  "displayName": "autodocstring-chatgpt",
  "description": "generate docstring with auto description via openai api",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.76.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "autodocstring-chatgpt.insertDocstring",
        "title": "Generate python docstring with ChatGPT"
      }
    ],
    "keybindings": [
      {
        "command": "autodocstring-chatgpt.insertDocstring",
        "key": "ctrl+shift+3",
        "mac": "cmd+shift+3",
        "when": "editorTextFocus"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "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.76.0",
    "@typescript-eslint/eslint-plugin": "^5.56.0",
    "@typescript-eslint/parser": "^5.56.0",
    "@vscode/test-electron": "^2.3.0",
    "eslint": "^8.36.0",
    "glob": "^8.1.0",
    "mocha": "^10.2.0",
    "typescript": "^4.9.5"
  },
  "dependencies": {
    "openai": "^3.2.1"
  }
}
```

extension.ts

```tsx
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code belowimport * as vscode from 'vscode';
import { Configuration, OpenAIApi } from 'openai';

const configuration = new Configuration({
  apiKey: 'YOUR-API-KEY',
});
const openai = new OpenAIApi(configuration);

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('autodocstring-chatgpt.insertDocstring', insertDocstring);
	
	context.subscriptions.push(disposable);
}

function extractPythonDocstring(functionText: string): string | null {
  const regex = /('''[\s\S]*?'''|"""[\s\S]*?""")/;
  const match = functionText.match(regex);

  if (match === null) {
    return null;
  }

  return match[0];
}

async function generateDocstring(functionText: string): Promise<string> {
  console.log('generateDocstring()');
	const prompt = `以下のPythonコードについて、Googleスタイルのdocstringを提案してください。
	Description, Args, Returnsのみ出力してください。Descriptionは日本語で出力してください。
	import文は出力しないでください。\n${functionText}:`;
  console.log(prompt);

	const result = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages: [{ role: "user", content: prompt }],
		temperature: 0.0
	});

	console.log(result);
	if (result?.data?.choices === undefined || result.data.choices.length === 0  || result.data.choices[0].message === undefined) {
		return '';
	}
	const resultContent = extractPythonDocstring(result?.data?.choices[0].message.content.trim()) || '';

	const regex = /\s*"""([\s\S]*?)"""/;
	const match = regex.exec(resultContent);

	let docstring = '';
	if (match) {
		const tabSize = vscode.workspace.getConfiguration().get('editor.tabSize') as number;
		const indent = ' '.repeat(tabSize);
		if (match[0][0] === indent) {
			docstring = match[0] + '\n';
		} else {
			const regexNewLine = /(\n)/g;
			docstring = indent + match[0].replace(regexNewLine, '$1' + indent) + '\n';
		}
	}

	console.log(docstring);

  return docstring || '';
}

function extractFunctionText(document: vscode.TextDocument, cursorPosition: vscode.Position): [vscode.Position, string]  {
	const pythonFunctionPattern = /(def)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*:/g;

	for (let lineNumber = cursorPosition.line; lineNumber >= 0; lineNumber--) {
		const line = document.lineAt(lineNumber).text;
		const match = pythonFunctionPattern.exec(line);
	
		if (match) {
			const startPosition = new vscode.Position(lineNumber, match.index);
			const endPosition = document.positionAt(document.offsetAt(new vscode.Position(lineNumber, line.length)) + 1);
			const functionRange = new vscode.Range(startPosition, endPosition);
			return [startPosition, document.getText(functionRange)];
		}
	}	
	return [cursorPosition, ''];
}

async function insertDocstring() {
  const editor = vscode.window.activeTextEditor;
	
  if (editor) {
    const document = editor.document;
    const position = editor.selection.active;
    const [newPosition, functionText] = extractFunctionText(document, position);
		const insertPosition = position.with(newPosition.line + 1, 0);
		const decorationType = vscode.window.createTextEditorDecorationType({
			after: {
				contentText: '',
				color: 'rgba(255, 0, 0, 1)'
			},
			rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed
		});
		vscode.window.showInformationMessage('docstring生成中...');
    try {
			const docstring = await generateDocstring(functionText);
			if (docstring !== '') {
				editor.edit((editBuilder) => {
					editBuilder.insert(insertPosition, `${docstring}`);
				});
				vscode.window.showInformationMessage('docstring生成完了.\n' + docstring);
			} else {
				vscode.window.showErrorMessage('docstring生成エラー.');
			}
    } catch (error:	any) {
      vscode.window.showErrorMessage('docstring生成エラー.' + error.message);
    }
  }
}

// This method is called when your extension is deactivated
export function deactivate() {}
```

コメントも何も書いていない自分専用コードですが、環境構築からここまで約2時間で書き上がりました。これまでは集中力を途切れさせずにここまで一気にできたことがなかったので感動！しています。

## 7. パッケージを生成してインストールする

最後に、パッケージを生成し、インストールしてみます。（公開はしません）

これもChatGPTに教えてもらいましょう。

**質問**

> 拡張機能をパッケージ化する方法を教えてください
> 

**回答(GPT-4)**

> 
> 
> 
> 拡張機能のパッケージ化は、Visual Studio Code Extension Manager (vsce) を使用して行います。以下の手順で拡張機能をパッケージ化できます。
> 
> 1. VSCE をインストールする:
> コマンドプロンプトまたは PowerShell を開いて、以下のコマンドを実行してください。
>     
>     ```
>     npm install -g vsce
>     ```
>     
> 2. 拡張機能プロジェクトのディレクトリに移動する:
> コマンドプロンプトまたは PowerShell で、拡張機能プロジェクトのルートディレクトリに移動してください。
> 3. package.json を更新する:
> 必要に応じて、`package.json` ファイルを編集して拡張機能の情報を更新してください。特に、`displayName`、`description`、`version`、`publisher`、`repository` などのフィールドを確認してください。
> 4. 拡張機能をパッケージ化する:
> 拡張機能プロジェクトのルートディレクトリで、以下のコマンドを実行して拡張機能をパッケージ化します。
>     
>     ```
>     vsce package
>     ```
>     
>     成功すると、`.vsix` ファイルが生成されます。このファイルは、拡張機能のインストールパッケージです。
>     
> 5. ローカルで拡張機能をインストールする (オプション):
> 拡張機能をローカル環境でテストする場合、VSCode で以下の手順を実行してインストールします。
>     - [Ctrl + Shift + X] を押して拡張機能タブを開く
>     - 右上のアイコンのうち、歯車アイコンの横にある「...」をクリック
>     - 「VSIX からインストール」を選択
>     - 生成された `.vsix` ファイルを選択

この通りに実行したところ、エラーが出てしまいました。エラーをそのままChatGPTに貼り付けて聞いてみます。

**質問**

> ERROR Make sure to edit the [README.md](http://readme.md/) file before you package or publish your extension.
> 

**回答**

> このエラーは、`README.md` ファイルがデフォルトのままで、拡張機能に関する情報が更新されていないことを示しています。拡張機能をパッケージ化する前に、`README.md` ファイルを編集して拡張機能の説明、使い方、必要な設定などの情報を追加してください。
> 
> 1. `README.md` を開く:
> 拡張機能プロジェクトのルートディレクトリにある `README.md` ファイルを開きます。
> 2. 拡張機能に関する情報を追加する:
> デフォルトの内容を削除し、以下のような情報を記載してください。
>     - 拡張機能の名前
>     - 拡張機能の機能概要
>     - 使い方や設定方法
>     - 依存関係や互換性に関する情報

なるほど、README.mdファイルをきちんと書かないとパッケージ化できないというエラーですね。今回は自分用なので最低限の編集だけしておきましょう。

改めて、パッケージを作成した後、ローカルで拡張機能をインストールしてみます。

動作確認。

![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664305/3d35859f-0d90-3d16-4d20-8da46acf8ea1.gif)

キーボード操作でコマンドが起動されると、画面右下にメッセージが表示され、その後自動生成されたdocstringが関数定義のすぐ下に挿入される、という動作になっています。

APIの回答が返ってくるまで10秒ぐらい待ち時間がありますが、それでも面倒なコメント書きがずいぶんと楽になりました！

## 8. まとめと今後の学び

今回は、ChatGPTを活用して全く未経験の言語で単機能アプリ（機能拡張）を作成してみるというチャレンジをしてみました。

個人開発者で、アイデアメモが貯まりに貯まっている人、仕掛り中のアプリが積もり積もっている人はぜひ、ChatGPTを使い倒してガシガシ開発しちゃうことをおすすめします。

ちなみに私のオススメのChatGPTの使い方は、ChatGPT Plusに課金して、基本GPT-4で使うこと、単発の質問などはBing AIとpoe.comも併用してGPT-4の待ち時間を有効活用するという方法です。

ChatGPTを活用したひとりハッカソン、超おすすめですよ！

## 9. 参考文献

https://zenn.dev/kazuwombat/articles/5617468b97fa3f

https://qiita.com/Tyamamoto1007/items/07445f78a4b396666478

https://qiita.com/youtoy/items/b3e2e462029bf40f8a1a

https://www.npmjs.com/package/openai