1use oxc::ast::ast::{ArrayExpressionElement, Expression, UnaryOperator};
7
8use tracing::trace;
9
10use crate::value::JsValue;
11
12#[inline]
18pub fn string<'a>(expr: &'a Expression<'a>) -> Option<&'a str> {
19 match expr {
20 Expression::StringLiteral(lit) => Some(lit.value.as_str()),
21 Expression::ParenthesizedExpression(p) => string(&p.expression),
22 _ => None,
23 }
24}
25
26#[inline]
28pub fn number(expr: &Expression) -> Option<f64> {
29 match expr {
30 Expression::NumericLiteral(lit) => Some(lit.value),
31 Expression::Identifier(id) if id.name == "Infinity" => Some(f64::INFINITY),
32 Expression::Identifier(id) if id.name == "NaN" => Some(f64::NAN),
33 Expression::UnaryExpression(u) => match u.operator {
34 UnaryOperator::UnaryNegation => number(&u.argument).map(|v| -v),
35 UnaryOperator::UnaryPlus => number(&u.argument),
36 _ => None,
37 },
38 Expression::ParenthesizedExpression(p) => number(&p.expression),
39 _ => None,
40 }
41}
42
43#[inline]
45pub fn boolean(expr: &Expression) -> Option<bool> {
46 match expr {
47 Expression::BooleanLiteral(lit) => Some(lit.value),
48 Expression::ParenthesizedExpression(p) => boolean(&p.expression),
49 _ => None,
50 }
51}
52
53#[inline]
55pub fn is_null(expr: &Expression) -> bool {
56 match expr {
57 Expression::NullLiteral(_) => true,
58 Expression::ParenthesizedExpression(p) => is_null(&p.expression),
59 _ => false,
60 }
61}
62
63#[inline]
65pub fn is_undefined(expr: &Expression) -> bool {
66 match expr {
67 Expression::Identifier(id) if id.name == "undefined" => true,
68 Expression::UnaryExpression(u) if u.operator == UnaryOperator::Void => true,
69 Expression::ParenthesizedExpression(p) => is_undefined(&p.expression),
70 _ => false,
71 }
72}
73
74pub fn js_value(expr: &Expression) -> Option<JsValue> {
83 match expr {
84 Expression::NumericLiteral(lit) => Some(JsValue::Number(lit.value)),
85 Expression::StringLiteral(lit) => Some(JsValue::String(lit.value.to_string())),
86 Expression::BooleanLiteral(lit) => Some(JsValue::Boolean(lit.value)),
87 Expression::NullLiteral(_) => Some(JsValue::Null),
88 Expression::Identifier(id) if id.name == "undefined" => Some(JsValue::Undefined),
89 Expression::Identifier(id) if id.name == "Infinity" => Some(JsValue::Number(f64::INFINITY)),
90 Expression::Identifier(id) if id.name == "NaN" => Some(JsValue::Number(f64::NAN)),
91 Expression::UnaryExpression(u) => match u.operator {
92 UnaryOperator::UnaryNegation => {
93 if let Some(JsValue::Number(n)) = js_value(&u.argument) {
94 Some(JsValue::Number(-n))
95 } else {
96 None
97 }
98 }
99 UnaryOperator::UnaryPlus => {
100 if let Some(JsValue::Number(n)) = js_value(&u.argument) {
101 Some(JsValue::Number(n))
102 } else {
103 None
104 }
105 }
106 _ => None,
107 },
108 Expression::ParenthesizedExpression(p) => js_value(&p.expression),
109 _ => None,
110 }
111}
112
113pub fn array_elements<'a>(expr: &'a Expression<'a>) -> Option<Vec<&'a Expression<'a>>> {
122 match expr {
123 Expression::ArrayExpression(arr) => {
124 let mut elements = Vec::with_capacity(arr.elements.len());
125 for elem in &arr.elements {
126 match elem {
127 ArrayExpressionElement::SpreadElement(_) => {
128 trace!("array_elements: skipping array with spread element");
129 return None;
130 }
131 ArrayExpressionElement::Elision(_) => {
132 trace!("array_elements: skipping array with hole/elision");
133 return None;
134 }
135 _ => elements.push(elem.as_expression()?),
136 }
137 }
138 Some(elements)
139 }
140 Expression::ParenthesizedExpression(p) => array_elements(&p.expression),
141 _ => None,
142 }
143}
144
145#[cfg(test)]
150mod tests {
151 use super::*;
152 use oxc::allocator::Allocator;
153 use oxc::parser::Parser;
154 use oxc::span::SourceType;
155
156 fn parse_expr<'a>(alloc: &'a Allocator, src: &'a str) -> Expression<'a> {
157 Parser::new(alloc, src, SourceType::mjs())
158 .parse_expression()
159 .unwrap()
160 }
161
162 #[test]
163 fn test_string() {
164 let a = Allocator::default();
165 assert_eq!(string(&parse_expr(&a, "\"hello\"")), Some("hello"));
166 assert_eq!(string(&parse_expr(&a, "(\"world\")")), Some("world"));
167 assert_eq!(string(&parse_expr(&a, "42")), None);
168 }
169
170 #[test]
171 fn test_number() {
172 let a = Allocator::default();
173 assert_eq!(number(&parse_expr(&a, "42")), Some(42.0));
174 assert_eq!(number(&parse_expr(&a, "-5")), Some(-5.0));
175 assert_eq!(number(&parse_expr(&a, "+3")), Some(3.0));
176 assert_eq!(number(&parse_expr(&a, "(42)")), Some(42.0));
177 assert_eq!(number(&parse_expr(&a, "\"x\"")), None);
178 }
179
180 #[test]
181 fn test_boolean() {
182 let a = Allocator::default();
183 assert_eq!(boolean(&parse_expr(&a, "true")), Some(true));
184 assert_eq!(boolean(&parse_expr(&a, "false")), Some(false));
185 assert_eq!(boolean(&parse_expr(&a, "42")), None);
186 }
187
188 #[test]
189 fn test_null_undefined() {
190 let a = Allocator::default();
191 assert!(is_null(&parse_expr(&a, "null")));
192 assert!(!is_null(&parse_expr(&a, "42")));
193 assert!(is_undefined(&parse_expr(&a, "undefined")));
194 assert!(is_undefined(&parse_expr(&a, "void 0")));
195 }
196
197 #[test]
198 fn test_js_value() {
199 let a = Allocator::default();
200 assert_eq!(js_value(&parse_expr(&a, "42")), Some(JsValue::Number(42.0)));
201 assert_eq!(js_value(&parse_expr(&a, "\"hi\"")), Some(JsValue::String("hi".into())));
202 assert_eq!(js_value(&parse_expr(&a, "true")), Some(JsValue::Boolean(true)));
203 assert_eq!(js_value(&parse_expr(&a, "null")), Some(JsValue::Null));
204 assert_eq!(js_value(&parse_expr(&a, "undefined")), Some(JsValue::Undefined));
205 assert_eq!(js_value(&parse_expr(&a, "-5")), Some(JsValue::Number(-5.0)));
206 assert_eq!(js_value(&parse_expr(&a, "foo")), None);
207 }
208
209 #[test]
210 fn test_array_elements() {
211 let a = Allocator::default();
212 let expr = parse_expr(&a, "[1, 2, 3]");
213 let elems = array_elements(&expr).unwrap();
214 assert_eq!(elems.len(), 3);
215 assert_eq!(number(elems[0]), Some(1.0));
216 assert_eq!(number(elems[2]), Some(3.0));
217 }
218
219 #[test]
220 fn test_array_with_spread_returns_none() {
221 let a = Allocator::default();
222 let expr = parse_expr(&a, "[1, ...x]");
223 assert!(array_elements(&expr).is_none());
224 }
225}