Zum Hauptinhalt springen

Prettier 1.3

· 10 Min. Lesezeit
Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Dieser Beitrag gibt ein Update zur Facebook-Integration von Prettier, skizziert unsere Fortschritte mit TypeScript und erläutert Verbesserungen und Korrekturen im Prettier-Release 1.3.0.

Update zur Facebook-Einführung

Mein Ziel (@vjeux) bei der Arbeit an Prettier war stets, das gesamte Facebook-Codebase zu migrieren. Ich möchte berichten, wie der Stand ist und welchen Prozess wir dafür nutzen.

Die ersten Projekte, die Prettier übernahmen, waren Jest, React und immutable-js. Dabei handelt es sich um kleinere Codebasen mit einigen hundert Dateien und eigener Infrastruktur. Nur wenige Entwickler arbeiten Vollzeit daran.

Anschließend migrierten Oculus und Nuclide ihre Codebasen. Die Größenordnung ist mit einigen tausend Dateien und Dutzenden Vollzeit-Mitarbeitern größer, aber ähnlich strukturiert. Die Umstellungen erfolgten jeweils in einem großen Codemod.

Das gesamte Facebook-Codebase ist jedoch deutlich umfangreicher. Ein Komplett-Umstellung auf einen Schlag ist weder praktikabel noch könnten wir alle davon überzeugen, dass ihr Codebase unter ihren Händen neu formatiert wird. Daher benötigen wir einen schrittweisen Ansatz.

Skalierung der Einführung

Das Ausführen von Prettier auf Code ist ressourcenintensiv: Es macht Pull Requests unübersichtlich durch viele nicht zusammenhängende Änderungen und verursacht Merge-Konflikte bei offenen Pull Requests. Sobald eine Datei formatiert wurde, sollten Sie alles tun, um sie formatiert zu halten.

  • Beim Pretty-Printing einer Datei @format im ersten Blockkommentar (wie @flow) hinzufügen.

  • Linter-Regel mit Autofix einrichten, die bei vorhandenem @format die korrekte Formatierung prüft.

    • In Nuclide erscheint dies als Inline-Warnung mit Korrektur-Button.
    • Bei Pull Requests zeigt der Linter einen Fehler mit [J/N]-Eingabeaufforderung an – einfach Enter drücken.
  • Standard-Codevorlagen aktualisieren, um @format im Header einzufügen.

  • Bei Codeformatierung via cmd-shift-c in Nuclide automatisch @format im Header einfügen.

  • Stilregeln wie max-len deaktivieren, wenn @format im Header steht.

  • Einzeiliges Skript bereitstellen, das Prettier für gesamte Ordner mit vorkonfigurierten Einstellungen ausführt.

  • Praxisanleitung für Codebase-Migrationen mit Best Practices erstellen.

  • Bei neuen Prettier-Releases automatisch alle @format-Dateien verarbeiten, um spätere Warnungen zu vermeiden.

  • Tracking der Dateien mit @format über Zeit implementieren.

Vor 1,5 Wochen wurde dieses System aktiviert – die Resonanz ist überwältigend. Viele Teams migrierten eigenständig ihre Codebasen. Aktuell sind 15% des Facebook-Codebases konvertiert!

Bei Prettiers Start ahnte ich, dass Entwickler nach Formatierungstools hungern. Dass sie bei verfügbarer Tooling jedoch massenhaft migrieren würden, überraschte mich! Dies bestätigt, dass Prettier kein Spielzeug, sondern ein nützliches Werkzeug ist.

Fortschritte bei TypeScript-Unterstützung

@despairblue, @azz und @JamesHenry arbeiten intensiv an der meistgewünschten Funktion: TypeScript-Support. Noch werden 2000 von 11000 Dateien im TypeScript-Testsuite nicht korrekt formatiert. Verfolgen Sie den Fortschritt unter https://github.com/prettier/prettier/issues/1480 – Unterstützung ist willkommen!

Flow

Nachgestellte Kommas für Flow-Generics hinzufügen (#1381)

Die Option --trailing-comma=all soll eigentlich überall nachgestellte Kommas einfügen, wo möglich – aus Versehen hatten wir dies bei Flow-Generics vergessen.

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

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

Nullable in Flow-Generics inline darstellen (#1426)

Nachdem der Code korrekt formatiert ist, optimieren wir die Ausgabe für gängige Schreibweisen. Das Inline-Setzen optionaler Flow-Typen ist eine kleine, aber wirkungsvolle Verbesserung.

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

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

Zeilenumbrüche für Flow-Deklarationen bei StringLiteralTypeAnnotations ermöglichen (#1437)

Wir finden stets neue Stellen für Zeilenumbrüche bei >80 Zeichen. Diesmal betrifft es die Deklaration von Typen als konstante Strings.

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

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

Leerzeichen um = bei Flow-Generics-Standardargumenten hinzufügen (#1476)

Ein weiteres Beispiel für kleinteilige Verbesserungen: Bei Funktions-Standardargumenten setzen wir Leerzeichen um =, bei Flow-Generics jedoch nicht.

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

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

Keine Zeilenumbrüche für Flow-Funktionen mit ungeklammertem Einzelargument (#1452)

Ich suche nach einer passenden Erklärung, aber... das sieht einfach merkwürdig aus!

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

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

Optionale Klammern in Flow korrigieren (#1357)

Wir waren zu tolerant bei Klammern für optionale Flow-Typen. Ein Fall im Facebook-Codebase führte zu semantischen Änderungen. Wir haben die Regeln für klammerfreie Typen verschärft.

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

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

Nachgestellte Kommas bei FlowShorthandWithOneArg überspringen (#1364)

Ein nachgestelltes Komma ohne Klammern bei Pfeilfunktionstypen verursacht Parse-Fehler. Ein extrem seltener Fall, der einmal im Facebook-Codebase auftrat.

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

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

Flow-Objekteigenschaften neu anordnen (#1451)

Die AST-Struktur erschwert dies: Normale und Array-Schlüssel sind getrennt gruppiert. Um die Originalreihenfolge zu erhalten, lesen wir nun direkt aus dem Quellcode :(

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

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

Template Literal

Korrekte Einrückung für Template-Literals (#1385)

Das Einrückungsproblem in ${} bestand lange: Statt am Backtick auszurichten, nutzen Entwickler typischerweise die Einrückung des ${-Tokens. Diese Änderung macht GraphQL-Abfragen magisch lesbar!

// 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
})}
}
`
})

Keine Einrückung bei Aufrufen mit einzelner Template-Literal-Argument (#873)

Template-Literals sind für Pretty-Printing herausfordernd, da Leerzeichen bedeutungstragend sind. Bei Aufrufen mit einem Template-Literal rückten wir ursprünglich nicht ein – doch Nutzerberichte über falsche Einrückungen veranlassten uns zum Inlinen. Hoffentlich deckt dies die meisten Fälle ab!

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

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

Windows-Zeilenvorschübe bei Template-Literals korrigieren (#1439)

Wir bearbeiten Zeilenumbrüche in Prettier an vielen Stellen und haben uns große Mühe gegeben, sowohl \n als auch \r\n korrekt zu behandeln – außer bei Template-Literals, wo wir es vergessen hatten. Jetzt ist dies behoben!

// Before
const aLongString = `

Line 1

Line 2

Line 3

`;

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

Inline-Template-Literals als Pfeilfunktionskörper (#1485)

Wir setzen bereits getaggte Template-Literals inline (z.B. graphql`query`), aber nicht bei einfachen Template-Literals. Anekdotisch sei erwähnt: Der Code hätte dies eigentlich unterstützen sollen, verwendete aber fälschlicherweise TemplateElement statt TemplateLiteral :(

// 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>
`

Ternäre Ausdrücke

Klammern für ungewöhnlich verschachtelte Ternaries hinzufügen (#1386)

Bei der Formatierung verschachtelter Ternaries konzentrierten sich alle auf die if-then-else-Form cond1 ? elem1_if : cond2 ? elem2_if : elem_else, die am häufigsten vorkommt. Durch Vertauschen von ? und : entsteht jedoch ein anderes Muster. Es sieht fast identisch aus, hat aber eine andere Bedeutung. Um Verwirrung zu vermeiden, fügen wir bei dieser ungewöhnlichen Form nun Klammern hinzu.

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

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

Klammern nur bei Ternaries in Pfeilfunktionen hinzufügen, wenn nötig (#1450)

Die ESLint-Regel no-confusing-arrows empfiehlt Klammern für Ternaries in Pfeilfunktionen ohne geschweifte Klammern. Folgende Codebeispiele sind nämlich verwirrend:

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

In einer Zeile sind Klammern sinnvoll, aber bei mehrzeiliger Formatierung werden sie durch die Einrückung überflüssig. Daher setzen wir sie jetzt nur noch, wenn sie tatsächlich der Klarstellung dienen.

// 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;

Allgemeine JavaScript-Verbesserungen

Inline-Funktionsdeklaration mit Einzelargument als Objekt (#1173)

Dies wurde häufig für React Stateless Functional Components (SFC) angefragt. Bei umfangreicher Nutzung wird dies eine merkliche Änderung für Sie sein.

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

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

Inline-Objekte in Funktionsargumenten zuerst umbrechen (#1453)

Wir bemerkten früh, dass Entwickler meist Funktionsargumente vor Rückgabetypen umbrechen. Leider durchbrach unsere Logik für einzelne Destrukturierungsargumente diese Annahme und erzeugte unschönen Code. Die gute Nachricht: Dies ermöglicht uns nun, Inline-Formatierung für einzelne Objekt-Argumente zu aktivieren.

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

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

Keine Umbrüche bei leeren Arrays und Objekten (#1440)

Dieses lang bestehende Problem wäre einfach zu beheben gewesen, aber es war ein wertvolles Diagnosewerkzeug: Immer wenn jemand über umbrechende [] oder {} berichtete, löste dies tieferliegende Probleme. Glücklicherweise sind diese Fälle nun erschöpft – aktuelle Beispiele sehen nur wegen der unnötigen Umbrüche schlecht aus. Zeit für die Lösung!

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

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

Keine Umbrüche bei [0] (#1441)

Wir haben viele Fälle, bei denen Array-Zugriffe unschön umbrechen. Eine generelle Lösung existiert noch nicht, aber wir können einen speziellen Fix für häufige Fälle wie [0] implementieren.

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

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

Do-While-Bedingungen einrücken (#1373)

Wir verwendeten falsche Einrückungslogik für do-while-Bedingungen – bis es jemand bemerkte. Jetzt ist es behoben!

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

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

Inline-Kommentare als letztes Argument erhalten (#1390)

Wir hatten einen Fall in der Kommentarerkenungslogik vergessen, wenn Kommentare am Ende von JSX-Attributen oder Funktionsargumenten stehen, wodurch sie nach der schließenden Klammer platziert wurden. Bei JSX erzeugte dies Code mit veränderter Semantik. Glücklicherweise wird auskommentierter Code selten committet, sodass Produktionscode nicht betroffen war, aber während der Entwicklung ist das eine unangenehme Erfahrung.

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

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

Klassenausdrücke in Pfeilfunktionsrückgaben umbrechen (#1464)

In Version 1.0 hatten wir Klassen innerhalb von Pfeilfunktionen inline gesetzt. Dies funktioniert jedoch schlecht bei nicht-trivialen Klassen, daher machen wir diese Änderung rückgängig. Wir bemühen uns sehr, solche wechselhaften Formatierungsentscheidungen zu vermeiden, erlauben uns aber gelegentlich Korrekturen, um Fehler zu beheben!

// 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 = {};
};

Leere Zeile in Blöcken mit EmptyStatement korrigieren (#1375)

Dieser Fall wurde durch Fuzzing entdeckt. Im Praxis-Code wird dies kaum auftreten, aber gut zu wissen, dass es behoben ist!

// Input
if (a) {
b;


;
}

// Before
if (a) {
b;

}

// After
if (a) {
b;
}