1#![allow(clippy::redundant_allocation)]
2
3use std::sync::RwLock;
4
5use bytes_str::BytesStr;
6use once_cell::sync::Lazy;
7use rustc_hash::FxHashMap;
8use serde::{Deserialize, Serialize};
9use string_enum::StringEnum;
10use swc_atoms::{
11 atom,
12 wtf8::{Wtf8, Wtf8Buf},
13 Atom, Wtf8Atom,
14};
15use swc_common::{
16 comments::{Comment, CommentKind, Comments},
17 errors::HANDLER,
18 sync::Lrc,
19 util::take::Take,
20 FileName, Mark, SourceMap, Span, Spanned, SyntaxContext, DUMMY_SP,
21};
22use swc_config::merge::Merge;
23use swc_ecma_ast::*;
24use swc_ecma_hooks::VisitMutHook;
25use swc_ecma_parser::{parse_file_as_expr, Syntax};
26use swc_ecma_utils::{
27 drop_span, prepend_stmt, private_ident, quote_ident, str::is_line_terminator, ExprFactory,
28 StmtLike,
29};
30use swc_ecma_visit::VisitMut;
31
32use self::static_check::should_use_create_element;
33use crate::refresh::options::{deserialize_refresh, RefreshOptions};
34
35mod static_check;
36#[cfg(test)]
37mod tests;
38
39#[derive(StringEnum, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
41pub enum Runtime {
42 Automatic,
44 Classic,
46 Preserve,
48}
49
50impl Default for Runtime {
52 fn default() -> Self {
53 Runtime::Classic
54 }
55}
56
57#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, Merge)]
58#[serde(rename_all = "camelCase")]
59#[serde(deny_unknown_fields)]
60pub struct Options {
61 #[serde(skip, default)]
64 pub next: Option<bool>,
65
66 #[serde(default)]
67 pub runtime: Option<Runtime>,
68
69 #[serde(default)]
71 pub import_source: Option<Atom>,
72
73 #[serde(default)]
74 pub pragma: Option<BytesStr>,
75 #[serde(default)]
76 pub pragma_frag: Option<BytesStr>,
77
78 #[serde(default)]
79 pub throw_if_namespace: Option<bool>,
80
81 #[serde(default)]
82 pub development: Option<bool>,
83
84 #[deprecated(
87 since = "0.167.4",
88 note = r#"Since `useBuiltIns` is removed in swc, you can remove it from the config."#
89 )]
90 #[serde(default, alias = "useBuiltIns")]
91 pub use_builtins: Option<bool>,
92
93 #[deprecated(
97 since = "0.167.4",
98 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."#
99 )]
100 #[serde(default)]
101 pub use_spread: Option<bool>,
102
103 #[serde(default, deserialize_with = "deserialize_refresh")]
104 pub refresh: Option<RefreshOptions>,
106}
107
108#[cfg(feature = "concurrent")]
109macro_rules! static_str {
110 ($s:expr) => {{
111 static VAL: Lazy<BytesStr> = Lazy::new(|| $s.into());
112 VAL.clone()
113 }};
114}
115
116#[cfg(not(feature = "concurrent"))]
117macro_rules! static_str {
118 ($s:expr) => {
119 $s.into()
120 };
121}
122
123pub fn default_import_source() -> Atom {
124 atom!("react")
125}
126
127pub fn default_pragma() -> BytesStr {
128 static_str!("React.createElement")
129}
130
131pub fn default_pragma_frag() -> BytesStr {
132 static_str!("React.Fragment")
133}
134
135fn default_throw_if_namespace() -> bool {
136 true
137}
138
139pub fn parse_expr_for_jsx(
141 cm: &SourceMap,
142 name: &str,
143 src: BytesStr,
144 top_level_mark: Mark,
145) -> Box<Expr> {
146 let fm = cm.new_source_file(cache_filename(name), src);
147
148 parse_file_as_expr(
149 &fm,
150 Syntax::default(),
151 Default::default(),
152 None,
153 &mut Vec::new(),
154 )
155 .map_err(|e| {
156 if HANDLER.is_set() {
157 HANDLER.with(|h| {
158 e.into_diagnostic(h)
159 .note("Failed to parse jsx pragma")
160 .emit()
161 })
162 }
163 })
164 .map(drop_span)
165 .map(|mut expr| {
166 apply_mark(&mut expr, top_level_mark);
167 expr
168 })
169 .unwrap_or_else(|()| {
170 panic!(
171 "failed to parse jsx option {}: '{}' is not an expression",
172 name, fm.src,
173 )
174 })
175}
176
177fn apply_mark(e: &mut Expr, mark: Mark) {
178 match e {
179 Expr::Ident(i) => {
180 i.ctxt = i.ctxt.apply_mark(mark);
181 }
182 Expr::Member(MemberExpr { obj, .. }) => {
183 apply_mark(obj, mark);
184 }
185 _ => {}
186 }
187}
188
189pub fn hook<C>(
210 cm: Lrc<SourceMap>,
211 comments: Option<C>,
212 options: Options,
213 top_level_mark: Mark,
214 unresolved_mark: Mark,
215) -> impl VisitMutHook<()>
216where
217 C: Comments,
218{
219 Jsx {
220 cm: cm.clone(),
221 top_level_mark,
222 unresolved_mark,
223 runtime: options.runtime.unwrap_or_default(),
224 import_source: options.import_source.unwrap_or_else(default_import_source),
225 import_jsx: None,
226 import_jsxs: None,
227 import_fragment: None,
228 import_create_element: None,
229
230 pragma: Lrc::new(parse_expr_for_jsx(
231 &cm,
232 "pragma",
233 options.pragma.unwrap_or_else(default_pragma),
234 top_level_mark,
235 )),
236 comments,
237 pragma_frag: Lrc::new(parse_expr_for_jsx(
238 &cm,
239 "pragmaFrag",
240 options.pragma_frag.unwrap_or_else(default_pragma_frag),
241 top_level_mark,
242 )),
243 development: options.development.unwrap_or_default(),
244 throw_if_namespace: options
245 .throw_if_namespace
246 .unwrap_or_else(default_throw_if_namespace),
247 top_level_node: true,
248 }
249}
250
251pub fn jsx<C>(
253 cm: Lrc<SourceMap>,
254 comments: Option<C>,
255 options: Options,
256 top_level_mark: Mark,
257 unresolved_mark: Mark,
258) -> impl Pass + VisitMut
259where
260 C: Comments,
261{
262 use swc_ecma_hooks::VisitMutWithHook;
263 use swc_ecma_visit::visit_mut_pass;
264
265 visit_mut_pass(VisitMutWithHook {
266 hook: hook(cm, comments, options, top_level_mark, unresolved_mark),
267 context: (),
268 })
269}
270
271struct Jsx<C>
272where
273 C: Comments,
274{
275 cm: Lrc<SourceMap>,
276
277 top_level_mark: Mark,
278 unresolved_mark: Mark,
279
280 runtime: Runtime,
281 import_source: Atom,
283 import_jsx: Option<Ident>,
285 import_jsxs: Option<Ident>,
287 import_create_element: Option<Ident>,
289 import_fragment: Option<Ident>,
291 top_level_node: bool,
292
293 pragma: Lrc<Box<Expr>>,
294 comments: Option<C>,
295 pragma_frag: Lrc<Box<Expr>>,
296 development: bool,
297 throw_if_namespace: bool,
298}
299
300#[derive(Debug, Default, Clone, PartialEq, Eq)]
301pub struct JsxDirectives {
302 pub runtime: Option<Runtime>,
303
304 pub import_source: Option<Atom>,
306
307 pub pragma: Option<Lrc<Box<Expr>>>,
309
310 pub pragma_frag: Option<Lrc<Box<Expr>>>,
312}
313
314impl JsxDirectives {
315 pub fn from_comments(
316 cm: &SourceMap,
317 _: Span,
318 comments: &[Comment],
319 top_level_mark: Mark,
320 ) -> Self {
321 let mut res = JsxDirectives::default();
322
323 for cmt in comments {
324 if cmt.kind != CommentKind::Block {
325 continue;
326 }
327
328 for line in cmt.text.lines() {
329 let mut line = line.trim();
330 if line.starts_with('*') {
331 line = line[1..].trim();
332 }
333
334 if !line.starts_with("@jsx") {
335 continue;
336 }
337
338 let mut words = line.split_whitespace();
339 loop {
340 let pragma = words.next();
341 if pragma.is_none() {
342 break;
343 }
344 let val = words.next();
345
346 match pragma {
347 Some("@jsxRuntime") => match val {
348 Some("classic") => res.runtime = Some(Runtime::Classic),
349 Some("automatic") => res.runtime = Some(Runtime::Automatic),
350 None => {}
351 _ => {
352 if HANDLER.is_set() {
353 HANDLER.with(|handler| {
354 handler
355 .struct_span_err(
356 cmt.span,
357 "Runtime must be either `classic` or `automatic`.",
358 )
359 .emit()
360 });
361 }
362 }
363 },
364 Some("@jsxImportSource") => {
365 if let Some(src) = val {
366 res.runtime = Some(Runtime::Automatic);
367 res.import_source = Some(Atom::new(src));
368 }
369 }
370 Some("@jsxFrag") => {
371 if let Some(src) = val {
372 if is_valid_for_pragma(src) {
373 let mut e = parse_expr_for_jsx(
375 cm,
376 "module-jsx-pragma-frag",
377 cache_source(src),
378 top_level_mark,
379 );
380 e.set_span(cmt.span);
381 res.pragma_frag = Some(e.into())
382 }
383 }
384 }
385 Some("@jsx") => {
386 if let Some(src) = val {
387 if is_valid_for_pragma(src) {
388 let mut e = parse_expr_for_jsx(
390 cm,
391 "module-jsx-pragma",
392 cache_source(src),
393 top_level_mark,
394 );
395 e.set_span(cmt.span);
396 res.pragma = Some(e.into());
397 }
398 }
399 }
400 _ => {}
401 }
402 }
403 }
404 }
405
406 res
407 }
408}
409
410#[cfg(feature = "concurrent")]
411fn cache_filename(name: &str) -> Lrc<FileName> {
412 static FILENAME_CACHE: Lazy<RwLock<FxHashMap<String, Lrc<FileName>>>> =
413 Lazy::new(|| RwLock::new(FxHashMap::default()));
414
415 {
416 let cache = FILENAME_CACHE
417 .read()
418 .expect("Failed to read FILENAME_CACHE");
419 if let Some(f) = cache.get(name) {
420 return f.clone();
421 }
422 }
423
424 let file = Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")));
425
426 {
427 let mut cache = FILENAME_CACHE
428 .write()
429 .expect("Failed to write FILENAME_CACHE");
430 cache.insert(name.to_string(), file.clone());
431 }
432
433 file
434}
435
436#[cfg(not(feature = "concurrent"))]
437fn cache_filename(name: &str) -> Lrc<FileName> {
438 Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")))
439}
440
441#[cfg(feature = "concurrent")]
442fn cache_source(src: &str) -> BytesStr {
443 use rustc_hash::FxHashSet;
444
445 static CACHE: Lazy<RwLock<FxHashSet<BytesStr>>> =
446 Lazy::new(|| RwLock::new(FxHashSet::default()));
447
448 {
449 let cache = CACHE.write().unwrap();
450
451 if let Some(cached) = cache.get(src) {
452 return cached.clone();
453 }
454 }
455
456 let cached: BytesStr = src.to_string().into();
457 {
458 let mut cache = CACHE.write().unwrap();
459 cache.insert(cached.clone());
460 }
461 cached
462}
463
464#[cfg(not(feature = "concurrent"))]
465fn cache_source(src: &str) -> BytesStr {
466 src.to_string().into()
468}
469
470fn is_valid_for_pragma(s: &str) -> bool {
471 if s.is_empty() {
472 return false;
473 }
474
475 if !s.starts_with(|c: char| Ident::is_valid_start(c)) {
476 return false;
477 }
478
479 for c in s.chars() {
480 if !Ident::is_valid_continue(c) && c != '.' {
481 return false;
482 }
483 }
484
485 true
486}
487
488impl<C> Jsx<C>
489where
490 C: Comments,
491{
492 fn process_attr_value(&mut self, value: Option<JSXAttrValue>) -> Box<Expr> {
494 match value {
495 Some(JSXAttrValue::JSXElement(el)) => Box::new(self.jsx_elem_to_expr(*el)),
496 Some(JSXAttrValue::JSXFragment(frag)) => Box::new(self.jsx_frag_to_expr(frag)),
497 Some(JSXAttrValue::JSXExprContainer(container)) => match container.expr {
498 JSXExpr::Expr(e) => e,
499 JSXExpr::JSXEmptyExpr(_) => panic!("empty expression container"),
500 #[cfg(swc_ast_unknown)]
501 _ => panic!("unable to access unknown nodes"),
502 },
503 Some(v) => jsx_attr_value_to_expr(v).expect("empty expression container?"),
504 None => true.into(),
505 }
506 }
507
508 fn inject_runtime<T, F>(&mut self, body: &mut Vec<T>, inject: F)
509 where
510 T: StmtLike,
511 F: Fn(Vec<(Ident, IdentName)>, &str, &mut Vec<T>),
513 {
514 if self.runtime == Runtime::Automatic {
515 if let Some(local) = self.import_create_element.take() {
516 inject(
517 vec![(local, quote_ident!("createElement"))],
518 &self.import_source,
519 body,
520 );
521 }
522
523 let imports = self.import_jsx.take();
524 let imports = if self.development {
525 imports
526 .map(|local| (local, quote_ident!("jsxDEV")))
527 .into_iter()
528 .chain(
529 self.import_fragment
530 .take()
531 .map(|local| (local, quote_ident!("Fragment"))),
532 )
533 .collect::<Vec<_>>()
534 } else {
535 imports
536 .map(|local| (local, quote_ident!("jsx")))
537 .into_iter()
538 .chain(
539 self.import_jsxs
540 .take()
541 .map(|local| (local, quote_ident!("jsxs"))),
542 )
543 .chain(
544 self.import_fragment
545 .take()
546 .map(|local| (local, quote_ident!("Fragment"))),
547 )
548 .collect::<Vec<_>>()
549 };
550
551 if !imports.is_empty() {
552 let jsx_runtime = if self.development {
553 "jsx-dev-runtime"
554 } else {
555 "jsx-runtime"
556 };
557
558 let value = format!("{}/{}", self.import_source, jsx_runtime);
559 inject(imports, &value, body)
560 }
561 }
562 }
563
564 fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
565 let mut span = el.span();
566
567 if let Some(comments) = &self.comments {
568 if span.lo.is_dummy() {
569 span.lo = Span::dummy_with_cmt().lo;
570 }
571
572 comments.add_pure_comment(span.lo);
573 }
574
575 match self.runtime {
576 Runtime::Automatic => {
577 let fragment = self
578 .import_fragment
579 .get_or_insert_with(|| private_ident!("_Fragment"))
580 .clone();
581
582 let mut props_obj = ObjectLit {
583 span: DUMMY_SP,
584 props: Vec::new(),
585 };
586
587 let children = el
588 .children
589 .into_iter()
590 .filter_map(|child| self.jsx_elem_child_to_expr(child))
591 .map(Some)
592 .collect::<Vec<_>>();
593
594 let use_jsxs = match children.len() {
595 0 => false,
596 1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
597 props_obj
598 .props
599 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
600 key: PropName::Ident(quote_ident!("children")),
601 value: children.into_iter().next().flatten().unwrap().expr,
602 }))));
603
604 false
605 }
606 _ => {
607 props_obj
608 .props
609 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
610 key: PropName::Ident(quote_ident!("children")),
611 value: ArrayLit {
612 span: DUMMY_SP,
613 elems: children,
614 }
615 .into(),
616 }))));
617 true
618 }
619 };
620
621 let jsx = if use_jsxs && !self.development {
622 self.import_jsxs
623 .get_or_insert_with(|| private_ident!("_jsxs"))
624 .clone()
625 } else {
626 let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
627 self.import_jsx
628 .get_or_insert_with(|| private_ident!(jsx))
629 .clone()
630 };
631
632 let args = if self.development {
634 vec![
635 fragment.as_arg(),
636 props_obj.as_arg(),
637 Expr::undefined(DUMMY_SP).as_arg(),
638 use_jsxs.as_arg(),
639 ]
640 } else {
641 vec![fragment.as_arg(), props_obj.as_arg()]
642 };
643
644 CallExpr {
645 span,
646 callee: jsx.as_callee(),
647 args,
648 ..Default::default()
649 }
650 .into()
651 }
652 Runtime::Classic => {
653 let children_capacity = el.children.len();
655 let mut args = Vec::with_capacity(2 + children_capacity);
656
657 args.push((*self.pragma_frag).clone().as_arg());
658 args.push(Lit::Null(Null { span: DUMMY_SP }).as_arg());
659
660 for child in el.children {
662 if let Some(expr) = self.jsx_elem_child_to_expr(child) {
663 args.push(expr);
664 }
665 }
666
667 CallExpr {
668 span,
669 callee: (*self.pragma).clone().as_callee(),
670 args,
671 ..Default::default()
672 }
673 .into()
674 }
675 Runtime::Preserve => unreachable!(),
676 }
677 }
678
679 fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr {
687 let top_level_node = self.top_level_node;
688 let mut span = el.span();
689 let use_create_element = should_use_create_element(&el.opening.attrs);
690 self.top_level_node = false;
691
692 let name = self.jsx_name(el.opening.name);
693
694 if let Some(comments) = &self.comments {
695 if span.lo.is_dummy() {
696 span.lo = Span::dummy_with_cmt().lo;
697 }
698
699 comments.add_pure_comment(span.lo);
700 }
701
702 match self.runtime {
703 Runtime::Automatic => {
704 let estimated_props_capacity = el.opening.attrs.len() + 1; let mut props_obj = ObjectLit {
709 span: DUMMY_SP,
710 props: Vec::with_capacity(estimated_props_capacity),
711 };
712
713 let mut key = None;
714 let mut source_props = None;
715 let mut self_props = None;
716
717 for attr in el.opening.attrs {
718 match attr {
719 JSXAttrOrSpread::JSXAttr(attr) => {
720 match attr.name {
722 JSXAttrName::Ident(i) => {
723 if !use_create_element && i.sym == "key" {
725 key = attr
726 .value
727 .and_then(jsx_attr_value_to_expr)
728 .map(|expr| expr.as_arg());
729
730 if key.is_none() && HANDLER.is_set() {
731 HANDLER.with(|handler| {
732 handler
733 .struct_span_err(
734 i.span,
735 "The value of property 'key' should not \
736 be empty",
737 )
738 .emit();
739 });
740 }
741 continue;
742 }
743
744 if !use_create_element
745 && *i.sym == *"__source"
746 && self.development
747 {
748 if source_props.is_some() {
749 panic!("Duplicate __source is found");
750 }
751 source_props = attr
752 .value
753 .and_then(jsx_attr_value_to_expr)
754 .map(|expr| expr.as_arg());
755 assert_ne!(
756 source_props, None,
757 "value of property '__source' should not be empty"
758 );
759 continue;
760 }
761
762 if !use_create_element
763 && *i.sym == *"__self"
764 && self.development
765 {
766 if self_props.is_some() {
767 panic!("Duplicate __self is found");
768 }
769 self_props = attr
770 .value
771 .and_then(jsx_attr_value_to_expr)
772 .map(|expr| expr.as_arg());
773 assert_ne!(
774 self_props, None,
775 "value of property '__self' should not be empty"
776 );
777 continue;
778 }
779
780 let value = self.process_attr_value(attr.value);
781
782 let key = if i.sym.contains('-') {
784 PropName::Str(Str {
785 span: i.span,
786 raw: None,
787 value: i.sym.into(),
788 })
789 } else {
790 PropName::Ident(i)
791 };
792 props_obj.props.push(PropOrSpread::Prop(Box::new(
793 Prop::KeyValue(KeyValueProp { key, value }),
794 )));
795 }
796 JSXAttrName::JSXNamespacedName(JSXNamespacedName {
797 ns,
798 name,
799 ..
800 }) => {
801 if self.throw_if_namespace && HANDLER.is_set() {
802 HANDLER.with(|handler| {
803 handler
804 .struct_span_err(
805 span,
806 "JSX Namespace is disabled by default because \
807 react does not support it yet. You can \
808 specify jsc.transform.react.throwIfNamespace \
809 to false to override default behavior",
810 )
811 .emit()
812 });
813 }
814
815 let value = self.process_attr_value(attr.value);
816
817 let str_value = format!("{}:{}", ns.sym, name.sym);
818 let key = Str {
819 span,
820 raw: None,
821 value: str_value.into(),
822 };
823 let key = PropName::Str(key);
824
825 props_obj.props.push(PropOrSpread::Prop(Box::new(
826 Prop::KeyValue(KeyValueProp { key, value }),
827 )));
828 }
829 #[cfg(swc_ast_unknown)]
830 _ => panic!("unable to access unknown nodes"),
831 }
832 }
833 JSXAttrOrSpread::SpreadElement(attr) => match *attr.expr {
834 Expr::Object(obj) => {
835 props_obj.props.extend(obj.props);
836 }
837 _ => {
838 props_obj.props.push(PropOrSpread::Spread(attr));
839 }
840 },
841 #[cfg(swc_ast_unknown)]
842 _ => panic!("unable to access unknown nodes"),
843 }
844 }
845
846 let mut children = el
847 .children
848 .into_iter()
849 .filter_map(|child| self.jsx_elem_child_to_expr(child))
850 .map(Some)
851 .collect::<Vec<_>>();
852
853 let use_jsxs = match children.len() {
854 0 => false,
855 1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
856 if !use_create_element {
857 props_obj
858 .props
859 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
860 key: PropName::Ident(quote_ident!("children")),
861 value: children
862 .take()
863 .into_iter()
864 .next()
865 .flatten()
866 .unwrap()
867 .expr,
868 }))));
869 }
870
871 false
872 }
873 _ => {
874 props_obj
875 .props
876 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
877 key: PropName::Ident(quote_ident!("children")),
878 value: ArrayLit {
879 span: DUMMY_SP,
880 elems: children.take(),
881 }
882 .into(),
883 }))));
884 true
885 }
886 };
887
888 let jsx = if use_create_element {
889 self.import_create_element
890 .get_or_insert_with(|| private_ident!("_createElement"))
891 .clone()
892 } else if use_jsxs && !self.development {
893 self.import_jsxs
894 .get_or_insert_with(|| private_ident!("_jsxs"))
895 .clone()
896 } else {
897 let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
898 self.import_jsx
899 .get_or_insert_with(|| private_ident!(jsx))
900 .clone()
901 };
902
903 self.top_level_node = top_level_node;
904
905 let args = if use_create_element {
907 let mut args = Vec::with_capacity(2 + children.len());
908 args.push(name.as_arg());
909 args.push(props_obj.as_arg());
910 args.extend(children.into_iter().flatten());
911 args
912 } else if self.development {
913 let mut args = Vec::with_capacity(6);
914 args.push(name.as_arg());
915 args.push(props_obj.as_arg());
916
917 let key = key.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
919 args.push(key);
920
921 args.push(use_jsxs.as_arg());
922
923 let source_props =
925 source_props.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
926 args.push(source_props);
927
928 let self_props =
930 self_props.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
931 args.push(self_props);
932
933 args
934 } else {
935 let mut args = Vec::with_capacity(if key.is_some() { 3 } else { 2 });
936 args.push(name.as_arg());
937 args.push(props_obj.as_arg());
938 if let Some(key) = key {
939 args.push(key);
940 }
941 args
942 };
943 CallExpr {
944 span,
945 callee: jsx.as_callee(),
946 args,
947 ..Default::default()
948 }
949 .into()
950 }
951 Runtime::Classic => {
952 let children_capacity = el.children.len();
954 let mut args = Vec::with_capacity(2 + children_capacity);
955
956 args.push(name.as_arg());
957 args.push(self.fold_attrs_for_classic(el.opening.attrs).as_arg());
958
959 for child in el.children {
961 if let Some(expr) = self.jsx_elem_child_to_expr(child) {
962 args.push(expr);
963 }
964 }
965
966 CallExpr {
967 span,
968 callee: (*self.pragma).clone().as_callee(),
969 args,
970 ..Default::default()
971 }
972 .into()
973 }
974 Runtime::Preserve => unreachable!(),
975 }
976 }
977
978 fn jsx_elem_child_to_expr(&mut self, c: JSXElementChild) -> Option<ExprOrSpread> {
979 self.top_level_node = false;
980
981 Some(match c {
982 JSXElementChild::JSXText(text) => {
983 let value = jsx_text_to_str_with_raw(&text.value, &text.raw);
985 let s = Str {
986 span: text.span,
987 raw: None,
988 value,
989 };
990
991 if s.value.is_empty() {
992 return None;
993 }
994
995 Lit::Str(s).as_arg()
996 }
997 JSXElementChild::JSXExprContainer(JSXExprContainer {
998 expr: JSXExpr::Expr(e),
999 ..
1000 }) => e.as_arg(),
1001 JSXElementChild::JSXExprContainer(JSXExprContainer {
1002 expr: JSXExpr::JSXEmptyExpr(..),
1003 ..
1004 }) => return None,
1005 JSXElementChild::JSXElement(el) => self.jsx_elem_to_expr(*el).as_arg(),
1006 JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
1007 JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => ExprOrSpread {
1008 spread: Some(span),
1009 expr,
1010 },
1011 #[cfg(swc_ast_unknown)]
1012 _ => panic!("unable to access unknown nodes"),
1013 })
1014 }
1015
1016 fn fold_attrs_for_classic(&mut self, attrs: Vec<JSXAttrOrSpread>) -> Box<Expr> {
1017 if attrs.is_empty() {
1018 return Lit::Null(Null { span: DUMMY_SP }).into();
1019 }
1020 let attr_cnt = attrs.len();
1021
1022 let mut props = Vec::new();
1023 for attr in attrs {
1024 match attr {
1025 JSXAttrOrSpread::JSXAttr(attr) => {
1026 props.push(PropOrSpread::Prop(Box::new(self.attr_to_prop(attr))))
1027 }
1028 JSXAttrOrSpread::SpreadElement(spread) => {
1029 if attr_cnt == 1 {
1030 return spread.expr;
1031 }
1032 match *spread.expr {
1034 Expr::Object(obj) => props.extend(obj.props),
1035 _ => props.push(PropOrSpread::Spread(spread)),
1036 }
1037 }
1038 #[cfg(swc_ast_unknown)]
1039 _ => panic!("unable to access unknown nodes"),
1040 }
1041 }
1042
1043 let obj = ObjectLit {
1044 span: DUMMY_SP,
1045 props,
1046 };
1047
1048 obj.into()
1049 }
1050
1051 fn attr_to_prop(&mut self, a: JSXAttr) -> Prop {
1052 let key = to_prop_name(a.name);
1053 let value = a
1054 .value
1055 .map(|v| match v {
1056 JSXAttrValue::Str(s) => {
1057 let value = transform_jsx_attr_str(&s.value);
1058
1059 Lit::Str(Str {
1060 span: s.span,
1061 raw: None,
1062 value: value.into(),
1063 })
1064 .into()
1065 }
1066 JSXAttrValue::JSXExprContainer(JSXExprContainer {
1067 expr: JSXExpr::Expr(e),
1068 ..
1069 }) => e,
1070 JSXAttrValue::JSXElement(element) => Box::new(self.jsx_elem_to_expr(*element)),
1071 JSXAttrValue::JSXFragment(fragment) => Box::new(self.jsx_frag_to_expr(fragment)),
1072 JSXAttrValue::JSXExprContainer(JSXExprContainer {
1073 span: _,
1074 expr: JSXExpr::JSXEmptyExpr(_),
1075 }) => unreachable!("attr_to_prop(JSXEmptyExpr)"),
1076 #[cfg(swc_ast_unknown)]
1077 _ => panic!("unable to access unknown nodes"),
1078 })
1079 .unwrap_or_else(|| {
1080 Lit::Bool(Bool {
1081 span: key.span(),
1082 value: true,
1083 })
1084 .into()
1085 });
1086 Prop::KeyValue(KeyValueProp { key, value })
1087 }
1088}
1089
1090impl<C> Jsx<C>
1091where
1092 C: Comments,
1093{
1094 fn parse_directives(&mut self, span: Span) -> bool {
1096 let mut found = false;
1097
1098 let directives = self.comments.with_leading(span.lo, |comments| {
1099 JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
1100 });
1101
1102 let JsxDirectives {
1103 runtime,
1104 import_source,
1105 pragma,
1106 pragma_frag,
1107 } = directives;
1108
1109 if let Some(runtime) = runtime {
1110 found = true;
1111 self.runtime = runtime;
1112 }
1113
1114 if let Some(import_source) = import_source {
1115 found = true;
1116 self.import_source = import_source;
1117 }
1118
1119 if let Some(pragma) = pragma {
1120 if let Runtime::Automatic = self.runtime {
1121 if HANDLER.is_set() {
1122 HANDLER.with(|handler| {
1123 handler
1124 .struct_span_err(
1125 pragma.span(),
1126 "pragma cannot be set when runtime is automatic",
1127 )
1128 .emit()
1129 });
1130 }
1131 }
1132
1133 found = true;
1134 self.pragma = pragma;
1135 }
1136
1137 if let Some(pragma_frag) = pragma_frag {
1138 if let Runtime::Automatic = self.runtime {
1139 if HANDLER.is_set() {
1140 HANDLER.with(|handler| {
1141 handler
1142 .struct_span_err(
1143 pragma_frag.span(),
1144 "pragmaFrag cannot be set when runtime is automatic",
1145 )
1146 .emit()
1147 });
1148 }
1149 }
1150
1151 found = true;
1152 self.pragma_frag = pragma_frag;
1153 }
1154
1155 found
1156 }
1157}
1158
1159impl<C> VisitMutHook<()> for Jsx<C>
1160where
1161 C: Comments,
1162{
1163 fn exit_expr(&mut self, expr: &mut Expr, _ctx: &mut ()) {
1170 let top_level_node = self.top_level_node;
1171 let mut did_work = false;
1172
1173 if let Expr::JSXElement(el) = expr {
1174 did_work = true;
1175 *expr = self.jsx_elem_to_expr(*el.take());
1177 } else if let Expr::JSXFragment(frag) = expr {
1178 did_work = true;
1180 *expr = self.jsx_frag_to_expr(frag.take());
1181 } else if let Expr::Paren(ParenExpr {
1182 expr: inner_expr, ..
1183 }) = expr
1184 {
1185 if let Expr::JSXElement(el) = &mut **inner_expr {
1186 did_work = true;
1187 *expr = self.jsx_elem_to_expr(*el.take());
1188 } else if let Expr::JSXFragment(frag) = &mut **inner_expr {
1189 did_work = true;
1191 *expr = self.jsx_frag_to_expr(frag.take());
1192 }
1193 }
1194
1195 if did_work {
1196 self.top_level_node = false;
1197 }
1198
1199 self.top_level_node = top_level_node;
1200 }
1201
1202 fn enter_module(&mut self, module: &mut Module, _ctx: &mut ()) {
1203 self.parse_directives(module.span);
1204
1205 for item in &module.body {
1206 let span = item.span();
1207 if self.parse_directives(span) {
1208 break;
1209 }
1210 }
1211 }
1212
1213 fn exit_module(&mut self, module: &mut Module, _ctx: &mut ()) {
1214 if self.runtime == Runtime::Automatic {
1215 self.inject_runtime(&mut module.body, |imports, src, stmts| {
1216 let specifiers = imports
1217 .into_iter()
1218 .map(|(local, imported)| {
1219 ImportSpecifier::Named(ImportNamedSpecifier {
1220 span: DUMMY_SP,
1221 local,
1222 imported: Some(ModuleExportName::Ident(imported.into())),
1223 is_type_only: false,
1224 })
1225 })
1226 .collect();
1227
1228 prepend_stmt(
1229 stmts,
1230 ImportDecl {
1231 span: DUMMY_SP,
1232 specifiers,
1233 src: Str {
1234 span: DUMMY_SP,
1235 raw: None,
1236 value: src.into(),
1237 }
1238 .into(),
1239 type_only: Default::default(),
1240 with: Default::default(),
1241 phase: Default::default(),
1242 }
1243 .into(),
1244 )
1245 });
1246 }
1247 }
1248
1249 fn enter_script(&mut self, script: &mut Script, _ctx: &mut ()) {
1250 self.parse_directives(script.span);
1251
1252 for item in &script.body {
1253 let span = item.span();
1254 if self.parse_directives(span) {
1255 break;
1256 }
1257 }
1258 }
1259
1260 fn exit_script(&mut self, script: &mut Script, _ctx: &mut ()) {
1261 if self.runtime == Runtime::Automatic {
1262 let mark = self.unresolved_mark;
1263 self.inject_runtime(&mut script.body, |imports, src, stmts| {
1264 prepend_stmt(stmts, add_require(imports, src, mark))
1265 });
1266 }
1267 }
1268}
1269
1270fn add_require(imports: Vec<(Ident, IdentName)>, src: &str, unresolved_mark: Mark) -> Stmt {
1273 VarDecl {
1274 span: DUMMY_SP,
1275 kind: VarDeclKind::Const,
1276 declare: false,
1277 decls: vec![VarDeclarator {
1278 span: DUMMY_SP,
1279 name: Pat::Object(ObjectPat {
1280 span: DUMMY_SP,
1281 props: imports
1282 .into_iter()
1283 .map(|(local, imported)| {
1284 if imported.sym != local.sym {
1285 ObjectPatProp::KeyValue(KeyValuePatProp {
1286 key: PropName::Ident(imported),
1287 value: Box::new(Pat::Ident(local.into())),
1288 })
1289 } else {
1290 ObjectPatProp::Assign(AssignPatProp {
1291 span: DUMMY_SP,
1292 key: local.into(),
1293 value: None,
1294 })
1295 }
1296 })
1297 .collect(),
1298 optional: false,
1299 type_ann: None,
1300 }),
1301 init: Some(Box::new(Expr::Call(CallExpr {
1303 span: DUMMY_SP,
1304 callee: Callee::Expr(Box::new(Expr::Ident(Ident {
1305 ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
1306 sym: atom!("require"),
1307 optional: false,
1308 ..Default::default()
1309 }))),
1310 args: vec![ExprOrSpread {
1311 spread: None,
1312 expr: Box::new(Expr::Lit(Lit::Str(Str {
1313 span: DUMMY_SP,
1314 value: src.into(),
1315 raw: None,
1316 }))),
1317 }],
1318 ..Default::default()
1319 }))),
1320 definite: false,
1321 }],
1322 ..Default::default()
1323 }
1324 .into()
1325}
1326
1327impl<C> Jsx<C>
1328where
1329 C: Comments,
1330{
1331 fn jsx_name(&self, name: JSXElementName) -> Box<Expr> {
1332 let span = name.span();
1333 match name {
1334 JSXElementName::Ident(i) => {
1335 if i.sym == "this" {
1336 return ThisExpr { span }.into();
1337 }
1338
1339 if i.as_ref().starts_with(|c: char| c.is_ascii_lowercase()) {
1341 Lit::Str(Str {
1342 span,
1343 raw: None,
1344 value: i.sym.into(),
1345 })
1346 .into()
1347 } else {
1348 i.into()
1349 }
1350 }
1351 JSXElementName::JSXNamespacedName(JSXNamespacedName {
1352 ref ns, ref name, ..
1353 }) => {
1354 if self.throw_if_namespace && HANDLER.is_set() {
1355 HANDLER.with(|handler| {
1356 handler
1357 .struct_span_err(
1358 span,
1359 "JSX Namespace is disabled by default because react does not \
1360 support it yet. You can specify \
1361 jsc.transform.react.throwIfNamespace to false to override \
1362 default behavior",
1363 )
1364 .emit()
1365 });
1366 }
1367
1368 let value = format!("{}:{}", ns.sym, name.sym);
1369
1370 Lit::Str(Str {
1371 span,
1372 raw: None,
1373 value: value.into(),
1374 })
1375 .into()
1376 }
1377 JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop, .. }) => {
1378 fn convert_obj(obj: JSXObject) -> Box<Expr> {
1379 let span = obj.span();
1380
1381 (match obj {
1382 JSXObject::Ident(i) => {
1383 if i.sym == "this" {
1384 Expr::This(ThisExpr { span })
1385 } else {
1386 i.into()
1387 }
1388 }
1389 JSXObject::JSXMemberExpr(e) => MemberExpr {
1390 span,
1391 obj: convert_obj(e.obj),
1392 prop: MemberProp::Ident(e.prop),
1393 }
1394 .into(),
1395 #[cfg(swc_ast_unknown)]
1396 _ => panic!("unable to access unknown nodes"),
1397 })
1398 .into()
1399 }
1400 MemberExpr {
1401 span,
1402 obj: convert_obj(obj),
1403 prop: MemberProp::Ident(prop),
1404 }
1405 .into()
1406 }
1407 #[cfg(swc_ast_unknown)]
1408 _ => panic!("unable to access unknown nodes"),
1409 }
1410 }
1411}
1412
1413fn to_prop_name(n: JSXAttrName) -> PropName {
1414 let span = n.span();
1415
1416 match n {
1417 JSXAttrName::Ident(i) => {
1418 if i.sym.contains('-') {
1419 PropName::Str(Str {
1420 span,
1421 raw: None,
1422 value: i.sym.into(),
1423 })
1424 } else {
1425 PropName::Ident(i)
1426 }
1427 }
1428 JSXAttrName::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => {
1429 let value = format!("{}:{}", ns.sym, name.sym);
1430
1431 PropName::Str(Str {
1432 span,
1433 raw: None,
1434 value: value.into(),
1435 })
1436 }
1437 #[cfg(swc_ast_unknown)]
1438 _ => panic!("unable to access unknown nodes"),
1439 }
1440}
1441
1442#[inline]
1462fn jsx_text_to_str_with_raw(value: &Atom, raw: &Atom) -> Wtf8Atom {
1463 if value.as_str() == raw.as_str() {
1465 return jsx_text_to_str_impl(value).into();
1466 }
1467
1468 let entity_mask = build_entity_mask(value, raw);
1470
1471 jsx_text_to_str_with_entity_mask(value, &entity_mask).into()
1472}
1473
1474fn build_entity_mask(value: &str, raw: &str) -> Vec<bool> {
1480 let mut mask = vec![false; value.chars().count()];
1481 let mut value_char_idx = 0;
1482 let mut raw_chars = raw.chars().peekable();
1483
1484 while let Some(raw_c) = raw_chars.next() {
1485 if raw_c == '&' {
1486 let mut entity_chars: Vec<char> = vec!['&'];
1488 let mut found_semicolon = false;
1489
1490 for _ in 0..10 {
1492 if let Some(&next_c) = raw_chars.peek() {
1493 entity_chars.push(next_c);
1494 raw_chars.next();
1495 if next_c == ';' {
1496 found_semicolon = true;
1497 break;
1498 }
1499 } else {
1500 break;
1501 }
1502 }
1503
1504 if found_semicolon && is_valid_entity(&entity_chars) {
1505 if value_char_idx < mask.len() {
1508 mask[value_char_idx] = true;
1509 }
1510 value_char_idx += 1;
1511 } else {
1512 value_char_idx += 1;
1514 for _ in 1..entity_chars.len() {
1516 value_char_idx += 1;
1517 }
1518 }
1519 } else {
1520 value_char_idx += 1;
1522 }
1523 }
1524
1525 mask
1526}
1527
1528fn is_valid_entity(chars: &[char]) -> bool {
1530 if chars.len() < 3 {
1531 return false;
1532 }
1533 if chars[0] != '&' || chars[chars.len() - 1] != ';' {
1534 return false;
1535 }
1536
1537 let inner: String = chars[1..chars.len() - 1].iter().collect();
1538
1539 if let Some(stripped) = inner.strip_prefix('#') {
1540 if let Some(hex) = stripped
1542 .strip_prefix('x')
1543 .or_else(|| stripped.strip_prefix('X'))
1544 {
1545 !hex.is_empty() && hex.chars().all(|c| c.is_ascii_hexdigit())
1547 } else {
1548 !stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
1550 }
1551 } else {
1552 is_known_html_entity(&inner)
1554 }
1555}
1556
1557fn is_known_html_entity(name: &str) -> bool {
1559 matches!(
1563 name,
1564 "nbsp"
1565 | "iexcl"
1566 | "cent"
1567 | "pound"
1568 | "curren"
1569 | "yen"
1570 | "brvbar"
1571 | "sect"
1572 | "uml"
1573 | "copy"
1574 | "ordf"
1575 | "laquo"
1576 | "not"
1577 | "shy"
1578 | "reg"
1579 | "macr"
1580 | "deg"
1581 | "plusmn"
1582 | "sup2"
1583 | "sup3"
1584 | "acute"
1585 | "micro"
1586 | "para"
1587 | "middot"
1588 | "cedil"
1589 | "sup1"
1590 | "ordm"
1591 | "raquo"
1592 | "frac14"
1593 | "frac12"
1594 | "frac34"
1595 | "iquest"
1596 | "Agrave"
1597 | "Aacute"
1598 | "Acirc"
1599 | "Atilde"
1600 | "Auml"
1601 | "Aring"
1602 | "AElig"
1603 | "Ccedil"
1604 | "Egrave"
1605 | "Eacute"
1606 | "Ecirc"
1607 | "Euml"
1608 | "Igrave"
1609 | "Iacute"
1610 | "Icirc"
1611 | "Iuml"
1612 | "ETH"
1613 | "Ntilde"
1614 | "Ograve"
1615 | "Oacute"
1616 | "Ocirc"
1617 | "Otilde"
1618 | "Ouml"
1619 | "times"
1620 | "Oslash"
1621 | "Ugrave"
1622 | "Uacute"
1623 | "Ucirc"
1624 | "Uuml"
1625 | "Yacute"
1626 | "THORN"
1627 | "szlig"
1628 | "agrave"
1629 | "aacute"
1630 | "acirc"
1631 | "atilde"
1632 | "auml"
1633 | "aring"
1634 | "aelig"
1635 | "ccedil"
1636 | "egrave"
1637 | "eacute"
1638 | "ecirc"
1639 | "euml"
1640 | "igrave"
1641 | "iacute"
1642 | "icirc"
1643 | "iuml"
1644 | "eth"
1645 | "ntilde"
1646 | "ograve"
1647 | "oacute"
1648 | "ocirc"
1649 | "otilde"
1650 | "ouml"
1651 | "divide"
1652 | "oslash"
1653 | "ugrave"
1654 | "uacute"
1655 | "ucirc"
1656 | "uuml"
1657 | "yacute"
1658 | "thorn"
1659 | "yuml"
1660 | "OElig"
1661 | "oelig"
1662 | "Scaron"
1663 | "scaron"
1664 | "Yuml"
1665 | "fnof"
1666 | "circ"
1667 | "tilde"
1668 | "Alpha"
1669 | "Beta"
1670 | "Gamma"
1671 | "Delta"
1672 | "Epsilon"
1673 | "Zeta"
1674 | "Eta"
1675 | "Theta"
1676 | "Iota"
1677 | "Kappa"
1678 | "Lambda"
1679 | "Mu"
1680 | "Nu"
1681 | "Xi"
1682 | "Omicron"
1683 | "Pi"
1684 | "Rho"
1685 | "Sigma"
1686 | "Tau"
1687 | "Upsilon"
1688 | "Phi"
1689 | "Chi"
1690 | "Psi"
1691 | "Omega"
1692 | "alpha"
1693 | "beta"
1694 | "gamma"
1695 | "delta"
1696 | "epsilon"
1697 | "zeta"
1698 | "eta"
1699 | "theta"
1700 | "iota"
1701 | "kappa"
1702 | "lambda"
1703 | "mu"
1704 | "nu"
1705 | "xi"
1706 | "omicron"
1707 | "pi"
1708 | "rho"
1709 | "sigmaf"
1710 | "sigma"
1711 | "tau"
1712 | "upsilon"
1713 | "phi"
1714 | "chi"
1715 | "psi"
1716 | "omega"
1717 | "thetasym"
1718 | "upsih"
1719 | "piv"
1720 | "ensp"
1721 | "emsp"
1722 | "thinsp"
1723 | "zwnj"
1724 | "zwj"
1725 | "lrm"
1726 | "rlm"
1727 | "ndash"
1728 | "mdash"
1729 | "lsquo"
1730 | "rsquo"
1731 | "sbquo"
1732 | "ldquo"
1733 | "rdquo"
1734 | "bdquo"
1735 | "dagger"
1736 | "Dagger"
1737 | "bull"
1738 | "hellip"
1739 | "permil"
1740 | "prime"
1741 | "Prime"
1742 | "lsaquo"
1743 | "rsaquo"
1744 | "oline"
1745 | "frasl"
1746 | "euro"
1747 | "image"
1748 | "weierp"
1749 | "real"
1750 | "trade"
1751 | "alefsym"
1752 | "larr"
1753 | "uarr"
1754 | "rarr"
1755 | "darr"
1756 | "harr"
1757 | "crarr"
1758 | "lArr"
1759 | "uArr"
1760 | "rArr"
1761 | "dArr"
1762 | "hArr"
1763 | "forall"
1764 | "part"
1765 | "exist"
1766 | "empty"
1767 | "nabla"
1768 | "isin"
1769 | "notin"
1770 | "ni"
1771 | "prod"
1772 | "sum"
1773 | "minus"
1774 | "lowast"
1775 | "radic"
1776 | "prop"
1777 | "infin"
1778 | "ang"
1779 | "and"
1780 | "or"
1781 | "cap"
1782 | "cup"
1783 | "int"
1784 | "there4"
1785 | "sim"
1786 | "cong"
1787 | "asymp"
1788 | "ne"
1789 | "equiv"
1790 | "le"
1791 | "ge"
1792 | "sub"
1793 | "sup"
1794 | "nsub"
1795 | "sube"
1796 | "supe"
1797 | "oplus"
1798 | "otimes"
1799 | "perp"
1800 | "sdot"
1801 | "lceil"
1802 | "rceil"
1803 | "lfloor"
1804 | "rfloor"
1805 | "lang"
1806 | "rang"
1807 | "loz"
1808 | "spades"
1809 | "clubs"
1810 | "hearts"
1811 | "diams"
1812 | "quot"
1813 | "amp"
1814 | "lt"
1815 | "gt"
1816 )
1817}
1818
1819fn jsx_text_to_str_with_entity_mask(t: &str, entity_mask: &[bool]) -> Atom {
1822 let chars: Vec<char> = t.chars().collect();
1825 let has_line_terminator = chars.iter().any(|&c| is_line_terminator(c));
1826
1827 if !t.is_empty() && !has_line_terminator {
1831 return t.into();
1832 }
1833
1834 let mut acc: Option<String> = None;
1835 let mut only_line: Option<String> = None;
1836 let mut line_start: Option<usize> = Some(0);
1837 let mut line_end: Option<usize> = None;
1838 let mut is_first_line = true;
1840
1841 for (char_idx, c) in chars.iter().enumerate() {
1842 let is_from_entity = *entity_mask.get(char_idx).unwrap_or(&false);
1843
1844 if is_line_terminator(*c) {
1845 if let (Some(start), Some(end)) = (line_start, line_end) {
1848 let line_text =
1849 extract_line_content(&chars, start, end, entity_mask, !is_first_line, true);
1850 add_line_of_jsx_text_owned(line_text, &mut acc, &mut only_line);
1851 }
1852 is_first_line = false;
1853 line_start = None;
1854 line_end = None;
1855 } else if !is_white_space_single_line(*c) || is_from_entity {
1856 line_end = Some(char_idx + 1);
1858 if line_start.is_none() {
1859 line_start = Some(char_idx);
1860 }
1861 }
1862 }
1863
1864 if let Some(start) = line_start {
1867 let line_text = extract_line_content(
1868 &chars,
1869 start,
1870 chars.len(),
1871 entity_mask,
1872 !is_first_line,
1873 false,
1874 );
1875 add_line_of_jsx_text_owned(line_text, &mut acc, &mut only_line);
1876 }
1877
1878 if let Some(acc) = acc {
1879 acc.into()
1880 } else if let Some(only_line) = only_line {
1881 only_line.into()
1882 } else {
1883 "".into()
1884 }
1885}
1886
1887fn extract_line_content(
1892 chars: &[char],
1893 start: usize,
1894 end: usize,
1895 entity_mask: &[bool],
1896 trim_leading: bool,
1897 trim_trailing: bool,
1898) -> String {
1899 let mut actual_start = start;
1901 if trim_leading {
1902 while actual_start < end {
1903 let c = chars[actual_start];
1904 let is_from_entity = *entity_mask.get(actual_start).unwrap_or(&false);
1905 if !is_white_space_single_line(c) || is_from_entity {
1906 break;
1907 }
1908 actual_start += 1;
1909 }
1910 }
1911
1912 let mut actual_end = end;
1914 if trim_trailing {
1915 while actual_end > actual_start {
1916 let c = chars[actual_end - 1];
1917 let is_from_entity = *entity_mask.get(actual_end - 1).unwrap_or(&false);
1918 if !is_white_space_single_line(c) || is_from_entity {
1919 break;
1920 }
1921 actual_end -= 1;
1922 }
1923 }
1924
1925 chars[actual_start..actual_end].iter().collect()
1926}
1927
1928fn add_line_of_jsx_text_owned(
1930 line: String,
1931 acc: &mut Option<String>,
1932 only_line: &mut Option<String>,
1933) {
1934 if line.is_empty() {
1935 return;
1936 }
1937
1938 if let Some(buffer) = acc.as_mut() {
1939 buffer.push(' ');
1940 buffer.push_str(&line);
1941 } else if let Some(only_line_content) = only_line.take() {
1942 let mut buffer = String::with_capacity(line.len() * 2);
1943 buffer.push_str(&only_line_content);
1944 buffer.push(' ');
1945 buffer.push_str(&line);
1946 *acc = Some(buffer);
1947 } else {
1948 *only_line = Some(line);
1949 }
1950}
1951
1952#[allow(dead_code)]
1953#[inline]
1954fn jsx_text_to_str<'a, T>(t: &'a T) -> Wtf8Atom
1955where
1956 &'a T: Into<&'a Wtf8>,
1957 T: ?Sized,
1958{
1959 let t = t.into();
1960 if let Some(s) = t.as_str() {
1962 return jsx_text_to_str_impl(s).into();
1963 }
1964
1965 jsx_text_to_str_wtf8_impl(t)
1967}
1968
1969fn jsx_text_to_str_wtf8_impl(t: &Wtf8) -> Wtf8Atom {
1971 let mut acc: Option<Wtf8Buf> = None;
1972 let mut only_line: Option<(usize, usize)> = None; let mut first_non_whitespace: Option<usize> = Some(0);
1974 let mut last_non_whitespace: Option<usize> = None;
1975
1976 let mut byte_pos = 0;
1977 for cp in t.code_points() {
1978 let c = cp.to_char_lossy();
1979 let cp_value = cp.to_u32();
1980
1981 let cp_byte_len = if cp_value < 0x80 {
1983 1
1984 } else if cp_value < 0x800 {
1985 2
1986 } else if cp_value < 0x10000 {
1987 3
1988 } else {
1989 4
1990 };
1991
1992 if is_line_terminator(c) {
1993 if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
1994 add_line_of_jsx_text_wtf8(first, last, t, &mut acc, &mut only_line);
1995 }
1996 first_non_whitespace = None;
1997 } else if !is_white_space_single_line(c) {
1998 last_non_whitespace = Some(byte_pos + cp_byte_len);
1999 if first_non_whitespace.is_none() {
2000 first_non_whitespace.replace(byte_pos);
2001 }
2002 }
2003
2004 byte_pos += cp_byte_len;
2005 }
2006
2007 if let Some(first) = first_non_whitespace {
2009 add_line_of_jsx_text_wtf8(first, t.len(), t, &mut acc, &mut only_line);
2010 }
2011
2012 if let Some(acc) = acc {
2013 acc.into()
2014 } else if let Some((start, end)) = only_line {
2015 t.slice(start, end).into()
2016 } else {
2017 Wtf8Atom::default()
2018 }
2019}
2020
2021fn add_line_of_jsx_text_wtf8(
2023 line_start: usize,
2024 line_end: usize,
2025 source: &Wtf8,
2026 acc: &mut Option<Wtf8Buf>,
2027 only_line: &mut Option<(usize, usize)>,
2028) {
2029 if let Some((only_start, only_end)) = only_line.take() {
2030 let mut buffer = Wtf8Buf::with_capacity(source.len());
2032 buffer.push_wtf8(source.slice(only_start, only_end));
2033 buffer.push_str(" ");
2034 buffer.push_wtf8(source.slice(line_start, line_end));
2035 *acc = Some(buffer);
2036 } else if let Some(ref mut buffer) = acc {
2037 buffer.push_str(" ");
2039 buffer.push_wtf8(source.slice(line_start, line_end));
2040 } else {
2041 *only_line = Some((line_start, line_end));
2043 }
2044}
2045
2046#[inline]
2048fn jsx_text_to_str_impl(t: &str) -> Atom {
2049 if !t.is_empty()
2051 && !t.chars().any(is_line_terminator)
2052 && !t.starts_with(is_white_space_single_line)
2053 && !t.ends_with(is_white_space_single_line)
2054 {
2055 return t.into();
2056 }
2057
2058 let mut acc: Option<String> = None;
2059 let mut only_line: Option<&str> = None;
2060 let mut first_non_whitespace: Option<usize> = Some(0);
2061 let mut last_non_whitespace: Option<usize> = None;
2062
2063 for (index, c) in t.char_indices() {
2064 if is_line_terminator(c) {
2065 if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
2066 let line_text = &t[first..last];
2067 add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
2068 }
2069 first_non_whitespace = None;
2070 } else if !is_white_space_single_line(c) {
2071 last_non_whitespace = Some(index + c.len_utf8());
2072 if first_non_whitespace.is_none() {
2073 first_non_whitespace.replace(index);
2074 }
2075 }
2076 }
2077
2078 if let Some(first) = first_non_whitespace {
2079 let line_text = &t[first..];
2080 add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
2081 }
2082
2083 if let Some(acc) = acc {
2084 acc.into()
2085 } else if let Some(only_line) = only_line {
2086 only_line.into()
2087 } else {
2088 "".into()
2089 }
2090}
2091
2092fn is_white_space_single_line(c: char) -> bool {
2103 matches!(c, ' ' | '\t')
2104}
2105
2106fn add_line_of_jsx_text<'a>(
2109 trimmed_line: &'a str,
2110 acc: &mut Option<String>,
2111 only_line: &mut Option<&'a str>,
2112) {
2113 if let Some(buffer) = acc.as_mut() {
2114 buffer.push(' ');
2117 } else if let Some(only_line_content) = only_line.take() {
2118 let mut buffer = String::with_capacity(trimmed_line.len() * 2); buffer.push_str(only_line_content);
2124 buffer.push(' ');
2125 *acc = Some(buffer);
2126 }
2127
2128 if let Some(buffer) = acc.as_mut() {
2132 buffer.push_str(trimmed_line);
2133 } else {
2134 *only_line = Some(trimmed_line);
2139 }
2140}
2141
2142fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option<Box<Expr>> {
2143 Some(match v {
2144 JSXAttrValue::Str(s) => {
2145 let value = transform_jsx_attr_str(&s.value);
2146
2147 Lit::Str(Str {
2148 span: s.span,
2149 raw: None,
2150 value: value.into(),
2151 })
2152 .into()
2153 }
2154 JSXAttrValue::JSXExprContainer(e) => match e.expr {
2155 JSXExpr::JSXEmptyExpr(_) => None?,
2156 JSXExpr::Expr(e) => e,
2157 #[cfg(swc_ast_unknown)]
2158 _ => panic!("unable to access unknown nodes"),
2159 },
2160 JSXAttrValue::JSXElement(e) => e.into(),
2161 JSXAttrValue::JSXFragment(f) => f.into(),
2162 #[cfg(swc_ast_unknown)]
2163 _ => panic!("unable to access unknown nodes"),
2164 })
2165}
2166
2167fn transform_jsx_attr_str(v: &Wtf8) -> Wtf8Buf {
2168 let needs_transform = v.code_points().any(|cp| {
2170 if let Some(c) = cp.to_char() {
2171 matches!(
2172 c,
2173 '\u{0008}' | '\u{000c}' | '\n' | '\r' | '\t' | '\u{000b}' | '\0'
2174 )
2175 } else {
2176 false
2177 }
2178 });
2179
2180 if !needs_transform {
2181 return v.to_owned();
2182 }
2183
2184 let single_quote = false;
2185 let mut buf = Wtf8Buf::with_capacity(v.len());
2186 let mut iter = v.code_points().peekable();
2187
2188 while let Some(code_point) = iter.next() {
2189 if let Some(c) = code_point.to_char() {
2190 match c {
2191 '\u{0008}' => buf.push_str("\\b"),
2192 '\u{000c}' => buf.push_str("\\f"),
2193 ' ' => buf.push_char(' '),
2194
2195 '\n' | '\r' | '\t' => {
2196 buf.push_char(' ');
2197
2198 while let Some(next) = iter.peek() {
2199 if next.to_char() == Some(' ') {
2200 iter.next();
2201 } else {
2202 break;
2203 }
2204 }
2205 }
2206 '\u{000b}' => buf.push_str("\\v"),
2207 '\0' => buf.push_str("\\x00"),
2208
2209 '\'' if single_quote => buf.push_str("\\'"),
2210 '"' if !single_quote => buf.push_char('"'),
2211
2212 '\x01'..='\x0f' | '\x10'..='\x1f' => {
2213 buf.push_char(c);
2214 }
2215
2216 '\x20'..='\x7e' => {
2217 buf.push_char(c);
2219 }
2220 '\u{7f}'..='\u{ff}' => {
2221 buf.push_char(c);
2222 }
2223
2224 _ => {
2225 buf.push_char(c);
2226 }
2227 }
2228 } else {
2229 buf.push(code_point);
2230 }
2231 }
2232
2233 buf
2234}