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