Skip to main content

boa_ast/expression/literal/
object.rs

1//! Object Expression.
2
3use crate::{
4    LinearPosition, LinearSpan, LinearSpanIgnoreEq, Span, Spanned, block_to_string,
5    expression::{
6        Expression, Identifier, RESERVED_IDENTIFIERS_STRICT,
7        operator::assign::{AssignOp, AssignTarget},
8    },
9    function::{FormalParameterList, FunctionBody},
10    join_nodes,
11    operations::{ContainsSymbol, contains},
12    pattern::{ObjectPattern, ObjectPatternElement},
13    property::{MethodDefinitionKind, PropertyName},
14    scope::FunctionScopes,
15    visitor::{VisitWith, Visitor, VisitorMut},
16};
17use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
18use core::{fmt::Write as _, ops::ControlFlow};
19
20/// Objects in ECMAScript may be defined as an unordered collection of related data, of
21/// primitive or reference types, in the form of “key: value” pairs.
22///
23/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
24/// notation.
25///
26/// An object initializer is an expression that describes the initialization of an
27/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
28/// Values of object properties can either contain [`primitive`][primitive] data types or other
29/// objects.
30///
31/// More information:
32///  - [ECMAScript reference][spec]
33///  - [MDN documentation][mdn]
34///
35/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
36/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
37/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
38/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
41#[derive(Clone, Debug, PartialEq)]
42pub struct ObjectLiteral {
43    properties: Box<[PropertyDefinition]>,
44    span: Span,
45}
46
47impl ObjectLiteral {
48    /// Create a new [`ObjectLiteral`].
49    #[inline]
50    #[must_use]
51    pub fn new<T>(properties: T, span: Span) -> Self
52    where
53        T: Into<Box<[PropertyDefinition]>>,
54    {
55        Self {
56            properties: properties.into(),
57            span,
58        }
59    }
60
61    /// Gets the object literal properties
62    #[inline]
63    #[must_use]
64    pub const fn properties(&self) -> &[PropertyDefinition] {
65        &self.properties
66    }
67
68    /// Converts the object literal into an [`ObjectPattern`].
69    #[must_use]
70    pub fn to_pattern(&self, strict: bool) -> Option<ObjectPattern> {
71        let mut bindings = Vec::new();
72        for (i, property) in self.properties.iter().enumerate() {
73            match property {
74                PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
75                    return None;
76                }
77                PropertyDefinition::IdentifierReference(ident) => {
78                    if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) {
79                        return None;
80                    }
81
82                    bindings.push(ObjectPatternElement::SingleName {
83                        ident: *ident,
84                        name: PropertyName::Literal(*ident),
85                        default_init: None,
86                    });
87                }
88                PropertyDefinition::Property(name, expr) => match (name, expr) {
89                    (PropertyName::Literal(name), Expression::Identifier(ident))
90                        if name.sym() == ident.sym() =>
91                    {
92                        if strict && *name == Sym::EVAL {
93                            return None;
94                        }
95                        if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name.sym()) {
96                            return None;
97                        }
98
99                        bindings.push(ObjectPatternElement::SingleName {
100                            ident: *ident,
101                            name: PropertyName::Literal(*name),
102                            default_init: None,
103                        });
104                    }
105                    (PropertyName::Literal(name), Expression::Identifier(ident)) => {
106                        bindings.push(ObjectPatternElement::SingleName {
107                            ident: *ident,
108                            name: PropertyName::Literal(*name),
109                            default_init: None,
110                        });
111                    }
112                    (PropertyName::Literal(name), Expression::ObjectLiteral(object)) => {
113                        let pattern = object.to_pattern(strict)?.into();
114                        bindings.push(ObjectPatternElement::Pattern {
115                            name: PropertyName::Literal(*name),
116                            pattern,
117                            default_init: None,
118                        });
119                    }
120                    (PropertyName::Literal(name), Expression::ArrayLiteral(array)) => {
121                        let pattern = array.to_pattern(strict)?.into();
122                        bindings.push(ObjectPatternElement::Pattern {
123                            name: PropertyName::Literal(*name),
124                            pattern,
125                            default_init: None,
126                        });
127                    }
128                    (_, Expression::Assign(assign)) => {
129                        if assign.op() != AssignOp::Assign {
130                            return None;
131                        }
132                        match assign.lhs() {
133                            AssignTarget::Identifier(ident) => {
134                                let name = name.literal()?;
135                                if name.sym() == ident.sym() {
136                                    if strict && name == Sym::EVAL {
137                                        return None;
138                                    }
139                                    if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name.sym()) {
140                                        return None;
141                                    }
142                                }
143                                let mut init = assign.rhs().clone();
144                                init.set_anonymous_function_definition_name(ident);
145                                bindings.push(ObjectPatternElement::SingleName {
146                                    ident: *ident,
147                                    name: PropertyName::Literal(name),
148                                    default_init: Some(init),
149                                });
150                            }
151                            AssignTarget::Pattern(pattern) => {
152                                bindings.push(ObjectPatternElement::Pattern {
153                                    name: name.clone(),
154                                    pattern: pattern.clone(),
155                                    default_init: Some(assign.rhs().clone()),
156                                });
157                            }
158                            AssignTarget::Access(access) => {
159                                bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
160                                    name: name.clone(),
161                                    access: access.clone(),
162                                    default_init: Some(assign.rhs().clone()),
163                                });
164                            }
165                        }
166                    }
167                    (_, Expression::PropertyAccess(access)) => {
168                        bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
169                            name: name.clone(),
170                            access: access.clone(),
171                            default_init: None,
172                        });
173                    }
174                    (PropertyName::Computed(name), Expression::Identifier(ident)) => {
175                        bindings.push(ObjectPatternElement::SingleName {
176                            ident: *ident,
177                            name: PropertyName::Computed(name.clone()),
178                            default_init: None,
179                        });
180                    }
181                    _ => return None,
182                },
183                PropertyDefinition::SpreadObject(spread) => {
184                    match spread {
185                        Expression::Identifier(ident) => {
186                            bindings.push(ObjectPatternElement::RestProperty { ident: *ident });
187                        }
188                        Expression::PropertyAccess(access) => {
189                            bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
190                                access: access.clone(),
191                            });
192                        }
193                        _ => return None,
194                    }
195                    if i + 1 != self.properties.len() {
196                        return None;
197                    }
198                }
199                PropertyDefinition::MethodDefinition(_) => return None,
200                PropertyDefinition::CoverInitializedName(ident, expr) => {
201                    if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
202                        return None;
203                    }
204                    let mut expr = expr.clone();
205                    expr.set_anonymous_function_definition_name(ident);
206                    bindings.push(ObjectPatternElement::SingleName {
207                        ident: *ident,
208                        name: PropertyName::Literal(*ident),
209                        default_init: Some(expr),
210                    });
211                }
212            }
213        }
214
215        Some(ObjectPattern::new(bindings.into(), self.span))
216    }
217}
218
219impl Spanned for ObjectLiteral {
220    #[inline]
221    fn span(&self) -> Span {
222        self.span
223    }
224}
225
226impl ToIndentedString for ObjectLiteral {
227    fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
228        let mut buf = "{\n".to_owned();
229        let indentation = "    ".repeat(indent_n + 1);
230        for property in &*self.properties {
231            match property {
232                PropertyDefinition::IdentifierReference(ident) => {
233                    let _ = writeln!(
234                        buf,
235                        "{indentation}{},",
236                        interner.resolve_expect(ident.sym())
237                    );
238                }
239                PropertyDefinition::Property(key, value) => {
240                    let _ = writeln!(
241                        buf,
242                        "{indentation}{}: {},",
243                        key.to_interned_string(interner),
244                        value.to_no_indent_string(interner, indent_n + 1)
245                    );
246                }
247                PropertyDefinition::SpreadObject(key) => {
248                    let _ = writeln!(buf, "{indentation}...{},", key.to_interned_string(interner));
249                }
250                PropertyDefinition::MethodDefinition(m) => {
251                    buf.push_str(&m.to_indented_string(interner, indent_n));
252                }
253                PropertyDefinition::CoverInitializedName(ident, expr) => {
254                    let _ = writeln!(
255                        buf,
256                        "{indentation}{} = {},",
257                        interner.resolve_expect(ident.sym()),
258                        expr.to_no_indent_string(interner, indent_n + 1)
259                    );
260                }
261            }
262        }
263        let _ = write!(buf, "{}}}", "    ".repeat(indent_n));
264
265        buf
266    }
267}
268
269impl From<ObjectLiteral> for Expression {
270    #[inline]
271    fn from(obj: ObjectLiteral) -> Self {
272        Self::ObjectLiteral(obj)
273    }
274}
275
276impl VisitWith for ObjectLiteral {
277    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
278    where
279        V: Visitor<'a>,
280    {
281        for pd in &*self.properties {
282            visitor.visit_property_definition(pd)?;
283        }
284        ControlFlow::Continue(())
285    }
286
287    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
288    where
289        V: VisitorMut<'a>,
290    {
291        for pd in &mut *self.properties {
292            visitor.visit_property_definition_mut(pd)?;
293        }
294        ControlFlow::Continue(())
295    }
296}
297
298/// Describes the definition of a property within an object literal.
299///
300/// A property has a name (a string) and a value (primitive, method, or object reference).
301/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference".
302/// This distinction matters because the original referenced object remains unchanged when you change the property's value.
303///
304/// More information:
305///  - [ECMAScript reference][spec]
306///  - [MDN documentation][mdn]
307///
308/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
309/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
310// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
313#[derive(Clone, Debug, PartialEq)]
314pub enum PropertyDefinition {
315    /// Puts a variable into an object.
316    ///
317    /// More information:
318    ///  - [ECMAScript reference][spec]
319    ///  - [MDN documentation][mdn]
320    ///
321    /// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference
322    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
323    IdentifierReference(Identifier),
324
325    /// Binds a property name to a JavaScript value.
326    ///
327    /// More information:
328    ///  - [ECMAScript reference][spec]
329    ///  - [MDN documentation][mdn]
330    ///
331    /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
332    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
333    Property(PropertyName, Expression),
334
335    /// A property of an object can also refer to a function or a getter or setter method.
336    ///
337    /// More information:
338    ///  - [ECMAScript reference][spec]
339    ///  - [MDN documentation][mdn]
340    ///
341    /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
342    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions
343    MethodDefinition(ObjectMethodDefinition),
344
345    /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals.
346    /// It copies own enumerable properties from a provided object onto a new object.
347    ///
348    /// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`.
349    ///
350    /// More information:
351    ///  - [ECMAScript reference][spec]
352    ///  - [MDN documentation][mdn]
353    ///
354    /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
355    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties
356    SpreadObject(Expression),
357
358    /// Cover grammar for when an object literal is used as an object binding pattern.
359    ///
360    /// More information:
361    ///  - [ECMAScript reference][spec]
362    ///
363    /// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName
364    CoverInitializedName(Identifier, Expression),
365}
366
367impl VisitWith for PropertyDefinition {
368    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
369    where
370        V: Visitor<'a>,
371    {
372        match self {
373            Self::IdentifierReference(id) => visitor.visit_identifier(id),
374            Self::Property(pn, expr) => {
375                visitor.visit_property_name(pn)?;
376                visitor.visit_expression(expr)
377            }
378            Self::MethodDefinition(m) => visitor.visit_object_method_definition(m),
379            Self::SpreadObject(expr) => visitor.visit_expression(expr),
380            Self::CoverInitializedName(id, expr) => {
381                visitor.visit_identifier(id)?;
382                visitor.visit_expression(expr)
383            }
384        }
385    }
386
387    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
388    where
389        V: VisitorMut<'a>,
390    {
391        match self {
392            Self::IdentifierReference(id) => visitor.visit_identifier_mut(id),
393            Self::Property(pn, expr) => {
394                visitor.visit_property_name_mut(pn)?;
395                visitor.visit_expression_mut(expr)
396            }
397            Self::MethodDefinition(m) => visitor.visit_object_method_definition_mut(m),
398            Self::SpreadObject(expr) => visitor.visit_expression_mut(expr),
399            Self::CoverInitializedName(id, expr) => {
400                visitor.visit_identifier_mut(id)?;
401                visitor.visit_expression_mut(expr)
402            }
403        }
404    }
405}
406
407/// A method definition.
408///
409/// This type is specific to object method definitions.
410///
411/// More information:
412///  - [ECMAScript reference][spec]
413///
414/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
415#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
416#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
417#[derive(Clone, Debug, PartialEq)]
418pub struct ObjectMethodDefinition {
419    pub(crate) name: PropertyName,
420    pub(crate) parameters: FormalParameterList,
421    pub(crate) body: FunctionBody,
422    pub(crate) contains_direct_eval: bool,
423    kind: MethodDefinitionKind,
424
425    #[cfg_attr(feature = "serde", serde(skip))]
426    pub(crate) scopes: FunctionScopes,
427    linear_span: LinearSpanIgnoreEq,
428}
429
430impl ObjectMethodDefinition {
431    /// Creates a new object method definition.
432    #[inline]
433    #[must_use]
434    pub fn new(
435        name: PropertyName,
436        parameters: FormalParameterList,
437        body: FunctionBody,
438        kind: MethodDefinitionKind,
439        start_linear_pos: LinearPosition,
440    ) -> Self {
441        let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
442            || contains(&body, ContainsSymbol::DirectEval);
443        let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()).into();
444
445        Self {
446            name,
447            parameters,
448            body,
449            contains_direct_eval,
450            kind,
451            scopes: FunctionScopes::default(),
452            linear_span,
453        }
454    }
455
456    /// Returns the name of the object method definition.
457    #[inline]
458    #[must_use]
459    pub const fn name(&self) -> &PropertyName {
460        &self.name
461    }
462
463    /// Returns the parameters of the object method definition.
464    #[inline]
465    #[must_use]
466    pub const fn parameters(&self) -> &FormalParameterList {
467        &self.parameters
468    }
469
470    /// Returns the body of the object method definition.
471    #[inline]
472    #[must_use]
473    pub const fn body(&self) -> &FunctionBody {
474        &self.body
475    }
476
477    /// Returns the kind of the object method definition.
478    #[inline]
479    #[must_use]
480    pub const fn kind(&self) -> MethodDefinitionKind {
481        self.kind
482    }
483
484    /// Gets the scopes of the object method definition.
485    #[inline]
486    #[must_use]
487    pub const fn scopes(&self) -> &FunctionScopes {
488        &self.scopes
489    }
490
491    /// Gets linear span of the function declaration.
492    #[inline]
493    #[must_use]
494    pub const fn linear_span(&self) -> LinearSpan {
495        self.linear_span.0
496    }
497
498    /// Returns `true` if the object method definition contains a direct call to `eval`.
499    #[inline]
500    #[must_use]
501    pub const fn contains_direct_eval(&self) -> bool {
502        self.contains_direct_eval
503    }
504}
505
506impl ToIndentedString for ObjectMethodDefinition {
507    fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
508        let indentation = "    ".repeat(indent_n + 1);
509        let prefix = match &self.kind {
510            MethodDefinitionKind::Get => "get ",
511            MethodDefinitionKind::Set => "set ",
512            MethodDefinitionKind::Ordinary => "",
513            MethodDefinitionKind::Generator => "*",
514            MethodDefinitionKind::AsyncGenerator => "async *",
515            MethodDefinitionKind::Async => "async ",
516        };
517        let name = self.name.to_interned_string(interner);
518        let parameters = join_nodes(interner, self.parameters.as_ref());
519        let body = block_to_string(&self.body.statements, interner, indent_n + 1);
520        format!("{indentation}{prefix}{name}({parameters}) {body},\n")
521    }
522}
523
524impl VisitWith for ObjectMethodDefinition {
525    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
526    where
527        V: Visitor<'a>,
528    {
529        visitor.visit_property_name(&self.name)?;
530        visitor.visit_formal_parameter_list(&self.parameters)?;
531        visitor.visit_function_body(&self.body)
532    }
533
534    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
535    where
536        V: VisitorMut<'a>,
537    {
538        visitor.visit_property_name_mut(&mut self.name)?;
539        visitor.visit_formal_parameter_list_mut(&mut self.parameters)?;
540        visitor.visit_function_body_mut(&mut self.body)
541    }
542}