diff --git a/changelog_unreleased/api/17548.md b/changelog_unreleased/api/17548.md new file mode 100644 index 000000000..d3c6bf6fe --- /dev/null +++ b/changelog_unreleased/api/17548.md @@ -0,0 +1,29 @@ +#### Fix plugin loading in `prettier.getFileInfo()` (#17548 by @fisker) + +In previous version, `prettier.getFileInfo()` only read `parser` config from `.prettierrc`, but doesn't load plugin to infer parser from plugin `languages`, Prettier main fixed it. + +```js +// prettier-plugin-foo +export const languages = [ + { + parsers: ["foo"], + extensions: [".foo"], + }, +]; +``` + +```yaml +# .prettierrc +plugins: + - prettier-plugin-foo +``` + +```console +prettier --file-info file.foo + +# Prettier stable +{ "ignored": false, "inferredParser": null } + +# Prettier main +{ "ignored": false, "inferredParser": "foo" } +``` diff --git a/src/cli/file-info.js b/src/cli/file-info.js index 377445757..1b21bdfbc 100644 --- a/src/cli/file-info.js +++ b/src/cli/file-info.js @@ -3,6 +3,7 @@ import stringify from "fast-json-stable-stringify"; import { format, getFileInfo } from "../index.js"; import { printToScreen } from "./utils.js"; +// Note: This does not work with `--config-precedence` async function logFileInfoOrDie(context) { const { fileInfo: file, @@ -15,7 +16,7 @@ async function logFileInfoOrDie(context) { const fileInfo = await getFileInfo(path.resolve(file), { ignorePath, withNodeModules, - plugins, + plugins: plugins.length > 0 ? plugins : undefined, resolveConfig: config !== false, }); diff --git a/src/common/get-file-info.js b/src/common/get-file-info.js index d29b8ae51..6a967db6f 100644 --- a/src/common/get-file-info.js +++ b/src/common/get-file-info.js @@ -1,4 +1,5 @@ import { resolveConfig } from "../config/resolve-config.js"; +import { loadBuiltinPlugins, loadPlugins } from "../main/plugins/index.js"; import { isIgnored } from "../utils/ignore.js"; import inferParser from "../utils/infer-parser.js"; @@ -13,10 +14,9 @@ import inferParser from "../utils/infer-parser.js"; * @returns {Promise} * * Please note that prettier.getFileInfo() expects options.plugins to be an array of paths, - * not an object. A transformation from this array to an object is automatically done - * internally by the method wrapper. See withPlugins() in index.js. + * not an object. */ -async function getFileInfo(file, options) { +async function getFileInfo(file, options = {}) { if (typeof file !== "string" && !(file instanceof URL)) { throw new TypeError( `expect \`file\` to be a string or URL, got \`${typeof file}\``, @@ -30,10 +30,9 @@ async function getFileInfo(file, options) { } const ignored = await isIgnored(file, { ignorePath, withNodeModules }); - let inferredParser; if (!ignored) { - inferredParser = await getParser(file, options); + inferredParser = options.parser ?? (await getParser(file, options)); } return { @@ -51,7 +50,16 @@ async function getParser(file, options) { }); } - return config?.parser ?? inferParser(options, { physicalFile: file }); + if (config?.parser) { + return config.parser; + } + + let plugins = options.plugins ?? config?.plugins ?? []; + plugins = ( + await Promise.all([loadBuiltinPlugins(), loadPlugins(plugins)]) + ).flat(); + + return inferParser({ plugins }, { physicalFile: file }); } export default getFileInfo; diff --git a/src/index.js b/src/index.js index cf06e88c9..1ac5a26ad 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,6 @@ import leven from "leven"; import picocolors from "picocolors"; import * as vnopts from "vnopts"; import * as errors from "./common/errors.js"; -import getFileInfoWithoutPlugins from "./common/get-file-info.js"; import { mockable } from "./common/mockable.js"; import { clearCache as clearConfigCache, @@ -84,9 +83,6 @@ async function clearCache() { clearPluginCache(); } -/** @type {typeof getFileInfoWithoutPlugins} */ -const getFileInfo = withPlugins(getFileInfoWithoutPlugins); - /** @type {typeof getSupportInfoWithoutPlugins} */ const getSupportInfo = withPlugins(getSupportInfoWithoutPlugins, 0); @@ -136,11 +132,11 @@ export { clearCache as clearConfigCache, format, formatWithCursor, - getFileInfo, getSupportInfo, resolveConfig, resolveConfigFile, }; +export { default as getFileInfo } from "./common/get-file-info.js"; export * as doc from "./document/public.js"; export { default as version } from "./main/version.evaluate.js"; export * as util from "./utils/public.js"; diff --git a/tests/integration/__tests__/__snapshots__/file-info.js.snap b/tests/integration/__tests__/__snapshots__/file-info.js.snap index ac7edf07c..0f67b3885 100644 --- a/tests/integration/__tests__/__snapshots__/file-info.js.snap +++ b/tests/integration/__tests__/__snapshots__/file-info.js.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`extracts file-info for a file in not_node_modules 1`] = ` { diff --git a/tests/integration/__tests__/file-info.js b/tests/integration/__tests__/file-info.js index 2fe76dbe1..a9d209645 100644 --- a/tests/integration/__tests__/file-info.js +++ b/tests/integration/__tests__/file-info.js @@ -397,6 +397,58 @@ test("API getFileInfo accepts path or URL", async () => { expect(resultByRelativePath).toEqual(expectedResult); }); +test("getFileInfo should support `parser` and `plugins` in options", async () => { + { + const { inferredParser: parser } = await prettier.getFileInfo( + new URL("../cli/file-info/empty-config/foo.js", import.meta.url), + { + parser: "parser-from-options", + }, + ); + expect(parser).toBe("parser-from-options"); + } + + { + const { inferredParser: parser } = await prettier.getFileInfo( + new URL("../cli/file-info/empty-config/foo.js", import.meta.url), + ); + expect(parser).toBe("babel"); + } + + { + const { inferredParser: parser } = await prettier.getFileInfo( + new URL("../cli/file-info/config-with-parser/foo.js", import.meta.url), + ); + expect(parser).toBe("parser-from-prettierrc"); + } + + { + const { inferredParser: parser } = await prettier.getFileInfo( + new URL("../cli/file-info/config-with-plugin/file.foo", import.meta.url), + ); + expect(parser).toBe("parser-for-foo-file-from-plugin"); + } + + { + const { stdout } = await runCli("cli/file-info/config-with-plugin", [ + "--file-info", + "file.foo", + ]); + const { inferredParser: parser } = JSON.parse(stdout); + expect(parser).toBe("parser-for-foo-file-from-plugin"); + } + + { + const { inferredParser: parser } = await prettier.getFileInfo( + new URL("../cli/file-info/empty-config/file.bar", import.meta.url), + { + plugins: [new URL("../cli/file-info/plugin.js", import.meta.url)], + }, + ); + expect(parser).toBe("parser-for-bar-file-from-plugin"); + } +}); + test("API getFileInfo accepts path or URL as ignorePath", async () => { const file = new URL( "../cli/ignore-path/file-info-test/ignored-by-customignore.js", diff --git a/tests/integration/cli/file-info/config-with-parser/.prettierrc b/tests/integration/cli/file-info/config-with-parser/.prettierrc new file mode 100644 index 000000000..945a5f3af --- /dev/null +++ b/tests/integration/cli/file-info/config-with-parser/.prettierrc @@ -0,0 +1 @@ +parser: parser-from-prettierrc diff --git a/tests/integration/cli/file-info/config-with-plugin/.prettierrc b/tests/integration/cli/file-info/config-with-plugin/.prettierrc new file mode 100644 index 000000000..64bbc899a --- /dev/null +++ b/tests/integration/cli/file-info/config-with-plugin/.prettierrc @@ -0,0 +1,2 @@ +plugins: + - ../plugin.js diff --git a/tests/integration/cli/file-info/empty-config/.prettierrc b/tests/integration/cli/file-info/empty-config/.prettierrc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/integration/cli/file-info/empty-config/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/tests/integration/cli/file-info/plugin.js b/tests/integration/cli/file-info/plugin.js new file mode 100644 index 000000000..60027eb1b --- /dev/null +++ b/tests/integration/cli/file-info/plugin.js @@ -0,0 +1,10 @@ +export const languages = [ + { + parsers: ['parser-for-foo-file-from-plugin'], + extensions: ['.foo'] + }, + { + parsers: ['parser-for-bar-file-from-plugin'], + extensions: ['.bar'] + } +]