Support jsx pragma (#1015)

swc_ecma_transforms
 - Support jsx pragma (#107, #838)
This commit is contained in:
강동윤 2020-08-30 20:32:45 +09:00 committed by GitHub
parent a60b0a9cd4
commit 578d64a398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 104 additions and 29 deletions

View File

@ -32,14 +32,14 @@ install:
- rm -rf ~/.nvm
- git clone https://github.com/creationix/nvm.git ~/.nvm
- source ~/.nvm/nvm.sh
- nvm install 8.15.0
- nvm use 8.15.0
- nvm install 12.18.3
- nvm use 12.18.3
- npm install
- npm install browserslist regenerator
- npm install -g jest
script:
# - RUST_BACKTRACE=0 cargo check --color always --all --all-targets
# - RUST_BACKTRACE=0 cargo check --color always --all --all-targets
- true
before_deploy:

View File

@ -1,6 +1,6 @@
[package]
name = "swc_bundler"
version = "0.5.0"
version = "0.6.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
@ -20,7 +20,7 @@ swc_common = { version = "0.10.0", path = "../common" }
swc_ecma_ast = { version = "0.30.0", path = "../ecmascript/ast" }
swc_ecma_codegen = { version = "0.34.0", path = "../ecmascript/codegen" }
swc_ecma_parser = { version = "0.36.0", path = "../ecmascript/parser" }
swc_ecma_transforms = { version = "0.22.0", path = "../ecmascript/transforms" }
swc_ecma_transforms = { version = "0.23.0", path = "../ecmascript/transforms" }
swc_ecma_utils = { version = "0.20.0", path = "../ecmascript/utils" }
swc_ecma_visit = { version = "0.16.0", path = "../ecmascript/visit" }
anyhow = "1"

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecmascript"
version = "0.6.3"
version = "0.7.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
@ -25,7 +25,7 @@ swc_ecma_codegen = { version = "0.34.0", path ="./codegen", optional = true }
swc_ecma_dep_graph = { version = "0.1.0", path = "./dep-graph", optional = true }
swc_ecma_parser = { version = "0.36.0", path ="./parser", optional = true }
swc_ecma_utils = { version = "0.20.0", path ="./utils", optional = true }
swc_ecma_transforms = { version = "0.22.0", path ="./transforms", optional = true }
swc_ecma_transforms = { version = "0.23.0", path ="./transforms", optional = true }
swc_ecma_visit = { version = "0.16.0", path ="./visit", optional = true }
[dev-dependencies]

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms"
repository = "https://github.com/swc-project/swc.git"
version = "0.22.2"
version = "0.23.0"
[features]
const-modules = ["dashmap"]

View File

@ -6,7 +6,7 @@ pub use self::{
jsx_self::jsx_self,
jsx_src::jsx_src,
};
use swc_common::{chain, sync::Lrc, SourceMap};
use swc_common::{chain, comments::Comments, sync::Lrc, SourceMap};
use swc_ecma_visit::Fold;
mod display_name;
@ -17,11 +17,14 @@ mod jsx_src;
/// `@babel/preset-react`
///
/// Preset for all React plugins.
pub fn react(cm: Lrc<SourceMap>, options: Options) -> impl Fold {
pub fn react<C>(cm: Lrc<SourceMap>, comments: Option<C>, options: Options) -> impl Fold
where
C: Comments,
{
let Options { development, .. } = options;
chain!(
jsx(cm.clone(), options),
jsx(cm.clone(), comments, options),
display_name(),
jsx_src(development, cm),
jsx_self(development)

View File

@ -5,7 +5,12 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{iter, mem};
use swc_atoms::{js_word, JsWord};
use swc_common::{iter::IdentifyLast, sync::Lrc, FileName, SourceMap, Spanned, DUMMY_SP};
use swc_common::{
comments::{CommentKind, Comments},
iter::IdentifyLast,
sync::Lrc,
FileName, SourceMap, Spanned, DUMMY_SP,
};
use swc_ecma_ast::*;
use swc_ecma_parser::{Parser, StringInput, Syntax};
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
@ -86,9 +91,14 @@ fn parse_option(cm: &SourceMap, name: &str, src: String) -> Box<Expr> {
/// `@babel/plugin-transform-react-jsx`
///
/// Turn JSX into React function calls
pub fn jsx(cm: Lrc<SourceMap>, options: Options) -> impl Fold {
pub fn jsx<C>(cm: Lrc<SourceMap>, comments: Option<C>, options: Options) -> impl Fold
where
C: Comments,
{
Jsx {
cm: cm.clone(),
pragma: ExprOrSuper::Expr(parse_option(&cm, "pragma", options.pragma)),
comments,
pragma_frag: ExprOrSpread {
spread: None,
expr: parse_option(&cm, "pragmaFrag", options.pragma_frag),
@ -98,14 +108,22 @@ pub fn jsx(cm: Lrc<SourceMap>, options: Options) -> impl Fold {
}
}
struct Jsx {
struct Jsx<C>
where
C: Comments,
{
cm: Lrc<SourceMap>,
pragma: ExprOrSuper,
comments: Option<C>,
pragma_frag: ExprOrSpread,
use_builtins: bool,
throw_if_namespace: bool,
}
impl Jsx {
impl<C> Jsx<C>
where
C: Comments,
{
fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
let span = el.span();
@ -252,9 +270,59 @@ impl Jsx {
}
}
impl Fold for Jsx {
impl<C> Fold for Jsx<C>
where
C: Comments,
{
noop_fold_type!();
fn fold_module(&mut self, module: Module) -> Module {
let leading = if let Some(comments) = &self.comments {
let leading = comments.take_leading(module.span.lo);
if let Some(leading) = &leading {
for leading in &**leading {
if leading.kind != CommentKind::Block {
continue;
}
for line in leading.text.lines() {
if !line.trim().starts_with("* @jsx") {
continue;
}
if line.trim().starts_with("* @jsxFrag") {
let src = line.replace("* @jsxFrag", "").trim().to_string();
self.pragma_frag = ExprOrSpread {
expr: parse_option(&self.cm, "module-jsx-pragma-frag", src),
spread: None,
};
} else {
let src = line.replace("* @jsx", "").trim().to_string();
self.pragma =
ExprOrSuper::Expr(parse_option(&self.cm, "module-jsx-pragma", src));
}
}
}
}
leading
} else {
None
};
let module = module.fold_children_with(self);
if let Some(leading) = leading {
if let Some(comments) = &self.comments {
comments.add_leading_comments(module.span.lo, leading);
}
}
module
}
fn fold_expr(&mut self, expr: Expr) -> Expr {
let mut expr = expr.fold_children_with(self);
@ -290,7 +358,10 @@ impl Fold for Jsx {
}
}
impl Jsx {
impl<C> Jsx<C>
where
C: Comments,
{
fn jsx_name(&self, name: JSXElementName) -> Box<Expr> {
let span = name.span();
match name {

View File

@ -12,7 +12,7 @@ use swc_common::{chain, Mark};
fn tr(t: &mut Tester, options: Options) -> impl Fold {
chain!(
jsx(t.cm.clone(), options),
jsx(t.cm.clone(), Some(t.comments.clone()), options),
display_name(),
classes(),
arrow(),
@ -288,7 +288,6 @@ test!(
);
test!(
ignore,
::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
jsx: true,
..Default::default()
@ -313,7 +312,6 @@ var profile = dom("div", null, dom("img", {
);
test!(
ignore,
::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
jsx: true,
..Default::default()
@ -564,7 +562,6 @@ React.createElement("div", null, React.createElement(
);
test!(
ignore,
::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
jsx: true,
..Default::default()
@ -583,7 +580,6 @@ dom("div", null, "no fragment is used");
);
test!(
ignore,
::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
jsx: true,
..Default::default()
@ -915,7 +911,6 @@ test!(
test!(
// Comments are currently stripped out
ignore,
::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
jsx: true,
..Default::default()

View File

@ -20,7 +20,7 @@ use tempfile::tempdir_in;
pub(crate) struct Tester<'a> {
pub cm: Lrc<SourceMap>,
pub handler: &'a Handler,
pub comments: SingleThreadedComments,
pub comments: Lrc<SingleThreadedComments>,
}
impl<'a> Tester<'a> {
@ -101,7 +101,7 @@ impl<'a> Tester<'a> {
.new_source_file(FileName::Real(name.into()), src.into());
let module = {
let mut p = Parser::new(syntax, StringInput::from(&*fm), None);
let mut p = Parser::new(syntax, StringInput::from(&*fm), Some(&self.comments));
let res = p
.parse_module()
.map_err(|e| e.into_diagnostic(&self.handler).emit());
@ -174,7 +174,7 @@ pub(crate) fn test_transform<F, P>(
expected: &str,
ok_if_code_eq: bool,
) where
F: FnOnce(&mut Tester<'_>) -> P,
F: FnOnce(&mut Tester) -> P,
P: Fold,
{
crate::tests::Tester::run(|tester| {

View File

@ -49,7 +49,7 @@ macro_rules! validate {
pub struct Tester<'a> {
pub cm: Lrc<SourceMap>,
pub handler: &'a Handler,
pub comments: SingleThreadedComments,
pub comments: Lrc<SingleThreadedComments>,
}
impl<'a> Tester<'a> {

View File

@ -2195,7 +2195,10 @@ test!(
jsx: true,
..Default::default()
}),
|t| chain!(tr(), jsx(t.cm.clone(), Default::default())),
|t| chain!(
tr(),
jsx(t.cm.clone(), Some(t.comments.clone()), Default::default())
),
regression_2775,
r#"
import React, {Component} from 'react';

View File

@ -223,7 +223,10 @@ impl Options {
let pass = chain!(
// handle jsx
Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()),
Optional::new(
react::react(cm.clone(), comments, transform.react),
syntax.jsx()
),
// Decorators may use type information
Optional::new(
decorators(decorators::Config {