From 3ba48bb26620cfadfca91c2eefd50c0df3597e2e Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 18 Feb 2022 16:36:53 +0800 Subject: [PATCH] Add `--no-plugin-search` (#10274) Co-authored-by: Alexander Kachkaev --- changelog_unreleased/cli/10274.md | 12 ++++ docs/cli.md | 4 ++ docs/plugins.md | 2 +- src/cli/constant.js | 4 ++ src/cli/index.js | 15 +++++ src/cli/options/parse-cli-arguments.js | 34 +++++++--- src/common/load-plugins.js | 17 +++-- src/index.js | 1 + src/main/options-normalizer.js | 30 ++++++++- .../__snapshots__/early-exit.js.snap | 2 + .../__snapshots__/help-options.js.snap | 11 +++ .../__tests__/config-resolution.js | 8 +++ .../__tests__/plugin-resolution.js | 67 +++++++++++++++++++ .../cli/config/plugin-search-dirs/.prettierrc | 3 + 14 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 changelog_unreleased/cli/10274.md create mode 100644 tests/integration/cli/config/plugin-search-dirs/.prettierrc diff --git a/changelog_unreleased/cli/10274.md b/changelog_unreleased/cli/10274.md new file mode 100644 index 000000000..4c810005e --- /dev/null +++ b/changelog_unreleased/cli/10274.md @@ -0,0 +1,12 @@ +#### Add `--no-plugin-search` to turn off plugin autoloading (#10274 by @fisker) + +To turn off plugin autoloading, use `--no-plugin-search` when using Prettier CLI or add `{ pluginSearchDirs: false }` to options in `prettier.format()` or to the config file. + + +```cli +// Prettier stable +$ prettier . --plugin-search-dir=a-dir-without-plugins + +// Prettier main +$ prettier . --no-plugin-search +``` diff --git a/docs/cli.md b/docs/cli.md index fa5e42060..49535d52a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -200,3 +200,7 @@ $ prettier "**/*" --write --ignore-unknown ## `--no-error-on-unmatched-pattern` Prevent errors when pattern is unmatched. + +## `--no-plugin-search` + +Disable plugin autoloading. diff --git a/docs/plugins.md b/docs/plugins.md index a227e74e0..be52c8f49 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -46,7 +46,7 @@ The paths that are provided to `pluginSearchDirs` will be searched for `@prettie Strings provided to `plugins` are ultimately passed to `require()`, so you can provide a module/package name, a path, or anything else `require()` takes. (`pluginSearchDirs` works the same way. That is, valid plugin paths that it finds are passed to `require()`.) -Providing at least one path to `--plugin-search-dir`/`pluginSearchDirs` turns off plugin autoloading in the default directory (i.e. `node_modules` above `prettier` binary). +To turn off plugin autoloading, use `--no-plugin-search` when using Prettier CLI or add `{ pluginSearchDirs: false }` to options in `prettier.format()` or to the config file. ## Official Plugins diff --git a/src/cli/constant.js b/src/cli/constant.js index 584fa0833..88087abfb 100644 --- a/src/cli/constant.js +++ b/src/cli/constant.js @@ -202,6 +202,10 @@ const options = { default: "log", choices: ["silent", "error", "warn", "log", "debug"], }, + "plugin-search": { + type: "boolean", + oppositeDescription: "Disable plugin autoloading.", + }, "support-info": { type: "boolean", description: "Print support information as JSON.", diff --git a/src/cli/index.js b/src/cli/index.js index f43fb0b43..699f8044b 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -15,6 +15,9 @@ const { createDetailedUsage, createUsage } = require("./usage.js"); const { formatStdin, formatFiles } = require("./format.js"); const logFileInfoOrDie = require("./file-info.js"); const logResolvedConfigPathOrDie = require("./find-config-path.js"); +const { + utils: { isNonEmptyArray }, +} = require("./prettier-internal.js"); async function run(rawArguments) { // Create a default level logger, so we can log errors during `logLevel` parsing @@ -42,6 +45,18 @@ async function main(rawArguments, logger) { logger.debug(`normalized argv: ${JSON.stringify(context.argv)}`); + if (context.argv.pluginSearch === false) { + const rawPluginSearchDirs = context.argv.__raw["plugin-search-dir"]; + if ( + typeof rawPluginSearchDirs === "string" || + isNonEmptyArray(rawPluginSearchDirs) + ) { + throw new Error( + "Cannot use --no-plugin-search and --plugin-search-dir together." + ); + } + } + if (context.argv.check && context.argv.listDifferent) { throw new Error("Cannot use --check and --list-different together."); } diff --git a/src/cli/options/parse-cli-arguments.js b/src/cli/options/parse-cli-arguments.js index d1b341d08..b96de1cff 100644 --- a/src/cli/options/parse-cli-arguments.js +++ b/src/cli/options/parse-cli-arguments.js @@ -14,6 +14,10 @@ function parseArgv(rawArguments, detailedOptions, logger, keys) { let argv = minimist(rawArguments, minimistOptions); if (keys) { + if (keys.includes("plugin-search-dir") && !keys.includes("plugin-search")) { + keys.push("plugin-search"); + } + detailedOptions = detailedOptions.filter((option) => keys.includes(option.name) ); @@ -25,19 +29,27 @@ function parseArgv(rawArguments, detailedOptions, logger, keys) { colorsModule: chalk, }); - return Object.fromEntries( - Object.entries(normalized).map(([key, value]) => { - const option = detailedOptions.find(({ name }) => name === key) || {}; - // If the flag is a prettier option, use the option name - // `--plugin-search-dir` -> `pluginSearchDirs` - // Otherwise use camel case for readability - // `--ignore-unknown` -> `ignoreUnknown` - return [option.forwardToApi || camelCase(key), value]; - }) - ); + return { + ...Object.fromEntries( + Object.entries(normalized).map(([key, value]) => { + const option = detailedOptions.find(({ name }) => name === key) || {}; + // If the flag is a prettier option, use the option name + // `--plugin-search-dir` -> `pluginSearchDirs` + // Otherwise use camel case for readability + // `--ignore-unknown` -> `ignoreUnknown` + return [option.forwardToApi || camelCase(key), value]; + }) + ), + get __raw() { + return argv; + }, + }; } -const detailedOptionsWithoutPlugins = getContextOptions().detailedOptions; +const detailedOptionsWithoutPlugins = getContextOptions( + [], + false +).detailedOptions; function parseArgvWithoutPlugins(rawArguments, logger, keys) { return parseArgv( rawArguments, diff --git a/src/common/load-plugins.js b/src/common/load-plugins.js index 9985a6f38..374730ed4 100644 --- a/src/common/load-plugins.js +++ b/src/common/load-plugins.js @@ -22,14 +22,17 @@ function load(plugins, pluginSearchDirs) { plugins = []; } - if (!pluginSearchDirs) { + if (pluginSearchDirs === false) { pluginSearchDirs = []; - } - // unless pluginSearchDirs are provided, auto-load plugins from node_modules that are parent to Prettier - if (pluginSearchDirs.length === 0) { - const autoLoadDir = thirdParty.findParentDir(__dirname, "node_modules"); - if (autoLoadDir) { - pluginSearchDirs = [autoLoadDir]; + } else { + pluginSearchDirs = pluginSearchDirs || []; + + // unless pluginSearchDirs are provided, auto-load plugins from node_modules that are parent to Prettier + if (pluginSearchDirs.length === 0) { + const autoLoadDir = thirdParty.findParentDir(__dirname, "node_modules"); + if (autoLoadDir) { + pluginSearchDirs = [autoLoadDir]; + } } } diff --git a/src/index.js b/src/index.js index 0312d18e7..ed6e6884b 100644 --- a/src/index.js +++ b/src/index.js @@ -75,6 +75,7 @@ module.exports = { utils: { arrayify: require("./utils/arrayify.js"), getLast: require("./utils/get-last.js"), + isNonEmptyArray: require("./common/util.js").isNonEmptyArray, }, }, diff --git a/src/main/options-normalizer.js b/src/main/options-normalizer.js index 9dc55eeab..17742108f 100644 --- a/src/main/options-normalizer.js +++ b/src/main/options-normalizer.js @@ -94,6 +94,10 @@ function normalizeOptions( hasDeprecationWarned = normalizer._hasDeprecationWarned; } + if (isCLI && normalized["plugin-search"] === false) { + normalized["plugin-search-dir"] = false; + } + return normalized; } @@ -123,8 +127,32 @@ function optionInfosToSchemas(optionInfos, { isCLI, colorsModule }) { } function optionInfoToSchema(optionInfo, { isCLI, optionInfos, colorsModule }) { + const { name } = optionInfo; + + if (name === "plugin-search-dir" || name === "pluginSearchDirs") { + return vnopts.AnySchema.create({ + name, + preprocess(value) { + if (value === false) { + return value; + } + value = Array.isArray(value) ? value : [value]; + return value; + }, + validate(value) { + if (value === false) { + return true; + } + return value.every((dir) => typeof dir === "string"); + }, + expected() { + return "false or paths to plugin search dir"; + }, + }); + } + + const parameters = { name }; let SchemaConstructor; - const parameters = { name: optionInfo.name }; const handlers = {}; switch (optionInfo.type) { diff --git a/tests/integration/__tests__/__snapshots__/early-exit.js.snap b/tests/integration/__tests__/__snapshots__/early-exit.js.snap index 66d605828..1f88c4859 100644 --- a/tests/integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests/integration/__tests__/__snapshots__/early-exit.js.snap @@ -153,6 +153,7 @@ Other options: --loglevel What level of logs to report. Defaults to log. + --no-plugin-search Disable plugin autoloading. --require-pragma Require either '@prettier' or '@format' to be present in the file's first docblock comment in order for it to be formatted. Defaults to false. @@ -322,6 +323,7 @@ Other options: --loglevel What level of logs to report. Defaults to log. + --no-plugin-search Disable plugin autoloading. --require-pragma Require either '@prettier' or '@format' to be present in the file's first docblock comment in order for it to be formatted. Defaults to false. diff --git a/tests/integration/__tests__/__snapshots__/help-options.js.snap b/tests/integration/__tests__/__snapshots__/help-options.js.snap index bf750b96b..e4821dc24 100644 --- a/tests/integration/__tests__/__snapshots__/help-options.js.snap +++ b/tests/integration/__tests__/__snapshots__/help-options.js.snap @@ -358,6 +358,17 @@ exports[`show detailed usage with --help no-error-on-unmatched-pattern (stdout) exports[`show detailed usage with --help no-error-on-unmatched-pattern (write) 1`] = `Array []`; +exports[`show detailed usage with --help no-plugin-search (stderr) 1`] = `""`; + +exports[`show detailed usage with --help no-plugin-search (stdout) 1`] = ` +"--no-plugin-search + + Disable plugin autoloading. +" +`; + +exports[`show detailed usage with --help no-plugin-search (write) 1`] = `Array []`; + exports[`show detailed usage with --help no-semi (stderr) 1`] = `""`; exports[`show detailed usage with --help no-semi (stdout) 1`] = ` diff --git a/tests/integration/__tests__/config-resolution.js b/tests/integration/__tests__/config-resolution.js index 84149e970..71d0d9877 100644 --- a/tests/integration/__tests__/config-resolution.js +++ b/tests/integration/__tests__/config-resolution.js @@ -304,6 +304,14 @@ test("API resolveConfig resolves relative path values based on config filepath", plugins: [path.join(parentDir, "path-to-plugin")], pluginSearchDirs: [path.join(parentDir, "path-to-plugin-search-dir")], }); + + expect( + prettier.resolveConfig.sync( + path.join(__dirname, "../cli/config/plugin-search-dirs/index.js") + ) + ).toMatchObject({ + pluginSearchDirs: false, + }); }); test("API resolveConfig de-references to an external module", () => { diff --git a/tests/integration/__tests__/plugin-resolution.js b/tests/integration/__tests__/plugin-resolution.js index 55118184b..315850037 100644 --- a/tests/integration/__tests__/plugin-resolution.js +++ b/tests/integration/__tests__/plugin-resolution.js @@ -205,3 +205,70 @@ describe("loads --plugin by bespoke plugin name (assuming it is installed in cwd write: [], }); }); + +test("--no-plugin-search", async () => { + async function getParser(args = []) { + const { stdout } = await runPrettier("plugins/automatic", [ + "--file-info=file.foo", + ...args, + ]); + return JSON.parse(stdout).inferredParser; + } + + expect(await getParser()).toBe("foo"); + expect(await getParser(["--plugin-search-dir=."])).toBe("foo"); + expect(await getParser(["--no-plugin-search"])).toBeNull(); +}); + +test("--no-plugin-search still allow use --plugin", async () => { + const args = ["file.txt", "--parser=bar"]; + const { stdout: stdoutWithoutPlugin } = await runPrettier( + "plugins/automatic", + args + ); + const argsWithPlugin = [...args, "--plugin=./prettier-plugin-bar.js"]; + const { stdout: stdoutWithPlugin } = await runPrettier( + "plugins/automatic", + argsWithPlugin + ); + const { stdout: stdoutWithoutPluginAndNoPluginSearch } = await runPrettier( + "plugins/automatic", + [...args, "--no-plugin-search"] + ); + const { stdout: stdoutWithPluginButNoPluginSearch } = await runPrettier( + "plugins/automatic", + [...argsWithPlugin, "--no-plugin-search"] + ); + + expect(stdoutWithoutPlugin).not.toBe(stdoutWithPlugin); + expect(stdoutWithoutPluginAndNoPluginSearch).toBe(""); + expect(stdoutWithPlugin).toBe(stdoutWithPluginButNoPluginSearch); +}); + +test("--no-plugin-search together with --plugin-search-dir", async () => { + const result1 = await runPrettier("plugins/automatic", [ + "--no-plugin-search", + "--plugin-search-dir", + ]); + const result2 = await runPrettier("plugins/automatic", [ + "--no-plugin-search", + "--plugin-search-dir=foo", + ]); + const result3 = await runPrettier("plugins/automatic", [ + "--no-plugin-search", + "--plugin-search-dir=foo", + "--plugin-search-dir=bar", + ]); + + expect(result1).toMatchInlineSnapshot(` + Object { + "status": 1, + "stderr": "[error] Cannot use --no-plugin-search and --plugin-search-dir together. + ", + "stdout": "", + "write": Array [], + } + `); + expect(result2).toEqual(result1); + expect(result3).toEqual(result1); +}); diff --git a/tests/integration/cli/config/plugin-search-dirs/.prettierrc b/tests/integration/cli/config/plugin-search-dirs/.prettierrc new file mode 100644 index 000000000..d5f6d95b0 --- /dev/null +++ b/tests/integration/cli/config/plugin-search-dirs/.prettierrc @@ -0,0 +1,3 @@ +{ + "pluginSearchDirs": false +}