boa_engine/object/builtins/
jsarray.rs

1//! A Rust API wrapper for Boa's `Array` Builtin ECMAScript Object
2use crate::{
3    builtins::Array,
4    error::JsNativeError,
5    object::{JsFunction, JsObject},
6    value::{IntoOrUndefined, TryFromJs},
7    Context, JsResult, JsString, JsValue,
8};
9use boa_gc::{Finalize, Trace};
10use std::ops::Deref;
11
12/// `JsArray` provides a wrapper for Boa's implementation of the JavaScript `Array` object.
13#[derive(Debug, Clone, Trace, Finalize)]
14pub struct JsArray {
15    inner: JsObject,
16}
17
18impl JsArray {
19    /// Create a new empty array.
20    #[inline]
21    pub fn new(context: &mut Context) -> Self {
22        let inner = Array::array_create(0, None, context)
23            .expect("creating an empty array with the default prototype must not fail");
24
25        Self { inner }
26    }
27
28    /// Create an array from a `IntoIterator<Item = JsValue>` convertible object.
29    pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
30    where
31        I: IntoIterator<Item = JsValue>,
32    {
33        Self {
34            inner: Array::create_array_from_list(elements, context),
35        }
36    }
37
38    /// Create a [`JsArray`] from a [`JsObject`], if the object is not an array throw a `TypeError`.
39    ///
40    /// This does not clone the fields of the array, it only does a shallow clone of the object.
41    #[inline]
42    pub fn from_object(object: JsObject) -> JsResult<Self> {
43        if object.is_array() {
44            Ok(Self { inner: object })
45        } else {
46            Err(JsNativeError::typ()
47                .with_message("object is not an Array")
48                .into())
49        }
50    }
51
52    /// Get the length of the array.
53    ///
54    /// Same as `array.length` in JavaScript.
55    #[inline]
56    pub fn length(&self, context: &mut Context) -> JsResult<u64> {
57        self.inner.length_of_array_like(context)
58    }
59
60    /// Check if the array is empty, i.e. the `length` is zero.
61    #[inline]
62    pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> {
63        self.inner.length_of_array_like(context).map(|len| len == 0)
64    }
65
66    /// Push an element to the array.
67    pub fn push<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
68    where
69        T: Into<JsValue>,
70    {
71        self.push_items(&[value.into()], context)
72    }
73
74    /// Pushes a slice of elements to the array.
75    #[inline]
76    pub fn push_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
77        Array::push(&self.inner.clone().into(), items, context)
78    }
79
80    /// Pops an element from the array.
81    #[inline]
82    pub fn pop(&self, context: &mut Context) -> JsResult<JsValue> {
83        Array::pop(&self.inner.clone().into(), &[], context)
84    }
85
86    /// Calls `Array.prototype.at()`.
87    pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue>
88    where
89        T: Into<i64>,
90    {
91        Array::at(&self.inner.clone().into(), &[index.into().into()], context)
92    }
93
94    /// Calls `Array.prototype.shift()`.
95    #[inline]
96    pub fn shift(&self, context: &mut Context) -> JsResult<JsValue> {
97        Array::shift(&self.inner.clone().into(), &[], context)
98    }
99
100    /// Calls `Array.prototype.unshift()`.
101    #[inline]
102    pub fn unshift(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
103        Array::shift(&self.inner.clone().into(), items, context)
104    }
105
106    /// Calls `Array.prototype.reverse()`.
107    #[inline]
108    pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
109        Array::reverse(&self.inner.clone().into(), &[], context)?;
110        Ok(self.clone())
111    }
112
113    /// Calls `Array.prototype.concat()`.
114    #[inline]
115    pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> {
116        let object = Array::concat(&self.inner.clone().into(), items, context)?
117            .as_object()
118            .cloned()
119            .expect("Array.prototype.filter should always return object");
120
121        Self::from_object(object)
122    }
123
124    /// Calls `Array.prototype.join()`.
125    #[inline]
126    pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
127        Array::join(
128            &self.inner.clone().into(),
129            &[separator.into_or_undefined()],
130            context,
131        )
132        .map(|x| {
133            x.as_string()
134                .cloned()
135                .expect("Array.prototype.join always returns string")
136        })
137    }
138
139    /// Calls `Array.prototype.fill()`.
140    pub fn fill<T>(
141        &self,
142        value: T,
143        start: Option<u32>,
144        end: Option<u32>,
145        context: &mut Context,
146    ) -> JsResult<Self>
147    where
148        T: Into<JsValue>,
149    {
150        Array::fill(
151            &self.inner.clone().into(),
152            &[
153                value.into(),
154                start.into_or_undefined(),
155                end.into_or_undefined(),
156            ],
157            context,
158        )?;
159        Ok(self.clone())
160    }
161
162    /// Calls `Array.prototype.indexOf()`.
163    pub fn index_of<T>(
164        &self,
165        search_element: T,
166        from_index: Option<u32>,
167        context: &mut Context,
168    ) -> JsResult<Option<u32>>
169    where
170        T: Into<JsValue>,
171    {
172        let index = Array::index_of(
173            &self.inner.clone().into(),
174            &[search_element.into(), from_index.into_or_undefined()],
175            context,
176        )?
177        .as_number()
178        .expect("Array.prototype.indexOf should always return number");
179
180        #[allow(clippy::float_cmp)]
181        if index == -1.0 {
182            Ok(None)
183        } else {
184            Ok(Some(index as u32))
185        }
186    }
187
188    /// Calls `Array.prototype.lastIndexOf()`.
189    pub fn last_index_of<T>(
190        &self,
191        search_element: T,
192        from_index: Option<u32>,
193        context: &mut Context,
194    ) -> JsResult<Option<u32>>
195    where
196        T: Into<JsValue>,
197    {
198        let index = Array::last_index_of(
199            &self.inner.clone().into(),
200            &[search_element.into(), from_index.into_or_undefined()],
201            context,
202        )?
203        .as_number()
204        .expect("Array.prototype.lastIndexOf should always return number");
205
206        #[allow(clippy::float_cmp)]
207        if index == -1.0 {
208            Ok(None)
209        } else {
210            Ok(Some(index as u32))
211        }
212    }
213
214    /// Calls `Array.prototype.find()`.
215    #[inline]
216    pub fn find(
217        &self,
218        predicate: JsFunction,
219        this_arg: Option<JsValue>,
220        context: &mut Context,
221    ) -> JsResult<JsValue> {
222        Array::find(
223            &self.inner.clone().into(),
224            &[predicate.into(), this_arg.into_or_undefined()],
225            context,
226        )
227    }
228
229    /// Calls `Array.prototype.filter()`.
230    #[inline]
231    pub fn filter(
232        &self,
233        callback: JsFunction,
234        this_arg: Option<JsValue>,
235        context: &mut Context,
236    ) -> JsResult<Self> {
237        let object = Array::filter(
238            &self.inner.clone().into(),
239            &[callback.into(), this_arg.into_or_undefined()],
240            context,
241        )?
242        .as_object()
243        .cloned()
244        .expect("Array.prototype.filter should always return object");
245
246        Self::from_object(object)
247    }
248
249    /// Calls `Array.prototype.map()`.
250    #[inline]
251    pub fn map(
252        &self,
253        callback: JsFunction,
254        this_arg: Option<JsValue>,
255        context: &mut Context,
256    ) -> JsResult<Self> {
257        let object = Array::map(
258            &self.inner.clone().into(),
259            &[callback.into(), this_arg.into_or_undefined()],
260            context,
261        )?
262        .as_object()
263        .cloned()
264        .expect("Array.prototype.map should always return object");
265
266        Self::from_object(object)
267    }
268
269    /// Calls `Array.prototype.every()`.
270    #[inline]
271    pub fn every(
272        &self,
273        callback: JsFunction,
274        this_arg: Option<JsValue>,
275        context: &mut Context,
276    ) -> JsResult<bool> {
277        let result = Array::every(
278            &self.inner.clone().into(),
279            &[callback.into(), this_arg.into_or_undefined()],
280            context,
281        )?
282        .as_boolean()
283        .expect("Array.prototype.every should always return boolean");
284
285        Ok(result)
286    }
287
288    /// Calls `Array.prototype.some()`.
289    #[inline]
290    pub fn some(
291        &self,
292        callback: JsFunction,
293        this_arg: Option<JsValue>,
294        context: &mut Context,
295    ) -> JsResult<bool> {
296        let result = Array::some(
297            &self.inner.clone().into(),
298            &[callback.into(), this_arg.into_or_undefined()],
299            context,
300        )?
301        .as_boolean()
302        .expect("Array.prototype.some should always return boolean");
303
304        Ok(result)
305    }
306
307    /// Calls `Array.prototype.sort()`.
308    #[inline]
309    pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> {
310        Array::sort(
311            &self.inner.clone().into(),
312            &[compare_fn.into_or_undefined()],
313            context,
314        )?;
315
316        Ok(self.clone())
317    }
318
319    /// Calls `Array.prototype.slice()`.
320    #[inline]
321    pub fn slice(
322        &self,
323        start: Option<u32>,
324        end: Option<u32>,
325        context: &mut Context,
326    ) -> JsResult<Self> {
327        let object = Array::slice(
328            &self.inner.clone().into(),
329            &[start.into_or_undefined(), end.into_or_undefined()],
330            context,
331        )?
332        .as_object()
333        .cloned()
334        .expect("Array.prototype.slice should always return object");
335
336        Self::from_object(object)
337    }
338
339    /// Calls `Array.prototype.reduce()`.
340    #[inline]
341    pub fn reduce(
342        &self,
343        callback: JsFunction,
344        initial_value: Option<JsValue>,
345        context: &mut Context,
346    ) -> JsResult<JsValue> {
347        Array::reduce(
348            &self.inner.clone().into(),
349            &[callback.into(), initial_value.into_or_undefined()],
350            context,
351        )
352    }
353
354    /// Calls `Array.prototype.reduceRight()`.
355    #[inline]
356    pub fn reduce_right(
357        &self,
358        callback: JsFunction,
359        initial_value: Option<JsValue>,
360        context: &mut Context,
361    ) -> JsResult<JsValue> {
362        Array::reduce_right(
363            &self.inner.clone().into(),
364            &[callback.into(), initial_value.into_or_undefined()],
365            context,
366        )
367    }
368
369    /// Calls `Array.prototype.toReversed`.
370    #[inline]
371    pub fn to_reversed(&self, context: &mut Context) -> JsResult<Self> {
372        let array = Array::to_reversed(&self.inner.clone().into(), &[], context)?;
373
374        Ok(Self {
375            inner: array
376                .as_object()
377                .cloned()
378                .expect("`to_reversed` must always return an `Array` on success"),
379        })
380    }
381
382    /// Calls `Array.prototype.toSorted`.
383    #[inline]
384    pub fn to_sorted(
385        &self,
386        compare_fn: Option<JsFunction>,
387        context: &mut Context,
388    ) -> JsResult<Self> {
389        let array = Array::to_sorted(
390            &self.inner.clone().into(),
391            &[compare_fn.into_or_undefined()],
392            context,
393        )?;
394
395        Ok(Self {
396            inner: array
397                .as_object()
398                .cloned()
399                .expect("`to_sorted` must always return an `Array` on success"),
400        })
401    }
402
403    /// Calls `Array.prototype.with`.
404    #[inline]
405    pub fn with(&self, index: u64, value: JsValue, context: &mut Context) -> JsResult<Self> {
406        let array = Array::with(&self.inner.clone().into(), &[index.into(), value], context)?;
407
408        Ok(Self {
409            inner: array
410                .as_object()
411                .cloned()
412                .expect("`with` must always return an `Array` on success"),
413        })
414    }
415}
416
417impl From<JsArray> for JsObject {
418    #[inline]
419    fn from(o: JsArray) -> Self {
420        o.inner.clone()
421    }
422}
423
424impl From<JsArray> for JsValue {
425    #[inline]
426    fn from(o: JsArray) -> Self {
427        o.inner.clone().into()
428    }
429}
430
431impl Deref for JsArray {
432    type Target = JsObject;
433
434    #[inline]
435    fn deref(&self) -> &Self::Target {
436        &self.inner
437    }
438}
439
440impl TryFromJs for JsArray {
441    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
442        match value {
443            JsValue::Object(o) => Self::from_object(o.clone()),
444            _ => Err(JsNativeError::typ()
445                .with_message("value is not an Array object")
446                .into()),
447        }
448    }
449}