Skip to content

Commit 7ffb4c0

Browse files
authored
Extension for jest (#1081)
1 parent cfca078 commit 7ffb4c0

File tree

8 files changed

+257
-23
lines changed

8 files changed

+257
-23
lines changed

Cargo.toml

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,44 @@
22
members = ["ecmascript", "ecmascript/jsdoc", "native", "spack", "wasm"]
33

44
[package]
5-
name = "swc"
6-
version = "0.1.0"
75
authors = ["강동윤 <[email protected]>"]
8-
license = "Apache-2.0/MIT"
9-
repository = "https://github.com/swc-project/swc.git"
10-
documentation = "https://swc-project.github.io/rustdoc/swc/"
116
description = "Speedy web compiler"
7+
documentation = "https://swc-project.github.io/rustdoc/swc/"
128
edition = "2018"
9+
license = "Apache-2.0/MIT"
10+
name = "swc"
11+
repository = "https://github.com/swc-project/swc.git"
12+
version = "0.0.0"
1313

1414
[lib]
1515
name = "swc"
1616

1717
[dependencies]
18-
swc_atoms = { path ="./atoms" }
19-
swc_common = { path ="./common", features = ["sourcemap", "concurrent"] }
20-
swc_ecma_ast = { path ="./ecmascript/ast" }
21-
swc_ecma_codegen = { path ="./ecmascript/codegen" }
22-
swc_ecma_parser = { path ="./ecmascript/parser" }
23-
swc_ecma_preset_env = { path ="./ecmascript/preset_env" }
24-
swc_ecma_transforms = { path ="./ecmascript/transforms", features = ["const-modules", "react"] }
25-
swc_ecma_visit = { path ="./ecmascript/visit" }
26-
swc_visit = { path ="./visit" }
2718
anyhow = "1"
28-
log = { version = "0.4", features = ["release_max_level_info"] }
29-
serde = { version = "1", features = ["derive"] }
30-
serde_json = "1"
19+
base64 = "0.12.0"
20+
dashmap = "3"
21+
either = "1"
22+
log = {version = "0.4", features = ["release_max_level_info"]}
3123
once_cell = "1"
3224
regex = "1"
33-
either = "1"
34-
dashmap = "3"
25+
serde = {version = "1", features = ["derive"]}
26+
serde_json = "1"
3527
sourcemap = "6"
36-
base64 = "0.12.0"
28+
swc_atoms = {path = "./atoms"}
29+
swc_common = {path = "./common", features = ["sourcemap", "concurrent"]}
30+
swc_ecma_ast = {path = "./ecmascript/ast"}
31+
swc_ecma_codegen = {path = "./ecmascript/codegen"}
32+
swc_ecma_ext_transforms = {path = "./ecmascript/ext-transforms"}
33+
swc_ecma_parser = {path = "./ecmascript/parser"}
34+
swc_ecma_preset_env = {path = "./ecmascript/preset_env"}
35+
swc_ecma_transforms = {path = "./ecmascript/transforms", features = ["const-modules", "react"]}
36+
swc_ecma_visit = {path = "./ecmascript/visit"}
37+
swc_visit = {path = "./visit"}
3738

3839
[dev-dependencies]
39-
testing = { path = "./testing" }
40-
walkdir = "2"
4140
rayon = "1"
41+
testing = {path = "./testing"}
42+
walkdir = "2"
4243

4344
[[example]]
4445
name = "usage"
@@ -51,4 +52,4 @@ lto = true
5152

5253
[profile.bench]
5354
codegen-units = 1
54-
debug = true
55+
debug = true
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
authors = ["강동윤 <[email protected]>"]
3+
description = "Extensions for @swc/core (nodejs)"
4+
edition = "2018"
5+
name = "swc_ecma_ext_transforms"
6+
publish = false
7+
version = "0.0.0"
8+
9+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10+
11+
[dependencies]
12+
phf = {version = "0.8.0", features = ["macros"]}
13+
swc_atoms = {path = "../../atoms"}
14+
swc_common = {path = "../../common"}
15+
swc_ecma_ast = {path = "../ast"}
16+
swc_ecma_parser = {path = "../parser"}
17+
swc_ecma_utils = {path = "../utils"}
18+
swc_ecma_visit = {path = "../visit"}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use crate::util::MapWithMut;
2+
use phf::phf_set;
3+
use swc_ecma_ast::*;
4+
use swc_ecma_utils::{prepend_stmts, StmtLike};
5+
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
6+
7+
static HOIST_METHODS: phf::Set<&str> = phf_set![
8+
"mock",
9+
"unmock",
10+
"enableAutomock",
11+
"disableAutomock",
12+
"deepUnmock"
13+
];
14+
15+
pub fn jest() -> impl Fold {
16+
as_folder(Jest)
17+
}
18+
19+
struct Jest;
20+
21+
impl Jest {
22+
fn visit_mut_stmt_like<T>(&mut self, orig: &mut Vec<T>)
23+
where
24+
T: StmtLike + VisitMutWith<Self>,
25+
{
26+
for item in &mut *orig {
27+
item.visit_mut_with(self);
28+
}
29+
30+
let items = orig.take();
31+
32+
let mut new = Vec::with_capacity(items.len());
33+
let mut hoisted = Vec::with_capacity(8);
34+
items.into_iter().for_each(|item| {
35+
match item.try_into_stmt() {
36+
Ok(stmt) => match &stmt {
37+
Stmt::Expr(ExprStmt { expr, .. }) => match &**expr {
38+
Expr::Call(CallExpr {
39+
callee: ExprOrSuper::Expr(callee),
40+
..
41+
}) => match &**callee {
42+
Expr::Member(
43+
callee
44+
@
45+
MemberExpr {
46+
computed: false, ..
47+
},
48+
) => match &callee.obj {
49+
ExprOrSuper::Super(_) => {}
50+
ExprOrSuper::Expr(callee_obj) => match &**callee_obj {
51+
Expr::Ident(i) if i.sym == *"jest" => match &*callee.prop {
52+
Expr::Ident(prop) if HOIST_METHODS.contains(&*prop.sym) => {
53+
hoisted.push(T::from_stmt(stmt));
54+
return;
55+
}
56+
_ => new.push(T::from_stmt(stmt)),
57+
},
58+
_ => new.push(T::from_stmt(stmt)),
59+
},
60+
},
61+
_ => new.push(T::from_stmt(stmt)),
62+
},
63+
_ => new.push(T::from_stmt(stmt)),
64+
},
65+
66+
_ => new.push(T::from_stmt(stmt)),
67+
},
68+
Err(node) => new.push(node),
69+
};
70+
});
71+
72+
prepend_stmts(&mut new, hoisted.into_iter());
73+
74+
*orig = new;
75+
}
76+
}
77+
78+
impl VisitMut for Jest {
79+
noop_visit_mut_type!();
80+
81+
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
82+
self.visit_mut_stmt_like(stmts)
83+
}
84+
85+
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
86+
self.visit_mut_stmt_like(items)
87+
}
88+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod jest;
2+
mod util;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use std::mem::replace;
2+
use swc_atoms::js_word;
3+
use swc_common::DUMMY_SP;
4+
use swc_ecma_ast::*;
5+
/// Helper for migration from [Fold] to [VisitMut]
6+
pub(crate) trait MapWithMut: Sized {
7+
fn dummy() -> Self;
8+
9+
fn take(&mut self) -> Self {
10+
replace(self, Self::dummy())
11+
}
12+
13+
#[inline]
14+
fn map_with_mut<F>(&mut self, op: F)
15+
where
16+
F: FnOnce(Self) -> Self,
17+
{
18+
let dummy = Self::dummy();
19+
let v = replace(self, dummy);
20+
let v = op(v);
21+
let _dummy = replace(self, v);
22+
}
23+
}
24+
25+
impl MapWithMut for ModuleItem {
26+
#[inline(always)]
27+
fn dummy() -> Self {
28+
ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }))
29+
}
30+
}
31+
32+
impl MapWithMut for Stmt {
33+
#[inline(always)]
34+
fn dummy() -> Self {
35+
Stmt::Empty(EmptyStmt { span: DUMMY_SP })
36+
}
37+
}
38+
39+
impl MapWithMut for Expr {
40+
#[inline(always)]
41+
fn dummy() -> Self {
42+
Expr::Invalid(Invalid { span: DUMMY_SP })
43+
}
44+
}
45+
46+
impl MapWithMut for Pat {
47+
#[inline(always)]
48+
fn dummy() -> Self {
49+
Pat::Invalid(Invalid { span: DUMMY_SP })
50+
}
51+
}
52+
53+
impl<T> MapWithMut for Option<T> {
54+
#[inline(always)]
55+
fn dummy() -> Self {
56+
None
57+
}
58+
}
59+
60+
impl<T> MapWithMut for Vec<T> {
61+
#[inline(always)]
62+
fn dummy() -> Self {
63+
Vec::new()
64+
}
65+
}
66+
67+
impl<T> MapWithMut for Box<T>
68+
where
69+
T: MapWithMut,
70+
{
71+
#[inline(always)]
72+
fn dummy() -> Self {
73+
Box::new(T::dummy())
74+
}
75+
}
76+
77+
impl MapWithMut for Ident {
78+
fn dummy() -> Self {
79+
Ident::new(js_word!(""), DUMMY_SP)
80+
}
81+
}
82+
83+
impl MapWithMut for ObjectPatProp {
84+
fn dummy() -> Self {
85+
ObjectPatProp::Assign(AssignPatProp {
86+
span: DUMMY_SP,
87+
key: Ident::dummy(),
88+
value: None,
89+
})
90+
}
91+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const swc = require("../../../");
2+
3+
it("should hoist methods", () => {
4+
const src = 'console.log("Hello"); jest.mock(); console.log("World")';
5+
6+
expect(
7+
swc.transformSync(src, {
8+
jsc: {
9+
transform: {
10+
hidden: {
11+
jest: true
12+
}
13+
}
14+
}
15+
})
16+
.code.trim()
17+
).toBe(`jest.mock();
18+
console.log(\"Hello\");
19+
console.log(\"World\");`);
20+
});

spack/src/loaders/swc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl Load for SwcLoader {
8282
optimizer: None,
8383
legacy_decorator: c.legacy_decorator,
8484
decorator_metadata: c.decorator_metadata,
85+
hidden: Default::default(),
8586
})
8687
} else {
8788
None

src/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use swc_atoms::JsWord;
1616
pub use swc_common::chain;
1717
use swc_common::{comments::Comments, errors::Handler, FileName, Mark, SourceMap};
1818
use swc_ecma_ast::{Expr, ExprStmt, ModuleItem, Stmt};
19+
use swc_ecma_ext_transforms::jest;
1920
pub use swc_ecma_parser::JscTarget;
2021
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
2122
use swc_ecma_transforms::{
@@ -255,6 +256,8 @@ impl Options {
255256
.preset_env(config.env)
256257
.finalize(syntax, config.module, comments);
257258

259+
let pass = chain!(pass, Optional::new(jest::jest(), transform.hidden.jest));
260+
258261
BuiltConfig {
259262
minify: config.minify.unwrap_or(false),
260263
pass,
@@ -577,6 +580,16 @@ pub struct TransformConfig {
577580

578581
#[serde(default)]
579582
pub decorator_metadata: bool,
583+
584+
#[serde(default)]
585+
pub hidden: HiddenTransformConfig,
586+
}
587+
588+
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
589+
#[serde(deny_unknown_fields, rename_all = "camelCase")]
590+
pub struct HiddenTransformConfig {
591+
#[serde(default)]
592+
pub jest: bool,
580593
}
581594

582595
#[derive(Debug, Default, Clone, Serialize, Deserialize)]

0 commit comments

Comments
 (0)