Плагины
Плагины — это способы добавления новых языков или правил форматирования в Prettier. Собственные реализации всех языков в Prettier выражены с помощью API плагинов. Основной пакет prettier содержит JavaScript и другие ориентированные на веб-языки, встроенные в него. Для дополнительных языков вам потребуется установить плагин.
Использование плагинов
Вы можете загружать плагины с помощью:
-
Командной строки (CLI), через
--plugin:prettier --write main.foo --plugin=prettier-plugin-foo
Подсказка: Вы можете устанавливать параметры
--pluginнесколько раз. -
API (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-elmот @giCentre -
prettier-plugin-erbот @adamzapasnik -
prettier-plugin-gherkinот @mapado -
prettier-plugin-glslот @NaridaL -
prettier-plugin-go-templateот @NiklasPor -
prettier-plugin-javaот @JHipster -
prettier-plugin-jinja-templateот @davidodenwald -
prettier-plugin-jsonataот @Stedi -
prettier-plugin-kotlinот @Angry-Potato -
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от @bd82
Разработка плагинов
Плагины 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. Значение содержит функцию разбора, имя формата AST и две функции извлечения местоположений (locStart и locEnd).
export const parsers = { "dance-parse": { parse, // The name of the AST that the parser produces. astFormat: "dance-ast", hasPragma, 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;
(Необязательно) Функция preprocess может обработать входной текст перед передачей в функцию parse.
function preprocess(text: string, options: object): string;
printers
Печатальщиках преобразуют AST в промежуточное представление Prettier, также известное как Doc.
Ключ должен соответствовать astFormat, которое создаёт парсер. Значение содержит объект с функцией print. Все остальные свойства (embed, preprocess, и т.д.) необязательны.
export const printers = { "dance-ast": { print, embed, preprocess, getVisitorKeys, insertPragma, canAttachComment, isBlockComment, printComment, getCommentChildNodes, handleComments: { ownLine, endOfLine, remaining, }, }, };
Процесс печати
Prettier использует промежуточное представление, называемое Doc, которое Prettier затем преобразует в строку (на основе таких параметров, как printWidth). Задача печатальщика — принять AST, сгенерированный parsers[<parser name>].parse, и вернуть Doc. Doc строится с помощью команд-строителей:
import { doc } from "prettier"; const { join, line, ifBreak, group } = doc.builders;
Процесс печати состоит из следующих шагов:
-
Предварительная обработка AST (необязательно). Смотрите
preprocess. - Прикрепление комментариев (необязательно). Смотрите Обработка комментариев в печатальщике.
-
Обработка вложенных языков (необязательно). Метод
embed, если определён, вызывается для каждого узла в глубину. Хотя по соображениям производительности сама рекурсия синхронная,embedможет возвращать асинхронные функции, которые могут вызывать другие парсеры и печатальщики для построения документов для вложенных синтаксисов, таких как CSS-в-JS. Эти возвращённые функции помещаются в очередь и выполняются последовательно перед следующим шагом. -
Рекурсивный вывод. Doc рекурсивно строится из AST. Начиная с корневого узла:
- Если из шага 3 существует документ вложенного языка, связанный с текущим узлом, этот документ используется.
- В противном случае вызывается метод
print(path, options, print): 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. Это структура данных, похожая на стек, которая сохраняет текущее состояние рекурсии. Она называется «путь», потому что представляет собой путь к текущему узлу от корня AST. Текущий узел возвращается функциейpath.node. -
options: Постоянный объект, который содержит глобальные параметры и который плагин может изменить для хранения контекстуальных данных. -
print: Обратный вызов для вывода дочерних узлов. Эта функция содержит основную логику вывода, которая состоит из шагов, реализация которых предоставляется плагинами. В частности, она вызывает функцию печатальщикаprintи передаёт себе в качестве аргумента. Таким образом, обе функцииprint— основная и плагина — вызывают друг друга, рекурсивно опускаясь по AST.
Вот упрощённый пример, чтобы понять, как выглядит типичная реализация функции print:
import { doc } from "prettier"; const { group, indent, join, line, softline } = 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-в-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_DEBUG в ненулевое значение, если вы хотите, чтобы Prettier повторно выбросил эти ошибки.
Например, плагин, у которого есть узлы с встроенным 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, чтобы вернуться к поведению по умолчанию.
(дополнительный) printComment
Вызывается всякий раз, когда необходимо напечатать узел комментария. Её сигнатура:
function printComment( // Path to the current comment node commentPath: AstPath, // Current options options: object, ): Doc;
(дополнительный) canAttachComment
function canAttachComment(node: AST): boolean;
Эта функция используется для определения возможности прикрепления комментария к конкретному узлу AST. По умолчанию все свойства AST обрабатываются в поисках узлов, к которым можно прикрепить комментарии. Эта функция используется для предотвращения прикрепления комментариев к конкретному узлу. Типичная реализация выглядит так:
function canAttachComment(node) { return node.type && node.type !== "comment"; }
(дополнительный) 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 и исходный текст также передаются для принятия более сложных решений).
Вручную прикрепление комментария
Функции util.addTrailingComment/addLeadingComment/addDanglingComment могут использоваться для ручного прикрепления комментария к узлу AST. Пример функции ownLine , которая гарантирует, что комментарий не следует за узлом «знаков препинания» (представлена для демонстрационных целей), может выглядеть следующим образом:
import { util } from "prettier"; function ownLine(comment, text, options, ast, isLastComment) { const { precedingNode } = comment; if (precedingNode && precedingNode.type === "punctuation") { util.addTrailingComment(precedingNode, comment); return true; } return false; }
Ожидается, что узлы с комментариями будут иметь свойство comments, содержащее массив комментариев. Каждый комментарий должен иметь следующие свойства: leading, trailing, printed.
В приведённом выше примере используется util.addTrailingComment, что автоматически устанавливает comment.leading/trailing/printed в соответствующие значения и добавляет комментарий в массив comments узла AST.
Флаг командной строки --debug-print-comments может помочь в отладке проблем с прикреплением комментариев. Он выводит подробный список комментариев, который включает информацию о том, как каждый комментарий был классифицирован (ownLine/endOfLine/remaining, leading/trailing/dangling) и к какому узлу он был прикреплён. Для встроенных языков Prettier эта информация также доступна на Playground (флажок «показать комментарии» в разделе «Отладка»).
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, }, };
Функции для работы с данными
Модуль util из ядра Prettier считается частным API и не предназначен для использования плагинами. Вместо этого модуль util-shared предоставляет следующий ограниченный набор функций для работы с данными для плагинов:
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: ["."], });
Это позволит разрешить плагин относительно текущей рабочей директории.
© James Long and contributors
https://prettier.io/docs/en/plugins