boa_engine/object/builtins/
jsarray.rs

1//! A Rust API wrapper for Boa's `Array` Builtin ECMAScript Object
2use crate::{
3    Context, JsResult, JsString, JsValue,
4    builtins::Array,
5    error::JsNativeError,
6    object::{JsFunction, JsObject},
7    value::{IntoOrUndefined, TryFromJs},
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::unshift(&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            .expect("Array.prototype.filter should always return object");
119
120        Self::from_object(object)
121    }
122
123    /// Calls `Array.prototype.join()`.
124    #[inline]
125    pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
126        Array::join(
127            &self.inner.clone().into(),
128            &[separator.into_or_undefined()],
129            context,
130        )
131        .map(|x| {
132            x.as_string()
133                .expect("Array.prototype.join always returns string")
134        })
135    }
136
137    /// Calls `Array.prototype.fill()`.
138    pub fn fill<T>(
139        &self,
140        value: T,
141        start: Option<u32>,
142        end: Option<u32>,
143        context: &mut Context,
144    ) -> JsResult<Self>
145    where
146        T: Into<JsValue>,
147    {
148        Array::fill(
149            &self.inner.clone().into(),
150            &[
151                value.into(),
152                start.into_or_undefined(),
153                end.into_or_undefined(),
154            ],
155            context,
156        )?;
157        Ok(self.clone())
158    }
159
160    /// Calls `Array.prototype.indexOf()`.
161    pub fn index_of<T>(
162        &self,
163        search_element: T,
164        from_index: Option<u32>,
165        context: &mut Context,
166    ) -> JsResult<Option<u32>>
167    where
168        T: Into<JsValue>,
169    {
170        let index = Array::index_of(
171            &self.inner.clone().into(),
172            &[search_element.into(), from_index.into_or_undefined()],
173            context,
174        )?
175        .as_number()
176        .expect("Array.prototype.indexOf should always return number");
177
178        #[allow(clippy::float_cmp)]
179        if index == -1.0 {
180            Ok(None)
181        } else {
182            Ok(Some(index as u32))
183        }
184    }
185
186    /// Calls `Array.prototype.lastIndexOf()`.
187    pub fn last_index_of<T>(
188        &self,
189        search_element: T,
190        from_index: Option<u32>,
191        context: &mut Context,
192    ) -> JsResult<Option<u32>>
193    where
194        T: Into<JsValue>,
195    {
196        let index = Array::last_index_of(
197            &self.inner.clone().into(),
198            &[search_element.into(), from_index.into_or_undefined()],
199            context,
200        )?
201        .as_number()
202        .expect("Array.prototype.lastIndexOf should always return number");
203
204        #[allow(clippy::float_cmp)]
205        if index == -1.0 {
206            Ok(None)
207        } else {
208            Ok(Some(index as u32))
209        }
210    }
211
212    /// Calls `Array.prototype.find()`.
213    #[inline]
214    pub fn find(
215        &self,
216        predicate: JsFunction,
217        this_arg: Option<JsValue>,
218        context: &mut Context,
219    ) -> JsResult<JsValue> {
220        Array::find(
221            &self.inner.clone().into(),
222            &[predicate.into(), this_arg.into_or_undefined()],
223            context,
224        )
225    }
226
227    /// Calls `Array.prototype.filter()`.
228    #[inline]
229    pub fn filter(
230        &self,
231        callback: JsFunction,
232        this_arg: Option<JsValue>,
233        context: &mut Context,
234    ) -> JsResult<Self> {
235        let object = Array::filter(
236            &self.inner.clone().into(),
237            &[callback.into(), this_arg.into_or_undefined()],
238            context,
239        )?
240        .as_object()
241        .expect("Array.prototype.filter should always return object");
242
243        Self::from_object(object)
244    }
245
246    /// Calls `Array.prototype.map()`.
247    #[inline]
248    pub fn map(
249        &self,
250        callback: JsFunction,
251        this_arg: Option<JsValue>,
252        context: &mut Context,
253    ) -> JsResult<Self> {
254        let object = Array::map(
255            &self.inner.clone().into(),
256            &[callback.into(), this_arg.into_or_undefined()],
257            context,
258        )?
259        .as_object()
260        .expect("Array.prototype.map should always return object");
261
262        Self::from_object(object)
263    }
264
265    /// Calls `Array.prototype.every()`.
266    #[inline]
267    pub fn every(
268        &self,
269        callback: JsFunction,
270        this_arg: Option<JsValue>,
271        context: &mut Context,
272    ) -> JsResult<bool> {
273        let result = Array::every(
274            &self.inner.clone().into(),
275            &[callback.into(), this_arg.into_or_undefined()],
276            context,
277        )?
278        .as_boolean()
279        .expect("Array.prototype.every should always return boolean");
280
281        Ok(result)
282    }
283
284    /// Calls `Array.prototype.some()`.
285    #[inline]
286    pub fn some(
287        &self,
288        callback: JsFunction,
289        this_arg: Option<JsValue>,
290        context: &mut Context,
291    ) -> JsResult<bool> {
292        let result = Array::some(
293            &self.inner.clone().into(),
294            &[callback.into(), this_arg.into_or_undefined()],
295            context,
296        )?
297        .as_boolean()
298        .expect("Array.prototype.some should always return boolean");
299
300        Ok(result)
301    }
302
303    /// Calls `Array.prototype.sort()`.
304    #[inline]
305    pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> {
306        Array::sort(
307            &self.inner.clone().into(),
308            &[compare_fn.into_or_undefined()],
309            context,
310        )?;
311
312        Ok(self.clone())
313    }
314
315    /// Calls `Array.prototype.slice()`.
316    #[inline]
317    pub fn slice(
318        &self,
319        start: Option<u32>,
320        end: Option<u32>,
321        context: &mut Context,
322    ) -> JsResult<Self> {
323        let object = Array::slice(
324            &self.inner.clone().into(),
325            &[start.into_or_undefined(), end.into_or_undefined()],
326            context,
327        )?
328        .as_object()
329        .expect("Array.prototype.slice should always return object");
330
331        Self::from_object(object)
332    }
333
334    /// Calls `Array.prototype.reduce()`.
335    #[inline]
336    pub fn reduce(
337        &self,
338        callback: JsFunction,
339        initial_value: Option<JsValue>,
340        context: &mut Context,
341    ) -> JsResult<JsValue> {
342        Array::reduce(
343            &self.inner.clone().into(),
344            &[callback.into(), initial_value.into_or_undefined()],
345            context,
346        )
347    }
348
349    /// Calls `Array.prototype.reduceRight()`.
350    #[inline]
351    pub fn reduce_right(
352        &self,
353        callback: JsFunction,
354        initial_value: Option<JsValue>,
355        context: &mut Context,
356    ) -> JsResult<JsValue> {
357        Array::reduce_right(
358            &self.inner.clone().into(),
359            &[callback.into(), initial_value.into_or_undefined()],
360            context,
361        )
362    }
363
364    /// Calls `Array.prototype.toReversed`.
365    #[inline]
366    pub fn to_reversed(&self, context: &mut Context) -> JsResult<Self> {
367        let array = Array::to_reversed(&self.inner.clone().into(), &[], context)?;
368
369        Ok(Self {
370            inner: array
371                .as_object()
372                .expect("`to_reversed` must always return an `Array` on success"),
373        })
374    }
375
376    /// Calls `Array.prototype.toSorted`.
377    #[inline]
378    pub fn to_sorted(
379        &self,
380        compare_fn: Option<JsFunction>,
381        context: &mut Context,
382    ) -> JsResult<Self> {
383        let array = Array::to_sorted(
384            &self.inner.clone().into(),
385            &[compare_fn.into_or_undefined()],
386            context,
387        )?;
388
389        Ok(Self {
390            inner: array
391                .as_object()
392                .expect("`to_sorted` must always return an `Array` on success"),
393        })
394    }
395
396    /// Calls `Array.prototype.with`.
397    #[inline]
398    pub fn with(&self, index: u64, value: JsValue, context: &mut Context) -> JsResult<Self> {
399        let array = Array::with(&self.inner.clone().into(), &[index.into(), value], context)?;
400
401        Ok(Self {
402            inner: array
403                .as_object()
404                .expect("`with` must always return an `Array` on success"),
405        })
406    }
407}
408
409impl From<JsArray> for JsObject {
410    #[inline]
411    fn from(o: JsArray) -> Self {
412        o.inner.clone()
413    }
414}
415
416impl From<JsArray> for JsValue {
417    #[inline]
418    fn from(o: JsArray) -> Self {
419        o.inner.clone().into()
420    }
421}
422
423impl Deref for JsArray {
424    type Target = JsObject;
425
426    #[inline]
427    fn deref(&self) -> &Self::Target {
428        &self.inner
429    }
430}
431
432impl TryFromJs for JsArray {
433    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
434        if let Some(o) = value.as_object() {
435            Self::from_object(o.clone())
436        } else {
437            Err(JsNativeError::typ()
438                .with_message("value is not an Array object")
439                .into())
440        }
441    }
442}