✌️

コードレビューのためのコメント評価機能の作成

に公開

この記事はMisskey.devアドベントカレンダー1日目、Vim駅伝2025年12月01日分の記事です。

NeoVim上でソースコードのコメントを評価できる機能を作成してみました。
完成したプラグイン、ソースコードはGitHubにあります。
思いつきで作ってみたものですので粗があるかと思います。

はじめに

昨今、コーディング補助AIの発展は目覚ましく、コードレビューの依頼から部分的なコーディング作業の置き換え、Vibe Codingによるコーディングの自動化まで、様々な場面で使われています。

しかし、AIにコードを書かせると、不要なコード、コメントを大量に書き込まれることがよくあります。特にコメントはかなり細かく挿入されるため、かえってコードが読みづらくなってしまい、人間が一つ一つチェックして削除、書き直しを行うという事態になりがちです。

そこで、コードリーディング中にコメントに対して評価を行い、後で評価状況を確認できるような機能を作成しました。

イメージとしてはダークソウルのメッセージ評価です。

要件

  • コメント行でポジティブ/ネガティブ評価ができる
  • コメントにカーソル合わせると、ポップアップで評価が表示される
  • バッファ中のコメントの評価の一覧を表示できる

実現方法

今回はNeoVimのプラグインをLuaで作成します。

コメント行でポジティブ/ネガティブ評価をする

コメント上にカーソルがある状態で<Leader>bでポジティブ評価、<Leader>qでネガティブ評価をすることができます。

キーマップはいつもの通りに登録しています。

-- Key mappings
-- <Leader>b for positive rating
vim.keymap.set('n', '<Leader>b', function()
  comment_bp.add_rating('b')
end, { silent = true, desc = 'Add positive rating to comment' })

-- <Leader>q for negative rating
vim.keymap.set('n', '<Leader>q', function()
  comment_bp.add_rating('p')
end, { silent = true, desc = 'Add negative rating to comment' })

マップにb, qを割り当てているのは、サムズアップとサムズダウンを意識してのものです。
ネガティブ評価はpに割り当てたかったのですが、ペーストと重複するためqにしています。

コメントは正規表現で抽出しています。ファイル形式を見てどの正規表現を用いるか判断しています。

-- Get comment patterns based on filetype
local function get_comment_patterns()
  local ft = vim.bo.filetype
  local patterns = {}

  if ft == 'c' or ft == 'cpp' or ft == 'java' or ft == 'javascript' or ft == 'typescript' then
    patterns = {
      '^%s*//(.*)$',
      '^%s*/%*(.*)%*/$'
    }
  elseif ft == 'python' or ft == 'sh' or ft == 'bash' or ft == 'ruby' then
    patterns = { '^%s*#(.*)$' }
  elseif ft == 'vim' then
    patterns = { '^%s*"(.*)$' }
  elseif ft == 'lua' then
    patterns = { '^%s*%-%-(.*)$' }
  else
    patterns = {
      '^%s*//(.*)$',
      '^%s*/%*(.*)%*/$',
      '^%s*#(.*)$',
      '^%s*"(.*)$',
      '^%s*%-%-(.*)$'
    }
  end

  return patterns
end

評価集計は別ファイルに持つのではなく、コメント末尾に直接埋め込むようにします。コメント末尾にb:3, p:0という形で挿入されます。

local new_rating = ' ' .. format_rating(rating.b, rating.p)

if rating.found then
	local replaced = false

    if line:match(patterns.emoji) then
	    line = line:gsub(patterns.emoji, new_rating)
        replaced = true
    end

    if not replaced and line:match(patterns.simple) then
        line = line:gsub(patterns.simple, new_rating)
        replaced = true
    end
else
    line = line:gsub('%s*$', '')

    if line:match('%*/$') then
	    line = line:gsub('%s*%*/$', new_rating .. ' */')
    else
        line = line .. new_rating
    end
end

なお、コメントに埋め込まれた方の評価集計はconceal機能で見えなくしています。

local function setup_conceal()
  local bufnr = vim.api.nvim_get_current_buf()
  local ft = vim.bo.filetype

  local supported = vim.tbl_contains(
    {'python', 'c', 'cpp', 'vim', 'lua', 'sh', 'bash', 'ruby', 'java', 'javascript', 'typescript'}, ft)

  if not supported then
    return
  end

  if conceal_matches[bufnr] then
    for _, match_id in ipairs(conceal_matches[bufnr]) do
      pcall(vim.fn.matchdelete, match_id)
    end
  end
  conceal_matches[bufnr] = {}

-- matchaddのConcealを正規表現で指定する
  local match_id = vim.fn.matchadd('Conceal', [[\s\+b:\d\+,\s*p:\d\+]], 10, -1, {conceal = ''})
  table.insert(conceal_matches[bufnr], match_id)
 
  if vim.wo.conceallevel == 0 then
    vim.wo.conceallevel = 2
  end

  if vim.wo.concealcursor == '' then
    vim.wo.concealcursor = 'nc'
  end
end

ポップアップで評価を表示する

ポップアップで評価を表示する際は自作のNeoVimのFloating WindowでPopup Menuを作ってみたからポップアップ表示部分を抜き出し、popup_infoとして使用しています。

popup_info.popup_info(text, {
      relative = 'editor',
      row = row,
      col = col,
      width = width,
      height = 1,
      border = 'rounded',
      title = 'Rating',
      zindex = 200,
      timeout = 3000
})

表示は以下のようになります。
fig_1

評価の一覧を表示する

現在のバッファ中のコメントの評価について一覧を表記します。

function M.show_rating_list()
  local qf_list = {}
  local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)

  for line_num, line in ipairs(lines) do
    if is_comment_line(line) then
      local rating = extract_rating(line)
      local text

      if rating.found then
        text = string.format('%s | %s', format_rating(rating.b, rating.p), line)
      else
        text = string.format('No rating | %s', line)
      end

      table.insert(qf_list, {
        lnum = line_num,
        text = text
      })
    end
  end

  vim.fn.setqflist(qf_list)

  if #qf_list == 0 then
    print('No comments found')
  else
    vim.cmd('copen')
    print(string.format('Found %d comments', #qf_list))
  end
end

一覧はQuickFixを使用して表示しています。

:CommentBPListコマンドを実行することで以下のように表示されます。
fig_2

使い方

アイデア先行でなんとなく作ってみたものなのですが、この機能はコードレビューの簡易化で使えるかと思います。

ユースケース

  • AIにソースコードを生成させる
  • コードレビューでコードを読む
  • 必要なコメントは<Leader> + b, 不要なコメントは<Leader> + q
  • :CommentBPListで一覧表示
  • QuickFixからBad評価のコメントを削除

AIの書いたコードをレビューするときだけでなく、複数人で運用する大規模コードについても一定期間で評価集計を確認し、不要なものは削除するなどの使い方ができます。

課題

やっつけで作ったのでイマイチなところがあります。ブラッシュアップしていきたいです。

  • 複数行コメントに対応していない
  • 評価の取り消しができない
  • 評価した後に基準値を上回るものは全部削除ができた方が便利
  • 今はバッファ単位で管理しているが、プロジェクト全体で傾向などを表示できるといいかも
  • コメントだけでなくコード行にも評価できた方がいいかも

まとめ

AIコーディングが登場してからというもの、AIが生成した膨大なコードをいかに効率よく処理していくか、が人間の役目になりつつあります。
それに伴いエディタの役割も変わりつつあります。コードをより速く正確に書くためのツールから、AIが書いたコードを読む、評価するツールになっていくでしょう。今回作成したコメント評価プラグインもそこを意識しています。やっつけで作ったものではありますが、叩き台程度にはなるのではないでしょうか。

Discussion