boa/syntax/ast/node/object/
mod.rs

1//! Object node.
2
3use crate::{
4    exec::Executable,
5    gc::{Finalize, Trace},
6    property::PropertyDescriptor,
7    syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition, PropertyName},
8    BoaProfiler, Context, JsResult, JsValue,
9};
10use std::fmt;
11
12#[cfg(feature = "deser")]
13use serde::{Deserialize, Serialize};
14
15#[cfg(test)]
16mod tests;
17
18/// Objects in JavaScript may be defined as an unordered collection of related data, of
19/// primitive or reference types, in the form of “key: value” pairs.
20///
21/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
22/// notation.
23///
24/// An object initializer is an expression that describes the initialization of an
25/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
26/// Values of object properties can either contain [`primitive`][primitive] data types or other
27/// objects.
28///
29/// More information:
30///  - [ECMAScript reference][spec]
31///  - [MDN documentation][mdn]
32///
33/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
34/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
35/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
36/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
37#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
38#[cfg_attr(feature = "deser", serde(transparent))]
39#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
40pub struct Object {
41    properties: Box<[PropertyDefinition]>,
42}
43
44impl Object {
45    pub fn properties(&self) -> &[PropertyDefinition] {
46        &self.properties
47    }
48
49    /// Implements the display formatting with indentation.
50    pub(in crate::syntax::ast::node) fn display(
51        &self,
52        f: &mut fmt::Formatter<'_>,
53        indent: usize,
54    ) -> fmt::Result {
55        f.write_str("{\n")?;
56        let indentation = "    ".repeat(indent + 1);
57        for property in self.properties().iter() {
58            match property {
59                PropertyDefinition::IdentifierReference(key) => {
60                    writeln!(f, "{}{},", indentation, key)?;
61                }
62                PropertyDefinition::Property(key, value) => {
63                    write!(f, "{}{}: ", indentation, key,)?;
64                    value.display_no_indent(f, indent + 1)?;
65                    writeln!(f, ",")?;
66                }
67                PropertyDefinition::SpreadObject(key) => {
68                    writeln!(f, "{}...{},", indentation, key)?;
69                }
70                PropertyDefinition::MethodDefinition(kind, key, node) => {
71                    write!(f, "{}", indentation)?;
72                    match &kind {
73                        MethodDefinitionKind::Get => write!(f, "get ")?,
74                        MethodDefinitionKind::Set => write!(f, "set ")?,
75                        MethodDefinitionKind::Ordinary => (),
76                    }
77                    write!(f, "{}(", key)?;
78                    join_nodes(f, node.parameters())?;
79                    write!(f, ") ")?;
80                    node.display_block(f, indent + 1)?;
81                    writeln!(f, ",")?;
82                }
83            }
84        }
85        write!(f, "{}}}", "    ".repeat(indent))
86    }
87}
88
89impl Executable for Object {
90    fn run(&self, context: &mut Context) -> JsResult<JsValue> {
91        let _timer = BoaProfiler::global().start_event("object", "exec");
92        let obj = JsValue::new_object(context);
93
94        // TODO: Implement the rest of the property types.
95        for property in self.properties().iter() {
96            match property {
97                PropertyDefinition::Property(name, value) => {
98                    let name = match name {
99                        PropertyName::Literal(name) => name.clone().into(),
100                        PropertyName::Computed(node) => {
101                            node.run(context)?.to_property_key(context)?
102                        }
103                    };
104                    obj.set_property(
105                        name,
106                        PropertyDescriptor::builder()
107                            .value(value.run(context)?)
108                            .writable(true)
109                            .enumerable(true)
110                            .configurable(true),
111                    );
112                }
113                PropertyDefinition::MethodDefinition(kind, name, func) => {
114                    let name = match name {
115                        PropertyName::Literal(name) => name.clone().into(),
116                        PropertyName::Computed(node) => {
117                            node.run(context)?.to_property_key(context)?
118                        }
119                    };
120                    match kind {
121                        MethodDefinitionKind::Ordinary => {
122                            obj.set_property(
123                                name,
124                                PropertyDescriptor::builder()
125                                    .value(func.run(context)?)
126                                    .writable(true)
127                                    .enumerable(true)
128                                    .configurable(true),
129                            );
130                        }
131                        MethodDefinitionKind::Get => {
132                            let set = obj
133                                .get_property(name.clone())
134                                .as_ref()
135                                .and_then(|a| a.set())
136                                .cloned();
137                            obj.set_property(
138                                name,
139                                PropertyDescriptor::builder()
140                                    .maybe_get(func.run(context)?.as_object())
141                                    .maybe_set(set)
142                                    .enumerable(true)
143                                    .configurable(true),
144                            )
145                        }
146                        MethodDefinitionKind::Set => {
147                            let get = obj
148                                .get_property(name.clone())
149                                .as_ref()
150                                .and_then(|a| a.get())
151                                .cloned();
152                            obj.set_property(
153                                name,
154                                PropertyDescriptor::builder()
155                                    .maybe_get(get)
156                                    .maybe_set(func.run(context)?.as_object())
157                                    .enumerable(true)
158                                    .configurable(true),
159                            )
160                        }
161                    }
162                }
163                // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation
164                PropertyDefinition::SpreadObject(node) => {
165                    let val = node.run(context)?;
166
167                    if val.is_null_or_undefined() {
168                        continue;
169                    }
170
171                    obj.as_object().unwrap().copy_data_properties::<String>(
172                        &val,
173                        vec![],
174                        context,
175                    )?;
176                }
177                _ => {} // unimplemented!("{:?} type of property", i),
178            }
179        }
180
181        Ok(obj)
182    }
183}
184
185impl fmt::Display for Object {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        self.display(f, 0)
188    }
189}
190
191impl<T> From<T> for Object
192where
193    T: Into<Box<[PropertyDefinition]>>,
194{
195    fn from(props: T) -> Self {
196        Self {
197            properties: props.into(),
198        }
199    }
200}
201
202impl From<Object> for Node {
203    fn from(obj: Object) -> Self {
204        Self::Object(obj)
205    }
206}