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