プラグイン
プラグインは、Prettierに新しい言語やフォーマットルールを追加する方法です。Prettier自身のすべての言語の実装は、プラグインAPIを使用して表現されています。コアのprettier
パッケージには、JavaScriptやその他のWeb中心の言語が組み込まれています。追加の言語については、プラグインをインストールする必要があります。
プラグインの使用
プラグインは以下を使用してロードできます
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
by @Shinigami92@prettier/plugin-ruby
@prettier/plugin-xml
コミュニティプラグイン
prettier-plugin-apex
by @dangmaiprettier-plugin-astro
by @withastro contributorsprettier-plugin-elm
by @giCentreprettier-plugin-erb
by @adamzapasnikprettier-plugin-gherkin
by @mapadoprettier-plugin-glsl
by @NaridaLprettier-plugin-go-template
by @NiklasPorprettier-plugin-java
by @JHipsterprettier-plugin-jinja-template
by @davidodenwaldprettier-plugin-jsonata
by @Stediprettier-plugin-kotlin
by @Angry-Potatoprettier-plugin-motoko
by @dfinityprettier-plugin-nginx
by @joedeandevprettier-plugin-prisma
by @umidbekkprettier-plugin-properties
by @eemeliprettier-plugin-rust
by @jinxdashprettier-plugin-sh
by @JounQinprettier-plugin-sql
by @JounQinprettier-plugin-sql-cst
by @neneprettier-plugin-solidity
by @mattiaerreprettier-plugin-svelte
by @sveltejsprettier-plugin-toml
by @bd82
プラグインの開発
Prettierプラグインは、次の5つのエクスポート、または次のプロパティを持つデフォルトエクスポートを備えた通常のJavaScriptモジュールです
languages
parsers
printers
options
defaultOptions
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に変換します。
キーは、languages
のparsers
配列の名前と一致する必要があります。値には、parse関数、ASTフォーマット名、および2つの位置抽出関数(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;
(オプション)プラグマ検出関数(hasPragma
)は、テキストにプラグマコメントが含まれているかどうかを返す必要があります。
function hasPragma(text: string): boolean;
(オプション)プリプロセス関数は、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
のようなオプションに基づいて)文字列に変換します。プリンターの仕事は、parsers[<パーサー名>].parse
によって生成されたASTを取得し、Docを返すことです。Docは、ビルダーコマンドを使用して構築されます
import { doc } from "prettier";
const { join, line, ifBreak, group } = doc.builders;
印刷プロセスは、次の手順で構成されます
- ASTプリプロセス(オプション)。
preprocess
を参照してください。 - コメントの添付(オプション)。プリンターでのコメントの処理を参照してください。
- 埋め込み言語の処理 (オプション)。
embed
メソッドが定義されている場合、各ノードに対して深さ優先で呼び出されます。パフォーマンス上の理由から、再帰自体は同期的に行われますが、embed
は、CSS-in-JS のような埋め込み構文のドキュメントを構成するために、他のパーサーやプリンターを呼び出すことができる非同期関数を返すことができます。これらの返された関数はキューに入れられ、次のステップの前に順次実行されます。 - 再帰的な印刷。ドキュメントは、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
関数とプラグインからのprint
関数の 2 つの関数は、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-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
メソッドは、AST ノードをドキュメントにマッピングするという点で print
メソッドに似ていますが、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
メソッドは、print
メソッドに渡す前に、パーサーからの AST を処理できます。
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));
}
2 番目の引数 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
(オプション) プラグインは、--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
オブジェクトには、3 つのオプション関数が含まれており、それぞれが次の署名を持っています。
(
// 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
のいずれか1つを注釈付け(つまり、新しいプロパティを作成)します。これらは、プラグインの決定プロセスを支援するために使用できます(もちろん、より複雑な決定を行うために、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
を適切な値に自動的に設定し、コメントを AST ノードの comments
配列に追加します。
--debug-print-comments
CLI フラグは、コメント付加の問題をデバッグするのに役立ちます。これは、すべてのコメントがどのように分類されたか(ownLine
/endOfLine
/remaining
、leading
/trailing
/dangling
)と、どのノードに付加されたかに関する情報を含む、コメントの詳細なリストを出力します。Prettier の組み込み言語の場合、この情報はプレイグラウンドでも利用できます(デバッグ セクションの「コメントを表示」チェックボックス)。
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
モジュールはプライベート 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 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 のプラグインを作成する方法: TOML の非常に基本的な Prettier プラグインを作成する方法を教えてくれます。
プラグインのテスト
プラグインは相対パスを使用して解決できるため、プラグインを操作するときは、次のように実行できます。
import * as prettier from "prettier";
const code = "(add 1 2)";
await prettier.format(code, {
parser: "lisp",
plugins: ["."],
});
これにより、現在の作業ディレクトリを基準にしてプラグインが解決されます。