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