Saltar al contenido principal

Complementos

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 →

Los complementos permiten agregar nuevos lenguajes o reglas de formato a Prettier. Las implementaciones nativas de Prettier para todos los lenguajes utilizan su API de complementos. El paquete principal prettier incluye JavaScript y otros lenguajes web integrados. Para lenguajes adicionales necesitarás instalar un complemento.

Uso de complementos

Puedes cargar complementos mediante:

  • La CLI usando --plugin:

    prettier --write main.foo --plugin=prettier-plugin-foo
    consejo

    Puedes especificar múltiples opciones --plugin.

  • La API mediante la opción plugins:

    await prettier.format("code", {
    parser: "foo",
    plugins: ["prettier-plugin-foo"],
    });
  • El archivo de configuración:

    {
    "plugins": ["prettier-plugin-foo"]
    }

Las cadenas proporcionadas a plugins se pasan a la expresión import(), por lo que puedes usar nombres de módulos/paquetes, rutas o cualquier valor válido para import().

Complementos oficiales

Complementos de la comunidad

Desarrollo de Plugins

Los plugins de Prettier son módulos JavaScript regulares que exportan las siguientes cinco propiedades o una exportación por defecto con estas propiedades:

languages

parsers

printers

options

defaultOptions

languages

Languages es un arreglo de definiciones de lenguajes que tu plugin aportará a Prettier. Puede incluir todos los campos especificados en prettier.getSupportInfo().

Debe incluir name y parsers.

export const languages = [
{
// The language name
name: "InterpretedDanceScript",
// Parsers that can parse this language.
// This can be built-in parsers, or parsers you have contributed via this plugin.
parsers: ["dance-parse"],
},
];

parsers

Los parsers convierten código en formato de texto a un AST.

La clave debe coincidir con el nombre en el arreglo parsers de languages. El valor contiene una función de parseo, un nombre de formato AST y dos funciones de extracción de ubicación (locStart y locEnd).

export const parsers = {
"dance-parse": {
parse,
// The name of the AST that the parser produces.
astFormat: "dance-ast",
hasPragma,
hasIgnorePragma,
locStart,
locEnd,
preprocess,
},
};

La firma de la función parse es:

function parse(text: string, options: object): Promise<AST> | AST;

Las funciones de extracción de ubicación (locStart y locEnd) devuelven las posiciones inicial y final de un nodo dado del AST:

function locStart(node: object): number;

(Opcional) La función de detección de pragma (hasPragma) debe indicar si el texto contiene el comentario pragma.

function hasPragma(text: string): boolean;

(Opcional) La función de detección de "ignorar pragma" (hasIgnorePragma) debe indicar si el texto contiene un pragma que señale que no debe formatearse.

function hasIgnorePragma(text: string): boolean;

(Opcional) La función de preprocesamiento puede procesar el texto de entrada antes de pasarlo a la función parse.

function preprocess(text: string, options: object): string | Promise<string>;

Soporte para preprocesamiento asíncrono añadido por primera vez en v3.7.0

printers

Los printers convierten ASTs en una representación intermedia de Prettier, conocida como Doc.

La clave debe coincidir con el astFormat que genera el parser. El valor contiene un objeto con una función print. Todas las demás propiedades (embed, preprocess, etc.) son opcionales.

export const printers = {
"dance-ast": {
print,
embed,
preprocess,
getVisitorKeys,
insertPragma,
canAttachComment,
isBlockComment,
printComment,
getCommentChildNodes,
hasPrettierIgnore,
printPrettierIgnored,
handleComments: {
ownLine,
endOfLine,
remaining,
},
},
};

El proceso de impresión

Prettier utiliza una representación intermedia llamada Doc, que luego convierte en una cadena (basada en opciones como printWidth). El trabajo de un printer es tomar el AST generado por parsers[<parser name>].parse y devolver un Doc. Un Doc se construye usando comandos de construcción:

import * as prettier from "prettier";

const { join, line, ifBreak, group } = prettier.doc.builders;

El proceso de impresión consta de los siguientes pasos:

  1. Preprocesamiento del AST (opcional). Consulta preprocess.

  2. Vinculación de comentarios (opcional). Consulta Manejo de comentarios en un printer.

  3. Procesamiento de lenguajes embebidos (opcional). El método embed, si está definido, se llama para cada nodo en profundidad. Aunque la recursividad es síncrona por rendimiento, embed puede devolver funciones asíncronas que llaman a otros parsers y printers para componer docs para sintaxis embebidas como CSS-in-JS. Estas funciones se encolan y ejecutan secuencialmente antes del siguiente paso.

  4. Impresión recursiva. Se construye un doc recursivamente desde el AST. Comenzando desde el nodo raíz:

    • Si, desde el paso 3, hay un doc de lenguaje embebido asociado al nodo actual, se usa ese doc.
    • De lo contrario, se llama al método print(path, options, print): Doc. Este compone un doc para el nodo actual, frecuentemente imprimiendo nodos hijos mediante el callback print.

print

La mayor parte del trabajo de un printer de plugin ocurre en su función print, cuya firma es:

function print(
// Path to the AST node to print
path: AstPath,
options: object,
// Recursively print a child node
print: (selector?: string | number | Array<string | number> | AstPath) => Doc,
): Doc;

La función print recibe los siguientes parámetros:

  • path: Un objeto que permite acceder a nodos en el AST. Es una estructura de datos tipo pila que mantiene el estado actual de la recursión. Se llama "path" porque representa la ruta desde el nodo actual hasta la raíz del AST. El nodo actual se obtiene con path.node.

  • options: Un objeto persistente que contiene opciones globales y que un plugin puede modificar para almacenar datos contextuales.

  • print: Un callback para imprimir subnodos. Esta función contiene la lógica central de impresión que consta de pasos implementados por plugins. En particular, llama a la función print del printer y se pasa a sí misma como argumento. Así, las dos funciones print (la del core y la del plugin) se llaman mutuamente mientras descienden recursivamente por el AST.

Este ejemplo simplificado ilustra una implementación típica de print:

import * as prettier from "prettier";

const { group, indent, join, line, softline } = prettier.doc.builders;

function print(path, options, print) {
const node = path.node;

switch (node.type) {
case "list":
return group([
"(",
indent([softline, join(line, path.map(print, "elements"))]),
softline,
")",
]);

case "pair":
return group([
"(",
indent([softline, print("left"), line, ". ", print("right")]),
softline,
")",
]);

case "symbol":
return node.name;
}

throw new Error(`Unknown node type: ${node.type}`);
}

Consulta el printer de prettier-python para ver ejemplos de lo que es posible.

(opcional) embed

Una impresora puede tener el método embed para formatear un lenguaje dentro de otro. Ejemplos de esto son CSS-in-JS o bloques de código en Markdown. La firma es:

function embed(
// Path to the current AST node
path: AstPath,
// Current options
options: Options,
):
| ((
// Parses and prints the passed text using a different parser.
// You should set `options.parser` to specify which parser to use.
textToDoc: (text: string, options: Options) => Promise<Doc>,
// Prints the current node or its descendant node with the current printer
print: (
selector?: string | number | Array<string | number> | AstPath,
) => Doc,
// The following two arguments are passed for convenience.
// They're the same `path` and `options` that are passed to `embed`.
path: AstPath,
options: Options,
) => Promise<Doc | undefined> | Doc | undefined)
| Doc
| undefined;

El método embed es similar a print porque mapea nodos AST a documentos, pero a diferencia de print, puede realizar trabajo asíncrono devolviendo una función asíncrona. El primer parámetro de esa función, textToDoc, puede usarse para renderizar un documento usando otro plugin.

Si una función devuelta por embed retorna un documento o una promesa que se resuelve en un documento, ese documento se usará para el formateo y no se llamará al método print para ese nodo. También es posible (y en raras situaciones conveniente) devolver un documento sincrónicamente directamente desde embed, pero en ese caso textToDoc y el callback print no están disponibles. Devuelve una función para acceder a ellos.

Si embed devuelve undefined, o si una función devuelta retorna undefined o una promesa que se resuelve en undefined, el nodo se formateará normalmente con print. Lo mismo ocurrirá si la función devuelta lanza un error o retorna una promesa rechazada (ej. por un error de parsing). Establece la variable de entorno PRETTIER_DEBUG con un valor no vacío si quieres que Prettier relance estos errores.

Por ejemplo, un plugin con nodos de JavaScript incrustado podría tener este método embed:

function embed(path, options) {
const node = path.node;
if (node.type === "javascript") {
return async (textToDoc) => {
return [
"<script>",
hardline,
await textToDoc(node.javaScriptCode, { parser: "babel" }),
hardline,
"</script>",
];
};
}
}

Si la opción --embedded-language-formatting está en off, se omite completamente el paso de incrustación: no se llama a embed y todos los nodos se formatean con print.

(opcional) preprocess

El método preprocess puede procesar el AST del parser antes de pasarlo a print.

function preprocess(ast: AST, options: Options): AST | Promise<AST>;

(opcional) getVisitorKeys

Esta propiedad es útil si el plugin usa adjunción de comentarios o lenguajes incrustados. Estas características recorren el AST iterando por todas las propiedades enumerables propias de cada nodo desde la raíz. Si el AST tiene ciclos, este recorrido entraría en un bucle infinito. Además, los nodos pueden contener objetos no-nodo (ej. datos de ubicación), cuya iteración desperdicia recursos. Para resolver esto, la impresora puede definir una función que devuelva los nombres de propiedades a recorrer.

Su firma es:

function getVisitorKeys(node, nonTraversableKeys: Set<string>): string[];

El getVisitorKeys predeterminado:

function getVisitorKeys(node, nonTraversableKeys) {
return Object.keys(node).filter((key) => !nonTraversableKeys.has(key));
}

El segundo argumento nonTraversableKeys es un conjunto de claves comunes y claves que Prettier usa internamente.

Si tienes la lista completa de claves a recorrer:

const visitorKeys = {
Program: ["body"],
Identifier: [],
// ...
};

function getVisitorKeys(node /* , nonTraversableKeys*/) {
// Return `[]` for unknown node to prevent Prettier fallback to use `Object.keys()`
return visitorKeys[node.type] ?? [];
}

Si solo necesitas excluir un pequeño conjunto de claves:

const ignoredKeys = new Set(["prev", "next", "range"]);

function getVisitorKeys(node, nonTraversableKeys) {
return Object.keys(node).filter(
(key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key),
);
}

(opcional) insertPragma

Un plugin puede implementar cómo se inserta un comentario pragma en el código resultante cuando se usa la opción --insert-pragma, mediante la función insertPragma. Su firma es:

function insertPragma(text: string): string;

Manejo de comentarios en una impresora

Los comentarios frecuentemente no son parte del AST de un lenguaje y representan un desafío. Un plugin de Prettier puede imprimir comentarios directamente en su función print o depender del algoritmo de comentarios de Prettier.

Por defecto, si el AST tiene una propiedad comments de nivel superior, Prettier asume que comments contiene un array de nodos de comentario. Luego usará las funciones parsers[<plugin>].locStart/locEnd para buscar el nodo AST al que "pertenece" cada comentario. Los comentarios se adjuntan a estos nodos mutando el AST, y se elimina la propiedad comments de la raíz. Las funciones *Comment ajustan el algoritmo de Prettier. Una vez adjuntos, Prettier llamará automáticamente a printComment(path, options): Doc e insertará el documento devuelto en el (con suerte) lugar correcto.

(opcional) getCommentChildNodes

Por defecto, Prettier busca recursivamente todas las propiedades de objeto (excepto algunas predefinidas) en cada nodo. Esta función puede proporcionarse para sobrescribir ese comportamiento. Tiene la firma:

function getCommentChildNodes(
// The node whose children should be returned.
node: AST,
// Current options
options: object,
): AST[] | undefined;

Devuelve [] si el nodo no tiene hijos o undefined para recurrir al comportamiento predeterminado.

(opcional) hasPrettierIgnore

function hasPrettierIgnore(path: AstPath): boolean;

Indica si el nodo del AST está ignorado con prettier-ignore o no.

(opcional) printPrettierIgnored

Si un nodo del AST es prettier-ignored, Prettier por defecto extraerá el texto para el análisis sin llamar a la función print. Sin embargo, un complemento también puede manejar la impresión del nodo prettier-ignored agregando esta propiedad.

Esta propiedad tiene la misma firma que la propiedad print.

Disponible por primera vez en v3.7.0

(opcional) printComment

Se invoca cuando se necesita imprimir un nodo de comentario. Tiene la firma:

function printComment(
// Path to the current comment node
commentPath: AstPath,
// Current options
options: object,
): Doc;

(opcional) canAttachComment

function canAttachComment(node: AST, ancestors: T[]): boolean;

Esta función decide si un comentario puede adjuntarse a un nodo AST específico. Por defecto, se recorren todas las propiedades AST buscando nodos donde puedan adjuntarse comentarios. Esta función evita que los comentarios se adjunten a nodos específicos. Una implementación típica sería:

function canAttachComment(node, [parent]) {
return !(
node.type === "comment" ||
(parent?.type === "shorthand-property" &&
parent.key === node &&
parent.key !== parent.value)
);
}

El segundo parámetro ancestors añadido por primera vez en v3.7.0.

(opcional) isBlockComment

function isBlockComment(node: AST): boolean;

Determina si un nodo AST es un comentario de bloque.

(opcional) handleComments

El objeto handleComments contiene tres funciones opcionales, cada una con firma:

(
// The AST node corresponding to the comment
comment: AST,
// The full source code text
text: string,
// The global options object
options: object,
// The AST
ast: AST,
// Whether this comment is the last comment
isLastComment: boolean,
) => boolean;

Estas funciones sobrescriben el algoritmo predeterminado de Prettier para adjuntar comentarios. Se espera que ownLine/endOfLine/remaining adjunte manualmente el comentario a un nodo (devolviendo true) o devuelva false para que Prettier lo adjunte.

Según el texto alrededor de un comentario, Prettier clasifica:

  • ownLine: si el comentario tiene solo espacios antes y un salto de línea después.

  • endOfLine: si tiene salto de línea después pero algún texto no espaciado antes.

  • remaining: en todos los demás casos.

Al clasificar, Prettier anota cada nodo de comentario AST con al menos una de: enclosingNode, precedingNode, o followingNode. Estas ayudan en la decisión del plugin (el AST completo y texto original también están disponibles).

Adjuntar comentarios manualmente

Las funciones prettier.util.addTrailingComment/addLeadingComment/addDanglingComment pueden utilizarse para adjuntar manualmente comentarios a un nodo AST. Por ejemplo, una función ownLine que garantice que un comentario no siga a un nodo de "puntuación" (creado con fines demostrativos) podría verse así:

import * as prettier from "prettier";

function ownLine(comment, text, options, ast, isLastComment) {
const { precedingNode } = comment;
if (precedingNode && precedingNode.type === "punctuation") {
prettier.util.addTrailingComment(precedingNode, comment);
return true;
}
return false;
}

Los nodos con comentarios deben tener una propiedad comments con un array de comentarios. Cada comentario debe tener: leading, trailing, printed.

El ejemplo anterior utiliza prettier.util.addTrailingComment, que establece automáticamente comment.leading/trailing/printed con valores apropiados y agrega el comentario al array comments del nodo AST.

La bandera CLI --debug-print-comments ayuda a depurar adjuntos de comentarios. Muestra detalles clasificados (ownLine/endOfLine/remaining, leading/trailing/dangling) y nodos asociados. En los lenguajes integrados de Prettier, esta información está disponible en el Playground (casilla 'show comments' en Debug).

options

options es un objeto que contiene las opciones personalizadas que admite tu plugin.

Ejemplo:

export default {
// ... plugin implementation
options: {
openingBraceNewLine: {
type: "boolean",
category: "Global",
default: true,
description: "Move open brace for code blocks onto new line.",
},
},
};

defaultOptions

Si tu plugin requiere valores predeterminados diferentes para opciones del core de Prettier, especifícalos en defaultOptions:

export default {
// ... plugin implementation
defaultOptions: {
tabWidth: 4,
},
};

Funciones de utilidad

prettier.util proporciona el siguiente conjunto limitado de funciones utilitarias para complementos:

type Quote = '"' | "'";
type SkipOptions = { backwards?: boolean };

function getMaxContinuousCount(text: string, searchString: string): number;

function getStringWidth(text: string): number;

function getAlignmentSize(
text: string,
tabWidth: number,
startIndex?: number,
): number;

function getIndentSize(value: string, tabWidth: number): number;

function skip(
characters: string | RegExp,
): (
text: string,
startIndex: number | false,
options?: SkipOptions,
) => number | false;

function skipWhitespace(
text: string,
startIndex: number | false,
options?: SkipOptions,
): number | false;

function skipSpaces(
text: string,
startIndex: number | false,
options?: SkipOptions,
): number | false;

function skipToLineEnd(
text: string,
startIndex: number | false,
options?: SkipOptions,
): number | false;

function skipEverythingButNewLine(
text: string,
startIndex: number | false,
options?: SkipOptions,
): number | false;

function skipInlineComment(
text: string,
startIndex: number | false,
): number | false;

function skipTrailingComment(
text: string,
startIndex: number | false,
): number | false;

function skipNewline(
text: string,
startIndex: number | false,
options?: SkipOptions,
): number | false;

function hasNewline(
text: string,
startIndex: number,
options?: SkipOptions,
): boolean;

function hasNewlineInRange(
text: string,
startIndex: number,
startIndex: number,
): boolean;

function hasSpaces(
text: string,
startIndex: number,
options?: SkipOptions,
): boolean;

function getPreferredQuote(
text: string,
preferredQuoteOrPreferSingleQuote: Quote | boolean,
): Quote;

function makeString(
rawText: string,
enclosingQuote: Quote,
unescapeUnnecessaryEscapes?: boolean,
): string;

function getNextNonSpaceNonCommentCharacter(
text: string,
startIndex: number,
): string;

function getNextNonSpaceNonCommentCharacterIndex(
text: string,
startIndex: number,
): number | false;

function isNextLineEmpty(text: string, startIndex: number): boolean;

function isPreviousLineEmpty(text: string, startIndex: number): boolean;

Tutoriales

Pruebas de plugins

Dado que los plugins pueden resolverse mediante rutas relativas, al trabajar en uno puedes hacer:

import * as prettier from "prettier";

const code = "(add 1 2)";
await prettier.format(code, {
parser: "lisp",
plugins: ["./index.js"],
});

Esto resolverá un plugin en relación al directorio de trabajo actual.