swc_estree_compat/babelify/
jsx.rs

1use copyless::BoxHelper;
2use swc_common::{BytePos, Span, Spanned};
3use swc_ecma_ast::{
4    JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXClosingElement, JSXClosingFragment,
5    JSXElement, JSXElementChild, JSXElementName, JSXEmptyExpr, JSXExpr, JSXExprContainer,
6    JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXOpeningElement,
7    JSXOpeningFragment, JSXSpreadChild, JSXText, Lit,
8};
9use swc_estree_ast::{
10    flavor::Flavor, JSXAttrName as BabelJSXAttrName, JSXAttrVal, JSXAttribute,
11    JSXClosingElement as BabelJSXClosingElement, JSXClosingFragment as BabelJSXClosingFragment,
12    JSXElement as BabelJSXElement, JSXElementChild as BabelJSXElementChild,
13    JSXElementName as BabelJSXElementName, JSXEmptyExpression, JSXExprContainerExpr,
14    JSXExpressionContainer, JSXFragment as BabelJSXFragment, JSXMemberExprObject,
15    JSXMemberExpression, JSXNamespacedName as BabelJSXNamespacedName, JSXOpeningElAttr,
16    JSXOpeningElement as BabelJSXOpeningElement, JSXOpeningFragment as BabelJSXOpeningFragment,
17    JSXSpreadAttribute, JSXSpreadChild as BabelJSXSpreadChild, JSXText as BabelJSXText,
18};
19
20use crate::babelify::{Babelify, Context};
21
22impl Babelify for JSXObject {
23    type Output = JSXMemberExprObject;
24
25    fn babelify(self, ctx: &Context) -> Self::Output {
26        match self {
27            JSXObject::JSXMemberExpr(e) => JSXMemberExprObject::Expr(e.babelify(ctx)),
28            JSXObject::Ident(i) => JSXMemberExprObject::Id(i.babelify(ctx).into()),
29            #[cfg(swc_ast_unknown)]
30            _ => panic!("unable to access unknown nodes"),
31        }
32    }
33}
34
35impl Babelify for JSXMemberExpr {
36    type Output = JSXMemberExpression;
37
38    fn babelify(self, ctx: &Context) -> Self::Output {
39        JSXMemberExpression {
40            base: ctx.base(self.span()),
41            object: Box::alloc().init(self.obj.babelify(ctx)),
42            property: self.prop.babelify(ctx).into(),
43        }
44    }
45}
46
47impl Babelify for JSXNamespacedName {
48    type Output = BabelJSXNamespacedName;
49
50    fn babelify(self, ctx: &Context) -> Self::Output {
51        BabelJSXNamespacedName {
52            base: ctx.base(self.span()),
53            namespace: self.ns.babelify(ctx).into(),
54            name: self.name.babelify(ctx).into(),
55        }
56    }
57}
58
59impl Babelify for JSXEmptyExpr {
60    type Output = JSXEmptyExpression;
61
62    fn babelify(self, ctx: &Context) -> Self::Output {
63        JSXEmptyExpression {
64            base: ctx.base(self.span),
65        }
66    }
67}
68
69impl Babelify for JSXExprContainer {
70    type Output = JSXExpressionContainer;
71
72    fn babelify(self, ctx: &Context) -> Self::Output {
73        JSXExpressionContainer {
74            base: ctx.base(self.span),
75            expression: self.expr.babelify(ctx),
76        }
77    }
78}
79
80impl Babelify for JSXExpr {
81    type Output = JSXExprContainerExpr;
82
83    fn babelify(self, ctx: &Context) -> Self::Output {
84        match self {
85            JSXExpr::JSXEmptyExpr(e) => JSXExprContainerExpr::Empty(e.babelify(ctx)),
86            JSXExpr::Expr(e) => {
87                JSXExprContainerExpr::Expr(Box::alloc().init(e.babelify(ctx).into()))
88            }
89            #[cfg(swc_ast_unknown)]
90            _ => panic!("unable to access unknown nodes"),
91        }
92    }
93}
94
95impl Babelify for JSXSpreadChild {
96    type Output = BabelJSXSpreadChild;
97
98    fn babelify(self, ctx: &Context) -> Self::Output {
99        BabelJSXSpreadChild {
100            base: ctx.base(self.span),
101            expression: Box::alloc().init(self.expr.babelify(ctx).into()),
102        }
103    }
104}
105
106impl Babelify for JSXElementName {
107    type Output = BabelJSXElementName;
108
109    fn babelify(self, ctx: &Context) -> Self::Output {
110        match self {
111            JSXElementName::Ident(i) => BabelJSXElementName::Id(i.babelify(ctx).into()),
112            JSXElementName::JSXMemberExpr(e) => BabelJSXElementName::Expr(e.babelify(ctx)),
113            JSXElementName::JSXNamespacedName(n) => BabelJSXElementName::Name(n.babelify(ctx)),
114            #[cfg(swc_ast_unknown)]
115            _ => panic!("unable to access unknown nodes"),
116        }
117    }
118}
119
120impl Babelify for JSXOpeningElement {
121    type Output = BabelJSXOpeningElement;
122
123    fn babelify(self, ctx: &Context) -> Self::Output {
124        BabelJSXOpeningElement {
125            base: ctx.base(self.span),
126            name: self.name.babelify(ctx),
127            attributes: self.attrs.babelify(ctx),
128            self_closing: self.self_closing,
129            type_parameters: self.type_args.map(|arg| arg.babelify(ctx).into()),
130        }
131    }
132}
133
134impl Babelify for JSXAttrOrSpread {
135    type Output = JSXOpeningElAttr;
136
137    fn babelify(self, ctx: &Context) -> Self::Output {
138        match self {
139            JSXAttrOrSpread::JSXAttr(a) => JSXOpeningElAttr::Attr(a.babelify(ctx)),
140            JSXAttrOrSpread::SpreadElement(spread) => {
141                // For JSX spread elements, babel includes the curly braces, and swc
142                // does not. So we extend the span to include the braces here.
143                let span = extend_spread_span_to_braces(spread.span(), ctx);
144                JSXOpeningElAttr::Spread(JSXSpreadAttribute {
145                    base: ctx.base(span),
146                    argument: Box::alloc().init(spread.expr.babelify(ctx).into()),
147                })
148            }
149            #[cfg(swc_ast_unknown)]
150            _ => panic!("unable to access unknown nodes"),
151        }
152    }
153}
154
155fn extend_spread_span_to_braces(sp: Span, ctx: &Context) -> Span {
156    let mut span = sp;
157    let _ = ctx.cm.with_span_to_prev_source(sp, |prev_source| {
158        let mut num_chars = 0;
159        for c in prev_source.chars().rev() {
160            num_chars += 1;
161            if c == '{' {
162                span = span.with_lo(span.lo - BytePos(num_chars));
163            } else if !c.is_whitespace() {
164                break;
165            }
166        }
167    });
168
169    let _ = ctx.cm.with_span_to_next_source(sp, |next_source| {
170        let mut num_chars = 0;
171        for c in next_source.chars() {
172            num_chars += 1;
173            if c == '}' {
174                span = span.with_hi(span.hi + BytePos(num_chars));
175            } else if !c.is_whitespace() {
176                break;
177            }
178        }
179    });
180
181    span
182}
183
184impl Babelify for JSXClosingElement {
185    type Output = BabelJSXClosingElement;
186
187    fn babelify(self, ctx: &Context) -> Self::Output {
188        BabelJSXClosingElement {
189            base: ctx.base(self.span),
190            name: self.name.babelify(ctx),
191        }
192    }
193}
194
195impl Babelify for JSXAttr {
196    type Output = JSXAttribute;
197
198    fn babelify(self, ctx: &Context) -> Self::Output {
199        JSXAttribute {
200            base: ctx.base(self.span),
201            name: self.name.babelify(ctx),
202            value: self.value.map(|val| val.babelify(ctx)),
203        }
204    }
205}
206
207impl Babelify for JSXAttrName {
208    type Output = BabelJSXAttrName;
209
210    fn babelify(self, ctx: &Context) -> Self::Output {
211        match self {
212            JSXAttrName::Ident(i) => BabelJSXAttrName::Id(i.babelify(ctx).into()),
213            JSXAttrName::JSXNamespacedName(n) => BabelJSXAttrName::Name(n.babelify(ctx)),
214            #[cfg(swc_ast_unknown)]
215            _ => panic!("unable to access unknown nodes"),
216        }
217    }
218}
219
220impl Babelify for JSXAttrValue {
221    type Output = JSXAttrVal;
222
223    fn babelify(self, ctx: &Context) -> Self::Output {
224        match self {
225            JSXAttrValue::Lit(lit) => {
226                // TODO(dwoznicki): Babel only seems to accept string literals here. Is that
227                // right?
228                match lit {
229                    Lit::Str(s) => JSXAttrVal::String(s.babelify(ctx)),
230                    _ => panic!(
231                        "illegal conversion: Cannot convert {:?} to JsxAttrVal::Lit",
232                        &lit
233                    ),
234                }
235            }
236            JSXAttrValue::JSXExprContainer(e) => JSXAttrVal::Expr(e.babelify(ctx)),
237            JSXAttrValue::JSXElement(e) => JSXAttrVal::Element(e.babelify(ctx)),
238            JSXAttrValue::JSXFragment(f) => JSXAttrVal::Fragment(f.babelify(ctx)),
239            #[cfg(swc_ast_unknown)]
240            _ => panic!("unable to access unknown nodes"),
241        }
242    }
243}
244
245impl Babelify for JSXText {
246    type Output = BabelJSXText;
247
248    fn babelify(self, ctx: &Context) -> Self::Output {
249        BabelJSXText {
250            base: ctx.base(self.span),
251            value: self.value,
252        }
253    }
254}
255
256impl Babelify for JSXElement {
257    type Output = BabelJSXElement;
258
259    fn babelify(self, ctx: &Context) -> Self::Output {
260        let self_closing = match Flavor::current() {
261            Flavor::Babel => None,
262            Flavor::Acorn { .. } => Some(self.closing.is_some()),
263        };
264        BabelJSXElement {
265            base: ctx.base(self.span),
266            opening_element: self.opening.babelify(ctx),
267            closing_element: self.closing.map(|el| el.babelify(ctx)),
268            children: self.children.babelify(ctx),
269            self_closing,
270        }
271    }
272}
273
274impl Babelify for JSXElementChild {
275    type Output = BabelJSXElementChild;
276
277    fn babelify(self, ctx: &Context) -> Self::Output {
278        match self {
279            JSXElementChild::JSXText(t) => BabelJSXElementChild::Text(t.babelify(ctx)),
280            JSXElementChild::JSXExprContainer(e) => BabelJSXElementChild::Expr(e.babelify(ctx)),
281            JSXElementChild::JSXSpreadChild(s) => BabelJSXElementChild::Spread(s.babelify(ctx)),
282            JSXElementChild::JSXElement(e) => BabelJSXElementChild::Element(e.babelify(ctx)),
283            JSXElementChild::JSXFragment(f) => BabelJSXElementChild::Fragment(f.babelify(ctx)),
284            #[cfg(swc_ast_unknown)]
285            _ => panic!("unable to access unknown nodes"),
286        }
287    }
288}
289
290impl Babelify for JSXFragment {
291    type Output = BabelJSXFragment;
292
293    fn babelify(self, ctx: &Context) -> Self::Output {
294        BabelJSXFragment {
295            base: ctx.base(self.span),
296            opening_fragment: self.opening.babelify(ctx),
297            closing_fragment: self.closing.babelify(ctx),
298            children: self.children.babelify(ctx),
299        }
300    }
301}
302
303impl Babelify for JSXOpeningFragment {
304    type Output = BabelJSXOpeningFragment;
305
306    fn babelify(self, ctx: &Context) -> Self::Output {
307        BabelJSXOpeningFragment {
308            base: ctx.base(self.span),
309        }
310    }
311}
312
313impl Babelify for JSXClosingFragment {
314    type Output = BabelJSXClosingFragment;
315
316    fn babelify(self, ctx: &Context) -> Self::Output {
317        BabelJSXClosingFragment {
318            base: ctx.base(self.span),
319        }
320    }
321}