考え方
Prettierは、意見の強いコードフォーマッターです。このドキュメントでは、そのいくつかの選択肢について説明します。
Prettierが考慮している点
正確性
Prettierの最初の要件は、フォーマット前と全く同じ動作をする有効なコードを出力することです。Prettierがこれらの正確性のルールに従わないコードがありましたら報告してください。それは修正が必要なバグです!
文字列
ダブルクォートかシングルクォートか?Prettierは、エスケープの数が最も少ない方を選択します。"It's gettin' better!"
、'It\'s gettin\' better!'
ではありません。同数の場合、または文字列に引用符が含まれていない場合は、Prettierはデフォルトでダブルクォートを使用します(ただし、singleQuoteオプションで変更できます)。
JSXには、引用符に関する独自のオプションがあります。jsxSingleQuote。JSXはHTMLに由来しており、属性の引用符としてダブルクォートが広く使用されています。ブラウザの開発者ツールも、ソースコードがシングルクォートを使用している場合でも、常にHTMLをダブルクォートで表示するというこの規則に従います。個別のオプションにより、JSにはシングルクォート、"HTML"(JSX)にはダブルクォートを使用できます。
Prettierは、文字列のエスケープ方法を維持します。たとえば、"🙂"
は"\uD83D\uDE42"
にはフォーマットされず、その逆も同様です。
空行
空行は自動生成が非常に難しいことが判明しました。Prettierが採用する方法は、空行を元のソースコードのまま保持することです。さらに2つのルールがあります。
- Prettierは複数の空行を1つの空行に縮約します。
- ブロックの先頭と末尾(およびファイル全体)にある空行は削除されます。(ただし、ファイルは常に1つの改行で終わります。)
複数行オブジェクト
デフォルトでは、Prettierの印刷アルゴリズムは、式が収まる場合は1行で印刷します。ただし、JavaScriptではオブジェクトはさまざまな用途に使用され、複数行のままにすることで可読性が向上する場合があります。オブジェクトリスト、ネストされた設定、スタイルシート、キー付きメソッドなどがその例です。これらのすべての場合に適したルールを見つけることができませんでした。そのため、Prettierは、元のソースコードで{
と最初のキーの間に改行がある場合、オブジェクトを複数行のままにします。その結果、長い1行のオブジェクトは自動的に展開されますが、短い複数行のオブジェクトは決して縮約されません。
ヒント:1行にまとめたい複数行オブジェクトがある場合
const user = {
name: "John Doe",
age: 30,
};
…必要なのは、{
の後の改行を削除することだけです。
const user = { name: "John Doe",
age: 30
};
…そして、Prettierを実行します。
const user = { name: "John Doe", age: 30 };
そして、もう一度複数行にしたい場合は、{
の後に改行を追加します。
const user = {
name: "John Doe", age: 30 };
…そして、Prettierを実行します。
const user = {
name: "John Doe",
age: 30,
};
♻️ フォーマットの可逆性に関する注意
オブジェクトリテラルの半手動フォーマットは、実際には機能ではなく回避策です。当時、適切なヒューリスティックが見つからず、緊急の修正が必要だったため実装されました。しかし、一般的な戦略として、Prettierはそのような非可逆的なフォーマットを避け、チームは依然としてこの動作を完全に削除するか、少なくとも適用される状況の数を減らすことができるヒューリスティックを探しています。
可逆的とはどういう意味ですか?オブジェクトリテラルが複数行になると、Prettierはそれを1行に戻しません。Prettierでフォーマットされたコードで、オブジェクトリテラルにプロパティを追加し、Prettierを実行してから考えを変え、追加したプロパティを削除し、もう一度Prettierを実行すると、最初のものと同一ではないフォーマットになる可能性があります。この無意味な変更は、コミットに含まれる可能性さえあり、まさにPrettierが作成された目的とは逆の状況です。
デコレーター
オブジェクトと同様に、デコレーターはさまざまな用途に使用されます。デコレートする行の上にデコレーターを書く方が意味のある場合もあれば、同じ行にある方が良い場合もあります。これに対する適切なルールを見つけることができませんでした。そのため、Prettierは(行に収まる場合)デコレーターを記述したとおりに配置します。これは理想的ではありませんが、難しい問題に対する実際的な解決策です。
@Component({
selector: "hero-button",
template: `<button>{{ label }}</button>`,
})
class HeroButtonComponent {
// These decorators were written inline and fit on the line so they stay
// inline.
@Output() change = new EventEmitter();
@Input() label: string;
// These were written multiline, so they stay multiline.
@readonly
@nonenumerable
NODE_TYPE: 2;
}
1つの例外があります。クラスです。クラスのデコレーターをインラインにすることは決して意味がないと考えているため、常に独自の行に移動されます。
// Before running Prettier:
@observer class OrderLine {
@observable price: number = 0;
}
// After running Prettier:
@observer
class OrderLine {
@observable price: number = 0;
}
注:Prettier 1.14.x以前のバージョンでは、デコレーターを自動的に移動しようとしていたため、古いPrettierバージョンをコードで実行したことがある場合は、不整合を避けるために、いくつかデコレーターを手動で結合する必要がある場合があります。
@observer
class OrderLine {
@observable price: number = 0;
@observable
amount: number = 0;
}
最後に1つ。TC39はデコレーターをexport
の前後に置くかどうかをまだ決定していません。それまでは、Prettierは両方ともサポートします。
@decorator export class Foo {}
export @decorator class Foo {}
テンプレートリテラル
テンプレートリテラルには、補間を含めることができます。補間の内部に改行を挿入するかどうかを決定することは、テンプレートのセマンティックな内容に依存するため、残念ながら困難です。たとえば、自然言語の文の途中に改行を挿入することは、通常望ましくありません。Prettierはそれ自体でこの決定を行うための十分な情報を持っていないため、オブジェクトで使用されているものと同様のヒューリスティックを使用します。補間式を複数の行に分割するのは、その補間に既に改行があった場合のみです。
つまり、次のようないくつかのリテラルは、プリント幅を超えても複数行に分割されません。
`this is a long message which contains an interpolation: ${format(data)} <- like this`;
Prettierに補間を分割させたい場合は、${...}
内に改行があることを確認する必要があります。そうでなければ、長さがどれだけ長くても、すべてを1行に保ちます。
チームとしては、このように元のフォーマットに依存したくありませんが、現時点ではこれが最良のヒューリスティックです。
セミコロン
これは、noSemiオプションの使い方についてです。
以下のコード例を考えてみてください。
if (shouldAddLines) {
[-1, 1].forEach(delta => addLine(delta * 20))
}
上記のコードはセミコロンがなくても問題なく動作しますが、Prettierは実際にはこれを以下のように変換します。
if (shouldAddLines) {
;[-1, 1].forEach(delta => addLine(delta * 20))
}
これは、間違いを防ぐためです。Prettierがそのセミコロンを挿入せず、次の行を追加しなかった場合を想像してみてください。
if (shouldAddLines) {
+ console.log('Do we even get here??')
[-1, 1].forEach(delta => addLine(delta * 20))
}
うっかり!実際には上記は以下を意味します。
if (shouldAddLines) {
console.log('Do we even get here??')[-1, 1].forEach(delta => addLine(delta * 20))
}
その`[`の前にセミコロンがあれば、このような問題は決して発生しません。これにより、行が他の行から独立するため、ASIルールを考慮せずに行を移動したり追加したりできます。
この方法は、セミコロンを使用しないスタイルを使用するstandardでも一般的です。
プログラムに現在セミコロン関連のバグがある場合、Prettierは**自動的にバグを修正しません**。Prettierはコードを再フォーマットするだけであり、コードの動作を変更するものではないことを忘れないでください。開発者が`(`の前にセミコロンを置くのを忘れた、以下のバグのあるコードを例として考えてみましょう。
console.log('Running a background task')
(async () => {
await doBackgroundWork()
})()
これをPrettierに入力しても、コードの動作は変わりません。代わりに、実行時のコードの動作を示すように再フォーマットされます。
console.log("Running a background task")(async () => {
await doBackgroundWork();
})();
プリント幅
printWidthオプションは、厳格なルールというよりも、Prettierに対するガイドラインです。許容される最大行長の制限ではありません。行の長さをどの程度にしたいかという目安をPrettierに伝える方法です。Prettierは、より短く、より長い行を作成しますが、一般的に指定されたプリント幅を満たすように努めます。
非常に長い文字列リテラル、正規表現、コメント、変数名など、行をまたいで分割できない(Prettierが行わないコード変換を使用しない限り)エッジケースがいくつかあります。または、コードを50レベルもネストすると、行の大部分はインデントになるのは当然です :)
それとは別に、Prettierが意図的にプリント幅を超える場合がいくつかあります。
インポート
Prettierは、長い`import`文を複数の行に分割できます。
import {
CollectionDashboard,
DashboardPlaceholder,
} from "../components/collections/collection-dashboard/main";
次の例はプリント幅に収まりませんが、Prettierはそれでも1行で出力します。
import { CollectionDashboard } from "../components/collections/collection-dashboard/main";
これは一部の人にとって予期しないかもしれませんが、`import`を単一要素で1行に保つことがよくある要望だったため、このようにしています。`require`呼び出しにも同じことが適用されます。
テスト関数
もう一つのよくある要望は、長すぎる場合でも、長いテストの説明を1行に保つことです。このような場合、引数を新しい行に折り返してもあまり効果がありません。
describe("NodeRegistry", () => {
it("makes no request if there are no nodes to prefetch, even if the cache is stale", async () => {
// The above line exceeds the print width but stayed on one line anyway.
});
});
Prettierは、`describe`、`it`、`test`などの一般的なテストフレームワーク関数に対して特別な処理を行います。
JSX
JSXが関与する場合、Prettierは他のJSとは少し異なる方法で出力を生成します。
function greet(user) {
return user
? `Welcome back, ${user.name}!`
: "Greetings, traveler! Sign up today!";
}
function Greet({ user }) {
return (
<div>
{user ? (
<p>Welcome back, {user.name}!</p>
) : (
<p>Greetings, traveler! Sign up today!</p>
)}
</div>
);
}
その理由は2つあります。
まず、多くの人が既にJSXを括弧で囲んでおり、特に`return`文ではそうです。Prettierはこの一般的なスタイルに従います。
第二に、代替のフォーマットにより、JSXの編集が容易になります。セミコロンを残してしまうのは簡単です。通常のJSとは異なり、JSXに残されたセミコロンは、ページに表示されるプレーンテキストとして扱われる可能性があります。
<div>
<p>Greetings, traveler! Sign up today!</p>; {/* <-- Oops! */}
</div>
コメント
コメントの*内容*に関しては、Prettierは実際にはあまりできません。コメントには、散文からコメントアウトされたコード、ASCII図まで、あらゆるものが含まれる可能性があります。コメントには何でも含まれる可能性があるため、Prettierはフォーマットや折り返し方法を認識できません。そのため、コメントはそのまま残されます。例外はJSDocスタイルのコメント(各行が`*`で始まるブロックコメント)で、Prettierはインデントを修正できます。
次に、コメントをどこに配置するかという問題があります。これは非常に難しい問題です。Prettierはコメントを元の場所にできるだけ近い場所に保持しようとしますが、コメントはほぼどこにでも配置できるため、簡単な作業ではありません。
一般的に、行末ではなく、**独自の行にコメントを配置すると**、最良の結果が得られます。`// eslint-disable-line`よりも`// eslint-disable-next-line`を優先してください。
`eslint-disable-next-line`や`$FlowFixMe`などの「マジックコメント」は、Prettierが式を複数の行に分割するため、手動で移動する必要がある場合があります。
以下のコード例を考えてみてください。
// eslint-disable-next-line no-eval
const result = safeToEval ? eval(input) : fallback(input);
次に、別の条件を追加する必要があります。
// eslint-disable-next-line no-eval
const result = safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
Prettierは上記を以下のように変換します。
// eslint-disable-next-line no-eval
const result =
safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
つまり、`eslint-disable-next-line`コメントはもはや有効ではありません。この場合、コメントを移動する必要があります。
const result =
// eslint-disable-next-line no-eval
safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
可能であれば、行範囲で動作するコメント(例:`eslint-disable`と`eslint-enable`)または文レベルで動作するコメント(例:`/* istanbul ignore next */`)を優先してください。これらはさらに安全です。 `eslint-plugin-eslint-comments`を使用して、`eslint-disable-line`と`eslint-disable-next-line`コメントの使用を禁止することもできます。
非標準構文に関する免責事項
Prettierは、ECMAScriptの早期段階の提案や、仕様で定義されていないMarkdown構文拡張など、非標準構文を認識してフォーマットすることがよくあります。このような構文のサポートは、最善を尽くした実験的なものと見なされています。非互換性はどのリリースでも導入される可能性があり、破壊的変更とは見なされません。
Prettierが関与しないもの
Prettierはコードを出力するだけです。変換しません。これはPrettierの範囲を限定するためです。出力に焦点を当て、それを本当にうまく行いましょう!
以下は、Prettierの範囲外にあるものの例です。
- 単一引用符または二重引用符の文字列をテンプレートリテラルに変換したり、その逆を行うこと。
- `+`を使用して、長い文字列リテラルをプリント幅に合う部分に分割すること。
- `{}`と`return`をオプションの場合に追加/削除すること。
- `?:`を`if`-`else`文に変換すること。
- インポート、オブジェクトキー、クラスメンバー、JSXキー、CSSプロパティ、その他何でもソート/移動すること。上記のように変換するのではなく、出力することであることに加えて、ソートは副作用(例:インポートの場合)の可能性があり、最も重要な正確性という目標の検証を困難にするためです。