From 1b9584cfc07c4450cc2533ffa359e6d31b2c9f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 13 Aug 2021 19:57:25 +0900 Subject: [PATCH] fix(swc): Fix bugs (#2067) swc_ecma_transforms_compat: - Fix optional chaining. (#2063) node/swc: - Fix definition of `ImportDeclaration`. (#2059) testing: - Allow using `testing` with stable `rustc`. testing_macros: - Add `#[inline(never)]`. --- Cargo.lock | 9 +-- ecmascript/ast/src/module_decl.rs | 2 +- ecmascript/transforms/compat/Cargo.toml | 2 +- .../compat/src/es2020/opt_chaining.rs | 52 +++++++++++--- .../compat/tests/es2020_optional_chaining.rs | 27 +++++--- .../fixture/opt-chain/issue-2063/1/exec.js | 7 ++ .../fixture/opt-chain/issue-2063/2/exec.js | 5 ++ ecmascript/transforms/testing/Cargo.toml | 3 +- ecmascript/transforms/testing/src/lib.rs | 67 +++++++++++++++++-- node-swc/src/types.ts | 2 + testing/Cargo.toml | 2 +- testing/macros/Cargo.toml | 2 +- testing/macros/src/fixture.rs | 1 + testing/src/lib.rs | 2 - tests/fixture/issue-2063/input/index.js | 7 ++ tests/fixture/issue-2063/output/index.js | 7 ++ 16 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/1/exec.js create mode 100644 ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/2/exec.js create mode 100644 tests/fixture/issue-2063/input/index.js create mode 100644 tests/fixture/issue-2063/output/index.js diff --git a/Cargo.lock b/Cargo.lock index 11b33e6958d..d3e45cf4c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2488,7 +2488,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.29.2" +version = "0.29.3" dependencies = [ "arrayvec", "fxhash", @@ -2622,9 +2622,10 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_testing" -version = "0.26.1" +version = "0.26.2" dependencies = [ "ansi_term 0.12.1", + "anyhow", "serde", "serde_json", "swc_common", @@ -2818,7 +2819,7 @@ dependencies = [ [[package]] name = "testing" -version = "0.12.0" +version = "0.12.1" dependencies = [ "ansi_term 0.12.1", "difference", @@ -2833,7 +2834,7 @@ dependencies = [ [[package]] name = "testing_macros" -version = "0.2.1" +version = "0.2.2" dependencies = [ "anyhow", "glob", diff --git a/ecmascript/ast/src/module_decl.rs b/ecmascript/ast/src/module_decl.rs index 13eb737157e..e5ca0cc152c 100644 --- a/ecmascript/ast/src/module_decl.rs +++ b/ecmascript/ast/src/module_decl.rs @@ -73,7 +73,7 @@ pub struct ImportDecl { #[serde(rename = "source")] pub src: Str, - #[serde(rename = "typeOnly")] + #[serde(default, rename = "typeOnly")] pub type_only: bool, #[serde(default)] diff --git a/ecmascript/transforms/compat/Cargo.toml b/ecmascript/transforms/compat/Cargo.toml index de9ec04f868..3a3a2a23632 100644 --- a/ecmascript/transforms/compat/Cargo.toml +++ b/ecmascript/transforms/compat/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecma_transforms_compat" repository = "https://github.com/swc-project/swc.git" -version = "0.29.2" +version = "0.29.3" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/ecmascript/transforms/compat/src/es2020/opt_chaining.rs b/ecmascript/transforms/compat/src/es2020/opt_chaining.rs index ebcf948f63d..2b43311b6fe 100644 --- a/ecmascript/transforms/compat/src/es2020/opt_chaining.rs +++ b/ecmascript/transforms/compat/src/es2020/opt_chaining.rs @@ -454,39 +454,71 @@ impl OptChaining { }, "_obj", ); - let obj = if !is_super_access && aliased { - self.vars_with_init.push(VarDeclarator { + let obj_expr = if !is_super_access && aliased { + self.vars_without_init.push(VarDeclarator { span: obj_span, definite: false, name: Pat::Ident(this_obj.clone().into()), - init: Some(obj), + init: None, }); - Box::new(Expr::Ident(this_obj.clone())) + match *obj { + Expr::Member( + obj + @ + MemberExpr { + obj: ExprOrSuper::Expr(..), + .. + }, + ) => Box::new(Expr::Member(MemberExpr { + span: obj.span, + obj: Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(Box::new(Pat::Ident( + this_obj.clone().into(), + ))), + right: obj.obj.expect_expr(), + }) + .as_obj(), + prop: obj.prop, + computed: obj.computed, + })), + _ => Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(Box::new(Pat::Ident( + this_obj.clone().into(), + ))), + right: obj, + })), + } } else { obj }; - let i = private_ident!(obj_span, "ref"); + + let tmp = private_ident!(obj_span, "ref"); + self.vars_without_init.push(VarDeclarator { span: obj_span, definite: false, - name: Pat::Ident(i.clone().into()), + name: Pat::Ident(tmp.clone().into()), init: None, }); ( Box::new(Expr::Assign(AssignExpr { span: DUMMY_SP, - left: PatOrExpr::Pat(Box::new(Pat::Ident(i.clone().into()))), + left: PatOrExpr::Pat(Box::new(Pat::Ident(tmp.clone().into()))), op: op!("="), - right: obj, + right: obj_expr, })), - Box::new(Expr::Ident(i.clone())), + Box::new(Expr::Ident(tmp.clone())), Box::new(Expr::Call(CallExpr { span, callee: ExprOrSuper::Expr(Box::new(Expr::Member(MemberExpr { span: DUMMY_SP, - obj: ExprOrSuper::Expr(Box::new(Expr::Ident(i.clone()))), + obj: ExprOrSuper::Expr(Box::new(Expr::Ident(tmp.clone()))), prop: Box::new(Expr::Ident(Ident::new("call".into(), span))), computed: false, }))), diff --git a/ecmascript/transforms/compat/tests/es2020_optional_chaining.rs b/ecmascript/transforms/compat/tests/es2020_optional_chaining.rs index 634635c00db..e4017186695 100644 --- a/ecmascript/transforms/compat/tests/es2020_optional_chaining.rs +++ b/ecmascript/transforms/compat/tests/es2020_optional_chaining.rs @@ -1,6 +1,7 @@ +use std::{fs::read_to_string, path::PathBuf}; use swc_ecma_parser::{Syntax, TsConfig}; use swc_ecma_transforms_compat::es2020::optional_chaining; -use swc_ecma_transforms_testing::{test, test_exec}; +use swc_ecma_transforms_testing::{compare_stdout, test, test_exec}; use swc_ecma_visit::Fold; fn tr(_: ()) -> impl Fold { @@ -298,7 +299,7 @@ foo?.bar()?.() "#, r#" -var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; +var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, _obj, ref9; foo === null || foo === void 0 ? void 0 : foo(foo); foo === null || foo === void 0 ? void 0 : foo.bar(); (ref = foo.bar) === null || ref === void 0 ? void 0 : ref.call(foo, foo.bar, false); @@ -309,8 +310,7 @@ foo === null || foo === void 0 ? void 0 : (ref2 = foo()) === null || ref2 === vo (ref4 = foo.bar) === null || ref4 === void 0 ? void 0 : (ref5 = ref4.call(foo)) === null || ref5 === void 0 ? void 0 : ref5.baz; foo === null || foo === void 0 ? void 0 : (ref6 = foo.bar) === null || ref6 === void 0 ? void 0 : ref6.call(foo).baz; foo === null || foo === void 0 ? void 0 : (ref7 = foo.bar) === null || ref7 === void 0 ? void 0 : (ref8 = ref7.call(foo)) === null || ref8 === void 0 ? void 0 : ref8.baz; -var _obj = foo === null || foo === void 0 ? void 0 : foo.bar(); -(ref9 = _obj) === null || ref9 === void 0 ? void 0 : ref9.call(_obj);"# +(ref9 = _obj = foo === null || foo === void 0 ? void 0 : foo.bar()) === null || ref9 === void 0 ? void 0 : ref9.call(_obj);"# ); // general_unary_exec @@ -696,11 +696,11 @@ test!( const patch = PATCHES.get(ident)?.(); ", " - var ref; + var _obj, ref; const PATCHES = new Map(); const ident = \"foo\"; - var _obj = PATCHES.get(ident); - const patch = (ref = _obj) === null || ref === void 0 ? void 0 : ref.call(_obj); + const patch = (ref = _obj = PATCHES.get(ident)) === null || ref === void 0 ? void 0 : \ + ref.call(_obj); " ); @@ -718,11 +718,18 @@ test!( " function bug() { const arrowFn = (arg)=>{ - var ref; - var _object = this.object[arg]; - return (ref = _object) === null || ref === void 0 ? void 0 : ref.call(_object); + var _object, ref; + return (ref = (_object = this.object)[arg]) === null || ref === void 0 ? void 0 : \ + ref.call(_object); }; } bug(); " ); + +#[testing::fixture("tests/fixture/opt-chain/**/exec.js")] +fn exec(input: PathBuf) { + let src = read_to_string(&input).unwrap(); + + compare_stdout(Default::default(), |_| optional_chaining(), &src); +} diff --git a/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/1/exec.js b/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/1/exec.js new file mode 100644 index 00000000000..750c92aaab1 --- /dev/null +++ b/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/1/exec.js @@ -0,0 +1,7 @@ +const myVar = { + target: { + value: "ABC" + } +} + +console.log(myVar.target.value.toLowerCase?.()) \ No newline at end of file diff --git a/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/2/exec.js b/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/2/exec.js new file mode 100644 index 00000000000..599da052743 --- /dev/null +++ b/ecmascript/transforms/compat/tests/fixture/opt-chain/issue-2063/2/exec.js @@ -0,0 +1,5 @@ +const myVar = { + value: "ABC" +} + +console.log(myVar.value.toLowerCase?.()) \ No newline at end of file diff --git a/ecmascript/transforms/testing/Cargo.toml b/ecmascript/transforms/testing/Cargo.toml index 3b3dc8d7538..1181b63da40 100644 --- a/ecmascript/transforms/testing/Cargo.toml +++ b/ecmascript/transforms/testing/Cargo.toml @@ -6,12 +6,13 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecma_transforms_testing" repository = "https://github.com/swc-project/swc.git" -version = "0.26.1" +version = "0.26.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ansi_term = "0.12.1" +anyhow = "1" serde = "1" serde_json = "1" swc_common = {version = "0.11.0", path = "../../../common"} diff --git a/ecmascript/transforms/testing/src/lib.rs b/ecmascript/transforms/testing/src/lib.rs index df2c8a0b7e3..c5cfdbcb9c3 100644 --- a/ecmascript/transforms/testing/src/lib.rs +++ b/ecmascript/transforms/testing/src/lib.rs @@ -1,4 +1,5 @@ use ansi_term::Color; +use anyhow::{bail, Context, Error}; use serde::de::DeserializeOwned; use std::{ env, @@ -16,7 +17,7 @@ use swc_common::{ }; use swc_ecma_ast::{Pat, *}; use swc_ecma_codegen::Emitter; -use swc_ecma_parser::{error::Error, lexer::Lexer, Parser, StringInput, Syntax}; +use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; use swc_ecma_transforms_base::{ fixer, helpers::{inject_helpers, HELPERS}, @@ -93,7 +94,7 @@ impl<'a> Tester<'a> { op: F, ) -> Result where - F: FnOnce(&mut Parser>) -> Result, + F: FnOnce(&mut Parser>) -> Result, { let fm = self .cm @@ -234,7 +235,7 @@ impl VisitMut for RegeneratorHandler { } } -fn make_tr(_: &'static str, op: F, tester: &mut Tester<'_>) -> impl Fold +fn make_tr(op: F, tester: &mut Tester<'_>) -> impl Fold where F: FnOnce(&mut Tester<'_>) -> P, P: Fold, @@ -266,7 +267,7 @@ pub fn test_transform( println!("----- Actual -----"); - let tr = make_tr("actual", tr, tester); + let tr = make_tr(tr, tester); let actual = tester.apply_transform(tr, "input.js", syntax, input)?; match ::std::env::var("PRINT_HYGIENE") { @@ -348,13 +349,49 @@ macro_rules! test { }; } -pub fn exec_tr(test_name: &'static str, syntax: Syntax, tr: F, input: &str) +/// Execute `node` for `input` and ensure that it prints same output after +/// transformation. +pub fn compare_stdout(syntax: Syntax, tr: F, input: &str) where F: FnOnce(&mut Tester<'_>) -> P, P: Fold, { Tester::run(|tester| { - let tr = make_tr(test_name, tr, tester); + let tr = make_tr(tr, tester); + + let module = tester.apply_transform(tr, "input.js", syntax, input)?; + + let mut module = module + .fold_with(&mut hygiene::hygiene()) + .fold_with(&mut fixer::fixer(Some(&tester.comments))); + + let src_without_helpers = tester.print(&module, &tester.comments.clone()); + module = module.fold_with(&mut inject_helpers()); + + let transfomred_src = tester.print(&module, &tester.comments.clone()); + + println!( + "\t>>>>> Orig <<<<<\n{}\n\t>>>>> Code <<<<<\n{}", + input, src_without_helpers + ); + + let expected = stdout_of(&input).unwrap(); + let actual = stdout_of(&transfomred_src).unwrap(); + + assert_eq!(expected, actual); + + Ok(()) + }) +} + +/// Execute `jest` after transpiling `input` using `tr`. +pub fn exec_tr(test_name: &str, syntax: Syntax, tr: F, input: &str) +where + F: FnOnce(&mut Tester<'_>) -> P, + P: Fold, +{ + Tester::run(|tester| { + let tr = make_tr(tr, tester); let module = tester.apply_transform( tr, @@ -442,6 +479,24 @@ where }) } +fn stdout_of(code: &str) -> Result { + let actual_output = Command::new("node") + .arg("-e") + .arg(&code) + .output() + .context("failed to execute output of minifier")?; + + if !actual_output.status.success() { + bail!( + "failed to execute:\n{}\n{}", + String::from_utf8_lossy(&actual_output.stdout), + String::from_utf8_lossy(&actual_output.stderr) + ) + } + + Ok(String::from_utf8_lossy(&actual_output.stdout).to_string()) +} + /// Test transformation. #[macro_export] macro_rules! test_exec { diff --git a/node-swc/src/types.ts b/node-swc/src/types.ts index 86e7a970696..f1796639a65 100644 --- a/node-swc/src/types.ts +++ b/node-swc/src/types.ts @@ -1449,6 +1449,8 @@ export interface ExportDeclaration extends Node, HasSpan { export interface ImportDeclaration extends Node, HasSpan { type: "ImportDeclaration"; + typeOnly?: boolean; + specifiers: ImportSpecifier[]; source: StringLiteral; diff --git a/testing/Cargo.toml b/testing/Cargo.toml index db1e8eb0491..949548aa54e 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "testing" repository = "https://github.com/swc-project/swc.git" -version = "0.12.0" +version = "0.12.1" [dependencies] ansi_term = "0.12.1" diff --git a/testing/macros/Cargo.toml b/testing/macros/Cargo.toml index 41de2ca125b..7bf3fea61d5 100644 --- a/testing/macros/Cargo.toml +++ b/testing/macros/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "testing_macros" repository = "https://github.com/swc-project/swc.git" -version = "0.2.1" +version = "0.2.2" [lib] proc-macro = true diff --git a/testing/macros/src/fixture.rs b/testing/macros/src/fixture.rs index 70551649af1..5623b3119b5 100644 --- a/testing/macros/src/fixture.rs +++ b/testing/macros/src/fixture.rs @@ -159,6 +159,7 @@ pub fn expand(callee: &Ident, attr: Config) -> Result, Error> { }, { #[test] + #[inline(never)] #[ignore] fn test_ident() { callee(::std::path::PathBuf::from(path_str)); diff --git a/testing/src/lib.rs b/testing/src/lib.rs index 047145c6c14..0e2732279a5 100644 --- a/testing/src/lib.rs +++ b/testing/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(test)] - pub use self::output::{NormalizedOutput, StdErr, StdOut, TestOutput}; use difference::Changeset; use once_cell::sync::Lazy; diff --git a/tests/fixture/issue-2063/input/index.js b/tests/fixture/issue-2063/input/index.js new file mode 100644 index 00000000000..2c94933327e --- /dev/null +++ b/tests/fixture/issue-2063/input/index.js @@ -0,0 +1,7 @@ +const myVar = { + target: { + value: "ABC" + } +} + +console.log(myVar.target.value.toLowerCase?.()); \ No newline at end of file diff --git a/tests/fixture/issue-2063/output/index.js b/tests/fixture/issue-2063/output/index.js new file mode 100644 index 00000000000..a95d615614c --- /dev/null +++ b/tests/fixture/issue-2063/output/index.js @@ -0,0 +1,7 @@ +var _value, ref; +var myVar = { + target: { + value: "ABC" + } +}; +console.log((ref = (_value = myVar.target.value).toLowerCase) === null || ref === void 0 ? void 0 : ref.call(_value));