Repository: prettier/prettier (JavaScript)
Date: 2021-02-15 12:47:17+00:00
Task type: Feature

Issue:
Allow JSON with trailing commas
I'm looking for a way to support the following style:
- JSON-based
- Always quote keys
- Always include trailing commas

This would typically be not a problem -- I should be able to set `parser: json` and `trailingComma: all`. However, #2308\* disables this specific combination and I can't get around it.

Here's my use case: I want Prettier to format my VSCode settings files, which I keep in a git repo.
- VSCode requires keys to be quoted, so I can't use the json5 parser.
- I would like trailing commas to make diffs more readable.

Note: I originally filed this bug in another repo https://github.com/prettier/prettier-vscode/issues/589

\* I am aware that #2308 was created because trailing commas _broke_ VSCode. However, times have changed and VSCode settings files now support trailing commas.

Thanks!

**Prettier 1.15.3**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEwA6UAEmD0OPZogCGRSmAjADQGZEBGZmATDVAL4hUgQAOMAS2gBnZKGIAnCRADuABUkJRKYgDcIAgCZcQ9CcTABrODADKvAwKgBzZDAkBXONwAWMALYAbAOouB8YQswOFMlfwFVfwBPZHBhUW4rYTgJGDl9a3diZAAzYk9k7gArYQAPACF9IxNTYnc4ABkrOFz8wpAS0tMra084AEUHCHhWgucQCwlkiViS6B1eCSsYby0YF2QADgAGbkWIZO99XljFuGnVFu4JOABHBwEb9OJM7KQ8se5k9wE7R3HhD0+oNhi13m1xjBiPRVpp1shWCB7MQBJ4egBhCDuLKxfKeHQOZIAFWhyg+yXY7CAA)
```sh
--parser json
--trailing-comma all
```

**Input:**
```jsonc
{
  //
  "a": 1,
  "b": 2,
}
```

**Output:**
```jsonc
{
  //
  "a": 1,
  "b": 2
}

```

**Expected behavior:**
I'd like a set of input options that doesn't make changes to the input; i.e., I want the output to also be
```jsonc
{
  //
  "a": 1,
  "b": 2,
}

```




Issue discussion:
`--parser json-vscode` :shudder:
wow, they [made their own JSON parser](https://github.com/Microsoft/vscode/blob/8700c2e4290968f5dfa3cc2c7f78ec0c54500394/src/vs/base/common/json.ts) in the first commit, and still use it.
@lydell the problem isn't the prettier `json` parser, it parses trailing commas just fine. The problem is that there is an explicit override to disable `trailingComma` when the parser is set to `json`. Can we somehow make that exception a little more nuanced, or give a way to override that override?

Also, this particular syntax isn't VSCode-specific, it's a pretty common JSON superset that's used in config files. Sublime Text's config files accept comments and trailing commas as well.
Have you considered suggesting to vscode (and whoever else) that they adopt JSON5 for their config, rather than this other intermediate thing? I don't think it would be a breaking change.
See also #4639 (linked from the prettier-vscode issue).
Personally, I think `jsonc` (the language named _JSON with Comments_ in VSCode, which is also used by TypeScript `tsconfig.json`) is better than JSON5 since it only adds missing features that are necessary so it's relatively simple.

I was thinking of adding a `--parser jsonc` to address this issue, but then I found that its name (_JSON with Comments_) is very confusing since it looks like `JSON + comments` but it's actually `JSON + comments + trailing commas` while our `--parser json` already handles `JSON + comments`, so the `jsonc` description may look something like _"JSON with Comments: same parser as `json`, but trailing commas are allowed"_, which is super confusing.
Since `jsonc` is a subset of JSON5, perhaps the `json5` parser could consume JSON5 and emit `jsonc`? I think that's pretty much just a matter of accepting #4639, though there might be other differences in output which I'm missing.
> Personally, I think `jsonc` (the language named _JSON with Comments_ in VSCode, which is also used by TypeScript `tsconfig.json`) is better than JSON5 since it only adds missing features that are necessary so it's relatively simple.


@ikatyang yeah I think json5 is a little too over-the-top, at that point they might as well be using yaml…


> Since jsonc is a subset of JSON5, perhaps the json5 parser could consume JSON5 and emit jsonc?

We did not have `--printer <something>` at this point so we can only use `--parser jsonc` to hint printer to output `jsonc`, and all of our JSON-like languages use the same parser (`parseExpression` from `@babel/parser`) so it's actually something like `--parser js-expression --printer some-json-variant`. 

> I think that's pretty much just a matter of accepting #4639

They are the same in theory, but accepting #4639 does not look like the same to me since we don't want to add more options (same as respecting original text).
I am suspecting that the most seamless solution might not be to change the `parser` property, but to change the `trailingComma` property, since it already does something similar. Maybe have `trailingComma: es5` not include it in JSON but have `trailingComma: all` include it?

Some other suggestions:
- Add a `compatibility` property: `compatibility: ["strict-json", "es5"]`
- Add a way to override by parser, not just by syntax. Then tell everyone who uses strict json to add `overrides: {parsers: "json", options: {"trailingCommas": false}}`
#4639 wouldn't require a new option, would it? It could just always output anything parsed as `--parser=json5` as valid `jsonc`, where possible.
True, but would it be possible for future changes to json5 output to be non-jsonc-compatible?
It's hard to predict what changes could "break" the output but I think it's safe to assume that if the source is jsonc-compatible, the output would be too.

For example, let's imagine the multiline feature didn't exist in JSON5 and the feature was just added

The following would continue to be JSONC-compatible:

```json
{
  "foo": "bar\nbaz",
}
```

But if you change your source code to use this new feature, then it woud no longer be compatible:

```json5
{
  "foo": "bar\
baz",
}
```

the `jsonc` parser of vscode  https://www.npmjs.com/package/jsonc-parser

Would also like this. It's very annoying when commenting out the last line of an array or object that prettier will remove the comma of the line before. So then when you comment the line back in, it's a syntax error because the comma is missing.
@felixfbecker That's indeed a very unfortunate side effect.
.prettierrc
```
{
...
  "overrides": [
    {
      "files": ["*.json", ".*rc"],
      "options": {
        "parser": "json5",
        "quoteProps": "preserve",
        "singleQuote": false,
        "trailingComma": "all",
      },
    },
  ],
}

```
:)
@difr thanks 👍 

@NewFuture commapeople should be tight together :),
This override works for me mostly:
```js
overrides: {
      files: '{**/.vscode/*.json,**/tsconfig.json,**/tsconfig.*.json}',
      options: {
        parser: 'json5',
        quoteProps: 'preserve',
        singleQuote: false,
        trailingComma: 'all',
      },
    },
```
Except that it fails if a string contains an escaped double quote `\"` because then Prettier will use single quotes for the string:

> If the number of quotes outweighs the other quote, the quote which is less used will be used to format the string - Example: "I'm double quoted" results in "I'm double quoted" and "This \"example\" is single quoted" results in 'This "example" is single quoted'.

Also, if for some reason you remove quotes around a key, Prettier won't add them back because there is no `quoteProps: "always"`.

It would be great if there was a `singleProps: "always"` and `"quoteProps": "always"` option.
@felixfbecker Those aren't unreasonable requests. If you're interested, would you open separate issues for them? Thanks
The json5 override doesn't work when you have strings that contain double quote characters -- to avoid escaping them, Prettier will use single quotes. See https://github.com/prettier/prettier/issues/973

Workaround: Use `\u0022` instead of `\"`
Hi, I'm the maintainer for JSON5. 

> True, but would it be possible for future changes to json5 output to be non-jsonc-compatible?

I don't understand your question since JSON5 isn't compatible with JSON with Comments. The converse is true though. JSON with Comments is a subset of JSON5. Maybe mean future changes to the output of Prettier for JSON5 documents?

> It's hard to predict what changes could "break" the output but I think it's safe to assume that if the source is jsonc-compatible, the output would be too.

There will be no future changes to JSON5 (as long as I'm still maintaining the project). The spec is frozen as there aren't really too many more ES5 features to implement. (Native Date and RegExp support is not going to happen, and neither is numeric keys.)

> yeah I think json5 is a little too over-the-top, at that point they might as well be using yaml…

Hey, I resent that. 😄 I do get where you're coming from though. There is quite a bit of overlap between JSON5 and YAML, and YAML has better community support. I think JSON5 is even a subset of YAML, but that was never a target. However, it's harder to write a YAML parser because it allows for so many different ways to say the same thing, which is the antithesis of JSON.
Hi @jordanbtucker thanks for chiming in. I'm going to preface the below by saying that although JSON5 has been mentioned a lot in this issue, I think this issue is not actually related to JSON5, and the solution might not need to involve JSON5. JSON5 is simply one of the two closest Prettier syntaxes to JSON with Comments that we have (with the other being JSON). So please know that the discussion about JSON5 here is generally not about how good JSON5 is itself but rather about its effectiveness as a solution for this issue.

> > True, but would it be possible for future changes to json5 output to be non-jsonc-compatible? 
> 
> I don't understand your question since JSON5 isn't compatible with JSON with Comments.

I meant that even if we "supported" JSON with Comments by using JSON5 and adding options to tell Prettier to not use each JSON5 feature that isn't compatible with JSON with Comments (e.g., by adding the "key keys quoted property" [as noted in the comment above mine](https://github.com/prettier/prettier/issues/5708#issuecomment-452542477)), it's possible that a future version of JSON5 adds even more leniency and we'll be in this state again until we add an option to turn off that new feature. But, as you've said below, new features are probably not going to be added, so this isn't a big concern.

> There will be no future changes to JSON5 (as long as I'm still maintaining the project). The spec is frozen as there aren't really too many more ES5 features to implement. (Native Date and RegExp support is not going to happen, and neither is numeric keys.)

This is good to hear, although I still think that it feels like a hack to support one lenient JSON syntax with another. Of course this is through no fault of JSON5.

> > yeah I think json5 is a little too over-the-top, at that point they might as well be using yaml…
> 
> Hey, I resent that. :smile: I do get where you're coming from though. There is quite a bit of overlap between JSON5 and YAML, and YAML has better community support. I think JSON5 is even a subset of YAML, but that was never a target. However, it's harder to write a YAML parser because it allows for so many different ways to say the same thing, which is the antithesis of JSON.

Sorry for some of the assumptions I made and thanks for clarifying -- now I understand why JSON5 was created even when YAML already exists.

Solution patch:
diff --git a/changelog_unreleased/json/10323.md b/changelog_unreleased/json/10323.md
new file mode 100644
index 000000000000..eab0505f60b0
--- /dev/null
+++ b/changelog_unreleased/json/10323.md
@@ -0,0 +1,21 @@
+#### Don't use smart quotes for JSON5 with `--quote-props=preserve` (#10323 by @thorn0)
+
+With the `quoteProps` option set to `preserve` and `singleQuotes` to `false` (default), double quotes are always used for printing strings, including situations like `"bla\"bla"`. This effectively allows using `--parser json5` for "JSON with comments and trailing commas".
+
+<!-- prettier-ignore -->
+```js
+// Input
+{
+  "char": "\"",
+}
+
+// Prettier stable
+{
+  "char": '"',
+}
+
+// Prettier main
+{
+  "char": "\"",
+}
+```
diff --git a/docs/options.md b/docs/options.md
index fbdd81db8424..caf6f5024f09 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -96,6 +96,8 @@ Note that Prettier never unquotes numeric property names in Angular expressions,
 [quote-props-flow]: https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVBjOA7AzgFzAA8wBeMAb1TDAAYAuMARlQF8g
 [quote-props-vue]: https://github.com/prettier/prettier/issues/10127
 
+If this option is set to `preserve`, `singleQuote` to `false` (default value), and `parser` to `json5`, double quotes are always used for strings. This effectively allows using the `json5` parser for “JSON with comments and trailing commas”.
+
 ## JSX Quotes
 
 Use single quotes instead of double quotes in JSX.
diff --git a/src/common/util.js b/src/common/util.js
index 33dfa77c7478..1dfeb2faae2d 100644
--- a/src/common/util.js
+++ b/src/common/util.js
@@ -393,7 +393,10 @@ function printString(raw, options, isDirectiveLiteral) {
 
   /** @type {Quote} */
   const enclosingQuote =
-    options.parser === "json"
+    options.parser === "json" ||
+    (options.parser === "json5" &&
+      options.quoteProps === "preserve" &&
+      !options.singleQuote)
       ? '"'
       : options.__isInHtmlAttribute
       ? "'"


Test patch:
diff --git a/tests/json/json5-as-json-with-trailing-commas/__snapshots__/jsfmt.spec.js.snap b/tests/json/json5-as-json-with-trailing-commas/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..fa8d48528a7b
--- /dev/null
+++ b/tests/json/json5-as-json-with-trailing-commas/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`nested-quotes.json - {"quoteProps":"preserve"} format 1`] = `
+====================================options=====================================
+parsers: ["json5"]
+printWidth: 80
+quoteProps: "preserve"
+                                                                                | printWidth
+=====================================input======================================
+{"allOn": "Single", "Line": "example",
+"noSpace":true,
+  "quote": {
+    'singleQuote': 'exa"mple',
+                  "indented": true,
+  },
+  "phoneNumbers": [
+    {"type'": "home",
+      "number\\"": "212 555-1234"},
+    {"type": "office",
+      "trailing": "commas by accident"},
+  ],
+}
+
+=====================================output=====================================
+{
+  "allOn": "Single",
+  "Line": "example",
+  "noSpace": true,
+  "quote": {
+    "singleQuote": "exa\\"mple",
+    "indented": true,
+  },
+  "phoneNumbers": [
+    { "type'": "home", "number\\"": "212 555-1234" },
+    { "type": "office", "trailing": "commas by accident" },
+  ],
+}
+
+================================================================================
+`;
diff --git a/tests/json/json5-as-json-with-trailing-commas/jsfmt.spec.js b/tests/json/json5-as-json-with-trailing-commas/jsfmt.spec.js
new file mode 100644
index 000000000000..1b15bcf4373c
--- /dev/null
+++ b/tests/json/json5-as-json-with-trailing-commas/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["json5"], { quoteProps: "preserve" });
diff --git a/tests/json/json5-as-json-with-trailing-commas/nested-quotes.json b/tests/json/json5-as-json-with-trailing-commas/nested-quotes.json
new file mode 100644
index 000000000000..9ecb537d1edf
--- /dev/null
+++ b/tests/json/json5-as-json-with-trailing-commas/nested-quotes.json
@@ -0,0 +1,13 @@
+{"allOn": "Single", "Line": "example",
+"noSpace":true,
+  "quote": {
+    'singleQuote': 'exa"mple',
+                  "indented": true,
+  },
+  "phoneNumbers": [
+    {"type'": "home",
+      "number\"": "212 555-1234"},
+    {"type": "office",
+      "trailing": "commas by accident"},
+  ],
+}