メインコンテンツへスキップ

プラグイン

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

プラグインは、Prettierに新しい言語やフォーマットルールを追加する方法です。Prettier自体の全言語実装はプラグインAPIを使用して表現されています。コアのprettierパッケージにはJavaScriptやその他のWeb向け言語が組み込まれています。追加の言語を使用するにはプラグインのインストールが必要です。

プラグインの使用方法

プラグインは以下の方法で読み込めます:

  • CLI--pluginを使用:

    prettier --write main.foo --plugin=prettier-plugin-foo
    ヒント

    --pluginオプションは複数回指定できます。

  • APIpluginsオプションを使用:

    await prettier.format("code", {
    parser: "foo",
    plugins: ["prettier-plugin-foo"],
    });
  • 設定ファイルで指定:

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

pluginsに渡される文字列は最終的にimport()に渡されるため、モジュール/パッケージ名、パス、その他import()が受け入れる形式であれば何でも指定可能です。

公式プラグイン

コミュニティプラグイン

プラグインの開発

Prettierプラグインは、以下の5つのエクスポートを持つ通常のJavaScriptモジュール、または以下のプロパティを持つデフォルトエクスポートです:

  • languages

  • parsers

  • printers

  • options

  • defaultOptions

languages

languagesはプラグインがPrettierに提供する言語定義の配列です。prettier.getSupportInfo()で指定されているすべてのフィールドを含めることができます。

必ずnameparsersを含める必要があります。

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に変換します。

キーはlanguagesparsers配列の名前と一致させる必要があります。値にはパース関数、ASTフォーマット名、および2つの位置抽出関数(locStartlocEnd)が含まれます。

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;

(オプション) プラグマ検出関数(hasPragma)は、テキストにプラグマコメントが含まれているかどうかを返す必要があります。

function hasPragma(text: string): boolean;

(オプション) 「無視プラグマ」検出関数(hasIgnorePragma)は、テキストがフォーマット対象外であることを示すプラグマが含まれているかどうかを返す必要があります。

function hasIgnorePragma(text: string): boolean;

(オプション) 前処理関数は、parse関数に渡す前に入力テキストを処理できます。

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

非同期プリプロセスのサポートは v3.7.0 で初めて追加されました

printers

プリンターはASTをPrettierの中間表現(Docとも呼ばれる)に変換します。

キーはパーサーが生成するastFormatと一致する必要があります。値にはprint関数を含むオブジェクトが格納されます。その他のプロパティ(embedpreprocessなど)はすべてオプションです。

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

印刷プロセス

PrettierはDocと呼ばれる中間表現を使用し、これを文字列に変換します(printWidthなどのオプションに基づいて)。_プリンター_の役割は、parsers[<parser name>].parseによって生成されたASTを受け取り、Docを返すことです。Docはビルダーコマンドを使用して構築されます:

import * as prettier from "prettier";

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

印刷プロセスは以下のステップで構成されます:

  1. AST前処理(オプション)。preprocessを参照。

  2. コメントのアタッチメント(オプション)。プリンターでのコメント処理を参照。

  3. 埋め込み言語の処理(オプション)。定義されている場合、embedメソッドが各ノードに対して深さ優先で呼び出されます。パフォーマンス上の理由から再帰自体は同期的ですが、embedは非同期関数を返すことが可能です。これによりCSS-in-JSのような埋め込み構文のDocを構成するため、他のパーサーやプリンターを呼び出せます。これらの返された関数はキューに入れられ、次のステップ前に順次実行されます。

  4. 再帰的印刷。ASTからDocが再帰的に構築されます。ルートノードから開始します:

    • ステップ3で現在のノードに関連付けられた埋め込み言語Docがある場合、そのDocが使用されます。
    • それ以外の場合、print(path, options, print): Docメソッドが呼び出されます。これはprintコールバックを使用して子ノードを印刷することで、現在のノードのDocを構成します。

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関数を呼び出し、自身を渡します。これにより、コアとプラグインの2つの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 メソッドは AST ノードをドキュメントにマッピングする点で print メソッドと似ていますが、print とは異なり、非同期的な関数を返すことで非同期処理を行う能力を持っています。この関数の第一引数である textToDoc 非同期関数は、別のプラグインを使用してドキュメントをレンダリングするために使用できます。

embed から返された関数がドキュメント、またはドキュメントに解決される Promise を返した場合、そのドキュメントが出力に使用され、このノードに対して print メソッドは呼び出されません。また、まれな状況ではありますが、embed から直接同期的にドキュメントを返すことも可能であり、便利な場合があります。ただし、この場合 textToDocprint コールバックは利用できません。これらを利用するには関数を返す必要があります。

embedundefined を返した場合、または返された関数が undefined もしくは undefined に解決される Promise を返した場合、ノードは print メソッドで通常通り出力されます。返された関数がエラーをスローした場合や拒否される Promise を返した場合(例:パースエラーが発生した場合)も同様です。これらのエラーを再スローさせたい場合は、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

プラグインは、--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プロパティと同じシグネチャを持ちます。

v3.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は v3.7.0 で追加されました。

(オプション) 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コメントノードに(新しいプロパティを作成して)少なくとも enclosingNodeprecedingNodefollowingNode のいずれかを注釈付けしています。これらはプラグインの決定プロセスを支援するために使用できます(もちろん、より複雑な決定を行うためにAST全体と元のテキストも渡されます)。

コメントの手動アタッチ

prettier.util.addTrailingComment/addLeadingComment/addDanglingComment関数を使用すると、手動でコメントをASTノードに付加できます。デモンストレーション目的で作成された、コメントが「句読点」ノードに続かないことを保証するownLine関数の例は次のようになります:

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 プロパティを持つことが期待されます。各コメントは leadingtrailingprinted プロパティを持つことが期待されます。

上記の例ではprettier.util.addTrailingCommentを使用しており、これは自動的にcomment.leading/trailing/printedを適切な値に設定し、コメントをASTノードのcomments配列に追加します。

--debug-print-comments CLIフラグはコメントアタッチの問題をデバッグするのに役立ちます。これはすべてのコメントの詳細なリストを出力し、各コメントの分類方法(ownLine/endOfLine/remainingleading/trailing/dangling)とアタッチ先ノードに関する情報を含みます。Prettier組み込み言語の場合、この情報はPlaygroundでも利用可能です(Debugセクションの「show comments」チェックボックス)。

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;

チュートリアル

プラグインのテスト

プラグインは相対パスで解決できるため、開発中は次のように実行できます:

import * as prettier from "prettier";

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

これによりカレントワーキングディレクトリからの相対パスでプラグインが解決されます