#![allow(clippy::redundant_allocation)]
use std::sync::RwLock;
use bytes_str::BytesStr;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use string_enum::StringEnum;
use swc_atoms::{
atom,
wtf8::{Wtf8, Wtf8Buf},
Atom, Wtf8Atom,
};
use swc_common::{
comments::{Comment, CommentKind, Comments},
errors::HANDLER,
sync::Lrc,
util::take::Take,
FileName, Mark, SourceMap, Span, Spanned, SyntaxContext, DUMMY_SP,
};
use swc_config::merge::Merge;
use swc_ecma_ast::*;
use swc_ecma_hooks::VisitMutHook;
use swc_ecma_parser::{parse_file_as_expr, Syntax};
use swc_ecma_utils::{
drop_span, prepend_stmt, private_ident, quote_ident, str::is_line_terminator, ExprFactory,
StmtLike,
};
use swc_ecma_visit::VisitMut;
use self::static_check::should_use_create_element;
use crate::{
jsx_self::JsxSelfCtx,
refresh::options::{deserialize_refresh, RefreshOptions},
};
mod static_check;
#[cfg(test)]
mod tests;
#[derive(StringEnum, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub enum Runtime {
Automatic,
Classic,
Preserve,
}
impl Default for Runtime {
fn default() -> Self {
Runtime::Classic
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, Merge)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Options {
#[serde(skip, default)]
pub next: Option<bool>,
#[serde(default)]
pub runtime: Option<Runtime>,
#[serde(default)]
pub import_source: Option<Atom>,
#[serde(default)]
pub pragma: Option<BytesStr>,
#[serde(default)]
pub pragma_frag: Option<BytesStr>,
#[serde(default)]
pub throw_if_namespace: Option<bool>,
#[serde(default)]
pub development: Option<bool>,
#[deprecated(
since = "0.167.4",
note = r#"Since `useBuiltIns` is removed in swc, you can remove it from the config."#
)]
#[serde(default, alias = "useBuiltIns")]
pub use_builtins: Option<bool>,
#[deprecated(
since = "0.167.4",
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."#
)]
#[serde(default)]
pub use_spread: Option<bool>,
#[serde(default, deserialize_with = "deserialize_refresh")]
pub refresh: Option<RefreshOptions>,
}
#[cfg(feature = "concurrent")]
macro_rules! static_str {
($s:expr) => {{
static VAL: Lazy<BytesStr> = Lazy::new(|| $s.into());
VAL.clone()
}};
}
#[cfg(not(feature = "concurrent"))]
macro_rules! static_str {
($s:expr) => {
$s.into()
};
}
pub fn default_import_source() -> Atom {
atom!("react")
}
pub fn default_pragma() -> BytesStr {
static_str!("React.createElement")
}
pub fn default_pragma_frag() -> BytesStr {
static_str!("React.Fragment")
}
fn default_throw_if_namespace() -> bool {
true
}
pub fn parse_expr_for_jsx(
cm: &SourceMap,
name: &str,
src: BytesStr,
top_level_mark: Mark,
) -> Box<Expr> {
let fm = cm.new_source_file(cache_filename(name), src);
parse_file_as_expr(
&fm,
Syntax::default(),
Default::default(),
None,
&mut Vec::new(),
)
.map_err(|e| {
if HANDLER.is_set() {
HANDLER.with(|h| {
e.into_diagnostic(h)
.note("Failed to parse jsx pragma")
.emit()
})
}
})
.map(drop_span)
.map(|mut expr| {
apply_mark(&mut expr, top_level_mark);
expr
})
.unwrap_or_else(|()| {
panic!(
"failed to parse jsx option {}: '{}' is not an expression",
name, fm.src,
)
})
}
fn apply_mark(e: &mut Expr, mark: Mark) {
match e {
Expr::Ident(i) => {
i.ctxt = i.ctxt.apply_mark(mark);
}
Expr::Member(MemberExpr { obj, .. }) => {
apply_mark(obj, mark);
}
_ => {}
}
}
pub fn hook<C>(
cm: Lrc<SourceMap>,
comments: Option<C>,
options: Options,
top_level_mark: Mark,
unresolved_mark: Mark,
) -> impl VisitMutHook<()>
where
C: Comments,
{
Jsx {
cm: cm.clone(),
top_level_mark,
unresolved_mark,
runtime: options.runtime.unwrap_or_default(),
import_source: options.import_source.unwrap_or_else(default_import_source),
import_jsx: None,
import_jsxs: None,
import_fragment: None,
import_create_element: None,
pragma: Lrc::new(parse_expr_for_jsx(
&cm,
"pragma",
options.pragma.unwrap_or_else(default_pragma),
top_level_mark,
)),
comments,
pragma_frag: Lrc::new(parse_expr_for_jsx(
&cm,
"pragmaFrag",
options.pragma_frag.unwrap_or_else(default_pragma_frag),
top_level_mark,
)),
development: options.development.unwrap_or_default(),
throw_if_namespace: options
.throw_if_namespace
.unwrap_or_else(default_throw_if_namespace),
top_level_node: true,
self_ctx: JsxSelfCtx::new(),
}
}
pub fn jsx<C>(
cm: Lrc<SourceMap>,
comments: Option<C>,
options: Options,
top_level_mark: Mark,
unresolved_mark: Mark,
) -> impl Pass + VisitMut
where
C: Comments,
{
use swc_ecma_hooks::VisitMutWithHook;
use swc_ecma_visit::visit_mut_pass;
visit_mut_pass(VisitMutWithHook {
hook: hook(cm, comments, options, top_level_mark, unresolved_mark),
context: (),
})
}
struct Jsx<C>
where
C: Comments,
{
cm: Lrc<SourceMap>,
top_level_mark: Mark,
unresolved_mark: Mark,
runtime: Runtime,
import_source: Atom,
import_jsx: Option<Ident>,
import_jsxs: Option<Ident>,
import_create_element: Option<Ident>,
import_fragment: Option<Ident>,
top_level_node: bool,
pragma: Lrc<Box<Expr>>,
comments: Option<C>,
pragma_frag: Lrc<Box<Expr>>,
development: bool,
throw_if_namespace: bool,
self_ctx: JsxSelfCtx,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct JsxDirectives {
pub runtime: Option<Runtime>,
pub import_source: Option<Atom>,
pub pragma: Option<Lrc<Box<Expr>>>,
pub pragma_frag: Option<Lrc<Box<Expr>>>,
}
impl JsxDirectives {
pub fn from_comments(
cm: &SourceMap,
_: Span,
comments: &[Comment],
top_level_mark: Mark,
) -> Self {
let mut res = JsxDirectives::default();
for cmt in comments {
if cmt.kind != CommentKind::Block {
continue;
}
for line in cmt.text.lines() {
let mut line = line.trim();
if line.starts_with('*') {
line = line[1..].trim();
}
if !line.starts_with("@jsx") {
continue;
}
let mut words = line.split_whitespace();
loop {
let pragma = words.next();
if pragma.is_none() {
break;
}
let val = words.next();
match pragma {
Some("@jsxRuntime") => match val {
Some("classic") => res.runtime = Some(Runtime::Classic),
Some("automatic") => res.runtime = Some(Runtime::Automatic),
None => {}
_ => {
if HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
cmt.span,
"Runtime must be either `classic` or `automatic`.",
)
.emit()
});
}
}
},
Some("@jsxImportSource") => {
if let Some(src) = val {
res.runtime = Some(Runtime::Automatic);
res.import_source = Some(Atom::new(src));
}
}
Some("@jsxFrag") => {
if let Some(src) = val {
if is_valid_for_pragma(src) {
let mut e = parse_expr_for_jsx(
cm,
"module-jsx-pragma-frag",
cache_source(src),
top_level_mark,
);
e.set_span(cmt.span);
res.pragma_frag = Some(e.into())
}
}
}
Some("@jsx") => {
if let Some(src) = val {
if is_valid_for_pragma(src) {
let mut e = parse_expr_for_jsx(
cm,
"module-jsx-pragma",
cache_source(src),
top_level_mark,
);
e.set_span(cmt.span);
res.pragma = Some(e.into());
}
}
}
_ => {}
}
}
}
}
res
}
}
#[cfg(feature = "concurrent")]
fn cache_filename(name: &str) -> Lrc<FileName> {
static FILENAME_CACHE: Lazy<RwLock<FxHashMap<String, Lrc<FileName>>>> =
Lazy::new(|| RwLock::new(FxHashMap::default()));
{
let cache = FILENAME_CACHE
.read()
.expect("Failed to read FILENAME_CACHE");
if let Some(f) = cache.get(name) {
return f.clone();
}
}
let file = Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")));
{
let mut cache = FILENAME_CACHE
.write()
.expect("Failed to write FILENAME_CACHE");
cache.insert(name.to_string(), file.clone());
}
file
}
#[cfg(not(feature = "concurrent"))]
fn cache_filename(name: &str) -> Lrc<FileName> {
Lrc::new(FileName::Internal(format!("jsx-config-{name}.js")))
}
#[cfg(feature = "concurrent")]
fn cache_source(src: &str) -> BytesStr {
use rustc_hash::FxHashSet;
static CACHE: Lazy<RwLock<FxHashSet<BytesStr>>> =
Lazy::new(|| RwLock::new(FxHashSet::default()));
{
let cache = CACHE.write().unwrap();
if let Some(cached) = cache.get(src) {
return cached.clone();
}
}
let cached: BytesStr = src.to_string().into();
{
let mut cache = CACHE.write().unwrap();
cache.insert(cached.clone());
}
cached
}
#[cfg(not(feature = "concurrent"))]
fn cache_source(src: &str) -> BytesStr {
src.to_string().into()
}
fn is_valid_for_pragma(s: &str) -> bool {
if s.is_empty() {
return false;
}
if !s.starts_with(|c: char| Ident::is_valid_start(c)) {
return false;
}
for c in s.chars() {
if !Ident::is_valid_continue(c) && c != '.' {
return false;
}
}
true
}
impl<C> Jsx<C>
where
C: Comments,
{
fn jsx_dev_source_arg(&self, span: Span) -> ExprOrSpread {
if span == DUMMY_SP {
return Expr::undefined(DUMMY_SP).as_arg();
}
let loc = self.cm.lookup_char_pos(span.lo);
ObjectLit {
span: DUMMY_SP,
props: vec![
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("fileName")),
value: Box::new(Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
raw: None,
value: loc.file.name.to_string().into(),
}))),
}))),
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("lineNumber")),
value: loc.line.into(),
}))),
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("columnNumber")),
value: (loc.col.0 + 1).into(),
}))),
],
}
.as_arg()
}
fn jsx_dev_self_arg(&self) -> ExprOrSpread {
if self.self_ctx.can_add_self() {
ThisExpr { span: DUMMY_SP }.as_arg()
} else {
Expr::undefined(DUMMY_SP).as_arg()
}
}
fn process_attr_value(&mut self, value: Option<JSXAttrValue>) -> Box<Expr> {
match value {
Some(JSXAttrValue::JSXElement(el)) => Box::new(self.jsx_elem_to_expr(*el)),
Some(JSXAttrValue::JSXFragment(frag)) => Box::new(self.jsx_frag_to_expr(frag)),
Some(JSXAttrValue::JSXExprContainer(container)) => match container.expr {
JSXExpr::Expr(e) => e,
JSXExpr::JSXEmptyExpr(_) => panic!("empty expression container"),
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
},
Some(v) => jsx_attr_value_to_expr(v).expect("empty expression container?"),
None => true.into(),
}
}
fn inject_runtime<T, F>(&mut self, body: &mut Vec<T>, inject: F)
where
T: StmtLike,
F: Fn(Vec<(Ident, IdentName)>, &str, &mut Vec<T>),
{
if self.runtime == Runtime::Automatic {
if let Some(local) = self.import_create_element.take() {
inject(
vec![(local, quote_ident!("createElement"))],
&self.import_source,
body,
);
}
let imports = self.import_jsx.take();
let imports = if self.development {
imports
.map(|local| (local, quote_ident!("jsxDEV")))
.into_iter()
.chain(
self.import_fragment
.take()
.map(|local| (local, quote_ident!("Fragment"))),
)
.collect::<Vec<_>>()
} else {
imports
.map(|local| (local, quote_ident!("jsx")))
.into_iter()
.chain(
self.import_jsxs
.take()
.map(|local| (local, quote_ident!("jsxs"))),
)
.chain(
self.import_fragment
.take()
.map(|local| (local, quote_ident!("Fragment"))),
)
.collect::<Vec<_>>()
};
if !imports.is_empty() {
let jsx_runtime = if self.development {
"jsx-dev-runtime"
} else {
"jsx-runtime"
};
let value = format!("{}/{}", self.import_source, jsx_runtime);
inject(imports, &value, body)
}
}
}
fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
let mut span = el.span();
if let Some(comments) = &self.comments {
if span.lo.is_dummy() {
span.lo = Span::dummy_with_cmt().lo;
}
comments.add_pure_comment(span.lo);
}
match self.runtime {
Runtime::Automatic => {
let fragment = self
.import_fragment
.get_or_insert_with(|| private_ident!("_Fragment"))
.clone();
let mut props_obj = ObjectLit {
span: DUMMY_SP,
props: Vec::new(),
};
let children = el
.children
.into_iter()
.filter_map(|child| self.jsx_elem_child_to_expr(child))
.map(Some)
.collect::<Vec<_>>();
let use_jsxs = match children.len() {
0 => false,
1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
props_obj
.props
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("children")),
value: children.into_iter().next().flatten().unwrap().expr,
}))));
false
}
_ => {
props_obj
.props
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("children")),
value: ArrayLit {
span: DUMMY_SP,
elems: children,
}
.into(),
}))));
true
}
};
let jsx = if use_jsxs && !self.development {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
} else {
let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
self.import_jsx
.get_or_insert_with(|| private_ident!(jsx))
.clone()
};
let args = if self.development {
vec![
fragment.as_arg(),
props_obj.as_arg(),
Expr::undefined(DUMMY_SP).as_arg(),
use_jsxs.as_arg(),
self.jsx_dev_source_arg(el.opening.span),
self.jsx_dev_self_arg(),
]
} else {
vec![fragment.as_arg(), props_obj.as_arg()]
};
CallExpr {
span,
callee: jsx.as_callee(),
args,
..Default::default()
}
.into()
}
Runtime::Classic => {
let children_capacity = el.children.len();
let mut args = Vec::with_capacity(2 + children_capacity);
args.push((*self.pragma_frag).clone().as_arg());
args.push(Lit::Null(Null { span: DUMMY_SP }).as_arg());
for child in el.children {
if let Some(expr) = self.jsx_elem_child_to_expr(child) {
args.push(expr);
}
}
CallExpr {
span,
callee: (*self.pragma).clone().as_callee(),
args,
..Default::default()
}
.into()
}
Runtime::Preserve => unreachable!(),
}
}
fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr {
let top_level_node = self.top_level_node;
let mut span = el.span();
let use_create_element = should_use_create_element(&el.opening.attrs);
self.top_level_node = false;
let name = self.jsx_name(el.opening.name);
if let Some(comments) = &self.comments {
if span.lo.is_dummy() {
span.lo = Span::dummy_with_cmt().lo;
}
comments.add_pure_comment(span.lo);
}
match self.runtime {
Runtime::Automatic => {
let estimated_props_capacity = el.opening.attrs.len() + 1; let mut props_obj = ObjectLit {
span: DUMMY_SP,
props: Vec::with_capacity(estimated_props_capacity),
};
let mut key = None;
let mut source_props = None;
let mut self_props = None;
for attr in el.opening.attrs {
match attr {
JSXAttrOrSpread::JSXAttr(attr) => {
match attr.name {
JSXAttrName::Ident(i) => {
if !use_create_element && i.sym == "key" {
key = attr
.value
.and_then(jsx_attr_value_to_expr)
.map(|expr| expr.as_arg());
if key.is_none() && HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
i.span,
"The value of property 'key' should not \
be empty",
)
.emit();
});
}
continue;
}
if !use_create_element
&& *i.sym == *"__source"
&& self.development
{
if source_props.is_some() {
panic!("Duplicate __source is found");
}
source_props = attr
.value
.and_then(jsx_attr_value_to_expr)
.map(|expr| expr.as_arg());
assert_ne!(
source_props, None,
"value of property '__source' should not be empty"
);
continue;
}
if !use_create_element
&& *i.sym == *"__self"
&& self.development
{
if self_props.is_some() {
panic!("Duplicate __self is found");
}
self_props = attr
.value
.and_then(jsx_attr_value_to_expr)
.map(|expr| expr.as_arg());
assert_ne!(
self_props, None,
"value of property '__self' should not be empty"
);
continue;
}
let value = self.process_attr_value(attr.value);
let key = if i.sym.contains('-') {
PropName::Str(Str {
span: i.span,
raw: None,
value: i.sym.into(),
})
} else {
PropName::Ident(i)
};
props_obj.props.push(PropOrSpread::Prop(Box::new(
Prop::KeyValue(KeyValueProp { key, value }),
)));
}
JSXAttrName::JSXNamespacedName(JSXNamespacedName {
ns,
name,
..
}) => {
if self.throw_if_namespace && HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
span,
"JSX Namespace is disabled by default because \
react does not support it yet. You can \
specify jsc.transform.react.throwIfNamespace \
to false to override default behavior",
)
.emit()
});
}
let value = self.process_attr_value(attr.value);
let str_value = format!("{}:{}", ns.sym, name.sym);
let key = Str {
span,
raw: None,
value: str_value.into(),
};
let key = PropName::Str(key);
props_obj.props.push(PropOrSpread::Prop(Box::new(
Prop::KeyValue(KeyValueProp { key, value }),
)));
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
JSXAttrOrSpread::SpreadElement(attr) => match *attr.expr {
Expr::Object(obj) => {
props_obj.props.extend(obj.props);
}
_ => {
props_obj.props.push(PropOrSpread::Spread(attr));
}
},
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
let mut children = el
.children
.into_iter()
.filter_map(|child| self.jsx_elem_child_to_expr(child))
.map(Some)
.collect::<Vec<_>>();
let use_jsxs = match children.len() {
0 => false,
1 if matches!(children.first(), Some(Some(child)) if child.spread.is_none()) => {
if !use_create_element {
props_obj
.props
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("children")),
value: children
.take()
.into_iter()
.next()
.flatten()
.unwrap()
.expr,
}))));
}
false
}
_ => {
props_obj
.props
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("children")),
value: ArrayLit {
span: DUMMY_SP,
elems: children.take(),
}
.into(),
}))));
true
}
};
let jsx = if use_create_element {
self.import_create_element
.get_or_insert_with(|| private_ident!("_createElement"))
.clone()
} else if use_jsxs && !self.development {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
} else {
let jsx = if self.development { "_jsxDEV" } else { "_jsx" };
self.import_jsx
.get_or_insert_with(|| private_ident!(jsx))
.clone()
};
self.top_level_node = top_level_node;
let args = if use_create_element {
let mut args = Vec::with_capacity(2 + children.len());
args.push(name.as_arg());
args.push(props_obj.as_arg());
args.extend(children.into_iter().flatten());
args
} else if self.development {
let mut args = Vec::with_capacity(6);
args.push(name.as_arg());
args.push(props_obj.as_arg());
let key = key.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
args.push(key);
args.push(use_jsxs.as_arg());
let source_props =
source_props.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
args.push(source_props);
let self_props =
self_props.unwrap_or_else(|| Expr::undefined(DUMMY_SP).as_arg());
args.push(self_props);
args
} else {
let mut args = Vec::with_capacity(if key.is_some() { 3 } else { 2 });
args.push(name.as_arg());
args.push(props_obj.as_arg());
if let Some(key) = key {
args.push(key);
}
args
};
CallExpr {
span,
callee: jsx.as_callee(),
args,
..Default::default()
}
.into()
}
Runtime::Classic => {
let children_capacity = el.children.len();
let mut args = Vec::with_capacity(2 + children_capacity);
args.push(name.as_arg());
args.push(self.fold_attrs_for_classic(el.opening.attrs).as_arg());
for child in el.children {
if let Some(expr) = self.jsx_elem_child_to_expr(child) {
args.push(expr);
}
}
CallExpr {
span,
callee: (*self.pragma).clone().as_callee(),
args,
..Default::default()
}
.into()
}
Runtime::Preserve => unreachable!(),
}
}
fn jsx_elem_child_to_expr(&mut self, c: JSXElementChild) -> Option<ExprOrSpread> {
self.top_level_node = false;
Some(match c {
JSXElementChild::JSXText(text) => {
let value = jsx_text_to_str_with_raw(&text.value, &text.raw);
let s = Str {
span: text.span,
raw: None,
value,
};
if s.value.is_empty() {
return None;
}
Lit::Str(s).as_arg()
}
JSXElementChild::JSXExprContainer(JSXExprContainer {
expr: JSXExpr::Expr(e),
..
}) => e.as_arg(),
JSXElementChild::JSXExprContainer(JSXExprContainer {
expr: JSXExpr::JSXEmptyExpr(..),
..
}) => return None,
JSXElementChild::JSXElement(el) => self.jsx_elem_to_expr(*el).as_arg(),
JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(),
JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => ExprOrSpread {
spread: Some(span),
expr,
},
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
})
}
fn fold_attrs_for_classic(&mut self, attrs: Vec<JSXAttrOrSpread>) -> Box<Expr> {
if attrs.is_empty() {
return Lit::Null(Null { span: DUMMY_SP }).into();
}
let attr_cnt = attrs.len();
let mut props = Vec::new();
for attr in attrs {
match attr {
JSXAttrOrSpread::JSXAttr(attr) => {
props.push(PropOrSpread::Prop(Box::new(self.attr_to_prop(attr))))
}
JSXAttrOrSpread::SpreadElement(spread) => {
if attr_cnt == 1 {
return spread.expr;
}
match *spread.expr {
Expr::Object(obj) => props.extend(obj.props),
_ => props.push(PropOrSpread::Spread(spread)),
}
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
let obj = ObjectLit {
span: DUMMY_SP,
props,
};
obj.into()
}
fn attr_to_prop(&mut self, a: JSXAttr) -> Prop {
let key = to_prop_name(a.name);
let value = a
.value
.map(|v| match v {
JSXAttrValue::Str(s) => {
let value = transform_jsx_attr_str(&s.value);
Lit::Str(Str {
span: s.span,
raw: None,
value: value.into(),
})
.into()
}
JSXAttrValue::JSXExprContainer(JSXExprContainer {
expr: JSXExpr::Expr(e),
..
}) => e,
JSXAttrValue::JSXElement(element) => Box::new(self.jsx_elem_to_expr(*element)),
JSXAttrValue::JSXFragment(fragment) => Box::new(self.jsx_frag_to_expr(fragment)),
JSXAttrValue::JSXExprContainer(JSXExprContainer {
span: _,
expr: JSXExpr::JSXEmptyExpr(_),
}) => unreachable!("attr_to_prop(JSXEmptyExpr)"),
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
})
.unwrap_or_else(|| {
Lit::Bool(Bool {
span: key.span(),
value: true,
})
.into()
});
Prop::KeyValue(KeyValueProp { key, value })
}
}
impl<C> Jsx<C>
where
C: Comments,
{
fn parse_directives(&mut self, span: Span) -> bool {
let mut found = false;
let directives = self.comments.with_leading(span.lo, |comments| {
JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
});
let JsxDirectives {
runtime,
import_source,
pragma,
pragma_frag,
} = directives;
if let Some(runtime) = runtime {
found = true;
self.runtime = runtime;
}
if let Some(import_source) = import_source {
found = true;
self.import_source = import_source;
}
if let Some(pragma) = pragma {
if let Runtime::Automatic = self.runtime {
if HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
pragma.span(),
"pragma cannot be set when runtime is automatic",
)
.emit()
});
}
}
found = true;
self.pragma = pragma;
}
if let Some(pragma_frag) = pragma_frag {
if let Runtime::Automatic = self.runtime {
if HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
pragma_frag.span(),
"pragmaFrag cannot be set when runtime is automatic",
)
.emit()
});
}
}
found = true;
self.pragma_frag = pragma_frag;
}
found
}
}
impl<C> VisitMutHook<()> for Jsx<C>
where
C: Comments,
{
fn enter_class(&mut self, n: &mut Class, _ctx: &mut ()) {
self.self_ctx.enter_class(n);
}
fn exit_class(&mut self, _n: &mut Class, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_fn_decl(&mut self, _n: &mut FnDecl, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_fn_decl(&mut self, _n: &mut FnDecl, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_fn_expr(&mut self, _n: &mut FnExpr, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_fn_expr(&mut self, _n: &mut FnExpr, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_getter_prop(&mut self, _n: &mut GetterProp, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_getter_prop(&mut self, _n: &mut GetterProp, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_setter_prop(&mut self, _n: &mut SetterProp, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_setter_prop(&mut self, _n: &mut SetterProp, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_method_prop(&mut self, _n: &mut MethodProp, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_method_prop(&mut self, _n: &mut MethodProp, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_constructor(&mut self, _n: &mut Constructor, _ctx: &mut ()) {
self.self_ctx.enter_constructor();
}
fn exit_constructor(&mut self, _n: &mut Constructor, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_class_method(&mut self, _n: &mut ClassMethod, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_class_method(&mut self, _n: &mut ClassMethod, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_private_method(&mut self, _n: &mut PrivateMethod, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_private_method(&mut self, _n: &mut PrivateMethod, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn enter_static_block(&mut self, _n: &mut StaticBlock, _ctx: &mut ()) {
self.self_ctx.enter_non_constructor_scope();
}
fn exit_static_block(&mut self, _n: &mut StaticBlock, _ctx: &mut ()) {
self.self_ctx.pop();
}
fn exit_expr(&mut self, expr: &mut Expr, _ctx: &mut ()) {
let top_level_node = self.top_level_node;
let mut did_work = false;
if let Expr::JSXElement(el) = expr {
did_work = true;
*expr = self.jsx_elem_to_expr(*el.take());
} else if let Expr::JSXFragment(frag) = expr {
did_work = true;
*expr = self.jsx_frag_to_expr(frag.take());
} else if let Expr::Paren(ParenExpr {
expr: inner_expr, ..
}) = expr
{
if let Expr::JSXElement(el) = &mut **inner_expr {
did_work = true;
*expr = self.jsx_elem_to_expr(*el.take());
} else if let Expr::JSXFragment(frag) = &mut **inner_expr {
did_work = true;
*expr = self.jsx_frag_to_expr(frag.take());
}
}
if did_work {
self.top_level_node = false;
}
self.top_level_node = top_level_node;
}
fn enter_module(&mut self, module: &mut Module, _ctx: &mut ()) {
self.parse_directives(module.span);
for item in &module.body {
let span = item.span();
if self.parse_directives(span) {
break;
}
}
}
fn exit_module(&mut self, module: &mut Module, _ctx: &mut ()) {
if self.runtime == Runtime::Automatic {
self.inject_runtime(&mut module.body, |imports, src, stmts| {
let specifiers = imports
.into_iter()
.map(|(local, imported)| {
ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(ModuleExportName::Ident(imported.into())),
is_type_only: false,
})
})
.collect();
prepend_stmt(
stmts,
ImportDecl {
span: DUMMY_SP,
specifiers,
src: Str {
span: DUMMY_SP,
raw: None,
value: src.into(),
}
.into(),
type_only: Default::default(),
with: Default::default(),
phase: Default::default(),
}
.into(),
)
});
}
}
fn enter_script(&mut self, script: &mut Script, _ctx: &mut ()) {
self.parse_directives(script.span);
for item in &script.body {
let span = item.span();
if self.parse_directives(span) {
break;
}
}
}
fn exit_script(&mut self, script: &mut Script, _ctx: &mut ()) {
if self.runtime == Runtime::Automatic {
let mark = self.unresolved_mark;
self.inject_runtime(&mut script.body, |imports, src, stmts| {
prepend_stmt(stmts, add_require(imports, src, mark))
});
}
}
}
fn add_require(imports: Vec<(Ident, IdentName)>, src: &str, unresolved_mark: Mark) -> Stmt {
VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Object(ObjectPat {
span: DUMMY_SP,
props: imports
.into_iter()
.map(|(local, imported)| {
if imported.sym != local.sym {
ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(imported),
value: Box::new(Pat::Ident(local.into())),
})
} else {
ObjectPatProp::Assign(AssignPatProp {
span: DUMMY_SP,
key: local.into(),
value: None,
})
}
})
.collect(),
optional: false,
type_ann: None,
}),
init: Some(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(Ident {
ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
sym: atom!("require"),
optional: false,
..Default::default()
}))),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
value: src.into(),
raw: None,
}))),
}],
..Default::default()
}))),
definite: false,
}],
..Default::default()
}
.into()
}
impl<C> Jsx<C>
where
C: Comments,
{
fn jsx_name(&self, name: JSXElementName) -> Box<Expr> {
let span = name.span();
match name {
JSXElementName::Ident(i) => {
if i.sym == "this" {
return ThisExpr { span }.into();
}
if i.as_ref().starts_with(|c: char| c.is_ascii_lowercase()) {
Lit::Str(Str {
span,
raw: None,
value: i.sym.into(),
})
.into()
} else {
i.into()
}
}
JSXElementName::JSXNamespacedName(JSXNamespacedName {
ref ns, ref name, ..
}) => {
if self.throw_if_namespace && HANDLER.is_set() {
HANDLER.with(|handler| {
handler
.struct_span_err(
span,
"JSX Namespace is disabled by default because react does not \
support it yet. You can specify \
jsc.transform.react.throwIfNamespace to false to override \
default behavior",
)
.emit()
});
}
let value = format!("{}:{}", ns.sym, name.sym);
Lit::Str(Str {
span,
raw: None,
value: value.into(),
})
.into()
}
JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop, .. }) => {
fn convert_obj(obj: JSXObject) -> Box<Expr> {
let span = obj.span();
(match obj {
JSXObject::Ident(i) => {
if i.sym == "this" {
Expr::This(ThisExpr { span })
} else {
i.into()
}
}
JSXObject::JSXMemberExpr(e) => MemberExpr {
span,
obj: convert_obj(e.obj),
prop: MemberProp::Ident(e.prop),
}
.into(),
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
})
.into()
}
MemberExpr {
span,
obj: convert_obj(obj),
prop: MemberProp::Ident(prop),
}
.into()
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
}
fn to_prop_name(n: JSXAttrName) -> PropName {
let span = n.span();
match n {
JSXAttrName::Ident(i) => {
if i.sym.contains('-') {
PropName::Str(Str {
span,
raw: None,
value: i.sym.into(),
})
} else {
PropName::Ident(i)
}
}
JSXAttrName::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => {
let value = format!("{}:{}", ns.sym, name.sym);
PropName::Str(Str {
span,
raw: None,
value: value.into(),
})
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
#[inline]
fn jsx_text_to_str_with_raw(value: &Atom, raw: &Atom) -> Wtf8Atom {
if value.as_str() == raw.as_str() {
return jsx_text_to_str_impl(value).into();
}
let entity_mask = build_entity_mask(value, raw);
jsx_text_to_str_with_entity_mask(value, &entity_mask).into()
}
fn build_entity_mask(value: &str, raw: &str) -> Vec<bool> {
let mut mask = vec![false; value.chars().count()];
let mut value_char_idx = 0;
let mut raw_chars = raw.chars().peekable();
while let Some(raw_c) = raw_chars.next() {
if raw_c == '&' {
let mut entity_chars: Vec<char> = vec!['&'];
let mut found_semicolon = false;
for _ in 0..10 {
if let Some(&next_c) = raw_chars.peek() {
entity_chars.push(next_c);
raw_chars.next();
if next_c == ';' {
found_semicolon = true;
break;
}
} else {
break;
}
}
if found_semicolon && is_valid_entity(&entity_chars) {
if value_char_idx < mask.len() {
mask[value_char_idx] = true;
}
value_char_idx += 1;
} else {
value_char_idx += 1;
for _ in 1..entity_chars.len() {
value_char_idx += 1;
}
}
} else {
value_char_idx += 1;
}
}
mask
}
fn is_valid_entity(chars: &[char]) -> bool {
if chars.len() < 3 {
return false;
}
if chars[0] != '&' || chars[chars.len() - 1] != ';' {
return false;
}
let inner: String = chars[1..chars.len() - 1].iter().collect();
if let Some(stripped) = inner.strip_prefix('#') {
if let Some(hex) = stripped
.strip_prefix('x')
.or_else(|| stripped.strip_prefix('X'))
{
!hex.is_empty() && hex.chars().all(|c| c.is_ascii_hexdigit())
} else {
!stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
}
} else {
is_known_html_entity(&inner)
}
}
fn is_known_html_entity(name: &str) -> bool {
matches!(
name,
"nbsp"
| "iexcl"
| "cent"
| "pound"
| "curren"
| "yen"
| "brvbar"
| "sect"
| "uml"
| "copy"
| "ordf"
| "laquo"
| "not"
| "shy"
| "reg"
| "macr"
| "deg"
| "plusmn"
| "sup2"
| "sup3"
| "acute"
| "micro"
| "para"
| "middot"
| "cedil"
| "sup1"
| "ordm"
| "raquo"
| "frac14"
| "frac12"
| "frac34"
| "iquest"
| "Agrave"
| "Aacute"
| "Acirc"
| "Atilde"
| "Auml"
| "Aring"
| "AElig"
| "Ccedil"
| "Egrave"
| "Eacute"
| "Ecirc"
| "Euml"
| "Igrave"
| "Iacute"
| "Icirc"
| "Iuml"
| "ETH"
| "Ntilde"
| "Ograve"
| "Oacute"
| "Ocirc"
| "Otilde"
| "Ouml"
| "times"
| "Oslash"
| "Ugrave"
| "Uacute"
| "Ucirc"
| "Uuml"
| "Yacute"
| "THORN"
| "szlig"
| "agrave"
| "aacute"
| "acirc"
| "atilde"
| "auml"
| "aring"
| "aelig"
| "ccedil"
| "egrave"
| "eacute"
| "ecirc"
| "euml"
| "igrave"
| "iacute"
| "icirc"
| "iuml"
| "eth"
| "ntilde"
| "ograve"
| "oacute"
| "ocirc"
| "otilde"
| "ouml"
| "divide"
| "oslash"
| "ugrave"
| "uacute"
| "ucirc"
| "uuml"
| "yacute"
| "thorn"
| "yuml"
| "OElig"
| "oelig"
| "Scaron"
| "scaron"
| "Yuml"
| "fnof"
| "circ"
| "tilde"
| "Alpha"
| "Beta"
| "Gamma"
| "Delta"
| "Epsilon"
| "Zeta"
| "Eta"
| "Theta"
| "Iota"
| "Kappa"
| "Lambda"
| "Mu"
| "Nu"
| "Xi"
| "Omicron"
| "Pi"
| "Rho"
| "Sigma"
| "Tau"
| "Upsilon"
| "Phi"
| "Chi"
| "Psi"
| "Omega"
| "alpha"
| "beta"
| "gamma"
| "delta"
| "epsilon"
| "zeta"
| "eta"
| "theta"
| "iota"
| "kappa"
| "lambda"
| "mu"
| "nu"
| "xi"
| "omicron"
| "pi"
| "rho"
| "sigmaf"
| "sigma"
| "tau"
| "upsilon"
| "phi"
| "chi"
| "psi"
| "omega"
| "thetasym"
| "upsih"
| "piv"
| "ensp"
| "emsp"
| "thinsp"
| "zwnj"
| "zwj"
| "lrm"
| "rlm"
| "ndash"
| "mdash"
| "lsquo"
| "rsquo"
| "sbquo"
| "ldquo"
| "rdquo"
| "bdquo"
| "dagger"
| "Dagger"
| "bull"
| "hellip"
| "permil"
| "prime"
| "Prime"
| "lsaquo"
| "rsaquo"
| "oline"
| "frasl"
| "euro"
| "image"
| "weierp"
| "real"
| "trade"
| "alefsym"
| "larr"
| "uarr"
| "rarr"
| "darr"
| "harr"
| "crarr"
| "lArr"
| "uArr"
| "rArr"
| "dArr"
| "hArr"
| "forall"
| "part"
| "exist"
| "empty"
| "nabla"
| "isin"
| "notin"
| "ni"
| "prod"
| "sum"
| "minus"
| "lowast"
| "radic"
| "prop"
| "infin"
| "ang"
| "and"
| "or"
| "cap"
| "cup"
| "int"
| "there4"
| "sim"
| "cong"
| "asymp"
| "ne"
| "equiv"
| "le"
| "ge"
| "sub"
| "sup"
| "nsub"
| "sube"
| "supe"
| "oplus"
| "otimes"
| "perp"
| "sdot"
| "lceil"
| "rceil"
| "lfloor"
| "rfloor"
| "lang"
| "rang"
| "loz"
| "spades"
| "clubs"
| "hearts"
| "diams"
| "quot"
| "amp"
| "lt"
| "gt"
)
}
fn jsx_text_to_str_with_entity_mask(t: &str, entity_mask: &[bool]) -> Atom {
let chars: Vec<char> = t.chars().collect();
let has_line_terminator = chars.iter().any(|&c| is_line_terminator(c));
if !t.is_empty() && !has_line_terminator {
return t.into();
}
let mut acc: Option<String> = None;
let mut only_line: Option<String> = None;
let mut line_start: Option<usize> = Some(0);
let mut line_end: Option<usize> = None;
let mut is_first_line = true;
for (char_idx, c) in chars.iter().enumerate() {
let is_from_entity = *entity_mask.get(char_idx).unwrap_or(&false);
if is_line_terminator(*c) {
if let (Some(start), Some(end)) = (line_start, line_end) {
let line_text =
extract_line_content(&chars, start, end, entity_mask, !is_first_line, true);
add_line_of_jsx_text_owned(line_text, &mut acc, &mut only_line);
}
is_first_line = false;
line_start = None;
line_end = None;
} else if !is_white_space_single_line(*c) || is_from_entity {
line_end = Some(char_idx + 1);
if line_start.is_none() {
line_start = Some(char_idx);
}
}
}
if let Some(start) = line_start {
let line_text = extract_line_content(
&chars,
start,
chars.len(),
entity_mask,
!is_first_line,
false,
);
add_line_of_jsx_text_owned(line_text, &mut acc, &mut only_line);
}
if let Some(acc) = acc {
acc.into()
} else if let Some(only_line) = only_line {
only_line.into()
} else {
"".into()
}
}
fn extract_line_content(
chars: &[char],
start: usize,
end: usize,
entity_mask: &[bool],
trim_leading: bool,
trim_trailing: bool,
) -> String {
let mut actual_start = start;
if trim_leading {
while actual_start < end {
let c = chars[actual_start];
let is_from_entity = *entity_mask.get(actual_start).unwrap_or(&false);
if !is_white_space_single_line(c) || is_from_entity {
break;
}
actual_start += 1;
}
}
let mut actual_end = end;
if trim_trailing {
while actual_end > actual_start {
let c = chars[actual_end - 1];
let is_from_entity = *entity_mask.get(actual_end - 1).unwrap_or(&false);
if !is_white_space_single_line(c) || is_from_entity {
break;
}
actual_end -= 1;
}
}
chars[actual_start..actual_end].iter().collect()
}
fn add_line_of_jsx_text_owned(
line: String,
acc: &mut Option<String>,
only_line: &mut Option<String>,
) {
if line.is_empty() {
return;
}
if let Some(buffer) = acc.as_mut() {
buffer.push(' ');
buffer.push_str(&line);
} else if let Some(only_line_content) = only_line.take() {
let mut buffer = String::with_capacity(line.len() * 2);
buffer.push_str(&only_line_content);
buffer.push(' ');
buffer.push_str(&line);
*acc = Some(buffer);
} else {
*only_line = Some(line);
}
}
#[allow(dead_code)]
#[inline]
fn jsx_text_to_str<'a, T>(t: &'a T) -> Wtf8Atom
where
&'a T: Into<&'a Wtf8>,
T: ?Sized,
{
let t = t.into();
if let Some(s) = t.as_str() {
return jsx_text_to_str_impl(s).into();
}
jsx_text_to_str_wtf8_impl(t)
}
fn jsx_text_to_str_wtf8_impl(t: &Wtf8) -> Wtf8Atom {
let mut acc: Option<Wtf8Buf> = None;
let mut only_line: Option<(usize, usize)> = None; let mut first_non_whitespace: Option<usize> = Some(0);
let mut last_non_whitespace: Option<usize> = None;
let mut byte_pos = 0;
for cp in t.code_points() {
let c = cp.to_char_lossy();
let cp_value = cp.to_u32();
let cp_byte_len = if cp_value < 0x80 {
1
} else if cp_value < 0x800 {
2
} else if cp_value < 0x10000 {
3
} else {
4
};
if is_line_terminator(c) {
if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
add_line_of_jsx_text_wtf8(first, last, t, &mut acc, &mut only_line);
}
first_non_whitespace = None;
} else if !is_white_space_single_line(c) {
last_non_whitespace = Some(byte_pos + cp_byte_len);
if first_non_whitespace.is_none() {
first_non_whitespace.replace(byte_pos);
}
}
byte_pos += cp_byte_len;
}
if let Some(first) = first_non_whitespace {
add_line_of_jsx_text_wtf8(first, t.len(), t, &mut acc, &mut only_line);
}
if let Some(acc) = acc {
acc.into()
} else if let Some((start, end)) = only_line {
t.slice(start, end).into()
} else {
Wtf8Atom::default()
}
}
fn add_line_of_jsx_text_wtf8(
line_start: usize,
line_end: usize,
source: &Wtf8,
acc: &mut Option<Wtf8Buf>,
only_line: &mut Option<(usize, usize)>,
) {
if let Some((only_start, only_end)) = only_line.take() {
let mut buffer = Wtf8Buf::with_capacity(source.len());
buffer.push_wtf8(source.slice(only_start, only_end));
buffer.push_str(" ");
buffer.push_wtf8(source.slice(line_start, line_end));
*acc = Some(buffer);
} else if let Some(ref mut buffer) = acc {
buffer.push_str(" ");
buffer.push_wtf8(source.slice(line_start, line_end));
} else {
*only_line = Some((line_start, line_end));
}
}
#[inline]
fn jsx_text_to_str_impl(t: &str) -> Atom {
if !t.is_empty()
&& !t.chars().any(is_line_terminator)
&& !t.starts_with(is_white_space_single_line)
&& !t.ends_with(is_white_space_single_line)
{
return t.into();
}
let mut acc: Option<String> = None;
let mut only_line: Option<&str> = None;
let mut first_non_whitespace: Option<usize> = Some(0);
let mut last_non_whitespace: Option<usize> = None;
for (index, c) in t.char_indices() {
if is_line_terminator(c) {
if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) {
let line_text = &t[first..last];
add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
}
first_non_whitespace = None;
} else if !is_white_space_single_line(c) {
last_non_whitespace = Some(index + c.len_utf8());
if first_non_whitespace.is_none() {
first_non_whitespace.replace(index);
}
}
}
if let Some(first) = first_non_whitespace {
let line_text = &t[first..];
add_line_of_jsx_text(line_text, &mut acc, &mut only_line);
}
if let Some(acc) = acc {
acc.into()
} else if let Some(only_line) = only_line {
only_line.into()
} else {
"".into()
}
}
fn is_white_space_single_line(c: char) -> bool {
matches!(c, ' ' | '\t')
}
fn add_line_of_jsx_text<'a>(
trimmed_line: &'a str,
acc: &mut Option<String>,
only_line: &mut Option<&'a str>,
) {
if let Some(buffer) = acc.as_mut() {
buffer.push(' ');
} else if let Some(only_line_content) = only_line.take() {
let mut buffer = String::with_capacity(trimmed_line.len() * 2); buffer.push_str(only_line_content);
buffer.push(' ');
*acc = Some(buffer);
}
if let Some(buffer) = acc.as_mut() {
buffer.push_str(trimmed_line);
} else {
*only_line = Some(trimmed_line);
}
}
fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option<Box<Expr>> {
Some(match v {
JSXAttrValue::Str(s) => {
let value = transform_jsx_attr_str(&s.value);
Lit::Str(Str {
span: s.span,
raw: None,
value: value.into(),
})
.into()
}
JSXAttrValue::JSXExprContainer(e) => match e.expr {
JSXExpr::JSXEmptyExpr(_) => None?,
JSXExpr::Expr(e) => e,
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
},
JSXAttrValue::JSXElement(e) => e.into(),
JSXAttrValue::JSXFragment(f) => f.into(),
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
})
}
fn transform_jsx_attr_str(v: &Wtf8) -> Wtf8Buf {
v.to_owned()
}