import { parseExpression } from '@babel/parser';
function printComments(comments, indent) {
if (!comments) return '';
comments.forEach((c) => {
c.printed = true;
});
return (
comments
.map((c) => {
const space = ' '.repeat(indent);
if (c.type === 'CommentLine') {
return `${space}//${c.value.trim()}`;
} else if (c.type === 'CommentBlock') {
return `${space}/*${c.value.trim()}*/`;
}
return '';
})
.join('\n') + (comments.length ? '\n' : '')
);
}
function printNode(node, indent, root = false) {
if (!node) return 'null';
let result = '';
if (node.leadingComments && root) {
result += printComments(node.leadingComments, indent);
}
switch (node.type) {
case 'ObjectExpression':
result += '{\n';
node.properties.forEach((prop, idx) => {
if (prop.leadingComments) {
result += printComments(prop.leadingComments, indent + 2);
}
const key = prop.key.type === 'Identifier' ? prop.key.name : prop.key.value;
result += ' '.repeat(indent + 2) + JSON.stringify(key) + ': ';
result += printNode(prop.value, indent + 2);
if (idx < node.properties.length - 1) {
result += ',';
}
if (prop.trailingComments) {
result += ' ' + printComments(prop.trailingComments, 0).trim();
}
result += '\n';
});
result += ' '.repeat(indent) + '}';
break;
case 'ArrayExpression':
result += '[\n';
node.elements.forEach((el, idx) => {
if (el?.leadingComments) {
result += printComments(el.leadingComments, indent + 2);
}
result += ' '.repeat(indent + 2) + printNode(el, indent + 2);
if (idx < node.elements.length - 1) {
result += ',';
}
if (el?.trailingComments) {
result += ' ' + printComments(el.trailingComments, 0).trim();
}
result += '\n';
});
result += ' '.repeat(indent) + ']';
break;
case 'StringLiteral':
result += JSON.stringify(node.value);
break;
case 'NumericLiteral':
result += String(node.value);
break;
case 'BooleanLiteral':
result += node.value ? 'true' : 'false';
break;
case 'NullLiteral':
result += 'null';
break;
case 'Literal':
result += JSON.stringify(node.value);
break;
default:
throw new Error(`Unsupported node type: ${node.type}`);
}
if (node.trailingComments && root) {
result += '\n' + printComments(node.trailingComments, indent);
}
return result;
}
export default {
languages: [
{
name: 'JSON',
parsers: ['custom-json'],
extensions: ['.json'],
},
],
parsers: {
'custom-json': {
parse(text) {
return parseExpression(text, {
allowComments: true,
plugins: ['estree'],
});
},
astFormat: 'custom-json-ast',
locStart: (node) => node.start ?? 0,
locEnd: (node) => node.end ?? 0,
},
},
printers: {
'custom-json-ast': {
print(path) {
const node = path.getValue();
const result = printNode(node, 0, true) + '\n';
return result;
},
},
},
};