@ohos-ports/jest_workaround 0.79.19-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) magic-akari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # [SWC plugin] workaround for jest
2
+
3
+ [![Crates.io](https://img.shields.io/crates/v/jest_workaround)](https://crates.io/crates/jest_workaround)
4
+ [![npm](https://img.shields.io/npm/v/jest_workaround)](https://www.npmjs.com/package/jest_workaround)
5
+
6
+ [![Test](https://github.com/magic-akari/jest_workaround/actions/workflows/test.yml/badge.svg)](https://github.com/magic-akari/jest_workaround/actions/workflows/test.yml)
7
+ [![with @swc/core@latest](https://github.com/magic-akari/jest_workaround/actions/workflows/cron.yml/badge.svg)](https://github.com/magic-akari/jest_workaround/actions/workflows/cron.yml)
8
+
9
+ This is a SWC plugin to handle jest compatibility issues.
10
+
11
+ This SWC plugin should be used with `@swc/jest`.
12
+
13
+ ## plugin version
14
+
15
+ https://swc.rs/docs/plugin/selecting-swc-core
16
+
17
+ ## usage
18
+
19
+ install
20
+
21
+ ```bash
22
+ npm i -D jest @swc/core @swc/jest jest_workaround
23
+ ```
24
+
25
+ ```js
26
+ // jest.config.js
27
+ const fs = require("node:fs");
28
+
29
+ const swcrc = JSON.parse(fs.readFileSync(".swcrc", "utf8"));
30
+
31
+ // If you have other plugins, change this line.
32
+ ((swcrc.jsc ??= {}).experimental ??= {}).plugins = [["jest_workaround", {}]];
33
+
34
+ module.exports = {
35
+ transform: {
36
+ "^.+\\.(t|j)sx?$": ["@swc/jest", swcrc],
37
+ },
38
+ };
39
+ ```
40
+
41
+ Make sure that `module.type` is `commonjs` in your `.swcrc` since this plugin
42
+ does not touch non-workaround parts, such as import statements.
43
+
44
+ ## FAQ
45
+
46
+ #### 1. When do I need this?
47
+
48
+ If you're using the swc compiler to transform your code to comply with the ESM
49
+ specification, but you're also using Jest to test it in a CJS environment, you
50
+ may encounter issues due to the immutable issue of `exports`.
51
+
52
+ This plugin can help by transforming the `export` statements into mutable
53
+ `exports`.
54
+
55
+ #### 2. Do I have a better choice?
56
+
57
+ You may have other options depending on your specific needs:
58
+
59
+ - If you're able to run Jest in an ESM environment, you can use swc to transpile
60
+ TypeScript/JSX syntax or downgrade JavaScript syntax without module
61
+ conversion. Simply set the value of `module.type` to `es6` to achieve this.
62
+
63
+ - It's possible that some issues related to running Jest in an ESM environment
64
+ will be resolved over time. Keep an eye on
65
+ [facebook/jest#9430](https://github.com/facebook/jest/issues/9430) for
66
+ updates.
67
+
68
+ - If you don't need the behavior of ESM specifically, you can stick with the CJS
69
+ syntax to get the CJS behavior of `exports`.
70
+
71
+ These options may be worth considering before using this plugin.
72
+
73
+ CJS syntax
74
+
75
+ ```JavaScript
76
+ exports.foo = function foo() {
77
+ return 42;
78
+ };
79
+ ```
80
+
81
+ CTS(CJS in TypeScript) syntax
82
+
83
+ ```TypeScript
84
+ export = {
85
+ foo: function foo() {
86
+ return 42;
87
+ },
88
+ };
89
+ ```
90
+
91
+ Notes:
92
+
93
+ - ESM style export means immutable exports when transformed into CJS
94
+ - ESM style import means hoisted require when transformed into CJS
95
+
96
+ #### 3. After upgrading the plugin version, the changes have not taken effect.
97
+
98
+ This is a known issue. You could remove the Jest cache by running
99
+ [`jest --clearCache`](https://jestjs.io/docs/cli#--clearcache) as a workaround.
Binary file
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@ohos-ports/jest_workaround",
3
+ "version": "0.79.19-beta.0",
4
+ "description": "[SWC plugin] workaround for jest",
5
+ "author": "magic-akari <akari.ccino@gmail.com>",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "swc-plugin",
9
+ "jest"
10
+ ],
11
+ "scripts": {
12
+ "test:debug": "NODE_ENV=development pnpm build:debug && NODE_ENV=development jest",
13
+ "build:debug": "cargo build --target wasm32-wasi && cp -rf target/wasm32-wasi/debug/jest_workaround.wasm ./debug",
14
+ "test": "pnpm build && jest",
15
+ "build": "cargo prepublish --release && cp -rf target/wasm32-wasi/release/jest_workaround.wasm ."
16
+ },
17
+ "packageManager": "pnpm@8.6.8",
18
+ "main": "jest_workaround.wasm",
19
+ "files": [
20
+ "src",
21
+ "jest_workaround.wasm"
22
+ ],
23
+ "homepage": "https://github.com/magic-akari/jest_workaround#readme",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
27
+ "directory": "ports/jest_workaround/0.79.19"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/ohos-ports/ohos-ports/issues"
31
+ },
32
+ "devDependencies": {
33
+ "@ohos-ports/swc-core": "^1.16.1-beta.1",
34
+ "@swc/jest": "^0.2.26",
35
+ "@types/jest": "^29.5.1",
36
+ "jest": "^29.5.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@ohos-ports/swc-core": "^1.16.1-beta.1",
40
+ "@swc/jest": "^0.2.26"
41
+ }
42
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,282 @@
1
+ mod local_export_strip;
2
+ mod utils;
3
+
4
+ use local_export_strip::LocalExportStrip;
5
+ use std::collections::HashSet;
6
+
7
+ use swc_core::{
8
+ common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP},
9
+ ecma::{
10
+ ast::*,
11
+ utils::{
12
+ member_expr, private_ident, quote_ident, quote_str, ExprFactory, IntoIndirectCall,
13
+ },
14
+ visit::{noop_visit_mut_type, VisitMut, VisitMutWith},
15
+ },
16
+ plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
17
+ };
18
+ use utils::emit_export_stmts;
19
+
20
+ #[derive(Debug)]
21
+ pub struct TransformVisitor {
22
+ unresolved_mark: Mark,
23
+
24
+ export_decl_id: HashSet<Id>,
25
+ }
26
+
27
+ impl VisitMut for TransformVisitor {
28
+ noop_visit_mut_type!();
29
+
30
+ fn visit_mut_script(&mut self, _: &mut Script) {
31
+ // skip
32
+ }
33
+
34
+ fn visit_mut_module(&mut self, n: &mut Module) {
35
+ let mut strip = LocalExportStrip::default();
36
+ n.visit_mut_with(&mut strip);
37
+
38
+ let LocalExportStrip {
39
+ has_export_assign,
40
+ export,
41
+ export_all,
42
+ export_decl_id,
43
+ ..
44
+ } = strip;
45
+
46
+ self.export_decl_id = export_decl_id;
47
+
48
+ let mut stmts: Vec<ModuleItem> = Vec::with_capacity(n.body.len() + 1);
49
+
50
+ if !has_export_assign && !export.is_empty() {
51
+ // keep module env
52
+ stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
53
+ NamedExport::dummy(),
54
+ )));
55
+
56
+ let exports = self.exports();
57
+
58
+ let export_obj_prop_list = export.into_iter().map(Into::into).collect();
59
+
60
+ stmts.extend(
61
+ emit_export_stmts(exports, export_obj_prop_list)
62
+ .into_iter()
63
+ .map(Into::into),
64
+ );
65
+
66
+ if !self.export_decl_id.is_empty() {
67
+ n.visit_mut_children_with(self);
68
+ }
69
+ }
70
+
71
+ stmts.extend(export_all.into_iter().map(|id| self.export_all(id)));
72
+
73
+ stmts.extend(n.body.take());
74
+
75
+ n.body = stmts;
76
+ }
77
+
78
+ fn visit_mut_prop(&mut self, n: &mut Prop) {
79
+ match n {
80
+ Prop::Shorthand(ref_ident) => {
81
+ if self.export_decl_id.contains(&ref_ident.to_id()) {
82
+ *n = KeyValueProp {
83
+ key: ref_ident.clone().into(),
84
+ value: Box::new(Expr::from(
85
+ self.exports().make_member(ref_ident.take().into()),
86
+ )),
87
+ }
88
+ .into()
89
+ }
90
+ }
91
+ _ => n.visit_mut_children_with(self),
92
+ }
93
+ }
94
+
95
+ fn visit_mut_expr(&mut self, n: &mut Expr) {
96
+ match n {
97
+ Expr::Ident(ref_ident) => {
98
+ if self.export_decl_id.contains(&ref_ident.to_id()) {
99
+ *n = Expr::from(self.exports().make_member(ref_ident.take().into()));
100
+ }
101
+ }
102
+
103
+ _ => n.visit_mut_children_with(self),
104
+ };
105
+ }
106
+
107
+ fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
108
+ let is_indirect = n
109
+ .tag
110
+ .as_ident()
111
+ .map(|ident| self.export_decl_id.contains(&ident.to_id()))
112
+ .unwrap_or_default();
113
+
114
+ n.visit_mut_children_with(self);
115
+
116
+ if is_indirect {
117
+ *n = n.take().into_indirect()
118
+ }
119
+ }
120
+
121
+ fn visit_mut_callee(&mut self, n: &mut Callee) {
122
+ match n {
123
+ Callee::Expr(e) if e.is_ident() => {
124
+ let is_indirect_callee = e
125
+ .as_ident()
126
+ .map(|ident| self.export_decl_id.contains(&ident.to_id()))
127
+ .unwrap_or_default();
128
+
129
+ e.visit_mut_with(self);
130
+
131
+ if is_indirect_callee {
132
+ *n = n.take().into_indirect()
133
+ }
134
+ }
135
+
136
+ _ => n.visit_mut_children_with(self),
137
+ }
138
+ }
139
+ }
140
+
141
+ impl TransformVisitor {
142
+ pub fn new(unresolved_mark: Mark) -> Self {
143
+ Self {
144
+ unresolved_mark,
145
+ export_decl_id: Default::default(),
146
+ }
147
+ }
148
+
149
+ fn exports(&self) -> Ident {
150
+ quote_ident!(SyntaxContext::empty().apply_mark(self.unresolved_mark), "exports")
151
+ }
152
+
153
+ /// ```JavaScript
154
+ /// Object.keys(_mod).forEach(function (key) {
155
+ /// if (key === "default" || key === "__esModule") return;
156
+ /// if (key in exports && exports[key] === _mod[key]) return;
157
+ /// exports[key] = _mod[key];
158
+ /// });
159
+ /// ```
160
+ fn export_all(&self, id: Id) -> ModuleItem {
161
+ let mod_name = Ident::from(id);
162
+ let key = private_ident!("key");
163
+
164
+ member_expr!(Default::default(), DUMMY_SP, Object.keys)
165
+ .as_call(DUMMY_SP, vec![mod_name.clone().as_arg()])
166
+ .make_member(quote_ident!("forEach"))
167
+ .as_call(
168
+ DUMMY_SP,
169
+ vec![Function {
170
+ params: vec![key.clone().into()],
171
+ decorators: vec![],
172
+ span: DUMMY_SP,
173
+ ctxt: SyntaxContext::empty(),
174
+ this_param: None,
175
+ body: Some(FunctionBody {
176
+ span: DUMMY_SP,
177
+ stmts: vec![
178
+ // if (key === "default" || key === "__esModule") return;
179
+ IfStmt {
180
+ span: DUMMY_SP,
181
+ test: BinExpr {
182
+ span: DUMMY_SP,
183
+ op: op!("||"),
184
+ left: BinExpr {
185
+ span: DUMMY_SP,
186
+ op: op!("==="),
187
+ left: key.clone().into(),
188
+ right: quote_str!("default").into(),
189
+ }
190
+ .into(),
191
+ right: BinExpr {
192
+ span: DUMMY_SP,
193
+ op: op!("==="),
194
+ left: key.clone().into(),
195
+ right: quote_str!("__esModule").into(),
196
+ }
197
+ .into(),
198
+ }
199
+ .into(),
200
+ cons: Box::new(
201
+ ReturnStmt {
202
+ span: DUMMY_SP,
203
+ arg: None,
204
+ }
205
+ .into(),
206
+ ),
207
+ alt: None,
208
+ }
209
+ .into(),
210
+ // if (key in exports && exports[key] === _mod[key]) return;
211
+ IfStmt {
212
+ span: DUMMY_SP,
213
+ test: BinExpr {
214
+ span: DUMMY_SP,
215
+ op: op!("&&"),
216
+ left: BinExpr {
217
+ span: DUMMY_SP,
218
+ op: op!("in"),
219
+ left: key.clone().into(),
220
+ right: self.exports().into(),
221
+ }
222
+ .into(),
223
+ right: BinExpr {
224
+ span: DUMMY_SP,
225
+ op: op!("==="),
226
+ left: self
227
+ .exports()
228
+ .computed_member(quote_ident!("key"))
229
+ .into(),
230
+ right: mod_name
231
+ .clone()
232
+ .computed_member(quote_ident!("key"))
233
+ .into(),
234
+ }
235
+ .into(),
236
+ }
237
+ .into(),
238
+ cons: Box::new(
239
+ ReturnStmt {
240
+ span: DUMMY_SP,
241
+ arg: None,
242
+ }
243
+ .into(),
244
+ ),
245
+ alt: None,
246
+ }
247
+ .into(),
248
+ // exports[key] = _mod[key];
249
+ mod_name
250
+ .clone()
251
+ .computed_member(quote_ident!("key"))
252
+ .make_assign_to(
253
+ op!("="),
254
+ self.exports()
255
+ .computed_member(quote_ident!("key"))
256
+ .into(),
257
+ )
258
+ .into_stmt(),
259
+ ],
260
+ }),
261
+ is_generator: false,
262
+ is_async: false,
263
+ type_params: None,
264
+ return_type: None,
265
+ }
266
+ .as_arg()],
267
+ )
268
+ .into_stmt()
269
+ .into()
270
+ }
271
+ }
272
+
273
+ #[plugin_transform]
274
+ pub fn process_transform(
275
+ mut program: Program,
276
+ metadata: TransformPluginProgramMetadata,
277
+ ) -> Program {
278
+ program.visit_mut_with(&mut TransformVisitor::new(
279
+ metadata.unresolved_mark,
280
+ ));
281
+ program
282
+ }
@@ -0,0 +1,340 @@
1
+ use std::collections::HashSet;
2
+
3
+ use indexmap::IndexMap;
4
+ use swc_core::{
5
+ common::{util::take::Take, Span, DUMMY_SP},
6
+ ecma::{
7
+ ast::*,
8
+ atoms::{atom, Atom},
9
+ utils::{find_pat_ids, private_ident, ExprFactory},
10
+ visit::{noop_visit_mut_type, VisitMut, VisitMutWith},
11
+ },
12
+ };
13
+
14
+ use crate::utils::{key_from_export_name, local_ident_from_export_name};
15
+
16
+ type Export = IndexMap<(Atom, Span), Ident>;
17
+
18
+ #[derive(Debug, Default)]
19
+ pub(crate) struct LocalExportStrip {
20
+ pub(crate) has_export_assign: bool,
21
+ pub(crate) export: Export,
22
+ pub(crate) export_all: HashSet<Id>,
23
+ pub(crate) export_decl_id: HashSet<Id>,
24
+ export_default: Option<Stmt>,
25
+ }
26
+
27
+ impl VisitMut for LocalExportStrip {
28
+ noop_visit_mut_type!();
29
+
30
+ fn visit_mut_script(&mut self, _: &mut Script) {
31
+ // skip
32
+ }
33
+
34
+ fn visit_mut_module(&mut self, n: &mut Module) {
35
+ let mut list = Vec::with_capacity(n.body.len());
36
+
37
+ for item in n.body.drain(..) {
38
+ match item {
39
+ ModuleItem::Stmt(stmt) => list.push(stmt.into()),
40
+
41
+ ModuleItem::ModuleDecl(mut module_decl) => {
42
+ // collect link meta
43
+ module_decl.visit_mut_with(self);
44
+
45
+ // emit stmt
46
+ match module_decl {
47
+ ModuleDecl::ExportDecl(ExportDecl { decl, .. }) => {
48
+ list.push(Stmt::Decl(decl).into());
49
+ }
50
+ ModuleDecl::ExportNamed(NamedExport { src: None, .. }) => continue,
51
+ ModuleDecl::ExportNamed(
52
+ item @ NamedExport {
53
+ src: Some(..),
54
+ type_only: false,
55
+ ..
56
+ },
57
+ ) => {
58
+ let decl: ModuleDecl = self.convert_export_decl(item).into();
59
+ list.push(decl.into());
60
+ }
61
+ ModuleDecl::ExportAll(
62
+ e @ ExportAll {
63
+ type_only: false, ..
64
+ },
65
+ ) => {
66
+ let decl: ModuleDecl = self.convert_export_all(e).into();
67
+ list.push(decl.into());
68
+ }
69
+ ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
70
+ decl:
71
+ decl @ (DefaultDecl::Class(ClassExpr {
72
+ ident: Some(..), ..
73
+ })
74
+ | DefaultDecl::Fn(FnExpr {
75
+ ident: Some(..), ..
76
+ })),
77
+ ..
78
+ }) => match decl {
79
+ DefaultDecl::Class(class_expr) => list.extend(
80
+ class_expr
81
+ .as_class_decl()
82
+ .map(|decl| Stmt::Decl(Decl::Class(decl)))
83
+ .map(Into::into),
84
+ ),
85
+ DefaultDecl::Fn(fn_expr) => list.extend(
86
+ fn_expr
87
+ .as_fn_decl()
88
+ .map(|decl| Stmt::Decl(Decl::Fn(decl)))
89
+ .map(Into::into),
90
+ ),
91
+ _ => unreachable!(),
92
+ },
93
+ ModuleDecl::ExportDefaultExpr(..) => {
94
+ list.extend(self.export_default.take().map(From::from))
95
+ }
96
+ ModuleDecl::TsExportAssignment(..) => {
97
+ self.has_export_assign = true;
98
+ list.push(module_decl.into());
99
+ }
100
+ _ => list.push(module_decl.into()),
101
+ };
102
+ }
103
+ };
104
+ }
105
+
106
+ n.body = list;
107
+ }
108
+
109
+ /// ```javascript
110
+ /// export const foo = 1, bar = 2, { baz } = { baz: 3 };
111
+ /// export let a = 1, [b] = [2];
112
+ /// export function x() {}
113
+ /// export class y {}
114
+ /// ```
115
+ /// ->
116
+ /// ```javascript
117
+ /// const foo = 1, bar = 2, { baz } = { baz: 3 };
118
+ /// let a = 1, [b] = [2];
119
+ /// function x() {}
120
+ /// class y {}
121
+ /// ```
122
+ fn visit_mut_export_decl(&mut self, n: &mut ExportDecl) {
123
+ match &n.decl {
124
+ Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
125
+ let ident = ident.clone();
126
+
127
+ self.export.insert((ident.sym.clone(), ident.span), ident);
128
+ }
129
+
130
+ Decl::Var(v) => {
131
+ let ids = find_pat_ids::<_, Ident>(&v.decls);
132
+
133
+ self.export_decl_id.extend(ids.iter().map(Ident::to_id));
134
+
135
+ self.export.extend(ids.into_iter().map(|id| {
136
+ let ident = id.clone();
137
+
138
+ ((id.sym, id.span), ident)
139
+ }));
140
+ }
141
+ _ => {}
142
+ };
143
+ }
144
+
145
+ /// ```javascript
146
+ /// export { foo, foo as bar, foo as "baz" };
147
+ /// export { "foo", foo as bar, "foo" as "baz" } from "mod";
148
+ /// export * as foo from "mod";
149
+ /// export * as "bar" from "mod";
150
+ /// ```
151
+ fn visit_mut_named_export(&mut self, n: &mut NamedExport) {
152
+ if n.type_only || n.src.is_some() {
153
+ return;
154
+ }
155
+
156
+ let NamedExport { specifiers, .. } = n.take();
157
+
158
+ self.export.extend(specifiers.into_iter().map(|e| match e {
159
+ ExportSpecifier::Namespace(..) => {
160
+ unreachable!("`export *` without src is invalid")
161
+ }
162
+ ExportSpecifier::Default(..) => {
163
+ unreachable!("`export foo` without src is invalid")
164
+ }
165
+ ExportSpecifier::Named(ExportNamedSpecifier { orig, exported, .. }) => {
166
+ let orig = match orig {
167
+ ModuleExportName::Ident(id) => id,
168
+ ModuleExportName::Str(_) => {
169
+ unreachable!(r#"`export {{ "foo" }}` without src is invalid"#)
170
+ }
171
+ };
172
+
173
+ if let Some(exported) = exported {
174
+ let exported = match exported {
175
+ ModuleExportName::Ident(Ident { span, sym, .. }) => (sym, span),
176
+ ModuleExportName::Str(Str { span, value, .. }) => (value.to_atom_lossy().into_owned(), span),
177
+ };
178
+
179
+ (exported, orig)
180
+ } else {
181
+ let exported = orig.sym.clone();
182
+ ((exported, orig.span), orig)
183
+ }
184
+ }
185
+ }))
186
+ }
187
+
188
+ /// ```javascript
189
+ /// export default class foo {};
190
+ /// export default class {};
191
+ /// export default function bar () {};
192
+ /// export default function () {};
193
+ /// ```
194
+ /// ->
195
+ /// ```javascript
196
+ /// class foo {};
197
+ /// class _default {};
198
+ /// function bar () {};
199
+ /// function _default () {};
200
+ /// ```
201
+ fn visit_mut_export_default_decl(&mut self, n: &mut ExportDefaultDecl) {
202
+ match &mut n.decl {
203
+ DefaultDecl::Class(class_expr) => {
204
+ if let Some(ident) = class_expr.ident.clone() {
205
+ self.export.insert((atom!("default"), n.span), ident);
206
+ }
207
+ }
208
+ DefaultDecl::Fn(fn_expr) => {
209
+ if let Some(ident) = fn_expr.ident.clone() {
210
+ self.export.insert((atom!("default"), n.span), ident);
211
+ }
212
+ }
213
+ DefaultDecl::TsInterfaceDecl(_) => {}
214
+ }
215
+ }
216
+
217
+ /// ```javascript
218
+ /// export default foo;
219
+ /// export default 1
220
+ /// ```
221
+ /// ->
222
+ /// ```javascript
223
+ /// var _default = foo;
224
+ /// var _default = 1;
225
+ /// ```
226
+ fn visit_mut_export_default_expr(&mut self, n: &mut ExportDefaultExpr) {
227
+ let ident = private_ident!(n.span, "_default");
228
+
229
+ self.export
230
+ .insert((atom!("default"), n.span), ident.clone());
231
+
232
+ self.export_default = Some(Stmt::Decl(
233
+ n.expr
234
+ .take()
235
+ .into_var_decl(VarDeclKind::Const, ident.into())
236
+ .into(),
237
+ ));
238
+ }
239
+ }
240
+
241
+ impl LocalExportStrip {
242
+ fn convert_export_decl(&mut self, n: NamedExport) -> ImportDecl {
243
+ let NamedExport {
244
+ span,
245
+ specifiers,
246
+ src,
247
+ type_only,
248
+ with,
249
+ } = n;
250
+
251
+ let src = src.unwrap();
252
+
253
+ let specifiers = specifiers
254
+ .into_iter()
255
+ .flat_map(|s| self.convert_export_specifier(s))
256
+ .collect();
257
+
258
+ ImportDecl {
259
+ span,
260
+ specifiers,
261
+ src,
262
+ type_only,
263
+ with,
264
+ phase: ImportPhase::Evaluation,
265
+ }
266
+ }
267
+
268
+ fn convert_export_specifier(&mut self, s: ExportSpecifier) -> Option<ImportSpecifier> {
269
+ match s {
270
+ ExportSpecifier::Namespace(ExportNamespaceSpecifier { span, name }) => {
271
+ let key = key_from_export_name(&name);
272
+ let local = local_ident_from_export_name(name);
273
+ self.export.insert(key, local.clone());
274
+
275
+ Some(ImportSpecifier::Namespace(ImportStarAsSpecifier {
276
+ span,
277
+ local,
278
+ }))
279
+ }
280
+ ExportSpecifier::Default(ExportDefaultSpecifier { exported }) => {
281
+ let key = (exported.sym.clone(), exported.span);
282
+ let local = exported.into_private();
283
+ self.export.insert(key, local.clone());
284
+
285
+ Some(ImportSpecifier::Default(ImportDefaultSpecifier {
286
+ local,
287
+ span: DUMMY_SP,
288
+ }))
289
+ }
290
+ ExportSpecifier::Named(ExportNamedSpecifier {
291
+ span,
292
+ orig,
293
+ exported,
294
+ is_type_only: false,
295
+ }) => {
296
+ // export { "x-1" as "y-1" } from "foo"
297
+ // ->
298
+ // import { "x-1" as x1 } from "foo"
299
+ // export { x1 as "y-1" }
300
+ let name = exported.as_ref().unwrap_or(&orig);
301
+
302
+ let key = key_from_export_name(name);
303
+ let local = local_ident_from_export_name(orig.clone());
304
+ self.export.insert(key, local.clone());
305
+
306
+ Some(ImportSpecifier::Named(ImportNamedSpecifier {
307
+ span,
308
+ local,
309
+ imported: Some(orig),
310
+ is_type_only: false,
311
+ }))
312
+ }
313
+ _ => None,
314
+ }
315
+ }
316
+
317
+ fn convert_export_all(&mut self, e: ExportAll) -> ImportDecl {
318
+ let ExportAll {
319
+ span, src, with, ..
320
+ } = e;
321
+
322
+ let mod_name = private_ident!("mod");
323
+
324
+ self.export_all.insert(mod_name.to_id());
325
+
326
+ let star = ImportStarAsSpecifier {
327
+ span,
328
+ local: mod_name.clone(),
329
+ };
330
+
331
+ ImportDecl {
332
+ span,
333
+ specifiers: vec![star.into()],
334
+ src,
335
+ type_only: false,
336
+ with,
337
+ phase: ImportPhase::Evaluation,
338
+ }
339
+ }
340
+ }
package/src/utils.rs ADDED
@@ -0,0 +1,257 @@
1
+ use swc_core::{
2
+ common::{Span, Spanned, SyntaxContext, DUMMY_SP},
3
+ ecma::{
4
+ ast::*,
5
+ atoms::{atom, Atom},
6
+ utils::{
7
+ is_valid_prop_ident, member_expr, private_ident, quote_ident, quote_str, ExprFactory,
8
+ FunctionFactory,
9
+ },
10
+ },
11
+ };
12
+
13
+ /// {
14
+ /// "key": ident,
15
+ /// }
16
+ pub(crate) struct ObjPropKeyIdent(Atom, Span, Ident);
17
+
18
+ impl From<((Atom, Span), Ident)> for ObjPropKeyIdent {
19
+ fn from(((key, span), ident): ((Atom, Span), Ident)) -> Self {
20
+ Self(key, span, ident)
21
+ }
22
+ }
23
+
24
+ impl From<(Atom, Span, Ident)> for ObjPropKeyIdent {
25
+ fn from((key, span, ident): (Atom, Span, Ident)) -> Self {
26
+ Self(key, span, ident)
27
+ }
28
+ }
29
+
30
+ impl Spanned for ObjPropKeyIdent {
31
+ fn span(&self) -> Span {
32
+ self.1
33
+ }
34
+ }
35
+
36
+ impl ObjPropKeyIdent {
37
+ pub fn key(&self) -> &Atom {
38
+ &self.0
39
+ }
40
+
41
+ pub fn into_expr(self) -> Expr {
42
+ self.2.into()
43
+ }
44
+ }
45
+
46
+ /// ```javascript
47
+ /// {
48
+ /// key: () => expr,
49
+ /// }
50
+ /// ```
51
+ pub(crate) fn prop_arrow(prop: ObjPropKeyIdent) -> Prop {
52
+ let key = prop_name(prop.key(), prop.span()).into();
53
+
54
+ KeyValueProp {
55
+ key,
56
+ value: Box::new(prop.into_expr().into_lazy_arrow(Default::default()).into()),
57
+ }
58
+ .into()
59
+ }
60
+
61
+ pub(crate) fn prop_name(key: &str, span: Span) -> IdentOrStr {
62
+ if is_valid_prop_ident(key) {
63
+ IdentOrStr::Ident(quote_ident!(Default::default(), span, key))
64
+ } else {
65
+ IdentOrStr::Str(quote_str!(span, key))
66
+ }
67
+ }
68
+
69
+ pub(crate) enum IdentOrStr {
70
+ Ident(Ident),
71
+ Str(Str),
72
+ }
73
+
74
+ impl From<IdentOrStr> for PropName {
75
+ fn from(val: IdentOrStr) -> Self {
76
+ match val {
77
+ IdentOrStr::Ident(i) => Self::Ident(i.into()),
78
+ IdentOrStr::Str(s) => Self::Str(s),
79
+ }
80
+ }
81
+ }
82
+
83
+ impl From<IdentOrStr> for MemberProp {
84
+ fn from(val: IdentOrStr) -> Self {
85
+ match val {
86
+ IdentOrStr::Ident(i) => Self::Ident(i.into()),
87
+ IdentOrStr::Str(s) => Self::Computed(ComputedPropName {
88
+ span: DUMMY_SP,
89
+ expr: s.into(),
90
+ }),
91
+ }
92
+ }
93
+ }
94
+
95
+ /// Creates
96
+ ///
97
+ ///```js
98
+ ///
99
+ /// Object.defineProperty(target, prop_name, {
100
+ /// ...props
101
+ /// });
102
+ /// ```
103
+ pub(super) fn object_define_property(
104
+ target: ExprOrSpread,
105
+ prop_name: ExprOrSpread,
106
+ descriptor: ExprOrSpread,
107
+ ) -> Expr {
108
+ member_expr!(Default::default(), DUMMY_SP, Object.defineProperty)
109
+ .as_call(DUMMY_SP, vec![target, prop_name, descriptor])
110
+ }
111
+
112
+ pub(crate) fn object_define_enumerable_configurable(
113
+ target: ExprOrSpread,
114
+ prop_name: ExprOrSpread,
115
+ prop: PropOrSpread,
116
+ ) -> Expr {
117
+ object_define_property(
118
+ target,
119
+ prop_name,
120
+ ObjectLit {
121
+ span: DUMMY_SP,
122
+ props: vec![
123
+ PropOrSpread::Prop(Box::new(
124
+ KeyValueProp {
125
+ key: quote_ident!("enumerable").into(),
126
+ value: Box::new(true.into()),
127
+ }
128
+ .into(),
129
+ )),
130
+ prop,
131
+ PropOrSpread::Prop(Box::new(
132
+ KeyValueProp {
133
+ key: quote_ident!("configurable").into(),
134
+ value: Box::new(true.into()),
135
+ }
136
+ .into(),
137
+ )),
138
+ ],
139
+ }
140
+ .as_arg(),
141
+ )
142
+ }
143
+
144
+ pub(crate) fn emit_export_stmts(exports: Ident, mut prop_list: Vec<ObjPropKeyIdent>) -> Vec<Stmt> {
145
+ match prop_list.len() {
146
+ 0 | 1 => prop_list
147
+ .pop()
148
+ .map(|obj_prop| {
149
+ object_define_enumerable_configurable(
150
+ exports.as_arg(),
151
+ quote_str!(obj_prop.span(), obj_prop.key().clone()).as_arg(),
152
+ prop_arrow((atom!("get"), DUMMY_SP, obj_prop.2.clone()).into()).into(),
153
+ )
154
+ .into_stmt()
155
+ })
156
+ .into_iter()
157
+ .collect(),
158
+ _ => {
159
+ let props = prop_list
160
+ .into_iter()
161
+ .map(prop_arrow)
162
+ .map(Into::into)
163
+ .collect();
164
+ let obj_lit = ObjectLit {
165
+ span: DUMMY_SP,
166
+ props,
167
+ };
168
+
169
+ let esm_export_ident = private_ident!("_export");
170
+
171
+ vec![
172
+ Stmt::Decl(Decl::Fn(
173
+ esm_export().into_fn_decl(esm_export_ident.clone()),
174
+ )),
175
+ esm_export_ident
176
+ .as_call(DUMMY_SP, vec![exports.as_arg(), obj_lit.as_arg()])
177
+ .into_stmt(),
178
+ ]
179
+ }
180
+ }
181
+ }
182
+
183
+ /// ```javascript
184
+ /// function _esmExport(target, all) {
185
+ /// for (var name in all)Object.defineProperty(target, name, { get: all[name], enumerable: true, configurable: true });
186
+ /// }
187
+ /// ```
188
+ pub(crate) fn esm_export() -> Function {
189
+ let target = private_ident!("target");
190
+ let all = private_ident!("all");
191
+ let name = private_ident!("name");
192
+
193
+ let getter = KeyValueProp {
194
+ key: quote_ident!("get").into(),
195
+ value: Box::new(Expr::from(all.clone().computed_member(Expr::from(name.clone())))),
196
+ };
197
+
198
+ let body = object_define_enumerable_configurable(
199
+ target.clone().as_arg(),
200
+ name.clone().as_arg(),
201
+ PropOrSpread::Prop(Box::new(Prop::KeyValue(getter))),
202
+ )
203
+ .into_stmt();
204
+
205
+ let for_in_stmt: Stmt = ForInStmt {
206
+ span: DUMMY_SP,
207
+ left: VarDecl {
208
+ span: DUMMY_SP,
209
+ ctxt: SyntaxContext::empty(),
210
+ kind: VarDeclKind::Var,
211
+ declare: false,
212
+ decls: vec![VarDeclarator {
213
+ span: DUMMY_SP,
214
+ name: name.into(),
215
+ init: None,
216
+ definite: false,
217
+ }],
218
+ }
219
+ .into(),
220
+ right: Box::new(all.clone().into()),
221
+ body: Box::new(body),
222
+ }
223
+ .into();
224
+
225
+ Function {
226
+ params: vec![target.into(), all.into()],
227
+ decorators: Default::default(),
228
+ span: DUMMY_SP,
229
+ ctxt: SyntaxContext::empty(),
230
+ this_param: None,
231
+ body: Some(FunctionBody {
232
+ span: DUMMY_SP,
233
+ stmts: vec![for_in_stmt],
234
+ }),
235
+ is_generator: false,
236
+ is_async: false,
237
+ type_params: None,
238
+ return_type: None,
239
+ }
240
+ }
241
+
242
+ pub(crate) fn key_from_export_name(n: &ModuleExportName) -> (Atom, Span) {
243
+ match n {
244
+ ModuleExportName::Ident(ident) => (ident.sym.clone(), ident.span),
245
+ ModuleExportName::Str(str) => (str.value.to_atom_lossy().into_owned(), str.span),
246
+ }
247
+ }
248
+
249
+ pub(crate) fn local_ident_from_export_name(n: ModuleExportName) -> Ident {
250
+ match n {
251
+ ModuleExportName::Ident(ident) => private_ident!(ident.span, ident.sym.clone()),
252
+ ModuleExportName::Str(str) => match Ident::verify_symbol(&str.value.to_string_lossy()) {
253
+ Ok(_) => private_ident!(str.value.to_atom_lossy().into_owned()),
254
+ Err(s) => private_ident!(s),
255
+ },
256
+ }
257
+ }