From 206c0dbebe5eecaa8a5c1846189b26cb5517d84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Tue, 3 Jan 2023 18:45:03 +0900 Subject: [PATCH] fix(es/compat): Fix syntax context of `async-to-generator` (#6741) **Description:** Previously, the `async-to-generator` produced invalid AST, in the aspect of span hygiene. [Playground](https://play.swc.rs/?version=1.3.24&code=H4sIAAAAAAAAAz1MbQqAIBT77yn2UyG6gNQJuoSZRCAa7xkk4d1TicZgH7C5%2B4yUYGPgBE7ZuyWazREmSIVpxiMAcumi0C3ANZxyvQ6%2Fqa8CehxjaGNpOAcLaWjn%2F6KhKN1dGaoULSpfHPTdxn8AAAA%3D&config=H4sIAAAAAAAAA0WOSwrDMAxE76K1F22hXfgE3fQQxlWCi39ICsQY3z12cMlOjObNTIUfW9AVsiFGGheXKGYHDVIysiWXBRQId2kxnrEpwF2QovFv9BmJQQtt2D2GVpROIj9u92enfEqMk1MQXHRLGR02hUzIfL1MXP3f2XpFSN9tCPWccWa%2BoF0Zk3P8mcYxoR3Kj7IYzwAAAA%3D%3D). It generate two bindings for `args` so it's invalid. **Related issue:** - Closes https://github.com/swc-project/swc/issues/6730. --- Cargo.lock | 1 + .../fixture/issues-6xxx/6730/input/.swcrc | 26 +++++++++++ .../fixture/issues-6xxx/6730/input/index.ts | 11 +++++ .../fixture/issues-6xxx/6730/output/index.ts | 13 ++++++ crates/swc/tests/projects.rs | 4 ++ .../vercel/full/next-31419/1/output/index.js | 2 +- .../vercel/full/next-31419/2/output/index.js | 2 +- .../src/compress/optimize/iife.rs | 8 ++-- .../src/compress/optimize/util.rs | 20 +-------- .../tests/fixture/issues/6730/config.json | 3 ++ .../tests/fixture/issues/6730/input.js | 45 +++++++++++++++++++ .../tests/fixture/issues/6730/mangle.json | 3 ++ .../tests/fixture/issues/6730/output.js | 42 +++++++++++++++++ .../src/es2017/async_to_generator.rs | 14 +++++- crates/swc_ecma_utils/Cargo.toml | 1 + crates/swc_ecma_utils/src/lib.rs | 26 +++++++++++ 16 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 crates/swc/tests/fixture/issues-6xxx/6730/input/.swcrc create mode 100644 crates/swc/tests/fixture/issues-6xxx/6730/input/index.ts create mode 100644 crates/swc/tests/fixture/issues-6xxx/6730/output/index.ts create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/6730/config.json create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/6730/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/6730/mangle.json create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/6730/output.js diff --git a/Cargo.lock b/Cargo.lock index d574e437ef9..ef25225e8bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4044,6 +4044,7 @@ dependencies = [ "num_cpus", "once_cell", "rayon", + "rustc-hash", "swc_atoms", "swc_common", "swc_ecma_ast", diff --git a/crates/swc/tests/fixture/issues-6xxx/6730/input/.swcrc b/crates/swc/tests/fixture/issues-6xxx/6730/input/.swcrc new file mode 100644 index 00000000000..b697a1d6ca1 --- /dev/null +++ b/crates/swc/tests/fixture/issues-6xxx/6730/input/.swcrc @@ -0,0 +1,26 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false + }, + "target": "es2015", + "loose": false, + "minify": { + "compress": false, + "mangle": { + "toplevel": false, + "keep_classnames": false, + "keep_fnames": false, + "keep_private_props": false, + "ie8": false, + "safari10": false + } + } + }, + "module": { + "type": "es6" + }, + "minify": false, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-6xxx/6730/input/index.ts b/crates/swc/tests/fixture/issues-6xxx/6730/input/index.ts new file mode 100644 index 00000000000..8d9b26c59e5 --- /dev/null +++ b/crates/swc/tests/fixture/issues-6xxx/6730/input/index.ts @@ -0,0 +1,11 @@ +import { Plugin, PluginBuild } from 'esbuild'; + +export const styleLoader = (): Plugin => { + return { + setup(build: PluginBuild) { + build.onLoad(async (args) => { + + }); + }, + }; +}; \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-6xxx/6730/output/index.ts b/crates/swc/tests/fixture/issues-6xxx/6730/output/index.ts new file mode 100644 index 00000000000..a2c88b56dbb --- /dev/null +++ b/crates/swc/tests/fixture/issues-6xxx/6730/output/index.ts @@ -0,0 +1,13 @@ +import r from "@swc/helpers/src/_async_to_generator.mjs"; +export const styleLoader = ()=>{ + return { + setup (t) { + t.onLoad(function() { + var t = r(function*(r) {}); + return function(r) { + return t.apply(this, arguments); + }; + }()); + } + }; +}; diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs index 1ac31c3d8d1..8538fa0391d 100644 --- a/crates/swc/tests/projects.rs +++ b/crates/swc/tests/projects.rs @@ -812,6 +812,10 @@ fn should_visit() { #[testing::fixture("tests/fixture/**/input/")] #[testing::fixture("tests/vercel/**/input/")] +fn fixture(input_dir: PathBuf) { + tests(input_dir) +} + fn tests(input_dir: PathBuf) { let output = input_dir.parent().unwrap().join("output"); diff --git a/crates/swc/tests/vercel/full/next-31419/1/output/index.js b/crates/swc/tests/vercel/full/next-31419/1/output/index.js index e02f2f38e24..59ba1d37403 100644 --- a/crates/swc/tests/vercel/full/next-31419/1/output/index.js +++ b/crates/swc/tests/vercel/full/next-31419/1/output/index.js @@ -64,7 +64,7 @@ export var listOfUser = function(t) { 2 ]; }); - }), function(r, e) { + }), function(r, n) { return e.apply(this, arguments); })); }; diff --git a/crates/swc/tests/vercel/full/next-31419/2/output/index.js b/crates/swc/tests/vercel/full/next-31419/2/output/index.js index 3a7d9c50cd4..33535d28e33 100644 --- a/crates/swc/tests/vercel/full/next-31419/2/output/index.js +++ b/crates/swc/tests/vercel/full/next-31419/2/output/index.js @@ -19,7 +19,7 @@ export const listOfUser = function(n) { postgreSQL.query(t, null, function(n, t) { n ? e(n) : r(t.rows); }); - }), function(r, e) { + }), function(r, n) { return e.apply(this, arguments); })); }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 257e0a5db04..dfd9084fa80 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -5,7 +5,7 @@ use swc_atoms::js_word; use swc_common::{pass::Either, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{ - contains_arguments, contains_this_expr, find_pat_ids, undefined, ExprFactory, + contains_arguments, contains_this_expr, find_pat_ids, undefined, ExprFactory, Remapper, }; use swc_ecma_visit::VisitMutWith; @@ -13,7 +13,7 @@ use super::{util::NormalMultiReplacer, Optimizer}; #[cfg(feature = "debug")] use crate::debug::dump; use crate::{ - compress::optimize::{util::Remapper, Ctx}, + compress::optimize::Ctx, mode::Mode, util::{idents_captured_by, idents_used_by, make_number}, }; @@ -578,7 +578,7 @@ where if self.vars.inline_with_multi_replacer(body) { self.changed = true; } - body.visit_mut_with(&mut Remapper { vars: remap }); + body.visit_mut_with(&mut Remapper::new(&remap)); exprs.push(body.take()); report_change!("inline: Inlining a call to an arrow function"); @@ -889,7 +889,7 @@ where if self.vars.inline_with_multi_replacer(body) { self.changed = true; } - body.visit_mut_with(&mut Remapper { vars: remap }); + body.visit_mut_with(&mut Remapper::new(&remap)); let mut vars = Vec::new(); let mut exprs = Vec::new(); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index 17fb63d4685..f0b9bd4b412 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -5,7 +5,7 @@ use std::{ use rustc_hash::{FxHashMap, FxHashSet}; use swc_atoms::js_word; -use swc_common::{util::take::Take, Span, SyntaxContext, DUMMY_SP}; +use swc_common::{util::take::Take, Span, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::{Parallel, ParallelExt}; use swc_ecma_utils::{ExprCtx, ExprExt}; @@ -185,24 +185,6 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool { !matches!(e, Expr::Lit(..) | Expr::Unary(..)) } -/// Variable remapper -/// -/// - Used for evaluating IIFEs - -pub(crate) struct Remapper { - pub vars: FxHashMap, -} - -impl VisitMut for Remapper { - noop_visit_mut_type!(); - - fn visit_mut_ident(&mut self, i: &mut Ident) { - if let Some(new_ctxt) = self.vars.get(&i.to_id()).copied() { - i.span.ctxt = new_ctxt; - } - } -} - /// A visitor responsible for inlining special kind of variables and removing /// (some) unused variables. Due to the order of visit, the main visitor cannot /// handle all edge cases and this type is the complement for it. diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6730/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6730/config.json new file mode 100644 index 00000000000..eceaef835f5 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6730/config.json @@ -0,0 +1,3 @@ +{ + "defaults": false +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6730/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6730/input.js new file mode 100644 index 00000000000..79c8a3a49d9 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6730/input.js @@ -0,0 +1,45 @@ +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} +function _asyncToGenerator(fn) { + return function () { + var self = this, args = arguments; + return new Promise(function (resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } + _next(undefined); + }); + }; +} +export const styleLoader = () => { + return { + name: 'style-loader', + setup(build) { + build.onLoad({ + filter: /.*/, + namespace: 'less' + }, function () { + var _ref = _asyncToGenerator(function* (args) { }); + return function (args) { + return _ref.apply(this, arguments); + }; + }()); + } + }; +}; diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6730/mangle.json b/crates/swc_ecma_minifier/tests/fixture/issues/6730/mangle.json new file mode 100644 index 00000000000..9fa9d303874 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6730/mangle.json @@ -0,0 +1,3 @@ +{ + "toplevel": true +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6730/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6730/output.js new file mode 100644 index 00000000000..88aed1267e7 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/6730/output.js @@ -0,0 +1,42 @@ +function n(n, e, t, r, o, u, i) { + try { + var a = n[u](i); + var c = a.value; + } catch (s) { + t(s); + return; + } + if (a.done) e(c); + else Promise.resolve(c).then(r, o); +} +function e(e) { + return function() { + var t = this, r = arguments; + return new Promise(function(o, u) { + var i = e.apply(t, r); + function a(e) { + n(i, o, u, a, c, "next", e); + } + function c(e) { + n(i, o, u, a, c, "throw", e); + } + a(void 0); + }); + }; +} +export const styleLoader = ()=>{ + return { + name: 'style-loader', + setup (n) { + n.onLoad({ + filter: /.*/, + namespace: 'less' + }, function() { + var n = e(function*(n) {}); + return function(e) { + return n.apply(this, arguments); + }; + }()); + } + }; +}; diff --git a/crates/swc_ecma_transforms_compat/src/es2017/async_to_generator.rs b/crates/swc_ecma_transforms_compat/src/es2017/async_to_generator.rs index f07f27a1d74..20f43bfb0ca 100644 --- a/crates/swc_ecma_transforms_compat/src/es2017/async_to_generator.rs +++ b/crates/swc_ecma_transforms_compat/src/es2017/async_to_generator.rs @@ -6,9 +6,9 @@ use swc_ecma_ast::*; use swc_ecma_transforms_base::{helper, helper_expr, perf::Check}; use swc_ecma_transforms_macros::fast_path; use swc_ecma_utils::{ - contains_this_expr, + contains_this_expr, find_pat_ids, function::{FnEnvHoister, FnWrapperResult, FunctionWrapper}, - private_ident, quote_ident, ExprFactory, StmtLike, + private_ident, quote_ident, ExprFactory, Remapper, StmtLike, }; use swc_ecma_visit::{ as_folder, noop_visit_mut_type, noop_visit_type, Fold, Visit, VisitMut, VisitMutWith, VisitWith, @@ -393,6 +393,16 @@ impl Actual { /// `_asyncToGenerator(function*() {})` from `async function() {}`; #[tracing::instrument(level = "info", skip_all)] fn make_fn_ref(mut expr: FnExpr) -> Expr { + { + let param_ids: Vec = find_pat_ids(&expr.function.params); + let mapping = param_ids + .into_iter() + .map(|id| (id, SyntaxContext::empty().apply_mark(Mark::new()))) + .collect(); + + expr.function.visit_mut_with(&mut Remapper::new(&mapping)); + } + expr.function.body.visit_mut_with(&mut AsyncFnBodyHandler { is_async_generator: expr.function.is_generator, }); diff --git a/crates/swc_ecma_utils/Cargo.toml b/crates/swc_ecma_utils/Cargo.toml index f2f90f4c7bb..ba6ba7b9c14 100644 --- a/crates/swc_ecma_utils/Cargo.toml +++ b/crates/swc_ecma_utils/Cargo.toml @@ -24,6 +24,7 @@ indexmap = "1.6.1" num_cpus = "1.13.1" once_cell = "1.10.0" rayon = {version = "1.5.1", optional = true} +rustc-hash = "1.1.0" swc_atoms = {version = "0.4.32", path = "../swc_atoms"} swc_common = {version = "0.29.25", path = "../swc_common"} swc_ecma_ast = {version = "0.95.9", path = "../swc_ecma_ast"} diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index e98cf48895a..4ac50f03426 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -18,6 +18,7 @@ use std::{ ops::Add, }; +use rustc_hash::FxHashMap; use swc_atoms::{js_word, JsWord}; use swc_common::{collections::AHashSet, Mark, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; @@ -2825,6 +2826,31 @@ pub fn contains_top_level_await>(t: &V) -> bool { finder.found } +/// Variable remapper +/// +/// This visitor modifies [SyntaxContext] while preserving the symbol of +/// [Ident]s. + +pub struct Remapper<'a> { + vars: &'a FxHashMap, +} + +impl<'a> Remapper<'a> { + pub fn new(vars: &'a FxHashMap) -> Self { + Self { vars } + } +} + +impl VisitMut for Remapper<'_> { + noop_visit_mut_type!(); + + fn visit_mut_ident(&mut self, i: &mut Ident) { + if let Some(new_ctxt) = self.vars.get(&i.to_id()).copied() { + i.span.ctxt = new_ctxt; + } + } +} + #[cfg(test)] mod test { use swc_common::{input::StringInput, BytePos};