1use 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#[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::unshift(&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 .expect("Array.prototype.filter should always return object");
119
120 Self::from_object(object)
121 }
122
123 #[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 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}