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#[derive(StringEnum, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
37pub enum Runtime {
38 Automatic,
40 Classic,
42}
43
44impl 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 #[serde(skip, default)]
58 pub next: Option<bool>,
59
60 #[serde(default)]
61 pub runtime: Option<Runtime>,
62
63 #[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 #[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 #[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 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
133pub 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
183pub 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 import_source: Atom,
257 import_jsx: Option<Ident>,
259 import_jsxs: Option<Ident>,
261 import_create_element: Option<Ident>,
263 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 pub import_source: Option<Atom>,
280
281 pub pragma: Option<Lrc<Box<Expr>>>,
283
284 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 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 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 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 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 .chain(iter::once(Lit::Null(Null { span: DUMMY_SP }).as_arg()))
625 .chain({
626 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 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 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 match attr.name {
700 JSXAttrName::Ident(i) => {
701 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 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 let key = match key {
875 Some(key) => key,
876 None => Expr::undefined(DUMMY_SP).as_arg(),
877 };
878
879 let source_props = match source_props {
881 Some(source_props) => source_props,
882 None => Expr::undefined(DUMMY_SP).as_arg(),
883 };
884
885 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 self.fold_attrs_for_classic(el.opening.attrs).as_arg()
914 }))
915 .chain({
916 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 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 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 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 *expr = self.jsx_elem_to_expr(*el.take());
1115 } else if let Expr::JSXFragment(frag) = expr {
1116 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 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
1210fn 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 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 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 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}