boa_ast/statement/
switch.rs

1//! Switch node.
2
3use crate::{
4    StatementList,
5    expression::Expression,
6    operations::{ContainsSymbol, contains},
7    scope::Scope,
8    statement::Statement,
9    visitor::{VisitWith, Visitor, VisitorMut},
10};
11use boa_interner::{Interner, ToIndentedString, ToInternedString};
12use core::{fmt::Write as _, ops::ControlFlow};
13
14/// A case clause inside a [`Switch`] statement, as defined by the [spec].
15///
16/// Even though every [`Case`] body is a [`StatementList`], it doesn't create a new lexical
17/// environment. This means any variable declared in a `Case` will be considered as part of the
18/// lexical environment of the parent [`Switch`] block.
19///
20/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
21/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24#[derive(Clone, Debug, PartialEq)]
25pub struct Case {
26    condition: Option<Expression>,
27    body: StatementList,
28}
29
30impl Case {
31    /// Creates a regular `Case` AST node.
32    #[inline]
33    #[must_use]
34    pub const fn new(condition: Expression, body: StatementList) -> Self {
35        Self {
36            condition: Some(condition),
37            body,
38        }
39    }
40
41    /// Creates a default `Case` AST node.
42    #[inline]
43    #[must_use]
44    pub const fn default(body: StatementList) -> Self {
45        Self {
46            condition: None,
47            body,
48        }
49    }
50
51    /// Gets the condition of the case.
52    ///
53    /// If it's a regular case returns [`Some`] with the [`Expression`],
54    /// otherwise [`None`] is returned if it's the default case.
55    #[inline]
56    #[must_use]
57    pub const fn condition(&self) -> Option<&Expression> {
58        self.condition.as_ref()
59    }
60
61    /// Gets the statement listin the body of the case.
62    #[inline]
63    #[must_use]
64    pub const fn body(&self) -> &StatementList {
65        &self.body
66    }
67
68    /// Check if the case is the `default` case.
69    #[inline]
70    #[must_use]
71    pub const fn is_default(&self) -> bool {
72        self.condition.is_none()
73    }
74}
75
76impl VisitWith for Case {
77    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
78    where
79        V: Visitor<'a>,
80    {
81        if let Some(condition) = &self.condition {
82            visitor.visit_expression(condition)?;
83        }
84
85        visitor.visit_statement_list(&self.body)
86    }
87
88    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
89    where
90        V: VisitorMut<'a>,
91    {
92        if let Some(condition) = &mut self.condition {
93            visitor.visit_expression_mut(condition)?;
94        }
95        visitor.visit_statement_list_mut(&mut self.body)
96    }
97}
98
99/// The `switch` statement evaluates an expression, matching the expression's value to a case
100/// clause, and executes statements associated with that case, as well as statements in cases
101/// that follow the matching case.
102///
103/// A `switch` statement first evaluates its expression. It then looks for the first case
104/// clause whose expression evaluates to the same value as the result of the input expression
105/// (using the strict comparison, `===`) and transfers control to that clause, executing the
106/// associated statements. (If multiple cases match the provided value, the first case that
107/// matches is selected, even if the cases are not equal to each other.)
108///
109/// More information:
110///  - [ECMAScript reference][spec]
111///  - [MDN documentation][mdn]
112///
113/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
114/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
117#[derive(Clone, Debug, PartialEq)]
118pub struct Switch {
119    pub(crate) val: Expression,
120    pub(crate) cases: Box<[Case]>,
121    pub(crate) contains_direct_eval: bool,
122
123    #[cfg_attr(feature = "serde", serde(skip))]
124    pub(crate) scope: Option<Scope>,
125}
126
127impl Switch {
128    /// Creates a `Switch` AST node.
129    #[inline]
130    #[must_use]
131    pub fn new(val: Expression, cases: Box<[Case]>) -> Self {
132        let mut contains_direct_eval = false;
133        for case in &cases {
134            contains_direct_eval |= contains(case, ContainsSymbol::DirectEval);
135        }
136        Self {
137            val,
138            cases,
139            contains_direct_eval,
140            scope: None,
141        }
142    }
143
144    /// Gets the value to switch.
145    #[inline]
146    #[must_use]
147    pub const fn val(&self) -> &Expression {
148        &self.val
149    }
150
151    /// Gets the list of cases for the switch statement.
152    #[inline]
153    #[must_use]
154    pub const fn cases(&self) -> &[Case] {
155        &self.cases
156    }
157
158    /// Gets the default statement list, if any.
159    #[inline]
160    #[must_use]
161    pub fn default(&self) -> Option<&StatementList> {
162        for case in self.cases.as_ref() {
163            if case.is_default() {
164                return Some(case.body());
165            }
166        }
167        None
168    }
169
170    /// Gets the scope of the switch statement.
171    #[inline]
172    #[must_use]
173    pub const fn scope(&self) -> Option<&Scope> {
174        self.scope.as_ref()
175    }
176}
177
178impl ToIndentedString for Switch {
179    fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
180        let indent = "    ".repeat(indentation);
181        let mut buf = format!("switch ({}) {{\n", self.val().to_interned_string(interner));
182        for e in &*self.cases {
183            if let Some(condition) = e.condition() {
184                let _ = write!(
185                    buf,
186                    "{indent}    case {}:\n{}",
187                    condition.to_interned_string(interner),
188                    e.body().to_indented_string(interner, indentation + 2)
189                );
190            } else {
191                let _ = write!(
192                    buf,
193                    "{indent}    default:\n{}",
194                    e.body().to_indented_string(interner, indentation + 2)
195                );
196            }
197        }
198
199        let _ = write!(buf, "{indent}}}");
200
201        buf
202    }
203}
204
205impl From<Switch> for Statement {
206    #[inline]
207    fn from(switch: Switch) -> Self {
208        Self::Switch(switch)
209    }
210}
211
212impl VisitWith for Switch {
213    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
214    where
215        V: Visitor<'a>,
216    {
217        visitor.visit_expression(&self.val)?;
218        for case in &*self.cases {
219            visitor.visit_case(case)?;
220        }
221        ControlFlow::Continue(())
222    }
223
224    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
225    where
226        V: VisitorMut<'a>,
227    {
228        visitor.visit_expression_mut(&mut self.val)?;
229        for case in &mut *self.cases {
230            visitor.visit_case_mut(case)?;
231        }
232        ControlFlow::Continue(())
233    }
234}