Aller au contenu principal

Plugins

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 →

Les plugins permettent d'ajouter de nouveaux langages ou règles de formatage à Prettier. Les implémentations natives de Prettier pour tous les langages utilisent l'API de plugin. Le paquet principal prettier inclut JavaScript et d'autres langages orientés web. Pour les langages supplémentaires, vous devrez installer un plugin.

Utilisation des plugins

Vous pouvez charger des plugins via :

  • La CLI avec --plugin :

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

    Vous pouvez spécifier plusieurs fois l'option --plugin.

  • L'API via l'option plugins :

    await prettier.format("code", {
    parser: "foo",
    plugins: ["prettier-plugin-foo"],
    });
  • Le fichier de configuration :

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

Les chaînes fournies à plugins sont transmises à l'expression import(), vous pouvez donc spécifier un nom de module/paquet, un chemin ou tout autre élément accepté par import().

Plugins officiels

Plugins communautaires

Développement de plugins

Les plugins Prettier sont des modules JavaScript standards qui doivent exporter les cinq éléments suivants, ou un export par défaut avec ces propriétés :

  • languages

  • parsers

  • printers

  • options

  • defaultOptions

languages

Languages est un tableau de définitions de langages que votre plugin ajoutera à Prettier. Il peut inclure tous les champs spécifiés dans prettier.getSupportInfo().

Il doit inclure name et 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

Les parseurs convertissent du code sous forme de chaîne de caractères en un AST.

La clé doit correspondre au nom dans le tableau parsers de languages. La valeur contient une fonction d'analyse, un nom de format d'AST et deux fonctions d'extraction de position (locStart et 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 signature de la fonction parse est :

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

Les fonctions d'extraction de position (locStart et locEnd) renvoient les positions de début et de fin d'un nœud AST donné :

function locStart(node: object): number;

(Optionnel) La fonction de détection de pragma (hasPragma) doit indiquer si le texte contient le commentaire pragma.

function hasPragma(text: string): boolean;

(Optionnel) La fonction de détection "d'ignorer le pragma" (hasIgnorePragma) doit indiquer si le texte contient un pragma signalant qu'il ne doit pas être formaté.

function hasIgnorePragma(text: string): boolean;

(Optionnel) La fonction de prétraitement peut traiter le texte d'entrée avant de le transmettre à la fonction parse.

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

Prise en charge du pré-traitement asynchrone ajoutée initialement dans v3.7.0

printers

Les printers convertissent les ASTs en une représentation intermédiaire Prettier, également appelée Doc.

La clé doit correspondre à l'astFormat produit par le parser. La valeur contient un objet avec une fonction print. Toutes les autres propriétés (embed, preprocess, etc.) sont optionnelles.

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

Le processus d'impression

Prettier utilise une représentation intermédiaire appelée Doc, que Prettier transforme ensuite en chaîne de caractères (en fonction d'options comme printWidth). Le rôle d'un printer est de prendre l'AST généré par parsers[<parser name>].parse et de renvoyer un Doc. Un Doc est construit à l'aide de commandes du builder :

import * as prettier from "prettier";

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

Le processus d'impression comprend les étapes suivantes :

  1. Prétraitement AST (optionnel). Voir preprocess.

  2. Attachement des commentaires (optionnel). Voir Gestion des commentaires dans un printer.

  3. Traitement des langages embarqués (optionnel). La méthode embed, si définie, est appelée pour chaque nœud en parcours en profondeur. Bien que la récursion soit synchrone pour des raisons de performance, embed peut renvoyer des fonctions asynchrones qui appellent d'autres parsers et printers pour composer des docs pour des syntaxes embarquées comme du CSS-in-JS. Ces fonctions retournées sont mises en file d'attente et exécutées séquentiellement avant l'étape suivante.

  4. Impression récursive. Un doc est construit récursivement à partir de l'AST. En partant du nœud racine :

    • Si, à partir de l'étape 3, il existe un doc de langage embarqué associé au nœud courant, ce doc est utilisé.
    • Sinon, la méthode print(path, options, print): Doc est appelée. Elle compose un doc pour le nœud courant, souvent en imprimant les nœuds enfants à l'aide du callback print.

print

La majeure partie du travail d'un printer de plugin se déroule dans sa fonction print, dont la signature est :

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 fonction print reçoit les paramètres suivants :

  • path : Un objet permettant d'accéder aux nœuds de l'AST. C'est une structure de données de type pile qui maintient l'état courant de la récursion. Elle est appelée "path" car elle représente le chemin du nœud courant depuis la racine de l'AST. Le nœud courant est renvoyé par path.node.

  • options : Un objet persistant contenant les options globales, qu'un plugin peut muter pour stocker des données contextuelles.

  • print : Un callback pour imprimer les sous-nœuds. Cette fonction contient la logique d'impression principale qui comprend des étapes dont l'implémentation est fournie par les plugins. En particulier, elle appelle la fonction print du printer et se passe elle-même en paramètre. Ainsi, les deux fonctions print – celle du core et celle du plugin – s'appellent mutuellement tout en descendant récursivement dans l'AST.

Voici un exemple simplifié pour illustrer une implémentation typique 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}`);
}

Consultez le printer de prettier-python pour des exemples de ce qui est possible.

(optionnel) embed

Une imprimante peut avoir la méthode embed pour formater un langage à l'intérieur d'un autre. Des exemples incluent le CSS-in-JS ou les blocs de code délimités dans Markdown. Sa signature est :

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;

La méthode embed est similaire à print car elle transforme des nœuds AST en docs, mais contrairement à print, elle peut effectuer des opérations asynchrones en renvoyant une fonction asynchrone. Le premier paramètre de cette fonction, textToDoc, permet de formater un doc en utilisant un autre plugin.

Si une fonction renvoyée par embed retourne un doc ou une promesse qui résout en doc, ce doc sera utilisé pour l'impression, et la méthode print ne sera pas appelée pour ce nœud. Il est aussi possible, dans de rares situations, de retourner un doc de manière synchrone directement depuis embed, mais textToDoc et le callback print ne seront pas disponibles dans ce cas. Renvoyez une fonction pour y accéder.

Si embed renvoie undefined, ou si une fonction qu'elle a retournée renvoie undefined ou une promesse qui résout en undefined, le nœud sera imprimé normalement avec la méthode print. Il en sera de même si une fonction retournée lève une erreur ou retourne une promesse rejetée (par exemple en cas d'erreur d'analyse). Définissez la variable d'environnement PRETTIER_DEBUG avec une valeur non vide pour que Prettier propage ces erreurs.

Par exemple, un plugin avec des nœuds contenant du JavaScript embarqué pourrait avoir cette méthode 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 l'option --embedded-language-formatting est définie sur off, l'étape d'intégration est entièrement ignorée : embed n'est pas appelé et tous les nœuds sont imprimés avec la méthode print.

(optionnel) preprocess

La méthode preprocess peut modifier l'AST obtenu par l'analyseur avant de le transmettre à la méthode print.

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

(optionnel) getVisitorKeys

Cette propriété est utile si le plugin utilise l'attachement de commentaires ou des langages embarqués. Ces fonctionnalités parcourent l'AST en itérant sur toutes les propriétés propres énumérables de chaque nœud depuis la racine. Si l'AST contient des cycles, ce parcours peut entrer dans une boucle infinie. De plus, les nœuds peuvent contenir des objets non pertinents (comme des données de localisation), dont l'itération gaspille des ressources. Pour résoudre ces problèmes, l'imprimante peut définir une fonction renvoyant les noms de propriétés à parcourir.

Sa signature est :

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

La fonction getVisitorKeys par défaut :

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

Le deuxième argument nonTraversableKeys est un ensemble de clés communes et de clés utilisées en interne par Prettier.

Si vous disposez de la liste complète des clés de parcours :

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 vous ne devez exclure qu'un petit ensemble de clés :

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

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

(optionnel) insertPragma

Un plugin peut implémenter l'insertion d'un commentaire pragma dans le code résultant quand l'option --insert-pragma est utilisée, via la fonction insertPragma. Sa signature est :

function insertPragma(text: string): string;

Gestion des commentaires dans une imprimante

Les commentaires font souvent partie intégrante de l'AST d'un langage et posent défi aux formateurs. Un plugin Prettier peut soit imprimer lui-même les commentaires dans sa fonction print, soit s'appuyer sur l'algorithme de commentaires de Prettier.

Par défaut, si l'AST a une propriété comments de niveau supérieur, Prettier suppose que comments contient un tableau de nœuds de commentaires. Prettier utilisera alors les fonctions parsers[<plugin>].locStart/locEnd pour trouver le nœud AST auquel chaque commentaire "appartient". Les commentaires sont ensuite attachés à ces nœuds en modifiant l'AST, et la propriété comments est supprimée de la racine de l'AST. Les fonctions *Comment servent à ajuster l'algorithme de Prettier. Une fois les commentaires attachés à l'AST, Prettier appellera automatiquement printComment(path, options): Doc et insérera le doc retourné au bon endroit.

(optionnel) getCommentChildNodes

Par défaut, Prettier recherche récursivement toutes les propriétés d'objet (à l'exception de quelques-unes prédéfinies) de chaque nœud. Cette fonction peut être fournie pour remplacer ce comportement. Sa signature est :

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

Retournez [] si le nœud n'a pas d'enfants, ou undefined pour revenir au comportement par défaut.

(optionnel) hasPrettierIgnore

function hasPrettierIgnore(path: AstPath): boolean;

Indique si le nœud AST est ignoré par prettier-ignore ou non.

(optionnel) printPrettierIgnored

Si un nœud AST est ignoré par prettier-ignore, Prettier découpera le texte pour l'analyse sans appeler la fonction print par défaut. Cependant, un plugin peut gérer l'impression des nœuds prettier-ignored en ajoutant cette propriété.

Cette propriété possède la même signature que la propriété print.

Disponible initialement dans v3.7.0

(optionnel) printComment

Appelée lorsqu'un nœud de commentaire doit être imprimé. Sa signature est :

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

(optionnel) canAttachComment

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

Cette fonction permet de déterminer si un commentaire peut être attaché à un nœud AST spécifique. Par défaut, toutes les propriétés AST sont parcourues pour trouver des nœuds auxquels des commentaires peuvent être attachés. Cette fonction sert à empêcher l'attache de commentaires à un nœud particulier. Une implémentation typique ressemble à :

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

Le second paramètre ancestors ajouté initialement dans v3.7.0.

(optionnel) isBlockComment

function isBlockComment(node: AST): boolean;

Détermine si le nœud AST est un commentaire de bloc.

(optionnel) handleComments

L'objet handleComments contient trois fonctions optionnelles, chacune avec la signature :

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

Ces fonctions permettent de remplacer l'algorithme d'attache de commentaires par défaut de Prettier. ownLine/endOfLine/remaining doit soit attacher manuellement le commentaire à un nœud et retourner true, soit retourner false pour laisser Prettier l'attacher.

En fonction du texte entourant un nœud de commentaire, Prettier effectue le dispatch :

  • ownLine si un commentaire a uniquement des espaces avant et un saut de ligne après,

  • endOfLine si un commentaire a un saut de ligne après mais du texte non blanc avant,

  • remaining dans tous les autres cas.

Au moment du dispatch, Prettier aura annoté chaque nœud de commentaire AST (créé de nouvelles propriétés) avec au moins enclosingNode, precedingNode, ou followingNode. Ces propriétés peuvent aider la prise de décision du plugin (l'AST complet et le texte original sont également disponibles).

Attacher manuellement un commentaire

Les fonctions prettier.util.addTrailingComment/addLeadingComment/addDanglingComment permettent d'attacher manuellement un commentaire à un nœud AST. Une fonction ownLine personnalisée (illustrative) garantissant qu'un commentaire ne suit pas un nœud "ponctuation" pourrait s'écrire ainsi :

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

Les nœuds avec commentaires doivent avoir une propriété comments contenant un tableau de commentaires. Chaque commentaire doit avoir ces propriétés : leading, trailing, printed.

L'exemple ci-dessus utilise prettier.util.addTrailingComment, qui définit automatiquement comment.leading/trailing/printed avec les valeurs appropriées et ajoute le commentaire au tableau comments du nœud AST.

L'option CLI --debug-print-comments aide à déboguer l'attache des commentaires. Elle affiche une liste détaillée incluant : classification (ownLine/endOfLine/remaining, leading/trailing/dangling) et nœud attaché. Pour les langages intégrés, ces informations sont disponibles dans le Playground (case à cocher 'show comments' dans la section Debug).

options

options est un objet contenant les options personnalisées supportées par votre plugin.

Exemple :

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

defaultOptions

Si votre plugin nécessite des valeurs par défaut différentes pour certaines options de Prettier, spécifiez-les dans defaultOptions :

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

Fonctions utilitaires

prettier.util fournit aux plugins cet ensemble utilitaire de fonctions :

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;

Tutoriels

Tester les plugins

Comme les plugins peuvent être résolus via des chemins relatifs, lorsque vous travaillez sur l'un d'eux, vous pouvez faire :

import * as prettier from "prettier";

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

Cela résoudra le plugin relativement au répertoire de travail courant.