mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 13:11:31 +03:00
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:
parent
d8541c4f63
commit
b1e4122b02
12
README.md
12
README.md
@ -133,6 +133,18 @@ New generation javascript to old-days javascript.
|
||||
- [x] optional-catch-binding
|
||||
- [ ] 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
|
||||
- [x] jsx
|
||||
|
||||
|
@ -31,6 +31,34 @@ impl Comments {
|
||||
pub fn leading_comments(&self, pos: BytePos) -> Option<ReadGuard<'_, BytePos, Vec<Comment>>> {
|
||||
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)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.12.1"
|
||||
version = "0.13.0"
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
|
@ -10,8 +10,8 @@ use crate::{
|
||||
prop::Prop,
|
||||
stmt::BlockStmt,
|
||||
typescript::{
|
||||
TsAsExpr, TsConstAssertion, TsNonNullExpr, TsOptChain, TsTypeAnn, TsTypeAssertion,
|
||||
TsTypeCastExpr, TsTypeParamDecl, TsTypeParamInstantiation,
|
||||
TsAsExpr, TsConstAssertion, TsNonNullExpr, TsTypeAnn, TsTypeAssertion, TsTypeCastExpr,
|
||||
TsTypeParamDecl, TsTypeParamInstantiation,
|
||||
},
|
||||
Invalid,
|
||||
};
|
||||
@ -144,8 +144,8 @@ pub enum Expr {
|
||||
#[tag("PrivateName")]
|
||||
PrivateName(PrivateName),
|
||||
|
||||
#[tag("TsOptionalChainingExpression")]
|
||||
TsOptChain(TsOptChain),
|
||||
#[tag("OptionalChainingExpression")]
|
||||
OptChain(OptChainExpr),
|
||||
|
||||
#[tag("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!(
|
||||
jsx_element,
|
||||
JSXElement,
|
||||
|
@ -15,8 +15,8 @@ pub use self::{
|
||||
expr::{
|
||||
ArrayLit, ArrowExpr, AssignExpr, AwaitExpr, BinExpr, BlockStmtOrExpr, CallExpr, ClassExpr,
|
||||
CondExpr, Expr, ExprOrSpread, ExprOrSuper, FnExpr, MemberExpr, MetaPropExpr, NewExpr,
|
||||
ObjectLit, ParenExpr, PatOrExpr, PropOrSpread, SeqExpr, SpreadElement, Super, TaggedTpl,
|
||||
ThisExpr, Tpl, TplElement, UnaryExpr, UpdateExpr, YieldExpr,
|
||||
ObjectLit, OptChainExpr, ParenExpr, PatOrExpr, PropOrSpread, SeqExpr, SpreadElement, Super,
|
||||
TaggedTpl, ThisExpr, Tpl, TplElement, UnaryExpr, UpdateExpr, YieldExpr,
|
||||
},
|
||||
function::{Function, PatOrTsParamProp},
|
||||
ident::{Ident, IdentExt, PrivateName},
|
||||
@ -56,12 +56,12 @@ pub use self::{
|
||||
TsInterfaceBody, TsInterfaceDecl, TsIntersectionType, TsKeywordType, TsKeywordTypeKind,
|
||||
TsLit, TsLitType, TsMappedType, TsMethodSignature, TsModuleBlock, TsModuleDecl,
|
||||
TsModuleName, TsModuleRef, 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, TsTypeQueryExpr, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
|
||||
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, TsTypeQueryExpr,
|
||||
TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
|
||||
},
|
||||
};
|
||||
use swc_common::{ast_node, Span};
|
||||
|
@ -106,6 +106,9 @@ macro_rules! op {
|
||||
("**") => {
|
||||
$crate::BinaryOp::Exp
|
||||
};
|
||||
("??") => {
|
||||
$crate::BinaryOp::NullishCoalescing
|
||||
};
|
||||
|
||||
("=") => {
|
||||
$crate::AssignOp::Assign
|
||||
|
@ -85,6 +85,10 @@ pub enum BinaryOp {
|
||||
/// `**`
|
||||
#[kind(precedence = "11")]
|
||||
Exp,
|
||||
|
||||
/// `??`
|
||||
#[kind(precedence = "1")]
|
||||
NullishCoalescing,
|
||||
}
|
||||
|
||||
#[derive(StringEnum, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
|
@ -837,9 +837,3 @@ pub struct TsConstAssertion {
|
||||
pub span: Span,
|
||||
pub expr: Box<Expr>,
|
||||
}
|
||||
|
||||
#[ast_node("TsOptionalChainingExpression")]
|
||||
pub struct TsOptChain {
|
||||
pub span: Span,
|
||||
pub expr: Box<Expr>,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swc_ecma_codegen"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
@ -13,11 +13,11 @@ bitflags = "1"
|
||||
hashbrown = "0.6"
|
||||
swc_atoms = { version = "0.2", path ="../../atoms" }
|
||||
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" }
|
||||
sourcemap = "4.1.1"
|
||||
num-bigint = { version = "0.2", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { version = "0.4", path ="../../testing" }
|
||||
swc_ecma_parser = { version = "0.14", path ="../parser" }
|
||||
swc_ecma_parser = { version = "0.15", path ="../parser" }
|
@ -480,11 +480,40 @@ 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),
|
||||
Expr::OptChain(ref n) => emit!(n),
|
||||
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]
|
||||
pub fn emit_call_expr(&mut self, node: &CallExpr) -> Result {
|
||||
self.emit_leading_comments_of_pos(node.span().lo())?;
|
||||
|
@ -16,11 +16,6 @@ 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,7 +210,7 @@ impl StartsWithAlphaNum for Expr {
|
||||
|
||||
// TODO
|
||||
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,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
@ -18,7 +18,7 @@ verify = ["fold"]
|
||||
[dependencies]
|
||||
swc_atoms = { version = "0.2", path ="../../atoms" }
|
||||
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" }
|
||||
enum_kind = { version = "0.2", path ="../../macros/enum_kind" }
|
||||
unicode-xid = "0.2"
|
||||
|
@ -103,6 +103,9 @@ pub enum SyntaxError {
|
||||
AwaitStar,
|
||||
ReservedWordInObjShorthandOrPat,
|
||||
|
||||
NullishCoalescingWithLogicalOp,
|
||||
NullishCoalescingNotEnabled,
|
||||
|
||||
MultipleDefault {
|
||||
/// Span of the previous default case
|
||||
previous: Span,
|
||||
@ -359,6 +362,13 @@ impl<'a> From<ErrorToDiag<'a>> for DiagnosticBuilder<'a> {
|
||||
"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(),
|
||||
TS1141 => "literal in an import type should be string literal".into(),
|
||||
|
||||
|
@ -179,7 +179,7 @@ impl<'a, I: Input> Lexer<'a, I> {
|
||||
return Ok(Some(tok!('.')));
|
||||
}
|
||||
|
||||
'(' | ')' | ';' | ',' | '[' | ']' | '{' | '}' | '@' | '?' => {
|
||||
'(' | ')' | ';' | ',' | '[' | ']' | '{' | '}' | '@' => {
|
||||
// These tokens are emitted directly.
|
||||
self.input.bump();
|
||||
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();
|
||||
return Ok(Some(tok!('`')));
|
||||
|
@ -247,6 +247,18 @@ impl Syntax {
|
||||
_ => 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)]
|
||||
@ -292,6 +304,7 @@ pub struct EsConfig {
|
||||
#[serde(default)]
|
||||
pub jsx: bool,
|
||||
/// Support numeric separator.
|
||||
/// Stage 3.
|
||||
#[serde(rename = "numericSeparator")]
|
||||
#[serde(default)]
|
||||
pub num_sep: bool,
|
||||
@ -332,6 +345,10 @@ pub struct EsConfig {
|
||||
|
||||
#[serde(default)]
|
||||
pub dynamic_import: bool,
|
||||
|
||||
/// Stage 3.
|
||||
#[serde(default)]
|
||||
pub nullish_coalescing: bool,
|
||||
}
|
||||
|
||||
/// Syntactic context.
|
||||
|
@ -19,6 +19,9 @@ macro_rules! tok {
|
||||
('-') => {
|
||||
crate::token::Token::BinOp(crate::token::BinOpToken::Sub)
|
||||
};
|
||||
("??") => {
|
||||
crate::token::Token::BinOp(crate::token::BinOpToken::NullishCoalescing)
|
||||
};
|
||||
('~') => {
|
||||
crate::token::Token::Tilde
|
||||
};
|
||||
|
@ -869,7 +869,7 @@ impl<'a, I: Tokens> Parser<'a, I> {
|
||||
macro_rules! wrap {
|
||||
($e:expr) => {{
|
||||
if is_optional_chaining {
|
||||
Expr::TsOptChain(TsOptChain {
|
||||
Expr::OptChain(OptChainExpr {
|
||||
span: span!(self, start),
|
||||
expr: Box::new($e),
|
||||
})
|
||||
|
@ -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 {
|
||||
trace!(
|
||||
"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 {
|
||||
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)?;
|
||||
|
||||
if op == op!("??") {
|
||||
match *expr {
|
||||
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
|
||||
syntax_error!(span, SyntaxError::NullishCoalescingWithLogicalOp);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ pub(super) trait ExprExt {
|
||||
| Expr::JSXFragment(..) => false,
|
||||
|
||||
// typescript
|
||||
Expr::TsOptChain(TsOptChain { ref expr, .. })
|
||||
Expr::OptChain(OptChainExpr { ref expr, .. })
|
||||
| Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
|
||||
| Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
|
||||
| Expr::TsTypeCast(TsTypeCastExpr { ref expr, .. })
|
||||
|
@ -203,6 +203,9 @@ pub enum BinOpToken {
|
||||
LogicalOr,
|
||||
/// `&&`
|
||||
LogicalAnd,
|
||||
|
||||
/// `??`
|
||||
NullishCoalescing,
|
||||
}
|
||||
|
||||
impl BinOpToken {
|
||||
@ -539,6 +542,7 @@ impl From<BinOpToken> for BinaryOp {
|
||||
BinOpToken::LogicalOr => LogicalOr,
|
||||
BinOpToken::LogicalAnd => LogicalAnd,
|
||||
BinOpToken::Exp => Exp,
|
||||
BinOpToken::NullishCoalescing => NullishCoalescing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
c && d ?? e;
|
@ -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;
|
||||
| ^^^^^^
|
||||
|
@ -0,0 +1 @@
|
||||
a ?? b && c;
|
@ -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;
|
||||
| ^^^^^^
|
||||
|
@ -0,0 +1 @@
|
||||
e ?? f ?? g || h;
|
@ -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;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
@ -0,0 +1 @@
|
||||
h || i ?? j;
|
@ -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;
|
||||
| ^^^^^^
|
||||
|
@ -0,0 +1 @@
|
||||
(a && b) ?? c;
|
@ -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
|
||||
}
|
@ -0,0 +1 @@
|
||||
a ?? b ?? c;
|
@ -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
|
||||
}
|
@ -0,0 +1 @@
|
||||
foo ?? 1;
|
@ -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
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
a
|
||||
?? b
|
||||
?? c;
|
@ -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
|
||||
}
|
@ -0,0 +1 @@
|
||||
a ?? (b && c);
|
@ -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
|
||||
}
|
@ -0,0 +1 @@
|
||||
a ?? (b || c);
|
@ -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
|
||||
}
|
@ -0,0 +1 @@
|
||||
(a || b) ?? c;
|
@ -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
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
"ctxt": 0
|
||||
},
|
||||
"callee": {
|
||||
"type": "TsOptionalChainingExpression",
|
||||
"type": "OptionalChainingExpression",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 20,
|
||||
|
@ -11,8 +11,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
swc_atoms = { version = "0.2.0", path ="../../atoms" }
|
||||
swc_common = { version = "0.4.2", path ="../../common" }
|
||||
ast = { package = "swc_ecma_ast", version = "0.12.0", path ="../ast" }
|
||||
swc_ecma_parser = { version = "0.14", path ="../parser", features = ["verify"] }
|
||||
ast = { package = "swc_ecma_ast", version = "0.13.0", path ="../ast" }
|
||||
swc_ecma_parser = { version = "0.15", path ="../parser", features = ["verify"] }
|
||||
chashmap = "2.2.0"
|
||||
either = "1.5"
|
||||
fxhash = "0.2"
|
||||
@ -31,7 +31,7 @@ smallvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
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"
|
||||
pretty_assertions = "0.6"
|
||||
sourcemap = "4.1.1"
|
@ -1053,7 +1053,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),
|
||||
Expr::OptChain(ref e) => can_be_null(&e.expr),
|
||||
|
||||
Expr::Invalid(..) => unreachable!(),
|
||||
}
|
||||
|
@ -1,18 +1,29 @@
|
||||
use crate::{pass::Pass, util::ExprFactory};
|
||||
use crate::{
|
||||
pass::Pass,
|
||||
util::{ExprFactory, COMMENTS},
|
||||
};
|
||||
use ast::*;
|
||||
use fxhash::FxHashMap;
|
||||
use swc_common::{
|
||||
util::{map::Map, move_map::MoveMap},
|
||||
Fold, FoldWith,
|
||||
Fold, FoldWith, Span, Spanned,
|
||||
};
|
||||
|
||||
pub fn fixer() -> impl Pass {
|
||||
Fixer {
|
||||
ctx: Default::default(),
|
||||
span_map: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Fixer {
|
||||
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)]
|
||||
@ -33,12 +44,32 @@ enum Context {
|
||||
is_var_decl: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn fold(&mut self, node: KeyValuePatProp) -> KeyValuePatProp {
|
||||
let old = self.ctx;
|
||||
@ -84,7 +115,7 @@ impl Fold<BlockStmtOrExpr> for Fixer {
|
||||
|
||||
match body {
|
||||
BlockStmtOrExpr::Expr(box expr @ Expr::Object(..)) => {
|
||||
BlockStmtOrExpr::Expr(box expr.wrap_with_paren())
|
||||
BlockStmtOrExpr::Expr(box self.wrap(expr))
|
||||
}
|
||||
|
||||
_ => body,
|
||||
@ -108,7 +139,7 @@ impl Fold<Stmt> for Fixer {
|
||||
let stmt = match stmt {
|
||||
Stmt::Expr(ExprStmt { span, expr }) => Stmt::Expr(ExprStmt {
|
||||
span,
|
||||
expr: expr.map(handle_expr_stmt),
|
||||
expr: expr.map(|e| self.handle_expr_stmt(e)),
|
||||
}),
|
||||
|
||||
_ => stmt,
|
||||
@ -175,7 +206,7 @@ impl Fold<KeyValueProp> for Fixer {
|
||||
|
||||
match *prop.value {
|
||||
Expr::Seq(..) => KeyValueProp {
|
||||
value: box (*prop.value).wrap_with_paren(),
|
||||
value: box self.wrap(*prop.value),
|
||||
..prop
|
||||
},
|
||||
_ => prop,
|
||||
@ -183,14 +214,86 @@ impl Fold<KeyValueProp> for Fixer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes paren
|
||||
fn unwrap_expr(mut e: Expr) -> Expr {
|
||||
match e {
|
||||
Expr::Seq(SeqExpr { ref mut exprs, .. }) if exprs.len() == 1 => {
|
||||
unwrap_expr(*exprs.pop().unwrap())
|
||||
impl Fixer {
|
||||
fn wrap<T>(&mut self, e: T) -> Expr
|
||||
where
|
||||
T: Into<Expr>,
|
||||
{
|
||||
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 = expr.fold_children(self);
|
||||
let expr = validate!(expr);
|
||||
let expr = unwrap_expr(expr);
|
||||
let expr = self.unwrap_expr(expr);
|
||||
|
||||
match expr {
|
||||
Expr::Member(MemberExpr {
|
||||
@ -301,7 +404,7 @@ impl Fold<Expr> for Fixer {
|
||||
}) => validate!(MemberExpr {
|
||||
span,
|
||||
computed,
|
||||
obj: obj.wrap_with_paren().as_obj(),
|
||||
obj: self.wrap(*obj).as_obj(),
|
||||
prop,
|
||||
})
|
||||
.into(),
|
||||
@ -381,10 +484,10 @@ impl Fold<Expr> for Fixer {
|
||||
| e @ Expr::Seq(..)
|
||||
| e @ Expr::Yield(..)
|
||||
| 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, .. }) => {
|
||||
if op_of_rhs.precedence() <= expr.op.precedence() {
|
||||
box expr.right.wrap_with_paren()
|
||||
box self.wrap(*expr.right)
|
||||
} else {
|
||||
validate!(expr.right)
|
||||
}
|
||||
@ -398,7 +501,7 @@ impl Fold<Expr> for Fixer {
|
||||
Expr::Bin(BinExpr { op: op_of_lhs, .. }) => {
|
||||
if op_of_lhs.precedence() < expr.op.precedence() {
|
||||
Expr::Bin(validate!(BinExpr {
|
||||
left: box expr.left.wrap_with_paren(),
|
||||
left: box self.wrap(*expr.left),
|
||||
..expr
|
||||
}))
|
||||
} else {
|
||||
@ -411,7 +514,7 @@ impl Fold<Expr> for Fixer {
|
||||
| e @ Expr::Cond(..)
|
||||
| e @ Expr::Assign(..)
|
||||
| e @ Expr::Arrow(..) => validate!(Expr::Bin(BinExpr {
|
||||
left: box e.wrap_with_paren(),
|
||||
left: box self.wrap(e),
|
||||
..expr
|
||||
})),
|
||||
_ => validate!(Expr::Bin(expr)),
|
||||
@ -423,11 +526,11 @@ impl Fold<Expr> for Fixer {
|
||||
e @ Expr::Seq(..)
|
||||
| e @ Expr::Assign(..)
|
||||
| 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(..) => {
|
||||
if self.ctx == Context::Default {
|
||||
box e.wrap_with_paren()
|
||||
box self.wrap(e)
|
||||
} else {
|
||||
box e
|
||||
}
|
||||
@ -436,12 +539,12 @@ impl Fold<Expr> for Fixer {
|
||||
};
|
||||
|
||||
let cons = match *expr.cons {
|
||||
e @ Expr::Seq(..) => box e.wrap_with_paren(),
|
||||
e @ Expr::Seq(..) => box self.wrap(e),
|
||||
_ => expr.cons,
|
||||
};
|
||||
|
||||
let alt = match *expr.alt {
|
||||
e @ Expr::Seq(..) => box e.wrap_with_paren(),
|
||||
e @ Expr::Seq(..) => box self.wrap(e),
|
||||
_ => expr.alt,
|
||||
};
|
||||
validate!(Expr::Cond(CondExpr {
|
||||
@ -459,7 +562,7 @@ impl Fold<Expr> for Fixer {
|
||||
| e @ Expr::Seq(..)
|
||||
| e @ Expr::Cond(..)
|
||||
| e @ Expr::Arrow(..)
|
||||
| e @ Expr::Yield(..) => box e.wrap_with_paren(),
|
||||
| e @ Expr::Yield(..) => box self.wrap(e),
|
||||
_ => expr.arg,
|
||||
};
|
||||
|
||||
@ -479,7 +582,7 @@ impl Fold<Expr> for Fixer {
|
||||
}) => expr.right,
|
||||
|
||||
// Handle `foo = bar = init()
|
||||
Expr::Seq(right) => box right.wrap_with_paren(),
|
||||
Expr::Seq(right) => box self.wrap(right),
|
||||
_ => expr.right,
|
||||
};
|
||||
|
||||
@ -493,7 +596,7 @@ impl Fold<Expr> for Fixer {
|
||||
type_args,
|
||||
}) => validate!(Expr::Call(CallExpr {
|
||||
span,
|
||||
callee: callee.wrap_with_paren().as_callee(),
|
||||
callee: self.wrap(*callee).as_callee(),
|
||||
args,
|
||||
type_args,
|
||||
})),
|
||||
@ -512,17 +615,16 @@ impl Fold<Expr> for Fixer {
|
||||
type_args,
|
||||
})),
|
||||
|
||||
Context::Callee { is_new: true } => validate!(Expr::Call(CallExpr {
|
||||
Context::Callee { is_new: true } => self.wrap(CallExpr {
|
||||
span,
|
||||
callee: callee.as_callee(),
|
||||
args,
|
||||
type_args,
|
||||
}))
|
||||
.wrap_with_paren(),
|
||||
}),
|
||||
|
||||
_ => validate!(Expr::Call(CallExpr {
|
||||
span,
|
||||
callee: callee.wrap_with_paren().as_callee(),
|
||||
callee: self.wrap(*callee).as_callee(),
|
||||
args,
|
||||
type_args,
|
||||
})),
|
||||
@ -534,7 +636,7 @@ impl Fold<Expr> for Fixer {
|
||||
type_args,
|
||||
}) => validate!(Expr::Call(CallExpr {
|
||||
span,
|
||||
callee: callee.wrap_with_paren().as_callee(),
|
||||
callee: self.wrap(*callee).as_callee(),
|
||||
args,
|
||||
type_args,
|
||||
})),
|
||||
@ -552,7 +654,7 @@ impl Fold<ExprOrSpread> for Fixer {
|
||||
Expr::Yield(..) => {
|
||||
return ExprOrSpread {
|
||||
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;
|
||||
let mut node = node.fold_children(self);
|
||||
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,
|
||||
};
|
||||
self.ctx = old;
|
||||
@ -584,7 +686,7 @@ impl Fold<ArrowExpr> for Fixer {
|
||||
let mut node = node.fold_children(self);
|
||||
node.body = match node.body {
|
||||
BlockStmtOrExpr::Expr(e @ box Expr::Seq(..)) => {
|
||||
BlockStmtOrExpr::Expr(box e.wrap_with_paren())
|
||||
BlockStmtOrExpr::Expr(box self.wrap(*e))
|
||||
}
|
||||
_ => node.body,
|
||||
};
|
||||
@ -599,7 +701,7 @@ impl Fold<Class> for Fixer {
|
||||
self.ctx = Context::Default;
|
||||
let mut node = node.fold_children(self);
|
||||
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,
|
||||
};
|
||||
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)]
|
||||
mod tests {
|
||||
struct Noop;
|
||||
|
@ -993,7 +993,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),
|
||||
Expr::OptChain(e) => add_effects(v, e.expr),
|
||||
|
||||
Expr::Invalid(..) => unreachable!(),
|
||||
}
|
||||
|
@ -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;
|
||||
pub mod decorators;
|
||||
mod export;
|
||||
mod opt_chaining;
|
||||
|
@ -48,7 +48,7 @@ where
|
||||
impl Fold<Expr> for OptChaining {
|
||||
fn fold(&mut self, e: Expr) -> Expr {
|
||||
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::Member(e) => validate!(self.handle_member(e)),
|
||||
Expr::Call(e) => validate!(self.handle_call(e)),
|
||||
@ -66,7 +66,7 @@ impl OptChaining {
|
||||
|
||||
if let op!("delete") = e.op {
|
||||
match *e.arg {
|
||||
Expr::TsOptChain(o) => {
|
||||
Expr::OptChain(o) => {
|
||||
let expr = self.unwrap(o);
|
||||
|
||||
return CondExpr {
|
||||
@ -83,7 +83,7 @@ impl OptChaining {
|
||||
|
||||
Expr::Member(MemberExpr {
|
||||
span,
|
||||
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
|
||||
obj: ExprOrSuper::Expr(box Expr::OptChain(o)),
|
||||
prop,
|
||||
computed,
|
||||
}) => {
|
||||
@ -114,7 +114,7 @@ impl OptChaining {
|
||||
|
||||
/// Only called from [Fold<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);
|
||||
|
||||
return CondExpr {
|
||||
@ -133,7 +133,7 @@ impl OptChaining {
|
||||
|
||||
/// Only called from `[Fold<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);
|
||||
|
||||
return CondExpr {
|
||||
@ -150,13 +150,13 @@ impl OptChaining {
|
||||
Expr::Member(e)
|
||||
}
|
||||
|
||||
fn unwrap(&mut self, e: TsOptChain) -> CondExpr {
|
||||
fn unwrap(&mut self, e: OptChainExpr) -> CondExpr {
|
||||
let span = e.span;
|
||||
let cons = undefined(span);
|
||||
|
||||
match *e.expr {
|
||||
Expr::Member(MemberExpr {
|
||||
obj: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
|
||||
obj: ExprOrSuper::Expr(box Expr::OptChain(o)),
|
||||
prop,
|
||||
computed,
|
||||
span: m_span,
|
||||
@ -170,7 +170,7 @@ impl OptChaining {
|
||||
prop,
|
||||
computed,
|
||||
});
|
||||
let alt = box Expr::TsOptChain(TsOptChain {
|
||||
let alt = box Expr::OptChain(OptChainExpr {
|
||||
span: o_span,
|
||||
expr: alt,
|
||||
});
|
||||
@ -180,7 +180,7 @@ impl OptChaining {
|
||||
|
||||
Expr::Call(CallExpr {
|
||||
span,
|
||||
callee: ExprOrSuper::Expr(box Expr::TsOptChain(o)),
|
||||
callee: ExprOrSuper::Expr(box Expr::OptChain(o)),
|
||||
args,
|
||||
type_args,
|
||||
}) => {
|
||||
@ -192,7 +192,7 @@ impl OptChaining {
|
||||
args,
|
||||
type_args,
|
||||
});
|
||||
let alt = box Expr::TsOptChain(TsOptChain { span, expr: alt });
|
||||
let alt = box Expr::OptChain(OptChainExpr { span, expr: alt });
|
||||
|
||||
return validate!(CondExpr {
|
||||
span: DUMMY_SP,
|
@ -1,4 +1,3 @@
|
||||
pub use self::opt_chaining::optional_chaining;
|
||||
use crate::{
|
||||
pass::Pass,
|
||||
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,
|
||||
};
|
||||
|
||||
mod opt_chaining;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
@ -19,7 +19,8 @@ use std::{
|
||||
};
|
||||
use swc_atoms::{js_word, JsWord};
|
||||
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;
|
||||
|
||||
@ -693,7 +694,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(),
|
||||
Expr::OptChain(ref e) => e.expr.may_have_side_effects(),
|
||||
|
||||
Expr::Invalid(..) => unreachable!(),
|
||||
}
|
||||
@ -923,7 +924,7 @@ not_lit!(TsTypeAssertion);
|
||||
not_lit!(TsConstAssertion);
|
||||
|
||||
not_lit!(PrivateName);
|
||||
not_lit!(TsOptChain);
|
||||
not_lit!(OptChainExpr);
|
||||
|
||||
not_lit!(SpreadElement);
|
||||
not_lit!(Invalid);
|
||||
@ -1283,3 +1284,4 @@ impl<'a> UsageFinder<'a> {
|
||||
}
|
||||
|
||||
scoped_thread_local!(pub static HANDLER: Handler);
|
||||
scoped_thread_local!(pub static COMMENTS: Comments);
|
||||
|
@ -10,7 +10,7 @@ use ecmascript::{
|
||||
chain_at, const_modules, modules,
|
||||
optimization::{simplifier, InlineGlobals, JsonParse},
|
||||
pass::{noop, Optional, Pass},
|
||||
proposals::{class_properties, decorators, export},
|
||||
proposals::{class_properties, decorators, export, optional_chaining},
|
||||
react, resolver, typescript,
|
||||
},
|
||||
};
|
||||
@ -181,7 +181,7 @@ impl Options {
|
||||
// handle jsx
|
||||
Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()),
|
||||
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()),
|
||||
resolver(),
|
||||
const_modules,
|
||||
|
34
src/lib.rs
34
src/lib.rs
@ -25,6 +25,7 @@ use ecmascript::{
|
||||
transforms::{
|
||||
helpers::{self, Helpers},
|
||||
util,
|
||||
util::COMMENTS,
|
||||
},
|
||||
};
|
||||
pub use ecmascript::{
|
||||
@ -43,6 +44,7 @@ pub struct Compiler {
|
||||
/// CodeMap
|
||||
pub cm: Arc<SourceMap>,
|
||||
pub handler: Handler,
|
||||
comments: Comments,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@ -54,6 +56,10 @@ pub struct TransformOutput {
|
||||
|
||||
/// These are **low-level** apis.
|
||||
impl Compiler {
|
||||
pub fn comments(&self) -> &Comments {
|
||||
&self.comments
|
||||
}
|
||||
|
||||
/// Runs `op` in current compiler's context.
|
||||
///
|
||||
/// Note: Other methods of `Compiler` already uses this internally.
|
||||
@ -61,7 +67,13 @@ impl Compiler {
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
GLOBALS.set(&self.globals, || op())
|
||||
GLOBALS.set(&self.globals, || {
|
||||
//
|
||||
COMMENTS.set(&self.comments, || {
|
||||
//
|
||||
op()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// This method parses a javascript / typescript file
|
||||
@ -71,7 +83,7 @@ impl Compiler {
|
||||
target: JscTarget,
|
||||
syntax: Syntax,
|
||||
is_module: bool,
|
||||
comments: Option<&Comments>,
|
||||
parse_comments: bool,
|
||||
) -> Result<Program, Error> {
|
||||
self.run(|| {
|
||||
let session = ParseSess {
|
||||
@ -82,7 +94,11 @@ impl Compiler {
|
||||
syntax,
|
||||
target,
|
||||
SourceFileInput::from(&*fm),
|
||||
comments,
|
||||
if parse_comments {
|
||||
Some(&self.comments)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
let mut parser = Parser::new_from(session, lexer);
|
||||
let program = if is_module {
|
||||
@ -181,6 +197,7 @@ impl Compiler {
|
||||
cm,
|
||||
handler,
|
||||
globals: Globals::new(),
|
||||
comments: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,13 +307,12 @@ impl Compiler {
|
||||
eprintln!("processing js file: {:?}", fm)
|
||||
}
|
||||
|
||||
let comments = Default::default();
|
||||
let module = self.parse_js(
|
||||
fm.clone(),
|
||||
config.target,
|
||||
config.syntax,
|
||||
config.is_module,
|
||||
if config.minify { None } else { Some(&comments) },
|
||||
!config.minify,
|
||||
)?;
|
||||
let mut pass = config.pass;
|
||||
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,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -228,3 +228,25 @@ fn issue_467() {
|
||||
fn issue_468() {
|
||||
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
|
||||
];"
|
||||
);
|
||||
}
|
||||
|
11
tests/projects/issue-528/input.js
Normal file
11
tests/projects/issue-528/input.js
Normal file
@ -0,0 +1,11 @@
|
||||
// bar
|
||||
[
|
||||
// foo
|
||||
a,
|
||||
|
||||
//bar
|
||||
(
|
||||
//baz
|
||||
b
|
||||
)
|
||||
]
|
Loading…
Reference in New Issue
Block a user