Skip to main content

boa_engine/object/internal_methods/
string.rs

1use crate::{
2    Context, JsExpect, JsResult, JsString,
3    object::{JsData, JsObject},
4    property::{PropertyDescriptor, PropertyKey},
5};
6
7use super::{InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
8
9impl JsData for JsString {
10    fn internal_methods(&self) -> &'static InternalObjectMethods {
11        static METHODS: InternalObjectMethods = InternalObjectMethods {
12            __get_own_property__: string_exotic_get_own_property,
13            __define_own_property__: string_exotic_define_own_property,
14            __own_property_keys__: string_exotic_own_property_keys,
15            ..ORDINARY_INTERNAL_METHODS
16        };
17
18        &METHODS
19    }
20}
21
22/// Gets own property of 'String' exotic object
23///
24/// More information:
25///  - [ECMAScript reference][spec]
26///
27/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
28pub(crate) fn string_exotic_get_own_property(
29    obj: &JsObject,
30    key: &PropertyKey,
31    context: &mut InternalMethodPropertyContext<'_>,
32) -> JsResult<Option<PropertyDescriptor>> {
33    // 1. Assert: IsPropertyKey(P) is true.
34    // 2. Let desc be OrdinaryGetOwnProperty(S, P).
35    let desc = super::ordinary_get_own_property(obj, key, context)?;
36
37    // 3. If desc is not undefined, return desc.
38    if desc.is_some() {
39        Ok(desc)
40    } else {
41        // 4. Return ! StringGetOwnProperty(S, P).
42        Ok(string_get_own_property(obj, key))
43    }
44}
45
46/// Defines own property of 'String' exotic object
47///
48/// More information:
49///  - [ECMAScript reference][spec]
50///
51/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
52pub(crate) fn string_exotic_define_own_property(
53    obj: &JsObject,
54    key: &PropertyKey,
55    desc: PropertyDescriptor,
56    context: &mut InternalMethodPropertyContext<'_>,
57) -> JsResult<bool> {
58    // 1. Assert: IsPropertyKey(P) is true.
59    // 2. Let stringDesc be ! StringGetOwnProperty(S, P).
60    let string_desc = string_get_own_property(obj, key);
61
62    // 3. If stringDesc is not undefined, then
63    if let Some(string_desc) = string_desc {
64        // a. Let extensible be S.[[Extensible]].
65        let extensible = obj.borrow().extensible;
66        // b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc).
67        Ok(super::is_compatible_property_descriptor(
68            extensible,
69            desc,
70            Some(string_desc),
71        ))
72    } else {
73        // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).
74        super::ordinary_define_own_property(obj, key, desc, context)
75    }
76}
77
78/// Gets own property keys of 'String' exotic object
79///
80/// More information:
81///  - [ECMAScript reference][spec]
82///
83/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
84#[allow(clippy::unnecessary_wraps)]
85pub(crate) fn string_exotic_own_property_keys(
86    obj: &JsObject,
87    _context: &mut Context,
88) -> JsResult<Vec<PropertyKey>> {
89    // 2. Let str be O.[[StringData]].
90    // 3. Assert: Type(str) is String.
91    let string = obj
92        .downcast_ref::<JsString>()
93        .js_expect("string exotic method should only be callable from string objects")?
94        .clone();
95    // 4. Let len be the length of str.
96    let len = string.len();
97
98    // 1. Let keys be a new empty List.
99    let mut keys = Vec::with_capacity(len);
100
101    // 5. For each integer i starting with 0 such that i < len, in ascending order, do
102    //      a. Add ! ToString(𝔽(i)) as the last element of keys.
103    keys.extend((0..len).map(Into::into));
104
105    // 6. For each own property key P of O such that P is an array index
106    // and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do
107    //      a. Add P as the last element of keys.
108    let mut remaining_indices: Vec<_> = obj
109        .borrow()
110        .properties
111        .index_property_keys()
112        .filter(|idx| (*idx as usize) >= len)
113        .collect();
114    remaining_indices.sort_unstable();
115    keys.extend(remaining_indices.into_iter().map(Into::into));
116
117    // 7. For each own property key P of O such that Type(P) is String and P is not
118    // an array index, in ascending chronological order of property creation, do
119    //      a. Add P as the last element of keys.
120
121    // 8. For each own property key P of O such that Type(P) is Symbol, in ascending
122    // chronological order of property creation, do
123    //      a. Add P as the last element of keys.
124    keys.extend(obj.borrow().properties.shape.keys());
125
126    // 9. Return keys.
127    Ok(keys)
128}
129
130/// `StringGetOwnProperty` abstract operation
131///
132/// More information:
133///  - [ECMAScript reference][spec]
134///
135/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
136fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option<PropertyDescriptor> {
137    // 1. Assert: S is an Object that has a [[StringData]] internal slot.
138    // 2. Assert: IsPropertyKey(P) is true.
139    // 3. If Type(P) is not String, return undefined.
140    // 4. Let index be ! CanonicalNumericIndexString(P).
141    // 5. If index is undefined, return undefined.
142    // 6. If IsIntegralNumber(index) is false, return undefined.
143    // 7. If index is -0𝔽, return undefined.
144    let pos = match key {
145        PropertyKey::Index(index) => index.get() as usize,
146        _ => return None,
147    };
148
149    // 8. Let str be S.[[StringData]].
150    // 9. Assert: Type(str) is String.
151    let string = obj
152        .downcast_ref::<JsString>()
153        .expect("string exotic method should only be callable from string objects")
154        .clone();
155
156    // 10. Let len be the length of str.
157    // 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
158    // 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
159    let result_str = string.get(pos..=pos)?;
160
161    // 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
162    let desc = PropertyDescriptor::builder()
163        .value(result_str)
164        .writable(false)
165        .enumerable(true)
166        .configurable(false)
167        .build();
168
169    Some(desc)
170}