1use 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#[derive(Debug, Clone, Trace, Finalize)]
14pub struct JsArray {
15 inner: JsObject,
16}
17
18impl JsArray {
19 #[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 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 #[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 #[inline]
56 pub fn length(&self, context: &mut Context) -> JsResult<u64> {
57 self.inner.length_of_array_like(context)
58 }
59
60 #[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 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 #[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 #[inline]
82 pub fn pop(&self, context: &mut Context) -> JsResult<JsValue> {
83 Array::pop(&self.inner.clone().into(), &[], context)
84 }
85
86 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 #[inline]
96 pub fn shift(&self, context: &mut Context) -> JsResult<JsValue> {
97 Array::shift(&self.inner.clone().into(), &[], context)
98 }
99
100 #[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 #[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 #[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 #[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 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 .cloned()
334 .expect("Array.prototype.slice should always return object");
335
336 Self::from_object(object)
337 }
338
339 #[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 #[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 #[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 #[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 #[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}