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                                if let Some(name) = name.literal() {
135                                    if name.sym() == ident.sym() {
136                                        if strict && name == Sym::EVAL {
137                                            return None;
138                                        }
139                                        if strict
140                                            && RESERVED_IDENTIFIERS_STRICT.contains(&name.sym())
141                                        {
142                                            return None;
143                                        }
144                                    }
145                                    let mut init = assign.rhs().clone();
146                                    init.set_anonymous_function_definition_name(ident);
147                                    bindings.push(ObjectPatternElement::SingleName {
148                                        ident: *ident,
149                                        name: PropertyName::Literal(name),
150                                        default_init: Some(init),
151                                    });
152                                } else {
153                                    return None;
154                                }
155                            }
156                            AssignTarget::Pattern(pattern) => {
157                                bindings.push(ObjectPatternElement::Pattern {
158                                    name: name.clone(),
159                                    pattern: pattern.clone(),
160                                    default_init: Some(assign.rhs().clone()),
161                                });
162                            }
163                            AssignTarget::Access(access) => {
164                                bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
165                                    name: name.clone(),
166                                    access: access.clone(),
167                                    default_init: Some(assign.rhs().clone()),
168                                });
169                            }
170                        }
171                    }
172                    (_, Expression::PropertyAccess(access)) => {
173                        bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
174                            name: name.clone(),
175                            access: access.clone(),
176                            default_init: None,
177                        });
178                    }
179                    (PropertyName::Computed(name), Expression::Identifier(ident)) => {
180                        bindings.push(ObjectPatternElement::SingleName {
181                            ident: *ident,
182                            name: PropertyName::Computed(name.clone()),
183                            default_init: None,
184                        });
185                    }
186                    _ => return None,
187                },
188                PropertyDefinition::SpreadObject(spread) => {
189                    match spread {
190                        Expression::Identifier(ident) => {
191                            bindings.push(ObjectPatternElement::RestProperty { ident: *ident });
192                        }
193                        Expression::PropertyAccess(access) => {
194                            bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
195                                access: access.clone(),
196                            });
197                        }
198                        _ => return None,
199                    }
200                    if i + 1 != self.properties.len() {
201                        return None;
202                    }
203                }
204                PropertyDefinition::MethodDefinition(_) => return None,
205                PropertyDefinition::CoverInitializedName(ident, expr) => {
206                    if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
207                        return None;
208                    }
209                    let mut expr = expr.clone();
210                    expr.set_anonymous_function_definition_name(ident);
211                    bindings.push(ObjectPatternElement::SingleName {
212                        ident: *ident,
213                        name: PropertyName::Literal(*ident),
214                        default_init: Some(expr),
215                    });
216                }
217            }
218        }
219
220        Some(ObjectPattern::new(bindings.into(), self.span))
221    }
222}
223
224impl Spanned for ObjectLiteral {
225    #[inline]
226    fn span(&self) -> Span {
227        self.span
228    }
229}
230
231impl ToIndentedString for ObjectLiteral {
232    fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
233        let mut buf = "{\n".to_owned();
234        let indentation = "    ".repeat(indent_n + 1);
235        for property in &*self.properties {
236            match property {
237                PropertyDefinition::IdentifierReference(ident) => {
238                    let _ = writeln!(
239                        buf,
240                        "{indentation}{},",
241                        interner.resolve_expect(ident.sym())
242                    );
243                }
244                PropertyDefinition::Property(key, value) => {
245                    let _ = writeln!(
246                        buf,
247                        "{indentation}{}: {},",
248                        key.to_interned_string(interner),
249                        value.to_no_indent_string(interner, indent_n + 1)
250                    );
251                }
252                PropertyDefinition::SpreadObject(key) => {
253                    let _ = writeln!(buf, "{indentation}...{},", key.to_interned_string(interner));
254                }
255                PropertyDefinition::MethodDefinition(m) => {
256                    buf.push_str(&m.to_indented_string(interner, indent_n));
257                }
258                PropertyDefinition::CoverInitializedName(ident, expr) => {
259                    let _ = writeln!(
260                        buf,
261                        "{indentation}{} = {},",
262                        interner.resolve_expect(ident.sym()),
263                        expr.to_no_indent_string(interner, indent_n + 1)
264                    );
265                }
266            }
267        }
268        let _ = write!(buf, "{}}}", "    ".repeat(indent_n));
269
270        buf
271    }
272}
273
274impl From<ObjectLiteral> for Expression {
275    #[inline]
276    fn from(obj: ObjectLiteral) -> Self {
277        Self::ObjectLiteral(obj)
278    }
279}
280
281impl VisitWith for ObjectLiteral {
282    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
283    where
284        V: Visitor<'a>,
285    {
286        for pd in &*self.properties {
287            visitor.visit_property_definition(pd)?;
288        }
289        ControlFlow::Continue(())
290    }
291
292    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
293    where
294        V: VisitorMut<'a>,
295    {
296        for pd in &mut *self.properties {
297            visitor.visit_property_definition_mut(pd)?;
298        }
299        ControlFlow::Continue(())
300    }
301}
302
303/// Describes the definition of a property within an object literal.
304///
305/// A property has a name (a string) and a value (primitive, method, or object reference).
306/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference".
307/// This distinction matters because the original referenced object remains unchanged when you change the property's value.
308///
309/// More information:
310///  - [ECMAScript reference][spec]
311///  - [MDN documentation][mdn]
312///
313/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
314/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
315// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition
316#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
317#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
318#[derive(Clone, Debug, PartialEq)]
319pub enum PropertyDefinition {
320    /// Puts a variable into an object.
321    ///
322    /// More information:
323    ///  - [ECMAScript reference][spec]
324    ///  - [MDN documentation][mdn]
325    ///
326    /// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference
327    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
328    IdentifierReference(Identifier),
329
330    /// Binds a property name to a JavaScript value.
331    ///
332    /// More information:
333    ///  - [ECMAScript reference][spec]
334    ///  - [MDN documentation][mdn]
335    ///
336    /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
337    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
338    Property(PropertyName, Expression),
339
340    /// A property of an object can also refer to a function or a getter or setter method.
341    ///
342    /// More information:
343    ///  - [ECMAScript reference][spec]
344    ///  - [MDN documentation][mdn]
345    ///
346    /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
347    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions
348    MethodDefinition(ObjectMethodDefinition),
349
350    /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals.
351    /// It copies own enumerable properties from a provided object onto a new object.
352    ///
353    /// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`.
354    ///
355    /// More information:
356    ///  - [ECMAScript reference][spec]
357    ///  - [MDN documentation][mdn]
358    ///
359    /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
360    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties
361    SpreadObject(Expression),
362
363    /// Cover grammar for when an object literal is used as an object binding pattern.
364    ///
365    /// More information:
366    ///  - [ECMAScript reference][spec]
367    ///
368    /// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName
369    CoverInitializedName(Identifier, Expression),
370}
371
372impl VisitWith for PropertyDefinition {
373    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
374    where
375        V: Visitor<'a>,
376    {
377        match self {
378            Self::IdentifierReference(id) => visitor.visit_identifier(id),
379            Self::Property(pn, expr) => {
380                visitor.visit_property_name(pn)?;
381                visitor.visit_expression(expr)
382            }
383            Self::MethodDefinition(m) => visitor.visit_object_method_definition(m),
384            Self::SpreadObject(expr) => visitor.visit_expression(expr),
385            Self::CoverInitializedName(id, expr) => {
386                visitor.visit_identifier(id)?;
387                visitor.visit_expression(expr)
388            }
389        }
390    }
391
392    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
393    where
394        V: VisitorMut<'a>,
395    {
396        match self {
397            Self::IdentifierReference(id) => visitor.visit_identifier_mut(id),
398            Self::Property(pn, expr) => {
399                visitor.visit_property_name_mut(pn)?;
400                visitor.visit_expression_mut(expr)
401            }
402            Self::MethodDefinition(m) => visitor.visit_object_method_definition_mut(m),
403            Self::SpreadObject(expr) => visitor.visit_expression_mut(expr),
404            Self::CoverInitializedName(id, expr) => {
405                visitor.visit_identifier_mut(id)?;
406                visitor.visit_expression_mut(expr)
407            }
408        }
409    }
410}
411
412/// A method definition.
413///
414/// This type is specific to object method definitions.
415///
416/// More information:
417///  - [ECMAScript reference][spec]
418///
419/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
420#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
421#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
422#[derive(Clone, Debug, PartialEq)]
423pub struct ObjectMethodDefinition {
424    pub(crate) name: PropertyName,
425    pub(crate) parameters: FormalParameterList,
426    pub(crate) body: FunctionBody,
427    pub(crate) contains_direct_eval: bool,
428    kind: MethodDefinitionKind,
429
430    #[cfg_attr(feature = "serde", serde(skip))]
431    pub(crate) scopes: FunctionScopes,
432    linear_span: LinearSpanIgnoreEq,
433}
434
435impl ObjectMethodDefinition {
436    /// Creates a new object method definition.
437    #[inline]
438    #[must_use]
439    pub fn new(
440        name: PropertyName,
441        parameters: FormalParameterList,
442        body: FunctionBody,
443        kind: MethodDefinitionKind,
444        start_linear_pos: LinearPosition,
445    ) -> Self {
446        let contains_direct_eval = contains(&parameters, ContainsSymbol::DirectEval)
447            || contains(&body, ContainsSymbol::DirectEval);
448        let linear_span = LinearSpan::new(start_linear_pos, body.linear_pos_end()).into();
449
450        Self {
451            name,
452            parameters,
453            body,
454            contains_direct_eval,
455            kind,
456            scopes: FunctionScopes::default(),
457            linear_span,
458        }
459    }
460
461    /// Returns the name of the object method definition.
462    #[inline]
463    #[must_use]
464    pub const fn name(&self) -> &PropertyName {
465        &self.name
466    }
467
468    /// Returns the parameters of the object method definition.
469    #[inline]
470    #[must_use]
471    pub const fn parameters(&self) -> &FormalParameterList {
472        &self.parameters
473    }
474
475    /// Returns the body of the object method definition.
476    #[inline]
477    #[must_use]
478    pub const fn body(&self) -> &FunctionBody {
479        &self.body
480    }
481
482    /// Returns the kind of the object method definition.
483    #[inline]
484    #[must_use]
485    pub const fn kind(&self) -> MethodDefinitionKind {
486        self.kind
487    }
488
489    /// Gets the scopes of the object method definition.
490    #[inline]
491    #[must_use]
492    pub const fn scopes(&self) -> &FunctionScopes {
493        &self.scopes
494    }
495
496    /// Gets linear span of the function declaration.
497    #[inline]
498    #[must_use]
499    pub const fn linear_span(&self) -> LinearSpan {
500        self.linear_span.0
501    }
502
503    /// Returns `true` if the object method definition contains a direct call to `eval`.
504    #[inline]
505    #[must_use]
506    pub const fn contains_direct_eval(&self) -> bool {
507        self.contains_direct_eval
508    }
509}
510
511impl ToIndentedString for ObjectMethodDefinition {
512    fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
513        let indentation = "    ".repeat(indent_n + 1);
514        let prefix = match &self.kind {
515            MethodDefinitionKind::Get => "get ",
516            MethodDefinitionKind::Set => "set ",
517            MethodDefinitionKind::Ordinary => "",
518            MethodDefinitionKind::Generator => "*",
519            MethodDefinitionKind::AsyncGenerator => "async *",
520            MethodDefinitionKind::Async => "async ",
521        };
522        let name = self.name.to_interned_string(interner);
523        let parameters = join_nodes(interner, self.parameters.as_ref());
524        let body = block_to_string(&self.body.statements, interner, indent_n + 1);
525        format!("{indentation}{prefix}{name}({parameters}) {body},\n")
526    }
527}
528
529impl VisitWith for ObjectMethodDefinition {
530    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
531    where
532        V: Visitor<'a>,
533    {
534        visitor.visit_property_name(&self.name)?;
535        visitor.visit_formal_parameter_list(&self.parameters)?;
536        visitor.visit_function_body(&self.body)
537    }
538
539    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
540    where
541        V: VisitorMut<'a>,
542    {
543        visitor.visit_property_name_mut(&mut self.name)?;
544        visitor.visit_formal_parameter_list_mut(&mut self.parameters)?;
545        visitor.visit_function_body_mut(&mut self.body)
546    }
547}