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