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}