Aller au contenu principal

Prettier 1.3

· 12 minutes de lecture
Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Cet article présente une mise à jour sur l'adoption de Prettier par Facebook, détaille nos progrès concernant TypeScript, et liste les améliorations et corrections incluses dans la version 1.3.0 de Prettier.

Mise à jour sur l'adoption par Facebook

La raison pour laquelle je (@vjeux) me suis lancé dans cette aventure avec Prettier a toujours été de convertir l'intégralité du codebase de Facebook. Je souhaite vous faire part de l'avancement et du processus mis en place pour y parvenir.

Les premiers projets à adopter Prettier ont été Jest, React et immutable-js. Il s'agit de petits codebases, de l'ordre de centaines de fichiers, qui disposent de leur propre infrastructure. Quelques personnes y travaillent à plein temps.

Ensuite, Oculus et Nuclide ont converti leur codebase. L'échelle est plus grande, avec quelques milliers de fichiers et des dizaines de contributeurs à plein temps, mais le processus reste similaire. Les conversions se sont faites en un seul gros codemod.

Le codebase complet de Facebook est toutefois bien plus volumineux. Il n'est pas envisageable de tout convertir d'un coup ni de convaincre tout le monde que leur codebase sera reformatée sans préavis. Nous devons donc adopter une approche plus progressive.

Passage à l'échelle

Exécuter Prettier sur un morceau de code est coûteux : cela rend les pull requests illisibles à cause des changements non liés et provoque des conflits de fusion pour les PR en cours. Une fois un fichier formaté, vous devez tout faire pour qu'il reste formaté.

  • Lors du pretty-printing, ajoutez @format au premier bloc de commentaire comme @flow.

  • Implémentez une règle lint avec autofix vérifiant le formatage quand @format est présent :

    • Dans Nuclide, un avertissement en ligne s'affiche avec un bouton de correction
    • À l'envoi d'une pull request, le lint échoue avec une invite [Yn] où appuyer sur Entrée suffit
  • Mettez à jour les modèles de code par défaut pour inclure @format dans l'en-tête.

  • Lors du formatage via cmd-shift-c dans Nuclide, insérez automatiquement l'en-tête @format.

  • Désactivez les règles stylistiques comme max-len quand @format est présent.

  • Créez un script pour exécuter Prettier sur un dossier entier en une seule commande.

  • Rédigez un guide complet avec instructions et bonnes pratiques pour convertir un codebase.

  • Lors des nouvelles versions de Prettier, relancez-le sur tous les fichiers avec @format pour éviter les avertissements ultérieurs.

  • Ajoutez un suivi du nombre de fichiers avec @format dans le temps.

Nous avons finalement connecté tous ces éléments il y a 1,5 semaine. L'accueil a été incroyable : de nombreuses équipes ont converti leur codebase spontanément. Aujourd'hui, 15% du codebase de Facebook est converti !

Dès le début, je pressentais que les développeurs cherchaient désespérément des solutions de formatage. Mais je ne m'attendais pas à cet engouement une fois l'outillage en place ! Cela confirme que Prettier est réellement utile, pas juste un gadget.

Progrès sur le support TypeScript

@despairblue, @azz et @JamesHenry travaillent d'arrache-pied sur le support TypeScript, fonctionnalité la plus demandée. 2000 fichiers sur 11000 de la suite de tests ne sont pas encore correctement imprimés. Suivez les progrès sur https://github.com/prettier/prettier/issues/1480 et n'hésitez pas à contribuer !

Flow

Ajout des virgules finales pour les generics de Flow (#1381)

L'option --trailing-comma=all est censée ajouter des virgules finales partout où possible, mais par inadvertance nous avons oublié de le faire pour les generics de Flow.

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

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

Mise en ligne des types nullable dans les generics de Flow (#1426)

Après avoir correctement imprimé les éléments, la phase suivante consiste à ajuster le rendu pour le rapprocher de la façon dont les gens écrivent réellement leur code. Mettre en ligne les types optionnels de Flow est un petit détail qui fait la différence.

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

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

Autoriser les déclarations Flow à couper sur les StringLiteralTypeAnnotations (#1437)

On peut toujours trouver plus d'endroits où ajouter des sauts de ligne quand le contenu dépasse 80 colonnes. Cette fois, c'est autour de la déclaration d'un type comme chaîne de caractères constante.

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

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

Ajout d'un espace autour du = pour les arguments par défaut des generics de Flow (#1476)

Autre exemple de petit détail où l'on peut améliorer l'affichage du code Flow. Pour les arguments par défaut des fonctions, nous mettons un espace autour du =, mais pas pour les generics de Flow.

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

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

Ne pas couper pour les fonctions Flow à un argument sans parenthèses (#1452)

J'essaie de trouver quelque chose d'intelligent à écrire ici, mais... ça semble bizarre !

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

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

Correction des parenthèses optionnelles de Flow (#1357)

Nous étions un peu trop permissifs concernant les parenthèses pour les types optionnels de Flow. Dans un seul cas sur l'ensemble du codebase de Facebook, cela a généré du code avec une sémantique différente. Dans le cadre de cette correction, nous avons durci la liste des types pouvant être écrits sans parenthèses.

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

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

Ignorer les virgules finales avec FlowShorthandWithOneArg (#1364)

C'est une erreur d'analyse d'ajouter une virgule finale sans parenthèses pour les arguments des types de fonctions fléchées. Nous avons trouvé un cas dans le codebase de Facebook où cela s'est produit, c'est très rare.

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

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

Réorganiser les propriétés des objets Flow (#1451)

C'est un exemple où la structure de l'AST ne joue pas en notre faveur. Au lieu d'avoir une liste d'éléments à l'intérieur d'un type, l'AST est structuré de manière à ce que les clés normales et les clés de tableau aient chacun leur propre groupe. Pour restaurer l'ordre initial, nous lisons maintenant depuis la source originale :(

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

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

Littéraux de modèle

Indentation correcte pour les littéraux de modèle (#1385)

Un problème récurrent avec les littéraux de modèle et Prettier concernait l'indentation du code à l'intérieur des ${}. Elle était basée sur l'indentation de l'accent grave, mais cela donnait de mauvais résultats. À la place, les gens utilisent généralement l'indentation du ${. Nous avons changé ce comportement et cela a rendu les requêtes GraphQL magnifiques comme par magie !

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

Ne pas indenter les appels avec un seul argument de type littéral de modèle (#873)

Les littéraux de modèle sont très difficiles à gérer pour un formateur car les espaces à l'intérieur sont significatifs, donc on ne peut pas les ré-indenter. Nous ne savions pas quoi faire pour un appel avec un seul littéral de modèle, donc nous n'avions rien fait, mais nous avons constamment reçu des rapports indiquant que Prettier les indentait mal. Nous les mettons maintenant en ligne. Croisons les doigts pour que cela couvre la plupart des cas d'usage.

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

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

Correction des fins de ligne Windows dans les littéraux de modèle (#1439)

Nous manipulons les fins de ligne à de nombreux endroits dans Prettier et avons pris grand soin de gérer à la fois \n et \r\n, sauf pour les littéraux de modèle où nous avions oublié. C'est maintenant corrigé !

// Before
const aLongString = `

Line 1

Line 2

Line 3

`;

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

Littéraux de modèle en ligne comme corps de flèche (#1485)

Nous mettions déjà en ligne les littéraux de modèle étiquetés (ex. graphql`query`) mais pas les littéraux simples. Pour l'anecdote, il s'avère que le code était censé le supporter mais utilisait TemplateElement au lieu de 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>
`

Ternaires

Ajout de parenthèses pour les ternaires imbriqués inhabituels (#1386)

Lors du traitement des ternaires imbriqués, tout le monde s'est concentré sur ceux ayant la forme d'un if-then-else cond1 ? elem1_if : cond2 ? elem2_if : elem_else, la plus courante. Mais en déplaçant certains ? et :, on peut obtenir un autre motif. Il semble presque identique mais a une signification différente. Pour réduire la confusion, nous ajoutons des parenthèses autour de la forme inhabituelle.

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

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

Ajout de parenthèses sur les ternaires dans les fonctions fléchées uniquement si nécessaire (#1450)

Il existe une règle ESLint no-confusing-arrows qui suggère d'ajouter des parenthèses pour les ternaires dans les fonctions fléchées sans accolades. Les deux exemples suivants prêtent à confusion :

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

Cela a du sens en une seule ligne, mais lorsque le code est réparti sur plusieurs lignes, les parenthèses deviennent superflues grâce à l'indentation. Nous ne les ajoutons donc plus que lorsqu'elles servent à lever une ambiguïté.

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

Améliorations générales JavaScript

Déclaration de fonction en ligne avec un seul argument objet (#1173)

Cette amélioration était souvent demandée pour les composants fonctionnels sans état React (SFC). Si vous en utilisez beaucoup, cela représentera probablement un changement important pour vous.

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

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

Casser les objets en ligne en premier dans les arguments de fonction (#1453)

Nous avons découvert tôt que les utilisateurs cassent généralement les arguments de fonction avant le type de retour. Malheureusement, le code responsable de l'alignement des arguments de déstructuration uniques contredisait cette logique, produisant un code peu élégant comme cet exemple. La bonne nouvelle est que cela nous permet d'activer l'alignement pour les arguments uniques typés comme objet.

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

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

Ne pas casser les tableaux et objets vides (#1440)

C'était un problème récurrent et facile à corriger, mais il constituait un outil précieux : chaque fois que quelqu'un signalait que [] ou {} se cassaient, nous corrigions l'exemple en résolvant autre chose. C'était donc un excellent moyen de détecter des cas limites. Heureusement, cette veine est maintenant tarie et tous les exemples récents étaient simplement inélégants sans autre raison. Il est temps de régler ça !

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

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

Ne pas casser sur [0] (#1441)

Nous avons de nombreux problèmes où l'accès aux tableaux se casse inélégamment. Nous n'avons pas encore de solution générique, mais nous pouvons corriger une situation courante : [0].

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

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

Indenter la condition des boucles do-while (#1373)

Nous n'utilisions pas la bonne logique d'indentation pour les conditions do-until, mais quelqu'un l'a remarqué. C'est maintenant corrigé !

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

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

Préserver les commentaires en ligne comme dernier argument (#1390)

Nous avons oublié de traiter un cas particulier dans le code de détection des commentaires lorsqu'ils apparaissent en dernier pour les attributs JSX et les arguments de fonction, ce qui les plaçait après la fermeture. Dans le cas du JSX, cela générait du code avec une sémantique différente. Heureusement, comme nous ne validons généralement pas de code commenté, cela n'a pas affecté le code en production, mais cela reste une expérience désagréable pendant le développement.

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

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

Sauter l'expression de classe retournée par un appel fléché (#1464)

Dans la version 1.0, nous avions choisi d'aligner les classes à l'intérieur des fonctions fléchées. Il s'avère que cela ne fonctionne pas bien lorsque la classe est non triviale, donc nous revenons sur cette décision. Nous faisons tout notre possible pour éviter les décisions médiocres où le style oscille, mais nous nous autorisons parfois ces corrections pour réparer nos erreurs !

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

Corriger la ligne vide dans un bloc avec EmptyStatement (#1375)

Ce problème a été découvert par fuzzing. Vous ne rencontrerez probablement jamais ce cas dans du code réel, mais c'est rassurant de savoir qu'il est corrigé !

// Input
if (a) {
b;


;
}

// Before
if (a) {
b;

}

// After
if (a) {
b;
}