Vim で Prettier かけながら、TypeScript の型チェックを行う（with vim-lsp）

この記事は [Vim Advent Calendar 2019](https://qiita.com/advent-calendar/2019/vim) 向けに書かれました。
もう一つネタがあって記事を書いていたのですが、
なんか微妙な出来だったので Advent Calendar とは別として投稿しようかなと思っています。

# はじめに
この記事は

- Vim を使っている
- vim-lsp を使っている
- TypeScript を書いている
- prettier/eslint を連携させたい！

な人向けの記事です。

# 本題

vim-lsp を使って TypeScript の開発をしているならば、
使っている言語サーバは `typescript-language-server` ではありませんか？

よく出来ていますよね。快適です。

しかし、自分は下記の点が物足りませんでした。

### Prettier でフォーマットできない
今どきのフロントエンドの現場であれば、Prettier を使うことが多いと思います。
当然ながら `typescript-language-server` によるフォーマットは Prettier によるそれではありません。

### eslint のエラーが検出できない
`typescript-language-server` を使えば型エラーが即座にわかります。最高！
しかし、動作するコードでも lint を通らなければコミットはできません。
コードを書いている最中に unfixable な eslint エラーを知れたら嬉しいですよね。

# 解決方法

`efm-langserver`、もしくは `diagnostic-languageserver` を利用しましょう。

これらは linter/formatter などの CLI ツールを、LSP のインターフェースでラップしてくれる言語サーバです。
つまり、`vim-lsp` から `eslint` などを利用できるようになります。

しかし、はっきりいって設定はちょっと難しいです。
そのため、ここに `eslint` 向けの設定を書いてしまいます。
`prettier` は `eslint --fix` 経由で実行することとしましょう。
`eslint` 経由で `prettier` を走らせる方法は他の記事をご参照ください。


# 設定方法

#### diagnostic-languageserver のインストール
`npm i -g diagnostic-languageserver`


#### eslint_d のインストール
`npm i -g eslint_d`
※ これを入れると、リントのパフォーマンスが劇的に向上します。


#### vim-lsp に設定を追加
```viml
  autocmd! vimrc User lsp_setup call lsp#register_server({
        \   'name': 'diagnostic-languageserver',
        \   'cmd': { info -> ['diagnostic-languageserver', '--stdio'] },
        \   'whitelist': [
        \     'typescript',
        \     'typescript.tsx',
        \     'typescriptreact',
        \     'javascript',
        \     'javascript.jsx',
        \     'javascriptreact',
        \   ],
        \   'initialization_options': {
        \     'linters': {
        \       'eslint': {
        \         'sourceName': 'eslint',
        \         'command': 'eslint_d',
        \         'args': ['--stdin', '--stdin-filename=%filename', '--no-color'],
        \         'rootPatterns': ['.eslintrc', '.eslintrc.js'],
        \         'formatLines': 1,
        \         'formatPattern': [
        \           '^\s*(\d+):(\d+)\s+([^ ]+)\s+(.*?)\s+([^ ]+)$',
        \           {
        \             'line': 1,
        \             'column': 2,
        \             'message': [4, ' [', 5, ']' ],
        \             'security': 3
        \           }
        \         ],
        \         'securities': {
        \            'error': 'error',
        \            'warning': 'warning'
        \         },
        \       },
        \     },
        \     'filetypes': {
        \       'javascript': 'eslint',
        \       'javascript.tsx': 'eslint',
        \       'javascriptreact': 'eslint',
        \       'typescript': 'eslint',
        \       'typescript.tsx': 'eslint',
        \       'typescriptreact': 'eslint',
        \     },
        \     'formatters': {
        \       'eslint': {
        \         'rootPatterns': ['.eslintrc', '.eslintrc.js'],
        \         'command': 'eslint_d',
        \         'args': ['--fix', '--fix-to-stdout', '--stdin', '--stdin-filename=%filename'],
        \         'isStdout': v:true,
        \         'isStderr': v:true,
        \       }
        \     },
        \     'formatFiletypes': {
        \       'javascript': 'eslint',
        \       'javascript.tsx': 'eslint',
        \       'javascriptreact': 'eslint',
        \       'typescript': 'eslint',
        \       'typescript.tsx': 'eslint',
        \       'typescriptreact': 'eslint'
        \     }
        \   }
        \ })

```

※ 完全におまじないですね。
※ diagnostic-languageserver 向けの設定しか提供できず悔しいです。
※ efm-langserver に関しても設定をいろいろ試している最中です。うまく動いたら移行予定だし、記事を書きます！


#### vim-lsp でフォーマットする
`eslint` のエラーは勝手に表示されますが、フォーマットは下記のコマンドを実行する必要があります。

`:LspDocumentFormat`



# まとめ
これで `vim-lsp` を利用して、

- TypeScript の賢い補完や型チェック、定義ジャンプを利用する
- 合わせて、eslint による lint 結果を表示する
- Prettier でフォーマットをかける

をすべて同時に満たすことができます。

vim-lsp は一つの filetype に複数の言語サーバを走らせることが可能で、強みです。
これに対応しているのは、`coc.nvim` と `vim-lsp`くらいしか知りません。

この設定を行って快適なフロントエンド開発をやっていきましょう！


# 以下、ハマった点など

#### TypeScript/JavaScript 両方への対応
`eslint` に STDIN 経由でファイルを渡した場合、そのテキストが TypeScript なのか JavaScript なのか `eslint` さんには判別できません。
そのため、`--stdin-filename` にファイル名を渡してやる必要がありました。

#### `eslint` を毎回起動してるとめちゃくちゃ遅いし重い問題への対応
最初、`eslint_d` を使わずに設定をしていましたが、死ぬほど遅かったです。
いろいろ調査していると、`eslint_d` というものを見つけて組み合わせて解決しました。