Add JSON support (#2173)

* Add JSON support

This fixes https://github.com/prettier/prettier/issues/1973 by using
[json-to-ast] to parse JSON, then converting the AST into a
mostly-Babylon AST, so as to piggyback on the existing printer logic.

Identifiers and literals are currently printed verbatim from the input,
but this could be improved upon later (at least for literals, not sure
about identifiers).

[json-to-ast]: https://github.com/vtrushin/json-to-ast

* Rename `json-to-ast` parser to `json`

See https://github.com/prettier/prettier/pull/2173#discussion_r122574644

* json: Use createError to have a pretty error

See https://github.com/prettier/prettier/pull/2173#discussion_r122574748

* json: Transform AST without mutations

See https://github.com/prettier/prettier/pull/2173#discussion_r122574690
This commit is contained in:
Joseph Frazier authored and Christopher Chedeau committed 2017-06-17 10:36:42 -10:00
1 parent 59a294fcc9
commit a42db30946
13 files changed
+281 -2

No files matched your search

+1 -1
View File
@@ -283,7 +283,7 @@ Prettier ships with a handful of customizable format options, usable in both the
| **Cursor Offset** - Specify where the cursor is. This option only works with `prettier.formatWithCursor`, and cannot be used with `rangeStart` and `rangeEnd`. | `-1` | `--cursor-offset <int>` | `cursorOffset: <int>` |
| **Range Start** - Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. This option cannot be used with `cursorOffset`. | `0` | `--range-start <int>` | `rangeStart: <int>` |
| **Range End** - Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. This option cannot be used with `cursorOffset`. | `Infinity` | `--range-end <int>` | `rangeEnd: <int>` |
| **Parser** - Specify which parser to use. Both the `babylon` and `flow` parsers support the same set of JavaScript features (including Flow). Prettier automatically infers the parser from the input file path, so you shouldn't have to change this setting. [Custom parsers](#custom-parser-api) are supported. | `babylon` | <code>--parser <flow&#124;babylon&#124;typescript&#124;postcss></code><br /><code>--parser ./path/to/my-parser</code> | <code>parser: "<flow&#124;babylon&#124;typescript&#124;postcss>"</code><br /><code>parser: require("./my-parser")</code> |
| **Parser** - Specify which parser to use. Both the `babylon` and `flow` parsers support the same set of JavaScript features (including Flow). Prettier automatically infers the parser from the input file path, so you shouldn't have to change this setting. [Custom parsers](#custom-parser-api) are supported. | `babylon` | <code>--parser <flow&#124;babylon&#124;typescript&#124;postcss&#124;json></code><br /><code>--parser ./path/to/my-parser</code> | <code>parser: "<flow&#124;babylon&#124;typescript&#124;postcss&#124;json>"</code><br /><code>parser: require("./my-parser")</code> |
| **Filepath** - Specify the input filepath this will be used to do parser inference.<br /><br /> Example: <br />`cat foo \| prettier --stdin-filepath foo.css`<br /> will default to use `postcss` parser | | `--stdin-filepath` | `filepath: <string>` |
+1 -1
View File
@@ -231,7 +231,7 @@ if (argv["help"] || (!filepatterns.length && !stdin)) {
" --jsx-bracket-same-line Put > on the last line instead of at a new line.\n" +
" --trailing-comma <none|es5|all>\n" +
" Print trailing commas wherever possible. Defaults to none.\n" +
" --parser <flow|babylon|typescript|postcss>\n" +
" --parser <flow|babylon|typescript|postcss|json>\n" +
" Specify which parse to use. Defaults to babylon.\n" +
" --cursor-offset <int> Print (to stderr) where a cursor at the given position would move to after formatting.\n" +
" This option cannot be used with --range-start and --range-end\n" +
+1
View File
@@ -20,6 +20,7 @@
"glob": "7.1.2",
"graphql": "0.10.1",
"jest-validate": "20.0.3",
"json-to-ast": "2.0.0-alpha1.2",
"minimist": "1.2.0",
"parse5": "3.0.2",
"postcss": "^6.0.1",
+2
View File
@@ -35,6 +35,8 @@ function normalize(options) {
normalized.parser = "parse5";
} else if (/\.(ts|tsx)$/.test(filepath)) {
normalized.parser = "typescript";
} else if (/\.json$/.test(filepath)) {
normalized.parser = "json";
}
if (typeof normalized.trailingComma === "boolean") {
+68
View File
@@ -0,0 +1,68 @@
"use strict";
const createError = require("./parser-create-error");
function parse(text) {
const jsonToAst = require("json-to-ast");
try {
const ast = jsonToAst(text);
return toBabylon(ast);
} catch (err) {
const firstNewlineIndex = err.message.indexOf("\n");
const firstLine = err.message.slice(0, firstNewlineIndex);
const lastSpaceIndex = firstLine.lastIndexOf(" ");
const message = firstLine.slice(0, lastSpaceIndex);
const locString = firstLine.slice(lastSpaceIndex + 1);
const lineCol = locString.split(":").map(Number);
throw createError("(json-to-ast) " + message, {
start: {
line: lineCol[0],
column: lineCol[1]
}
});
}
}
function toBabylon(node) {
const typeMap = {
object: "ObjectExpression",
property: "ObjectProperty",
identifier: "json-identifier",
array: "ArrayExpression",
literal: "json-literal"
};
const result = {
type: typeMap[node.type],
start: node.loc.start.offset,
end: node.loc.end.offset,
loc: node.loc
};
switch (node.type) {
case "object":
return Object.assign(result, {
properties: node.children.map(toBabylon)
});
case "property":
return Object.assign(result, {
key: toBabylon(node.key),
value: toBabylon(node.value)
});
case "identifier":
return Object.assign(result, {
value: node.value
});
case "array":
return Object.assign(result, {
elements: node.children.map(toBabylon)
});
case "literal":
return Object.assign(result, {
rawValue: node.rawValue
});
}
}
module.exports = parse;
+3
View File
@@ -20,6 +20,9 @@ const parsers = {
},
get postcss() {
return eval("require")("./parser-postcss");
},
get json() {
return eval("require")("./parser-json");
}
};
+4
View File
@@ -2634,6 +2634,10 @@ function genericPrintNoParens(path, options, print, args) {
return path.call(bodyPath => {
return printStatementSequence(bodyPath, options, print);
}, "body");
case "json-identifier":
return '"' + n.value + '"';
case "json-literal":
return n.rawValue;
default:
throw new Error("unknown type: " + JSON.stringify(n.type));
+135
View File
@@ -0,0 +1,135 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`multi-line.json 1`] = `
{"key1":[true,false,null],"key2":{"key3":[1,2,"3",
1e10,1e-3]}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
"key1": [true, false, null],
"key2": {
"key3": [1, 2, "3", 1e10, 1e-3]
}
}
`;
exports[`pass1.json 1`] = `
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\\"",
"backslash": "\\\\",
"controls": "\\b\\f\\n\\r\\t",
"slash": "/ & \\/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "\`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}",
"quotes": "&#34; \\u0022 %22 0x22 034 &#x22;",
"\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[
"JSON Test Pattern pass1",
{ "object with 1 member": ["array with 1 element"] },
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\\"",
"backslash": "\\\\",
"controls": "\\b\\f\\n\\r\\t",
"slash": "/ & \\/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "\`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A",
"true": true,
"false": false,
"null": null,
"array": [],
"object": {},
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d ": [1, 2, 3, 4, 5, 6, 7],
"compact": [1, 2, 3, 4, 5, 6, 7],
"jsontext": "{\\"object with 1 member\\":[\\"array with 1 element\\"]}",
"quotes": "&#34; \\u0022 %22 0x22 034 &#x22;",
"\\/\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t\`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string"
},
0.5,
98.6,
99.44,
1066,
1e1,
0.1e1,
1e-1,
1e00,
2e+00,
2e-00,
"rosebud"
]
`;
exports[`single-line.json 1`] = `
{"key1":[true,false,null],"key2":{"key3":[1,2,"3",1e10,1e-3]}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{ "key1": [true, false, null], "key2": { "key3": [1, 2, "3", 1e10, 1e-3] } }
`;
+1
View File
@@ -0,0 +1 @@
run_spec(__dirname, { parser: "json" });
+2
View File
@@ -0,0 +1,2 @@
{"key1":[true,false,null],"key2":{"key3":[1,2,"3",
1e10,1e-3]}}
+58
View File
@@ -0,0 +1,58 @@
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]
+1
View File
@@ -0,0 +1 @@
{"key1":[true,false,null],"key2":{"key3":[1,2,"3",1e10,1e-3]}}
+4
View File
@@ -2316,6 +2316,10 @@ json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
json-to-ast@2.0.0-alpha1.2:
version "2.0.0-alpha1.2"
resolved "https://registry.yarnpkg.com/json-to-ast/-/json-to-ast-2.0.0-alpha1.2.tgz#fe27fd89eb639eca1e01f578e79d46ee36e238e8"
json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"