diff --git a/crates/oxc_ecmascript/src/array_join.rs b/crates/oxc_ecmascript/src/array_join.rs new file mode 100644 index 000000000..a518c9b10 --- /dev/null +++ b/crates/oxc_ecmascript/src/array_join.rs @@ -0,0 +1,66 @@ +use crate::ToJsString; +use oxc_ast::ast::*; + +pub trait ArrayJoin<'a> { + /// `Array.prototype.join ( separator )` + /// + fn array_join(&self, separator: Option<&str>) -> Option; +} + +impl<'a> ArrayJoin<'a> for ArrayExpression<'a> { + fn array_join(&self, separator: Option<&str>) -> Option { + let strings = + self.elements.iter().map(ToJsString::to_js_string).collect::>>(); + strings + .map(|v| v.iter().map(AsRef::as_ref).collect::>().join(separator.unwrap_or(","))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use oxc_allocator::{Allocator, CloneIn}; + use oxc_ast::AstBuilder; + use oxc_span::SPAN; + + #[test] + fn test() { + let allocator = Allocator::default(); + let ast = AstBuilder::new(&allocator); + let mut elements = ast.vec(); + elements.push(ast.array_expression_element_elision(SPAN)); + elements.push(ArrayExpressionElement::NullLiteral(ast.alloc(ast.null_literal(SPAN)))); + elements.push(ArrayExpressionElement::NumericLiteral(ast.alloc(ast.numeric_literal( + SPAN, + 42f64, + "42", + NumberBase::Decimal, + )))); + elements.push(ArrayExpressionElement::StringLiteral( + ast.alloc(ast.string_literal(SPAN, "foo")), + )); + elements.push(ArrayExpressionElement::BooleanLiteral( + ast.alloc(ast.boolean_literal(SPAN, true)), + )); + elements.push(ArrayExpressionElement::BigIntLiteral(ast.alloc(ast.big_int_literal( + SPAN, + "42n", + BigintBase::Decimal, + )))); + let array = ast.array_expression(SPAN, elements.clone_in(&allocator), None); + let mut array2 = array.clone_in(&allocator); + array2.elements.push(ArrayExpressionElement::ArrayExpression(ast.alloc(array))); + array2.elements.push(ArrayExpressionElement::ObjectExpression( + ast.alloc(ast.object_expression(SPAN, ast.vec(), None)), + )); + let joined = array2.array_join(Some("_")); + assert_eq!(joined, Some("__42_foo_true_42n_,,42,foo,true,42n_[object Object]".to_string())); + + let joined2 = array2.array_join(None); + // By default, in `Array.prototype.toString`, the separator is a comma. However, in `Array.prototype.join`, the separator is none if not given. + assert_eq!( + joined2, + Some(",,42,foo,true,42n,,,42,foo,true,42n,[object Object]".to_string()) + ); + } +} diff --git a/crates/oxc_ecmascript/src/constant_evaluation/is_litral_value.rs b/crates/oxc_ecmascript/src/constant_evaluation/is_literal_value.rs similarity index 100% rename from crates/oxc_ecmascript/src/constant_evaluation/is_litral_value.rs rename to crates/oxc_ecmascript/src/constant_evaluation/is_literal_value.rs diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index de0675302..365966a2a 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -1,4 +1,4 @@ -mod is_litral_value; +mod is_literal_value; mod value; mod value_type; @@ -11,7 +11,7 @@ use oxc_ast::ast::*; use crate::{side_effects::MayHaveSideEffects, ToBigInt, ToBoolean, ToInt32, ToJsString, ToNumber}; -pub use self::{is_litral_value::IsLiteralValue, value::ConstantValue, value_type::ValueType}; +pub use self::{is_literal_value::IsLiteralValue, value::ConstantValue, value_type::ValueType}; pub trait ConstantEvaluation<'a> { fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool { diff --git a/crates/oxc_ecmascript/src/lib.rs b/crates/oxc_ecmascript/src/lib.rs index 1c3d635c7..a64d01407 100644 --- a/crates/oxc_ecmascript/src/lib.rs +++ b/crates/oxc_ecmascript/src/lib.rs @@ -7,6 +7,7 @@ mod private_bound_identifiers; mod prop_name; // Abstract Operations +mod array_join; mod string_char_at; mod string_char_code_at; mod string_index_of; @@ -27,7 +28,8 @@ pub mod constant_evaluation; pub mod side_effects; pub use self::{ - bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList, + array_join::ArrayJoin, bound_names::BoundNames, + is_simple_parameter_list::IsSimpleParameterList, private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName, string_char_at::StringCharAt, string_char_code_at::StringCharCodeAt, string_index_of::StringIndexOf, string_last_index_of::StringLastIndexOf, diff --git a/crates/oxc_ecmascript/src/to_string.rs b/crates/oxc_ecmascript/src/to_string.rs index 76f317e72..08d958beb 100644 --- a/crates/oxc_ecmascript/src/to_string.rs +++ b/crates/oxc_ecmascript/src/to_string.rs @@ -1,10 +1,10 @@ use std::borrow::Cow; +use crate::array_join::ArrayJoin; +use crate::ToBoolean; use oxc_ast::ast::*; use oxc_syntax::operator::UnaryOperator; -use crate::ToBoolean; - /// `ToString` /// /// @@ -30,6 +30,23 @@ impl<'a> ToJsString<'a> for Expression<'a> { } } +impl<'a> ToJsString<'a> for ArrayExpressionElement<'a> { + fn to_js_string(&self) -> Option> { + match self { + ArrayExpressionElement::SpreadElement(_) => None, + ArrayExpressionElement::Elision(_) | ArrayExpressionElement::NullLiteral(_) => { + Some(Cow::Borrowed("")) + } + ArrayExpressionElement::Identifier(id) if id.name.as_str() == "undefined" => { + Some(Cow::Borrowed("")) + } + expr @ match_expression!(ArrayExpressionElement) => { + expr.as_expression().and_then(ToJsString::to_js_string) + } + } + } +} + impl<'a> ToJsString<'a> for StringLiteral<'a> { fn to_js_string(&self) -> Option> { Some(Cow::Borrowed(self.value.as_str())) @@ -101,7 +118,7 @@ impl<'a> ToJsString<'a> for UnaryExpression<'a> { impl<'a> ToJsString<'a> for ArrayExpression<'a> { fn to_js_string(&self) -> Option> { // TODO: https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L302-L303 - None + self.array_join(Some(",")).map(Cow::Owned) } }