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