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
292impl JsxDirectives {
293 pub fn from_comments(
294 cm: &SourceMap,
295 _: Span,
296 comments: &[Comment],
297 top_level_mark: Mark,
298 ) -> Self {
299 let mut res = JsxDirectives::default();
300
301 for cmt in comments {
302 if cmt.kind != CommentKind::Block {
303 continue;
304 }
305
306 for line in cmt.text.lines() {
307 let mut line = line.trim();
308 if line.starts_with('*') {
309 line = line[1..].trim();
310 }
311
312 if !line.starts_with("@jsx") {
313 continue;
314 }
315
316 let mut words = line.split_whitespace();
317 loop {
318 let pragma = words.next();
319 if pragma.is_none() {
320 break;
321 }
322 let val = words.next();
323
324 match pragma {
325 Some("@jsxRuntime") => match val {
326 Some("classic") => res.runtime = Some(Runtime::Classic),
327 Some("automatic") => res.runtime = Some(Runtime::Automatic),
328 None => {}
329 _ => {
330 if HANDLER.is_set() {
331 HANDLER.with(|handler| {
332 handler
333 .struct_span_err(
334 cmt.span,
335 "Runtime must be either `classic` or `automatic`.",
336 )
337 .emit()
338 });
339 }
340 }
341 },
342 Some("@jsxImportSource") => {
343 if let Some(src) = val {
344 res.runtime = Some(Runtime::Automatic);
345 res.import_source = Some(Atom::new(src));
346 }
347 }
348 Some("@jsxFrag") => {
349 if let Some(src) = val {
350 if is_valid_for_pragma(src) {
351 let mut e = parse_expr_for_jsx(
353 cm,
354 "module-jsx-pragma-frag",
355 cache_source(src),
356 top_level_mark,
357 );
358 e.set_span(cmt.span);
359 res.pragma_frag = Some(e.into())
360 }
361 }
362 }
363 Some("@jsx") => {
364 if let Some(src) = val {
365 if is_valid_for_pragma(src) {
366 let mut e = parse_expr_for_jsx(
368 cm,
369 "module-jsx-pragma",
370 cache_source(src),
371 top_level_mark,
372 );
373 e.set_span(cmt.span);
374 res.pragma = Some(e.into());
375 }
376 }
377 }
378 _ => {}
379 }
380 }
381 }
382 }
383
384 res
385 }
386}
387
388#[cfg(feature = "concurrent")]
389fn cache_filename(name: &str) -> Lrc<FileName> {
390 static FILENAME_CACHE: Lazy<RwLock<FxHashMap<String, Lrc<FileName>>>> =
391 Lazy::new(|| RwLock::new(FxHashMap::default()));
392
393 {
394 let cache = FILENAME_CACHE
395 .read()
396 .expect("Failed to read FILENAME_CACHE");
397 if let Some(f) = cache.get(name) {
398 return f.clone();
399 }
400 }
401
402 let file = Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")));
403
404 {
405 let mut cache = FILENAME_CACHE
406 .write()
407 .expect("Failed to write FILENAME_CACHE");
408 cache.insert(name.to_string(), file.clone());
409 }
410
411 file
412}
413
414#[cfg(not(feature = "concurrent"))]
415fn cache_filename(name: &str) -> Lrc<FileName> {
416 Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")))
417}
418
419#[cfg(feature = "concurrent")]
420fn cache_source(src: &str) -> BytesStr {
421 use rustc_hash::FxHashSet;
422
423 static CACHE: Lazy<RwLock<FxHashSet<BytesStr>>> =
424 Lazy::new(|| RwLock::new(FxHashSet::default()));
425
426 {
427 let cache = CACHE.write().unwrap();
428
429 if let Some(cached) = cache.get(src) {
430 return cached.clone();
431 }
432 }
433
434 let cached: BytesStr = src.to_string().into();
435 {
436 let mut cache = CACHE.write().unwrap();
437 cache.insert(cached.clone());
438 }
439 cached
440}
441
442#[cfg(not(feature = "concurrent"))]
443fn cache_source(src: &str) -> BytesStr {
444 src.to_string().into()
446}
447
448fn is_valid_for_pragma(s: &str) -> bool {
449 if s.is_empty() {
450 return false;
451 }
452
453 if !s.starts_with(|c: char| Ident::is_valid_start(c)) {
454 return false;
455 }
456
457 for c in s.chars() {
458 if !Ident::is_valid_continue(c) && c != '.' {
459 return false;
460 }
461 }
462
463 true
464}
465
466impl<C> Jsx<C>
467where
468 C: Comments,
469{
470 fn inject_runtime<T, F>(&mut self, body: &mut Vec<T>, inject: F)
471 where
472 T: StmtLike,
473 F: Fn(Vec<(Ident, IdentName)>, &str, &mut Vec<T>),
475 {
476 if self.runtime == Runtime::Automatic {
477 if let Some(local) = self.import_create_element.take() {
478 inject(
479 vec![(local, quote_ident!("createElement"))],
480 &self.import_source,
481 body,
482 );
483 }
484
485 let imports = self.import_jsx.take();
486 let imports = if self.development {
487 imports
488 .map(|local| (local, quote_ident!("jsxDEV")))
489 .into_iter()
490 .chain(
491 self.import_fragment
492 .take()
493 .map(|local| (local, quote_ident!("Fragment"))),
494 )
495 .collect::<Vec<_>>()
496 } else {
497 imports
498 .map(|local| (local, quote_ident!("jsx")))
499 .into_iter()
500 .chain(
501 self.import_jsxs
502 .take()
503 .map(|local| (local, quote_ident!("jsxs"))),
504 )
505 .chain(
506 self.import_fragment
507 .take()
508 .map(|local| (local, quote_ident!("Fragment"))),
509 )
510 .collect::<Vec<_>>()
511 };
512
513 if !imports.is_empty() {
514 let jsx_runtime = if self.development {
515 "jsx-dev-runtime"
516 } else {
517 "jsx-runtime"
518 };
519
520 let value = format!("{}/{}", self.import_source, jsx_runtime);
521 inject(imports, &value, body)
522 }
523 }
524 }
525
526 fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
527 let mut span = el.span();
528
529 if let Some(comments) = &self.comments {
530 if span.lo.is_dummy() {
531 span.lo = Span::dummy_with_cmt().lo;
532 }
533
534 comments.add_pure_comment(span.lo);
535 }
536
537 match self.runtime {
538 Runtime::Automatic => {
539 let fragment = self
540 .import_fragment
541 .get_or_insert_with(|| private_ident!("_Fragment"))
542 .clone();
543
544 let mut props_obj = ObjectLit {
545 span: DUMMY_SP,
546 props: Vec::new(),
547 };
548
549 let children = el
550 .children
551 .into_iter()
552 .filter_map(|child| self.jsx_elem_child_to_expr(child))
553 .map(Some)
554 .collect::<Vec<_>>();
555
556 let use_jsxs = match children.len() {
557 0 => false,
558 1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
559 props_obj
560 .props
561 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
562 key: PropName::Ident(quote_ident!("children")),
563 value: children.into_iter().next().flatten().unwrap().expr,
564 }))));
565
566 false
567 }
568 _ => {
569 props_obj
570 .props
571 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
572 key: PropName::Ident(quote_ident!("children")),
573 value: ArrayLit {
574 span: DUMMY_SP,
575 elems: children,
576 }
577 .into(),
578 }))));
579 true
580 }
581 };
582
583 let jsx = if use_jsxs && !self.development {
584 self.import_jsxs
585 .get_or_insert_with(|| private_ident!("_jsxs"))
586 .clone()
587 } else {
588 let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
589 self.import_jsx
590 .get_or_insert_with(|| private_ident!(jsx))
591 .clone()
592 };
593
594 let args = once(fragment.as_arg()).chain(once(props_obj.as_arg()));
595
596 let args = if self.development {
597 args.chain(once(Expr::undefined(DUMMY_SP).as_arg()))
598 .chain(once(use_jsxs.as_arg()))
599 .collect()
600 } else {
601 args.collect()
602 };
603
604 CallExpr {
605 span,
606 callee: jsx.as_callee(),
607 args,
608 ..Default::default()
609 }
610 .into()
611 }
612 Runtime::Classic => {
613 CallExpr {
614 span,
615 callee: (*self.pragma).clone().as_callee(),
616 args: iter::once((*self.pragma_frag).clone().as_arg())
617 .chain(iter::once(Lit::Null(Null { span: DUMMY_SP }).as_arg()))
619 .chain({
620 el.children
622 .into_iter()
623 .filter_map(|c| self.jsx_elem_child_to_expr(c))
624 })
625 .collect(),
626 ..Default::default()
627 }
628 .into()
629 }
630 Runtime::Preserve => unreachable!(),
631 }
632 }
633
634 fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr {
642 let top_level_node = self.top_level_node;
643 let mut span = el.span();
644 let use_create_element = should_use_create_element(&el.opening.attrs);
645 self.top_level_node = false;
646
647 let name = self.jsx_name(el.opening.name);
648
649 if let Some(comments) = &self.comments {
650 if span.lo.is_dummy() {
651 span.lo = Span::dummy_with_cmt().lo;
652 }
653
654 comments.add_pure_comment(span.lo);
655 }
656
657 match self.runtime {
658 Runtime::Automatic => {
659 let mut props_obj = ObjectLit {
662 span: DUMMY_SP,
663 props: Vec::new(),
664 };
665
666 let mut key = None;
667 let mut source_props = None;
668 let mut self_props = None;
669
670 for attr in el.opening.attrs {
671 match attr {
672 JSXAttrOrSpread::JSXAttr(attr) => {
673 match attr.name {
675 JSXAttrName::Ident(i) => {
676 if !use_create_element && i.sym == "key" {
678 key = attr
679 .value
680 .and_then(jsx_attr_value_to_expr)
681 .map(|expr| expr.as_arg());
682
683 if key.is_none() && HANDLER.is_set() {
684 HANDLER.with(|handler| {
685 handler
686 .struct_span_err(
687 i.span,
688 "The value of property 'key' should not \
689 be empty",
690 )
691 .emit();
692 });
693 }
694 continue;
695 }
696
697 if !use_create_element
698 && *i.sym == *"__source"
699 && self.development
700 {
701 if source_props.is_some() {
702 panic!("Duplicate __source is found");
703 }
704 source_props = attr
705 .value
706 .and_then(jsx_attr_value_to_expr)
707 .map(|expr| expr.as_arg());
708 assert_ne!(
709 source_props, None,
710 "value of property '__source' should not be empty"
711 );
712 continue;
713 }
714
715 if !use_create_element
716 && *i.sym == *"__self"
717 && self.development
718 {
719 if self_props.is_some() {
720 panic!("Duplicate __self is found");
721 }
722 self_props = attr
723 .value
724 .and_then(jsx_attr_value_to_expr)
725 .map(|expr| expr.as_arg());
726 assert_ne!(
727 self_props, None,
728 "value of property '__self' should not be empty"
729 );
730 continue;
731 }
732
733 let value = match attr.value {
734 Some(v) => jsx_attr_value_to_expr(v)
735 .expect("empty expression container?"),
736 None => true.into(),
737 };
738
739 let key = if i.sym.contains('-') {
741 PropName::Str(Str {
742 span: i.span,
743 raw: None,
744 value: i.sym,
745 })
746 } else {
747 PropName::Ident(i)
748 };
749 props_obj.props.push(PropOrSpread::Prop(Box::new(
750 Prop::KeyValue(KeyValueProp { key, value }),
751 )));
752 }
753 JSXAttrName::JSXNamespacedName(JSXNamespacedName {
754 ns,
755 name,
756 ..
757 }) => {
758 if self.throw_if_namespace && HANDLER.is_set() {
759 HANDLER.with(|handler| {
760 handler
761 .struct_span_err(
762 span,
763 "JSX Namespace is disabled by default because \
764 react does not support it yet. You can \
765 specify jsc.transform.react.throwIfNamespace \
766 to false to override default behavior",
767 )
768 .emit()
769 });
770 }
771
772 let value = match attr.value {
773 Some(v) => jsx_attr_value_to_expr(v)
774 .expect("empty expression container?"),
775 None => true.into(),
776 };
777
778 let str_value = format!("{}:{}", ns.sym, name.sym);
779 let key = Str {
780 span,
781 raw: None,
782 value: str_value.into(),
783 };
784 let key = PropName::Str(key);
785
786 props_obj.props.push(PropOrSpread::Prop(Box::new(
787 Prop::KeyValue(KeyValueProp { key, value }),
788 )));
789 }
790 #[cfg(swc_ast_unknown)]
791 _ => panic!("unable to access unknown nodes"),
792 }
793 }
794 JSXAttrOrSpread::SpreadElement(attr) => match *attr.expr {
795 Expr::Object(obj) => {
796 props_obj.props.extend(obj.props);
797 }
798 _ => {
799 props_obj.props.push(PropOrSpread::Spread(attr));
800 }
801 },
802 #[cfg(swc_ast_unknown)]
803 _ => panic!("unable to access unknown nodes"),
804 }
805 }
806
807 let mut children = el
808 .children
809 .into_iter()
810 .filter_map(|child| self.jsx_elem_child_to_expr(child))
811 .map(Some)
812 .collect::<Vec<_>>();
813
814 let use_jsxs = match children.len() {
815 0 => false,
816 1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
817 if !use_create_element {
818 props_obj
819 .props
820 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
821 key: PropName::Ident(quote_ident!("children")),
822 value: children
823 .take()
824 .into_iter()
825 .next()
826 .flatten()
827 .unwrap()
828 .expr,
829 }))));
830 }
831
832 false
833 }
834 _ => {
835 props_obj
836 .props
837 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
838 key: PropName::Ident(quote_ident!("children")),
839 value: ArrayLit {
840 span: DUMMY_SP,
841 elems: children.take(),
842 }
843 .into(),
844 }))));
845 true
846 }
847 };
848
849 let jsx = if use_create_element {
850 self.import_create_element
851 .get_or_insert_with(|| private_ident!("_createElement"))
852 .clone()
853 } else if use_jsxs && !self.development {
854 self.import_jsxs
855 .get_or_insert_with(|| private_ident!("_jsxs"))
856 .clone()
857 } else {
858 let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
859 self.import_jsx
860 .get_or_insert_with(|| private_ident!(jsx))
861 .clone()
862 };
863
864 self.top_level_node = top_level_node;
865
866 let args = once(name.as_arg()).chain(once(props_obj.as_arg()));
867 let args = if use_create_element {
868 args.chain(children.into_iter().flatten()).collect()
869 } else if self.development {
870 let key = match key {
872 Some(key) => key,
873 None => Expr::undefined(DUMMY_SP).as_arg(),
874 };
875
876 let source_props = match source_props {
878 Some(source_props) => source_props,
879 None => Expr::undefined(DUMMY_SP).as_arg(),
880 };
881
882 let self_props = match self_props {
884 Some(self_props) => self_props,
885 None => Expr::undefined(DUMMY_SP).as_arg(),
886 };
887 args.chain(once(key))
888 .chain(once(use_jsxs.as_arg()))
889 .chain(once(source_props))
890 .chain(once(self_props))
891 .collect()
892 } else {
893 args.chain(key).collect()
894 };
895 CallExpr {
896 span,
897 callee: jsx.as_callee(),
898 args,
899 ..Default::default()
900 }
901 .into()
902 }
903 Runtime::Classic => {
904 CallExpr {
905 span,
906 callee: (*self.pragma).clone().as_callee(),
907 args: iter::once(name.as_arg())
908 .chain(iter::once({
909 self.fold_attrs_for_classic(el.opening.attrs).as_arg()
911 }))
912 .chain({
913 el.children
915 .into_iter()
916 .filter_map(|c| self.jsx_elem_child_to_expr(c))
917 })
918 .collect(),
919 ..Default::default()
920 }
921 .into()
922 }
923 Runtime::Preserve => unreachable!(),
924 }
925 }
926
927 fn jsx_elem_child_to_expr(&mut self, c: JSXElementChild) -> Option<ExprOrSpread> {
928 self.top_level_node = false;
929
930 Some(match c {
931 JSXElementChild::JSXText(text) => {
932 let value = jsx_text_to_str(&text.value);
934 let s = Str {
935 span: text.span,
936 raw: None,
937 value,
938 };
939
940 if s.value.is_empty() {
941 return None;
942 }
943
944 Lit::Str(s).as_arg()
945 }
946 JSXElementChild::JSXExprContainer(JSXExprContainer {
947 expr: JSXExpr::Expr(e),
948 ..
949 }) => e.as_arg(),
950 JSXElementChild::JSXExprContainer(JSXExprContainer {
951 expr: JSXExpr::JSXEmptyExpr(..),
952 ..
953 }) => return None,
954 JSXElementChild::JSXElement(el) => self.jsx_elem_to_expr(*el).as_arg(),
955 JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
956 JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => ExprOrSpread {
957 spread: Some(span),
958 expr,
959 },
960 #[cfg(swc_ast_unknown)]
961 _ => panic!("unable to access unknown nodes"),
962 })
963 }
964
965 fn fold_attrs_for_classic(&mut self, attrs: Vec<JSXAttrOrSpread>) -> Box<Expr> {
966 if attrs.is_empty() {
967 return Lit::Null(Null { span: DUMMY_SP }).into();
968 }
969 let attr_cnt = attrs.len();
970
971 let mut props = Vec::new();
972 for attr in attrs {
973 match attr {
974 JSXAttrOrSpread::JSXAttr(attr) => {
975 props.push(PropOrSpread::Prop(Box::new(self.attr_to_prop(attr))))
976 }
977 JSXAttrOrSpread::SpreadElement(spread) => {
978 if attr_cnt == 1 {
979 return spread.expr;
980 }
981 match *spread.expr {
983 Expr::Object(obj) => props.extend(obj.props),
984 _ => props.push(PropOrSpread::Spread(spread)),
985 }
986 }
987 #[cfg(swc_ast_unknown)]
988 _ => panic!("unable to access unknown nodes"),
989 }
990 }
991
992 let obj = ObjectLit {
993 span: DUMMY_SP,
994 props,
995 };
996
997 obj.into()
998 }
999
1000 fn attr_to_prop(&mut self, a: JSXAttr) -> Prop {
1001 let key = to_prop_name(a.name);
1002 let value = a
1003 .value
1004 .map(|v| match v {
1005 JSXAttrValue::Lit(Lit::Str(s)) => {
1006 let value = transform_jsx_attr_str(&s.value);
1007
1008 Lit::Str(Str {
1009 span: s.span,
1010 raw: None,
1011 value: value.into(),
1012 })
1013 .into()
1014 }
1015 JSXAttrValue::JSXExprContainer(JSXExprContainer {
1016 expr: JSXExpr::Expr(e),
1017 ..
1018 }) => e,
1019 JSXAttrValue::JSXElement(element) => Box::new(self.jsx_elem_to_expr(*element)),
1020 JSXAttrValue::JSXFragment(fragment) => Box::new(self.jsx_frag_to_expr(fragment)),
1021 JSXAttrValue::Lit(lit) => Box::new(lit.into()),
1022 JSXAttrValue::JSXExprContainer(JSXExprContainer {
1023 span: _,
1024 expr: JSXExpr::JSXEmptyExpr(_),
1025 }) => unreachable!("attr_to_prop(JSXEmptyExpr)"),
1026 #[cfg(swc_ast_unknown)]
1027 _ => panic!("unable to access unknown nodes"),
1028 })
1029 .unwrap_or_else(|| {
1030 Lit::Bool(Bool {
1031 span: key.span(),
1032 value: true,
1033 })
1034 .into()
1035 });
1036 Prop::KeyValue(KeyValueProp { key, value })
1037 }
1038}
1039
1040impl<C> Jsx<C>
1041where
1042 C: Comments,
1043{
1044 fn parse_directives(&mut self, span: Span) -> bool {
1046 let mut found = false;
1047
1048 let directives = self.comments.with_leading(span.lo, |comments| {
1049 JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
1050 });
1051
1052 let JsxDirectives {
1053 runtime,
1054 import_source,
1055 pragma,
1056 pragma_frag,
1057 } = directives;
1058
1059 if let Some(runtime) = runtime {
1060 found = true;
1061 self.runtime = runtime;
1062 }
1063
1064 if let Some(import_source) = import_source {
1065 found = true;
1066 self.import_source = import_source;
1067 }
1068
1069 if let Some(pragma) = pragma {
1070 if let Runtime::Automatic = self.runtime {
1071 if HANDLER.is_set() {
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
1083 found = true;
1084 self.pragma = pragma;
1085 }
1086
1087 if let Some(pragma_frag) = pragma_frag {
1088 if let Runtime::Automatic = self.runtime {
1089 if HANDLER.is_set() {
1090 HANDLER.with(|handler| {
1091 handler
1092 .struct_span_err(
1093 pragma_frag.span(),
1094 "pragmaFrag cannot be set when runtime is automatic",
1095 )
1096 .emit()
1097 });
1098 }
1099 }
1100
1101 found = true;
1102 self.pragma_frag = pragma_frag;
1103 }
1104
1105 found
1106 }
1107}
1108
1109impl<C> VisitMut for Jsx<C>
1110where
1111 C: Comments,
1112{
1113 noop_visit_mut_type!();
1114
1115 fn visit_mut_expr(&mut self, expr: &mut Expr) {
1116 let top_level_node = self.top_level_node;
1117 let mut did_work = false;
1118
1119 if let Expr::JSXElement(el) = expr {
1120 did_work = true;
1121 *expr = self.jsx_elem_to_expr(*el.take());
1123 } else if let Expr::JSXFragment(frag) = expr {
1124 did_work = true;
1126 *expr = self.jsx_frag_to_expr(frag.take());
1127 } else if let Expr::Paren(ParenExpr {
1128 expr: inner_expr, ..
1129 }) = expr
1130 {
1131 if let Expr::JSXElement(el) = &mut **inner_expr {
1132 did_work = true;
1133 *expr = self.jsx_elem_to_expr(*el.take());
1134 } else if let Expr::JSXFragment(frag) = &mut **inner_expr {
1135 did_work = true;
1137 *expr = self.jsx_frag_to_expr(frag.take());
1138 }
1139 }
1140
1141 if did_work {
1142 self.top_level_node = false;
1143 }
1144
1145 expr.visit_mut_children_with(self);
1146
1147 self.top_level_node = top_level_node;
1148 }
1149
1150 fn visit_mut_module(&mut self, module: &mut Module) {
1151 self.parse_directives(module.span);
1152
1153 for item in &module.body {
1154 let span = item.span();
1155 if self.parse_directives(span) {
1156 break;
1157 }
1158 }
1159
1160 module.visit_mut_children_with(self);
1161
1162 if self.runtime == Runtime::Automatic {
1163 self.inject_runtime(&mut module.body, |imports, src, stmts| {
1164 let specifiers = imports
1165 .into_iter()
1166 .map(|(local, imported)| {
1167 ImportSpecifier::Named(ImportNamedSpecifier {
1168 span: DUMMY_SP,
1169 local,
1170 imported: Some(ModuleExportName::Ident(imported.into())),
1171 is_type_only: false,
1172 })
1173 })
1174 .collect();
1175
1176 prepend_stmt(
1177 stmts,
1178 ImportDecl {
1179 span: DUMMY_SP,
1180 specifiers,
1181 src: Str {
1182 span: DUMMY_SP,
1183 raw: None,
1184 value: src.into(),
1185 }
1186 .into(),
1187 type_only: Default::default(),
1188 with: Default::default(),
1189 phase: Default::default(),
1190 }
1191 .into(),
1192 )
1193 });
1194 }
1195 }
1196
1197 fn visit_mut_script(&mut self, script: &mut Script) {
1198 self.parse_directives(script.span);
1199
1200 for item in &script.body {
1201 let span = item.span();
1202 if self.parse_directives(span) {
1203 break;
1204 }
1205 }
1206
1207 script.visit_mut_children_with(self);
1208
1209 if self.runtime == Runtime::Automatic {
1210 let mark = self.unresolved_mark;
1211 self.inject_runtime(&mut script.body, |imports, src, stmts| {
1212 prepend_stmt(stmts, add_require(imports, src, mark))
1213 });
1214 }
1215 }
1216}
1217
1218fn add_require(imports: Vec<(Ident, IdentName)>, src: &str, unresolved_mark: Mark) -> Stmt {
1221 VarDecl {
1222 span: DUMMY_SP,
1223 kind: VarDeclKind::Const,
1224 declare: false,
1225 decls: vec![VarDeclarator {
1226 span: DUMMY_SP,
1227 name: Pat::Object(ObjectPat {
1228 span: DUMMY_SP,
1229 props: imports
1230 .into_iter()
1231 .map(|(local, imported)| {
1232 if imported.sym != local.sym {
1233 ObjectPatProp::KeyValue(KeyValuePatProp {
1234 key: PropName::Ident(imported),
1235 value: Box::new(Pat::Ident(local.into())),
1236 })
1237 } else {
1238 ObjectPatProp::Assign(AssignPatProp {
1239 span: DUMMY_SP,
1240 key: local.into(),
1241 value: None,
1242 })
1243 }
1244 })
1245 .collect(),
1246 optional: false,
1247 type_ann: None,
1248 }),
1249 init: Some(Box::new(Expr::Call(CallExpr {
1251 span: DUMMY_SP,
1252 callee: Callee::Expr(Box::new(Expr::Ident(Ident {
1253 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1254 sym: atom!("require"),
1255 optional: false,
1256 ..Default::default()
1257 }))),
1258 args: vec![ExprOrSpread {
1259 spread: None,
1260 expr: Box::new(Expr::Lit(Lit::Str(Str {
1261 span: DUMMY_SP,
1262 value: src.into(),
1263 raw: None,
1264 }))),
1265 }],
1266 ..Default::default()
1267 }))),
1268 definite: false,
1269 }],
1270 ..Default::default()
1271 }
1272 .into()
1273}
1274
1275impl<C> Jsx<C>
1276where
1277 C: Comments,
1278{
1279 fn jsx_name(&self, name: JSXElementName) -> Box<Expr> {
1280 let span = name.span();
1281 match name {
1282 JSXElementName::Ident(i) => {
1283 if i.sym == "this" {
1284 return ThisExpr { span }.into();
1285 }
1286
1287 if i.as_ref().starts_with(|c: char| c.is_ascii_lowercase()) {
1289 Lit::Str(Str {
1290 span,
1291 raw: None,
1292 value: i.sym,
1293 })
1294 .into()
1295 } else {
1296 i.into()
1297 }
1298 }
1299 JSXElementName::JSXNamespacedName(JSXNamespacedName {
1300 ref ns, ref name, ..
1301 }) => {
1302 if self.throw_if_namespace && HANDLER.is_set() {
1303 HANDLER.with(|handler| {
1304 handler
1305 .struct_span_err(
1306 span,
1307 "JSX Namespace is disabled by default because react does not \
1308 support it yet. You can specify \
1309 jsc.transform.react.throwIfNamespace to false to override \
1310 default behavior",
1311 )
1312 .emit()
1313 });
1314 }
1315
1316 let value = format!("{}:{}", ns.sym, name.sym);
1317
1318 Lit::Str(Str {
1319 span,
1320 raw: None,
1321 value: value.into(),
1322 })
1323 .into()
1324 }
1325 JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop, .. }) => {
1326 fn convert_obj(obj: JSXObject) -> Box<Expr> {
1327 let span = obj.span();
1328
1329 (match obj {
1330 JSXObject::Ident(i) => {
1331 if i.sym == "this" {
1332 Expr::This(ThisExpr { span })
1333 } else {
1334 i.into()
1335 }
1336 }
1337 JSXObject::JSXMemberExpr(e) => MemberExpr {
1338 span,
1339 obj: convert_obj(e.obj),
1340 prop: MemberProp::Ident(e.prop),
1341 }
1342 .into(),
1343 #[cfg(swc_ast_unknown)]
1344 _ => panic!("unable to access unknown nodes"),
1345 })
1346 .into()
1347 }
1348 MemberExpr {
1349 span,
1350 obj: convert_obj(obj),
1351 prop: MemberProp::Ident(prop),
1352 }
1353 .into()
1354 }
1355 #[cfg(swc_ast_unknown)]
1356 _ => panic!("unable to access unknown nodes"),
1357 }
1358 }
1359}
1360
1361fn to_prop_name(n: JSXAttrName) -> PropName {
1362 let span = n.span();
1363
1364 match n {
1365 JSXAttrName::Ident(i) => {
1366 if i.sym.contains('-') {
1367 PropName::Str(Str {
1368 span,
1369 raw: None,
1370 value: i.sym,
1371 })
1372 } else {
1373 PropName::Ident(i)
1374 }
1375 }
1376 JSXAttrName::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => {
1377 let value = format!("{}:{}", ns.sym, name.sym);
1378
1379 PropName::Str(Str {
1380 span,
1381 raw: None,
1382 value: value.into(),
1383 })
1384 }
1385 #[cfg(swc_ast_unknown)]
1386 _ => panic!("unable to access unknown nodes"),
1387 }
1388}
1389
1390#[inline]
1406fn jsx_text_to_str(t: &str) -> Atom {
1407 let mut acc: Option<String> = None;
1408 let mut only_line: Option<&str> = None;
1409 let mut first_non_whitespace: Option<usize> = Some(0);
1410 let mut last_non_whitespace: Option<usize> = None;
1411
1412 for (index, c) in t.char_indices() {
1413 if is_line_terminator(c) {
1414 if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
1415 let line_text = &t[first..last];
1416 add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
1417 }
1418 first_non_whitespace = None;
1419 } else if !is_white_space_single_line(c) {
1420 last_non_whitespace = Some(index + c.len_utf8());
1421 if first_non_whitespace.is_none() {
1422 first_non_whitespace.replace(index);
1423 }
1424 }
1425 }
1426
1427 if let Some(first) = first_non_whitespace {
1428 let line_text = &t[first..];
1429 add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
1430 }
1431
1432 if let Some(acc) = acc {
1433 acc.into()
1434 } else if let Some(only_line) = only_line {
1435 only_line.into()
1436 } else {
1437 "".into()
1438 }
1439}
1440
1441fn is_white_space_single_line(c: char) -> bool {
1452 matches!(c, ' ' | '\t')
1453}
1454
1455fn add_line_of_jsx_text<'a>(
1458 trimmed_line: &'a str,
1459 acc: &mut Option<String>,
1460 only_line: &mut Option<&'a str>,
1461) {
1462 if let Some(buffer) = acc.as_mut() {
1463 buffer.push(' ');
1466 } else if let Some(only_line_content) = only_line.take() {
1467 let mut buffer = String::with_capacity(trimmed_line.len() * 2); buffer.push_str(only_line_content);
1473 buffer.push(' ');
1474 *acc = Some(buffer);
1475 }
1476
1477 if let Some(buffer) = acc.as_mut() {
1481 buffer.push_str(trimmed_line);
1482 } else {
1483 *only_line = Some(trimmed_line);
1488 }
1489}
1490
1491fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option<Box<Expr>> {
1492 Some(match v {
1493 JSXAttrValue::Lit(Lit::Str(s)) => {
1494 let value = transform_jsx_attr_str(&s.value);
1495
1496 Lit::Str(Str {
1497 span: s.span,
1498 raw: None,
1499 value: value.into(),
1500 })
1501 .into()
1502 }
1503 JSXAttrValue::Lit(lit) => Box::new(lit.into()),
1504 JSXAttrValue::JSXExprContainer(e) => match e.expr {
1505 JSXExpr::JSXEmptyExpr(_) => None?,
1506 JSXExpr::Expr(e) => e,
1507 #[cfg(swc_ast_unknown)]
1508 _ => panic!("unable to access unknown nodes"),
1509 },
1510 JSXAttrValue::JSXElement(e) => e.into(),
1511 JSXAttrValue::JSXFragment(f) => f.into(),
1512 #[cfg(swc_ast_unknown)]
1513 _ => panic!("unable to access unknown nodes"),
1514 })
1515}
1516
1517fn transform_jsx_attr_str(v: &str) -> String {
1518 let single_quote = false;
1519 let mut buf = String::with_capacity(v.len());
1520 let mut iter = v.chars().peekable();
1521
1522 while let Some(c) = iter.next() {
1523 match c {
1524 '\u{0008}' => buf.push_str("\\b"),
1525 '\u{000c}' => buf.push_str("\\f"),
1526 ' ' => buf.push(' '),
1527
1528 '\n' | '\r' | '\t' => {
1529 buf.push(' ');
1530
1531 while let Some(' ') = iter.peek() {
1532 iter.next();
1533 }
1534 }
1535 '\u{000b}' => buf.push_str("\\v"),
1536 '\0' => buf.push_str("\\x00"),
1537
1538 '\'' if single_quote => buf.push_str("\\'"),
1539 '"' if !single_quote => buf.push('\"'),
1540
1541 '\x01'..='\x0f' | '\x10'..='\x1f' => {
1542 buf.push(c);
1543 }
1544
1545 '\x20'..='\x7e' => {
1546 buf.push(c);
1548 }
1549 '\u{7f}'..='\u{ff}' => {
1550 buf.push(c);
1551 }
1552
1553 _ => {
1554 buf.push(c);
1555 }
1556 }
1557 }
1558
1559 buf
1560}