From e830ad36a71ba2f81dda9ddeb4780fb6377494e9 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 25 Feb 2025 08:45:25 +0800 Subject: [PATCH] Support `URL` in `plugins` options (#17166) --- changelog_unreleased/api/17166.md | 47 ++++++++++++++++++++++++++ docs/api.md | 2 +- src/index.d.ts | 6 ++-- src/main/plugins/load-plugin.js | 12 ++++++- tests/integration/__tests__/plugins.js | 32 ++++++++++++++++++ 5 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 changelog_unreleased/api/17166.md create mode 100644 tests/integration/__tests__/plugins.js diff --git a/changelog_unreleased/api/17166.md b/changelog_unreleased/api/17166.md new file mode 100644 index 000000000..41671a113 --- /dev/null +++ b/changelog_unreleased/api/17166.md @@ -0,0 +1,47 @@ +#### Accept `URL` in `plugins` option (#17166 by @fisker) + +`plugins` option now accepts [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) with `file:` protocol or url string starts with `file:` in all public APIs. + +```js +// `URL` +await prettier.check("foo", { + parser: "my-cool-parser", + plugins: [new URL("./path/to/plugin.js", import.meta.url)], +}); +await prettier.format("foo", { + parser: "my-cool-parser", + plugins: [new URL("./path/to/plugin.js", import.meta.url)], +}); +await prettier.formatWithCursor("foo", { + parser: "my-cool-parser", + cursorOffset: 2, + plugins: [new URL("./path/to/plugin.js", import.meta.url)], +}); +await prettier.getFileInfo("/path/to/file", { + plugins: [new URL("./path/to/plugin.js", import.meta.url)], +}); +await prettier.getSupportInfo({ + plugins: [new URL("./path/to/plugin.js", import.meta.url)], +}); + +// URL string +await prettier.check("foo", { + parser: "my-cool-parser", + plugins: ["file:///path/to/plugin.js"], +}); +await prettier.format("foo", { + parser: "my-cool-parser", + plugins: ["file:///path/to/plugin.js"], +}); +await prettier.formatWithCursor("foo", { + parser: "my-cool-parser", + cursorOffset: 2, + plugins: ["file:///path/to/plugin.js"], +}); +await prettier.getFileInfo("/path/to/file", { + plugins: ["file:///path/to/plugin.js"], +}); +await prettier.getSupportInfo({ + plugins: ["file:///path/to/plugin.js"], +}); +``` diff --git a/docs/api.md b/docs/api.md index 2154c1ebe..ec7efc10b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -99,7 +99,7 @@ Setting `options.ignorePath` (`string | URL | (string | URL)[]`) and `options.wi If the given `fileUrlOrPath` is ignored, the `inferredParser` is always `null`. -Providing [plugin](plugins.md) paths in `options.plugins` (`string[]`) helps extract `inferredParser` for files that are not supported by Prettier core. +Providing [plugin](plugins.md) paths in `options.plugins` (`(string | URL | Plugin)[]`) helps extract `inferredParser` for files that are not supported by Prettier core. When setting `options.resolveConfig` (`boolean`, default `true`) to `false`, Prettier will not search for configuration file. This can be useful if this function is only used to check if file is ignored. diff --git a/src/index.d.ts b/src/index.d.ts index c3e43e54f..bb52309cc 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -398,7 +398,7 @@ export interface RequiredOptions extends doc.printer.Options { /** * Provide ability to support new languages to prettier. */ - plugins: Array; + plugins: Array; /** * How to handle whitespaces in HTML. * @default "css" @@ -801,7 +801,7 @@ export interface SupportInfo { export interface FileInfoOptions { ignorePath?: string | URL | (string | URL)[] | undefined; withNodeModules?: boolean | undefined; - plugins?: Array | undefined; + plugins?: Array | undefined; resolveConfig?: boolean | undefined; } @@ -816,7 +816,7 @@ export function getFileInfo( ): Promise; export interface SupportInfoOptions { - plugins?: Array | undefined; + plugins?: Array | undefined; showDeprecated?: boolean | undefined; } diff --git a/src/main/plugins/load-plugin.js b/src/main/plugins/load-plugin.js index fb189c62f..a1b85343b 100644 --- a/src/main/plugins/load-plugin.js +++ b/src/main/plugins/load-plugin.js @@ -1,8 +1,18 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; +import { isUrl } from "url-or-path"; import importFromDirectory from "../../utils/import-from-directory.js"; +/** +@param {string | URL} name +@param {string} cwd +*/ async function importPlugin(name, cwd) { + if (isUrl(name)) { + // @ts-expect-error -- Pass `URL` to `import()` works too + return import(name); + } + if (path.isAbsolute(name)) { return import(pathToFileURL(name).href); } @@ -23,7 +33,7 @@ async function loadPluginWithoutCache(plugin, cwd) { const cache = new Map(); function loadPlugin(plugin) { - if (typeof plugin !== "string") { + if (typeof plugin !== "string" && !(plugin instanceof URL)) { return plugin; } diff --git a/tests/integration/__tests__/plugins.js b/tests/integration/__tests__/plugins.js new file mode 100644 index 000000000..5ee34bf2c --- /dev/null +++ b/tests/integration/__tests__/plugins.js @@ -0,0 +1,32 @@ +import url from "node:url"; +import prettier from "../../config/prettier-entry.js"; + +const pluginUrl = new URL( + "../../config/prettier-plugins/prettier-plugin-uppercase-rocks/index.js", + import.meta.url, +); + +test("plugins", async () => { + const input = "foo"; + const expectedOutput = "FOO\n"; + + await expect( + prettier.format(input, { + parser: "uppercase-rocks", + plugins: [pluginUrl], + }), + ).resolves.toEqual(expectedOutput); + + await expect( + prettier.format(input, { + parser: "uppercase-rocks", + plugins: [pluginUrl.href], + }), + ).resolves.toEqual(expectedOutput); + await expect( + prettier.format(input, { + parser: "uppercase-rocks", + plugins: [url.fileURLToPath(pluginUrl)], + }), + ).resolves.toEqual(expectedOutput); +});