メインコンテンツへスキップ

Prettier 1.3

· 1分で読める
非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

この記事では、FacebookにおけるPrettierの採用状況の最新情報、TypeScriptサポートの進捗状況、そしてPrettier 1.3.0リリースに含まれる改善点と修正点について詳しく説明します。

Facebookにおける採用状況の最新情報

私(@vjeux)がPrettierの開発に取り組み始めた当初からの目標は、Facebookの全コードベースを変換することでした。現在の進捗状況とそのプロセスについてお伝えします。

最初にPrettierを採用したプロジェクトはJest、React、immutable-jsでした。これらは数百ファイル規模の小さなコードベースで、独自のインフラを持ち、専任の開発者が数名携わっています。

続いてOculusとNuclideがコードベースの変換を実施しました。数千ファイル規模で数十名の専任コントリビューターが関わる大規模なプロジェクトですが、基本的なアプローチは初期プロジェクトと同様で、一括コード変換により完了しました。

しかしFacebook全体のコードベースはこれよりはるかに大規模であり、全ファイルを一括変換したり、開発者にコードベースが突然再フォーマットされることを納得してもらうのは現実的ではありません。そのため段階的なアプローチが必要でした。

採用スケールの拡大

コード片に対してPrettierを実行するのは負荷の高い操作であり、無関係な変更が大量に発生してプルリクエストが乱雑に見えるほか、進行中の全プルリクエストでマージコンフリクトが発生します。したがってファイルがフォーマットされた後は、その状態を維持するためのあらゆる対策を講じる必要があります

  • ファイルをプリティプリントする際、@flowと同様に最初のブロックコメントに@formatを追加する

  • @formatが存在する場合、ファイルが正しくプリティプリントされているかを自動修正付きでチェックするlintルールを設定する

    • Nuclide実行時にはインライン警告として表示され、修正ボタンが表示される
    • プルリクエスト送信時にはlint失敗が表示され、[Yn]プロンプトでEnterキーを押すだけで対応可能
  • デフォルトのコードテンプレートを更新し、ヘッダーに@formatを追加する

  • Nuclide内でcmd-shift-c経由でコードフォーマットを実行する際、自動的に@formatヘッダーを挿入する

  • ヘッダーに@formatが存在する場合、max-lenなどの全スタイル関連ルールを無効化する

  • 設定済みの状態でフォルダ全体にPrettierを実行するワンライン操作スクリプトを用意する

  • コードベース変換を希望する開発者向けに、手順とベストプラクティスをまとめた優れたガイドを提供する

  • Prettierの新リリースをプッシュする際、警告発生を防ぐため@format付き全ファイルに対して再実行する

  • @format付きファイル数の経時的な追跡機能を追加する

1週間半前にこれらの仕組みをすべて整えたところ、予想を超える反響がありました。各チームから多くの開発者が自主的にコードベースをPrettierに変換し、現在ではFacebookコードベースの15%が変換済みです!

Prettier開発を始めた当初、開発者がフォーマット解決ツールを切望しているという直感はありました。しかしツール環境が整うと、これほど多くの開発者がコードベース変換に殺到するとは予想していませんでした!このプロジェクトが単なるギミックではなく、実際に役立つツールであることの確かな証左です。

TypeScriptサポートの進捗状況

最も要望の多い機能であるTypeScriptサポートの実現に向け、@despairblue@azz@JamesHenryが尽力しています。TypeScriptテストスイートの11,000ファイル中2,000ファイルがまだ適切にプリントされていません。進捗は https://github.com/prettier/prettier/issues/1480 で追跡可能ですので、ぜひ協力をお願いします!

Flow

Flowのジェネリック型に末尾カンマを追加 (#1381)

--trailing-comma=allオプションは可能な箇所全てに末尾カンマを追加する設計ですが、見落としによりFlowのジェネリック型で適用されていませんでした。

// Before
type Errors = Immutable.Map<
Ahohohhohohohohohohohohohohooh,
Fbt | Immutable.Map<ErrorIndex, Fbt>
>;

// After
type Errors = Immutable.Map<
Ahohohhohohohohohohohohohohooh,
Fbt | Immutable.Map<ErrorIndex, Fbt>,
>;

Flowジェネリック型でのnullable型のインライン化 (#1426)

コードを正しく出力する段階の次は、実際の開発者が書くスタイルに近づける微調整です。オプショナルなFlow型をインライン化するのは、見た目を改善する小さな工夫です。

// Before
type Cursor = Promise<
?{
newCursor?: number,
formatted: string,
}
>;

// After
type Cursor = Promise<?{
newCursor?: number,
formatted: string,
}>;

StringLiteralTypeAnnotationsにおけるFlow宣言の改行許可 (#1437)

80列を超える場合の改行箇所は常に改善の余地があります。今回対象となったのは定数文字列として型を宣言するケースです。

// Before
export type AdamPlacementValidationSingleErrorKey = 'SOME_FANCY_TARGETS.GLOBAL_TARGET';

// After
export type AdamPlacementValidationSingleErrorKey =
'SOME_FANCY_TARGETS.GLOBAL_TARGET';

Flowジェネリック型のデフォルト引数で=の前後にスペースを追加 (#1476)

Flowコードの表示を改善する別の事例です。関数のデフォルト引数では=の前後にスペースを入れますが、Flowジェネリック型では適用されていませんでした。

// Before
class PolyDefault<T=string> {}

// After
class PolyDefault<T = string> {}

単一引数のFlow関数で括弧がない場合の改行禁止 (#1452)

この変更について何か書こうと思ったのですが…ちょっと説明しにくい挙動ですね!

// Before
const selectorByPath:
Path
=> SomethingSelector<
SomethingUEditorContextType,
SomethingUEditorContextType,
SomethingBulkValue<string>
> = memoizeWithArgs(/* ... */)

// After
const selectorByPath: Path => SomethingSelector<
SomethingUEditorContextType,
SomethingUEditorContextType,
SomethingBulkValue<string>
> = memoizeWithArgs(/* ... */);

Flowオプショナル型の括弧処理を修正 (#1357)

オプショナルなFlow型の括弧処理が寛容すぎました。Facebookコードベース全体で1ケースのみ、意味が変わるコードを生成していました。修正に伴い、括弧なしで記述可能な型リストを厳格化しました。

// Before
type X = ?(number, number) => number => void;

// After
type X = (?(number, number) => number) => void;

FlowShorthandWithOneArgでの末尾カンマスキップ (#1364)

アロー関数型の引数で括弧なしに末尾カンマを追加すると構文エラーになります。Facebookコードベースでこのケースを1件検出しましたが、非常に稀な事例です。

// Before
type IdeConnectionFactory =
child_process$ChildProcess,
=> FlowIDEConnection = defaultIDEConnectionFactory;

// After
type IdeConnectionFactory =
child_process$ChildProcess
=> FlowIDEConnection = defaultIDEConnectionFactory;

Flowオブジェクトプロパティの並び順を再整列 (#1451)

ASTの構造が都合悪い事例です。型内部の要素リストではなく、通常キーと配列キーが別グループに分かれる構造になっていました。元の順序を維持するため、ついにソースコードから直接読み取る実装に変更しました。

// Before
type Foo = {
[key: string]: void,
alpha: "hello",
beta: 10
};

// After
type Foo = {
alpha: 'hello',
[key: string]: void,
beta: 10
}

テンプレートリテラル

テンプレートリテラル内の適切なインデント処理 (#1385)

テンプレートリテラルとPrettierの長年の課題は${}内のコードインデントでした。バッククォートのインデント基準だと結果が不自然だったため、${のインデント基準に変更しました。これによりGraphQLクエリが見違えるほど整いました!

// Before
Relay.createContainer({
nodes: ({ solution_type, time_frame }) => Relay.QL`
fragment {
__typename
${OptimalSolutionsSection.getFragment("node", {
solution_type,
time_frame
})}
}
`
})
// After
Relay.createContainer({
nodes: ({ solution_type, time_frame }) => Relay.QL`
fragment {
__typename
${OptimalSolutionsSection.getFragment("node", {
solution_type,
time_frame
})}
}
`
})

単一テンプレートリテラル引数の呼び出しでインデントしない (#873)

テンプレートリテラルは内部のスペースが意味を持つため、再インデントが困難です。単一テンプレートリテラル引数の扱いが不明だったため何も処理せず放置していましたが、誤ったインデントとの報告が相次いだためインライン化に変更しました。主要ユースケースをカバーできることを願っています。

// Before
insertRule(
`*, *:before, *:after {
box-sizing: inherit;
}`,
);

// After
insertRule(`*, *:before, *:after {
box-sizing: inherit;
}`);

テンプレートリテラルにおけるWindows改行コードの修正 (#1439)

Prettierでは多くの箇所で改行コードを扱っており、\n\r\nの両方を注意深く処理していましたが、テンプレートリテラルで対応を忘れていました。この問題を修正しました。

// Before
const aLongString = `

Line 1

Line 2

Line 3

`;

// After
const aLongString = `
Line 1
Line 2
Line 3
`;

アロー関数本体でのインラインテンプレートリテラル (#1485)

タグ付きテンプレートリテラル(例: graphql`query`)は既にインライン化していましたが、通常のテンプレートリテラルでは対応していませんでした。興味深いことに、コード自体は対応可能な設計でしたがTemplateLiteralの代わりにTemplateElementを使用していたため実装漏れが発生していました :(

// Before
const inlineStore = preloadedState =>
`
<script>
window.preloadedState = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
`

// After
const inlineStore = preloadedState => `
<script>
window.preloadedState = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
`

三項演算子

特殊なネスト三項演算子に丸括弧を追加 (#1386)

ネスト三項演算子の処理では、最も一般的な形式であるif-then-else型(cond1 ? elem1_if : cond2 ? elem2_if : elem_else)に焦点が当てられていました。しかし?:の配置を変えることで別のパターンが生まれます。見た目は似ていますが意味が異なり、混乱を避けるため非標準形式には丸括弧を追加します。

// Before
cond1 ? cond2 ? elem2_if : elem2_else : elem1_else

// After
cond1 ? (cond2 ? elem2_if : elem2_else) : elem1_else

アロー関数内の三項演算子に改行が発生しない場合のみ丸括弧を追加 (#1450)

ESLintルールno-confusing-arrowsでは、括弧のないアロー関数内の三項演算子に丸括弧を追加するよう推奨しています。以下の二つのコードは混同しやすいためです:

var x = a => 1 ? 2 : 3;
var x = a <= 1 ? 2 : 3;

一行のコードではこの対策は理にかなっていますが、複数行に分割された場合、インデントが明確であれば丸括弧は不要です。そのため、曖昧さ解消が必要な場合のみ追加するように変更しました。

// Before
var x = a => (1 ? 2 : 3);
var x = a =>
(1
? 2
: 3);

// After
var x = a => (1 ? 2 : 3);
var x = a =>
1
? 2
: 3;

JavaScript全般の改善

オブジェクト引数を取る関数宣言のインライン化 (#1173)

これはReactのStateless Functional Components(SFC)で頻繁に要望されていた機能です。多くのSFCを使用している場合、この変更は大きく影響する可能性があります。

// Before
const X = (
props: {
a: boolean,
},
) => <div />;

// After
const X = (props: {
a: boolean,
}) => <div />;

関数引数内のインラインオブジェクトを優先的に改行 (#1453)

関数の引数よりも戻り値の型を先に改行するのが一般的だと早期に判明しました。しかし単一の分割代入引数をインライン化するコードがこの前提を破り、不自然なコードを生成していました。今回の修正により、オブジェクト型注釈付きの単一引数でもインライン化が可能になります。

// Before
class X {
async onDidInsertSuggestion({editor, triggerPosition, suggestion}): Promise<
void
> {
}
}

// After
class X {
async onDidInsertSuggestion({
editor,
triggerPosition,
suggestion
}): Promise<void> {
}
}

空の配列・オブジェクトで改行しない (#1440)

これは長年懸案だった簡単な修正ですが、貴重な気付きをもたらしました:[]{}で改行が発生する報告がある度に、別の問題を修正すれば解決できたのです。これはエッジケースを発見する優れた方法でした。幸い、最近の事例では単なる改行問題のみが残ったため、遂に修正を実施します。

// Before
const a = someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeLong.Expression || [
];

// After
const a = someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeLong.Expression || [];

[0]で改行しない (#1441)

配列アクセスでの不自然な改行問題が多数報告されていました。汎用的な解決策はまだありませんが、頻出ケースである[0]に対して暫定修正を追加しました。

// Before
queryThenMutateDOM(() => {
title = SomeThing.call(root, "someLongStringThatPushesThisTextReall")[
0
];
});

// After
queryThenMutateDOM(() => {
title = SomeThing.call(
root,
"someLongStringThatPushesThisTextReall",
)[0];
});

do-while条件文のインデント修正 (#1373)

do-while条件文で正しいインデントロジックが適用されていませんでしたが、指摘を受けて修正しました。

// Before
do {}
while (someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD);

// After
do {}
while (
someVeryLongStringA &&
someVeryLongStringB &&
someVeryLongStringC &&
someVeryLongStringD
);

最終引数としてのインラインコメント保持 (#1390)

JSX属性や関数引数の最後にコメントが現れる場合の検出処理を1ケース見落としていたため、コメントが閉じタグの後ろに配置される問題がありました。JSXの場合、この問題はコードの意味を変更する可能性がありました。幸い、通常はコメントアウトされたコードをコミットしないため本番環境への影響はありませんでしたが、コーディング中の体験としては好ましくありませんでした。

// Before
const x = (
<div
attr1={1}>
// attr3={3}
{children}
</div>
);

// After
const x = (
<div
attr1={1}
// attr3={3}
>
{children}
</div>
);

アロー関数呼び出しで返されるクラス式の改行 (#1464)

バージョン1.0では、アロー関数内のクラスをインライン表示するように変更しましたが、クラスが複雑な場合に適さないことが判明したため、この変更を元に戻します。スタイルが行ったり来たりするような場当たり的な判断は極力避けるよう努めていますが、時には誤りを修正するためにこうした調整を行うことも必要だと判断しました。

// Before
export default (ViewComponent: Function, ContainerComponent: Function) =>
class extends React.Component {
static propTypes = {};
};

// After
export default (ViewComponent: Function, ContainerComponent: Function) =>
class extends React.Component {
static propTypes = {};
};

EmptyStatementを含むブロック内の空行を修正 (#1375)

この問題はファジングによって発見されました。実際のコードで発生することは稀ですが、修正されたことを知っておく価値はあります!

// Input
if (a) {
b;


;
}

// Before
if (a) {
b;

}

// After
if (a) {
b;
}