Плагины
Плагины позволяют добавлять в Prettier новые языки или правила форматирования. Собственные реализации всех языков в Prettier используют API плагинов. В пакет prettier встроены JavaScript и другие языки, ориентированные на веб-разработку. Для поддержки дополнительных языков необходимо установить плагин.
Использование плагинов
Плагины можно загрузить с помощью:
-
CLI, используя
--plugin:prettier --write main.foo --plugin=prettier-plugin-foo
советПараметры
--pluginможно указывать несколько раз. -
API, используя параметры
plugins:await prettier.format("code", { parser: "foo", plugins: ["prettier-plugin-foo"], }); -
{ "plugins": ["prettier-plugin-foo"] }
Строки, переданные в plugins, в итоге передаются в выражение import(), поэтому можно указать имя модуля или пакета, путь либо любое другое значение, принимаемое import().
Официальные плагины
@prettier/plugin-php-
@prettier/plugin-pug, автор — @Shinigami92 @prettier/plugin-ruby@prettier/plugin-xml
Плагины сообщества
-
prettier-plugin-apex, автор — @dangmai -
prettier-plugin-astro, авторы — @withastro contributors -
prettier-plugin-bigcommerce-stencil, автор — @phoenix128 -
prettier-plugin-elm, автор — @giCentre -
prettier-plugin-erb, автор — @adamzapasnik -
prettier-plugin-gherkin, автор — @mapado -
prettier-plugin-glsl, автор — @NaridaL -
prettier-plugin-go-template, автор — @NiklasPor -
prettier-plugin-hugo-post, автор — @metcalfc -
prettier-plugin-java, автор — @JHipster -
prettier-plugin-jinja-template, автор — @davidodenwald -
prettier-plugin-jsonata, автор — @Stedi -
prettier-plugin-kotlin, автор — @Angry-Potato -
prettier-plugin-marko, автор — @marko-js -
prettier-plugin-motoko, автор — @dfinity -
prettier-plugin-nginx, автор — @joedeandev -
prettier-plugin-prisma, автор — @umidbekk -
prettier-plugin-properties, автор — @eemeli -
prettier-plugin-rust, автор — @jinxdash -
prettier-plugin-sh, автор — @JounQin -
prettier-plugin-sql, автор — @JounQin -
prettier-plugin-sql-cst, автор — @nene -
prettier-plugin-solidity, автор — @mattiaerre -
prettier-plugin-svelte, автор — @sveltejs -
prettier-plugin-toml, авторы — @JounQin и @so1ve -
prettier-plugin-xquery, автор — @DrRataplan
Разработка плагинов
Плагины Prettier — это обычные модули JavaScript с пятью экспортами или экспортом по умолчанию, содержащим следующие свойства:
languagesparsersprintersoptionsdefaultOptions
languages
Languages — это массив описаний языков, которые ваш плагин добавляет в Prettier. Он может содержать все поля, указанные в prettier.getSupportInfo().
Он обязательно должен содержать name и 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
Парсеры преобразуют код в виде строки в AST.
Ключ должен совпадать с именем в массиве parsers из languages. Значение содержит функцию parse, имя формата AST и две функции извлечения расположения (locStart и locEnd).
export const parsers = {
"dance-parse": {
parse,
// The name of the AST that the parser produces.
astFormat: "dance-ast",
hasPragma,
hasIgnorePragma,
locStart,
locEnd,
preprocess,
},
}; Сигнатура функции parse:
function parse(text: string, options: object): Promise<AST> | AST;
Функции извлечения расположения (locStart и locEnd) возвращают начальное и конечное расположение заданного узла AST:
function locStart(node: object): number;
(Необязательно) Функция обнаружения pragma (hasPragma) должна возвращать значение, указывающее, содержит ли текст комментарий pragma.
function hasPragma(text: string): boolean;
(Необязательно) Функция обнаружения pragma игнорирования (hasIgnorePragma) должна возвращать значение, указывающее, содержит ли текст pragma, обозначающую, что форматировать его не следует.
function hasIgnorePragma(text: string): boolean;
(Необязательно) Функция preprocess может обработать входной текст перед передачей функции parse.
function preprocess(text: string, options: object): string | Promise<string>;
Поддержка асинхронной preprocess впервые появилась в версии 3.7.0
printers
Принтеры преобразуют AST в промежуточное представление Prettier, также известное как Doc.
Ключ должен совпадать с astFormat, который возвращает парсер. Значение содержит объект с функцией print. Все остальные свойства (embed, preprocess и т. д.) необязательны.
export const printers = {
"dance-ast": {
print,
embed,
preprocess,
getVisitorKeys,
insertPragma,
canAttachComment,
isBlockComment,
printComment,
getCommentChildNodes,
hasPrettierIgnore,
printPrettierIgnored,
handleComments: {
ownLine,
endOfLine,
remaining,
},
},
}; Процесс печати
Prettier использует промежуточное представление Doc, которое затем преобразует в строку (с учётом таких параметров, как printWidth). Задача принтера — принять AST, созданное с помощью parsers[<parser name>].parse, и вернуть Doc. Doc создаётся с помощью команд построения:
import * as prettier from "prettier";
const { join, line, ifBreak, group } = prettier.doc.builders; Процесс печати состоит из следующих этапов:
-
Предварительная обработка AST (необязательно). См.
preprocess. - Привязка комментариев (необязательно). См. раздел Обработка комментариев в принтере.
-
Обработка встроенных языков (необязательно). Метод
embed, если он определён, вызывается для каждого узла при обходе в глубину. По соображениям производительности сама рекурсия выполняется синхронно, однакоembedможет возвращать асинхронные функции, вызывающие другие парсеры и принтеры для создания документов для встроенных синтаксисов, например CSS-in-JS. Эти возвращённые функции ставятся в очередь и выполняются последовательно перед следующим этапом. -
Рекурсивная печать. Doc рекурсивно создаётся на основе AST. Начиная с корневого узла:
- Если на этапе 3 для текущего узла был создан документ встроенного языка, используется этот документ.
- В противном случае вызывается метод
print(path, options, print): Doc. Он формирует Doc для текущего узла, часто печатая дочерние узлы с помощью обратного вызоваprint.
print
Основная работа принтера плагина выполняется в функции print, сигнатура которой выглядит так:
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;
Функция print получает следующие параметры:
-
path: объект для доступа к узлам AST. Это структура данных, похожая на стек, которая хранит текущее состояние рекурсии. Она называется «path» (путь), поскольку представляет путь от корня AST к текущему узлу. Текущий узел возвращаетpath.node. -
options: постоянный объект, содержащий глобальные параметры; плагин может изменять его, чтобы хранить контекстные данные. -
print: обратный вызов для печати дочерних узлов. Эта функция содержит основную логику печати, состоящую из этапов, реализация которых предоставляется плагинами. В частности, она вызывает функциюprintпринтера и передаёт ей себя. Таким образом, две функцииprint— из ядра и из плагина — вызывают друг друга при рекурсивном обходе AST.
Ниже приведён упрощённый пример типичной реализации функции 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}`);
} Примеры возможностей можно найти в принтере prettier-python.
(необязательно) embed
Принтер может содержать метод embed для печати одного языка внутри другого. Например, для печати CSS-in-JS или блоков кода с ограждениями в Markdown. Сигнатура:
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; Метод embed похож на метод print тем, что сопоставляет узлы AST с документами, но, в отличие от print, он может выполнять асинхронные операции, возвращая асинхронную функцию. Первый параметр этой функции — асинхронная функция textToDoc — позволяет отрисовать документ с помощью другого плагина.
Если функция, возвращённая из embed, возвращает документ или промис, результатом которого становится документ, этот документ будет использован при печати, а метод print для данного узла вызван не будет. Также можно — и в редких случаях это может быть удобно — синхронно вернуть документ непосредственно из embed, однако в этом случае textToDoc и обратный вызов print недоступны. Чтобы получить к ним доступ, верните функцию.
Если embed возвращает undefined либо возвращённая им функция возвращает undefined или промис, результатом которого становится undefined, узел будет напечатан обычным образом с помощью метода print. То же произойдёт, если возвращённая функция выдаст ошибку или вернёт отклонённый промис (например, если произошла ошибка разбора). Чтобы Prettier повторно выдавал эти ошибки, задайте непустое значение переменной среды PRETTIER_DEBUG.
Например, плагин с узлами, содержащими встроенный JavaScript, может иметь следующий метод 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>",
];
};
}
} Если параметр --embedded-language-formatting имеет значение off, этап встраивания полностью пропускается, embed не вызывается, а все узлы печатаются с помощью метода print.
(необязательно) preprocess
Метод preprocess может обработать AST, полученное от парсера, перед передачей его методу print.
function preprocess(ast: AST, options: Options): AST | Promise<AST>;
(необязательно) getVisitorKeys
Это свойство может пригодиться, если плагин использует привязку комментариев или встроенные языки. Эти функции обходят AST, начиная с корня и перебирая все собственные перечисляемые свойства каждого узла. Если в AST есть циклы, такой обход приводит к бесконечному циклу. Кроме того, узлы могут содержать объекты, не являющиеся узлами (например, данные о расположении), перебор которых приводит к напрасной трате ресурсов. Чтобы решить эти проблемы, принтер может определить функцию, возвращающую имена свойств, которые следует обходить.
Её сигнатура:
function getVisitorKeys(node, nonTraversableKeys: Set<string>): string[];
Значение getVisitorKeys по умолчанию:
function getVisitorKeys(node, nonTraversableKeys) {
return Object.keys(node).filter((key) => !nonTraversableKeys.has(key));
} Второй аргумент nonTraversableKeys — это набор общих ключей и ключей, используемых Prettier внутри.
Если у вас есть полный список ключей посетителя
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] ?? [];
} Если нужно исключить только несколько ключей
const ignoredKeys = new Set(["prev", "next", "range"]);
function getVisitorKeys(node, nonTraversableKeys) {
return Object.keys(node).filter(
(key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key),
);
} (необязательно) insertPragma
Плагин может реализовать вставку комментария pragma в результирующий код при использовании параметра --insert-pragma с помощью функции insertPragma. Её сигнатура:
function insertPragma(text: string): string;
Обработка комментариев в принтере
Комментарии часто не входят в AST языка и создают сложности для форматировщиков. Плагин Prettier может печатать комментарии самостоятельно в функции print или использовать алгоритм обработки комментариев Prettier.
По умолчанию, если AST содержит свойство верхнего уровня comments, Prettier предполагает, что comments хранит массив узлов комментариев. Затем Prettier использует предоставленные функции parsers[<plugin>].locStart/locEnd, чтобы найти узел AST, к которому «относится» каждый комментарий. Комментарии прикрепляются к этим узлам, изменяя AST в процессе, а свойство comments удаляется из корня AST. Функции *Comment используются для настройки алгоритма Prettier. После привязки комментариев к AST Prettier автоматически вызывает функцию printComment(path, options): Doc и вставляет возвращённый документ в (предположительно) нужное место.
(необязательно) getCommentChildNodes
По умолчанию Prettier рекурсивно ищет все свойства объектов каждого узла, кроме нескольких заранее определённых. Эту функцию можно использовать, чтобы переопределить такое поведение. Её сигнатура:
function getCommentChildNodes( // The node whose children should be returned. node: AST, // Current options options: object, ): AST[] | undefined;
Верните [], если у узла нет дочерних элементов, или undefined, чтобы использовать поведение по умолчанию.
(необязательно) hasPrettierIgnore
function hasPrettierIgnore(path: AstPath): boolean;
Возвращает признак того, что узел AST помечен как prettier-ignore.
(необязательно) printPrettierIgnored
Если узел AST помечен как prettier-ignore, по умолчанию Prettier извлекает текст для разбора, не вызывая функцию print. Однако плагин также может обработать печать узла, помеченного как prettier-ignore, добавив это свойство.
Это свойство имеет ту же сигнатуру, что и свойство print.
Впервые доступно в версии 3.7.0
(необязательно) printComment
Вызывается при необходимости напечатать узел комментария. Сигнатура:
function printComment( // Path to the current comment node commentPath: AstPath, // Current options options: object, ): Doc;
(необязательно) canAttachComment
function canAttachComment(node: AST, ancestors: T[]): boolean;
Эта функция определяет, можно ли прикрепить комментарий к конкретному узлу AST. По умолчанию выполняется обход всех свойств AST в поисках узлов, к которым можно прикрепить комментарии. Эта функция предотвращает прикрепление комментариев к определённому узлу. Типичная реализация выглядит так:
function canAttachComment(node, [parent]) {
return !(
node.type === "comment" ||
(parent?.type === "shorthand-property" &&
parent.key === node &&
parent.key !== parent.value)
);
} Второй параметр ancestors впервые появился в версии 3.7.0.
(необязательно) isBlockComment
function isBlockComment(node: AST): boolean;
Возвращает признак того, что узел AST является блочным комментарием.
(необязательно) handleComments
Объект handleComments содержит три необязательные функции, каждая из которых имеет сигнатуру:
( // 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;
Эти функции переопределяют стандартный алгоритм привязки комментариев Prettier. Ожидается, что ownLine/endOfLine/remaining либо вручную прикрепит комментарий к узлу и вернёт true, либо вернёт false, чтобы Prettier сам прикрепил комментарий.
В зависимости от текста вокруг узла комментария Prettier вызывает:
-
ownLine, если перед комментарием есть только пробельные символы, а после него — перевод строки; -
endOfLine, если после комментария есть перевод строки, а перед ним — непробельные символы; -
remainingво всех остальных случаях.
Перед вызовом Prettier добавляет к каждому узлу комментария AST (то есть создаёт новые свойства) как минимум одно из свойств: enclosingNode, precedingNode или followingNode. Плагин может использовать их при принятии решения (разумеется, для более сложных решений также передаются всё AST и исходный текст).
Ручная привязка комментария
Функции prettier.util.addTrailingComment/addLeadingComment/addDanglingComment можно использовать для ручной привязки комментария к узлу AST. Например, функция ownLine, которая гарантирует, что комментарий не следует за узлом «punctuation» (созданным для демонстрации), может выглядеть так:
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;
} У узлов с комментариями должно быть свойство comments, содержащее массив комментариев. Каждый комментарий должен иметь следующие свойства: leading, trailing, printed.
В приведённом выше примере используется prettier.util.addTrailingComment, который автоматически задаёт подходящие значения comment.leading/trailing/printed и добавляет комментарий в массив comments узла AST.
Флаг CLI --debug-print-comments помогает отлаживать проблемы с привязкой комментариев. Он выводит подробный список комментариев с информацией о классификации каждого комментария (ownLine/endOfLine/remaining, leading/trailing/dangling) и узле, к которому он был прикреплён. Для встроенных языков Prettier эта информация также доступна на Playground (флажок «show comments» в разделе Debug).
options
options — это объект, содержащий пользовательские параметры, поддерживаемые плагином.
Пример:
export default {
// ... plugin implementation
options: {
openingBraceNewLine: {
type: "boolean",
category: "Global",
default: true,
description: "Move open brace for code blocks onto new line.",
},
},
}; defaultOptions
Если для работы плагина нужны значения по умолчанию, отличные от значений некоторых основных параметров Prettier, их можно указать в defaultOptions:
export default {
// ... plugin implementation
defaultOptions: {
tabWidth: 4,
},
}; Вспомогательные функции
prettier.util предоставляет плагинам следующий ограниченный набор вспомогательных функций:
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; Руководства
- Как написать плагин для Prettier: рассказывает, как создать самый простой плагин Prettier для TOML.
Тестирование плагинов
Поскольку плагины можно подключать с помощью относительных путей, во время разработки можно выполнить следующее:
import * as prettier from "prettier";
const code = "(add 1 2)";
await prettier.format(code, {
parser: "lisp",
plugins: ["./index.js"],
}); Плагин будет найден относительно текущего рабочего каталога.
© James Long and contributors
https://prettier.io/docs/plugins