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

Prettier 1.0のリリース

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

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

この記事はvjeuxによって執筆され、 jlongsterによって編集され、 こちらで最初に公開されました

私たちは約2ヶ月前に、コードのフォーマットに時間を浪費する問題を解決する手段として prettierを正式に発表しました。 当初は実験的なプロジェクトでしたが、多くの開発者に受け入れられ、 わずか2ヶ月で約7000のGitHubスターと月間10万を超えるnpm downloadsを獲得しました。

ご存じない方のために説明すると、prettierはコードをASTにコンパイルし、 そのASTを整形出力するJavaScriptフォーマッターです。ブラウザがテキストを折り返すように、 prettierも指定された行長に基づいてコードを折り返します。その結果、誰が書いたかに関わらず完全に一貫性のある 美しいコードが生成されます。これにより、開発者がエディタで手動でコードを調整したり スタイルについて議論したりする時間の浪費が解消されます(gif参照)。

当初は常に有効で美しいコードを生成できるか不明でしたが、良い知らせはこれがもはや未解決の問題ではないことです。 多くのプロジェクト(React、Jest、immutable-js、Haulなど)や企業(Oculus、Cloudflareなど)が JavaScriptコードベースのフォーマットにprettierを採用し、自動フォーマットの利点を実感しています (詳細はこのツイート参照)。 中には数万行に及ぶ全コードベースにprettierを適用した事例もあります。

過去3週間、私たちは全ての未解決イシューを精査し、コードフォーマット方法に関する可能な限りの決定を行い、 既知のバグの大半を修正しました。まだ完璧ではありませんが継続的に改善していく中で、 バージョン1.0をリリースする良いタイミングが来ました!

これはフォーマットを全く変更しないという意味ではなく、変更は最小限になるということです。 例えば三項演算子の調整を検討していますが、 これからはそのような変更は非常に少なくなり、変更がある場合にはメジャーバージョンを上げて対応します。

重要なのはprettierがプロダクション環境で安全に使用できるということです。 私たちは大量のバグを修正し、出力が安定し意味的に正しいことを確認しました。 Prettierはサイドプロジェクトの数ファイルから全コードベースの変換まで、 あらゆる規模で使用できる準備が整っています。 特定のコードフォーマット方法に固執する旅路のどの段階にいるかは、皆さん次第です。

それでは、1.0がprettierにもたらすものをご覧ください!

本リリースに貢献してくださった全てのコントリビューターに感謝します。 またロゴデザインを提供してくださったIan Storm Taylorにも感謝します!

Adam Stankiewicz, Alex Rattray, Alex Stachowiak, Amjad Masad, Andrey Okonetchnikov, António Nuno Monteiro, Artem Sapegin, Benjamin Tan, Bill Mill, Brandon Mills, Brian Holt, Brian Ng, Charles Pick, ChristianHersevoort, Christopher Chedeau, Christopher Moeller, Damien Lebrun, Dan Harper, Dara Hak, David Ascher, David Hong, Davy Duperron, Eric Clemmons, Erick Romero, Esben Petersen, Filipe Fortes, Gregor Adams, Hampus,hlsson, Hawken Rives, Henry Zhu, Ilya Panasenko, James Henry, James Long, Jamie Webb, Jan Kassens, Jed Watson, Jeffrey Horn, Jeremy Morrell, Joe Fiorini, Jon LaBelle, Joseph Savona, Karl Horky, Karl O'Keeffe, Kent C. Dodds, Kevin Gibbons, Kim Joar Bekkelund, Lucas Bento, Lucas Duailibe, MarekMatejkaKCL, Mateusz Zatorski, Michael Ficarra, Michał Pierzchała, Mikael Brevik, Nathan Friedly, Patrick Camacho, Paul,arduner, Rafael Hengles, Raghuvir Kasturi, Rasmus Eneman, Riley Tomasek, Rogelio Guzman, Royce Townsend, Sean Grove, Shigeaki Okazaki, Simen,ekkhus, Simon Lydell, Sorin Muntean, Spencer Dixon, Thai Pangsakulyanont, Tom McKearney, Travis Jefferson, Umidbek Karimov, Vincent Voyer, Vu Tran, Walter Breakell, Yatharth Khatri, azu, daleroy, kalmanb, skratchdot

設定オプション

Prettier は意見のあるコードフォーマッタです。プロジェクト開始当初は、gofmt や refmt のように設定を全く持たないことを意味すると考えていました。しかし進めるにつれ、これが Prettier の採用を妨げ、恩恵を受けるはずの人々が「期待する形式でコードを整形しない」という理由で使わなくなることに気づきました。

代わりに最近の私たちの解釈では、「意見のあるコードフォーマッタ」とは「これを実装しなければ、どれだけ優れていても絶対に使わない」という性質の基本的な構文に関するオプションを提供するものと位置づけています。たとえば私(@vjeux)はセミコロンを使わない standard を絶対に使いません。これは完全に非合理な考え方ですが、多くの人がそうした行動を取るため「スタイル論争」が起きるのです。

依然としてあらゆる構文タイプに対するオプションを導入するわけではありませんが、影響力の大きいものに限って提供します。このカテゴリに該当する主要なオプションとして「タブ vs スペース」と「セミコロンあり vs なし」の2つを特定しました。そこでこれらを Prettier に追加します!

--no-semi (#1129)

実装に尽力してくれた rattrayalex に感謝します!

// Before
console.log();
[1, 2, 3].map(x => x + 1);

// After
console.log()
;[1, 2, 3].map(x => x + 1)

--use-tabs (#1026)

この機能を実装し背景を説明してくれた rhengles に感謝します!

// Before
if (1) {
··console.log(); // Two spaces
}

// After
if (1) {
» console.log(); // One Tab!
}

フォーマット改善

この投稿の残り部分では、1.0に向けてまとめた細かな変更点をすべて記載します。通常は変更履歴を投稿として公開しませんが、Prettierがどれだけ安定してきたかを示し、現在修正している問題の種類を理解していただけると考えています。

括弧なしアロー関数で呼び出しを返す際の改行追加 (#927)

現在のPrettierで最も報告が多い問題の一つは、呼び出し内のアロー関数後に改行が追加されない点でした。これが改善され、改行が追加されるようになりました。注目すべきは、問題報告が「出力が完全に壊れている」ような大きなものから、このような細かい指摘に変わってきたことです。

// Before
const testResults = results.testResults.map(testResult =>
formatResult(testResult, formatter, reporter));

// After
const testResults = results.testResults.map(testResult =>
formatResult(testResult, formatter, reporter)
);

代入文にソフトラインを追加、return文のバイナリ式に括弧を付加 (#871 & #870)

長い論理式のチェーンは、最初の行が変数宣言やreturn文と同じ行にあると不自然に見えます。代わりに次の行に移動し、return文では有効化のため括弧を追加する方が適切です。

// Before
const computedDescriptionLines = (showConfirm &&
descriptionLinesConfirming) ||
(focused && !loading && descriptionLinesFocused) ||
descriptionLines;

// After
const computedDescriptionLines =
(showConfirm && descriptionLinesConfirming) ||
(focused && !loading && descriptionLinesFocused) ||
descriptionLines;


// Before
return !filePath.includes(coverageDirectory) &&
!filePath.endsWith(`.${SNAPSHOT_EXTENSION}`);

// After
return (
!filePath.includes(coverageDirectory) &&
!filePath.endsWith(`.${SNAPSHOT_EXTENSION}`)
);

インライン関数の第一引数をグループ化 (#947)

Prettierで初めて追加された大きなカスタムパターンは、最後の引数としての関数をインラインで出力する機能です。今回はより稀なケースとして第一引数でも同様の処理を実装。現代のライブラリではあまり使われませんが、setTimeoutのようなコアJavaScript関数がこのスタイルを使用しているため、Prettierでのサポートは理にかなっています。

// Before
setTimeout(
function() {
thing();
},
500
);

// After
setTimeout(function() {
thing();
}, 500);

スタイルコンポーネントのJSX出力を改善 (#1144)

テンプレートリテラルのフォーマットは非常に困難で、空白が意味を持ちます。Prettierはリフォーマットによるプログラムの意味変更を避けるため、これらをそのまま保持します。現在の処理方法は最適とは言えず、一般的な解決策のアイデアはありますが、当面はJSX向けにこのような特定の修正を行います。

// Before
<style>
{
`
color: red;
`
}
</style>

// After
<style>{`
color: red;
`}</style>

デコレーターでの同一行ドット記法をサポート (#1029)

MobX 3が導入したメンバー式はインラインで記述する必要がありますが、以前のヒューリスティックは制限が強すぎました。これが修正されました。

class X {
// Before
@action.bound
setPrice(price) {
this.price = price;
}

// After
@action.bound setPrice(price) {
this.price = price;
}
}

単一オブジェクトの分割代入関数をコンパクトに (#1022)

Reactのステートレス関数コンポーネントが主流となっている今、第一引数の分割代入をコンパクトに表示してスペースを節約するのは理にかなっています。

// Before
export default function StatelessFunctionalComponent(
{
searchFilters,
title,
items
}
) {
return <div />;
}

// After
export default function StatelessFunctionalComponent({
searchFilters,
title,
items,
}) {
return <div />
}

カリー化をサポート (#1066)

Reduxではカリー化形式(各引数がネストした関数)での記述が推奨されています。インデントする代わりに、これらをインラインで配置するように変更しました。

// Before
const mw = store =>
next =>
action => {
return next(action)
};

// After
const mw = store => next => action => {
return next(action)
};

JSX文字列のエスケープを尊重 (#1056)

文字列のエスケープ処理については様々な解決策を試みました:エスケープなし、全エスケープ、許可リスト方式... しかし全てのユースケースに適応できるヒューリスティックは見つかりませんでした。そのため入力時のエスケープ状態を保持する方針に決定し、JSX文字列でも同様の処理を行うようになりました。

// Before
<a href="https://foo.bar?q1=foo&amp;q2=bar" />

// After
<a href="https://foo.bar?q1=foo&q2=bar" />

括弧

ASTからの出力における祝福であり呪いでもあるのは、プログラムの全ての括弧を再出力しなければならない点です。以前はプログラムが有効で同じ実行結果を保つ最小限の括弧のみ出力していましたが、現在は厳密には不要でもコード理解を助ける括弧を追加する方針に転換しました。

二項演算子に括弧を追加 (#1153)

// Before
var sizeIndex = index - 1 >>> level & MASK;

// After
var sizeIndex = ((index - 1) >>> level) & MASK;

no-confusing-arrowルールで括弧を追加 (#1182)

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

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

チェーン代入から括弧を削除 (#967)

// Before
this.size = (this._origin = (this._capacity = 0));

// After
this.size = this._origin = this._capacity = 0;

Flow

Prettierの開発初期段階では、コアとなるJavaScript言語のサポート充実に注力してきました。現在では十分な状態となったため、Flowの構文をより洗練された方法で出力する時間的余裕が生まれています。

Flowのジェネリック型が改行可能に (#1041)

// Before
type _ReactElement<DefaultProps, Props, Config: $Diff<Props, DefaultProps>, C: $React.Component<DefaultProps, Props, any>> = $React.Element<Config>;

// After
type _ReactElement<
DefaultProps,
Props,
Config: $Diff<Props, DefaultProps>,
C: $React.Component<DefaultProps, Props, any>
> = $React.Element<Config>;

Flowの交差型出力の改善 (#1018 & #1155)

// Before
type Props =
& {
focusedChildren?: React.Children,
onClick: () => void,
overlayChildren?: React.Children,
}
& FooterProps;

// After
type Props = {
focusedChildren?: React.Children,
onClick: () => void,
overlayChildren?: React.Children,
} & FooterProps;

TupleTypeAnnotationでの改行サポート追加 (#1003)

// Before
export type FileMetaData = [/* id */ string, /* mtime */ number, /* visited */
| 0
| 1, /* dependencies */ Array<string>];

// After
export type FileMetaData = [
/* id */ string,
/* mtime */ number,
/* visited */ 0|1,
/* dependencies */ Array<string>,
];

単一引数のFlowショートハンドで括弧を省略 (#972)

// Before
type T = { method: (a) => void };

// After
type T = { method: a => void };

インターフェースの改行対応 (#1060)

// Before
export interface Environment1 extends GenericEnvironment<SomeType, AnotherType, YetAnotherType> {
m(): void
}

// After
export interface Environment1
extends GenericEnvironment<SomeType, AnotherType, YetAnotherType> {
m(): void
}

Flowの数値リテラル型出力の修正 (#1132)

// Before
type Hex = {n: 1};

// After
type Hex = {n: 0x01};

改行可能箇所の拡充

現在も、80カラムを超えるコードを出力するケースが一部存在しますが、改善可能な箇所があります。解決策としては、改行可能なポイントを慎重に特定し、一般的なケースでの出力品質に悪影響を与えないことを確認することです。

文字列とメンバー式で = の後ろでの改行を許可 (#1142 & #1188)

// Before
elements[0].innerHTML = '<div></div><div></div><div></div><div></div><div></div><div></div>';
var testExampleOrOrderOfGetterAndSetterReordered = obj.exampleOfOrderOfGetterAndSetterReordered;

// After
elements[0].innerHTML =
'<div></div><div></div><div></div><div></div><div></div><div></div>';
var testExampleOrOrderOfGetterAndSetterReordered =
obj.exampleOfOrderOfGetterAndSetterReordered;

トップレベルメンバー式での改行対応 (#1036)

// Before
expect(
findDOMNode(component.instance()).getElementsByClassName(styles.inner)[0].style.paddingRight
).toBe("1000px");

// After
expect(
findDOMNode(component.instance()).getElementsByClassName(styles.inner)[0]
.style.paddingRight
).toBe("1000px");

その他の改善点

括弧なしアロー関数のクラス式をインライン化 (#1023)

ごく小さな変更点として、アロー関数から返されるクラスをインライン化するようになりました。

// Before
jest.mock(
'../SearchSource',
() =>
class {
findMatchingTests(pattern) {
return {paths: []};
}
},
);

// After
jest.mock(
'../SearchSource',
() => class {
findMatchingTests(pattern) {
return {paths: []};
}
},
);

オブジェクト分割代入パターンでの改行を無視 (#981)

オブジェクトについては、元ソース内に改行(\n)が存在する場合に展開状態を維持する特別処理があります。これは当初オブジェクト専用の設計でしたが、同じコードが分割代入でも共有されていたため誤適用されていました。本修正でこの問題を解決しました。

// Before
const Component2 = ({
props
}) => <Text>Test</Text>;

// After
const Component1 = ({ props }) => <Text>Test</Text>;

言語サポートの拡大

Prettierを実用するには、記述したコードを正しく出力できる必要があります。私たちは基盤パーサー(babylonとflow)が解析可能なすべての構文を出力できることを目指しています。

awaitフラグ付きForOfStatementのサポート追加 (#1073)

async function f() {
for await (const line of readLines(\\"/path/to/file\\")) {
(line: void); // error: string ~> void
}
}

Flowの型スプレッド構文サポート (#1064)

type TypeB = { ...TypeA };

正確性の確保

Prettierの最優先要件は、元ソースと同一動作の有効なコードを出力することです。コードベース全体への適用を躊躇する必要はありません。これを保証するため、以下の取り組みを実施しています:

  • Prettierの出力が有効なコードであることを検証する自動化システムの構築

  • Facebook社内コードやCDNjs全体、コミュニティユーザーなど大規模コードベースでの実運用

  • eslumpを用いた網羅的ファジングテストの実施

  • 関連する問題報告を最高優先度で即時対応

もし不運にもバグを目にした場合は、ぜひ報告してください。修正して対応します。その間は// prettier-ignoreを使用して作業を継続できます。

JSXText内の\r\nを適切に処理 (#1170)

// Before
<div>
{" "}
Text{" "}
</div>;

// After
<div>
Text
</div>;

new演算子内の関数呼び出しにおける括弧を修正 (#1169)

// Before
new factory()();

// After
new (factory())();

アロー関数の戻り値型に括弧を追加 (#1152)

// Before
const f = (): string => string => {};

// After
const f = (): (string => string) => {};

Flowの厳密オブジェクト型アノテーションの出力を修正 (#1114)

// Before
type Props = {};

// After
type Props = {||};

return文後のコメント出力を修正 (#1178)

// Before
function f() {
return;
}

// After
function f() {
return /* a */;
}

オブジェクトキー後のコメント出力を修正 (#1131)

// Before
let a = {
"a": () => 1
};

// After
let a = {
"a" /* comment */: () => 1
};

戻り値のSequenceExpression内の先頭コメントを修正 (#1133)

// Before
function sequenceExpressionInside() {
return;
// Reason for a
a, b;
}

// After
function sequenceExpressionInside() {
return ( // Reason for a
a, b
);
}

単一オプション引数のアロー関数出力を修正 (#1002)

// Before
a = b? => c;

// After
a = (b?) => c;

デフォルトエクスポートを修正 (#998)

// Before
export { foo, bar } from "./baz";

// After
export foo, {bar} from './baz';

JSXEmptyExpression内の改行/混合コメントを新しい行に出力 (#985)

// Before
<div>
{
// single line comment}
</div>;

// After
<div>
{
// single line comment
}
</div>;

コメント処理の改善

PrettierはASTを出力することで動作するため、トークンの間に配置可能なコメントの扱いは複雑です。Prettierのアプローチは、コメントをASTノードに「前」または「後」として関連付けることです。汎用的なヒューリスティックでほとんどのケースをカバーしますが、多くのエッジケースでは手動での対応が必要です。

Prettierが全ての特殊な位置に配置されたコメントを完璧に出力することは不可能ですが、大抵のケースでは合理的な処理を提供します。コメント関連で不自然な出力を見かけた場合は、ぜひIssueを作成してください。対応策を検討します。

willBreakのbreakParentサポートを追加 (#674)

// Before
runtimeAgent.getProperties(objectId, false, false, false, ( // ownProperties // accessorPropertiesOnly // generatePreview
error,
properties,
internalProperties
) => {
return 1;
});

// After
runtimeAgent.getProperties(
objectId,
false, // ownProperties
false, // accessorPropertiesOnly
false, // generatePreview
(error, properties, internalProperties) => {
return 1
},
);

switch caseコメントにおける余分な空行を修正 (#936)

// Before
switch (foo) {
case "bar":
doThing()


// no default
}

// After
switch (foo) {
case "bar":
doThing()

// no default
}

import宣言のコメント処理を修正 (#1030)

// Before
import {
FN1,
FN2,
// FN3,
FN4
} from // FN4,
// FN5
"./module";

// After
import {
FN1,
FN2,
// FN3,
FN4,
// FN4,
// FN5
} from './module';

関数の最終引数コメントを修正 (#1176 & #905)

// Before
function f(
a: number
)// some comment here
: number {
return a + 1;
}

// After
function f(
a: number
// some comment here
): number {
return a + 1;
}

Flowのユニオン型コメントチェック制限を解除 (#1040)

// Before
type UploadState<E, EM, D> =
// The upload hasn't begun yet
| A
| // The upload timed out
B
| // Failed somewhere on the line
C;

// After
type UploadState<E, EM, D> =
// The upload hasn't begun yet
| A
// The upload timed out
| B
// Failed somewhere on the line
| C;

ObjectPattern内のコメント出力を修正 (#1045)

// Before
export default (
{
foo,
bar
// comment
// comment 2
}: {
foo?: Object,
bar?: Object
}
) => {};

// After
export default (
{
foo,
bar
}: {
// comment
foo?: Object,
// comment 2
bar?: Object,
},
) => {}

コメントソート位置を修正 (#1038)

// Before
let {
// comment
a = b
} = c;

// After
let {
a = b // comment
} = c;

二項演算子のコメント位置を修正 (#1043)

// Before
a = Math.random() * (yRange * (1 - minVerticalFraction)) +
minVerticalFraction * yRange// Comment
-
offset;

// After
a =
// Comment
Math.random() * (yRange * (1 - minVerticalFraction)) +
minVerticalFraction * yRange -
offset;

クラスメソッドのコメント出力を修正 (#1074)

// Before
class x {
focus() // do nothing
{
// do nothing
}
}

// After
class x {
focus() {
// do nothing
// do nothing
}
}

コメントブロック内での"// prettier-ignore"サポート (#1125)

// Before
module.exports = {
// Some comment
// prettier-ignore
m: matrix(1, 0, 0, 0, 1, 0, 0, 0, 1)
};

// After
module.exports = {
// Some comment
// prettier-ignore
m: matrix(
1, 0, 0,
0, 1, 0,
0, 0, 1
)
};

VariableDeclaratorのコメント出力を安定化 (#1130)

// Before
let obj = [ // Comment
'val'
];

// After
let obj = [
// Comment
'val'
];

カンマ前のコメント検出を修正 (#1127)

// Before
const foo = {
a: 'a' /* comment for this line */,

/* Section B */
b: 'b',
};

// After
const foo = {
a: 'a' /* comment for this line */,
/* Section B */
b: 'b',
};

ifテストの最後のコメントを修正 (#1042)

// Before
if (isIdentifierStart(code) || code === 92) {
/* '\' */
}

// After
if (isIdentifierStart(code) || code === 92 /* '\' */) {}