PrettierでMarkdownをフォーマットしたときの英語と日本語の間に入るスペースをどうにかする

## はじめに

Prettier - Code formatter(VSCode拡張)でMarkdownをフォーマットすると、英語（アルファベット）と日本語の間に半角スペースが入ります。

例えば、
`IPアドレス`は間にスペースが入り`IP アドレス`となります。

この問題は以下の記事でも指摘されています。
[VSCodeでMarkdownの自動フォーマット＆整形ルールを自由に設定](https://qiita.com/the_red/items/e121cbb659c52a60bca6)

この原因を調査し、強引に修正しました。

## コメントより追記（2021.04.05）

この問題に対処する便利なプラグインが @tats-u さんにより作成されました。
記事の『prettier-plugin-md-nocjspを利用する』の項だけお読み下さい。

[prettier-plugin-md-nocjspを利用する](#prettier-plugin-md-nocjspを利用する)

## 環境

動作を確認した環境は以下の通りです。

- Windows 10 Pro(64bit)
- VS Code 1.64.2
- Prettier - Code formatter 9.2.0

## 原因

原因はPrettierにあります。
これは[PrettierのPlayground](https://prettier.io/playground/)でも動作がそのようになることから明らかです。

対処法がないかググってみると、以下のissueが見つかりました。
[Markdown: unnecessary space is inserted between Korean & English words](https://github.com/prettier/prettier/issues/5028)
韓国語と英語の間にスペースが入ってしまうというissueです。
issueを読むと以下のことが分かります。

- [CJKV日中韓越情報処理](https://www.oreilly.co.jp/books/4873111080/)によれば、慣例として余分なスペースを使用するとある。
- 過去に議論があったようで(ref: [ikatyang/cjk-regex#48](https://github.com/ikatyang/cjk-regex/issues/48))そちらを読むと、W3C typographyの要件にもスペースを入れるとある。
- PrettierのMarkdown対応部分を書いてる人は台湾に住んでいる~~美少女~~らしい。

スペース無し派、劣勢ですかね･･･
でもこれテキストではなく、プレビューしたときにCSSでスタイリングすべきことのような気がします。

そして、issueを受けて韓国語はスペースを入れないという仕様に変更されています。
どのような修正がされたのかは、下記を見ると大体わかります。
<https://github.com/prettier/prettier/pull/5040/commits/da6186995df3ba1f8bdeb126d794fb32c404bcdb>

ここまで分かれば何とか出来そうですね。

## 対処

対処方法として2パターン思いつきました。

- 例外としている韓国語のパターンに日本語も追加する
- スペースを追加している処理をなくす

VSCodeの拡張はJavaScriptで書かれているので、直接拡張のjsファイルを修正して対処できます。

### 対処法1

まず、Prettierのファイルがあるディレクトリを開きます。
Windowsであれば、下記をコピペしてファイル名を指定して実行で開けます。
`%USERPROFILE%\.vscode\extensions\esbenp.prettier-vscode-3.20.0\node_modules\prettier`

ディレクトリ内のindex.jsをコピーしてindex.js.bkなどとしてバックアップしておきます。

index.jsをエディタで開き、38438行目まで移動します。
38438 から 38441行目が以下のようになっています。

```js:index.js
var cjkPattern = json.cjkPattern,
    kPattern = json.kPattern,
    punctuationPattern = json.punctuationPattern;
var getLast$3 = util.getLast;
```

ここの`kPattern`と言う変数が韓国語の判定に使われているので、38439行目を修正し以下のようにします。

```js:index.js
var cjkPattern = json.cjkPattern,
    kPattern = json.cjkPattern,
    punctuationPattern = json.punctuationPattern;
var getLast$3 = util.getLast;
```

kPatternにcjkPatternと同じ、json.cjkPatternを代入します。
中国語も巻き添えですが、まぁ良しとします。

### 対処法2

対処法1を行った場合は不要です。

まず、Prettierのファイルがあるディレクトリを開きます。

Windowsであれば、下記をコピペしてファイル名を指定して実行で開けます。
`%USERPROFILE%\.vscode\extensions\esbenp.prettier-vscode-3.20.0\node_modules\prettier`

ディレクトリ内のindex.jsをコピーしてindex.js.bkなどとしてバックアップしておきます。

index.jsをエディタで開き、38514行目まで移動します。
38514 から 38529行が以下のようになっています。

```js:index.js
if (lastNode && lastNode.type === "word") {
      if (lastNode.kind === KIND_NON_CJK && node.kind === KIND_CJ_LETTER && !lastNode.hasTrailingPunctuation || lastNode.kind === KIND_CJ_LETTER && node.kind === KIND_NON_CJK && !node.hasLeadingPunctuation) {
        nodes.push({
          type: "whitespace",
          value: " "
        });
      } else if (!isBetween(KIND_NON_CJK, KIND_CJK_PUNCTUATION) && // disallow leading/trailing full-width whitespace
      ![lastNode.value, node.value].some(function (value) {
        return /\u3000/.test(value);
      })) {
        nodes.push({
          type: "whitespace",
          value: ""
        });
      }
    }
```

38518行目

```js
value: " "
```

となっているのを

```js
value: ""
```

とします。
もちろん中国語も巻き添えです。

## textlintで対処する方法

根本的な解決ではありませんが、textlintで対処するのも良いです。

textlintについては、以下の記事に情報がまとまってます。
[Qiitaやはてブロに投稿するようなユーザーはみんなtextlintを使うと捗るぞ！](https://qiita.com/fruitriin/items/de0e3d4c2205b734bdc5)

私はtextlintのpreset-ja-spacingを有効にして、半角文字と全角文字の間にスペースを入れたら警告を出すようにしています。

## prettier-plugin-md-nocjspを利用する

作者の@tats-uさんよりコメントで紹介頂いた、`prettier-plugin-md-nocjsp` を利用する方法です。
もっとも手軽でスマートなので、こちらを利用することを推奨します。

プラグインの詳細については以下の作者様の記事を一読頂ければと思います。
[和欧文字間（漢字仮名と英数字の間）に半角スペースが挿入されないようにするPrettier Markdownプラグインを作った](https://qiita.com/tats-u/items/bcbfe2bb4e71bf0a2b87)

導入方法についてはプラグインのgithubに丁寧に書かれています。
https://github.com/tats-u/prettier-plugin-md-nocjsp#readme

注意点として
PrettierのVSCode拡張機能にバンドルされているPrettierではPrettierプラグインが利用できません。
Prettierを別途グローバル or ローカルにインストールする必要があります。

推奨はローカルインストールです。

#### ローカルインストール（推奨）の場合

カレントディレクトリをプロジェクトルートに移動します
以下をコマンド実行し、`prettier-plugin-md-nocjsp` をインストールします。

```sh
npm install --save-dev prettier-plugin-md-nocjsp
npm install prettier -D --save-exact
```



次に `.prettierrc`をプロジェクトルートに追加します。

```yml:.prettierrc
# *snip*
overrides:
  - files:
      - "*.md"
      - README
    options:
      parser: markdown-nocjsp
  - files:
      - "*.mdx"
    options:
      parser: mdx-nocjsp
```


#### グローバルインストールの場合

以下をコマンド実行し、`prettier-plugin-md-nocjsp` をインストールします。

```sh
npm install -g prettier-plugin-md-nocjsp
npm install -g prettier
```

~~さらにprettier拡張の設定で `prettier.resolveGlobalModules` を `true` にします。~~
`prettier.resolveGlobalModules` が `true` だと動かなくなりました（2022.02.13）
`true` になっている場合 `false` にしてください。

次に `.prettierrc`をユーザーディレクトリ（C:\Users\ユーザー名）に追加します。

```yml:.prettierrc
# *snip*
overrides:
  - files:
      - "*.md"
      - README
    options:
      parser: markdown-nocjsp
      plugins:
        - "./AppData/Roaming/npm/node_modules/prettier-plugin-md-nocjsp"
  - files:
      - "*.mdx"
    options:
      parser: mdx-nocjsp
      plugins:
        - "./AppData/Roaming/npm/node_modules/prettier-plugin-md-nocjsp"
```
こちらは @u3xbf8i9 さんのコメントが参考になりました。ありがとうございます。





## 注意

Prettier - Code formatter、Prettierのバージョンアップでこの内容が通用しなくなることがあります。
ご了承ください。

## 最後に

無理やり修正していますが、プルリクやissueを上げた方が建設的だと思います。
でも、英語無理！

## 追記（2019.03.08）

issueにあがってました、ありがたい。
<https://github.com/prettier/prettier/issues/5938>

## さらに追記（2020.02.14）

<https://github.com/prettier/prettier/issues/6385>

オプションで切り替える案が出されてました。

日本語と中国語が切り離せない。
中国語ではスペースを入れるのが一般的派と、中国語でも入れないよ派など
意見が割れているようです。
Prettier自体にあまりオプションを入れたくないともあります。
解決はすぐには難しそうですね。

## 参考

<https://github.com/ikatyang/cjk-regex/issues/48>
<https://github.com/prettier/prettier/issues/5028>
<https://github.com/prettier/prettier/pull/5040/commits/da6186995df3ba1f8bdeb126d794fb32c404bcdb>
<https://qiita.com/kumapo0313/items/92d1597da5f3752f6584#comment-e328de87992dbb82c9a1>