Zenn
🐭

Neovim0.11 x tsgoでTSファイルの型チェックをする

に公開

この記事はVim駅伝の2025-04-02の記事です。
前回の記事は静カニさんのvimmerになったです。
次回の記事はtadashi-aikawaさんの📘Neovimで最強のダッシュボードをつくってみたです。


MicrosoftがTypeScriptの関連ツールをGOに移植することがニュースになりました。10倍くらい速いらしいです。

https://www.publickey1.jp/blog/25/typescriptgo10.html

この記事では、これをNeovimから呼び出して型チェックします。

こちらのリポジトリで公開されています。直接実行可能な形では提供されていないので、クローンしてきてビルドしましょう。submodulesもあるのでクローンには少し時間を要します。

https://github.com/microsoft/typescript-go

package.jsonにビルドスクリプトが定義されています。最初はgoの依存をダウンロードするようなので、ここも少し時間がかかります。

$ npm install
$ npm run build

そうすると、built/local/tsgoに実行ファイルができるので、ここにパスを通しておきます。

型チェック確認用に、こんな感じの型エラーが出るtsファイルを作りました。nameがない可能性があるため、確認なしにtoUpperCase()を呼んではいけないコードです。

welcome.ts
type User = {
  name?: string;
}
export function welcome(user: User): string {
  return `Welcome ${user.name.toUpperCase()}`;
}

では、tsgoをNeovim builtin lspに設定します。Neovim v0.11でvim.lsp以下のAPIが整備されたのでそれを使ってみます。こちらのgistと、lspconfigのサンプルを参考にしました。

init.lua
vim.lsp.config.tsgo = {
  cmd = { "tsgo", "lsp", "--stdio" }, -- tsgoにはPATHを通しておく
  filetypes = {
    'javascript',
    'javascriptreact',
    'javascript.jsx',
    'typescript',
    'typescriptreact',
    'typescript.tsx',
  },
  root_markers = { 'tsconfig.json', 'jsconfig.json', 'package.json', '.git' },
  -- settings = {} ここに設定項目が入ってくる想定
}
vim.lsp.enable('tsgo')

上記を設定のうえ、問題のファイルを開くとこのように診断が表示されます。無事うごきました。


tsgo-lspの診断結果

続いて、プロジェクト全体の診断も実行してみます。コマンドラインからtsgoを実行すると、以下のようにエラーが報告されました。これはターミナル上では見やすいものの、エディタに取り込むには適していないため整形が必要です。


$ tsgo

まず-pretty=falseオプションをつけます。エラー箇所を下線で示していた部分がなくなりました。ファイル名などのハイライトも消えています。


$ tsgo -pretty=false

続いてstderrを捨てます。Warning...の警告がなくなりました。最初の見た目ではわかりませんでしたが、ここはstderrに出ていたんですね。


$ tsgo -pretty=false 2>/dev/null

ファイル数や実行時間のサマリーはオプションでは消せないようだったので、headで下から12行を吹き飛ばします。


$ tsgo -pretty=false 2>/dev/null | head -n-12

これでファイル名(行,桁): エラーメッセージのみを抽出できました。このコマンドとフォーマットをmakeprgerrorformatのオプションに設定すれば、出力を:makeで取り込めます。
こんな感じのコマンドにしました。makeprg|を、errorformat,をエスケープしなければならない点に注意しましょう。ここではmakeprg/errorformatを一旦save_変数に代入して保存していますが、他の場所でこれらを使っていない場合は直接上書きしても問題ありません。

init.lua
vim.api.nvim_create_user_command('Tsgo', function()
  local save_makeprg = vim.opt.makeprg
  local save_errorformat = vim.opt.errorformat

  vim.opt.makeprg = [[tsgo -pretty=false 2>/dev/null\|head -n-12]]
  vim.opt.errorformat = [[%f(%l\,%c): %m]]
  vim.cmd('silent! make!')
  local size = vim.fn.getqflist({ size = true }).size
  if size > 0 then
    vim.notify('[tsgo] type-error found', vim.log.levels.WARN)
    vim.cmd.copen()
  else
    vim.notify('[tsgo] type-check passed', vim.log.levels.INFO)
    vim.cmd.cclose()
  end

  vim.opt.makeprg = save_makeprg
  vim.opt.errorformat = save_errorformat
end, { desc = 'Tsgo' })

上記の設定を読み込むと、:Tsgoコマンドが使えるようになります。エラーが検出されるとquickfixが開きます。
以下が実行例です。今回は問題が起きているのが1ファイルしかないのであまり意味はありませんが、動作の雰囲気はわかるかと思います。エラーが複数ファイルで出ていれば、それらをすべてquickfixで一覧できます。


:Tsgo実行例(筆者はquicker.nvimを入れているのでqflistの表示がデフォルトとちょっと違います)

これで数日使ってみたところ、実行が速くて良い感じでした。ただ、途中の実行スクショに出ていますが、まだtsxの対応が不十分なところがあるので、vtslsやtsc.nvimも併用しています。今後のtsgoの発展に期待したいと思います。


以下追記

tsgoが更新されてけっこう変わっていたので書き換えました。

  • tsx未対応の警告が消えた
  • どうやらサマリーは12行とは限らないらしい
  • vim.systemを使ったほうが非同期になってお得
vim.api.nvim_create_user_command('Tsgo', function()
  local function on_exit(obj)
    if obj.stderr ~= '' then
      vim.notify('[tsgo] Error: ' .. obj.stderr, vim.log.levels.ERROR)
      return
    end

    local list = vim.split(obj.stdout, '\n')
    list = vim.tbl_filter(function(v)
      local t = vim.trim(v)
      if t == '' then
        return false
      end
      -- Files: などのサマリー情報を除外
      return not vim.regex('^\\s*\\u[[:lower:] ]*: '):match_str(t)
    end, list)
    if #list > 0 then
      vim.notify('[tsgo] type-error found', vim.log.levels.WARN)
    else
      vim.notify('[tsgo] type-check passed', vim.log.levels.INFO)
    end

    -- E5560対策
    local setqflist = function()
      -- set quickfix list
      vim.fn.setqflist({}, 'r', { title = 'tsgo', lines = list, efm = [[%f(%l\,%c): %m]] })
      vim.cmd.cwindow()
    end
    vim.defer_fn(setqflist, 100)
  end

  vim.system({ 'tsgo', '-noEmit', '-pretty=false' }, { text = true }, on_exit)
end, { desc = 'Tsgo' })

defer_fnについてはこちら↓
https://zenn.dev/vim_jp/articles/20230616vim_ekiden

vim-jp

Discussion

ログインするとコメントできます