graphql-ruby の Language Server を試す

この記事は、[GraphQL Advent Calendar 2017](https://qiita.com/advent-calendar/2017/graphql) の 10 日目の記事です。
残念ながら参加者が足りてないのでどなたか興味がある方は今からでもどうぞ。

---------

GraphQL Tokyo のオーガナイザーをしている松島です。

https://www.meetup.com/ja-JP/GraphQL-Tokyo/

2 ~ 3ヶ月に 1 回くらい集まってワイワイしているので、よろしければどうぞ。

--------

graphql-ruby の人が、Ruby で GraphQL の Language Server を書き始めたようなので試してみました。

https://github.com/rmosolgo/graphql-ruby/pull/1144

![graphql.gif](https://qiita-image-store.s3.amazonaws.com/0/28402/e0aee07d-7c1f-cc8c-ce6f-cab6c6f14de4.gif)

:construction_worker: 2017/12/10 現在の情報のため、マージされる頃にはもろもろ変更されている可能性があります :construction_worker: 

以下のリポジトリにサンプルがあるため、こちらを試してもらうのが手っ取り早いです。

https://github.com/mtsmfm/graphql-ruby-demo/tree/language-server

## Language Server とは?

Language Server とは、補完や定義ジャンプ、シンタックスエラーの検出といった、主にエディタに向けた便利機能を提供してくれるサーバです。
具体的にどのようにエディタとやり取りするかは Language Server Protocol に定義されています。
長いので以降 LSP と書きます。

「サーバ」と聞いてよく誤解があるようなのですが、どこかにサーバを立ててみんなで接続するというよりも、基本的に手元で起動させることになります。
(もちろん LSP としてはどこで動いていてもいいです)

## 公式になんかあるんじゃなかったっけ?

あります。

https://github.com/graphql/graphql-language-service

それをなぜ再実装するかというと、どうやら実装が足りてないけど JS を書きたくないというのが主なモチベーションに見えます。

> Why not JavaScript?
> There's a JS project called GraphQL Language Service, but I didn't try to use it because:
> - I'm not familiar with it's dependencies
> - The support for language server methods was not great anyways
> - I don't like JavaScript

## どうやって使うのか

### LSP クライアントの用意

適当に LSP を喋れるクライアントを見繕って、サーバと繋げば動きます。

Vim などは汎用的な LSP クライアントがあるので、起動方法と対象となるファイルの種類を設定ファイルで宣言すればいいでしょう。
一方、VS Code は LSP クライアントのコア部分を npm パッケージとして提供しており、簡単にラップして個別に拡張機能として提供する方針のようです。

僕は VS Code をメインエディタとして使っているため、まずは拡張機能を作るところから始める必要がありました。

といっても、書く内容はたいしてありません。

```typescript
'use strict';

import * as vscode from 'vscode';

import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient';

export async function activate(context: vscode.ExtensionContext) {
    const conf = vscode.workspace.getConfiguration("graphql-lsc");
    let [command, ...args] = (<[string]>conf.get("commandWithArgs"));
    const clientOptions: LanguageClientOptions = {
        documentSelector: ['graphql'],
    };
    const disposable = new LanguageClient('Language Server GraphQL', {command, args, options: {cwd: vscode.workspace.rootPath}}, clientOptions).start();

    context.subscriptions.push(disposable);
}
```

https://github.com/mtsmfm/vscode-graphql-lsc/blob/9b9838f29e396eea874d57bb01ac40d8c21911f8/src/extension.ts

一応起動コマンドくらいは設定できるようにしておきました。

こちらからインストールできます。

https://marketplace.visualstudio.com/items?itemName=mtsmfm.graphql-lsc

### Server の起動

graphql gem を使った Rails アプリケーション上での利用を前提に作られているようです。

まず、以下のようなファイルを作ります。
schema のクラスを指定する必要があるため、最初に config/environment.rb を読んで Rails アプリケーションをロードする必要があることに注意してください。

```ruby:bin/graphql_language_server
#!/usr/bin/env ruby

require_relative "../config/environment"
require "graphql/language_server"

class GraphQLLanguageServer < GraphQL::LanguageServer
  self.schema = StarWarsSchema
  self.logger = Logger.new(Rails.root.join("log/lsp.log"))
  self.development_mode = true
end

GraphQLLanguageServer.new.start
```

https://github.com/mtsmfm/graphql-ruby-demo/blob/5169f4a29a9110519e1ca73ff86b8f3358148e3d/bin/graphql_language_server

次に、起動コマンドを指定します。
当然 Docker 上で開発しているはずなので、Rails 用に定義している docker-compose の service そのまま、command を弄ればいいでしょう。

例えば app が Rails 環境の場合、以下のようになります。

```json:.vscode/settings.json
{
  "graphql-lsc.commandWithArgs": [
    "docker-compose", "run", "--rm", "app", "bin/graphql_language_server"
  ]
}
```

https://github.com/mtsmfm/graphql-ruby-demo/blob/5169f4a29a9110519e1ca73ff86b8f3358148e3d/.vscode/settings.json

## 使う

xxx.graphql を開けば使うことができます。

![graphql.gif](https://qiita-image-store.s3.amazonaws.com/0/28402/e0aee07d-7c1f-cc8c-ce6f-cab6c6f14de4.gif)

生成した GraphQL Schema ではなく、Ruby コードを読み込み、それを元に補完をかけてくれるようです。

## 所感

情報の起点は GraphQL Schema からだと思いこんでいましたが、graphql-ruby としてはあくまで Ruby DSL が正であるということでしょうか。

Syntax エラーな状態で補完をかけようとすると死ぬ場合があるようですが、その辺の作り込みがこれからされていくのでしょう。

Language Server で ○○ したら死ぬ、というのを気づくのも再現させるのも直すのも非常に手がかかるので、再現可能なプロジェクト一式を細かく報告していくのが大事ですね。