Error [prettier.resolveConfig.sync is not a function]

prettier.resolveConfig.sync is not a function の直し方(Prettier 3 で同期 API が削除された)

FIX SUMMARY verified
Applies when
node@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43prettier 3.0.0 (public API made asynchronous; the .sync variants were removed and synchronous users are pointed at @prettier/sync)a dependency update pulls prettier 3.x into node_modules while a script still calls prettier.resolveConfig.sync — often without any change to the project's own package.json, when prettier arrives through another package

Verified: reproduced in node@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43, then the prettier.resolveConfig.sync is not a function signature was gone after the fix (exit 0).

Prettier を呼び出している自作スクリプトや lint タスクが、依存を更新したあと次のエラーで止まります。

TypeError: prettier.resolveConfig.sync is not a function

落ちているのは node_modules に入った Prettier で、自分のソースの差分は要りません。

まず、.sync を呼んでいるのが誰かをトレースバックで確かめてください。 eslint-plugin-prettier を指しているなら、呼んでいるのはそのプラグインで、直し方も別になります(下の切り分け)。

直し方の要点を先に

Prettier を戻さず、呼び出し側を await に変えます。

// 変更前
const config = prettier.resolveConfig.sync(filePath);

// 変更後
const config = await prettier.resolveConfig(filePath);

await を使うので、呼び出している関数を async にします。CommonJS のトップレベルで呼んでいるなら、即時実行の async 関数で包みます。

const prettier = require("prettier");

(async () => {
  console.log(await prettier.resolveConfig("missing-prettier-config.json"));
})();

実測では、Prettier 3.0.0 のまま呼び出しを await prettier.resolveConfig(...) に変えると、同じ「存在しない設定ファイル」への問い合わせが null を返して終了コード 0 で終わりました。動かしたのは呼び出し側だけで、Prettier の版は下げていません。

戻り値が null になるのは設定ファイルが見つからなかったときで、これは同期版と同じ挙動です。 移行で変わるのは値ではなく、値の受け取り方です。

なぜ Prettier 3 で出るのか:public API が非同期に統一された

Prettier 3.0.0 は、公開 API を非同期へ統一し、同期版を削除しました。3.0.0 のリリース告知は「Change public APIs to asynchronous」として、prettier.resolveConfig.sync / prettier.resolveConfigFile.sync / prettier.getFileInfo.sync の3つを名指しで削除と挙げています。本記事のシグネチャはその1つ目です。API ドキュメントも「public API は非同期である」と述べ、同期呼び出しが必要な利用者を @prettier/sync へ案内しています。

削除なので、prettier.resolveConfig.sync は undefined になります。undefined を関数として呼んだ結果が、冒頭の TypeError です。警告や非推奨メッセージを経由せずに TypeError へ飛ぶのはこのためで、resolveConfig という名前自体は 3 系にも残っています。消えたのは .sync の側だけです。

Prettier呼び出し終了コード結果
2.8.8resolveConfig.sync("missing-prettier-config.json")0null を出力
3.0.0同じ1TypeError: prettier.resolveConfig.sync is not a function
3.0.0await resolveConfig("missing-prettier-config.json")0null を出力

測ったのは 2.8.8 と 3.0.0 の2点で、境界がこの2点の間にあることまでを確認しています。同期 API の削除が 3.0.0 の変更であることは、上記のリリース告知と API ドキュメントによります。

なぜ依存を更新した日に出るのか

package.json に "prettier": "^2.8.8" と書いてあれば、^ は 3.0.0 を含みません。この形のままなら 3 系は入らないので、境界はその範囲指定を動かしたときに来ます。

踏み方は主に2つです。

  • prettier の範囲を ^3.0.0 や latest へ上げた(意図した更新)
  • Prettier を直接の依存に持たず、他のパッケージ経由で入っている——この場合、自分の package.json を触っていなくても、そちらの更新で 3 系に入れ替わります

npm ls prettier で、いま解決されている版と、それを要求しているパッケージを確認できます。

同期呼び出しを残す必要があるとき

呼び出し元を async にできない事情があるなら、Prettier のドキュメントが案内しているのは @prettier/sync です。Prettier 3 と組み合わせて同期 API を提供する別パッケージで、Prettier 2 へ戻す方法ではありません。

const prettierSync = require("@prettier/sync");

const config = prettierSync.resolveConfig(filePath);

この経路は errfix では実測していません。 ドキュメントが示す選択肢として挙げています。上の await への移行は、同じ Prettier 3.0.0 で終了コード 0 まで確認したものです。

版を 2 系へ戻す前に

package.json を "prettier": "2.8.8" に固定すればエラーは消えます。ただしそれは、Prettier 3 の整形結果(Markdown やトレーリングカンマの既定など)を採らない選択と同じです。呼び出し側の変更は await の追加が主なので、戻す前に影響範囲を数えると比べられます。対象は resolveConfig.sync / resolveConfigFile.sync / getFileInfo.sync と、format の戻り値を文字列として扱っている箇所です。

切り分け

  • 【最初に見る】トレースバックが eslint-plugin-prettier を指している → 呼んでいるのはそのプラグインです。eslint-plugin-prettier@4 は内部で prettier.resolveConfig.sync を呼ぶので、Prettier 3 と組み合わせると lint の実行だけで同じ TypeError が出ます。この場合、自分のコードに .sync は 1 つもありません。 直すのはプラグインを Prettier 3 対応版(5 系)へ上げることで、下の await への移行ではありません。errfix で実測したのは eslint-plugin-prettier@4 が resolveConfig.sync を呼んでいることまでで、5 系へ上げて直るところは検証していません。
  • TypeError ではなく、整形結果が [object Promise] になっている → prettier.format の戻り値をそのまま文字列として使っています。3 系の format は Promise を返すので、こちらも await が要ります。例外が出ないぶん、ファイルに書き出したあとで気づく形になります。resolveConfigFile.sync と getFileInfo.sync も 3 系では消えています。
  • prettier コマンド(CLI)は動くのに、スクリプトだけ落ちる → CLI は 3 系でも同期・非同期を意識せずに使えます。境界は Node の API を直接呼んでいる箇所だけに出ます。
  • package.json の prettier を触っていないのに 3 系が入っている → 他の依存が Prettier を運んでいます。npm ls prettier で要求元を確認してください。
  • resolveConfig そのものが undefined → それは Prettier が読めていない形です。require("prettier") の解決先と、node_modules/prettier/package.json の version を確認してください。この記事の境界は .sync プロパティだけが消えている状態です。
  • エディタの保存時整形は効いているのに、スクリプトが落ちる → エディタの拡張は自前で Prettier を抱えていることがあり、プロジェクトの node_modules とは別の版を使っている場合があります。

検証環境

  • image: node(digest 固定 sha256:d32cdf61...)、ネットワーク有り(npm registry から Prettier を取得するため)
  • 再現:[email protected] を入れ、prettier.resolveConfig.sync("missing-prettier-config.json") を呼ぶ CommonJS スクリプトを実行 → TypeError: prettier.resolveConfig.sync is not a function で終了コード 1
  • 修正:同じ [email protected] のまま、呼び出しを await prettier.resolveConfig("missing-prettier-config.json") に変えて実行 → null を出力して終了コード 0
  • 版の境界表の 2.8.8 の行は、同じイメージ・同じスクリプトで Prettier だけを入れ替えて実行したもの

再現から修正までは errfix の検証ハーネスが機械的に確認しています。reproduce と fix の差は呼び出し側のスクリプトだけで、Node のイメージ・Prettier の版・問い合わせる設定ファイル名はすべて同一です。同期 API の削除と @prettier/sync の位置づけは、Prettier 3.0.0 のリリース告知および API ドキュメントで確認しました。@prettier/sync を使う経路そのものは実行していません。

検証(machine-verified)

この修正は node@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43 のバージョン固定コンテナ内で再現し、修正後に prettier.resolveConfig.sync is not a function のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/prettier3-resolveconfig-sync-removed
● reproduce prettier.resolveConfig.sync is not a function present ✓
● apply fix exit 0
● re-run prettier.resolveConfig.sync is not a function gone ✓
PASS verified · node@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43 · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。