js_deobfuscator/ast/
create.rs1use oxc::ast::AstBuilder;
4use oxc::ast::ast::{Expression, NumberBase, UnaryOperator};
5use oxc::span::SPAN;
6
7use crate::value::JsValue;
8
9pub fn make_number<'a>(value: f64, ast: &AstBuilder<'a>) -> Expression<'a> {
18 if value == 0.0 && value.is_sign_negative() {
19 let inner = ast.expression_numeric_literal(SPAN, 0.0, None, NumberBase::Decimal);
21 return ast.expression_unary(SPAN, UnaryOperator::UnaryNegation, inner);
22 }
23 if value.is_sign_negative() && !value.is_nan() {
24 let inner = ast.expression_numeric_literal(SPAN, value.abs(), None, NumberBase::Decimal);
25 return ast.expression_unary(SPAN, UnaryOperator::UnaryNegation, inner);
26 }
27 ast.expression_numeric_literal(SPAN, value, None, NumberBase::Decimal)
28}
29
30#[inline]
32pub fn make_string<'a>(value: &str, ast: &AstBuilder<'a>) -> Expression<'a> {
33 ast.expression_string_literal(SPAN, ast.str(value), None)
34}
35
36#[inline]
38pub fn make_boolean<'a>(value: bool, ast: &AstBuilder<'a>) -> Expression<'a> {
39 ast.expression_boolean_literal(SPAN, value)
40}
41
42#[inline]
44pub fn make_null<'a>(ast: &AstBuilder<'a>) -> Expression<'a> {
45 ast.expression_null_literal(SPAN)
46}
47
48#[inline]
50pub fn make_undefined<'a>(ast: &AstBuilder<'a>) -> Expression<'a> {
51 ast.expression_identifier(SPAN, "undefined")
52}
53
54pub fn from_js_value<'a>(value: &JsValue, ast: &AstBuilder<'a>) -> Expression<'a> {
60 match value {
61 JsValue::Number(n) => make_number(*n, ast),
62 JsValue::String(s) => make_string(s, ast),
63 JsValue::Boolean(b) => make_boolean(*b, ast),
64 JsValue::Null => make_null(ast),
65 JsValue::Undefined => make_undefined(ast),
66 }
67}
68
69#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::ast::extract;
77 use oxc::allocator::Allocator;
78
79 #[test]
80 fn test_make_number_positive() {
81 let alloc = Allocator::default();
82 let ast = AstBuilder::new(&alloc);
83 let expr = make_number(42.0, &ast);
84 assert_eq!(extract::number(&expr), Some(42.0));
85 }
86
87 #[test]
88 fn test_make_number_negative() {
89 let alloc = Allocator::default();
90 let ast = AstBuilder::new(&alloc);
91 let expr = make_number(-5.0, &ast);
92 assert_eq!(extract::number(&expr), Some(-5.0));
93 }
94
95 #[test]
96 fn test_make_number_negative_zero() {
97 let alloc = Allocator::default();
98 let ast = AstBuilder::new(&alloc);
99 let expr = make_number(-0.0, &ast);
100 let val = extract::number(&expr).unwrap();
101 assert!(val.is_sign_negative());
102 assert_eq!(val, 0.0);
103 }
104
105 #[test]
106 fn test_make_string() {
107 let alloc = Allocator::default();
108 let ast = AstBuilder::new(&alloc);
109 let expr = make_string("hello", &ast);
110 assert_eq!(extract::string(&expr), Some("hello"));
111 }
112
113 #[test]
114 fn test_make_boolean() {
115 let alloc = Allocator::default();
116 let ast = AstBuilder::new(&alloc);
117 assert_eq!(extract::boolean(&make_boolean(true, &ast)), Some(true));
118 assert_eq!(extract::boolean(&make_boolean(false, &ast)), Some(false));
119 }
120
121 #[test]
122 fn test_roundtrip_js_value() {
123 let alloc = Allocator::default();
124 let ast = AstBuilder::new(&alloc);
125
126 let cases = [
127 JsValue::Number(42.0),
128 JsValue::Number(-7.0),
129 JsValue::String("test".into()),
130 JsValue::Boolean(true),
131 JsValue::Null,
132 ];
133
134 for val in &cases {
135 let expr = from_js_value(val, &ast);
136 let extracted = extract::js_value(&expr);
137 assert_eq!(extracted.as_ref(), Some(val), "roundtrip failed for {val:?}");
138 }
139 }
140}