Saltar al contenido principal

Prettier 1.3

· 12 min de lectura
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Esta publicación ofrece una actualización sobre la adopción de Prettier en Facebook, describe nuestro progreso con TypeScript y detalla las mejoras y correcciones incluidas en la versión 1.3.0 de Prettier.

Actualización sobre la adopción en Facebook

La razón por la que yo (@vjeux) emprendí este camino trabajando en prettier siempre ha sido convertir toda la base de código de Facebook. Quisiera compartir cómo va el proceso y qué estrategias estamos siguiendo.

Los primeros proyectos en adoptar prettier fueron Jest, React e immutable-js. Son bases de código pequeñas, del orden de cientos de archivos, con su propia infraestructura. Hay un puñado de personas trabajando en ellos a tiempo completo.

Luego, Oculus y Nuclide convirtieron sus bases de código. La escala es mayor, con varios miles de archivos y decenas de contribuyentes a tiempo completo, pero el proceso fue similar a los primeros proyectos. Las conversiones se realizaron mediante un único codemod masivo.

Ahora bien, la base de código completa de Facebook es mucho más grande y no es viable convertir todo de una sola vez ni convencer a todos de que su código base será reformateado sin previo aviso. Por eso necesitamos un enfoque más incremental.

Escalando la adopción

Ejecutar prettier sobre un fragmento de código es una operación bastante costosa: ensucia tu pull request con cambios no relacionados y genera conflictos de fusión en todas las solicitudes pendientes. Por eso, una vez que un archivo ha sido formateado, debes asegurarte de que permanezca formateado.

  • Al formatear un archivo, añadir @format al primer comentario de bloque, junto a @flow.

  • Implementar una regla de lint con autofix que verifique si el archivo está correctamente formateado cuando @format está presente.

    • En Nuclide, esto se mostrará como una advertencia en línea con un botón de corrección.
    • Al enviar un pull request, el lint fallará con un prompt [Yn] donde basta presionar enter.
  • Actualizar las plantillas de código predeterminadas para incluir @format en el encabezado.

  • Al ejecutar formato de código mediante cmd-shift-c en Nuclide, insertar automáticamente el encabezado @format.

  • Desactivar todas las reglas de estilo como max-len cuando @format está presente en el encabezado.

  • Disponer de un script para ejecutar prettier en carpetas completas mediante una operación de una línea.

  • Crear una guía práctica con instrucciones y mejores prácticas para quienes deseen convertir su base de código.

  • Al publicar una nueva versión de prettier, aplicarla también a todos los archivos con @format para evitar advertencias posteriores.

  • Implementar seguimiento del número de archivos con @format a lo largo del tiempo.

Finalmente implementamos todo esto hace 1.5 semanas y la recepción ha sido abrumadora. Muchas personas de diversos equipos convirtieron sus bases de código por iniciativa propia. ¡Hoy ya tenemos el 15% de la base de código de Facebook convertida!

Cuando comencé a trabajar en prettier, intuía que la comunidad anhelaba herramientas para resolver el formateo. ¡Pero no imaginé que, una vez implementada la infraestructura, la gente se apresuraría a convertir sus bases de código! Esto confirma que el proyecto es realmente útil y no solo una herramienta superficial.

Progreso en el soporte para TypeScript

@despairblue, @azz y @JamesHenry han trabajado arduamente para implementar soporte de TypeScript en prettier, la función más solicitada. 2000 de 11000 archivos en la suite de pruebas de TypeScript aún no se formatean correctamente. Puedes seguir el progreso en https://github.com/prettier/prettier/issues/1480 y no dudes en contribuir.

Flow

Agregar comas finales en genéricos de Flow (#1381)

La opción --trailing-comma=all debe agregar comas finales siempre que sea posible, pero por descuido olvidamos implementarlo para genéricos de Flow.

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

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

Inlinear nulabilidad en genéricos de Flow (#1426)

Tras lograr una impresión correcta, el siguiente paso es ajustar la salida para acercarla a cómo la gente escribe código en la práctica. Inlinear tipos opcionales de Flow es un pequeño detalle que marca diferencia.

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

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

Permitir saltos de línea en declaraciones Flow con StringLiteralTypeAnnotations (#1437)

Siempre encontramos más lugares para agregar saltos cuando el contenido excede 80 columnas. Esta vez se trata de declarar un tipo como cadena constante.

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

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

Agregar espacio alrededor de = para argumentos predeterminados en genéricos de Flow (#1476)

Otro caso donde podemos mejorar la visualización de código Flow. Para argumentos predeterminados en funciones ponemos espacio alrededor de =, pero no lo hacíamos para genéricos de Flow.

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

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

Evitar saltos de línea para funciones Flow con un solo argumento sin paréntesis (#1452)

¡Intento encontrar algo que escribir aquí, pero... simplemente se ve raro!

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

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

Corregir paréntesis opcionales en Flow (#1357)

Éramos demasiado flexibles con los paréntesis en tipos opcionales de Flow. En un caso específico de todo el código base de Facebook, generó código con semántica diferente. Como parte de esta corrección, reforzamos la lista de tipos que pueden escribirse sin paréntesis.

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

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

Omitir comas finales con FlowShorthandWithOneArg (#1364)

Es un error de análisis agregar una coma final sin paréntesis en argumentos de tipos de funciones flecha. Encontramos un caso en el código base de Facebook donde ocurrió, una situación muy poco común.

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

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

Reordenar propiedades de objetos Flow (#1451)

Este es un ejemplo donde la estructura del AST no nos favorece. En lugar de tener una lista de elementos dentro de un tipo, el AST está organizado de modo que claves normales y claves de arreglos forman grupos separados. Para restaurar el orden original, ahora leemos directamente desde el código fuente original :(

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

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

Literales de Plantilla

Indentación adecuada para literales de plantilla (#1385)

Un problema persistente con literales de plantilla y Prettier era la indentación del código dentro de ${}. Solía tomar la indentación de la comilla invertida, pero daba resultados pobres. En cambio, la gente tiende a usar la indentación del ${. Cambiamos este comportamiento ¡y mágicamente las consultas GraphQL se ven mejor!

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

No indentar llamadas con un único argumento de literal de plantilla (#873)

Los literales de plantilla son muy difíciles de manejar para un formateador porque los espacios internos son significativos y no pueden re-indentarse. No sabíamos qué hacer con llamadas de un solo literal de plantilla, así que no hacíamos nada, pero seguíamos recibiendo reportes de gente diciendo que Prettier los indentaba mal. Ahora los inlineamos. ¡Crucemos los dedos para que cubra la mayoría de casos!

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

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

Corregir finales de línea Windows en literales de plantilla (#1439)

Manipulamos saltos de línea en muchos lugares de Prettier y nos aseguramos de manejar tanto \n como \r\n, excepto en literales de plantilla donde se nos pasó. ¡Ahora esto está solucionado!

// Before
const aLongString = `

Line 1

Line 2

Line 3

`;

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

Literales de plantilla en línea como cuerpo de flecha (#1485)

Ya inlineábamos literales de plantilla etiquetados (ej. graphql`query`) pero no lo hacíamos con literales simples. Como anécdota, resultó que el código debía soportarlo pero usaba TemplateElement en lugar 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>
`

Ternarios

Agregar paréntesis para ternarios anidados inusuales (#1386)

Al trabajar con ternarios anidados, todos se enfocaron en los que tienen estructura de if-then-else cond1 ? elem1_if : cond2 ? elem2_if : elem_else, que es el más común. Pero si reorganizas los ? y :, puedes obtener otro patrón. Parece similar pero tiene diferente significado. Para reducir confusiones, agregamos paréntesis alrededor de la forma menos común.

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

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

Solo agregar paréntesis en ternarios dentro de funciones flecha si evitan ambigüedad (#1450)

Existe una regla de ESLint no-confusing-arrows que sugiere agregar paréntesis para ternarios en funciones flecha sin llaves. Estos dos fragmentos son confusos:

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

Tiene sentido en una línea, pero al dividirse en múltiples líneas, los paréntesis son innecesarios dada la indentación. Ahora solo los agregamos cuando cumplen su propósito de desambiguación.

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

Mejoras generales en JavaScript

Declaración de función en línea con un solo argumento como objeto (#1173)

Solicitado frecuentemente para Componentes Funcionales Sin Estado (SFC) de React. Si usas muchos, este cambio será significativo.

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

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

Romper objetos en línea primero en argumentos de función (#1453)

Descubrimos que normalmente se rompen los argumentos antes que el tipo de retorno. Lamentablemente, el código para argumentos únicos de desestructuración rompía esta premisa, generando código antiestético como este ejemplo. La buena noticia es que ahora podemos activar el inlineado para argumentos únicos tipados como objeto.

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

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

No romper en arrays y objetos vacíos (#1440)

Este problema persistente era fácil de solucionar pero funcionaba como herramienta de diagnóstico: cuando alguien reportaba que [] o {} se rompían, arreglábamos el ejemplo corrigiendo otra cosa. Era excelente para detectar casos límite. Afortunadamente, esta veta se agotó y los ejemplos recientes simplemente se veían mal. ¡Es hora de solucionarlo!

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

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

No romper en [0] (#1441)

Tenemos muchos casos donde el acceso a arrays se rompe antiestéticamente. Aún no hay solución genérica, pero podemos arreglar un caso común: [0].

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

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

Indentar condición de do-while (#1373)

No usábamos la lógica correcta de indentación para condiciones de do-while, pero alguien lo notó. ¡Ahora sí!

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

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

Preservar comentarios en línea como último argumento (#1390)

Olvidamos manejar un caso en la detección de comentarios cuando aparecen al final de atributos JSX y argumentos de funciones, lo que hacía que se colocaran después del cierre. En JSX, esto generaba código con significado distinto. Afortunadamente, como no solemos confirmar código comentado, no afectó producción, pero genera mala experiencia durante el desarrollo.

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

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

Dividir expresiones de clase devueltas por llamadas flecha (#1464)

En la versión 1.0, implementamos clases en línea dentro de funciones flecha. Resultó que esto no funciona bien con clases complejas, así que revertimos el cambio. Nos esforzamos por evitar cambios de estilo oscilantes, pero a veces permitimos estas correcciones para enmendar errores.

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

Corregir línea vacía en bloques con EmptyStatement (#1375)

Este caso se descubrió mediante fuzzing. Es improbable que ocurra en código real, pero es positivo saber que está corregido.

// Input
if (a) {
b;


;
}

// Before
if (a) {
b;

}

// After
if (a) {
b;
}