Saltar al contenido principal

Lanzamiento de Prettier 1.0

· 17 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 →

Este post fue escrito por vjeux y editado por jlongster, y publicado originalmente aquí

Oficialmente anunciamos prettier hace más de dos meses como solución al problema de perder tiempo formateando código. Comenzó como un experimento pero claramente resonó con mucha gente, acumulando ~7000 estrellas en GitHub y más de 100,000 npm downloads mensuales en solo dos meses.

Por si no lo conoces, prettier es un formateador de JavaScript que funciona compilando tu código a un AST y luego imprimiendo ese AST de forma estructurada. Al igual que los navegadores ajustan texto, prettier también ajusta el código según un largo de línea determinado. El resultado es código bien presentado y completamente consistente sin importar quién lo escribió. Esto resuelve el problema de programadores que pierden mucho tiempo moviendo código manualmente en su editor y discutiendo sobre estilos (ver gif).

Al principio no estaba completamente claro si podríamos hacer que siempre generara código válido y bien presentado, pero la buena noticia es que esto ya no es una incógnita. Muchos proyectos (React, Jest, immutable-js, Haul y muchos más) y empresas (Oculus, Cloudflare) adoptaron prettier para formatear sus bases de código JavaScript y comprobaron las ventajas del formateo automatizado (¡consulta este tweet para más detalles!). Algunos ejecutaron prettier en toda su base de código (decenas de miles de líneas).

Durante las últimas tres semanas revisamos todos los issues abiertos, tomamos tantas decisiones como fue posible sobre cómo se formatea el código y corregimos la mayoría de los bugs conocidos. No es perfecto y seguiremos iterando, ¡pero es un buen momento para lanzar la versión 1.0!

Esto no significa que no cambiaremos más el formato, pero los cambios serán mínimos. Por ejemplo, estamos evaluando ajustar los ternarios, pero habrá muy pocos cambios de esa naturaleza de aquí en adelante, y cuando los haya, realizaremos una nueva versión mayor.

Lo que significa es que prettier es seguro para usar en producción. Hemos corregido montones de bugs y asegurado que la salida sea estable y semánticamente correcta. Prettier está listo para usarse, desde unos pocos archivos en tu proyecto personal hasta convertir toda tu base de código. Depende de ti determinar en qué punto estás del camino de abandonar una forma particular de formatear tu código.

¡Veamos qué aporta la versión 1.0 a prettier!

Agradecemos a todos los siguientes colaboradores que contribuyeron a este lanzamiento, y gracias a Ian Storm Taylor por el logo!

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

Opciones

Prettier es un formateador de código con opiniones definidas. Al inicio del proyecto, pensamos que esto significaba no tener configuración, como gofmt o refmt. Pero, a medida que avanzamos, nos dimos cuenta de que esto iba a perjudicar la adopción de Prettier y que personas que podrían haberse beneficiado de él no lo usarían porque no daba formato al código como ellos querían.

En cambio, nuestra interpretación reciente de ser un formateador con opiniones es ofrecer opciones para aspectos básicos de la sintaxis que son del tipo "si no hace X, no importa lo bueno que sea, nunca lo voy a usar". Por ejemplo, yo (@vjeux) nunca voy a usar standard porque no usa punto y coma. Esta no es para nada una forma racional de pensarlo, pero así se comporta mucha gente y por eso tenemos las "guerras de estilos".

Todavía no vamos a introducir opciones para cada tipo de sintaxis, sino solo para cosas más impactantes. Hemos identificado dos opciones principales que entran en esa categoría: tabuladores vs espacios y punto y coma vs sin punto y coma. ¡Así que las agregamos en Prettier!

--no-semi (#1129)

¡Gracias a rattrayalex que trabajó mucho para hacer esto posible!

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

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

--use-tabs (#1026)

¡Gracias a rhengles por implementar esto y explicar las razones detrás!

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

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

Formateo

El resto de esta publicación documenta todos los cambios menores que reunimos para la versión 1.0. Normalmente no publicaremos un registro de cambios como post, pero pensamos que muestra cómo Prettier se está volviendo estable y da una buena idea de los tipos de problemas que estamos corrigiendo actualmente.

Agregar nueva línea para funciones flecha sin llaves que retornan llamadas (#927)

Probablemente el problema más reportado en la versión actual de Prettier: no agregábamos un salto de línea después de funciones flecha dentro de llamadas. Ahora sí lo hacemos. Como nota al margen, es emocionante que los problemas ahora sean sobre detalles como este y ya no sean del tipo "esto se ve completamente roto".

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

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

Agregar línea suave en asignaciones y paréntesis alrededor de return para expresiones binarias (#871 & #870)

Las cadenas largas de expresiones lógicas se veían extrañas cuando la primera línea estaba en la misma línea que la declaración de variable o el return. En su lugar, es mejor moverla a la siguiente línea y agregar paréntesis en los return para que sea válido.

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

Agrupar primer argumento para funciones en línea (#947)

El primer patrón personalizado importante que agregamos en Prettier es imprimir funciones como últimos argumentos en línea. Uno menos común es hacer lo mismo para el primer argumento. Es poco común que las bibliotecas modernas usen ese estilo, pero algunas funciones centrales de JavaScript como setTimeout lo hacen, así que tiene sentido soportarlo en Prettier.

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

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

Mejorar salida JSX para componentes de estilo (#1144)

Los literales de plantilla son muy complejos de formatear porque los espacios en blanco son significativos. Con Prettier, no queremos cambiar la semántica de tu programa al reformatearlo, así que los dejamos como están. La forma actual de manejarlos no es óptima y tenemos ideas para corregirlos de manera general, pero mientras tanto podemos hacer correcciones específicas como esta para JSX.

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

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

Agregar soporte para notación de punto en la misma línea en decoradores (#1029)

MobX 3 ahora usa expresiones de miembro que deben escribirse en línea; la heurística anterior era demasiado restrictiva. Esto ya está corregido.

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

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

Compactar destructuring de objeto único en funciones (#1022)

Los componentes funcionales sin estado de React están muy de moda actualmente, y tiene sentido compactar el destructuring del primer argumento para ocupar menos espacio.

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

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

Agregar soporte para currificación (#1066)

Redux promueve fuertemente escribir funciones en forma currificada donde cada argumento es una función anidada. En lugar de indentarlas, ahora las colocamos en línea.

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

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

Respetar escapes para cadenas JSX (#1056)

Para las cadenas hemos probado varias soluciones respecto a escapes: no escapar nada, escapar todo, escapar un conjunto permitido... Pero no encontramos una heurística que cubriera todos los casos de uso razonablemente. Así que decidimos dejar las cadenas escapadas como se ingresaron. Ahora hacemos esto para cadenas JSX.

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

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

Paréntesis

Una bendición y maldición de imprimir desde el AST es que debemos reimprimir todos los paréntesis del programa. Nuestra postura solía ser imprimir solo el número mínimo necesario para que el programa fuera válido y se ejecutara igual. Ahora estamos dispuestos a agregar paréntesis que no son estrictamente necesarios pero ayudan a comprender el código.

Agregar paréntesis para operadores binarios (#1153)

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

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

Agregar paréntesis para la regla no-confusing-arrow (#1182)

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

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

Eliminar paréntesis en asignaciones encadenadas (#967)

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

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

Flow

En los primeros días de Prettier, nos enfocamos intensamente en lograr un buen soporte para el núcleo del lenguaje JavaScript. Ahora que está en buen estado, tenemos más tiempo para formatear construcciones de Flow de manera pulida.

Capacidad para romper generics de 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>;

Mejora en el formateo de intersecciones de Flow (#1018 y #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;

Soporte para saltos de línea en 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>,
];

Eliminación de paréntesis para shorthand de Flow con un argumento (#972)

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

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

Capacidad para romper interfaces (#1060)

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

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

Corrección en el formateo de literales numéricos en tipos de Flow (#1132)

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

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

Puntos de ruptura

Existen algunos casos donde Prettier aún produce código que supera las 80 columnas cuando existe una forma posible de escribirlo sin rebasar. La solución es identificar cuidadosamente lugares donde sea posible romper y asegurarse que esto no afecte negativamente los casos comunes.

Permitir ruptura después de = para strings y expresiones de miembro (#1142 y #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;

Capacidad para romper expresiones de miembro de nivel superior (#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");

Misceláneos

Expresiones de clase en línea para funciones flecha sin llaves (#1023)

Pequeño detalle: ahora integramos en línea las clases devueltas desde funciones flecha.

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

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

No respetar saltos de línea en patrones de destructuración de objetos (#981)

Para objetos, tenemos un manejo especial donde si existe un \n en el código fuente original, lo mantenemos expandido. Esto solo estaba destinado para objetos, pero como se comparte el mismo código con la destructuración, accidentalmente también se aplicaba allí. Esto ha sido corregido.

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

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

Soporte de lenguajes

Para que puedas usar Prettier, debe ser capaz de formatear el código que escribes. Nuestro objetivo es poder formatear todo lo que nuestros parsers subyacentes (babylon y flow) pueden analizar.

Soporte para ForOfStatement con flag await (#1073)

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

Soporte para spread de tipos en Flow (#1064)

type TypeB = { ...TypeA };

Corrección

El requisito absoluto de Prettier es producir código válido con el mismo comportamiento que el original. Debes poder ejecutarlo en todo tu código sin temor. Para garantizar esto, hacemos varias cosas:

  • Implementar métodos automatizados para verificar que Prettier genera código válido

  • Usarlo en bases de código grandes como Facebook, todo CDNjs y los muchos usuarios de la comunidad

  • Ejecutar fuzzing extensivo usando eslump

  • Tratar cada reporte de estos problemas como de alta prioridad y corregirlos inmediatamente

Si tienes la mala suerte de encontrar uno, por favor repórtalo para que podamos solucionarlo y puedas usar // prettier-ignore para desbloquearte.

Manejar correctamente \r\n en JSXText (#1170)

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

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

Corregir paréntesis en llamadas dentro de new (#1169)

// Before
new factory()();

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

Agregar paréntesis alrededor del tipo de retorno de funciones flecha (#1152)

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

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

Corregir impresión de anotaciones de tipo de objeto exacto en Flow (#1114)

// Before
type Props = {};

// After
type Props = {||};

Imprimir comentarios colgantes en return (#1178)

// Before
function f() {
return;
}

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

Imprimir comentario después de clave de objeto (#1131)

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

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

Corregir comentario inicial dentro de SequenceExpression retornado (#1133)

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

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

Corregir impresión de parámetro opcional único en funciones flecha (#1002)

// Before
a = b? => c;

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

Corregir exportaciones por defecto (#998)

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

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

Imprimir comentarios de línea/mixtos en nuevas líneas dentro de JSXEmptyExpression (#985)

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

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

Comentarios

prettier funciona imprimiendo el AST, lo que hace que manejar comentarios sea complicado ya que pueden colocarse en cualquier lugar entre tokens. La forma en que prettier funciona es adjuntar un comentario a un nodo del AST, ya sea antes o después. Tenemos una heurística genérica para encontrar la ubicación que funciona la mayoría de las veces, pero hay una gran cantidad de casos extremos que debemos manejar manualmente.

prettier nunca podrá imprimir correctamente los comentarios en todos los lugares extraños donde se puedan poner, pero esperamos poder hacer un trabajo razonable en la mayoría de los casos. Si ves algo raro relacionado con comentarios, por favor crea issues y podemos encontrar una solución.

Agregar soporte breakParent para willBreak (#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
},
);

Corregir línea vacía adicional en comentario de switch case (#936)

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


// no default
}

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

// no default
}

Corregir comentarios en declaraciones de importación (#1030)

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

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

Corregir comentario del último argumento en funciones (#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;
}

Eliminar restricción de comentarios en uniones de 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;

Corregir comentarios dentro de 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,
},
) => {}

Corregir ubicación de ordenamiento de comentarios (#1038)

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

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

Corregir ubicación de comentarios para expresiones binarias (#1043)

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

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

Corregir comentarios de métodos de clase (#1074)

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

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

Soporte para "// prettier-ignore" en bloques de comentarios (#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
)
};

Estabilizar comentarios en VariableDeclarator (#1130)

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

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

Corrección en la detección de comentarios antes de coma (#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',
};

Corrección del último comentario en pruebas condicionales (#1042)

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

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