swc_ecma_transforms_react/jsx/
mod.rs

1#![allow(clippy::redundant_allocation)]
2
3use std::{
4    borrow::Cow,
5    iter::{self, once},
6    sync::RwLock,
7};
8
9use bytes_str::BytesStr;
10use once_cell::sync::Lazy;
11use rustc_hash::FxHashMap;
12use serde::{Deserialize, Serialize};
13use string_enum::StringEnum;
14use swc_atoms::{atom, Atom};
15use swc_common::{
16    comments::{Comment, CommentKind, Comments},
17    errors::HANDLER,
18    iter::IdentifyLast,
19    sync::Lrc,
20    util::take::Take,
21    FileName, Mark, SourceMap, Span, Spanned, SyntaxContext, DUMMY_SP,
22};
23use swc_config::merge::Merge;
24use swc_ecma_ast::*;
25use swc_ecma_parser::{parse_file_as_expr, Syntax};
26use swc_ecma_utils::{drop_span, prepend_stmt, private_ident, quote_ident, ExprFactory, StmtLike};
27use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
28
29use self::static_check::should_use_create_element;
30use crate::refresh::options::{deserialize_refresh, RefreshOptions};
31
32mod static_check;
33#[cfg(test)]
34mod tests;
35
36/// https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#runtime
37#[derive(StringEnum, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
38pub enum Runtime {
39    /// `automatic`
40    Automatic,
41    /// `classic`
42    Classic,
43}
44
45/// Note: This will changed in v2
46impl Default for Runtime {
47    fn default() -> Self {
48        Runtime::Classic
49    }
50}
51
52#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, Merge)]
53#[serde(rename_all = "camelCase")]
54#[serde(deny_unknown_fields)]
55pub struct Options {
56    /// If this is `true`, swc will behave just like babel 8 with
57    /// `BABEL_8_BREAKING: true`.
58    #[serde(skip, default)]
59    pub next: Option<bool>,
60
61    #[serde(default)]
62    pub runtime: Option<Runtime>,
63
64    /// For automatic runtime
65    #[serde(default)]
66    pub import_source: Option<Atom>,
67
68    #[serde(default)]
69    pub pragma: Option<BytesStr>,
70    #[serde(default)]
71    pub pragma_frag: Option<BytesStr>,
72
73    #[serde(default)]
74    pub throw_if_namespace: Option<bool>,
75
76    #[serde(default)]
77    pub development: Option<bool>,
78
79    // @babel/plugin-transform-react-jsx: Since "useBuiltIns" is removed in Babel 8, you can remove
80    // it from the config.
81    #[deprecated(
82        since = "0.167.4",
83        note = r#"Since `useBuiltIns` is removed in swc, you can remove it from the config."#
84    )]
85    #[serde(default, alias = "useBuiltIns")]
86    pub use_builtins: Option<bool>,
87
88    // '@babel/plugin-transform-react-jsx: Since Babel 8, an inline object with spread elements is
89    // always used, and the "useSpread" option is no longer available. Please remove it from your
90    // config.',
91    #[deprecated(
92        since = "0.167.4",
93        note = r#"An inline object with spread elements is always used, and the `useSpread` option is no longer available. Please remove it from your config."#
94    )]
95    #[serde(default)]
96    pub use_spread: Option<bool>,
97
98    #[serde(default, deserialize_with = "deserialize_refresh")]
99    // default to disabled since this is still considered as experimental by now
100    pub refresh: Option<RefreshOptions>,
101}
102
103#[cfg(feature = "concurrent")]
104macro_rules! static_str {
105    ($s:expr) => {{
106        static VAL: Lazy<BytesStr> = Lazy::new(|| $s.into());
107        VAL.clone()
108    }};
109}
110
111#[cfg(not(feature = "concurrent"))]
112macro_rules! static_str {
113    ($s:expr) => {
114        $s.into()
115    };
116}
117
118pub fn default_import_source() -> Atom {
119    atom!("react")
120}
121
122pub fn default_pragma() -> BytesStr {
123    static_str!("React.createElement")
124}
125
126pub fn default_pragma_frag() -> BytesStr {
127    static_str!("React.Fragment")
128}
129
130fn default_throw_if_namespace() -> bool {
131    true
132}
133
134/// Parse `src` to use as a `pragma` or `pragmaFrag` in jsx.
135pub fn parse_expr_for_jsx(
136    cm: &SourceMap,
137    name: &str,
138    src: BytesStr,
139    top_level_mark: Mark,
140) -> Box<Expr> {
141    let fm = cm.new_source_file(cache_filename(name), src);
142
143    parse_file_as_expr(
144        &fm,
145        Syntax::default(),
146        Default::default(),
147        None,
148        &mut Vec::new(),
149    )
150    .map_err(|e| {
151        if HANDLER.is_set() {
152            HANDLER.with(|h| {
153                e.into_diagnostic(h)
154                    .note("Failed to parse jsx pragma")
155                    .emit()
156            })
157        }
158    })
159    .map(drop_span)
160    .map(|mut expr| {
161        apply_mark(&mut expr, top_level_mark);
162        expr
163    })
164    .unwrap_or_else(|()| {
165        panic!(
166            "failed to parse jsx option {}: '{}' is not an expression",
167            name, fm.src,
168        )
169    })
170}
171
172fn apply_mark(e: &mut Expr, mark: Mark) {
173    match e {
174        Expr::Ident(i) => {
175            i.ctxt = i.ctxt.apply_mark(mark);
176        }
177        Expr::Member(MemberExpr { obj, .. }) => {
178            apply_mark(obj, mark);
179        }
180        _ => {}
181    }
182}
183
184/// `@babel/plugin-transform-react-jsx`
185///
186/// Turn JSX into React function calls
187///
188///
189/// `top_level_mark` should be [Mark] passed to
190/// [swc_ecma_transforms_base::resolver::resolver_with_mark].
191///
192///
193/// # Parameters
194///
195/// ## `top_level_ctxt`
196///
197/// This is used to reference `React` defined by the user.
198///
199/// e.g.
200///
201/// ```js
202/// import React from 'react';
203/// ```
204pub fn jsx<C>(
205    cm: Lrc<SourceMap>,
206    comments: Option<C>,
207    options: Options,
208    top_level_mark: Mark,
209    unresolved_mark: Mark,
210) -> impl Pass + VisitMut
211where
212    C: Comments,
213{
214    visit_mut_pass(Jsx {
215        cm: cm.clone(),
216        top_level_mark,
217        unresolved_mark,
218        runtime: options.runtime.unwrap_or_default(),
219        import_source: options.import_source.unwrap_or_else(default_import_source),
220        import_jsx: None,
221        import_jsxs: None,
222        import_fragment: None,
223        import_create_element: None,
224
225        pragma: Lrc::new(parse_expr_for_jsx(
226            &cm,
227            "pragma",
228            options.pragma.unwrap_or_else(default_pragma),
229            top_level_mark,
230        )),
231        comments,
232        pragma_frag: Lrc::new(parse_expr_for_jsx(
233            &cm,
234            "pragmaFrag",
235            options.pragma_frag.unwrap_or_else(default_pragma_frag),
236            top_level_mark,
237        )),
238        development: options.development.unwrap_or_default(),
239        throw_if_namespace: options
240            .throw_if_namespace
241            .unwrap_or_else(default_throw_if_namespace),
242        top_level_node: true,
243    })
244}
245
246struct Jsx<C>
247where
248    C: Comments,
249{
250    cm: Lrc<SourceMap>,
251
252    top_level_mark: Mark,
253    unresolved_mark: Mark,
254
255    runtime: Runtime,
256    /// For automatic runtime.
257    import_source: Atom,
258    /// For automatic runtime.
259    import_jsx: Option<Ident>,
260    /// For automatic runtime.
261    import_jsxs: Option<Ident>,
262    /// For automatic runtime.
263    import_create_element: Option<Ident>,
264    /// For automatic runtime.
265    import_fragment: Option<Ident>,
266    top_level_node: bool,
267
268    pragma: Lrc<Box<Expr>>,
269    comments: Option<C>,
270    pragma_frag: Lrc<Box<Expr>>,
271    development: bool,
272    throw_if_namespace: bool,
273}
274
275#[derive(Debug, Default, Clone, PartialEq, Eq)]
276pub struct JsxDirectives {
277    pub runtime: Option<Runtime>,
278
279    /// For automatic runtime.
280    pub import_source: Option<Atom>,
281
282    /// Parsed from `@jsx`
283    pub pragma: Option<Lrc<Box<Expr>>>,
284
285    /// Parsed from `@jsxFrag`
286    pub pragma_frag: Option<Lrc<Box<Expr>>>,
287}
288
289fn respan(e: &mut Expr, span: Span) {
290    match e {
291        Expr::Ident(i) => {
292            i.span.lo = span.lo;
293            i.span.hi = span.hi;
294        }
295        Expr::Member(e) => {
296            e.span = span;
297        }
298        _ => {}
299    }
300}
301
302impl JsxDirectives {
303    pub fn from_comments(
304        cm: &SourceMap,
305        _: Span,
306        comments: &[Comment],
307        top_level_mark: Mark,
308    ) -> Self {
309        let mut res = JsxDirectives::default();
310
311        for cmt in comments {
312            if cmt.kind != CommentKind::Block {
313                continue;
314            }
315
316            for line in cmt.text.lines() {
317                let mut line = line.trim();
318                if line.starts_with('*') {
319                    line = line[1..].trim();
320                }
321
322                if !line.starts_with("@jsx") {
323                    continue;
324                }
325
326                let mut words = line.split_whitespace();
327                loop {
328                    let pragma = words.next();
329                    if pragma.is_none() {
330                        break;
331                    }
332                    let val = words.next();
333
334                    match pragma {
335                        Some("@jsxRuntime") => match val {
336                            Some("classic") => res.runtime = Some(Runtime::Classic),
337                            Some("automatic") => res.runtime = Some(Runtime::Automatic),
338                            None => {}
339                            _ => {
340                                HANDLER.with(|handler| {
341                                    handler
342                                        .struct_span_err(
343                                            cmt.span,
344                                            "Runtime must be either `classic` or `automatic`.",
345                                        )
346                                        .emit()
347                                });
348                            }
349                        },
350                        Some("@jsxImportSource") => {
351                            if let Some(src) = val {
352                                res.runtime = Some(Runtime::Automatic);
353                                res.import_source = Some(Atom::new(src));
354                            }
355                        }
356                        Some("@jsxFrag") => {
357                            if let Some(src) = val {
358                                if is_valid_for_pragma(src) {
359                                    // TODO: Optimize
360                                    let mut e = parse_expr_for_jsx(
361                                        cm,
362                                        "module-jsx-pragma-frag",
363                                        cache_source(src),
364                                        top_level_mark,
365                                    );
366                                    respan(&mut e, cmt.span);
367                                    res.pragma_frag = Some(e.into())
368                                }
369                            }
370                        }
371                        Some("@jsx") => {
372                            if let Some(src) = val {
373                                if is_valid_for_pragma(src) {
374                                    // TODO: Optimize
375                                    let mut e = parse_expr_for_jsx(
376                                        cm,
377                                        "module-jsx-pragma",
378                                        cache_source(src),
379                                        top_level_mark,
380                                    );
381                                    respan(&mut e, cmt.span);
382                                    res.pragma = Some(e.into());
383                                }
384                            }
385                        }
386                        _ => {}
387                    }
388                }
389            }
390        }
391
392        res
393    }
394}
395
396#[cfg(feature = "concurrent")]
397fn cache_filename(name: &str) -> Lrc<FileName> {
398    static FILENAME_CACHE: Lazy<RwLock<FxHashMap<String, Lrc<FileName>>>> =
399        Lazy::new(|| RwLock::new(FxHashMap::default()));
400
401    {
402        let cache = FILENAME_CACHE
403            .read()
404            .expect("Failed to read FILENAME_CACHE");
405        if let Some(f) = cache.get(name) {
406            return f.clone();
407        }
408    }
409
410    let file = Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")));
411
412    {
413        let mut cache = FILENAME_CACHE
414            .write()
415            .expect("Failed to write FILENAME_CACHE");
416        cache.insert(name.to_string(), file.clone());
417    }
418
419    file
420}
421
422#[cfg(not(feature = "concurrent"))]
423fn cache_filename(name: &str) -> Lrc<FileName> {
424    Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")))
425}
426
427#[cfg(feature = "concurrent")]
428fn cache_source(src: &str) -> BytesStr {
429    use rustc_hash::FxHashSet;
430
431    static CACHE: Lazy<RwLock<FxHashSet<BytesStr>>> =
432        Lazy::new(|| RwLock::new(FxHashSet::default()));
433
434    {
435        let cache = CACHE.write().unwrap();
436
437        if let Some(cached) = cache.get(src) {
438            return cached.clone();
439        }
440    }
441
442    let cached: BytesStr = src.to_string().into();
443    {
444        let mut cache = CACHE.write().unwrap();
445        cache.insert(cached.clone());
446    }
447    cached
448}
449
450#[cfg(not(feature = "concurrent"))]
451fn cache_source(src: &str) -> BytesStr {
452    // We cannot cache because Rc does not implement Send.
453    src.to_string().into()
454}
455
456fn is_valid_for_pragma(s: &str) -> bool {
457    if s.is_empty() {
458        return false;
459    }
460
461    if !s.starts_with(|c: char| Ident::is_valid_start(c)) {
462        return false;
463    }
464
465    for c in s.chars() {
466        if !Ident::is_valid_continue(c) && c != '.' {
467            return false;
468        }
469    }
470
471    true
472}
473
474impl<C> Jsx<C>
475where
476    C: Comments,
477{
478    fn inject_runtime<T, F>(&mut self, body: &mut Vec<T>, inject: F)
479    where
480        T: StmtLike,
481        // Fn(Vec<(local, imported)>, src, body)
482        F: Fn(Vec<(Ident, IdentName)>, &str, &mut Vec<T>),
483    {
484        if self.runtime == Runtime::Automatic {
485            if let Some(local) = self.import_create_element.take() {
486                inject(
487                    vec![(local, quote_ident!("createElement"))],
488                    &self.import_source,
489                    body,
490                );
491            }
492
493            let imports = self.import_jsx.take();
494            let imports = if self.development {
495                imports
496                    .map(|local| (local, quote_ident!("jsxDEV")))
497                    .into_iter()
498                    .chain(
499                        self.import_fragment
500                            .take()
501                            .map(|local| (local, quote_ident!("Fragment"))),
502                    )
503                    .collect::<Vec<_>>()
504            } else {
505                imports
506                    .map(|local| (local, quote_ident!("jsx")))
507                    .into_iter()
508                    .chain(
509                        self.import_jsxs
510                            .take()
511                            .map(|local| (local, quote_ident!("jsxs"))),
512                    )
513                    .chain(
514                        self.import_fragment
515                            .take()
516                            .map(|local| (local, quote_ident!("Fragment"))),
517                    )
518                    .collect::<Vec<_>>()
519            };
520
521            if !imports.is_empty() {
522                let jsx_runtime = if self.development {
523                    "jsx-dev-runtime"
524                } else {
525                    "jsx-runtime"
526                };
527
528                let value = format!("{}/{}", self.import_source, jsx_runtime);
529                inject(imports, &value, body)
530            }
531        }
532    }
533
534    fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
535        let mut span = el.span();
536
537        let count = count_children(&el.children);
538        let use_jsxs = count > 1
539            || (count == 1 && matches!(&el.children[0], JSXElementChild::JSXSpreadChild(..)));
540
541        if let Some(comments) = &self.comments {
542            if span.lo.is_dummy() {
543                span.lo = Span::dummy_with_cmt().lo;
544            }
545
546            comments.add_pure_comment(span.lo);
547        }
548
549        match self.runtime {
550            Runtime::Automatic => {
551                let jsx = if use_jsxs && !self.development {
552                    self.import_jsxs
553                        .get_or_insert_with(|| private_ident!("_jsxs"))
554                        .clone()
555                } else {
556                    let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
557                    self.import_jsx
558                        .get_or_insert_with(|| private_ident!(jsx))
559                        .clone()
560                };
561
562                let fragment = self
563                    .import_fragment
564                    .get_or_insert_with(|| private_ident!("_Fragment"))
565                    .clone();
566
567                let mut props_obj = ObjectLit {
568                    span: DUMMY_SP,
569                    props: Vec::new(),
570                };
571
572                let children = el
573                    .children
574                    .into_iter()
575                    .filter_map(|child| self.jsx_elem_child_to_expr(child))
576                    .map(Some)
577                    .collect::<Vec<_>>();
578
579                match (children.len(), use_jsxs) {
580                    (0, _) => {}
581                    (1, false) => {
582                        props_obj
583                            .props
584                            .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
585                                key: PropName::Ident(quote_ident!("children")),
586                                value: children.into_iter().next().flatten().unwrap().expr,
587                            }))));
588                    }
589                    _ => {
590                        props_obj
591                            .props
592                            .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
593                                key: PropName::Ident(quote_ident!("children")),
594                                value: ArrayLit {
595                                    span: DUMMY_SP,
596                                    elems: children,
597                                }
598                                .into(),
599                            }))));
600                    }
601                }
602
603                let args = once(fragment.as_arg()).chain(once(props_obj.as_arg()));
604
605                let args = if self.development {
606                    args.chain(once(Expr::undefined(DUMMY_SP).as_arg()))
607                        .chain(once(use_jsxs.as_arg()))
608                        .collect()
609                } else {
610                    args.collect()
611                };
612
613                CallExpr {
614                    span,
615                    callee: jsx.as_callee(),
616                    args,
617                    ..Default::default()
618                }
619                .into()
620            }
621            Runtime::Classic => {
622                CallExpr {
623                    span,
624                    callee: (*self.pragma).clone().as_callee(),
625                    args: iter::once((*self.pragma_frag).clone().as_arg())
626                        // attribute: null
627                        .chain(iter::once(Lit::Null(Null { span: DUMMY_SP }).as_arg()))
628                        .chain({
629                            // Children
630                            el.children
631                                .into_iter()
632                                .filter_map(|c| self.jsx_elem_child_to_expr(c))
633                        })
634                        .collect(),
635                    ..Default::default()
636                }
637                .into()
638            }
639        }
640    }
641
642    /// # Automatic
643    ///
644    /// <div></div> => jsx('div', null);
645    ///
646    /// # Classic
647    ///
648    /// <div></div> => React.createElement('div', null);
649    fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr {
650        let top_level_node = self.top_level_node;
651        let mut span = el.span();
652        let use_create_element = should_use_create_element(&el.opening.attrs);
653        self.top_level_node = false;
654
655        let name = self.jsx_name(el.opening.name);
656
657        if let Some(comments) = &self.comments {
658            if span.lo.is_dummy() {
659                span.lo = Span::dummy_with_cmt().lo;
660            }
661
662            comments.add_pure_comment(span.lo);
663        }
664
665        match self.runtime {
666            Runtime::Automatic => {
667                // function jsx(tagName: string, props: { children: Node[], ... }, key: string)
668
669                let count = count_children(&el.children);
670                let use_jsxs = count > 1
671                    || (count == 1
672                        && matches!(&el.children[0], JSXElementChild::JSXSpreadChild(..)));
673
674                let jsx = if use_create_element {
675                    self.import_create_element
676                        .get_or_insert_with(|| private_ident!("_createElement"))
677                        .clone()
678                } else if use_jsxs && !self.development {
679                    self.import_jsxs
680                        .get_or_insert_with(|| private_ident!("_jsxs"))
681                        .clone()
682                } else {
683                    let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
684                    self.import_jsx
685                        .get_or_insert_with(|| private_ident!(jsx))
686                        .clone()
687                };
688
689                let mut props_obj = ObjectLit {
690                    span: DUMMY_SP,
691                    props: Vec::new(),
692                };
693
694                let mut key = None;
695                let mut source_props = None;
696                let mut self_props = None;
697
698                for attr in el.opening.attrs {
699                    match attr {
700                        JSXAttrOrSpread::JSXAttr(attr) => {
701                            //
702                            match attr.name {
703                                JSXAttrName::Ident(i) => {
704                                    //
705                                    if !use_create_element && i.sym == "key" {
706                                        key = attr
707                                            .value
708                                            .and_then(jsx_attr_value_to_expr)
709                                            .map(|expr| expr.as_arg());
710
711                                        if key.is_none() {
712                                            HANDLER.with(|handler| {
713                                                handler
714                                                    .struct_span_err(
715                                                        i.span,
716                                                        "The value of property 'key' should not \
717                                                         be empty",
718                                                    )
719                                                    .emit();
720                                            });
721                                        }
722                                        continue;
723                                    }
724
725                                    if !use_create_element
726                                        && *i.sym == *"__source"
727                                        && self.development
728                                    {
729                                        if source_props.is_some() {
730                                            panic!("Duplicate __source is found");
731                                        }
732                                        source_props = attr
733                                            .value
734                                            .and_then(jsx_attr_value_to_expr)
735                                            .map(|expr| expr.as_arg());
736                                        assert_ne!(
737                                            source_props, None,
738                                            "value of property '__source' should not be empty"
739                                        );
740                                        continue;
741                                    }
742
743                                    if !use_create_element
744                                        && *i.sym == *"__self"
745                                        && self.development
746                                    {
747                                        if self_props.is_some() {
748                                            panic!("Duplicate __self is found");
749                                        }
750                                        self_props = attr
751                                            .value
752                                            .and_then(jsx_attr_value_to_expr)
753                                            .map(|expr| expr.as_arg());
754                                        assert_ne!(
755                                            self_props, None,
756                                            "value of property '__self' should not be empty"
757                                        );
758                                        continue;
759                                    }
760
761                                    let value = match attr.value {
762                                        Some(v) => jsx_attr_value_to_expr(v)
763                                            .expect("empty expression container?"),
764                                        None => true.into(),
765                                    };
766
767                                    // TODO: Check if `i` is a valid identifier.
768                                    let key = if i.sym.contains('-') {
769                                        PropName::Str(Str {
770                                            span: i.span,
771                                            raw: None,
772                                            value: i.sym,
773                                        })
774                                    } else {
775                                        PropName::Ident(i)
776                                    };
777                                    props_obj.props.push(PropOrSpread::Prop(Box::new(
778                                        Prop::KeyValue(KeyValueProp { key, value }),
779                                    )));
780                                }
781                                JSXAttrName::JSXNamespacedName(JSXNamespacedName {
782                                    ns,
783                                    name,
784                                    ..
785                                }) => {
786                                    if self.throw_if_namespace {
787                                        HANDLER.with(|handler| {
788                                            handler
789                                                .struct_span_err(
790                                                    span,
791                                                    "JSX Namespace is disabled by default because \
792                                                     react does not support it yet. You can \
793                                                     specify jsc.transform.react.throwIfNamespace \
794                                                     to false to override default behavior",
795                                                )
796                                                .emit()
797                                        });
798                                    }
799
800                                    let value = match attr.value {
801                                        Some(v) => jsx_attr_value_to_expr(v)
802                                            .expect("empty expression container?"),
803                                        None => true.into(),
804                                    };
805
806                                    let str_value = format!("{}:{}", ns.sym, name.sym);
807                                    let key = Str {
808                                        span,
809                                        raw: None,
810                                        value: str_value.into(),
811                                    };
812                                    let key = PropName::Str(key);
813
814                                    props_obj.props.push(PropOrSpread::Prop(Box::new(
815                                        Prop::KeyValue(KeyValueProp { key, value }),
816                                    )));
817                                }
818                            }
819                        }
820                        JSXAttrOrSpread::SpreadElement(attr) => match *attr.expr {
821                            Expr::Object(obj) => {
822                                props_obj.props.extend(obj.props);
823                            }
824                            _ => {
825                                props_obj.props.push(PropOrSpread::Spread(attr));
826                            }
827                        },
828                    }
829                }
830
831                let mut children = el
832                    .children
833                    .into_iter()
834                    .filter_map(|child| self.jsx_elem_child_to_expr(child))
835                    .map(Some)
836                    .collect::<Vec<_>>();
837
838                match children.len() {
839                    0 => {}
840                    1 if children[0].as_ref().unwrap().spread.is_none() => {
841                        if !use_create_element {
842                            props_obj
843                                .props
844                                .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
845                                    key: PropName::Ident(quote_ident!("children")),
846                                    value: children
847                                        .take()
848                                        .into_iter()
849                                        .next()
850                                        .flatten()
851                                        .unwrap()
852                                        .expr,
853                                }))));
854                        }
855                    }
856                    _ => {
857                        props_obj
858                            .props
859                            .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
860                                key: PropName::Ident(quote_ident!("children")),
861                                value: ArrayLit {
862                                    span: DUMMY_SP,
863                                    elems: children.take(),
864                                }
865                                .into(),
866                            }))));
867                    }
868                }
869
870                self.top_level_node = top_level_node;
871
872                let args = once(name.as_arg()).chain(once(props_obj.as_arg()));
873                let args = if use_create_element {
874                    args.chain(children.into_iter().flatten()).collect()
875                } else if self.development {
876                    // set undefined literal to key if key is None
877                    let key = match key {
878                        Some(key) => key,
879                        None => Expr::undefined(DUMMY_SP).as_arg(),
880                    };
881
882                    // set undefined literal to __source if __source is None
883                    let source_props = match source_props {
884                        Some(source_props) => source_props,
885                        None => Expr::undefined(DUMMY_SP).as_arg(),
886                    };
887
888                    // set undefined literal to __self if __self is None
889                    let self_props = match self_props {
890                        Some(self_props) => self_props,
891                        None => Expr::undefined(DUMMY_SP).as_arg(),
892                    };
893                    args.chain(once(key))
894                        .chain(once(use_jsxs.as_arg()))
895                        .chain(once(source_props))
896                        .chain(once(self_props))
897                        .collect()
898                } else {
899                    args.chain(key).collect()
900                };
901                CallExpr {
902                    span,
903                    callee: jsx.as_callee(),
904                    args,
905                    ..Default::default()
906                }
907                .into()
908            }
909            Runtime::Classic => {
910                CallExpr {
911                    span,
912                    callee: (*self.pragma).clone().as_callee(),
913                    args: iter::once(name.as_arg())
914                        .chain(iter::once({
915                            // Attributes
916                            self.fold_attrs_for_classic(el.opening.attrs).as_arg()
917                        }))
918                        .chain({
919                            // Children
920                            el.children
921                                .into_iter()
922                                .filter_map(|c| self.jsx_elem_child_to_expr(c))
923                        })
924                        .collect(),
925                    ..Default::default()
926                }
927                .into()
928            }
929        }
930    }
931
932    fn jsx_elem_child_to_expr(&mut self, c: JSXElementChild) -> Option<ExprOrSpread> {
933        self.top_level_node = false;
934
935        Some(match c {
936            JSXElementChild::JSXText(text) => {
937                // TODO(kdy1): Optimize
938                let value = jsx_text_to_str(text.value);
939                let s = Str {
940                    span: text.span,
941                    raw: None,
942                    value,
943                };
944
945                if s.value.is_empty() {
946                    return None;
947                }
948
949                Lit::Str(s).as_arg()
950            }
951            JSXElementChild::JSXExprContainer(JSXExprContainer {
952                expr: JSXExpr::Expr(e),
953                ..
954            }) => e.as_arg(),
955            JSXElementChild::JSXExprContainer(JSXExprContainer {
956                expr: JSXExpr::JSXEmptyExpr(..),
957                ..
958            }) => return None,
959            JSXElementChild::JSXElement(el) => self.jsx_elem_to_expr(*el).as_arg(),
960            JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
961            JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => ExprOrSpread {
962                spread: Some(span),
963                expr,
964            },
965        })
966    }
967
968    fn fold_attrs_for_classic(&mut self, attrs: Vec<JSXAttrOrSpread>) -> Box<Expr> {
969        if attrs.is_empty() {
970            return Lit::Null(Null { span: DUMMY_SP }).into();
971        }
972        let attr_cnt = attrs.len();
973
974        let mut props = Vec::new();
975        for attr in attrs {
976            match attr {
977                JSXAttrOrSpread::JSXAttr(attr) => {
978                    props.push(PropOrSpread::Prop(Box::new(self.attr_to_prop(attr))))
979                }
980                JSXAttrOrSpread::SpreadElement(spread) => {
981                    if attr_cnt == 1 {
982                        return spread.expr;
983                    }
984                    // babel does some optimizations
985                    match *spread.expr {
986                        Expr::Object(obj) => props.extend(obj.props),
987                        _ => props.push(PropOrSpread::Spread(spread)),
988                    }
989                }
990            }
991        }
992
993        let obj = ObjectLit {
994            span: DUMMY_SP,
995            props,
996        };
997
998        obj.into()
999    }
1000
1001    fn attr_to_prop(&mut self, a: JSXAttr) -> Prop {
1002        let key = to_prop_name(a.name);
1003        let value = a
1004            .value
1005            .map(|v| match v {
1006                JSXAttrValue::Lit(Lit::Str(s)) => {
1007                    let value = transform_jsx_attr_str(&s.value);
1008
1009                    Lit::Str(Str {
1010                        span: s.span,
1011                        raw: None,
1012                        value: value.into(),
1013                    })
1014                    .into()
1015                }
1016                JSXAttrValue::JSXExprContainer(JSXExprContainer {
1017                    expr: JSXExpr::Expr(e),
1018                    ..
1019                }) => e,
1020                JSXAttrValue::JSXElement(element) => Box::new(self.jsx_elem_to_expr(*element)),
1021                JSXAttrValue::JSXFragment(fragment) => Box::new(self.jsx_frag_to_expr(fragment)),
1022                JSXAttrValue::Lit(lit) => Box::new(lit.into()),
1023                JSXAttrValue::JSXExprContainer(JSXExprContainer {
1024                    span: _,
1025                    expr: JSXExpr::JSXEmptyExpr(_),
1026                }) => unreachable!("attr_to_prop(JSXEmptyExpr)"),
1027            })
1028            .unwrap_or_else(|| {
1029                Lit::Bool(Bool {
1030                    span: key.span(),
1031                    value: true,
1032                })
1033                .into()
1034            });
1035        Prop::KeyValue(KeyValueProp { key, value })
1036    }
1037}
1038
1039impl<C> Jsx<C>
1040where
1041    C: Comments,
1042{
1043    /// If we found required jsx directives, we returns true.
1044    fn parse_directives(&mut self, span: Span) -> bool {
1045        let mut found = false;
1046
1047        let directives = self.comments.with_leading(span.lo, |comments| {
1048            JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
1049        });
1050
1051        let JsxDirectives {
1052            runtime,
1053            import_source,
1054            pragma,
1055            pragma_frag,
1056        } = directives;
1057
1058        if let Some(runtime) = runtime {
1059            found = true;
1060            self.runtime = runtime;
1061        }
1062
1063        if let Some(import_source) = import_source {
1064            found = true;
1065            self.import_source = import_source;
1066        }
1067
1068        if let Some(pragma) = pragma {
1069            if let Runtime::Automatic = self.runtime {
1070                HANDLER.with(|handler| {
1071                    handler
1072                        .struct_span_err(
1073                            pragma.span(),
1074                            "pragma cannot be set when runtime is automatic",
1075                        )
1076                        .emit()
1077                });
1078            }
1079
1080            found = true;
1081            self.pragma = pragma;
1082        }
1083
1084        if let Some(pragma_frag) = pragma_frag {
1085            if let Runtime::Automatic = self.runtime {
1086                HANDLER.with(|handler| {
1087                    handler
1088                        .struct_span_err(
1089                            pragma_frag.span(),
1090                            "pragmaFrag cannot be set when runtime is automatic",
1091                        )
1092                        .emit()
1093                });
1094            }
1095
1096            found = true;
1097            self.pragma_frag = pragma_frag;
1098        }
1099
1100        found
1101    }
1102}
1103
1104impl<C> VisitMut for Jsx<C>
1105where
1106    C: Comments,
1107{
1108    noop_visit_mut_type!();
1109
1110    fn visit_mut_expr(&mut self, expr: &mut Expr) {
1111        let top_level_node = self.top_level_node;
1112        let mut did_work = false;
1113
1114        if let Expr::JSXElement(el) = expr {
1115            did_work = true;
1116            // <div></div> => React.createElement('div', null);
1117            *expr = self.jsx_elem_to_expr(*el.take());
1118        } else if let Expr::JSXFragment(frag) = expr {
1119            // <></> => React.createElement(React.Fragment, null);
1120            did_work = true;
1121            *expr = self.jsx_frag_to_expr(frag.take());
1122        } else if let Expr::Paren(ParenExpr {
1123            expr: inner_expr, ..
1124        }) = expr
1125        {
1126            if let Expr::JSXElement(el) = &mut **inner_expr {
1127                did_work = true;
1128                *expr = self.jsx_elem_to_expr(*el.take());
1129            } else if let Expr::JSXFragment(frag) = &mut **inner_expr {
1130                // <></> => React.createElement(React.Fragment, null);
1131                did_work = true;
1132                *expr = self.jsx_frag_to_expr(frag.take());
1133            }
1134        }
1135
1136        if did_work {
1137            self.top_level_node = false;
1138        }
1139
1140        expr.visit_mut_children_with(self);
1141
1142        self.top_level_node = top_level_node;
1143    }
1144
1145    fn visit_mut_module(&mut self, module: &mut Module) {
1146        self.parse_directives(module.span);
1147
1148        for item in &module.body {
1149            let span = item.span();
1150            if self.parse_directives(span) {
1151                break;
1152            }
1153        }
1154
1155        module.visit_mut_children_with(self);
1156
1157        if self.runtime == Runtime::Automatic {
1158            self.inject_runtime(&mut module.body, |imports, src, stmts| {
1159                let specifiers = imports
1160                    .into_iter()
1161                    .map(|(local, imported)| {
1162                        ImportSpecifier::Named(ImportNamedSpecifier {
1163                            span: DUMMY_SP,
1164                            local,
1165                            imported: Some(ModuleExportName::Ident(imported.into())),
1166                            is_type_only: false,
1167                        })
1168                    })
1169                    .collect();
1170
1171                prepend_stmt(
1172                    stmts,
1173                    ImportDecl {
1174                        span: DUMMY_SP,
1175                        specifiers,
1176                        src: Str {
1177                            span: DUMMY_SP,
1178                            raw: None,
1179                            value: src.into(),
1180                        }
1181                        .into(),
1182                        type_only: Default::default(),
1183                        with: Default::default(),
1184                        phase: Default::default(),
1185                    }
1186                    .into(),
1187                )
1188            });
1189        }
1190    }
1191
1192    fn visit_mut_script(&mut self, script: &mut Script) {
1193        self.parse_directives(script.span);
1194
1195        for item in &script.body {
1196            let span = item.span();
1197            if self.parse_directives(span) {
1198                break;
1199            }
1200        }
1201
1202        script.visit_mut_children_with(self);
1203
1204        if self.runtime == Runtime::Automatic {
1205            let mark = self.unresolved_mark;
1206            self.inject_runtime(&mut script.body, |imports, src, stmts| {
1207                prepend_stmt(stmts, add_require(imports, src, mark))
1208            });
1209        }
1210    }
1211}
1212
1213// const { createElement } = require('react')
1214// const { jsx: jsx } = require('react/jsx-runtime')
1215fn add_require(imports: Vec<(Ident, IdentName)>, src: &str, unresolved_mark: Mark) -> Stmt {
1216    VarDecl {
1217        span: DUMMY_SP,
1218        kind: VarDeclKind::Const,
1219        declare: false,
1220        decls: vec![VarDeclarator {
1221            span: DUMMY_SP,
1222            name: Pat::Object(ObjectPat {
1223                span: DUMMY_SP,
1224                props: imports
1225                    .into_iter()
1226                    .map(|(local, imported)| {
1227                        if imported.sym != local.sym {
1228                            ObjectPatProp::KeyValue(KeyValuePatProp {
1229                                key: PropName::Ident(imported),
1230                                value: Box::new(Pat::Ident(local.into())),
1231                            })
1232                        } else {
1233                            ObjectPatProp::Assign(AssignPatProp {
1234                                span: DUMMY_SP,
1235                                key: local.into(),
1236                                value: None,
1237                            })
1238                        }
1239                    })
1240                    .collect(),
1241                optional: false,
1242                type_ann: None,
1243            }),
1244            // require('react')
1245            init: Some(Box::new(Expr::Call(CallExpr {
1246                span: DUMMY_SP,
1247                callee: Callee::Expr(Box::new(Expr::Ident(Ident {
1248                    ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1249                    sym: "require".into(),
1250                    optional: false,
1251                    ..Default::default()
1252                }))),
1253                args: vec![ExprOrSpread {
1254                    spread: None,
1255                    expr: Box::new(Expr::Lit(Lit::Str(Str {
1256                        span: DUMMY_SP,
1257                        value: src.into(),
1258                        raw: None,
1259                    }))),
1260                }],
1261                ..Default::default()
1262            }))),
1263            definite: false,
1264        }],
1265        ..Default::default()
1266    }
1267    .into()
1268}
1269
1270impl<C> Jsx<C>
1271where
1272    C: Comments,
1273{
1274    fn jsx_name(&self, name: JSXElementName) -> Box<Expr> {
1275        let span = name.span();
1276        match name {
1277            JSXElementName::Ident(i) => {
1278                if i.sym == "this" {
1279                    return ThisExpr { span }.into();
1280                }
1281
1282                // If it starts with lowercase
1283                if i.as_ref().starts_with(|c: char| c.is_ascii_lowercase()) {
1284                    Lit::Str(Str {
1285                        span,
1286                        raw: None,
1287                        value: i.sym,
1288                    })
1289                    .into()
1290                } else {
1291                    i.into()
1292                }
1293            }
1294            JSXElementName::JSXNamespacedName(JSXNamespacedName {
1295                ref ns, ref name, ..
1296            }) => {
1297                if self.throw_if_namespace {
1298                    HANDLER.with(|handler| {
1299                        handler
1300                            .struct_span_err(
1301                                span,
1302                                "JSX Namespace is disabled by default because react does not \
1303                                 support it yet. You can specify \
1304                                 jsc.transform.react.throwIfNamespace to false to override \
1305                                 default behavior",
1306                            )
1307                            .emit()
1308                    });
1309                }
1310
1311                let value = format!("{}:{}", ns.sym, name.sym);
1312
1313                Lit::Str(Str {
1314                    span,
1315                    raw: None,
1316                    value: value.into(),
1317                })
1318                .into()
1319            }
1320            JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop, .. }) => {
1321                fn convert_obj(obj: JSXObject) -> Box<Expr> {
1322                    let span = obj.span();
1323
1324                    (match obj {
1325                        JSXObject::Ident(i) => {
1326                            if i.sym == "this" {
1327                                Expr::This(ThisExpr { span })
1328                            } else {
1329                                i.into()
1330                            }
1331                        }
1332                        JSXObject::JSXMemberExpr(e) => MemberExpr {
1333                            span,
1334                            obj: convert_obj(e.obj),
1335                            prop: MemberProp::Ident(e.prop),
1336                        }
1337                        .into(),
1338                    })
1339                    .into()
1340                }
1341                MemberExpr {
1342                    span,
1343                    obj: convert_obj(obj),
1344                    prop: MemberProp::Ident(prop),
1345                }
1346                .into()
1347            }
1348        }
1349    }
1350}
1351
1352fn to_prop_name(n: JSXAttrName) -> PropName {
1353    let span = n.span();
1354
1355    match n {
1356        JSXAttrName::Ident(i) => {
1357            if i.sym.contains('-') {
1358                PropName::Str(Str {
1359                    span,
1360                    raw: None,
1361                    value: i.sym,
1362                })
1363            } else {
1364                PropName::Ident(i)
1365            }
1366        }
1367        JSXAttrName::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => {
1368            let value = format!("{}:{}", ns.sym, name.sym);
1369
1370            PropName::Str(Str {
1371                span,
1372                raw: None,
1373                value: value.into(),
1374            })
1375        }
1376    }
1377}
1378
1379#[inline]
1380fn jsx_text_to_str(t: Atom) -> Atom {
1381    let mut buf = String::new();
1382    let replaced = t.replace('\t', " ");
1383
1384    for (is_last, (i, line)) in replaced.lines().enumerate().identify_last() {
1385        if line.is_empty() {
1386            continue;
1387        }
1388        let line = Cow::from(line);
1389        let line = if i != 0 {
1390            Cow::Borrowed(line.trim_start_matches(' '))
1391        } else {
1392            line
1393        };
1394        let line = if is_last {
1395            line
1396        } else {
1397            Cow::Borrowed(line.trim_end_matches(' '))
1398        };
1399        if line.is_empty() {
1400            continue;
1401        }
1402        if i != 0 && !buf.is_empty() {
1403            buf.push(' ')
1404        }
1405        buf.push_str(&line);
1406    }
1407    buf.into()
1408}
1409
1410fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option<Box<Expr>> {
1411    Some(match v {
1412        JSXAttrValue::Lit(Lit::Str(s)) => {
1413            let value = transform_jsx_attr_str(&s.value);
1414
1415            Lit::Str(Str {
1416                span: s.span,
1417                raw: None,
1418                value: value.into(),
1419            })
1420            .into()
1421        }
1422        JSXAttrValue::Lit(lit) => Box::new(lit.into()),
1423        JSXAttrValue::JSXExprContainer(e) => match e.expr {
1424            JSXExpr::JSXEmptyExpr(_) => None?,
1425            JSXExpr::Expr(e) => e,
1426        },
1427        JSXAttrValue::JSXElement(e) => e.into(),
1428        JSXAttrValue::JSXFragment(f) => f.into(),
1429    })
1430}
1431
1432fn count_children(children: &[JSXElementChild]) -> usize {
1433    children
1434        .iter()
1435        .filter(|v| match v {
1436            JSXElementChild::JSXText(text) => {
1437                let text = jsx_text_to_str(text.value.clone());
1438                !text.is_empty()
1439            }
1440            JSXElementChild::JSXExprContainer(e) => match e.expr {
1441                JSXExpr::JSXEmptyExpr(_) => false,
1442                JSXExpr::Expr(_) => true,
1443            },
1444            JSXElementChild::JSXSpreadChild(_) => true,
1445            JSXElementChild::JSXElement(_) => true,
1446            JSXElementChild::JSXFragment(_) => true,
1447        })
1448        .count()
1449}
1450
1451fn transform_jsx_attr_str(v: &str) -> String {
1452    let single_quote = false;
1453    let mut buf = String::with_capacity(v.len());
1454    let mut iter = v.chars().peekable();
1455
1456    while let Some(c) = iter.next() {
1457        match c {
1458            '\u{0008}' => buf.push_str("\\b"),
1459            '\u{000c}' => buf.push_str("\\f"),
1460            ' ' => buf.push(' '),
1461
1462            '\n' | '\r' | '\t' => {
1463                buf.push(' ');
1464
1465                while let Some(' ') = iter.peek() {
1466                    iter.next();
1467                }
1468            }
1469            '\u{000b}' => buf.push_str("\\v"),
1470            '\0' => buf.push_str("\\x00"),
1471
1472            '\'' if single_quote => buf.push_str("\\'"),
1473            '"' if !single_quote => buf.push('\"'),
1474
1475            '\x01'..='\x0f' | '\x10'..='\x1f' => {
1476                buf.push(c);
1477            }
1478
1479            '\x20'..='\x7e' => {
1480                //
1481                buf.push(c);
1482            }
1483            '\u{7f}'..='\u{ff}' => {
1484                buf.push(c);
1485            }
1486
1487            _ => {
1488                buf.push(c);
1489            }
1490        }
1491    }
1492
1493    buf
1494}