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