diff --git a/ecmascript/parser/src/lexer/jsx.rs b/ecmascript/parser/src/lexer/jsx.rs index c85b2d0096d..ca4ed936966 100644 --- a/ecmascript/parser/src/lexer/jsx.rs +++ b/ecmascript/parser/src/lexer/jsx.rs @@ -205,7 +205,15 @@ impl<'a, I: Input> Lexer<'a, I> { assert!(self.input.cur().unwrap().is_ident_start()); let cur_pos = self.input.cur_pos(); - let slice = self.input.uncons_while(|c| c.is_ident_part() || c == '-'); + let mut first = true; + let slice = self.input.uncons_while(|c| { + if first { + first = false; + c.is_ident_start() + } else { + c.is_ident_part() || c == '-' + } + }); Ok(Token::JSXName { name: slice.into() }) } diff --git a/ecmascript/transforms/src/const_modules.rs b/ecmascript/transforms/src/const_modules.rs index d62464363ed..29a7645626e 100644 --- a/ecmascript/transforms/src/const_modules.rs +++ b/ecmascript/transforms/src/const_modules.rs @@ -1,6 +1,9 @@ use crate::{ pass::Pass, - util::{drop_span, CM, SESSION}, + util::{ + drop_span, + options::{CM, SESSION}, + }, }; use ast::*; use chashmap::CHashMap; diff --git a/ecmascript/transforms/src/helpers/mod.rs b/ecmascript/transforms/src/helpers/mod.rs index 3e1bcfb52d4..2c54598b07f 100644 --- a/ecmascript/transforms/src/helpers/mod.rs +++ b/ecmascript/transforms/src/helpers/mod.rs @@ -1,4 +1,7 @@ -use crate::util::{prepend_stmts, DropSpan, CM, SESSION}; +use crate::util::{ + options::{CM, SESSION}, + prepend_stmts, DropSpan, +}; use ast::*; use scoped_tls::scoped_thread_local; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/ecmascript/transforms/src/react/jsx/mod.rs b/ecmascript/transforms/src/react/jsx/mod.rs index 93c2561b70e..98b3fdc963b 100644 --- a/ecmascript/transforms/src/react/jsx/mod.rs +++ b/ecmascript/transforms/src/react/jsx/mod.rs @@ -1,6 +1,10 @@ use crate::{ pass::Pass, - util::{drop_span, ExprFactory, CM, SESSION}, + util::{ + drop_span, + options::{CM, SESSION}, + ExprFactory, HANDLER, + }, }; use ast::*; use chashmap::CHashMap; @@ -100,6 +104,7 @@ pub fn jsx(options: Options) -> impl Pass { expr: parse_option("pragmaFrag", options.pragma_frag), }, use_builtins: options.use_builtins, + throw_if_namespace: options.throw_if_namespace, } } @@ -107,6 +112,7 @@ struct Jsx { pragma: ExprOrSuper, pragma_frag: ExprOrSpread, use_builtins: bool, + throw_if_namespace: bool, } impl Jsx { @@ -133,7 +139,7 @@ impl Jsx { fn jsx_elem_to_expr(&mut self, el: JSXElement) -> Expr { let span = el.span(); - let name = jsx_name(el.opening.name); + let name = self.jsx_name(el.opening.name); Expr::Call(CallExpr { span, @@ -278,60 +284,75 @@ impl Fold for Jsx { } } -fn jsx_name(name: JSXElementName) -> Box { - let span = name.span(); - match name { - JSXElementName::Ident(i) => { - // If it starts with lowercase digit - let c = i.sym.chars().next().unwrap(); +impl Jsx { + fn jsx_name(&self, name: JSXElementName) -> Box { + let span = name.span(); + match name { + JSXElementName::Ident(i) => { + // If it starts with lowercase digit + let c = i.sym.chars().next().unwrap(); - if i.sym == js_word!("this") { - return box Expr::This(ThisExpr { span }); - } + if i.sym == js_word!("this") { + return box Expr::This(ThisExpr { span }); + } - if c.is_ascii_lowercase() { - box Expr::Lit(Lit::Str(Str { - span, - value: i.sym, - has_escape: false, - })) - } else { - box Expr::Ident(i) - } - } - JSXElementName::JSXNamespacedName(JSXNamespacedName { ref ns, ref name }) => { - box Expr::Lit(Lit::Str(Str { - span, - value: format!("{}:{}", ns.sym, name.sym).into(), - has_escape: false, - })) - } - JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop }) => { - fn convert_obj(obj: JSXObject) -> ExprOrSuper { - let span = obj.span(); - - match obj { - JSXObject::Ident(i) => { - if i.sym == js_word!("this") { - return ExprOrSuper::Expr(box Expr::This(ThisExpr { span })); - } - i.as_obj() - } - JSXObject::JSXMemberExpr(box JSXMemberExpr { obj, prop }) => MemberExpr { + if c.is_ascii_lowercase() { + box Expr::Lit(Lit::Str(Str { span, - obj: convert_obj(obj), - prop: box Expr::Ident(prop), - computed: false, - } - .as_obj(), + value: i.sym, + has_escape: false, + })) + } else { + box Expr::Ident(i) } } - box Expr::Member(MemberExpr { - span, - obj: convert_obj(obj), - prop: box Expr::Ident(prop), - computed: false, - }) + JSXElementName::JSXNamespacedName(JSXNamespacedName { ref ns, ref name }) => { + if self.throw_if_namespace { + 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() + }); + } + box Expr::Lit(Lit::Str(Str { + span, + value: format!("{}:{}", ns.sym, name.sym).into(), + has_escape: false, + })) + } + JSXElementName::JSXMemberExpr(JSXMemberExpr { obj, prop }) => { + fn convert_obj(obj: JSXObject) -> ExprOrSuper { + let span = obj.span(); + + match obj { + JSXObject::Ident(i) => { + if i.sym == js_word!("this") { + return ExprOrSuper::Expr(box Expr::This(ThisExpr { span })); + } + i.as_obj() + } + JSXObject::JSXMemberExpr(box JSXMemberExpr { obj, prop }) => MemberExpr { + span, + obj: convert_obj(obj), + prop: box Expr::Ident(prop), + computed: false, + } + .as_obj(), + } + } + box Expr::Member(MemberExpr { + span, + obj: convert_obj(obj), + prop: box Expr::Ident(prop), + computed: false, + }) + } } } } diff --git a/ecmascript/transforms/src/react/jsx/tests.rs b/ecmascript/transforms/src/react/jsx/tests.rs index 0dda62c00b3..b1771ea3d99 100644 --- a/ecmascript/transforms/src/react/jsx/tests.rs +++ b/ecmascript/transforms/src/react/jsx/tests.rs @@ -645,7 +645,7 @@ React.createElement("div", { id: "w\xF4w" }); React.createElement("div", { - id: "\\w" + id: "w" }); React.createElement("div", { id: "w < w" diff --git a/ecmascript/transforms/src/tests.rs b/ecmascript/transforms/src/tests.rs index e3e08a824f0..8f546718868 100644 --- a/ecmascript/transforms/src/tests.rs +++ b/ecmascript/transforms/src/tests.rs @@ -29,7 +29,9 @@ impl<'a> Tester<'a> { F: FnOnce(&mut Tester) -> Result<(), ()>, { let out = ::testing::run_test(false, |cm, handler| { - HELPERS.set(&Default::default(), || op(&mut Tester { cm, handler })) + crate::util::HANDLER.set(handler, || { + HELPERS.set(&Default::default(), || op(&mut Tester { cm, handler })) + }) }); match out { diff --git a/ecmascript/transforms/src/util/mod.rs b/ecmascript/transforms/src/util/mod.rs index 72cdad6cc1d..a3904311248 100644 --- a/ecmascript/transforms/src/util/mod.rs +++ b/ecmascript/transforms/src/util/mod.rs @@ -10,23 +10,22 @@ pub use self::{ Purity::{MayBeImpure, Pure}, }; use ast::*; +use scoped_tls::scoped_thread_local; use std::{ borrow::Cow, f64::{INFINITY, NAN}, num::FpCategory, ops::Add, - sync::Arc, }; use swc_atoms::JsWord; use swc_common::{ - errors::{ColorConfig, Handler}, - FilePathMapping, Fold, FoldWith, Mark, SourceMap, Span, Spanned, Visit, VisitWith, DUMMY_SP, + errors::Handler, Fold, FoldWith, Mark, Span, Spanned, Visit, VisitWith, DUMMY_SP, }; -use swc_ecma_parser::Session; use unicode_xid::UnicodeXID; pub(crate) mod constructor; mod factory; +pub(crate) mod options; mod value; pub(crate) mod var; @@ -1015,10 +1014,4 @@ impl<'a> UsageFinder<'a> { } } -lazy_static! { - pub(crate) static ref CM: Arc = - { Arc::new(SourceMap::new(FilePathMapping::empty())) }; - pub(crate) static ref HANDLER: Handler = - { Handler::with_tty_emitter(ColorConfig::Always, false, true, Some(CM.clone())) }; - pub(crate) static ref SESSION: Session<'static> = { Session { handler: &*HANDLER } }; -} +scoped_thread_local!(pub static HANDLER: Handler); diff --git a/ecmascript/transforms/src/util/options.rs b/ecmascript/transforms/src/util/options.rs new file mode 100644 index 00000000000..b87be9b30d9 --- /dev/null +++ b/ecmascript/transforms/src/util/options.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; +use swc_common::{ + errors::{ColorConfig, Handler}, + FilePathMapping, SourceMap, +}; +use swc_ecma_parser::Session; + +lazy_static! { + pub(crate) static ref CM: Arc = + { Arc::new(SourceMap::new(FilePathMapping::empty())) }; + pub(crate) static ref HANDLER: Handler = + { Handler::with_tty_emitter(ColorConfig::Always, false, true, Some(CM.clone())) }; + pub(crate) static ref SESSION: Session<'static> = { Session { handler: &*HANDLER } }; +} diff --git a/testing/src/lib.rs b/testing/src/lib.rs index 2eddb4dbc54..0564560a5c8 100644 --- a/testing/src/lib.rs +++ b/testing/src/lib.rs @@ -42,6 +42,20 @@ where } } +pub fn run_test2(treat_err_as_bug: bool, op: F) -> Result +where + F: FnOnce(Arc, Handler) -> Result, +{ + let cm = Arc::new(SourceMap::new(FilePathMapping::empty())); + let (handler, errors) = self::errors::new_handler(cm.clone(), treat_err_as_bug); + let result = swc_common::GLOBALS.set(&swc_common::Globals::new(), || op(cm, handler)); + + match result { + Ok(res) => Ok(res), + Err(()) => Err(errors.into()), + } +} + /// Remove all span from `t`. pub fn drop_span(t: T) -> T where