Optional chaining for typescript (#444)

Implement typescript 3.7's optional chaining
This commit is contained in:
강동윤 2019-11-15 14:34:48 +09:00 committed by GitHub
parent 96aa4796ea
commit 37b80dfd08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1205 additions and 54 deletions

View File

@ -9,8 +9,8 @@ use crate::{
prop::Prop,
stmt::BlockStmt,
typescript::{
TsAsExpr, TsConstAssertion, TsNonNullExpr, TsTypeAnn, TsTypeAssertion, TsTypeCastExpr,
TsTypeParamDecl, TsTypeParamInstantiation,
TsAsExpr, TsConstAssertion, TsNonNullExpr, TsOptChain, TsTypeAnn, TsTypeAssertion,
TsTypeCastExpr, TsTypeParamDecl, TsTypeParamInstantiation,
},
};
use serde::{self, Deserialize, Serialize};
@ -140,6 +140,9 @@ pub enum Expr {
#[tag("PrivateName")]
PrivateName(PrivateName),
#[tag("TsOptionalChainingExpression")]
TsOptChain(TsOptChain),
}
#[ast_node("ThisExpression")]

View File

@ -61,12 +61,13 @@ pub use self::{
TsImportEqualsDecl, TsIndexSignature, TsIndexedAccessType, TsInferType, TsInterfaceBody,
TsInterfaceDecl, TsIntersectionType, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType,
TsMappedType, TsMethodSignature, TsModuleBlock, TsModuleDecl, TsModuleName, TsModuleRef,
TsNamespaceBody, TsNamespaceDecl, TsNamespaceExportDecl, TsNonNullExpr, TsOptionalType,
TsParamProp, TsParamPropParam, TsParenthesizedType, TsPropertySignature, TsQualifiedName,
TsRestType, TsSignatureDecl, TsThisType, TsThisTypeOrIdent, TsTupleType, TsType,
TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion, TsTypeCastExpr, TsTypeElement, TsTypeLit,
TsTypeOperator, TsTypeOperatorOp, TsTypeParam, TsTypeParamDecl, TsTypeParamInstantiation,
TsTypePredicate, TsTypeQuery, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
TsNamespaceBody, TsNamespaceDecl, TsNamespaceExportDecl, TsNonNullExpr, TsOptChain,
TsOptionalType, TsParamProp, TsParamPropParam, TsParenthesizedType, TsPropertySignature,
TsQualifiedName, TsRestType, TsSignatureDecl, TsThisType, TsThisTypeOrIdent, TsTupleType,
TsType, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion, TsTypeCastExpr, TsTypeElement,
TsTypeLit, TsTypeOperator, TsTypeOperatorOp, TsTypeParam, TsTypeParamDecl,
TsTypeParamInstantiation, TsTypePredicate, TsTypeQuery, TsTypeRef,
TsUnionOrIntersectionType, TsUnionType,
},
};

View File

@ -827,3 +827,9 @@ pub struct TsConstAssertion {
pub span: Span,
pub expr: Box<Expr>,
}
#[ast_node("TsOptionalChainingExpression")]
pub struct TsOptChain {
pub span: Span,
pub expr: Box<Expr>,
}

View File

@ -479,6 +479,7 @@ impl<'a> Emitter<'a> {
Expr::TsTypeAssertion(ref n) => emit!(n),
Expr::TsConstAssertion(ref n) => emit!(n),
Expr::TsTypeCast(ref n) => emit!(n),
Expr::TsOptChain(ref n) => emit!(n),
}
}

View File

@ -16,6 +16,11 @@ impl<'a> Emitter<'a> {
unimplemented!("emit_ts_array_type")
}
#[emitter]
pub fn emit_ts_opt_chain(&mut self, n: &TsOptChain) -> Result {
unimplemented!("emit_ts_opt_chain")
}
#[emitter]
pub fn emit_ts_as_expr(&mut self, n: &TsAsExpr) -> Result {
unimplemented!("emit_ts_as_expr")

View File

@ -210,6 +210,7 @@ impl StartsWithAlphaNum for Expr {
// TODO
Expr::TsTypeCast(..) => true,
Expr::TsOptChain(ref e) => e.expr.starts_with_alpha_num(),
}
}
}

View File

@ -145,7 +145,7 @@ impl Fold for InjectSelf {
};
}
"trace" | "debug" | "info" | "warn" | "error" => return i,
"trace" | "debug" | "info" | "warn" | "error" | "macro_rules" | "wrap" => return i,
//TODO
"unimplemented" => return i,

View File

@ -55,24 +55,26 @@ impl IntoIterator for Char {
type Item = char;
type IntoIter = CharIter;
#[allow(unsafe_code)]
fn into_iter(self) -> Self::IntoIter {
// TODO: Check if this is correct
fn to_char(v: u8) -> char {
char::from_digit(v as _, 16).unwrap_or('0')
}
// // TODO: Check if this is correct
// fn to_char(v: u8) -> char {
// char::from_digit(v as _, 16).unwrap_or('0')
// }
CharIter(match char::from_u32(self.0) {
Some(c) => smallvec![c],
None => {
//
smallvec![
'\\',
'u',
to_char(((self.0 >> 24) & 0xff) as u8),
to_char(((self.0 >> 16) & 0xff) as u8),
to_char(((self.0 >> 8) & 0xff) as u8),
to_char((self.0 & 0xff) as u8)
]
smallvec![unsafe { char::from_u32_unchecked(self.0) }]
// TODO:
// smallvec![
// '\\',
// 'u',
// to_char(((self.0 >> 24) & 0xff) as u8),
// to_char(((self.0 >> 16) & 0xff) as u8),
// to_char(((self.0 >> 8) & 0xff) as u8),
// to_char((self.0 & 0xff) as u8)
// ]
}
})
}

View File

@ -814,6 +814,55 @@ impl<'a, I: Tokens> Parser<'a, I> {
}
}
let is_optional_chaining =
self.input.syntax().typescript() && is!('?') && peeked_is!('.') && eat!('?');
/// Wrap with optional chaining
macro_rules! wrap {
($e:expr) => {{
if is_optional_chaining {
Expr::TsOptChain(TsOptChain {
span: span!(self, start),
expr: Box::new($e),
})
} else {
$e
}
}};
}
// $obj[name()]
if (is_optional_chaining && is!('.') && peeked_is!('[') && eat!('.') && eat!('['))
|| eat!('[')
{
let prop = self.include_in_expr(true).parse_expr()?;
expect!(']');
return Ok((
Box::new(wrap!(Expr::Member(MemberExpr {
span: span!(self, start),
obj,
prop,
computed: true,
}))),
true,
));
}
if (is_optional_chaining && is!('.') && peeked_is!('(') && eat!('.'))
|| (!no_call && (is!('(')))
{
let args = self.parse_args(is_import(&obj))?;
return Ok((
Box::new(wrap!(Expr::Call(CallExpr {
span: span!(self, start),
callee: obj,
args,
type_args: None,
}))),
true,
));
}
// member expression
// $obj.name
if eat!('.') {
@ -822,41 +871,13 @@ impl<'a, I: Tokens> Parser<'a, I> {
Either::Right(i) => Expr::Ident(i),
})?);
return Ok((
Box::new(Expr::Member(MemberExpr {
span: span!(start),
Box::new(wrap!(Expr::Member(MemberExpr {
span: span!(self, start),
obj,
prop,
computed: false,
})),
true,
));
}
// $obj[name()]
if eat!('[') {
let prop = self.include_in_expr(true).parse_expr()?;
expect!(']');
return Ok((
Box::new(Expr::Member(MemberExpr {
span: span!(start),
obj,
prop,
computed: true,
})),
true,
));
}
if !no_call && is!('(') {
let args = self.parse_args(is_import(&obj))?;
return Ok((
Box::new(Expr::Call(CallExpr {
span: span!(start),
callee: obj,
args,
type_args: None,
})),
}))),
true,
));
}

View File

@ -214,7 +214,8 @@ pub(super) trait ExprExt {
| Expr::JSXFragment(..) => false,
// typescript
Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
Expr::TsOptChain(TsOptChain { ref expr, .. })
| Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. })
| Expr::TsAs(TsAsExpr { ref expr, .. }) => {

View File

@ -214,3 +214,20 @@ impl Fold<PatOrExpr> for Normalizer {
}
}
}
impl Fold<PropName> for Normalizer {
fn fold(&mut self, node: PropName) -> PropName {
let node = node.fold_children(self);
match node {
PropName::Computed(box Expr::Lit(ref l)) => match l {
Lit::Str(s) => s.clone().into(),
Lit::Num(v) => v.clone().into(),
_ => return node,
},
_ => return node,
}
}
}

View File

@ -0,0 +1 @@
example.inner?.greet<string>()

View File

@ -0,0 +1,194 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 30,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 30
}
}
},
"body": [
{
"type": "CallExpression",
"span": {
"start": 0,
"end": 30,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 30
}
}
},
"callee": {
"type": "TsOptionalChainingExpression",
"span": {
"start": 13,
"end": 20,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 20
}
}
},
"expr": {
"type": "MemberExpression",
"span": {
"start": 13,
"end": 20,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 20
}
}
},
"object": {
"type": "MemberExpression",
"span": {
"start": 7,
"end": 13,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 13
}
}
},
"object": {
"type": "Identifier",
"span": {
"start": 0,
"end": 7,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 7
}
}
},
"value": "example",
"optional": false
},
"property": {
"type": "Identifier",
"span": {
"start": 8,
"end": 13,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 13
}
}
},
"value": "inner",
"optional": false
},
"computed": false
},
"property": {
"type": "Identifier",
"span": {
"start": 15,
"end": 20,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 20
}
}
},
"value": "greet",
"optional": false
},
"computed": false
}
},
"arguments": [],
"typeArguments": {
"type": "TsTypeParameterInstantiation",
"span": {
"start": 20,
"end": 28,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 20
},
"end": {
"line": 1,
"column": 28
}
}
},
"params": [
{
"type": "TsKeywordType",
"span": {
"start": 21,
"end": 27,
"ctxt": 0,
"loc": {
"start": {
"line": 1,
"column": 21
},
"end": {
"line": 1,
"column": 27
}
}
},
"kind": "string"
}
]
}
}
]
}

View File

@ -799,6 +799,7 @@ fn can_be_null(e: &Expr) -> bool {
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { ref expr, .. }) => can_be_null(expr),
Expr::TsOptChain(ref e) => can_be_null(&e.expr),
}
}

View File

@ -1016,6 +1016,7 @@ where
| Expr::TsTypeCast(TsTypeCastExpr { expr, .. })
| Expr::TsAs(TsAsExpr { expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { expr, .. }) => add_effects(v, expr),
Expr::TsOptChain(e) => add_effects(v, e.expr),
}
}

View File

@ -1,3 +1,4 @@
pub use self::opt_chaining::optional_chaining;
use crate::{
pass::Pass,
util::{prepend_stmts, var::VarCollector, ExprFactory},
@ -9,6 +10,7 @@ use swc_common::{
util::move_map::MoveMap, Fold, FoldWith, Spanned, SyntaxContext, Visit, VisitWith, DUMMY_SP,
};
mod opt_chaining;
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,355 @@
use crate::{pass::Pass, util::ExprFactory};
use ast::*;
use std::{fmt::Debug, iter::once, mem};
use swc_common::{Fold, FoldWith, Spanned, DUMMY_SP};
use util::{prepend, undefined, StmtLike};
#[cfg(test)]
mod tests;
pub fn optional_chaining() -> impl Pass {
OptChaining::default()
}
#[derive(Debug, Default)]
struct OptChaining {
vars: Vec<VarDeclarator>,
}
impl<T> Fold<Vec<T>> for OptChaining
where
T: Debug + StmtLike + FoldWith<Self>,
{
fn fold(&mut self, stmts: Vec<T>) -> Vec<T> {
// This is to support nested block statements
let old = mem::replace(&mut self.vars, vec![]);
let mut stmts = stmts.fold_children(self);
if !self.vars.is_empty() {
prepend(
&mut stmts,
T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
declare: false,
kind: VarDeclKind::Var,
decls: mem::replace(&mut self.vars, vec![]),
}))),
);
}
self.vars = old;
stmts
}
}
impl Fold<Expr> for OptChaining {
fn fold(&mut self, e: Expr) -> Expr {
let e = match e {
Expr::TsOptChain(e) => Expr::Cond(self.unwrap(e)),
Expr::Unary(e) => self.handle_unary(e),
Expr::Member(e) => self.handle_member(e),
Expr::Call(e) => self.handle_call(e),
_ => e,
};
e.fold_children(self)
}
}
impl OptChaining {
/// Only called from [Fold<Expr>].
fn handle_unary(&mut self, e: UnaryExpr) -> Expr {
let span = e.span;
match e.op {
op!("delete") => match *e.arg {
Expr::TsOptChain(o) => {
let expr = self.unwrap(o);
return CondExpr {
span,
alt: box Expr::Unary(UnaryExpr {
span,
op: op!("delete"),
arg: expr.alt,
}),
..expr
}
.into();
}
Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
prop,
computed,
}) => {
let expr = self.unwrap(o);
return CondExpr {
span,
alt: box Expr::Unary(UnaryExpr {
span,
op: op!("delete"),
arg: box Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(expr.alt),
prop,
computed,
}),
}),
..expr
}
.into();
}
_ => {}
},
_ => {}
}
Expr::Unary(e)
}
/// Only called from [Fold<Expr>].
fn handle_call(&mut self, e: CallExpr) -> Expr {
match e.callee {
ExprOrSuper::Expr(box Expr::TsOptChain(o)) => {
let expr = self.unwrap(o);
return CondExpr {
alt: box Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(expr.alt),
..e
}),
..expr
}
.into();
}
_ => {}
}
Expr::Call(e)
}
/// Only called from `[Fold<Expr>].
fn handle_member(&mut self, e: MemberExpr) -> Expr {
match e.obj {
ExprOrSuper::Expr(box Expr::TsOptChain(o)) => {
let expr = self.unwrap(o);
return CondExpr {
alt: box Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(expr.alt),
..e
}),
..expr
}
.into();
}
_ => {}
}
Expr::Member(e)
}
fn unwrap(&mut self, e: TsOptChain) -> CondExpr {
let span = e.span;
let cons = undefined(span);
match *e.expr {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
prop,
computed,
span: m_span,
}) => {
let o_span = o.span;
let obj = self.unwrap(o);
let alt = box Expr::Member(MemberExpr {
span: m_span,
obj: ExprOrSuper::Expr(obj.alt),
prop,
computed,
});
let alt = box Expr::TsOptChain(TsOptChain {
span: o_span,
expr: alt,
});
return CondExpr { alt, ..obj };
}
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
args,
type_args,
}) => {
let obj = self.unwrap(o);
let alt = box Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(obj.alt),
args,
type_args,
});
let alt = box Expr::TsOptChain(TsOptChain { span, expr: alt });
return CondExpr { alt, ..obj };
}
_ => {}
}
match *e.expr.clone() {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box obj),
prop,
computed,
span: m_span,
}) => {
let obj_span = obj.span();
let (left, right, alt) = match obj {
Expr::Ident(..) => (box obj.clone(), box obj.clone(), e.expr),
_ => {
let i = private_ident!(obj_span, "ref");
self.vars.push(VarDeclarator {
span: obj_span,
definite: false,
name: Pat::Ident(i.clone()),
init: None,
});
(
box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(i.clone())),
op: op!("="),
right: box obj,
}),
box Expr::Ident(i.clone()),
box Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box Expr::Ident(i.clone())),
computed,
span,
prop,
}),
)
}
};
let test = box Expr::Bin(BinExpr {
span,
left: box Expr::Bin(BinExpr {
span: obj_span,
left,
op: op!("==="),
right: box Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
}),
op: op!("||"),
right: box Expr::Bin(BinExpr {
span: obj_span,
left: right,
op: op!("==="),
right: undefined(span),
}),
});
CondExpr {
span,
test,
cons,
alt,
}
}
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(box obj),
args,
type_args,
..
}) => {
let obj_span = obj.span();
let is_super_access = match obj {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Super(..),
..
}) => true,
_ => false,
};
let (left, right, alt) = match obj {
Expr::Ident(..) => (box obj.clone(), box obj.clone(), e.expr),
_ => {
let i = private_ident!(obj_span, "ref");
self.vars.push(VarDeclarator {
span: obj_span,
definite: false,
name: Pat::Ident(i.clone()),
init: None,
});
(
box Expr::Assign(AssignExpr {
span: DUMMY_SP,
left: PatOrExpr::Pat(box Pat::Ident(i.clone())),
op: op!("="),
right: box obj,
}),
box Expr::Ident(i.clone()),
box Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(box Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(box Expr::Ident(i.clone())),
prop: box Expr::Ident(Ident::new("call".into(), span)),
computed: false,
})),
// TODO;
args: once(if is_super_access {
ThisExpr { span }.as_arg()
} else {
i.clone().as_arg()
})
.chain(args)
.collect(),
type_args,
}),
)
}
};
let test = box Expr::Bin(BinExpr {
span,
left: box Expr::Bin(BinExpr {
span: obj_span,
left,
op: op!("==="),
right: box Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
}),
op: op!("||"),
right: box Expr::Bin(BinExpr {
span: obj_span,
left: right,
op: op!("==="),
right: undefined(span),
}),
});
CondExpr {
span,
test,
cons,
alt,
}
}
_ => unreachable!("TsOptChain.expr = {:?}", e.expr),
}
}
}

View File

@ -0,0 +1,538 @@
use super::*;
use swc_ecma_parser::{Syntax, TsConfig};
fn tr(_: ()) -> impl Pass {
optional_chaining()
}
fn syntax() -> Syntax {
Syntax::Typescript(TsConfig {
..Default::default()
})
}
// general_memoize_loose
// general_lhs_assignment_read_and_update
// general_function_call_loose
// regression_7642
// general_super_method_call_loose
// general_lhs_update
// general_assignment
test!(
syntax(),
|_| tr(Default::default()),
general_assignment,
r#"
"use strict";
const obj = {
a: {
b: {
c: {
d: 2,
},
},
},
};
const a = obj?.a;
const b = obj?.a?.b;
const bad = obj?.b?.b;
let val;
val = obj?.a?.b;
"#,
r#"
"use strict";
var ref, ref1, ref2;
const obj = {
a: {
b: {
c: {
d: 2
}
}
}
};
const a = obj === null || obj === void 0 ? void 0 : obj.a;
const b = obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : ref.b;
const bad = obj === null || obj === void 0 ? void 0 : (ref1 = obj.b) === null || ref1 === void 0 ? void 0 : ref1.b;
let val;
val = obj === null || obj === void 0 ? void 0 : (ref2 = obj.a) === null || ref2 === void 0 ? void 0 : ref2.b;
"#
);
// general_memoize
test!(
syntax(),
|_| tr(Default::default()),
general_memoize,
r#"
function test(foo) {
foo?.bar;
foo?.bar?.baz;
foo?.(foo);
foo?.bar()
foo.bar?.(foo.bar, false)
foo?.bar?.(foo.bar, true)
foo.bar?.baz(foo.bar, false)
foo?.bar?.baz(foo.bar, true)
foo.bar?.baz?.(foo.bar, false)
foo?.bar?.baz?.(foo.bar, true)
}
"#,
r#"
function test(foo) {
var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8;
foo === null || foo === void 0 ? void 0 : foo.bar;
foo === null || foo === void 0 ? void 0 : (ref = foo.bar) === null || ref === void 0 ? void 0 : ref.baz;
foo === null || foo === void 0 ? void 0 : foo(foo);
foo === null || foo === void 0 ? void 0 : foo.bar();
(ref1 = foo.bar) === null || ref1 === void 0 ? void 0 : ref1.call(ref1, foo.bar, false);
foo === null || foo === void 0 ? void 0 : (ref2 = foo.bar) === null || ref2 === void 0 ? void 0 : ref2.call(ref2, foo.bar, true);
(ref3 = foo.bar) === null || ref3 === void 0 ? void 0 : ref3.baz(foo.bar, false);
foo === null || foo === void 0 ? void 0 : (ref4 = foo.bar) === null || ref4 === void 0 ? void 0 : ref4.baz(foo.bar, true);
(ref5 = foo.bar) === null || ref5 === void 0 ? void 0 : (ref6 = ref5.baz) === null || ref6 === void 0 ? void 0 : ref6.call(ref6, foo.bar, false);
foo === null || foo === void 0 ? void 0 : (ref7 = foo.bar) === null || ref7 === void 0 ? void 0 : (ref8 = ref7.baz) === null || ref8 === void 0 ? void 0 : ref8.call(ref8, foo.bar, true);
}
"#
);
// general_containers
test!(
syntax(),
|_| tr(Default::default()),
general_containers,
r#"
var street = user.address?.street
street = user.address?.street
test(a?.b, 1);
(a?.b, 2);
"#,
r#"
var ref, ref1;
var street = (ref = user.address) === null || ref === void 0 ? void 0 : ref.street;
street = (ref1 = user.address) === null || ref1 === void 0 ? void 0 : ref1.street;
test(a === null || a === void 0 ? void 0 : a.b, 1);
a === null || a === void 0 ? void 0 : a.b, 2;
"#
);
// general_function_call_spread
// general_lhs_assignment
// general_delete_exec
test_exec!(
syntax(),
|_| tr(Default::default()),
general_delete_exec,
r#"
"use strict";
const obj = {
a: {
b: 0,
},
};
let test = delete obj?.a?.b;
expect(obj.a.b).toBeUndefined();
expect(test).toBe(true);
test = delete obj?.a.b;
expect(obj.a.b).toBeUndefined();
expect(test).toBe(true);
test = delete obj?.b?.b;
expect(obj.b).toBeUndefined();
expect(test).toBeUndefined();
delete obj?.a;
expect(obj.a).toBeUndefined();
"#
);
// general_member_access
test!(
syntax(),
|_| tr(Default::default()),
general_member_access,
r#"
foo?.bar;
a?.b.c?.d.e;
a.b?.c.d?.e;
a.b.c?.d?.e;
orders?.[0].price;
orders?.[0]?.price;
orders[client?.key].price;
orders[client.key]?.price;
(0, a?.b).c;
(0, (0, a?.b).c?.d).e;
"#,
r#"
var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7;
foo === null || foo === void 0 ? void 0 : foo.bar;
(ref = a === null || a === void 0 ? void 0 : a.b.c) === null || ref === void 0 ? void 0 : ref.d.e;
(ref1 = (ref2 = a.b) === null || ref2 === void 0 ? void 0 : ref2.c.d) === null || ref1 === void 0 ? void 0 : ref1.e;
(ref3 = a.b.c) === null || ref3 === void 0 ? void 0 : (ref4 = ref3.d) === null || ref4 === void 0 ? void 0 : ref4.e;
orders === null || orders === void 0 ? void 0 : orders[0].price;
orders === null || orders === void 0 ? void 0 : (ref5 = orders[0]) === null || ref5 === void 0 ? void 0 : ref5.price;
orders[client === null || client === void 0 ? void 0 : client.key].price;
(ref6 = orders[client.key]) === null || ref6 === void 0 ? void 0 : ref6.price;
(a === null || a === void 0 ? void 0 : a.b).c;
((ref7 = (a === null || a === void 0 ? void 0 : a.b).c) === null || ref7 === void 0 ? void 0 : ref7.d).e;
"#
);
// general_unary
test!(
syntax(),
|_| tr(Default::default()),
general_unary,
r#"
"use strict";
const obj = {
a: {
b: 0,
},
};
let test = +obj?.a?.b;
test = +obj?.a.b;
test = +obj?.b?.b;
test = +obj?.b?.b;
"#,
r#"
"use strict";
var ref, ref1, ref2;
const obj = {
a: {
b: 0
}
};
let test = +(obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : ref.b);
test = +(obj === null || obj === void 0 ? void 0 : obj.a.b);
test = +(obj === null || obj === void 0 ? void 0 : (ref1 = obj.b) === null || ref1 === void 0 ? void 0 : ref1.b);
test = +(obj === null || obj === void 0 ? void 0 : (ref2 = obj.b) === null || ref2 === void 0 ? void 0 : ref2.b);
"#
);
// regression_8354
// general_function_call
test!(
syntax(),
|_| tr(Default::default()),
general_function_call,
r#"
foo?.(foo);
foo?.bar()
foo.bar?.(foo.bar, false)
foo?.bar?.(foo.bar, true)
foo?.().bar
foo?.()?.bar
foo.bar?.().baz
foo.bar?.()?.baz
foo?.bar?.().baz
foo?.bar?.()?.baz
"#,
r#"
var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8;
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(ref, foo.bar, false);
foo === null || foo === void 0 ? void 0 : (ref1 = foo.bar) === null || ref1 === void 0 ? void 0 : ref1.call(ref1, foo.bar, true);
foo === null || foo === void 0 ? void 0 : foo().bar;
foo === null || foo === void 0 ? void 0 : (ref2 = foo()) === null || ref2 === void 0 ? void 0 : ref2.bar;
(ref3 = foo.bar) === null || ref3 === void 0 ? void 0 : ref3.call(ref3).baz;
(ref4 = foo.bar) === null || ref4 === void 0 ? void 0 : (ref5 = ref4.call(ref4)) === 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(ref6).baz;
foo === null || foo === void 0 ? void 0 : (ref7 = foo.bar) === null || ref7 === void 0 ? void 0 : (ref8 = ref7.call(ref7)) === null || ref8 === void 0 ? void 0 : ref8.baz;
"#
);
// general_unary_exec
test_exec!(
syntax(),
|_| tr(Default::default()),
general_unary_exec,
r#"
"use strict";
const obj = {
a: {
b: 0,
},
};
let test = +obj?.a?.b;
expect(test).toBe(0);
test = +obj?.a.b;
expect(test).toBe(0);
test = +obj?.b?.b;
expect(test).toBe(NaN);
test = +obj?.b?.b;
expect(test).toBe(NaN);
"#
);
// general_call_exec
test_exec!(
syntax(),
|_| tr(Default::default()),
general_call_exec,
r#"
"#
);
// general_delete
test!(
syntax(),
|_| tr(Default::default()),
general_delete,
r#"
"use strict";
const obj = {
a: {
b: 0,
},
};
let test = delete obj?.a?.b;
test = delete obj?.a.b;
test = delete obj?.b?.b;
delete obj?.a;
"#,
r#"
"use strict";
var ref, ref1;
const obj = {
a: {
b: 0
}
};
let test = obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : delete ref.b;
test = obj === null || obj === void 0 ? void 0 : delete obj.a.b;
test = obj === null || obj === void 0 ? void 0 : (ref1 = obj.b) === null || ref1 === void 0 ? void 0 : delete ref1.b;
obj === null || obj === void 0 ? void 0 : delete obj.a;
"#
);
// regression_8354_exec
test_exec!(
syntax(),
|_| tr(Default::default()),
regression_8354_exec,
r#"
const foo = undefined;
const bar = 'bar';
const foobar = foo?.replace(`foo${bar}`, '');
expect(foobar).toBe(undefined);
"#
);
// general_assignment_exec
test_exec!(
syntax(),
|_| tr(Default::default()),
general_assignment_exec,
r#"
"use strict";
const obj = {
a: {
b: {
c: {
d: 2,
},
},
},
};
const a = obj?.a;
expect(a).toBe(obj.a);
const b = obj?.a?.b;
expect(b).toBe(obj.a.b);
const bad = obj?.b?.b;
expect(bad).toBeUndefined();
let val;
val = obj?.a?.b;
expect(val).toBe(obj.a.b);
expect(() => {
const bad = obj?.b.b;
}).toThrow();
"#
);
// general_super_method_call
test!(
syntax(),
|_| tr(Default::default()),
general_super_method_call,
r#"
"use strict";
class Base {
method() {
return 'Hello!';
}
}
class Derived extends Base {
method() {
return super.method?.()
}
}
"#,
r#"
"use strict";
class Base {
method() {
return 'Hello!';
}
}
class Derived extends Base {
method() {
var ref;
return (ref = super.method) === null || ref === void 0 ? void 0 : ref.call(this);
}
}
"#
);
test!(
syntax(),
|_| tr(Default::default()),
simple_1,
"obj?.a",
"obj === null || obj === void 0 ? void 0 : obj.a;"
);
test!(
syntax(),
|_| tr(Default::default()),
simple_2,
"obj?.a?.b",
"var ref;
obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : \
ref.b;"
);
test!(
syntax(),
|_| tr(Default::default()),
simple_3,
"obj?.a?.b.c",
"var ref;
obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : \
ref.b.c;"
);
test!(
syntax(),
|_| tr(Default::default()),
call_1,
"obj?.a?.b()",
"var ref;
obj === null || obj === void 0 ? void 0 : (ref = obj.a) === null || ref === void 0 ? void 0 : \
ref.b();",
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
call_2,
"a?.b?.c?.()",
"var ref, ref1;
a === null || a === void 0
? void 0
: (ref = a.b) === null || ref === void 0
? void 0
: (ref1 = ref.c) === null || ref1 === void 0
? void 0
: ref1.call(ref1);"
);

View File

@ -651,6 +651,7 @@ pub trait ExprExt {
| Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. }) => expr.may_have_side_effects(),
Expr::TsOptChain(ref e) => e.expr.may_have_side_effects(),
}
}
}