Skip to main content

js_deobfuscator/value/
mod.rs

1//! Layer 0: Pure JavaScript value computation.
2//!
3//! No OXC. No allocation. No AST. Just values in, values out.
4//! Every higher layer depends on this. This depends on nothing.
5
6pub mod ops;
7pub mod coerce;
8pub mod string;
9pub mod array;
10pub mod math;
11pub mod number;
12pub mod json;
13pub mod uri;
14pub mod runtime;
15
16// ============================================================================
17// Types
18// ============================================================================
19
20/// Compact JavaScript primitive value.
21///
22/// Owned, lifetime-free. Use for storing values across traversals
23/// (constant propagation maps, eval caches).
24///
25/// For zero-copy extraction within a single traversal, use
26/// `ast::extract::string()`, `ast::extract::number()` etc. instead.
27#[derive(Debug, Clone, PartialEq)]
28pub enum JsValue {
29    Number(f64),
30    String(String),
31    Boolean(bool),
32    Null,
33    Undefined,
34}
35
36// ============================================================================
37// Display
38// ============================================================================
39
40impl std::fmt::Display for JsValue {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            Self::Number(n) => write!(f, "{}", coerce::number_to_string(*n)),
44            Self::String(s) => write!(f, "{s}"),
45            Self::Boolean(b) => write!(f, "{b}"),
46            Self::Null => write!(f, "null"),
47            Self::Undefined => write!(f, "undefined"),
48        }
49    }
50}
51
52// ============================================================================
53// Truthiness
54// ============================================================================
55
56impl JsValue {
57    /// JavaScript falsy check.
58    ///
59    /// Falsy values: `false`, `0`, `-0`, `NaN`, `""`, `null`, `undefined`.
60    #[inline]
61    pub fn is_falsy(&self) -> bool {
62        match self {
63            Self::Boolean(false) | Self::Null | Self::Undefined => true,
64            Self::Number(n) => *n == 0.0 || n.is_nan(),
65            Self::String(s) => s.is_empty(),
66            Self::Boolean(true) => false,
67        }
68    }
69
70    /// JavaScript truthy check.
71    #[inline]
72    pub fn is_truthy(&self) -> bool {
73        !self.is_falsy()
74    }
75
76    /// JavaScript `typeof` result.
77    #[inline]
78    pub fn type_of(&self) -> &'static str {
79        match self {
80            Self::Number(_) => "number",
81            Self::String(_) => "string",
82            Self::Boolean(_) => "boolean",
83            Self::Null => "object",
84            Self::Undefined => "undefined",
85        }
86    }
87}
88
89// ============================================================================
90// Tests
91// ============================================================================
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_falsy_values() {
99        assert!(JsValue::Boolean(false).is_falsy());
100        assert!(JsValue::Number(0.0).is_falsy());
101        assert!(JsValue::Number(-0.0).is_falsy());
102        assert!(JsValue::Number(f64::NAN).is_falsy());
103        assert!(JsValue::String(String::new()).is_falsy());
104        assert!(JsValue::Null.is_falsy());
105        assert!(JsValue::Undefined.is_falsy());
106    }
107
108    #[test]
109    fn test_truthy_values() {
110        assert!(JsValue::Boolean(true).is_truthy());
111        assert!(JsValue::Number(1.0).is_truthy());
112        assert!(JsValue::Number(-1.0).is_truthy());
113        assert!(JsValue::String("x".into()).is_truthy());
114        assert!(JsValue::String("false".into()).is_truthy());
115    }
116
117    #[test]
118    fn test_typeof() {
119        assert_eq!(JsValue::Number(1.0).type_of(), "number");
120        assert_eq!(JsValue::String("x".into()).type_of(), "string");
121        assert_eq!(JsValue::Boolean(true).type_of(), "boolean");
122        assert_eq!(JsValue::Null.type_of(), "object");
123        assert_eq!(JsValue::Undefined.type_of(), "undefined");
124    }
125
126    #[test]
127    fn test_display() {
128        assert_eq!(JsValue::Number(42.0).to_string(), "42");
129        assert_eq!(JsValue::Number(-0.5).to_string(), "-0.5");
130        assert_eq!(JsValue::String("hello".into()).to_string(), "hello");
131        assert_eq!(JsValue::Boolean(true).to_string(), "true");
132        assert_eq!(JsValue::Null.to_string(), "null");
133        assert_eq!(JsValue::Undefined.to_string(), "undefined");
134    }
135}