use crate::abstract_syntax_tree_types::{
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, FunctionExpression,
Literal, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression, PipeExpression, Program,
UnaryExpression, Value,
};
fn recast_literal(literal: Literal) -> String {
if let serde_json::Value::String(value) = literal.value {
let quote = if literal.raw.trim().starts_with('"') { '"' } else { '\'' };
format!("{}{}{}", quote, value, quote)
} else {
literal.value.to_string()
}
}
pub fn precedence(operator: &BinaryOperator) -> u8 {
match operator {
BinaryOperator::Add | BinaryOperator::Sub => 11,
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
}
}
fn recast_binary_expression(expression: BinaryExpression) -> String {
let maybe_wrap_it = |a: String, doit: bool| -> String {
if doit {
format!("({})", a)
} else {
a
}
};
let should_wrap_right = match expression.right.clone() {
BinaryPart::BinaryExpression(bin_exp) => {
precedence(&expression.operator) > precedence(&bin_exp.operator)
|| expression.operator == BinaryOperator::Sub
}
_ => false,
};
let should_wrap_left = match expression.left.clone() {
BinaryPart::BinaryExpression(bin_exp) => precedence(&expression.operator) > precedence(&bin_exp.operator),
_ => false,
};
format!(
"{} {} {}",
maybe_wrap_it(recast_binary_part(expression.left), should_wrap_left),
expression.operator,
maybe_wrap_it(recast_binary_part(expression.right), should_wrap_right)
)
}
fn recast_binary_part(part: BinaryPart) -> String {
match part {
BinaryPart::Literal(literal) => recast_literal(*literal),
BinaryPart::Identifier(identifier) => identifier.name,
BinaryPart::BinaryExpression(binary_expression) => recast_binary_expression(*binary_expression),
BinaryPart::CallExpression(call_expression) => recast_call_expression(&call_expression, "", false),
_ => String::new(),
}
}
fn recast_value(node: Value, _indentation: String, is_in_pipe_expression: bool) -> String {
let indentation = _indentation + if is_in_pipe_expression { " " } else { "" };
match node {
Value::BinaryExpression(bin_exp) => recast_binary_expression(*bin_exp),
Value::ArrayExpression(array_exp) => recast_array_expression(&array_exp, &indentation),
Value::ObjectExpression(ref obj_exp) => recast_object_expression(obj_exp, &indentation, is_in_pipe_expression),
Value::MemberExpression(mem_exp) => recast_member_expression(*mem_exp),
Value::Literal(literal) => recast_literal(*literal),
Value::FunctionExpression(func_exp) => recast_function(*func_exp),
Value::CallExpression(call_exp) => recast_call_expression(&call_exp, &indentation, is_in_pipe_expression),
Value::Identifier(ident) => ident.name,
Value::PipeExpression(pipe_exp) => recast_pipe_expression(&pipe_exp),
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
_ => String::new(),
}
}
fn recast_array_expression(expression: &ArrayExpression, indentation: &str) -> String {
let flat_recast = format!(
"[{}]",
expression
.elements
.iter()
.map(|el| recast_value(el.clone(), String::new(), false))
.collect::<Vec<String>>()
.join(", ")
);
let max_array_length = 40;
if flat_recast.len() > max_array_length {
let _indentation = indentation.to_string() + " ";
format!(
"[\n{}{}\n{}]",
_indentation,
expression
.elements
.iter()
.map(|el| recast_value(el.clone(), _indentation.clone(), false))
.collect::<Vec<String>>()
.join(format!(",\n{}", _indentation).as_str()),
indentation
)
} else {
flat_recast
}
}
fn recast_object_expression(expression: &ObjectExpression, indentation: &str, is_in_pipe_expression: bool) -> String {
let flat_recast = format!(
"{{ {} }}",
expression
.properties
.iter()
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
recast_value(prop.value.clone(), String::new(), false)
)
})
.collect::<Vec<String>>()
.join(", ")
);
let max_array_length = 40;
if flat_recast.len() > max_array_length {
let _indentation = indentation.to_owned() + " ";
format!(
"{{\n{}{}\n{}}}",
_indentation,
expression
.properties
.iter()
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
recast_value(prop.value.clone(), _indentation.clone(), is_in_pipe_expression)
)
})
.collect::<Vec<String>>()
.join(format!(",\n{}", _indentation).as_str()),
if is_in_pipe_expression { " " } else { "" }
)
} else {
flat_recast
}
}
fn recast_call_expression(expression: &CallExpression, indentation: &str, is_in_pipe_expression: bool) -> String {
format!(
"{}({})",
expression.callee.name,
expression
.arguments
.iter()
.map(|arg| recast_argument(arg.clone(), indentation, is_in_pipe_expression))
.collect::<Vec<String>>()
.join(", ")
)
}
fn recast_argument(argument: Value, indentation: &str, is_in_pipe_expression: bool) -> String {
match argument {
Value::Literal(literal) => recast_literal(*literal),
Value::Identifier(identifier) => identifier.name,
Value::BinaryExpression(binary_exp) => recast_binary_expression(*binary_exp),
Value::ArrayExpression(array_exp) => recast_array_expression(&array_exp, indentation),
Value::ObjectExpression(object_exp) => {
recast_object_expression(&object_exp, indentation, is_in_pipe_expression)
}
Value::CallExpression(call_exp) => recast_call_expression(&call_exp, indentation, is_in_pipe_expression),
Value::FunctionExpression(function_exp) => recast_function(*function_exp),
Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
_ => String::new(),
}
}
fn recast_member_expression(expression: MemberExpression) -> String {
let key_str = match expression.property {
LiteralIdentifier::Identifier(identifier) => {
if expression.computed {
format!("[{}]", &(*identifier.name))
} else {
format!(".{}", &(*identifier.name))
}
}
LiteralIdentifier::Literal(lit) => format!("[{}]", &(*lit.raw)),
};
match expression.object {
MemberObject::MemberExpression(member_exp) => recast_member_expression(*member_exp) + key_str.as_str(),
MemberObject::Identifier(identifier) => identifier.name + key_str.as_str(),
}
}
fn recast_pipe_expression(expression: &PipeExpression) -> String {
expression
.body
.iter()
.enumerate()
.map(|(index, statement)| {
let mut indentation = " ".to_string();
let mut maybe_line_break = "\n".to_string();
let mut str = recast_value(statement.clone(), indentation.clone(), true);
let non_code_meta = expression.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index) {
if non_code_meta_value.value != " " {
str += non_code_meta_value.value.as_str();
indentation = String::new();
maybe_line_break = String::new();
}
}
if index != expression.body.len() - 1 {
str += maybe_line_break.as_str();
str += indentation.as_str();
str += "|> ".to_string().as_str();
}
str
})
.collect::<String>()
}
fn recast_unary_expression(expression: UnaryExpression) -> String {
let bin_part_val = match expression.argument {
BinaryPart::Literal(literal) => Value::Literal(literal),
BinaryPart::Identifier(identifier) => Value::Identifier(identifier),
BinaryPart::BinaryExpression(binary_expression) => Value::BinaryExpression(binary_expression),
BinaryPart::CallExpression(call_expression) => Value::CallExpression(call_expression),
BinaryPart::UnaryExpression(unary_expression) => Value::UnaryExpression(unary_expression),
};
format!(
"{}{}",
expression.operator,
recast_value(bin_part_val, String::new(), false)
)
}
pub fn recast(ast: &Program, indentation: &str, is_with_block: bool) -> String {
ast.body
.iter()
.map(|statement| match statement.clone() {
BodyItem::ExpressionStatement(expression_statement) => match expression_statement.expression {
Value::BinaryExpression(binary_expression) => recast_binary_expression(*binary_expression),
Value::ArrayExpression(array_expression) => recast_array_expression(&array_expression, ""),
Value::ObjectExpression(object_expression) => recast_object_expression(&object_expression, "", false),
Value::CallExpression(call_expression) => recast_call_expression(&call_expression, "", false),
_ => "Expression".to_string(),
},
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
.declarations
.iter()
.map(|declaration| {
format!(
"{} {} = {}",
variable_declaration.kind,
declaration.id.name,
recast_value(declaration.init.clone(), String::new(), false)
)
})
.collect::<String>(),
BodyItem::ReturnStatement(return_statement) => {
format!("return {}", recast_argument(return_statement.argument, "", false))
}
})
.enumerate()
.map(|(index, recast_str)| {
let is_legit_custom_whitespace_or_comment = |str: String| str != " " && str != "\n" && str != " ";
let last_white_space_or_comment = if index > 0 {
let tmp = if let Some(non_code_node) = ast.non_code_meta.none_code_nodes.get(&(index - 1)) {
non_code_node.value.clone()
} else {
" ".to_string()
};
tmp
} else {
" ".to_string()
};
let mut start_string = if is_legit_custom_whitespace_or_comment(last_white_space_or_comment) {
String::new()
} else {
indentation.to_owned()
};
if index == 0 {
if let Some(start) = ast.non_code_meta.start.clone() {
start_string = start.value;
} else {
start_string = indentation.to_owned();
}
}
if start_string.ends_with('\n') {
start_string += indentation;
}
let maybe_line_break: String = if index == ast.body.len() - 1 && !is_with_block {
String::new()
} else {
"\n".to_string()
};
let mut custom_white_space_or_comment = match ast.non_code_meta.none_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => custom_white_space_or_comment.value.clone(),
None => String::new(),
};
if !is_legit_custom_whitespace_or_comment(custom_white_space_or_comment.clone()) {
custom_white_space_or_comment = String::new();
}
let end_string = if custom_white_space_or_comment.is_empty() {
maybe_line_break
} else {
custom_white_space_or_comment
};
format!("{}{}{}", start_string, recast_str, end_string)
})
.collect::<String>()
}
pub fn recast_function(expression: FunctionExpression) -> String {
format!(
"({}) => {{{}}}",
expression
.params
.iter()
.map(|param| param.name.clone())
.collect::<Vec<String>>()
.join(", "),
recast(
&Program {
start: expression.body.start,
end: expression.body.start,
body: expression.body.body,
non_code_meta: expression.body.non_code_meta
},
"",
true
)
)
}