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