Nullish coalescing / optional chaining / comments (#529)

swc_ecma_ast:
 - rename `TsOptChain` to `OptChainExpr` (Fixes #525)
 - add `BinOp::NullishCoalescing`

swc_ecma_parser:
 - parse `??` (Fixes #526)

swc_ecma_transforms:
 - remap comments from fixer (Fixes #528)
This commit is contained in:
강동윤 2019-12-24 17:01:32 +09:00 committed by GitHub
parent d8541c4f63
commit b1e4122b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 954 additions and 144 deletions

View File

@ -133,6 +133,18 @@ New generation javascript to old-days javascript.
- [x] optional-catch-binding - [x] optional-catch-binding
- [ ] unicode-property-regex - [ ] unicode-property-regex
- es2019 (stage 4)
- [x] optional catch binding
- [x] big int literal
- stage 3
- [x] class fields
- [x] numeric separator
- [ ] nullish coalescing
- stage 2
- [x] decorators
- react - react
- [x] jsx - [x] jsx

View File

@ -31,6 +31,34 @@ impl Comments {
pub fn leading_comments(&self, pos: BytePos) -> Option<ReadGuard<'_, BytePos, Vec<Comment>>> { pub fn leading_comments(&self, pos: BytePos) -> Option<ReadGuard<'_, BytePos, Vec<Comment>>> {
self.leading.get(&pos) self.leading.get(&pos)
} }
pub fn move_leading(&self, from: BytePos, to: BytePos) {
let cmt = self.leading.remove(&from);
if let Some(cmt) = cmt {
self.leading.alter(to, |v| match v {
Some(mut value) => {
value.extend(cmt);
Some(value)
}
None => Some(cmt),
});
}
}
pub fn move_trailing(&self, from: BytePos, to: BytePos) {
let cmt = self.trailing.remove(&from);
if let Some(cmt) = cmt {
self.trailing.alter(to, |v| match v {
Some(mut value) => {
value.extend(cmt);
Some(value)
}
None => Some(cmt),
});
}
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "swc_ecma_ast" name = "swc_ecma_ast"
version = "0.12.1" version = "0.13.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"] authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"

View File

@ -10,8 +10,8 @@ use crate::{
prop::Prop, prop::Prop,
stmt::BlockStmt, stmt::BlockStmt,
typescript::{ typescript::{
TsAsExpr, TsConstAssertion, TsNonNullExpr, TsOptChain, TsTypeAnn, TsTypeAssertion, TsAsExpr, TsConstAssertion, TsNonNullExpr, TsTypeAnn, TsTypeAssertion, TsTypeCastExpr,
TsTypeCastExpr, TsTypeParamDecl, TsTypeParamInstantiation, TsTypeParamDecl, TsTypeParamInstantiation,
}, },
Invalid, Invalid,
}; };
@ -144,8 +144,8 @@ pub enum Expr {
#[tag("PrivateName")] #[tag("PrivateName")]
PrivateName(PrivateName), PrivateName(PrivateName),
#[tag("TsOptionalChainingExpression")] #[tag("OptionalChainingExpression")]
TsOptChain(TsOptChain), OptChain(OptChainExpr),
#[tag("Invalid")] #[tag("Invalid")]
Invalid(Invalid), Invalid(Invalid),
@ -544,6 +544,12 @@ impl From<Str> for Expr {
} }
} }
#[ast_node("OptionalChainingExpression")]
pub struct OptChainExpr {
pub span: Span,
pub expr: Box<Expr>,
}
test_de!( test_de!(
jsx_element, jsx_element,
JSXElement, JSXElement,

View File

@ -15,8 +15,8 @@ pub use self::{
expr::{ expr::{
ArrayLit, ArrowExpr, AssignExpr, AwaitExpr, BinExpr, BlockStmtOrExpr, CallExpr, ClassExpr, ArrayLit, ArrowExpr, AssignExpr, AwaitExpr, BinExpr, BlockStmtOrExpr, CallExpr, ClassExpr,
CondExpr, Expr, ExprOrSpread, ExprOrSuper, FnExpr, MemberExpr, MetaPropExpr, NewExpr, CondExpr, Expr, ExprOrSpread, ExprOrSuper, FnExpr, MemberExpr, MetaPropExpr, NewExpr,
ObjectLit, ParenExpr, PatOrExpr, PropOrSpread, SeqExpr, SpreadElement, Super, TaggedTpl, ObjectLit, OptChainExpr, ParenExpr, PatOrExpr, PropOrSpread, SeqExpr, SpreadElement, Super,
ThisExpr, Tpl, TplElement, UnaryExpr, UpdateExpr, YieldExpr, TaggedTpl, ThisExpr, Tpl, TplElement, UnaryExpr, UpdateExpr, YieldExpr,
}, },
function::{Function, PatOrTsParamProp}, function::{Function, PatOrTsParamProp},
ident::{Ident, IdentExt, PrivateName}, ident::{Ident, IdentExt, PrivateName},
@ -56,12 +56,12 @@ pub use self::{
TsInterfaceBody, TsInterfaceDecl, TsIntersectionType, TsKeywordType, TsKeywordTypeKind, TsInterfaceBody, TsInterfaceDecl, TsIntersectionType, TsKeywordType, TsKeywordTypeKind,
TsLit, TsLitType, TsMappedType, TsMethodSignature, TsModuleBlock, TsModuleDecl, TsLit, TsLitType, TsMappedType, TsMethodSignature, TsModuleBlock, TsModuleDecl,
TsModuleName, TsModuleRef, TsNamespaceBody, TsNamespaceDecl, TsNamespaceExportDecl, TsModuleName, TsModuleRef, TsNamespaceBody, TsNamespaceDecl, TsNamespaceExportDecl,
TsNonNullExpr, TsOptChain, TsOptionalType, TsParamProp, TsParamPropParam, TsNonNullExpr, TsOptionalType, TsParamProp, TsParamPropParam, TsParenthesizedType,
TsParenthesizedType, TsPropertySignature, TsQualifiedName, TsRestType, TsSignatureDecl, TsPropertySignature, TsQualifiedName, TsRestType, TsSignatureDecl, TsThisType,
TsThisType, TsThisTypeOrIdent, TsTupleType, TsType, TsTypeAliasDecl, TsTypeAnn, TsThisTypeOrIdent, TsTupleType, TsType, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion,
TsTypeAssertion, TsTypeCastExpr, TsTypeElement, TsTypeLit, TsTypeOperator, TsTypeCastExpr, TsTypeElement, TsTypeLit, TsTypeOperator, TsTypeOperatorOp, TsTypeParam,
TsTypeOperatorOp, TsTypeParam, TsTypeParamDecl, TsTypeParamInstantiation, TsTypePredicate, TsTypeParamDecl, TsTypeParamInstantiation, TsTypePredicate, TsTypeQuery, TsTypeQueryExpr,
TsTypeQuery, TsTypeQueryExpr, TsTypeRef, TsUnionOrIntersectionType, TsUnionType, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
}, },
}; };
use swc_common::{ast_node, Span}; use swc_common::{ast_node, Span};

View File

@ -106,6 +106,9 @@ macro_rules! op {
("**") => { ("**") => {
$crate::BinaryOp::Exp $crate::BinaryOp::Exp
}; };
("??") => {
$crate::BinaryOp::NullishCoalescing
};
("=") => { ("=") => {
$crate::AssignOp::Assign $crate::AssignOp::Assign

View File

@ -85,6 +85,10 @@ pub enum BinaryOp {
/// `**` /// `**`
#[kind(precedence = "11")] #[kind(precedence = "11")]
Exp, Exp,
/// `??`
#[kind(precedence = "1")]
NullishCoalescing,
} }
#[derive(StringEnum, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] #[derive(StringEnum, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]

View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "swc_ecma_codegen" name = "swc_ecma_codegen"
version = "0.10.0" version = "0.11.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"] authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"
@ -13,11 +13,11 @@ bitflags = "1"
hashbrown = "0.6" hashbrown = "0.6"
swc_atoms = { version = "0.2", path ="../../atoms" } swc_atoms = { version = "0.2", path ="../../atoms" }
swc_common = { version = "0.4.0", path ="../../common" } swc_common = { version = "0.4.0", path ="../../common" }
swc_ecma_ast = { version = "0.12.0", path ="../ast" } swc_ecma_ast = { version = "0.13.0", path ="../ast" }
swc_ecma_codegen_macros = { version = "0.4", path ="./macros" } swc_ecma_codegen_macros = { version = "0.4", path ="./macros" }
sourcemap = "4.1.1" sourcemap = "4.1.1"
num-bigint = { version = "0.2", features = ["serde"] } num-bigint = { version = "0.2", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
testing = { version = "0.4", path ="../../testing" } testing = { version = "0.4", path ="../../testing" }
swc_ecma_parser = { version = "0.14", path ="../parser" } swc_ecma_parser = { version = "0.15", path ="../parser" }

View File

@ -480,11 +480,40 @@ impl<'a> Emitter<'a> {
Expr::TsTypeAssertion(ref n) => emit!(n), Expr::TsTypeAssertion(ref n) => emit!(n),
Expr::TsConstAssertion(ref n) => emit!(n), Expr::TsConstAssertion(ref n) => emit!(n),
Expr::TsTypeCast(ref n) => emit!(n), Expr::TsTypeCast(ref n) => emit!(n),
Expr::TsOptChain(ref n) => emit!(n), Expr::OptChain(ref n) => emit!(n),
Expr::Invalid(..) => unimplemented!("emit Expr::Invalid"), Expr::Invalid(..) => unimplemented!("emit Expr::Invalid"),
} }
} }
#[emitter]
pub fn emit_opt_chain(&mut self, n: &OptChainExpr) -> Result {
self.emit_leading_comments_of_pos(n.span().lo())?;
match *n.expr {
Expr::Member(ref e) => {
emit!(e.obj);
self.wr.write_operator("?.")?;
if e.computed {
punct!("[");
emit!(e.prop);
punct!("]");
} else {
emit!(e.prop);
}
}
Expr::Call(ref e) => {
emit!(e.callee);
self.wr.write_operator("?.")?;
punct!("(");
self.emit_expr_or_spreads(n.span(), &e.args, ListFormat::CallExpressionArguments)?;
punct!(")");
}
_ => {}
}
}
#[emitter] #[emitter]
pub fn emit_call_expr(&mut self, node: &CallExpr) -> Result { pub fn emit_call_expr(&mut self, node: &CallExpr) -> Result {
self.emit_leading_comments_of_pos(node.span().lo())?; self.emit_leading_comments_of_pos(node.span().lo())?;

View File

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

View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "swc_ecma_parser" name = "swc_ecma_parser"
version = "0.14.0" version = "0.15.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"] authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"
@ -18,7 +18,7 @@ verify = ["fold"]
[dependencies] [dependencies]
swc_atoms = { version = "0.2", path ="../../atoms" } swc_atoms = { version = "0.2", path ="../../atoms" }
swc_common = { version = "0.4.0", path ="../../common" } swc_common = { version = "0.4.0", path ="../../common" }
swc_ecma_ast = { version = "0.12.0", path ="../ast" } swc_ecma_ast = { version = "0.13.0", path ="../ast" }
swc_ecma_parser_macros = { package = "swc_ecma_parser_macros", version = "0.4", path ="./macros" } swc_ecma_parser_macros = { package = "swc_ecma_parser_macros", version = "0.4", path ="./macros" }
enum_kind = { version = "0.2", path ="../../macros/enum_kind" } enum_kind = { version = "0.2", path ="../../macros/enum_kind" }
unicode-xid = "0.2" unicode-xid = "0.2"

View File

@ -103,6 +103,9 @@ pub enum SyntaxError {
AwaitStar, AwaitStar,
ReservedWordInObjShorthandOrPat, ReservedWordInObjShorthandOrPat,
NullishCoalescingWithLogicalOp,
NullishCoalescingNotEnabled,
MultipleDefault { MultipleDefault {
/// Span of the previous default case /// Span of the previous default case
previous: Span, previous: Span,
@ -359,6 +362,13 @@ impl<'a> From<ErrorToDiag<'a>> for DiagnosticBuilder<'a> {
"A numeric separator is only allowed between two digits".into() "A numeric separator is only allowed between two digits".into()
} }
NullishCoalescingWithLogicalOp => "Nullish coalescing operator(??) requires parens \
when mixing with logical operators"
.into(),
NullishCoalescingNotEnabled => {
"Nullish coalescing operator(??) requires jsc.parser.Coalescing".into()
}
TS1056 => "jsc.taraget should be es5 or upper to use getter / setter".into(), TS1056 => "jsc.taraget should be es5 or upper to use getter / setter".into(),
TS1141 => "literal in an import type should be string literal".into(), TS1141 => "literal in an import type should be string literal".into(),

View File

@ -179,7 +179,7 @@ impl<'a, I: Input> Lexer<'a, I> {
return Ok(Some(tok!('.'))); return Ok(Some(tok!('.')));
} }
'(' | ')' | ';' | ',' | '[' | ']' | '{' | '}' | '@' | '?' => { '(' | ')' | ';' | ',' | '[' | ']' | '{' | '}' | '@' => {
// These tokens are emitted directly. // These tokens are emitted directly.
self.input.bump(); self.input.bump();
return Ok(Some(match c { return Ok(Some(match c {
@ -197,6 +197,18 @@ impl<'a, I: Input> Lexer<'a, I> {
})); }));
} }
'?' => match self.input.peek() {
Some('?') => {
self.input.bump();
self.input.bump();
return Ok(Some(tok!("??")));
}
_ => {
self.input.bump();
return Ok(Some(tok!('?')));
}
},
'`' => { '`' => {
self.bump(); self.bump();
return Ok(Some(tok!('`'))); return Ok(Some(tok!('`')));

View File

@ -247,6 +247,18 @@ impl Syntax {
_ => false, _ => false,
} }
} }
pub fn nullish_coalescing(self) -> bool {
match self {
Syntax::Es(EsConfig {
nullish_coalescing: true,
..
})
| Syntax::Typescript(..) => true,
_ => false,
}
}
} }
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
@ -292,6 +304,7 @@ pub struct EsConfig {
#[serde(default)] #[serde(default)]
pub jsx: bool, pub jsx: bool,
/// Support numeric separator. /// Support numeric separator.
/// Stage 3.
#[serde(rename = "numericSeparator")] #[serde(rename = "numericSeparator")]
#[serde(default)] #[serde(default)]
pub num_sep: bool, pub num_sep: bool,
@ -332,6 +345,10 @@ pub struct EsConfig {
#[serde(default)] #[serde(default)]
pub dynamic_import: bool, pub dynamic_import: bool,
/// Stage 3.
#[serde(default)]
pub nullish_coalescing: bool,
} }
/// Syntactic context. /// Syntactic context.

View File

@ -19,6 +19,9 @@ macro_rules! tok {
('-') => { ('-') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Sub) crate::token::Token::BinOp(crate::token::BinOpToken::Sub)
}; };
("??") => {
crate::token::Token::BinOp(crate::token::BinOpToken::NullishCoalescing)
};
('~') => { ('~') => {
crate::token::Token::Tilde crate::token::Token::Tilde
}; };

View File

@ -869,7 +869,7 @@ impl<'a, I: Tokens> Parser<'a, I> {
macro_rules! wrap { macro_rules! wrap {
($e:expr) => {{ ($e:expr) => {{
if is_optional_chaining { if is_optional_chaining {
Expr::TsOptChain(TsOptChain { Expr::OptChain(OptChainExpr {
span: span!(self, start), span: span!(self, start),
expr: Box::new($e), expr: Box::new($e),
}) })

View File

@ -107,6 +107,10 @@ impl<'a, I: Tokens> Parser<'a, I> {
} }
}; };
if !self.syntax().nullish_coalescing() && op == op!("??") {
syntax_error!(left.span(), SyntaxError::NullishCoalescingNotEnabled)
}
if op.precedence() <= min_prec { if op.precedence() <= min_prec {
trace!( trace!(
"returning {:?} without parsing {:?} because min_prec={}, prec={}", "returning {:?} without parsing {:?} because min_prec={}, prec={}",
@ -154,6 +158,33 @@ impl<'a, I: Tokens> Parser<'a, I> {
}, },
)? )?
}; };
/* this check is for all ?? operators
* a ?? b && c for this example
* b && c => This is considered as a logical expression in the ast tree
* a => Identifier
* so for ?? operator we need to check in this case the right expression to
* have parenthesis second case a && b ?? c
* here a && b => This is considered as a logical expression in the ast tree
* c => identifier
* so now here for ?? operator we need to check the left expression to have
* parenthesis if the parenthesis is missing we raise an error and
* throw it
*/
if op == op!("??") {
match *left {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
syntax_error!(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
match *right {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
syntax_error!(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
}
let node = Box::new(Expr::Bin(BinExpr { let node = Box::new(Expr::Bin(BinExpr {
span: Span::new(left.span().lo(), right.span().hi(), Default::default()), span: Span::new(left.span().lo(), right.span().hi(), Default::default()),
@ -163,6 +194,16 @@ impl<'a, I: Tokens> Parser<'a, I> {
})); }));
let expr = self.parse_bin_op_recursively(node, min_prec)?; let expr = self.parse_bin_op_recursively(node, min_prec)?;
if op == op!("??") {
match *expr {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
syntax_error!(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
}
Ok(expr) Ok(expr)
} }

View File

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

View File

@ -203,6 +203,9 @@ pub enum BinOpToken {
LogicalOr, LogicalOr,
/// `&&` /// `&&`
LogicalAnd, LogicalAnd,
/// `??`
NullishCoalescing,
} }
impl BinOpToken { impl BinOpToken {
@ -539,6 +542,7 @@ impl From<BinOpToken> for BinaryOp {
BinOpToken::LogicalOr => LogicalOr, BinOpToken::LogicalOr => LogicalOr,
BinOpToken::LogicalAnd => LogicalAnd, BinOpToken::LogicalAnd => LogicalAnd,
BinOpToken::Exp => Exp, BinOpToken::Exp => Exp,
BinOpToken::NullishCoalescing => NullishCoalescing,
} }
} }
} }

View File

@ -0,0 +1,6 @@
error: Nullish coalescing operator(??) requires parens when mixing with logical operators
--> $DIR/tests/typescript-errors/nullish-coalescing-operator/no-paren-and-nullish/input.ts:1:1
|
1 | c && d ?? e;
| ^^^^^^

View File

@ -0,0 +1,6 @@
error: Nullish coalescing operator(??) requires parens when mixing with logical operators
--> $DIR/tests/typescript-errors/nullish-coalescing-operator/no-paren-nullish-and/input.ts:1:6
|
1 | a ?? b && c;
| ^^^^^^

View File

@ -0,0 +1,6 @@
error: Nullish coalescing operator(??) requires parens when mixing with logical operators
--> $DIR/tests/typescript-errors/nullish-coalescing-operator/no-paren-nullish-or/input.ts:1:1
|
1 | e ?? f ?? g || h;
| ^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
error: Nullish coalescing operator(??) requires parens when mixing with logical operators
--> $DIR/tests/typescript-errors/nullish-coalescing-operator/no-paren-or-nullish/input.ts:1:1
|
1 | h || i ?? j;
| ^^^^^^

View File

@ -0,0 +1,78 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 13,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "ParenthesisExpression",
"span": {
"start": 0,
"end": 8,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 1,
"end": 7,
"ctxt": 0
},
"operator": "&&",
"left": {
"type": "Identifier",
"span": {
"start": 1,
"end": 2,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 6,
"end": 7,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
}
}
},
"right": {
"type": "Identifier",
"span": {
"start": 12,
"end": 13,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1,70 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 12,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 12,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 11,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 6,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 1,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 5,
"end": 6,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
}
},
"right": {
"type": "Identifier",
"span": {
"start": 10,
"end": 11,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1,48 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 9,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 9,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 8,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"value": "foo",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "NumericLiteral",
"span": {
"start": 7,
"end": 8,
"ctxt": 0
},
"value": 1.0
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1,3 @@
a
?? b
?? c;

View File

@ -0,0 +1,70 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 17,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 17,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 16,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 9,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 1,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 8,
"end": 9,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
}
},
"right": {
"type": "Identifier",
"span": {
"start": 15,
"end": 16,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1,78 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 13,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 1,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "ParenthesisExpression",
"span": {
"start": 5,
"end": 13,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 6,
"end": 12,
"ctxt": 0
},
"operator": "&&",
"left": {
"type": "Identifier",
"span": {
"start": 6,
"end": 7,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 11,
"end": 12,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1 @@
a ?? (b || c);

View File

@ -0,0 +1,78 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 13,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 1,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "ParenthesisExpression",
"span": {
"start": 5,
"end": 13,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 6,
"end": 12,
"ctxt": 0
},
"operator": "||",
"left": {
"type": "Identifier",
"span": {
"start": 6,
"end": 7,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 11,
"end": 12,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
}
}
],
"interpreter": null
}

View File

@ -0,0 +1 @@
(a || b) ?? c;

View File

@ -0,0 +1,78 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 14,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 13,
"ctxt": 0
},
"operator": "??",
"left": {
"type": "ParenthesisExpression",
"span": {
"start": 0,
"end": 8,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 1,
"end": 7,
"ctxt": 0
},
"operator": "||",
"left": {
"type": "Identifier",
"span": {
"start": 1,
"end": 2,
"ctxt": 0
},
"value": "a",
"typeAnnotation": null,
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 6,
"end": 7,
"ctxt": 0
},
"value": "b",
"typeAnnotation": null,
"optional": false
}
}
},
"right": {
"type": "Identifier",
"span": {
"start": 12,
"end": 13,
"ctxt": 0
},
"value": "c",
"typeAnnotation": null,
"optional": false
}
}
}
],
"interpreter": null
}

View File

@ -21,7 +21,7 @@
"ctxt": 0 "ctxt": 0
}, },
"callee": { "callee": {
"type": "TsOptionalChainingExpression", "type": "OptionalChainingExpression",
"span": { "span": {
"start": 0, "start": 0,
"end": 20, "end": 20,

View File

@ -11,8 +11,8 @@ edition = "2018"
[dependencies] [dependencies]
swc_atoms = { version = "0.2.0", path ="../../atoms" } swc_atoms = { version = "0.2.0", path ="../../atoms" }
swc_common = { version = "0.4.2", path ="../../common" } swc_common = { version = "0.4.2", path ="../../common" }
ast = { package = "swc_ecma_ast", version = "0.12.0", path ="../ast" } ast = { package = "swc_ecma_ast", version = "0.13.0", path ="../ast" }
swc_ecma_parser = { version = "0.14", path ="../parser", features = ["verify"] } swc_ecma_parser = { version = "0.15", path ="../parser", features = ["verify"] }
chashmap = "2.2.0" chashmap = "2.2.0"
either = "1.5" either = "1.5"
fxhash = "0.2" fxhash = "0.2"
@ -31,7 +31,7 @@ smallvec = "1"
[dev-dependencies] [dev-dependencies]
testing = { version = "0.4", path ="../../testing" } testing = { version = "0.4", path ="../../testing" }
swc_ecma_codegen = { version = "0.10.0", path ="../codegen" } swc_ecma_codegen = { version = "0.11.0", path ="../codegen" }
tempfile = "3" tempfile = "3"
pretty_assertions = "0.6" pretty_assertions = "0.6"
sourcemap = "4.1.1" sourcemap = "4.1.1"

View File

@ -1053,7 +1053,7 @@ fn can_be_null(e: &Expr) -> bool {
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. }) | Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. }) | Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { ref expr, .. }) => can_be_null(expr), | Expr::TsConstAssertion(TsConstAssertion { ref expr, .. }) => can_be_null(expr),
Expr::TsOptChain(ref e) => can_be_null(&e.expr), Expr::OptChain(ref e) => can_be_null(&e.expr),
Expr::Invalid(..) => unreachable!(), Expr::Invalid(..) => unreachable!(),
} }

View File

@ -1,18 +1,29 @@
use crate::{pass::Pass, util::ExprFactory}; use crate::{
pass::Pass,
util::{ExprFactory, COMMENTS},
};
use ast::*; use ast::*;
use fxhash::FxHashMap;
use swc_common::{ use swc_common::{
util::{map::Map, move_map::MoveMap}, util::{map::Map, move_map::MoveMap},
Fold, FoldWith, Fold, FoldWith, Span, Spanned,
}; };
pub fn fixer() -> impl Pass { pub fn fixer() -> impl Pass {
Fixer { Fixer {
ctx: Default::default(), ctx: Default::default(),
span_map: Default::default(),
} }
} }
#[derive(Debug)]
struct Fixer { struct Fixer {
ctx: Context, ctx: Context,
/// A hash map to preserve original span.
///
/// Key is span of inner expression, and value is span of the paren
/// expression.
span_map: FxHashMap<Span, Span>,
} }
#[repr(u8)] #[repr(u8)]
@ -33,12 +44,32 @@ enum Context {
is_var_decl: bool, is_var_decl: bool,
}, },
} }
impl Default for Context { impl Default for Context {
fn default() -> Self { fn default() -> Self {
Context::Default Context::Default
} }
} }
impl Fold<Program> for Fixer {
fn fold(&mut self, p: Program) -> Program {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
let p = p.fold_children(self);
COMMENTS.with(|c| {
for (to, from) in self.span_map.drain() {
let (from, to) = (from.data(), to.data());
c.move_leading(from.lo, to.lo);
c.move_trailing(from.hi, to.hi);
}
});
p
}
}
impl Fold<KeyValuePatProp> for Fixer { impl Fold<KeyValuePatProp> for Fixer {
fn fold(&mut self, node: KeyValuePatProp) -> KeyValuePatProp { fn fold(&mut self, node: KeyValuePatProp) -> KeyValuePatProp {
let old = self.ctx; let old = self.ctx;
@ -84,7 +115,7 @@ impl Fold<BlockStmtOrExpr> for Fixer {
match body { match body {
BlockStmtOrExpr::Expr(box expr @ Expr::Object(..)) => { BlockStmtOrExpr::Expr(box expr @ Expr::Object(..)) => {
BlockStmtOrExpr::Expr(box expr.wrap_with_paren()) BlockStmtOrExpr::Expr(box self.wrap(expr))
} }
_ => body, _ => body,
@ -108,7 +139,7 @@ impl Fold<Stmt> for Fixer {
let stmt = match stmt { let stmt = match stmt {
Stmt::Expr(ExprStmt { span, expr }) => Stmt::Expr(ExprStmt { Stmt::Expr(ExprStmt { span, expr }) => Stmt::Expr(ExprStmt {
span, span,
expr: expr.map(handle_expr_stmt), expr: expr.map(|e| self.handle_expr_stmt(e)),
}), }),
_ => stmt, _ => stmt,
@ -175,7 +206,7 @@ impl Fold<KeyValueProp> for Fixer {
match *prop.value { match *prop.value {
Expr::Seq(..) => KeyValueProp { Expr::Seq(..) => KeyValueProp {
value: box (*prop.value).wrap_with_paren(), value: box self.wrap(*prop.value),
..prop ..prop
}, },
_ => prop, _ => prop,
@ -183,14 +214,86 @@ impl Fold<KeyValueProp> for Fixer {
} }
} }
/// Removes paren impl Fixer {
fn unwrap_expr(mut e: Expr) -> Expr { fn wrap<T>(&mut self, e: T) -> Expr
match e { where
Expr::Seq(SeqExpr { ref mut exprs, .. }) if exprs.len() == 1 => { T: Into<Expr>,
unwrap_expr(*exprs.pop().unwrap()) {
let expr = box e.into();
let span = expr.span();
let span = if let Some(span) = self.span_map.remove(&span) {
span
} else {
span
};
Expr::Paren(ParenExpr { expr, span })
}
/// Removes paren
fn unwrap_expr(&mut self, mut e: Expr) -> Expr {
match e {
Expr::Seq(SeqExpr { ref mut exprs, .. }) if exprs.len() == 1 => {
self.unwrap_expr(*exprs.pop().unwrap())
}
Expr::Paren(ParenExpr {
span: paren_span,
expr,
..
}) => {
let e = self.unwrap_expr(*expr);
self.span_map.insert(e.span(), paren_span);
e
}
_ => validate!(e),
}
}
fn handle_expr_stmt(&mut self, expr: Expr) -> Expr {
match expr {
// It's important for arrow pass to work properly.
Expr::Object(..) | Expr::Class(..) | Expr::Fn(..) => self.wrap(expr),
// ({ a } = foo)
Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(left @ box Pat::Object(..)),
op,
right,
}) => self.wrap(AssignExpr {
span,
left: PatOrExpr::Pat(left),
op,
right,
}),
Expr::Seq(SeqExpr { span, exprs }) => {
debug_assert!(
exprs.len() != 1,
"SeqExpr should be unwrapped if exprs.len() == 1, but length is 1"
);
let mut i = 0;
let len = exprs.len();
Expr::Seq(SeqExpr {
span,
exprs: exprs.move_map(|expr| {
i += 1;
let is_last = len == i;
if !is_last {
expr.map(|e| self.handle_expr_stmt(e))
} else {
expr
}
}),
})
}
_ => expr,
} }
Expr::Paren(ParenExpr { expr, .. }) => unwrap_expr(*expr),
_ => validate!(e),
} }
} }
@ -199,7 +302,7 @@ impl Fold<Expr> for Fixer {
let expr = validate!(expr); let expr = validate!(expr);
let expr = expr.fold_children(self); let expr = expr.fold_children(self);
let expr = validate!(expr); let expr = validate!(expr);
let expr = unwrap_expr(expr); let expr = self.unwrap_expr(expr);
match expr { match expr {
Expr::Member(MemberExpr { Expr::Member(MemberExpr {
@ -301,7 +404,7 @@ impl Fold<Expr> for Fixer {
}) => validate!(MemberExpr { }) => validate!(MemberExpr {
span, span,
computed, computed,
obj: obj.wrap_with_paren().as_obj(), obj: self.wrap(*obj).as_obj(),
prop, prop,
}) })
.into(), .into(),
@ -381,10 +484,10 @@ impl Fold<Expr> for Fixer {
| e @ Expr::Seq(..) | e @ Expr::Seq(..)
| e @ Expr::Yield(..) | e @ Expr::Yield(..)
| e @ Expr::Cond(..) | e @ Expr::Cond(..)
| e @ Expr::Arrow(..) => box validate!(e).wrap_with_paren(), | e @ Expr::Arrow(..) => box self.wrap(e),
Expr::Bin(BinExpr { op: op_of_rhs, .. }) => { Expr::Bin(BinExpr { op: op_of_rhs, .. }) => {
if op_of_rhs.precedence() <= expr.op.precedence() { if op_of_rhs.precedence() <= expr.op.precedence() {
box expr.right.wrap_with_paren() box self.wrap(*expr.right)
} else { } else {
validate!(expr.right) validate!(expr.right)
} }
@ -398,7 +501,7 @@ impl Fold<Expr> for Fixer {
Expr::Bin(BinExpr { op: op_of_lhs, .. }) => { Expr::Bin(BinExpr { op: op_of_lhs, .. }) => {
if op_of_lhs.precedence() < expr.op.precedence() { if op_of_lhs.precedence() < expr.op.precedence() {
Expr::Bin(validate!(BinExpr { Expr::Bin(validate!(BinExpr {
left: box expr.left.wrap_with_paren(), left: box self.wrap(*expr.left),
..expr ..expr
})) }))
} else { } else {
@ -411,7 +514,7 @@ impl Fold<Expr> for Fixer {
| e @ Expr::Cond(..) | e @ Expr::Cond(..)
| e @ Expr::Assign(..) | e @ Expr::Assign(..)
| e @ Expr::Arrow(..) => validate!(Expr::Bin(BinExpr { | e @ Expr::Arrow(..) => validate!(Expr::Bin(BinExpr {
left: box e.wrap_with_paren(), left: box self.wrap(e),
..expr ..expr
})), })),
_ => validate!(Expr::Bin(expr)), _ => validate!(Expr::Bin(expr)),
@ -423,11 +526,11 @@ impl Fold<Expr> for Fixer {
e @ Expr::Seq(..) e @ Expr::Seq(..)
| e @ Expr::Assign(..) | e @ Expr::Assign(..)
| e @ Expr::Cond(..) | e @ Expr::Cond(..)
| e @ Expr::Arrow(..) => box e.wrap_with_paren(), | e @ Expr::Arrow(..) => box self.wrap(e),
e @ Expr::Object(..) | e @ Expr::Fn(..) | e @ Expr::Class(..) => { e @ Expr::Object(..) | e @ Expr::Fn(..) | e @ Expr::Class(..) => {
if self.ctx == Context::Default { if self.ctx == Context::Default {
box e.wrap_with_paren() box self.wrap(e)
} else { } else {
box e box e
} }
@ -436,12 +539,12 @@ impl Fold<Expr> for Fixer {
}; };
let cons = match *expr.cons { let cons = match *expr.cons {
e @ Expr::Seq(..) => box e.wrap_with_paren(), e @ Expr::Seq(..) => box self.wrap(e),
_ => expr.cons, _ => expr.cons,
}; };
let alt = match *expr.alt { let alt = match *expr.alt {
e @ Expr::Seq(..) => box e.wrap_with_paren(), e @ Expr::Seq(..) => box self.wrap(e),
_ => expr.alt, _ => expr.alt,
}; };
validate!(Expr::Cond(CondExpr { validate!(Expr::Cond(CondExpr {
@ -459,7 +562,7 @@ impl Fold<Expr> for Fixer {
| e @ Expr::Seq(..) | e @ Expr::Seq(..)
| e @ Expr::Cond(..) | e @ Expr::Cond(..)
| e @ Expr::Arrow(..) | e @ Expr::Arrow(..)
| e @ Expr::Yield(..) => box e.wrap_with_paren(), | e @ Expr::Yield(..) => box self.wrap(e),
_ => expr.arg, _ => expr.arg,
}; };
@ -479,7 +582,7 @@ impl Fold<Expr> for Fixer {
}) => expr.right, }) => expr.right,
// Handle `foo = bar = init() // Handle `foo = bar = init()
Expr::Seq(right) => box right.wrap_with_paren(), Expr::Seq(right) => box self.wrap(right),
_ => expr.right, _ => expr.right,
}; };
@ -493,7 +596,7 @@ impl Fold<Expr> for Fixer {
type_args, type_args,
}) => validate!(Expr::Call(CallExpr { }) => validate!(Expr::Call(CallExpr {
span, span,
callee: callee.wrap_with_paren().as_callee(), callee: self.wrap(*callee).as_callee(),
args, args,
type_args, type_args,
})), })),
@ -512,17 +615,16 @@ impl Fold<Expr> for Fixer {
type_args, type_args,
})), })),
Context::Callee { is_new: true } => validate!(Expr::Call(CallExpr { Context::Callee { is_new: true } => self.wrap(CallExpr {
span, span,
callee: callee.as_callee(), callee: callee.as_callee(),
args, args,
type_args, type_args,
})) }),
.wrap_with_paren(),
_ => validate!(Expr::Call(CallExpr { _ => validate!(Expr::Call(CallExpr {
span, span,
callee: callee.wrap_with_paren().as_callee(), callee: self.wrap(*callee).as_callee(),
args, args,
type_args, type_args,
})), })),
@ -534,7 +636,7 @@ impl Fold<Expr> for Fixer {
type_args, type_args,
}) => validate!(Expr::Call(CallExpr { }) => validate!(Expr::Call(CallExpr {
span, span,
callee: callee.wrap_with_paren().as_callee(), callee: self.wrap(*callee).as_callee(),
args, args,
type_args, type_args,
})), })),
@ -552,7 +654,7 @@ impl Fold<ExprOrSpread> for Fixer {
Expr::Yield(..) => { Expr::Yield(..) => {
return ExprOrSpread { return ExprOrSpread {
spread: None, spread: None,
expr: box e.expr.wrap_with_paren(), expr: box self.wrap(*e.expr),
} }
} }
_ => {} _ => {}
@ -569,7 +671,7 @@ impl Fold<ExportDefaultExpr> for Fixer {
self.ctx = Context::Default; self.ctx = Context::Default;
let mut node = node.fold_children(self); let mut node = node.fold_children(self);
node.expr = match *node.expr { node.expr = match *node.expr {
Expr::Arrow(..) | Expr::Seq(..) => box node.expr.wrap_with_paren(), Expr::Arrow(..) | Expr::Seq(..) => box self.wrap(*node.expr),
_ => node.expr, _ => node.expr,
}; };
self.ctx = old; self.ctx = old;
@ -584,7 +686,7 @@ impl Fold<ArrowExpr> for Fixer {
let mut node = node.fold_children(self); let mut node = node.fold_children(self);
node.body = match node.body { node.body = match node.body {
BlockStmtOrExpr::Expr(e @ box Expr::Seq(..)) => { BlockStmtOrExpr::Expr(e @ box Expr::Seq(..)) => {
BlockStmtOrExpr::Expr(box e.wrap_with_paren()) BlockStmtOrExpr::Expr(box self.wrap(*e))
} }
_ => node.body, _ => node.body,
}; };
@ -599,7 +701,7 @@ impl Fold<Class> for Fixer {
self.ctx = Context::Default; self.ctx = Context::Default;
let mut node = node.fold_children(self); let mut node = node.fold_children(self);
node.super_class = match node.super_class { node.super_class = match node.super_class {
Some(e @ box Expr::Seq(..)) => Some(box e.wrap_with_paren()), Some(e @ box Expr::Seq(..)) => Some(box self.wrap(*e)),
_ => node.super_class, _ => node.super_class,
}; };
self.ctx = old; self.ctx = old;
@ -619,52 +721,6 @@ fn ignore_return_value(expr: Box<Expr>) -> Option<Box<Expr>> {
} }
} }
fn handle_expr_stmt(expr: Expr) -> Expr {
match expr {
// It's important for arrow pass to work properly.
Expr::Object(..) | Expr::Class(..) | Expr::Fn(..) => expr.wrap_with_paren(),
// ({ a } = foo)
Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(left @ box Pat::Object(..)),
op,
right,
}) => AssignExpr {
span,
left: PatOrExpr::Pat(left),
op,
right,
}
.wrap_with_paren(),
Expr::Seq(SeqExpr { span, exprs }) => {
debug_assert!(
exprs.len() != 1,
"SeqExpr should be unwrapped if exprs.len() == 1, but length is 1"
);
let mut i = 0;
let len = exprs.len();
Expr::Seq(SeqExpr {
span,
exprs: exprs.move_map(|expr| {
i += 1;
let is_last = len == i;
if !is_last {
expr.map(handle_expr_stmt)
} else {
expr
}
}),
})
}
_ => expr,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
struct Noop; struct Noop;

View File

@ -993,7 +993,7 @@ where
| Expr::TsTypeCast(TsTypeCastExpr { expr, .. }) | Expr::TsTypeCast(TsTypeCastExpr { expr, .. })
| Expr::TsAs(TsAsExpr { expr, .. }) | Expr::TsAs(TsAsExpr { expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { expr, .. }) => add_effects(v, expr), | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) => add_effects(v, expr),
Expr::TsOptChain(e) => add_effects(v, e.expr), Expr::OptChain(e) => add_effects(v, e.expr),
Expr::Invalid(..) => unreachable!(), Expr::Invalid(..) => unreachable!(),
} }

View File

@ -1,5 +1,9 @@
pub use self::{class_properties::class_properties, decorators::decorators, export::export}; pub use self::{
class_properties::class_properties, decorators::decorators, export::export,
opt_chaining::optional_chaining,
};
mod class_properties; mod class_properties;
pub mod decorators; pub mod decorators;
mod export; mod export;
mod opt_chaining;

View File

@ -48,7 +48,7 @@ where
impl Fold<Expr> for OptChaining { impl Fold<Expr> for OptChaining {
fn fold(&mut self, e: Expr) -> Expr { fn fold(&mut self, e: Expr) -> Expr {
let e = match e { let e = match e {
Expr::TsOptChain(e) => Expr::Cond(validate!(self.unwrap(e))), Expr::OptChain(e) => Expr::Cond(validate!(self.unwrap(e))),
Expr::Unary(e) => validate!(self.handle_unary(e)), Expr::Unary(e) => validate!(self.handle_unary(e)),
Expr::Member(e) => validate!(self.handle_member(e)), Expr::Member(e) => validate!(self.handle_member(e)),
Expr::Call(e) => validate!(self.handle_call(e)), Expr::Call(e) => validate!(self.handle_call(e)),
@ -66,7 +66,7 @@ impl OptChaining {
if let op!("delete") = e.op { if let op!("delete") = e.op {
match *e.arg { match *e.arg {
Expr::TsOptChain(o) => { Expr::OptChain(o) => {
let expr = self.unwrap(o); let expr = self.unwrap(o);
return CondExpr { return CondExpr {
@ -83,7 +83,7 @@ impl OptChaining {
Expr::Member(MemberExpr { Expr::Member(MemberExpr {
span, span,
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)), obj: ExprOrSuper::Expr(box Expr::OptChain(o)),
prop, prop,
computed, computed,
}) => { }) => {
@ -114,7 +114,7 @@ impl OptChaining {
/// Only called from [Fold<Expr>]. /// Only called from [Fold<Expr>].
fn handle_call(&mut self, e: CallExpr) -> Expr { fn handle_call(&mut self, e: CallExpr) -> Expr {
if let ExprOrSuper::Expr(box Expr::TsOptChain(o)) = e.callee { if let ExprOrSuper::Expr(box Expr::OptChain(o)) = e.callee {
let expr = self.unwrap(o); let expr = self.unwrap(o);
return CondExpr { return CondExpr {
@ -133,7 +133,7 @@ impl OptChaining {
/// Only called from `[Fold<Expr>]. /// Only called from `[Fold<Expr>].
fn handle_member(&mut self, e: MemberExpr) -> Expr { fn handle_member(&mut self, e: MemberExpr) -> Expr {
if let ExprOrSuper::Expr(box Expr::TsOptChain(o)) = e.obj { if let ExprOrSuper::Expr(box Expr::OptChain(o)) = e.obj {
let expr = self.unwrap(o); let expr = self.unwrap(o);
return CondExpr { return CondExpr {
@ -150,13 +150,13 @@ impl OptChaining {
Expr::Member(e) Expr::Member(e)
} }
fn unwrap(&mut self, e: TsOptChain) -> CondExpr { fn unwrap(&mut self, e: OptChainExpr) -> CondExpr {
let span = e.span; let span = e.span;
let cons = undefined(span); let cons = undefined(span);
match *e.expr { match *e.expr {
Expr::Member(MemberExpr { Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)), obj: ExprOrSuper::Expr(box Expr::OptChain(o)),
prop, prop,
computed, computed,
span: m_span, span: m_span,
@ -170,7 +170,7 @@ impl OptChaining {
prop, prop,
computed, computed,
}); });
let alt = box Expr::TsOptChain(TsOptChain { let alt = box Expr::OptChain(OptChainExpr {
span: o_span, span: o_span,
expr: alt, expr: alt,
}); });
@ -180,7 +180,7 @@ impl OptChaining {
Expr::Call(CallExpr { Expr::Call(CallExpr {
span, span,
callee: ExprOrSuper::Expr(box Expr::TsOptChain(o)), callee: ExprOrSuper::Expr(box Expr::OptChain(o)),
args, args,
type_args, type_args,
}) => { }) => {
@ -192,7 +192,7 @@ impl OptChaining {
args, args,
type_args, type_args,
}); });
let alt = box Expr::TsOptChain(TsOptChain { span, expr: alt }); let alt = box Expr::OptChain(OptChainExpr { span, expr: alt });
return validate!(CondExpr { return validate!(CondExpr {
span: DUMMY_SP, span: DUMMY_SP,

View File

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

View File

@ -19,7 +19,8 @@ use std::{
}; };
use swc_atoms::{js_word, JsWord}; use swc_atoms::{js_word, JsWord};
use swc_common::{ use swc_common::{
errors::Handler, Fold, FoldWith, Mark, Span, Spanned, Visit, VisitWith, DUMMY_SP, comments::Comments, errors::Handler, Fold, FoldWith, Mark, Span, Spanned, Visit, VisitWith,
DUMMY_SP,
}; };
use unicode_xid::UnicodeXID; use unicode_xid::UnicodeXID;
@ -693,7 +694,7 @@ pub trait ExprExt {
| Expr::TsNonNull(TsNonNullExpr { ref expr, .. }) | Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. }) | Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. }) => expr.may_have_side_effects(), | Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. }) => expr.may_have_side_effects(),
Expr::TsOptChain(ref e) => e.expr.may_have_side_effects(), Expr::OptChain(ref e) => e.expr.may_have_side_effects(),
Expr::Invalid(..) => unreachable!(), Expr::Invalid(..) => unreachable!(),
} }
@ -923,7 +924,7 @@ not_lit!(TsTypeAssertion);
not_lit!(TsConstAssertion); not_lit!(TsConstAssertion);
not_lit!(PrivateName); not_lit!(PrivateName);
not_lit!(TsOptChain); not_lit!(OptChainExpr);
not_lit!(SpreadElement); not_lit!(SpreadElement);
not_lit!(Invalid); not_lit!(Invalid);
@ -1283,3 +1284,4 @@ impl<'a> UsageFinder<'a> {
} }
scoped_thread_local!(pub static HANDLER: Handler); scoped_thread_local!(pub static HANDLER: Handler);
scoped_thread_local!(pub static COMMENTS: Comments);

View File

@ -10,7 +10,7 @@ use ecmascript::{
chain_at, const_modules, modules, chain_at, const_modules, modules,
optimization::{simplifier, InlineGlobals, JsonParse}, optimization::{simplifier, InlineGlobals, JsonParse},
pass::{noop, Optional, Pass}, pass::{noop, Optional, Pass},
proposals::{class_properties, decorators, export}, proposals::{class_properties, decorators, export, optional_chaining},
react, resolver, typescript, react, resolver, typescript,
}, },
}; };
@ -181,7 +181,7 @@ impl Options {
// handle jsx // handle jsx
Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()), Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()),
Optional::new(typescript::strip(), syntax.typescript()), Optional::new(typescript::strip(), syntax.typescript()),
Optional::new(typescript::optional_chaining(), syntax.typescript()), Optional::new(optional_chaining(), syntax.typescript()),
Optional::new(class_properties(), syntax.typescript()), Optional::new(class_properties(), syntax.typescript()),
resolver(), resolver(),
const_modules, const_modules,

View File

@ -25,6 +25,7 @@ use ecmascript::{
transforms::{ transforms::{
helpers::{self, Helpers}, helpers::{self, Helpers},
util, util,
util::COMMENTS,
}, },
}; };
pub use ecmascript::{ pub use ecmascript::{
@ -43,6 +44,7 @@ pub struct Compiler {
/// CodeMap /// CodeMap
pub cm: Arc<SourceMap>, pub cm: Arc<SourceMap>,
pub handler: Handler, pub handler: Handler,
comments: Comments,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -54,6 +56,10 @@ pub struct TransformOutput {
/// These are **low-level** apis. /// These are **low-level** apis.
impl Compiler { impl Compiler {
pub fn comments(&self) -> &Comments {
&self.comments
}
/// Runs `op` in current compiler's context. /// Runs `op` in current compiler's context.
/// ///
/// Note: Other methods of `Compiler` already uses this internally. /// Note: Other methods of `Compiler` already uses this internally.
@ -61,7 +67,13 @@ impl Compiler {
where where
F: FnOnce() -> R, F: FnOnce() -> R,
{ {
GLOBALS.set(&self.globals, || op()) GLOBALS.set(&self.globals, || {
//
COMMENTS.set(&self.comments, || {
//
op()
})
})
} }
/// This method parses a javascript / typescript file /// This method parses a javascript / typescript file
@ -71,7 +83,7 @@ impl Compiler {
target: JscTarget, target: JscTarget,
syntax: Syntax, syntax: Syntax,
is_module: bool, is_module: bool,
comments: Option<&Comments>, parse_comments: bool,
) -> Result<Program, Error> { ) -> Result<Program, Error> {
self.run(|| { self.run(|| {
let session = ParseSess { let session = ParseSess {
@ -82,7 +94,11 @@ impl Compiler {
syntax, syntax,
target, target,
SourceFileInput::from(&*fm), SourceFileInput::from(&*fm),
comments, if parse_comments {
Some(&self.comments)
} else {
None
},
); );
let mut parser = Parser::new_from(session, lexer); let mut parser = Parser::new_from(session, lexer);
let program = if is_module { let program = if is_module {
@ -181,6 +197,7 @@ impl Compiler {
cm, cm,
handler, handler,
globals: Globals::new(), globals: Globals::new(),
comments: Default::default(),
} }
} }
@ -290,13 +307,12 @@ impl Compiler {
eprintln!("processing js file: {:?}", fm) eprintln!("processing js file: {:?}", fm)
} }
let comments = Default::default();
let module = self.parse_js( let module = self.parse_js(
fm.clone(), fm.clone(),
config.target, config.target,
config.syntax, config.syntax,
config.is_module, config.is_module,
if config.minify { None } else { Some(&comments) }, !config.minify,
)?; )?;
let mut pass = config.pass; let mut pass = config.pass;
let module = helpers::HELPERS.set(&Helpers::new(config.external_helpers), || { let module = helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
@ -306,7 +322,13 @@ impl Compiler {
}) })
}); });
self.print(&module, fm, &comments, config.source_maps, config.minify) self.print(
&module,
fm,
&self.comments,
config.source_maps,
config.minify,
)
}) })
} }
} }

View File

@ -228,3 +228,25 @@ fn issue_467() {
fn issue_468() { fn issue_468() {
file("tests/projects/issue-468/input.ts").expect("failed to parse typescript"); file("tests/projects/issue-468/input.ts").expect("failed to parse typescript");
} }
#[test]
fn issue_528() {
let f = file("tests/projects/issue-528/input.js")
.unwrap()
.replace(" ", "");
let f = f.trim();
println!("{}", f);
assert_eq!(
f,
"\
//bar
[
//foo
a,
//baz
//bar
b
];"
);
}

View File

@ -0,0 +1,11 @@
// bar
[
// foo
a,
//bar
(
//baz
b
)
]