1use crate::{
3 Context, JsExpect, 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#[derive(Debug, Clone, Trace, Finalize)]
14pub struct JsArray {
15 inner: JsObject,
16}
17
18impl JsArray {
19 #[inline]
25 pub fn new(context: &mut Context) -> JsResult<Self> {
26 let inner = Array::array_create(0, None, context)
27 .js_expect("creating an empty array with the default prototype must not fail")?;
28
29 Ok(Self { inner })
30 }
31
32 pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
34 where
35 I: IntoIterator<Item = JsValue>,
36 {
37 Self {
38 inner: Array::create_array_from_list(elements, context),
39 }
40 }
41
42 #[inline]
46 pub fn from_object(object: JsObject) -> JsResult<Self> {
47 if object.is_array() {
48 Ok(Self { inner: object })
49 } else {
50 Err(JsNativeError::typ()
51 .with_message("object is not an Array")
52 .into())
53 }
54 }
55
56 #[inline]
60 pub fn length(&self, context: &mut Context) -> JsResult<u64> {
61 self.inner.length_of_array_like(context)
62 }
63
64 #[inline]
66 pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> {
67 self.inner.length_of_array_like(context).map(|len| len == 0)
68 }
69
70 pub fn push<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
72 where
73 T: Into<JsValue>,
74 {
75 self.push_items(&[value.into()], context)
76 }
77
78 #[inline]
80 pub fn push_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
81 Array::push(&self.inner.clone().into(), items, context)
82 }
83
84 #[inline]
86 pub fn pop(&self, context: &mut Context) -> JsResult<JsValue> {
87 Array::pop(&self.inner.clone().into(), &[], context)
88 }
89
90 pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue>
92 where
93 T: Into<i64>,
94 {
95 Array::at(&self.inner.clone().into(), &[index.into().into()], context)
96 }
97
98 #[inline]
100 pub fn shift(&self, context: &mut Context) -> JsResult<JsValue> {
101 Array::shift(&self.inner.clone().into(), &[], context)
102 }
103
104 #[inline]
106 pub fn unshift(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
107 Array::unshift(&self.inner.clone().into(), items, context)
108 }
109
110 #[inline]
112 pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
113 Array::reverse(&self.inner.clone().into(), &[], context)?;
114 Ok(self.clone())
115 }
116
117 #[inline]
119 pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> {
120 let object = Array::concat(&self.inner.clone().into(), items, context)?
121 .as_object()
122 .js_expect("Array.prototype.concat should always return object")?;
123
124 Self::from_object(object)
125 }
126
127 #[inline]
129 pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
130 let result = Array::join(
131 &self.inner.clone().into(),
132 &[separator.into_or_undefined()],
133 context,
134 )?;
135 result
136 .as_string()
137 .js_expect("Array.prototype.join always returns string")
138 .map_err(Into::into)
139 }
140
141 pub fn fill<T>(
143 &self,
144 value: T,
145 start: Option<u32>,
146 end: Option<u32>,
147 context: &mut Context,
148 ) -> JsResult<Self>
149 where
150 T: Into<JsValue>,
151 {
152 Array::fill(
153 &self.inner.clone().into(),
154 &[
155 value.into(),
156 start.into_or_undefined(),
157 end.into_or_undefined(),
158 ],
159 context,
160 )?;
161 Ok(self.clone())
162 }
163
164 pub fn index_of<T>(
166 &self,
167 search_element: T,
168 from_index: Option<u32>,
169 context: &mut Context,
170 ) -> JsResult<Option<u32>>
171 where
172 T: Into<JsValue>,
173 {
174 let index = Array::index_of(
175 &self.inner.clone().into(),
176 &[search_element.into(), from_index.into_or_undefined()],
177 context,
178 )?
179 .as_number()
180 .js_expect("Array.prototype.indexOf should always return number")?;
181
182 #[allow(clippy::float_cmp)]
183 if index == -1.0 {
184 Ok(None)
185 } else {
186 Ok(Some(index as u32))
187 }
188 }
189
190 pub fn last_index_of<T>(
192 &self,
193 search_element: T,
194 from_index: Option<u32>,
195 context: &mut Context,
196 ) -> JsResult<Option<u32>>
197 where
198 T: Into<JsValue>,
199 {
200 let index = Array::last_index_of(
201 &self.inner.clone().into(),
202 &[search_element.into(), from_index.into_or_undefined()],
203 context,
204 )?
205 .as_number()
206 .js_expect("Array.prototype.lastIndexOf should always return number")?;
207
208 #[allow(clippy::float_cmp)]
209 if index == -1.0 {
210 Ok(None)
211 } else {
212 Ok(Some(index as u32))
213 }
214 }
215
216 #[inline]
218 pub fn find(
219 &self,
220 predicate: JsFunction,
221 this_arg: Option<JsValue>,
222 context: &mut Context,
223 ) -> JsResult<JsValue> {
224 Array::find(
225 &self.inner.clone().into(),
226 &[predicate.into(), this_arg.into_or_undefined()],
227 context,
228 )
229 }
230
231 #[inline]
233 pub fn filter(
234 &self,
235 callback: JsFunction,
236 this_arg: Option<JsValue>,
237 context: &mut Context,
238 ) -> JsResult<Self> {
239 let object = Array::filter(
240 &self.inner.clone().into(),
241 &[callback.into(), this_arg.into_or_undefined()],
242 context,
243 )?
244 .as_object()
245 .js_expect("Array.prototype.filter should always return object")?;
246
247 Self::from_object(object)
248 }
249
250 #[inline]
252 pub fn map(
253 &self,
254 callback: JsFunction,
255 this_arg: Option<JsValue>,
256 context: &mut Context,
257 ) -> JsResult<Self> {
258 let object = Array::map(
259 &self.inner.clone().into(),
260 &[callback.into(), this_arg.into_or_undefined()],
261 context,
262 )?
263 .as_object()
264 .js_expect("Array.prototype.map should always return object")?;
265
266 Self::from_object(object)
267 }
268
269 #[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 .js_expect("Array.prototype.every should always return boolean")?;
284
285 Ok(result)
286 }
287
288 #[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 .js_expect("Array.prototype.some should always return boolean")?;
303
304 Ok(result)
305 }
306
307 #[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 #[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 .js_expect("Array.prototype.slice should always return object")?;
334
335 Self::from_object(object)
336 }
337
338 #[inline]
340 pub fn values(&self, context: &mut Context) -> JsResult<JsValue> {
341 Array::values(&self.inner.clone().into(), &[], context)
342 }
343
344 #[inline]
368 pub fn splice(
369 &self,
370 start: u32,
371 delete_count: Option<u32>,
372 items: &[JsValue],
373 context: &mut Context,
374 ) -> JsResult<Self> {
375 let start = JsValue::from(start);
376 let delete_count = delete_count.map(JsValue::from);
377 let object = Array::splice_internal(
378 &self.inner.clone().into(),
379 Some(&start),
380 delete_count.as_ref(),
381 items,
382 context,
383 )?
384 .as_object()
385 .js_expect("Array.prototype.splice should always return object")?;
386
387 Self::from_object(object)
388 }
389
390 #[inline]
392 pub fn reduce(
393 &self,
394 callback: JsFunction,
395 initial_value: Option<JsValue>,
396 context: &mut Context,
397 ) -> JsResult<JsValue> {
398 Array::reduce(
399 &self.inner.clone().into(),
400 &[callback.into(), initial_value.into_or_undefined()],
401 context,
402 )
403 }
404
405 #[inline]
407 pub fn reduce_right(
408 &self,
409 callback: JsFunction,
410 initial_value: Option<JsValue>,
411 context: &mut Context,
412 ) -> JsResult<JsValue> {
413 Array::reduce_right(
414 &self.inner.clone().into(),
415 &[callback.into(), initial_value.into_or_undefined()],
416 context,
417 )
418 }
419
420 #[inline]
422 pub fn to_reversed(&self, context: &mut Context) -> JsResult<Self> {
423 let array = Array::to_reversed(&self.inner.clone().into(), &[], context)?;
424
425 Ok(Self {
426 inner: array
427 .as_object()
428 .js_expect("`to_reversed` must always return an `Array` on success")?,
429 })
430 }
431
432 #[inline]
434 pub fn to_sorted(
435 &self,
436 compare_fn: Option<JsFunction>,
437 context: &mut Context,
438 ) -> JsResult<Self> {
439 let array = Array::to_sorted(
440 &self.inner.clone().into(),
441 &[compare_fn.into_or_undefined()],
442 context,
443 )?;
444
445 Ok(Self {
446 inner: array
447 .as_object()
448 .js_expect("`to_sorted` must always return an `Array` on success")?,
449 })
450 }
451
452 #[inline]
454 pub fn with(&self, index: u64, value: JsValue, context: &mut Context) -> JsResult<Self> {
455 let array = Array::with(&self.inner.clone().into(), &[index.into(), value], context)?;
456
457 Ok(Self {
458 inner: array
459 .as_object()
460 .js_expect("`with` must always return an `Array` on success")?,
461 })
462 }
463}
464
465impl From<JsArray> for JsObject {
466 #[inline]
467 fn from(o: JsArray) -> Self {
468 o.inner.clone()
469 }
470}
471
472impl From<JsArray> for JsValue {
473 #[inline]
474 fn from(o: JsArray) -> Self {
475 o.inner.clone().into()
476 }
477}
478
479impl Deref for JsArray {
480 type Target = JsObject;
481
482 #[inline]
483 fn deref(&self) -> &Self::Target {
484 &self.inner
485 }
486}
487
488impl TryFromJs for JsArray {
489 fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
490 if let Some(o) = value.as_object() {
491 Self::from_object(o.clone())
492 } else {
493 Err(JsNativeError::typ()
494 .with_message("value is not an Array object")
495 .into())
496 }
497 }
498}
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn splice_remove() {
505 let context = &mut Context::default();
506 let array = JsArray::from_iter([1, 2, 3].map(JsValue::from), context);
507
508 let removed = array.splice(1, Some(1), &[], context).unwrap();
509
510 assert_eq!(array.length(context).unwrap(), 2);
511 assert_eq!(removed.length(context).unwrap(), 1);
512 }
513
514 #[test]
515 fn splice_insert() {
516 let context = &mut Context::default();
517 let array = JsArray::from_iter([1, 2, 3].map(JsValue::from), context);
518
519 let removed = array
520 .splice(1, Some(0), &[JsValue::from(10), JsValue::from(20)], context)
521 .unwrap();
522
523 assert_eq!(array.length(context).unwrap(), 5);
524 assert_eq!(removed.length(context).unwrap(), 0);
525 }
526
527 #[test]
528 fn splice_replace() {
529 let context = &mut Context::default();
530 let array = JsArray::from_iter([1, 2, 3].map(JsValue::from), context);
531
532 let removed = array
533 .splice(1, Some(1), &[JsValue::from(99)], context)
534 .unwrap();
535
536 assert_eq!(array.length(context).unwrap(), 3);
537 assert_eq!(removed.length(context).unwrap(), 1);
538 }
539
540 #[test]
541 fn splice_from_start() {
542 let context = &mut Context::default();
543 let array = JsArray::from_iter([1, 2, 3].map(JsValue::from), context);
544
545 let removed = array.splice(0, Some(1), &[], context).unwrap();
546
547 assert_eq!(array.length(context).unwrap(), 2);
548 assert_eq!(removed.length(context).unwrap(), 1);
549 }
550
551 #[test]
552 fn splice_empty_array() {
553 let context = &mut Context::default();
554 let array = JsArray::new(context).unwrap();
555
556 let removed = array.splice(0, Some(0), &[], context).unwrap();
557
558 assert_eq!(array.length(context).unwrap(), 0);
559 assert_eq!(removed.length(context).unwrap(), 0);
560 }
561}