◆ オプションつけないのにやっかいな強制変更ばかりしてくる Prettier
◆ バージョン上げないように固定してる
◆ なのに久々に以前のファイルをフォーマットしたら結果が変わってる
  ◆ 折返し地点のみ
◆ 今のところ実用レベルの代替ツールはなさそう

Prettier は JavaScript のフォーマッタで設定が少なくいい感じにやってくれるのが良いところです
……が設定が少ないことにこだわりすぎて 逆に不便なところが多いです

issue で変更要望があって多くの人が賛成していても対応しないという姿勢です
オプションを追加すればいいのにそれすら拒否です

その割にときどき悪化するような破壊的変更を行います
それでもう新しいバージョンは使わないと宣言してる人やバージョン固定の方法を勧めてる人もみかけます

私もあるとき 更新しても余計な変更しかされないので更新しないようにしました
VSCode の拡張機能で使ってるので 自動更新をオフにして過去バージョンの VSIX ファイルをダウンロードしてインストールします
VSIX ファイルのダウンロードは marketplace で拡張機能をみつけて 「Download Extension」 から最新版をダウンロードして そのダウンロード URL のバージョン部分を目的のに置き換えると簡単です
prettier-vscode の 1.6.1 ならここ
インストールは Ctrl-Shift-P から vsix と打つと候補に出てくるコマンドを使います

バージョン変わってないのに結果が変わる

これで余計な変更は起きないはず……だったのですが 半年くらい触れてなかったファイルを少し変更しフォーマットするとなぜか変更してない箇所に差分が出ました
前回最後にフォーマットしているはずですし フォーマット漏れなら自分だと絶対入れないスペースが追加される差分が出るはずです
しかし そういうのはなく折返し地点だけが変わっていました
1 行の文字数の設定は前から 120 文字にしていて その設定は変わっていませんでした

120 文字に指定していても 110 文字くらいなのに折り返されるところがあれば 130 文字くらいなのに折り返しがないこともあってイマイチ基準がわかりません
こういう曖昧な部分の捉え方が変わったのかもしれませんが Prettier のバージョンやフォーマットするソースコードに変更がないのに変わるのは困ります
「ファイル全体を見て 傾向から折り返し基準を変える」みたいな高度なことはしてないと思いますし 文とその箇所のインデントレベルが一緒なら同じ結果になると思うのですけど
考えられるとすれば VSCode 自体のバージョンは上がっているので コア部分と同じパッケージを使っていたらパッケージを共有していて 依存パッケージのバージョンが変わったからとかでしょうか
そのパッケージがパーサ部分でタブを何文字とみなすとかそういうレベルで違えば折り返し地点だけが変わるのは納得できます
と言ってもパーサは Node.js 本体の機能でもないので 拡張機能内に依存パッケージは全部入っていて影響しなさそうなんですよね
結局はっきりとした原因はわかりませんでした

他フォーマッタ

以前から不満も多くなってきているので 他に良いのがあるなら乗り換えたいです
最近は調べてなかったので 久しぶりに調べてみました
しかし 特にそれっぽいのもありません
移り変わりが激しいのが JavaScript 関連ツールの特徴なんだからもっと色々でてきてほしいんですけど

Prettier フォーク

ベースは Prettier で要望が分かれるところはオプション化して行ってくれるのがベストだと思うので Prettier のフォークを探してみます
ただ Github のフォークってちょっといじってみたいからとか PR するからのフォークが多いんですよね
フォークして新たに独自のプロジェクトにするっていうのを分けてほしいと思います
独立するなら名前も変えるはずという考えで フォークから名前が異なってるものを取り出してみました

function getForks(owner, name, page = 1) {
return fetch(`https://api.github.com/repos/${owner}/${name}/forks?sort=stargazers&page=${page}`)
.then(e => e.json())
.then(repos => repos
.filter(e => e.name !== name)
.map(e => ({
name: e.full_name,
star: e.stargazers_count,
url: e.html_url,
}))
)
}

console.log(await getForks("prettier", "prettier"))
[
0: {name: "arijs/prettier-miscellaneous", star: 140, url: "https://github.com/arijs/prettier-miscellaneous"}
1: {name: "Automattic/wp-prettier", star: 35, url: "https://github.com/Automattic/wp-prettier"}
2: {name: "brodybits-archive/prettier-fork", star: 9, url: "https://github.com/brodybits-archive/prettier-fork"}
3: {name: "Skywalker13/prettier-space-parenthesis", star: 4, url: "https://github.com/Skywalker13/prettier-space-parenthesis"}
4: {name: "onecityuni/prettierchakas", star: 1, url: "https://github.com/onecityuni/prettierchakas"}
]

30 件ずつにページが分かれますが スター数でソートしてれば 1 ページでも十分そうです
arijs/prettier-miscellaneous くらいでしょうか
2 つめもスターは 35 ありますが wp は WordPress みたいで求めてるものではなさそうです

prettier-miscellaneous はオプションを追加したもので一見良さそうでした
しかし 差分がまとめられてないですし 最終更新が 3 年前になっていて放置されてるようです
Prettier が気に入らなくても 規模が大きいのでそれをフォークしてオプション追加してメンテし続けるのは大変そうですし 仕方ない気はします

あと そこまで積極的なプロジェクトだと フォーク扱いじゃなくて完全新規プロジェクトとして作ってることが多い気がします
Github の機能だとわからず README に◯◯のフォークですって書いていて気づくようなのです
そうなると探すのも大変です
ただそれなりのクオリティのが出たら話題になって名前くらい聞くと思うので 何も聞かない以上 代替と言えるレベルのはなさそうです

dprint

Prettier とは関係ないですが 個人的に今後に期待してるのは dprint というフォーマッタです
https://dprint.dev/

まだスターも 240 くらいで npm ダウンロード数も 100 行ったり行かなかったりくらいです
Rust 製で JavaScript などに限定されずプラグインで言語を増やせるフォーマッタらしいです
現状では TypeScript と JavaScript と JSONC (VSCode の設定などで使われるコメントあり JSON) だけがサポートされてるようです

JavaScript だけで十分なので使ってみようとしましたが 「まだ開発中なのでバージョン管理ツールを使って出力を確認するようにしてください」って注意書きもあるレベルでまだ実用は難しそうです
頻繁にコミットされてるので今後に期待です
Prettier の不満を解消してくれるものになってくれればいいなと思ってます
Rust 製な分 速度的にも速そうですし

Prettier の不満点

条件演算子

私が Prettier の更新をしなくなった理由は条件演算子のフォーマットの変更です

https://prettier.io/blog/2018/11/07/1.15.0.html#flatten-else-branch-for-nested-ternaries-5039-by-suchipi-5272-by-duailibe-5333-by-ikatyang

これをフォーマットすると

v = aaaaaaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbbbbb
: ccccccccccccccccccccc
? dddddddddddddddddd
: eeeeeeeeeeeeeeeeee

こうなります

v = aaaaaaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbbbbb
: ccccccccccccccccccccc
? dddddddddddddddddd
: eeeeeeeeeeeeeeeeee;

見づらすぎです
それまではインデントで構造がわかるようになってたのにそれを捨てています

if (aaaaaaaaa)
return bbbbbbbb
else
return cccccccc

こう書くようなものです
考えた人の正気を疑うレベルです

ネストが入ると

v = aaaaaaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbbbbb
? ccccccccccccccccc
: dddddddddddddddd
: eeeeeeeeeeeeeeee
? fffffffffffffffff
: gggggggggggggggggggg
? hhhhhhhhhhhhhhhhhhh
: iiiiiiiiiiiiiiiiii



v = aaaaaaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbbbbb
? ccccccccccccccccc
: dddddddddddddddd
: eeeeeeeeeeeeeeee
? fffffffffffffffff
: gggggggggggggggggggg
? hhhhhhhhhhhhhhhhhhh
: iiiiiiiiiiiiiiiiii;

こうなります

さすがにこれは最初はバグとしか思えなくて issue を見に行ったのですがこれが意図した動作らしいです
条件演算子なんてよく使うもの全部に // prettier-ignore をつけていくのは無理があるのでもう更新するのはやめました

issue でこれはおかしいという書き込みも多いのですが 変える気はなさそうです
一応この issue は最初の頃は読んでたのですが 今年のあたりからは見てないです
今の最新版でも変わってないあたり直す気はないのでしょうね

戻してほしいというユーザが多いのに戻さない理由に簡単に変えられないとか ここでアンケートとっても満足してる人は見ていないからとかそういう感じのことが言われてるのですが それならなんで最初に変えたんだ と思うのですよね
それがわかってるなら最初に変えるときのアンケートだって当てにならないし 実際変えた結果多くの不満が来てるわけですし

すべてインデントするとネストが深くなるのが理由らしいのですが 現実的な数のネストなら問題ないですし そこまで深くなるなら書く側が適度に分けるでしょう
この読みづらい出力のほうが何倍も迷惑です


ところでこの switch 風に使うときにネストが深くなったら見づらいというのは少し前に自分でも感じて Python の if/elif/else の見た目を参考にした書き方に変えてみています

aaaa ?
bbbb
: cccc ?
dddd
: eeee ?
ffff
:
gggg

これだと Prettier が ? と : の縦位置を合わせるように変換してしまうので // prettier-ignore が必須ですけど
それなら最新版にしてもいいかなと言う気もしましたが どうせ他の部分も余計な変更してソースコードに差分が出そうなのでやめました

connect

私が更新を止めたのは条件演算子が大きな理由ですが それ以外にも不満点はあります
例えば特定の名前だけ特別扱いするというのがありました

connect という名前の関数だと

connect(1, 2)



connect(
1,
2
)

と変な改行が入ります
それまでこんな改行は見たことがなかったので他の名前にしてみると 1 行に収まりました

調べたら たしか Redux か Rxjs のライブラリのためみたいな理由だったのですが 特定ライブラリなんて使わない人のほうが遥かに多いのにそれに合わせて特定の名前を特別扱いとか迷惑すぎです
オプションで有効化するならまだしもそれを強制とか 以前あった作者の好みを押し付けるせいで廃れたライブラリと変わらないと思うんです

この特別扱いは最新版では発生しないので 無事改善されたのかと思うのですが名前に関わらず引数が 2 つ以上の関数だと強制改行になりました

foo(a => 1, a => 1)
foo(
(a) => 1,
(a) => 1
);

以前のバージョンでは改行されません

ただ 私みたいな最新版に更新しない人にとっては 改行されるままです
この特殊な扱いは connect だけではなく pipe などいくつかの名前が強制改行対象です
// prettier-ignore を増やしたくないので

connect(...[1, 2])

と書いてみると改行されませんでしたが フォーマッタのために見やすさとパフォーマンスを落とすのはどうなのかなと言う気持ちになります

折り返し

以前からですが 折返し位置は保持されません
メソッドチェーンの部分で改行したくても引数で改行されたりします

fn(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
.method(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
fn(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa).method(
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
);

これもいまいちなのですが こういう issue もありました
こういう同じ系統が並んでいるのに

cy.get(".ready")
.should("have.text", "READY")
.should("have.css", "background-color", "rgb(136, 228, 229)");
cy.get(".pending")
.should("have.text", "PENDING")
.should("have.css", "background-color", "rgb(253, 212, 90)");

1 行にまとめられてしまうとわかりづらいというものです

cy.get(".ready").should("have.text", "READY").should("have.css", "background-color", "rgb(136, 228, 229)");
cy.get(".pending").should("have.text", "PENDING").should("have.css", "background-color", "rgb(253, 212, 90)");

すごくわかります
この経験はけっこうありました

Prettier では行の文字数を指定できますが これを超えるなら折り返してほしいのであって その文字数に収まるなら改行を消してほしいというわけではないのですよね
上で書いた connect の必要なさそうな改行であっても書いた人がそう書いたものなら改行ありで表示したほうがいいと思いますし
オブジェクトなら改行のするしないを保持するので全体でそうしてくれればいいのですけど

テンプレートリテラルの折返し

かなり前からなので以前どこかで書いたかもしれませんが `` の中で ${} が入るとおかしくなります
文字列の一部なので折返し自体がいらないのに折り返そうとするのですよね
折り返すだけならともかく折り返した場所のインデントは文字列内で揃っていてほしいのに揃ってくれません
長くなる `` の直前には // prettier-ignore が必須レベルです

a = `
<div>
<span data-aaaaaaaaa=${x + 1} data-bbbbbbbbbb=${"aaaaaaaaaaaaaa"} data-cccccc=${1 + 1}></span>
</div>
`

a = `
<div>
<span data-aaaaaaaaa=${x + 1} data-bbbbbbbbbb=${"aaaaaaaaaaaaaa"} data-cccccc=${
1 + 1
}></span>
</div>
`;

あと `` の直前のキーワードによっては中身の扱いが変わります
html をつけると

a = html`
<div>
<span data-aaaaaaaaa=${x + 1} data-bbbbbbbbbb=${"aaaaaaaaaaaaaa"} data-cccccc=${1 + 1}></span>
</div>
`
a = html`
<div>
<span
data-aaaaaaaaa=${x + 1}
data-bbbbbbbbbb=${"aaaaaaaaaaaaaa"}
data-cccccc=${1 + 1}
></span>
</div>
`;

一見便利なのですがタグ関数に html をつけるとこうする みたいな部分を使う側が制御できないので厄介な機能にもなります
Prettier のためにタグ関数の名前を変えないといけないです
HTML 扱いさせるために html 変数に代入したり 逆に HTML 扱いされたくないので別の変数に代入したり

複雑なことするならオプション作ればいいのに 頑なにやらないのですよね

タブ

これも以前も書いた気がしますが タブインデントにしてもスペースが入ります
なんのためにタブインデントを使ってるのか理解できてない人が作ってるんじゃないかと思います
これに対してスペースを使うべきじゃないという意見もあったのにやっぱり対応しないと言われてました

aaaaaaa
? something({
x: 1
})
: bbbbbbbb
aaaaaaa
? something({
x: 1,
})
: bbbbbbbb

? の式の最後の } の手前の 2 文字分だけ半角スペースになっています
そして something の中のインデントは 2 段インデントされています

なぜ ? と : を特殊扱いするのか謎です

!function(){
return 1
}()

こういう式の 「!」 の分をスペースインデントしてるならともかくここはしてないのになぜ 「?」 ならしようと思うのか理解できないです
タブのみのルールを崩してまで揃えるメリットは一切ないです

まだこうなるほうがマシです

aaaaaaa
?
something({
x: 1,
})
: bbbbbbbb

複雑な式

改行挟むとどう書いても見やすいとは言えなさそうなこの式

const result = (
deg({
from: args.from,
to: args.to
}) + 360
) % 360

フォーマットするとこうなりました

const result =
(deg({
from: args.from,
to: args.to,
}) +
360) %
360;

両方ともの 360 に 「え そこに来るの?」 って言いたいです

カッコの位置

カッコが要らないところにカッコが付きます

a * b / c
(a * b) / c;

物によっては b / c の方にカッコがあったほうがいいのもあるので ときどき気になるところです

付ける方向なのかと思えば消されることもあります

if (a || (b || c) || d) {
}
if (a && (b && c) && d) {
}
if (a || b || c || d) {
}
if (a && b && c && d) {
}

同じ結果とは言え 意味的にカッコを付けたいことはあるので外されるのも困るのですよね

まとめ

見直すと 基本余計なことをしなくて元のコードをリスペクトした上で変更してくれるといいのですよね
強制するなら好みが分かれる部分はオプション化してほしいです

ここで上げた以外にも 私は困ってないけど他の人は気に入らないみたいのは issue を見てると色々あります
そういうのは結構コメント数が多いので 10 とか 30 以上で検索してみると見つけやすいです
https://github.com/prettier/prettier/issues?q=is%3Aissue+comments%3A%3E30+