feat(ecma): implement array join method (#6936)

This commit is contained in:
7086cmd 2024-11-26 11:28:07 +00:00
parent 9c9deaeffe
commit 24189f28ad
5 changed files with 91 additions and 6 deletions

View file

@ -0,0 +1,66 @@
use crate::ToJsString;
use oxc_ast::ast::*;
pub trait ArrayJoin<'a> {
/// `Array.prototype.join ( separator )`
/// <https://tc39.es/ecma262/#sec-array.prototype.join>
fn array_join(&self, separator: Option<&str>) -> Option<String>;
}
impl<'a> ArrayJoin<'a> for ArrayExpression<'a> {
fn array_join(&self, separator: Option<&str>) -> Option<String> {
let strings =
self.elements.iter().map(ToJsString::to_js_string).collect::<Option<Vec<_>>>();
strings
.map(|v| v.iter().map(AsRef::as_ref).collect::<Vec<_>>().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())
);
}
}

View file

@ -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 {

View file

@ -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,

View file

@ -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`
///
/// <https://tc39.es/ecma262/#sec-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<Cow<'a, str>> {
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<Cow<'a, str>> {
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<Cow<'a, str>> {
// 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)
}
}