diff --git a/src/bin.ts b/src/bin.ts index d566abb..e74cf4a 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -3,9 +3,9 @@ import { toKebabCase } from "kasi"; import { bin, color, exit, parseArgv } from "specialist"; import { PRETTIER_VERSION, DEFAULT_PARSERS } from "./constants.js"; -import { getPlugin, isBoolean, isNumber, isIntegerInRange, isString } from "./utils.js"; +import { getPluginOrExit, isBoolean, isNumber, isIntegerInRange, isString } from "./utils.js"; import { normalizeOptions, normalizeFormatOptions, normalizePluginOptions } from "./utils.js"; -import type { Bin, PluginsOptions } from "./types.js"; +import type { Bin, PluginsOptions, PrettierPlugin } from "./types.js"; const makeBin = (): Bin => { return ( @@ -208,7 +208,7 @@ const makePluggableBin = async (): Promise => { for (let i = 0, l = pluginsNames.length; i < l; i++) { const pluginName = pluginsNames[i]; - const plugin = await getPlugin(pluginName); + const plugin = await getPluginOrExit(pluginName); for (const option in plugin.options) { optionsNames.push(option); diff --git a/src/prettier_serial.ts b/src/prettier_serial.ts index 685fa4d..2a1f05f 100644 --- a/src/prettier_serial.ts +++ b/src/prettier_serial.ts @@ -1,7 +1,7 @@ import { readFile, writeFile } from "atomically"; import process from "node:process"; import prettier from "prettier/standalone"; -import { getPlugins, getPluginsBuiltin, resolve } from "./utils.js"; +import { getPluginsOrExit, getPluginsBuiltin, resolve } from "./utils.js"; import type { ContextOptions, LazyFormatOptions, PluginsOptions } from "./types.js"; async function check( @@ -37,7 +37,7 @@ async function format( ): Promise { formatOptions = await resolve(formatOptions); const pluginsBuiltin = await getPluginsBuiltin(); - const plugins = await getPlugins(formatOptions.plugins || []); + const plugins = await getPluginsOrExit(formatOptions.plugins || []); const pluginsOverride = contextOptions.configPrecedence !== "file-override"; const options = { diff --git a/src/utils.ts b/src/utils.ts index 88d05b2..40dc72a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -129,6 +129,14 @@ const getPlugin = memoize((name: string): Promise => { return plugin; }); +async function getPluginOrExit(name: string): Promise { + try { + return await getPlugin(name); + } catch { + exit(`The plugin "${name}" could not be loaded`); + } +} + function getPluginPath(name: string): string { const rootPath = path.join(process.cwd(), "index.js"); const pluginPath = getModulePath(name, rootPath); @@ -146,9 +154,22 @@ function getPluginVersion(name: string): string | null { } } -function getPlugins(names: string[]): PromiseMaybe { +async function getPlugins(names: string[]): Promise { + if (!names.length) return []; + return ( + await Promise.all( + names.map((name) => getPlugin(name)) + ) + ); +} + +async function getPluginsOrExit(names: string[]): Promise { if (!names.length) return []; - return Promise.all(names.map(getPlugin)); + return ( + await Promise.all( + names.map((name) => getPluginOrExit(name)) + ) + ); } const getPluginsBuiltin = once(async (): Promise => { @@ -720,10 +741,12 @@ export { getModule, getModulePath, getPlugin, + getPluginOrExit, getPluginPath, getPluginVersion, getPlugins, getPluginsBuiltin, + getPluginsOrExit, getPluginsPaths, getPluginsVersions, getProjectPath, diff --git a/test/__fixtures__/plugin-options-string/config.json b/test/__fixtures__/plugin-options-string/config.json new file mode 100644 index 0000000..a1d5192 --- /dev/null +++ b/test/__fixtures__/plugin-options-string/config.json @@ -0,0 +1,4 @@ +{ + "plugins": ["./plugin.cjs"], + "fooString": "baz" +} diff --git a/test/__fixtures__/plugin-options-string/plugin.cjs b/test/__fixtures__/plugin-options-string/plugin.cjs new file mode 100644 index 0000000..85f5cc5 --- /dev/null +++ b/test/__fixtures__/plugin-options-string/plugin.cjs @@ -0,0 +1,31 @@ +"use strict"; + +module.exports = { + languages: [ + { + name: "foo", + parsers: ["foo-parser"], + extensions: [".foo"], + since: "1.0.0", + }, + ], + options: { + fooString: { + type: "string", + default: "bar", + description: "foo description", + }, + }, + parsers: { + "foo-parser": { + parse: (text) => ({ text }), + astFormat: "foo-ast", + }, + }, + printers: { + "foo-ast": { + print: (path, options) => + options.fooString ? `foo:${options.fooString}` : path.getValue().text, + }, + }, +}; diff --git a/test/__tests__/__snapshots__/plugin-options-string.js.snap b/test/__tests__/__snapshots__/plugin-options-string.js.snap new file mode 100644 index 0000000..5a47c59 --- /dev/null +++ b/test/__tests__/__snapshots__/plugin-options-string.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`show detailed external option with \`--help foo-string\` (stderr) 1`] = `""`; + +exports[`show detailed external option with \`--help foo-string\` (stdout) 1`] = ` +"--foo-string + + foo description + +Default: bar" +`; + +exports[`show detailed external option with \`--help foo-string\` (write) 1`] = `[]`; + +exports[`show external options with \`--help\` 1`] = ` +" --parser + --foo-string foo description + Defaults to "bar"" +`; diff --git a/test/__tests__/plugin-options-string.js b/test/__tests__/plugin-options-string.js new file mode 100644 index 0000000..b2dd2a3 --- /dev/null +++ b/test/__tests__/plugin-options-string.js @@ -0,0 +1,72 @@ +import { runCli } from "../utils"; + +test("show external options with `--help`", async () => { + const originalStdout = await runCli("plugin-options-string", ["--help"]) + .stdout; + const pluggedStdout = await runCli("plugin-options-string", [ + "--help", + "--plugin=./plugin.cjs", + ]).stdout; + const originalLines = originalStdout.split("\n"); + const pluggedLines = pluggedStdout.split("\n"); + const differentLines = pluggedLines.filter((line) => + !originalLines.includes(line)); + expect(differentLines.join("\n")).toMatchSnapshot(); +}); + +// Note (43081j); we don't currently support `--help {optionName}` +describe.skip("show detailed external option with `--help foo-string`", () => { + runCli("plugin-options-string", [ + "--plugin=./plugin.cjs", + "--help", + "foo-string", + ]).test({ + status: 0, + }); +}); + +describe("external options from CLI should work", () => { + runCli( + "plugin-options-string", + [ + "--plugin=./plugin.cjs", + "--stdin-filepath", + "example.foo", + "--foo-string", + "baz", + ], + { input: "hello-world" }, + ).test({ + stdout: "foo:baz", + stderr: "", + status: 0, + write: [], + }); +}); + +// TODO (43081j): this won't work until we fix #21 +describe.skip("external options from config file should work", () => { + runCli( + "plugin-options-string", + ["--config-path=./config.json", "--stdin-filepath", "example.foo"], + { input: "hello-world" }, + ).test({ + stdout: "foo:baz", + stderr: "", + status: 0, + write: [], + }); +}); + +describe("Non exists plugin", () => { + runCli( + "plugin-options-string", + ["--plugin=--foo--", "--stdin-filepath", "example.foo"], + { input: "hello-world" }, + ).test({ + stdout: "", + stderr: expect.stringMatching(/The plugin "--foo--" could not be loaded/u), + status: 1, + write: [], + }); +});