prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
149 lines (126 loc) • 3.56 kB
JavaScript
;
const { isNonEmptyArray } = require("../../common/util");
const createError = require("../../common/parser-create-error");
const createParser = require("./create-parser");
const createBabelParseError = require("./create-babel-parse-error");
function createJsonParse(options = {}) {
const { allowComments = true } = options;
return function parse(text /*, parsers, options*/) {
const { parseExpression } = require("@babel/parser");
let ast;
try {
ast = parseExpression(text, {
tokens: true,
ranges: true,
});
} catch (error) {
throw createBabelParseError(error);
}
// @ts-ignore
if (!allowComments && isNonEmptyArray(ast.comments)) {
// @ts-ignore
throw createJsonError(ast.comments[0], "Comment");
}
assertJsonNode(ast);
return ast;
};
}
function createJsonError(node, description) {
const [start, end] = [node.loc.start, node.loc.end].map(
({ line, column }) => ({
line,
column: column + 1,
})
);
return createError(`${description} is not allowed in JSON.`, { start, end });
}
function assertJsonNode(node) {
switch (node.type) {
case "ArrayExpression":
for (const element of node.elements) {
if (element !== null) {
assertJsonNode(element);
}
}
return;
case "ObjectExpression":
for (const property of node.properties) {
assertJsonNode(property);
}
return;
case "ObjectProperty":
if (node.computed) {
throw createJsonError(node.key, "Computed key");
}
if (node.shorthand) {
throw createJsonError(node.key, "Shorthand property");
}
if (node.key.type !== "Identifier") {
assertJsonNode(node.key);
}
assertJsonNode(node.value);
return;
case "UnaryExpression": {
const { operator, argument } = node;
if (operator !== "+" && operator !== "-") {
throw createJsonError(node, `Operator '${node.operator}'`);
}
if (
argument.type === "NumericLiteral" ||
(argument.type === "Identifier" &&
(argument.name === "Infinity" || argument.name === "NaN"))
) {
return;
}
throw createJsonError(
argument,
`Operator '${operator}' before '${argument.type}'`
);
}
case "Identifier":
if (
// JSON5 https://spec.json5.org/#numbers
node.name !== "Infinity" &&
node.name !== "NaN" &&
// JSON6 https://github.com/d3x0r/JSON6
node.name !== "undefined"
) {
throw createJsonError(node, `Identifier '${node.name}'`);
}
return;
case "TemplateLiteral":
if (isNonEmptyArray(node.expressions)) {
throw createJsonError(
node.expressions[0],
"'TemplateLiteral' with expression"
);
}
for (const element of node.quasis) {
assertJsonNode(element);
}
return;
case "NullLiteral":
case "BooleanLiteral":
case "NumericLiteral":
case "StringLiteral":
case "TemplateElement":
return;
default:
throw createJsonError(node, `'${node.type}'`);
}
}
const parseJson = createJsonParse();
const jsonParsers = {
json: createParser({
parse: parseJson,
hasPragma() {
return true;
},
}),
json5: createParser(parseJson),
"json-stringify": createParser({
parse: createJsonParse({ allowComments: false }),
astFormat: "estree-json",
}),
};
module.exports = jsonParsers;