|
| 1 | +--- |
| 2 | +title: Prettier との違い |
| 3 | +description: Prettier との違いを深く解説 |
| 4 | +--- |
| 5 | + |
| 6 | +Prettier との間にいくつかの相違点が存在します。 |
| 7 | + |
| 8 | +### Prettierは有効なJavaScript識別子である一部のオブジェクトプロパティの引用符を外しません |
| 9 | + |
| 10 | +PrettierとBiomeは、有効なJavaScript識別子であるオブジェクトおよびクラスプロパティの引用符を外します。 |
| 11 | +Prettierは、特に[ES5において有効な識別子のみ引用符を外します](https://github.com/prettier/prettier/blob/a5d502513e5de4819a41fd90b9be7247146effc7/src/language-js/utils/index.js#L646)。 |
| 12 | + |
| 13 | +ES2015が広まっている現在のエコシステムにおいて、これは古い制限です。 |
| 14 | +そのため、私たちはES2015以降の全ての有効なJavaScript識別子の引用符を外すことにしました。 |
| 15 | + |
| 16 | +プロジェクトが使用するECMAScriptバージョンを設定するための設定を導入することは可能な解決策かもしれません。 |
| 17 | +そのバージョンに基づいて引用符を外す動作を調整できます。 |
| 18 | +ECMAScriptのバージョンをES5に設定することで、Prettierの挙動に一致させることができます。 |
| 19 | + |
| 20 | +入力 |
| 21 | + |
| 22 | +```js title="example.js" |
| 23 | +const obj = { |
| 24 | + 'a': true, |
| 25 | + b: true, |
| 26 | + "𐊧": true, |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +差分 |
| 31 | + |
| 32 | +```js title="exmaple.js" del={4} ins={5} |
| 33 | +const obj = { |
| 34 | + a: true, |
| 35 | + b: true, |
| 36 | + "𐊧": true, |
| 37 | + 𐊧: true, |
| 38 | +}; |
| 39 | +``` |
| 40 | + |
| 41 | + |
| 42 | +### Prettierは計算プロパティでの代入で一貫性のない挙動を示します |
| 43 | + |
| 44 | +PrettierとBiomeは、特に条件文などで代入式を括弧で囲みます。 |
| 45 | +これにより、Biomeは比較式であるべきコードを識別できます。 |
| 46 | + |
| 47 | +Prettierは、オブジェクトの計算プロパティでの代入では括弧を追加する一方で、クラスの計算プロパティではそれを行いません。以下の例で示されます: |
| 48 | + |
| 49 | +入力 |
| 50 | + |
| 51 | +```js title="example.js" |
| 52 | +a = { |
| 53 | + [x = 0]: 1, |
| 54 | +} |
| 55 | + |
| 56 | +class C { |
| 57 | + [x = 0] = 1 |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +差分 |
| 62 | + |
| 63 | +```js title="example.js" del={2} ins={3} |
| 64 | +a = { |
| 65 | + [(x = 0)]: 1, |
| 66 | + [x = 0]: 1, |
| 67 | +}; |
| 68 | + |
| 69 | +class C { |
| 70 | + [x = 0] = 1; |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +[プレイグラウンドのリンク](https://biomejs.dev/playground?enabledLinting=false&code=YQAgAD0AIAB7AAoAIAAgAFsAeAAgAD0AIAAwAF0AOgAgADEALAAKAH0ACgAKAGMAbABhAHMAcwAgAEMAIAB7AAoAIAAgACAAIABbAHgAIAA9ACAAMABdACAAPQAgADEACgB9AAoA) |
| 75 | + |
| 76 | +一貫性を保つために、私たちは Prettier のformatに合わせることなく括弧を省略することにしました。 |
| 77 | +代替案としては、オブジェクトまたはクラスの計算プロパティでの代入を常に括弧で囲むことができます。 |
| 78 | + |
| 79 | +### Prettierは必要ない場合でもアロー関数の型パラメータに末尾のカンマを追加します |
| 80 | + |
| 81 | +特定のケースでは、JSX要素と区別するために、アロー関数の型パラメータリストに末尾のカンマが必要となります。 |
| 82 | +型パラメータにデフォルト値が提供されている場合、末尾のカンマは必要ありません。 |
| 83 | +ここでは、必要な場合にのみ末尾のカンマを追加するというPrettierの元々の意図を尊重するために、現在のformatの差分を受け入れました。 |
| 84 | + |
| 85 | +入力 |
| 86 | + |
| 87 | +```tsx title="example.tsx" |
| 88 | +<T = unknown>() => {}; |
| 89 | +``` |
| 90 | + |
| 91 | +差分 |
| 92 | + |
| 93 | +```tsx title="example.tsx" del={1} ins={2} |
| 94 | +<T = unknown,>() => {}; |
| 95 | +<T = unknown>() => {}; |
| 96 | +``` |
| 97 | + |
| 98 | +### Prettier は、括弧で囲まれた non-null アサーションを含むオプショナルチェーンに対して一貫性のない動作をします |
| 99 | + |
| 100 | +_TypeScript_ では、non-null アサーション演算子 `!` を使用して、値が null でないことをアサートできます。 |
| 101 | +オプショナルチェーンに適用される場合、アサーションは括弧の存在に関係なくチェーン全体に適用されます。つまり、`(a.?.b)!` と `a.?.b!` は同じ結果になるはずです。 |
| 102 | + |
| 103 | +Prettier は、先のコード例をどちらも適切にformatされているとみなします。つまり、括弧の有無は維持され、コードを正規化しようとしません。 |
| 104 | + |
| 105 | +さらに、Prettier は、括弧がnon-null アサーション演算子を囲んでいる場合でもかっこを削除しません。代わりに、演算子を括弧の外に移動します。 |
| 106 | + |
| 107 | +入力 |
| 108 | + |
| 109 | +```ts title="example.ts" |
| 110 | +a.?.b! |
| 111 | +(a.?.b)! |
| 112 | +(a.?.b!) |
| 113 | +``` |
| 114 | + |
| 115 | +差分 |
| 116 | + |
| 117 | +```ts title="example.ts" del={2, 4} ins={3, 5} |
| 118 | +a.?.b! |
| 119 | +(a.?.b)! |
| 120 | +a.?.b! |
| 121 | +(a.?.b)! |
| 122 | +a.?.b! |
| 123 | +``` |
| 124 | + |
| 125 | + |
| 126 | +### Prettierは無効な構文をformatします |
| 127 | + |
| 128 | +JavaScriptおよびTypeScriptのためにPrettierで利用されているBabel parserの解析は厳密なものではなく、[いくつかの構文エラーを無視](https://github.com/prettier/prettier/blob/e4a74c05f4502dd4ec70495c3130ff08ab088e05/src/language-js/parse/babel.js#L177-L218) することがあります。 |
| 129 | +Biomeのparserは、Prettierのparserよりも厳密に構文を解析します。 |
| 130 | +たとえば、以下の構文エラーを正確に識別します: |
| 131 | + |
| 132 | +- 関数には重複する修飾子を持つことはできません |
| 133 | +- プロパティの修飾子を無効な順序で指定することはできません |
| 134 | +- 関数宣言では関数の処理を定義することができません |
| 135 | +- 抽象クラス以外で抽象プロパティを持つことはできません |
| 136 | +- オプショナルチェーンに代入することはできません |
| 137 | +- インターフェースの型パラメータに `const` 修飾子を設定することはできません |
| 138 | +- トップレベルの return |
| 139 | + |
| 140 | +Prettierでは、これらのエラーを構文エラーとして扱わず、適切なノードでASTが "正しく" 構築されます。 |
| 141 | +そして、これらのノードを通常通り扱い、formatします。 |
| 142 | + |
| 143 | +Biomeでは、構文エラーは`Bogus`ノードとして扱います。`Bogus`ノードは、有効なノード、無効なノード、生の文字などを含みます。 |
| 144 | +format時、Biomeは bogusノードを事実上のプレーンテキストとして扱い、そのまま出力します。これは、formatを試みることでコードの意図を変更してしまう恐れがあるためです。 |
| 145 | + |
| 146 | +Prettierのparserは、クラスプロパティの修飾子に対してブール値のフィールドを使用します。これは、各種類の修飾子が1つしか存在できないことを意味します。(アクセス修飾子は単一の文字列として格納されます)。 |
| 147 | +formatの時には、ブール値のリストを見て、どの修飾子を再び出力するかを決定します。一方、Biomeは修飾子のリストを保持し、重複を含めて解析が可能になります(これが重複修飾子や順序に関する解析エラーを報告できる理由です)。 |
| 148 | +不正な修飾子を含むbogusノードをformatする際は、修飾子のリストはそのまま維持され、不正なテキストをそのまま出力します。 |
| 149 | + |
| 150 | +Biomeでは、この問題に対処するいくつかの方法があります。 |
| 151 | +その1つとしては、format時にBogus なノードを解釈し、有効なノードを構築する方法があります。 |
| 152 | +有効なノードが構築できれば通常通りそのノードをformatし、そうでない場合は現在のように不正なテキストをそのまま出力します。 |
| 153 | +しかし、これを実装するのは少し大変で、formatterに意味のないロジックを導入することになります。 |
| 154 | + |
| 155 | +別の方法としては、意味論的なエラー(重複修飾子、抽象クラス以外での抽象プロパティの使用)を受け入れる "syntactically-valid bogus node(文法的に不正確なノード)" をparserに導入する方法もあります。 |
| 156 | + |
| 157 | +これにより、通常通りノードを構築しつつ(Prettierの挙動と一致させつつ)、新しい種類のbogusノードに構文解析の情報を含めて格納します。 |
| 158 | +format時には、特定のbogusノードでは内部ノードをformatしようと試み、エラーが発生した場合はフォールバックします(既存の `format_or_verbatim` ユーティリティがこれを行っています)。 |
| 159 | +これにより、parserとformatterのロジックを分離しつつ、無効な状態を半有効とみなすような複雑なロジックをparserに実装することが可能になります。 |
| 160 | + |
| 161 | +#### クラスプロパティの重複する修飾子 |
| 162 | + |
| 163 | +入力 |
| 164 | + |
| 165 | +```ts title="example.ts" |
| 166 | +// 複数のアクセシビリティ修飾子 |
| 167 | +class Foo { |
| 168 | + private public a = 1; |
| 169 | +} |
| 170 | + |
| 171 | +// 処理を持つ関数の宣言 |
| 172 | +declare function foo ( ) { } |
| 173 | + |
| 174 | +// abstractの不正な使用 |
| 175 | +class Bar { |
| 176 | + abstract foo ; |
| 177 | +} |
| 178 | + |
| 179 | +// Readonlyの重複 |
| 180 | +class Read { |
| 181 | + readonly readonly x: number; |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +差分 |
| 186 | + |
| 187 | +```ts title="example.ts" del={3, 8, 13, 19} ins={4, 9, 14, 20} |
| 188 | +// 複数のアクセシビリティ修飾子 |
| 189 | +class Foo { |
| 190 | + private a = 1; |
| 191 | + private public a = 1; |
| 192 | +} |
| 193 | + |
| 194 | +// 処理を持つ関数の宣言 |
| 195 | +declare function foo() {}; |
| 196 | +declare function foo ( ) { } |
| 197 | + |
| 198 | +// abstractの不正な使用 |
| 199 | +class Bar { |
| 200 | + abstract foo; |
| 201 | + abstract foo ; |
| 202 | +} |
| 203 | + |
| 204 | +// Readonlyの重複 |
| 205 | +class Read { |
| 206 | + readonly x: number; |
| 207 | + readonly readonly x: number; |
| 208 | +} |
| 209 | + |
| 210 | + |
| 211 | +#### オプショナルチェーンへの代入 |
| 212 | + |
| 213 | +入力 |
| 214 | + |
| 215 | +```js title="example.js" |
| 216 | +(a?.b) = c; |
| 217 | +``` |
| 218 | + |
| 219 | +差分 |
| 220 | + |
| 221 | +```js title="example.js" del={1} ins={2} |
| 222 | +a?.b = c; |
| 223 | +(a?.b) = c; |
| 224 | +``` |
| 225 | + |
| 226 | +#### インターフェイスの型パラメータに対する誤った修飾子 |
| 227 | + |
| 228 | +入力 |
| 229 | + |
| 230 | +```ts title="example.js" |
| 231 | +interface L<in const T> {} |
| 232 | +``` |
| 233 | + |
| 234 | +差分 |
| 235 | + |
| 236 | +```ts title="example.js" del={1} ins={2} |
| 237 | +interface L<const in T> {} |
| 238 | +interface L<in const T> {} |
| 239 | +``` |
| 240 | + |
| 241 | +#### トップレベルのreturn |
| 242 | + |
| 243 | +```js title="example.js" |
| 244 | +return someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD |
| 245 | +``` |
| 246 | + |
| 247 | +```js title="example.js" del={1, 2, 3, 4, 5, 6} ins={7} |
| 248 | +return ( |
| 249 | + someVeryLongStringA && |
| 250 | + someVeryLongStringB && |
| 251 | + someVeryLongStringC && |
| 252 | + someVeryLongStringD |
| 253 | +); |
| 254 | +return someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD |
| 255 | +``` |
| 256 | + |
| 257 | +#### 誤った self-increment と self-decrement |
| 258 | + |
| 259 | +入力 |
| 260 | + |
| 261 | +```js title="example.js" |
| 262 | +(1)++; |
| 263 | +``` |
| 264 | + |
| 265 | +```js title="example.js" del{1} add={2} |
| 266 | +1++; |
| 267 | +(1)++; |
| 268 | +``` |
| 269 | + |
| 270 | +#### 抽象クラスでないクラスでの `abstract` 修飾子の使用 |
| 271 | + |
| 272 | +入力 |
| 273 | + |
| 274 | +```ts title="example.js" |
| 275 | +class C { |
| 276 | + abstract f() : number; |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +差分 |
| 281 | + |
| 282 | + |
| 283 | +```ts title="example.js" del{2} add={3} |
| 284 | +class C { |
| 285 | + abstract f(): number; |
| 286 | + abstract f() : number; |
| 287 | +} |
| 288 | +``` |
0 commit comments