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
- [ ] 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

View File

@ -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)]

View File

@ -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"

View File

@ -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,

View File

@ -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};

View File

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

View File

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

View File

@ -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>,
}

View File

@ -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" }

View File

@ -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())?;

View File

@ -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")

View File

@ -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,
}

View File

@ -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"

View File

@ -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(),

View File

@ -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!('`')));

View File

@ -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.

View File

@ -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
};

View File

@ -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),
})

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 {
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)
}

View File

@ -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, .. })

View File

@ -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,
}
}
}

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
},
"callee": {
"type": "TsOptionalChainingExpression",
"type": "OptionalChainingExpression",
"span": {
"start": 0,
"end": 20,

View File

@ -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"

View File

@ -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!(),
}

View File

@ -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;

View File

@ -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!(),
}

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;
pub mod decorators;
mod export;
mod opt_chaining;

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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,
)
})
}
}

View File

@ -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
];"
);
}

View File

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