use serde_json::{Map, Value};
use crate::actions::mdast::serialize::MAX_JSON_DEPTH;
use crate::actions::mdx::serialize::insert_mdx_jsx;
use crate::actions::position::serialize::serialize_position;
use crate::models::data::Data;
use crate::models::error::Error;
use crate::models::hast::{Node, PropertyItem, PropertyValue, Root};
use crate::models::lines::Lines;
pub fn serialize_hast(
node: &Node<'_>,
lines: Option<&Lines<'_>>,
) -> Result<Value, Error> {
serialize_node(node, lines, 0)
}
fn serialize_node(
node: &Node<'_>,
lines: Option<&Lines<'_>>,
depth: usize,
) -> Result<Value, Error> {
if depth > MAX_JSON_DEPTH {
return Err(Error::new(
"tree-too-deep",
format!("trees nest at most {MAX_JSON_DEPTH} levels in JSON"),
));
}
let mut object = Map::new();
let serialize_all = |children: &[Node<'_>]| -> Result<Value, Error> {
let children = children
.iter()
.map(|child| serialize_node(child, lines, depth + 1))
.collect::<Result<_, _>>()?;
Ok(Value::Array(children))
};
match node {
Node::Root(root) => return serialize_root(root, lines, depth),
Node::Element(element) => {
object.insert("type".into(), "element".into());
object.insert("tagName".into(), element.tag_name.into());
let properties = element
.properties
.iter()
.map(|property| {
(property.name.to_string(), serialize_value(&property.value))
})
.collect();
object.insert("properties".into(), Value::Object(properties));
object.insert("children".into(), serialize_all(&element.children)?);
if let Some(content) = element.content {
object
.insert("content".into(), serialize_root(content, lines, depth + 1)?);
}
}
Node::Text(text) => {
object.insert("type".into(), "text".into());
object.insert("value".into(), text.value.into());
}
Node::Comment(comment) => {
object.insert("type".into(), "comment".into());
object.insert("value".into(), comment.value.into());
}
Node::Raw(raw) => {
object.insert("type".into(), "raw".into());
object.insert("value".into(), raw.value.into());
}
Node::Doctype(_) => {
object.insert("type".into(), "doctype".into());
}
Node::Unknown(unknown) => {
object.insert("type".into(), unknown.kind.into());
insert_fields(&mut object, unknown.fields);
if let Some(children) = &unknown.children {
object.insert("children".into(), serialize_all(children)?);
}
}
Node::MdxJsxFlowElement(element) | Node::MdxJsxTextElement(element) => {
let kind = match node {
Node::MdxJsxFlowElement(_) => "mdxJsxFlowElement",
_ => "mdxJsxTextElement",
};
object.insert("type".into(), kind.into());
insert_mdx_jsx(&mut object, element.name, &element.attributes, lines);
object.insert("children".into(), serialize_all(&element.children)?);
}
Node::MdxFlowExpression(expression) | Node::MdxTextExpression(expression) => {
let kind = match node {
Node::MdxFlowExpression(_) => "mdxFlowExpression",
_ => "mdxTextExpression",
};
object.insert("type".into(), kind.into());
object.insert("value".into(), expression.value.into());
}
Node::MdxjsEsm(esm) => {
object.insert("type".into(), "mdxjsEsm".into());
object.insert("value".into(), esm.value.into());
}
}
if let Some(data) = node.data() {
object.insert("data".into(), serialize_data(data));
}
serialize_position(&mut object, node.position(), lines);
Ok(Value::Object(object))
}
fn serialize_root(
root: &Root<'_>,
lines: Option<&Lines<'_>>,
depth: usize,
) -> Result<Value, Error> {
if depth > MAX_JSON_DEPTH {
return Err(Error::new(
"tree-too-deep",
format!("trees nest at most {MAX_JSON_DEPTH} levels in JSON"),
));
}
let mut object = Map::new();
object.insert("type".into(), "root".into());
let children = root
.children
.iter()
.map(|child| serialize_node(child, lines, depth + 1))
.collect::<Result<_, _>>()?;
object.insert("children".into(), Value::Array(children));
let mut data = match root.data.map(serialize_data) {
Some(Value::Object(data)) => data,
_ => Map::new(),
};
if let Some(quirks_mode) = root.quirks_mode {
data.insert("quirksMode".into(), quirks_mode.into());
}
if !data.is_empty() {
object.insert("data".into(), Value::Object(data));
}
serialize_position(&mut object, root.position.as_ref(), lines);
Ok(Value::Object(object))
}
pub(crate) fn insert_fields(
object: &mut Map<String, Value>,
fields: &[(&str, Data<'_>)],
) {
for (key, value) in fields {
object.insert((*key).into(), serialize_data(value));
}
}
pub(crate) fn serialize_data(data: &Data<'_>) -> Value {
match data {
Data::Null => Value::Null,
Data::Boolean(value) => (*value).into(),
Data::Number(value) => serialize_number(*value),
Data::String(value) => (*value).into(),
Data::Array(items) => Value::Array(items.iter().map(serialize_data).collect()),
Data::Object(entries) => Value::Object(
entries
.iter()
.map(|(key, value)| (key.to_string(), serialize_data(value)))
.collect(),
),
}
}
fn serialize_value(value: &PropertyValue<'_>) -> Value {
match value {
PropertyValue::Null => Value::Null,
PropertyValue::Boolean(value) => (*value).into(),
PropertyValue::Number(value) => serialize_number(*value),
PropertyValue::String(value) => (*value).into(),
PropertyValue::List(items) => Value::Array(
items
.iter()
.map(|item| match item {
PropertyItem::Number(value) => serialize_number(*value),
PropertyItem::String(value) => (*value).into(),
})
.collect(),
),
}
}
fn serialize_number(value: f64) -> Value {
if value.fract() == 0.0 && value.abs() < 9_007_199_254_740_992.0 {
Value::from(value as i64)
} else {
Value::from(value)
}
}