◆ dprint 良い感じ
  ◆ ユーザのコードを尊重するので Prettier での問題がほとんど起きない
◆ VSCode 拡張はもう少し便利になってほしい

また Prettier に嫌な目にあわされました
以前 (1) (2) からこの辺で Prettier が嫌と言ってますが 他に代替と言える物なかったんですよね
dprint は良さそうと前から期待してたものの 開発中段階とかいうこともあって移行するのは避けてました
README に

This project is under active early development. I recommend you check its output to ensure it's doing its job correctly and only run this on code that has been checked into source control.

という注意書きもありますし

しかし すでに deno の標準フォーマッターとして使われて長いようですし そろそろ使っていこうかなと思います

準備

ドキュメントの通りインストールします
https://dprint.dev/install/

Prettier は VSCode でのみ使うなら Extension のインストールだけで使えましたが dprint は Rust のプログラムをローカルで動かすみたいなので 別にインストールが必要です
Windows の場合 npm の方法を使っても中では PowerShell スクリプトが実行されるようで 権限不足エラーでした
ポリシー許可した上での ps1 ファイルの実行か exe のインストーラーを使うのが良さそうです

インストールしたら dprint fmt コマンドでフォーマットできますが フォーマットには設定ファイルが必要です
dprint init コマンドで設定ファイルを作れます
グローバルなものではなくてプロジェクト単位で作るもののようです
グローバルな設定ファイルにするなら --config で設定ファイルのパスを指定できるので手動でグローバルな設定ファイルを参照させればできそうです

dprint init を実行すると追加するプラグインを選べます

>dprint init
Select plugins (use the spacebar to select/deselect and then press enter when finished):
> [x] dprint-plugin-typescript
[x] dprint-plugin-json
[x] dprint-plugin-markdown
[x] dprint-plugin-toml
[ ] dprint-plugin-dockerfile

スペースで x を切り替えられるので 使うものに x をつけます
エンターで確定です

JavaScript のフォーマットは typescript プラグインに含まれてます

選び終わったら dprint.json が作られます

{
"incremental": true,
"typescript": {
},
"includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs}"],
"excludes": [
"**/node_modules"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.66.0.wasm"
]
}

incremental は一括フォーマットするときに前回から変更なかったファイルをスキップしてくれるらしいです
typescript オブジェクトはプラグイン設定用のオブジェクトです
プラグインに dprint-plugin-json も選んでいたら json がキーのオブジェクトも作られます

設定の一覧はここにあります
https://dprint.dev/plugins/typescript/config/

行幅やセミコロンを使うかどうかなどいろいろな設定があります

VSCode で使う

フォーマッターはコマンドラインとして使うことはあまりなくて 基本はエディタの機能として使います
VSCode 用の Extension があるのでこれを使います

https://github.com/dprint/dprint-vscode
https://marketplace.visualstudio.com/items?itemName=dprint.dprint

JavaScript のフォーマッターを dprint にするために VSCode の設定ファイルに ↓のような設定を追加します

"[javascript]": {
"editor.defaultFormatter": "dprint.dprint"
}

dprint コマンドへのパスを通してない場合は dprint.path に dprint.exe へのパスを設定します
あとは .js ファイルを開いて Alt-Shift-F でフォーマットすると dprint.json での設定通りにフォーマットされます

不便なところ

使ってみて Extension としては Prettier よりも不便なところがいくつかありました
Prettier の Extension では JavaScript として認識されてるファイルを編集中であればどんな状態でも使えました

しかし dprint ではファイルとして保存されていて さらに dprint.json が見つからないといけません
新規作成してファイルには未保存の状態でコードを書いているとフォーマットできません
他フォーマッターでもこの仕様はものは多いです
ファイルに保存していても dprint.json が見つからないとフォーマットできません
コマンドラインからなら同じフォルダにあれば問題ないのですが VSCode の場合はフォルダを開いている必要もあるようです
フォルダを開かずに単純に .js ファイルを開いただけでは同じフォルダに dprint.json があってもフォーマットできませんでした

グローバル設定を追加する issue もあるので将来的には対応するのかもしれません
https://github.com/dprint/dprint-vscode/issues/13

フォーマット比較

実際にフォーマットして Prettier と比較してみます
オプションでは タブを使用してセミコロンは使わないようにしています

比較するだけならインストールしなくてもウェブ上で試すこともできます

Prettier
https://prettier.io/playground
dprint
https://dprint.dev/playground/

全体的には
Prettier はユーザが書いたものを尊重しません
AST から作り直すのが基本で ユーザがわかりやすくするために細かく工夫してもすべて無視されます
minify 済みのコードの復元や 崩れたものを直すときに向いています

dprint はユーザが書いたものを尊重します
できる限りそのままにするのでユーザが書いた工夫は保持されます
基本はそのままで明らかにおかしい部分だけ修正してくれる感じです
そのままでも十分読めるレベルに書かれたものを整えるのに向いています

カッコ

Prettier はわかりやすくするためにカッコを付けても結果として意味のないものなら消します


const x = (foo && bar) && baz
const y = foo || (bar || baz)

Prettier
const x = foo && bar && baz
const y = foo || bar || baz

dprint
const x = (foo && bar) && baz
const y = foo || (bar || baz)


const x = a && (b ? c : d)
const y = (a && b) ? c : d

Prettier
const x = a && (b ? c : d)
const y = a && b ? c : d

dprint
const x = a && (b ? c : d)
const y = (a && b) ? c : d

Prettier では逆にいらないところでもカッコをつけようとすることがあります
中には読みやすくなるケースもありますが 冗長に感じることのほうが多いです


!function() {
//
}()

Prettier
!(function () {
//
})()

dprint
!function() {
//
}()

dprint はカッコを残すからといって完全にそのままというわけではありません
ムダな二重括弧などは除去してくれます


const a = (((1 + 1))) + ((1 + 2))

Prettier
const a = 1 + 1 + (1 + 2)

dprint
const a = (1 + 1) + (1 + 2)

改行

Prettier は指定の行幅に収まるなら積極的に改行を消します
オブジェクトは例外で複数行なら複数行に 1 行なら 1 行のままにしようとします
全部この方針ならいいのに配列の場合やメソッドチェーンの場合などはそうなりません

dprint はそのままを維持してくれます


a = {
a: 1,
b: 2,
}

b = [
1,
2,
3,
]

Prettier
a = {
a: 1,
b: 2,
}

b = [1, 2, 3]

dprint
a = {
a: 1,
b: 2,
}

b = [
1,
2,
3,
]

Prettier では逆に文字数に収まっても改行を追加するケースがあります
一部ライブラリで読みやすくなるかららしいですが その一部ライブラリを使う時以外は余計な機能でしかありません


fn(x => x, x => x)

Prettier
fn(
x => x,
x => x
)

dprint
fn(x => x, x => x)

dprint はユーザの書いたものを尊重するので ユーザが複数行していれば複数行にもできます

条件演算子のネスト

これはどっちも同じようでした
スイッチケースの対応として 常にインデントをしてくれません
when と then が同列に並ぶのが嫌なので ここは無視する設定が必要そうです
dprint は設定が多いのですがこの部分の設定はなさそうでした


const x = aaaaaaaaaaa
? bbbbbbbbbb
? ccccccccccccccc
: dddddddddddddd
: eeeeeeee
? fffffff
: ggggggg
? hhhhhhhh
? iiiiiii
: jjjjjjjj
: kkkkkkkkkkk

Prettier
const x = aaaaaaaaaaa
? bbbbbbbbbb
? ccccccccccccccc
: dddddddddddddd
: eeeeeeee
? fffffff
: ggggggg
? hhhhhhhh
? iiiiiii
: jjjjjjjj
: kkkkkkkkkkk

dprint
const x = aaaaaaaaaaa
? bbbbbbbbbb
? ccccccccccccccc
: dddddddddddddd
: eeeeeeee
? fffffff
: ggggggg
? hhhhhhhh
? iiiiiii
: jjjjjjjj
: kkkkkkkkkkk

無視させるにはコメントで

// prettier-ignore

// dprint-ignore

を入れます

オペレータの位置

オペレータの位置はデフォルトでは dprint は次の行に配置します


const x = aaaaaaaaaaaa +
bbbbbbbbbbbb +
cccccccccccc +
dddddddddddd +
eeeeeeeeeeee +
ffffffffffff

Prettier
const x =
aaaaaaaaaaaa +
bbbbbbbbbbbb +
cccccccccccc +
dddddddddddd +
eeeeeeeeeeee +
ffffffffffff

dprint
const x = aaaaaaaaaaaa
+ bbbbbbbbbbbb
+ cccccccccccc
+ dddddddddddd
+ eeeeeeeeeeee
+ ffffffffffff

個人的には その行を見るだけで次の行に続いてることがわかる Prettier のほうが好きです
dprint は設定で operatorPosition を sameline にすることでオペレータを前の行に持ってこれます
しかし その場合は条件演算子の ? と : も変わってしまいます
これだけは特別扱いしてほしいので 難しいところです

タブとスペースの混合

Prettier の許せない挙動にタブを使用するように設定しても見た目を揃えるためにスペースも使うというのがありました
dprint ではこんなバカなことはしません


longlonglonglong
? {
x: 1
}
: () => {
return 1
}

Prettier
longlonglonglong
? {
x: 1,
}
: () => {
return 1
}

dprint
longlonglonglong
? {
x: 1,
}
: () => {
return 1
}

元から変更してないだけではなくて 折り返す必要が出た場合でも余計なスペースを挿入しません


longlonglonglong
? { aaaaaa: 1, bbbbbbb: 2, ccccccc: 3, ddddddd: 4, eeeeee: 5, fffffff: 6, ggggggg: 7 }
: () => { const a = 1; const b = 1; return a + b }

dprint
longlonglonglong
? {
aaaaaa: 1,
bbbbbbb: 2,
ccccccc: 3,
ddddddd: 4,
eeeeee: 5,
fffffff: 6,
ggggggg: 7,
}
: () => {
const a = 1
const b = 1
return a + b
}

テンプレートリテラル

便利機能なのか Prettier では html という名前の関数をテンプレートリテラルのタグとして使うと中身を HTML としてフォーマットします


const a = html`<div><div>${value}</div><div>aaaaa</div></div>`

Prettier
const a = html`<div>
<div>${value}</div>
<div>aaaaa</div>
</div>`

dprint
const a = html `<div><div>${value}</div><div>aaaaa</div></div>`

一見便利ですが 余計な問題が増えるのでやめて欲しい機能でもありました
例えばこういうものです

const a = html`<div style="white-space: pre-wrap">${value}</div>`

white-space を指定してるので value 前後にスペースを入れられたくないのですが 文字数が行の幅を超えていたり HTML のネスト度合いに応じて改行が追加されます
また html という名前にしないといけないので この機能を使って HTML としてフォーマットして欲しいときにはわざわざ html という名前の変数に代入しないといけない制限も出てきます
html としてフォーマットしたいならユーザがコメントでそういう指定をしたときだけにして欲しいです

埋め込み内の折り返しはどっちも同じようでした
一つ文字列として扱いたいので元のコードに改行が無い限り折り返してほしくないのですけど ここも ignore コメントするしかなさそうです


const a = `aaaaaaaaaa ${foo.bar} bbbbbbbbbbbb ${1} ccccccccccccc ${1 + 1} dddddddddddddddd`

Prettier
const a = `aaaaaaaaaa ${foo.bar} bbbbbbbbbbbb ${1} ccccccccccccc ${
1 + 1
} dddddddddddddddd`

dprint
const a = `aaaaaaaaaa ${foo.bar} bbbbbbbbbbbb ${1} ccccccccccccc ${
1 + 1
} dddddddddddddddd`

最近の Prettier

issue でどれだけ不満が出ようが意見が分かれようが頑なにオプションだけは追加しないと主張していたのに最近なぜかオプションの追加があったらしいです
やっと心を入れ替えたのかと期待してみたらすごくどうでもいいところでした
そんなところでオプションを入れるくらいならその他の意見が分かれるところはすべてオプション化してほしいですし dprint のようにユーザのコードを尊重するようにしてくれればほとんどの問題はなくなるはずです
オブジェクトのみの特殊な対応をなくして これからも絶対にオプションは入れないくらいの方針だったら まだ一貫してるしと思えましたが中途半端にオプションを追加するようなのでさらに不信感が高まりました

dprint は普通に使えそうなクオリティでしたし 新しく作るものではもう Prettier はいらないかな