Repository: prettier/prettier (JavaScript) — Feature

Issue:
Allow plugin to add option with string type
<!--

BEFORE SUBMITTING AN ISSUE:

1.  Search for your issue on GitHub: https://github.com/prettier/prettier/issues
    A large number of opened issues are duplicates of existing issues.
    If someone has already opened an issue for what you are experiencing,
    you do not need to open a new issue — please add a 👍 reaction to the
    existing issue instead.

2.  If your issue is with a prettier editor extension or add-on, please open the
    issue in the repo for that extension or add-on, instead of this repo.

-->

**Environments:**
- Prettier Version: 1.17.1
- Running Prettier via: <!-- CLI, Node.js API, Browser API, etc. --> CLI
- Runtime: <!-- Node.js v6, Chrome v67, etc. --> Node.js v10
- Operating System: <!-- Windows, Linux, macOS, etc. --> Linux

**Steps to reproduce:**
<!-- shell script, js code, or a link to the minimal reproducible repository -->
The `options` parameter that a plug in provides only accept the following types: `int`, `choice`, `boolean`, `flag`, `path`. I'd like the user to be able to provide a generic `string` type, which is not possible at the moment, see this check [here](https://github.com/prettier/prettier/blob/a9fd8e2cf42cd696aa1fb489ea7a374beb45b338/src/main/options-normalizer.js#L152).

Some background information: I'm developing a Prettier plugin for the [Apex language](https://github.com/dangmai/prettier-plugin-apex), and I'd to use a standalone server that does the parsing. Right now, I have to default making calls to `localhost` for the server, but I'd like to give the user the ability to specify this in case they have that server listening on a different interface or on a different network altogether.

**Expected behavior:**

Prettier allows me to specify a `string` type for option.

**Actual behavior:**

Prettier exits with error: `Unexpected type string`.


Discussion:

Resolving patch:
diff --git a/src/main/options-normalizer.js b/src/main/options-normalizer.js
index 69fd3384dc68..a46a579df887 100644
--- a/src/main/options-normalizer.js
+++ b/src/main/options-normalizer.js
@@ -118,6 +118,9 @@ function optionInfoToSchema(optionInfo, { isCLI, optionInfos }) {
         parameters.preprocess = value => Number(value);
       }
       break;
+    case "string":
+      SchemaConstructor = vnopts.StringSchema;
+      break;
     case "choice":
       SchemaConstructor = vnopts.ChoiceSchema;
       parameters.choices = optionInfo.choices.map(choiceInfo =>


Test patch:
diff --git a/tests_integration/__tests__/__snapshots__/plugin-options-string.js.snap b/tests_integration/__tests__/__snapshots__/plugin-options-string.js.snap
new file mode 100644
index 000000000000..77d3ae01f2f8
--- /dev/null
+++ b/tests_integration/__tests__/__snapshots__/plugin-options-string.js.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` 1`] = `
+"Snapshot Diff:
+- First value
++ Second value
+
+@@ -17,10 +17,12 @@
+                             Defaults to avoid.
+    --no-bracket-spacing     Do not print spaces between brackets.
+    --end-of-line <auto|lf|crlf|cr>
+                             Which end of line characters to apply.
+                             Defaults to auto.
++   --foo-string <string>    foo description
++                            Defaults to bar.
+    --html-whitespace-sensitivity <css|strict|ignore>
+                             How to handle whitespaces in HTML.
+                             Defaults to css.
+    --jsx-bracket-same-line  Put > on the last line instead of at a new line.
+                             Defaults to false."
+`;
+
+exports[`show detailed external option with \`--help foo-string\` (stderr) 1`] = `""`;
+
+exports[`show detailed external option with \`--help foo-string\` (stdout) 1`] = `
+"--foo-string <string>
+
+  foo description
+
+Default: bar
+"
+`;
+
+exports[`show detailed external option with \`--help foo-string\` (write) 1`] = `Array []`;
diff --git a/tests_integration/__tests__/plugin-options-string.js b/tests_integration/__tests__/plugin-options-string.js
new file mode 100644
index 000000000000..3c12c1e813d1
--- /dev/null
+++ b/tests_integration/__tests__/plugin-options-string.js
@@ -0,0 +1,56 @@
+"use strict";
+
+const runPrettier = require("../runPrettier");
+const snapshotDiff = require("snapshot-diff");
+
+describe("show external options with `--help`", () => {
+  const originalStdout = runPrettier("plugins/options-string", ["--help"])
+    .stdout;
+  const pluggedStdout = runPrettier("plugins/options-string", [
+    "--help",
+    "--plugin=./plugin"
+  ]).stdout;
+  expect(snapshotDiff(originalStdout, pluggedStdout)).toMatchSnapshot();
+});
+
+describe("show detailed external option with `--help foo-string`", () => {
+  runPrettier("plugins/options-string", [
+    "--plugin=./plugin",
+    "--help",
+    "foo-string"
+  ]).test({
+    status: 0
+  });
+});
+
+describe("external options from CLI should work", () => {
+  runPrettier(
+    "plugins/options-string",
+    [
+      "--plugin=./plugin",
+      "--stdin-filepath",
+      "example.foo",
+      "--foo-string",
+      "baz"
+    ],
+    { input: "hello-world" }
+  ).test({
+    stdout: "foo:baz",
+    stderr: "",
+    status: 0,
+    write: []
+  });
+});
+
+describe("external options from config file should work", () => {
+  runPrettier(
+    "plugins/options-string",
+    ["--config=./config.json", "--stdin-filepath", "example.foo"],
+    { input: "hello-world" }
+  ).test({
+    stdout: "foo:baz",
+    stderr: "",
+    status: 0,
+    write: []
+  });
+});
diff --git a/tests_integration/plugins/options-string/config.json b/tests_integration/plugins/options-string/config.json
new file mode 100644
index 000000000000..b6cca6ceb11a
--- /dev/null
+++ b/tests_integration/plugins/options-string/config.json
@@ -0,0 +1,4 @@
+{
+    "plugins": ["./plugin"],
+    "fooString": "baz"
+}
\ No newline at end of file
diff --git a/tests_integration/plugins/options-string/plugin.js b/tests_integration/plugins/options-string/plugin.js
new file mode 100644
index 000000000000..110902001d91
--- /dev/null
+++ b/tests_integration/plugins/options-string/plugin.js
@@ -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
+    }
+  }
+};