mdbook-angular 0.5.0

mdbook renderer to run angular code samples
Documentation
use serde_json::{Number, Value};
use swc_core::ecma::ast;

use super::{
	PlaygroundInputConfig, PlaygroundInputConfigExt, PlaygroundInputType,
	ReadonlyPlaygroundInputConfigExt,
};

fn apply_unary(
	op: ast::UnaryOp,
	arg: Option<PlaygroundInputConfig>,
) -> Option<PlaygroundInputConfig> {
	match op {
		ast::UnaryOp::Minus => {
			if let Some(value) = arg.into_number() {
				let new_value = if let Some(val) = value.as_i64() {
					Number::from(-val)
				} else {
					Number::from_f64(-value.as_f64().unwrap()).unwrap()
				};

				Some(PlaygroundInputConfig::from_default(new_value))
			} else {
				Some(PlaygroundInputConfig::number())
			}
		}
		ast::UnaryOp::Plus => {
			if let Some(value) = arg.into_number() {
				Some(PlaygroundInputConfig::from_default(value))
			} else {
				Some(PlaygroundInputConfig::number())
			}
		}

		ast::UnaryOp::Bang => {
			if let Some(value) = arg.as_boolean() {
				Some(PlaygroundInputConfig::from_default(!value))
			} else {
				Some(PlaygroundInputConfig::boolean())
			}
		}

		ast::UnaryOp::Tilde => Some(PlaygroundInputConfig::number()),
		ast::UnaryOp::TypeOf => Some(PlaygroundInputConfig::string()),

		_ => None,
	}
}

#[allow(clippy::too_many_lines)]
fn apply_binary(
	op: ast::BinaryOp,
	left: Option<&PlaygroundInputConfig>,
	right: Option<&PlaygroundInputConfig>,
) -> Option<PlaygroundInputConfig> {
	match op {
		ast::BinaryOp::NotEq
		| ast::BinaryOp::In
		| ast::BinaryOp::InstanceOf
		| ast::BinaryOp::EqEq => Some(PlaygroundInputConfig::boolean()),

		ast::BinaryOp::EqEqEq => Some(if let (Some(left), Some(right)) = (left, right) {
			PlaygroundInputConfig::from_default(left.get_default().eq(&right.get_default()))
		} else {
			PlaygroundInputConfig::boolean()
		}),
		ast::BinaryOp::NotEqEq => Some(if let (Some(left), Some(right)) = (left, right) {
			PlaygroundInputConfig::from_default(!left.get_default().eq(&right.get_default()))
		} else {
			PlaygroundInputConfig::boolean()
		}),

		ast::BinaryOp::LogicalAnd => Some(if let Some(left) = left.as_boolean() {
			if !left {
				PlaygroundInputConfig::from_default(false)
			} else if let Some(right) = right.as_boolean() {
				PlaygroundInputConfig::from_default(right)
			} else {
				PlaygroundInputConfig::boolean()
			}
		} else {
			PlaygroundInputConfig::boolean()
		}),
		ast::BinaryOp::LogicalOr => Some(if let Some(left) = left.as_boolean() {
			if left {
				PlaygroundInputConfig::from_default(true)
			} else if let Some(right) = right.as_boolean() {
				PlaygroundInputConfig::from_default(right)
			} else {
				PlaygroundInputConfig::boolean()
			}
		} else {
			PlaygroundInputConfig::boolean()
		}),

		ast::BinaryOp::BitAnd => Some(i64_i64_to_i64_operator(
			left,
			right,
			std::ops::BitAnd::bitand,
		)),
		ast::BinaryOp::BitOr => Some(i64_i64_to_i64_operator(left, right, std::ops::BitOr::bitor)),
		ast::BinaryOp::BitXor => Some(i64_i64_to_i64_operator(
			left,
			right,
			std::ops::BitXor::bitxor,
		)),
		ast::BinaryOp::LShift => Some(i64_i64_to_i64_operator(left, right, std::ops::Shl::shl)),
		ast::BinaryOp::RShift => Some(i64_i64_to_i64_operator(left, right, std::ops::Shr::shr)),
		ast::BinaryOp::ZeroFillRShift => Some(PlaygroundInputConfig::number()),

		ast::BinaryOp::Exp => Some(f64_f64_to_f64_operator(left, right, f64::powf)),
		ast::BinaryOp::Mod => Some(f64_f64_to_f64_operator(left, right, std::ops::Rem::rem)),
		ast::BinaryOp::Mul => Some(f64_f64_to_f64_operator(left, right, std::ops::Mul::mul)),
		ast::BinaryOp::Div => Some(f64_f64_to_f64_operator(left, right, std::ops::Div::div)),
		ast::BinaryOp::Sub => Some(f64_f64_to_f64_operator(left, right, std::ops::Sub::sub)),

		ast::BinaryOp::Gt => Some(f64_f64_to_bool_operator(left, right, |a, b| a > b)),
		ast::BinaryOp::GtEq => Some(f64_f64_to_bool_operator(left, right, |a, b| a >= b)),
		ast::BinaryOp::Lt => Some(f64_f64_to_bool_operator(left, right, |a, b| a < b)),
		ast::BinaryOp::LtEq => Some(f64_f64_to_bool_operator(left, right, |a, b| a <= b)),

		ast::BinaryOp::Add => {
			// Oh boy
			if let (Some(left), Some(right)) = (left.get_default(), right.get_default()) {
				match (left, right) {
					(Value::String(left), Value::String(right)) => {
						let mut result = String::with_capacity(left.len() + right.len());
						result.push_str(left);
						result.push_str(right);

						Some(PlaygroundInputConfig::from_default(result))
					}
					(Value::String(_), _) | (_, Value::String(_)) => {
						Some(PlaygroundInputConfig::string())
					}

					(Value::Number(left), Value::Number(right)) => Some(
						if let Some(result) = serde_json::Number::from_f64(
							left.as_f64().unwrap() + right.as_f64().unwrap(),
						) {
							PlaygroundInputConfig::from_default(result)
						} else {
							PlaygroundInputConfig::number()
						},
					),
					(Value::Bool(true), Value::Number(num))
					| (Value::Number(num), Value::Bool(true)) => Some(
						if let Some(result) =
							serde_json::Number::from_f64(num.as_f64().unwrap() + 1.0)
						{
							PlaygroundInputConfig::from_default(result)
						} else {
							PlaygroundInputConfig::number()
						},
					),

					(Value::Number(num), Value::Null | Value::Bool(false))
					| (Value::Null | Value::Bool(false), Value::Number(num)) => {
						Some(PlaygroundInputConfig::from_default(num.clone()))
					}

					_ => None,
				}
			} else {
				None
			}
		}

		ast::BinaryOp::NullishCoalescing => None,
	}
}

fn f64_f64_to_f64_operator<F>(
	left: Option<&PlaygroundInputConfig>,
	right: Option<&PlaygroundInputConfig>,
	f: F,
) -> PlaygroundInputConfig
where
	F: Fn(f64, f64) -> f64,
{
	if let (Some(Value::Number(left)), Some(Value::Number(right))) =
		(left.get_default(), right.get_default())
	{
		if let Some(value) =
			serde_json::Number::from_f64(f(left.as_f64().unwrap(), right.as_f64().unwrap()))
		{
			PlaygroundInputConfig::from_default(value)
		} else {
			PlaygroundInputConfig::number()
		}
	} else {
		PlaygroundInputConfig::number()
	}
}

fn f64_f64_to_bool_operator<F>(
	left: Option<&PlaygroundInputConfig>,
	right: Option<&PlaygroundInputConfig>,
	f: F,
) -> PlaygroundInputConfig
where
	F: Fn(f64, f64) -> bool,
{
	if let (Some(Value::Number(left)), Some(Value::Number(right))) =
		(left.get_default(), right.get_default())
	{
		PlaygroundInputConfig::from_default(f(left.as_f64().unwrap(), right.as_f64().unwrap()))
	} else {
		PlaygroundInputConfig::boolean()
	}
}

fn i64_i64_to_i64_operator<F>(
	left: Option<&PlaygroundInputConfig>,
	right: Option<&PlaygroundInputConfig>,
	f: F,
) -> PlaygroundInputConfig
where
	F: Fn(i64, i64) -> i64,
{
	if let (Some(Value::Number(left)), Some(Value::Number(right))) =
		(left.get_default(), right.get_default())
	{
		if let Some(left) = left.as_i64() {
			if let Some(right) = right.as_i64() {
				return PlaygroundInputConfig::from_default(f(left, right));
			}
		}
	}

	PlaygroundInputConfig::number()
}

pub(super) fn ts_type_to_input_type<T: AsRef<ast::TsType>>(
	ts_type: &T,
) -> Option<PlaygroundInputType> {
	match ts_type.as_ref() {
		ast::TsType::TsKeywordType(ast::TsKeywordType {
			kind: ast::TsKeywordTypeKind::TsNumberKeyword,
			..
		}) => Some(PlaygroundInputType::Number),
		ast::TsType::TsKeywordType(ast::TsKeywordType {
			kind: ast::TsKeywordTypeKind::TsStringKeyword,
			..
		}) => Some(PlaygroundInputType::String),
		ast::TsType::TsKeywordType(ast::TsKeywordType {
			kind: ast::TsKeywordTypeKind::TsBooleanKeyword,
			..
		}) => Some(PlaygroundInputType::Boolean),

		ast::TsType::TsUnionOrIntersectionType(ast::TsUnionOrIntersectionType::TsUnionType(
			ast::TsUnionType { types, .. },
		)) => {
			let string_types: Vec<_> = types
				.iter()
				.filter_map(|t| t.as_ts_lit_type())
				.filter_map(|t| t.lit.as_str())
				.filter_map(|v| v.value.as_str())
				.collect();

			if string_types.len() == types.len() {
				Some(PlaygroundInputType::Enum(
					string_types.into_iter().map(ToOwned::to_owned).collect(),
				))
			} else {
				None
			}
		}
		_ => None,
	}
}

pub(super) fn evaluate<T: AsRef<ast::Expr>>(expr: T) -> Option<PlaygroundInputConfig> {
	match expr.as_ref() {
		ast::Expr::Lit(ast::Lit::Bool(value)) => {
			Some(PlaygroundInputConfig::from_default(value.value))
		}
		ast::Expr::Lit(ast::Lit::Num(value)) => Some(PlaygroundInputConfig::new(
			Number::from_f64(value.value).map(Value::Number),
			PlaygroundInputType::Number,
		)),
		ast::Expr::Lit(ast::Lit::Str(value)) => value
			.value
			.as_str()
			.map(ToOwned::to_owned)
			.map(PlaygroundInputConfig::from_default),

		ast::Expr::Tpl(_) => Some(PlaygroundInputConfig::string()),

		ast::Expr::Unary(ast::UnaryExpr { op, arg, .. }) => apply_unary(*op, evaluate(arg)),

		ast::Expr::TsNonNull(ast::TsNonNullExpr { expr, .. }) => evaluate(expr),

		ast::Expr::TsAs(ast::TsAsExpr { expr, type_ann, .. })
		| ast::Expr::TsSatisfies(ast::TsSatisfiesExpr { expr, type_ann, .. })
		| ast::Expr::TsTypeAssertion(ast::TsTypeAssertion { expr, type_ann, .. }) => evaluate(expr)
			.or_else(|| ts_type_to_input_type(type_ann).map(PlaygroundInputConfig::from_type)),

		ast::Expr::Bin(ast::BinExpr {
			op, left, right, ..
		}) => apply_binary(*op, evaluate(left).as_ref(), evaluate(right).as_ref()),

		_ => None,
	}
}