oxc_ast/ast_impl/
jsx.rs

1//! [JSX](https://facebook.github.io/jsx)
2
3use std::fmt::{self, Display};
4
5use oxc_span::Atom;
6
7use crate::ast::*;
8
9// 1.2 JSX Elements
10
11impl Display for JSXIdentifier<'_> {
12    #[inline]
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        self.name.fmt(f)
15    }
16}
17
18impl Display for JSXNamespacedName<'_> {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "{}:{}", self.namespace.name, self.name.name)
21    }
22}
23
24impl<'a> JSXElementName<'a> {
25    /// Get this [`JSXElementName`]'s root identifier reference.
26    ///
27    /// e.g. `Foo` in `<Foo>` or `<Foo.bar>` or `<Foo.bar.qux>`.
28    ///
29    /// Returns [`None`] for any of:
30    /// * `<div>`
31    /// * `<this>`
32    /// * `<this.bar>`
33    /// * `<Foo:Bar>` - [namespaced identifiers](JSXElementName::NamespacedName)
34    pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> {
35        match self {
36            JSXElementName::Identifier(_)
37            | JSXElementName::NamespacedName(_)
38            | JSXElementName::ThisExpression(_) => None,
39            JSXElementName::IdentifierReference(ident) => Some(ident),
40            JSXElementName::MemberExpression(member_expr) => member_expr.get_identifier(),
41        }
42    }
43
44    /// Get this [`JSXElementName`]'s identifier as an [`Atom`], if it is a plain identifier
45    /// or identifier reference.
46    ///
47    /// e.g. `Foo` in `<Foo>`, or `div` in `<div>`.
48    ///
49    /// Returns [`None`] for any of:
50    /// * `<this>`
51    /// * `<Foo.bar>`
52    /// * `<Foo:Bar>` - [namespaced identifiers](JSXElementName::NamespacedName)
53    pub fn get_identifier_name(&self) -> Option<Atom<'a>> {
54        match self {
55            Self::Identifier(id) => Some(id.as_ref().name),
56            Self::IdentifierReference(id) => Some(id.as_ref().name),
57            _ => None,
58        }
59    }
60}
61
62impl<'a> JSXMemberExpression<'a> {
63    /// Get the identifier being referenced, if there is one.
64    ///
65    /// e.g. `Foo` in `<Foo.bar>` or `<Foo.bar.qux>`.
66    ///
67    /// Returns [`None`] if the root of the [`JSXMemberExpression`] is `this`
68    /// e.g. `<this.bar>` or `<this.bar.qux>`.
69    pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> {
70        self.object.get_identifier()
71    }
72}
73
74impl<'a> JSXMemberExpressionObject<'a> {
75    /// Get the identifier being referenced, if there is one.
76    ///
77    /// e.g. `Foo` in `<Foo.bar>` or `<Foo.bar.qux>`.
78    ///
79    /// Returns [`None`] if the root of the [`JSXMemberExpressionObject`] is `this`
80    /// e.g. `<this.bar>` or `<this.bar.qux>`.
81    pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> {
82        let mut object = self;
83        loop {
84            match object {
85                JSXMemberExpressionObject::IdentifierReference(ident) => return Some(ident),
86                JSXMemberExpressionObject::MemberExpression(member_expr) => {
87                    object = &member_expr.object;
88                }
89                JSXMemberExpressionObject::ThisExpression(_) => return None,
90            }
91        }
92    }
93}
94
95impl Display for JSXMemberExpression<'_> {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}.{}", self.object, self.property)
98    }
99}
100
101impl Display for JSXMemberExpressionObject<'_> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::IdentifierReference(id) => id.fmt(f),
105            Self::MemberExpression(expr) => expr.fmt(f),
106            Self::ThisExpression(_) => "this".fmt(f),
107        }
108    }
109}
110
111impl Display for JSXElementName<'_> {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self {
114            Self::Identifier(ident) => ident.fmt(f),
115            Self::IdentifierReference(ident) => ident.fmt(f),
116            Self::NamespacedName(namespaced) => namespaced.fmt(f),
117            Self::MemberExpression(member_expr) => member_expr.fmt(f),
118            Self::ThisExpression(_) => "this".fmt(f),
119        }
120    }
121}
122
123impl JSXExpression<'_> {
124    /// Determines whether the given expr is a `undefined` literal.
125    pub fn is_undefined(&self) -> bool {
126        matches!(self, Self::Identifier(ident) if ident.name == "undefined")
127    }
128}
129
130impl JSXAttribute<'_> {
131    /// Returns `true` if this attribute's name is the expected `name`.
132    ///
133    /// Use [`JSXAttribute::is_identifier_ignore_case`] if you want to ignore
134    /// upper/lower case differences.
135    pub fn is_identifier(&self, name: &str) -> bool {
136        matches!(&self.name, JSXAttributeName::Identifier(ident) if ident.name == name)
137    }
138
139    /// Returns `true` if this attribute's name is the expected `name`, ignoring casing.
140    pub fn is_identifier_ignore_case(&self, name: &str) -> bool {
141        matches!(&self.name, JSXAttributeName::Identifier(ident) if ident.name.eq_ignore_ascii_case(name))
142    }
143
144    /// Returns `true` if this is a React `key`.
145    ///
146    /// ## Example
147    /// ```tsx
148    /// <Foo key="value" /> // -> `true`
149    /// <Foo bar="value" /> // -> `false`
150    /// ```
151    pub fn is_key(&self) -> bool {
152        self.is_identifier("key")
153    }
154}
155
156impl<'a> JSXAttributeName<'a> {
157    /// Try to convert this attribute name into an [`JSXIdentifier`].
158    ///
159    /// Returns [`None`] for [namespaced names](JSXAttributeName::NamespacedName).
160    pub fn as_identifier(&self) -> Option<&JSXIdentifier<'a>> {
161        match self {
162            Self::Identifier(ident) => Some(ident.as_ref()),
163            Self::NamespacedName(_) => None,
164        }
165    }
166
167    /// Get the rightmost identifier in the attribute name.
168    ///
169    /// ## Example
170    /// ```tsx
171    /// <Foo bar={123} /> // -> `bar`
172    /// <Foo bar:qux={123} /> // -> `qux`
173    /// ```
174    pub fn get_identifier(&self) -> &JSXIdentifier<'a> {
175        match self {
176            Self::Identifier(ident) => ident.as_ref(),
177            Self::NamespacedName(namespaced) => &namespaced.name,
178        }
179    }
180}
181impl<'a> JSXAttributeValue<'a> {
182    /// Get the contained [`StringLiteral`], or [`None`] if this is some other kind of value.
183    pub fn as_string_literal(&self) -> Option<&StringLiteral<'a>> {
184        match self {
185            Self::StringLiteral(lit) => Some(lit.as_ref()),
186            _ => None,
187        }
188    }
189}
190
191impl<'a> JSXAttributeItem<'a> {
192    /// Get the contained [`JSXAttribute`] if it is an attribute item, otherwise returns [`None`].
193    ///
194    /// This is the inverse of [`JSXAttributeItem::as_spread`].
195    pub fn as_attribute(&self) -> Option<&JSXAttribute<'a>> {
196        match self {
197            Self::Attribute(attr) => Some(attr),
198            Self::SpreadAttribute(_) => None,
199        }
200    }
201
202    /// Get the contained [`JSXSpreadAttribute`] if it is a spread attribute item,
203    /// otherwise returns [`None`].
204    ///
205    /// This is the inverse of [`JSXAttributeItem::as_attribute`].
206    pub fn as_spread(&self) -> Option<&JSXSpreadAttribute<'a>> {
207        match self {
208            Self::Attribute(_) => None,
209            Self::SpreadAttribute(spread) => Some(spread),
210        }
211    }
212}
213
214impl JSXChild<'_> {
215    /// Returns `true` if this an [expression container](JSXChild::ExpressionContainer).
216    pub const fn is_expression_container(&self) -> bool {
217        matches!(self, Self::ExpressionContainer(_))
218    }
219}