プラグイン
プラグインは、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-pugby @Shinigami92
- @prettier/plugin-ruby
- @prettier/plugin-xml
コミュニティプラグイン
- prettier-plugin-apexby @dangmai
- prettier-plugin-astroby @withastro contributors
- prettier-plugin-elmby @giCentre
- prettier-plugin-erbby @adamzapasnik
- prettier-plugin-gherkinby @mapado
- prettier-plugin-glslby @NaridaL
- prettier-plugin-go-templateby @NiklasPor
- prettier-plugin-javaby @JHipster
- prettier-plugin-jinja-templateby @davidodenwald
- prettier-plugin-jsonataby @Stedi
- prettier-plugin-kotlinby @Angry-Potato
- prettier-plugin-motokoby @dfinity
- prettier-plugin-nginxby @joedeandev
- prettier-plugin-prismaby @umidbekk
- prettier-plugin-propertiesby @eemeli
- prettier-plugin-rustby @jinxdash
- prettier-plugin-shby @JounQin
- prettier-plugin-sqlby @JounQin
- prettier-plugin-sql-cstby @nene
- prettier-plugin-solidityby @mattiaerre
- prettier-plugin-svelteby @sveltejs
- prettier-plugin-tomlby @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: ["."],
});
これにより、現在の作業ディレクトリを基準にしてプラグインが解決されます。
