mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 14:16:12 +03:00
Optional chaining for typescript (#444)
Implement typescript 3.7's optional chaining
This commit is contained in:
parent
96aa4796ea
commit
37b80dfd08
@ -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")]
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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>,
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -210,6 +210,7 @@ impl StartsWithAlphaNum for Expr {
|
||||
|
||||
// TODO
|
||||
Expr::TsTypeCast(..) => true,
|
||||
Expr::TsOptChain(ref e) => e.expr.starts_with_alpha_num(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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)
|
||||
// ]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
@ -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, .. }) => {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
example.inner?.greet<string>()
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
355
ecmascript/transforms/src/typescript/opt_chaining/mod.rs
Normal file
355
ecmascript/transforms/src/typescript/opt_chaining/mod.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
538
ecmascript/transforms/src/typescript/opt_chaining/tests.rs
Normal file
538
ecmascript/transforms/src/typescript/opt_chaining/tests.rs
Normal 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);"
|
||||
);
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user