diff --git a/src/expr.rs b/src/expr.rs index 5a9f69b..a8c01b4 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,5 +1,7 @@ use crate::pat::Pat; -use crate::{AssignOp, BinaryOp, IntoAllocated, LogicalOp, PropKind, UnaryOp, UpdateOp}; +use crate::{ + AssignOp, BinaryOp, IntoAllocated, LogicalOp, MemberIndexer, PropKind, UnaryOp, UpdateOp, +}; use crate::{Class, Func, FuncArg, FuncBody, Ident}; #[cfg(feature = "serde")] @@ -89,6 +91,7 @@ pub enum Expr { Update(UpdateExpr), /// yield a value from inside of a generator function Yield(YieldExpr), + OptionalChain(Box>), } impl IntoAllocated for Expr @@ -134,6 +137,7 @@ where Expr::Unary(inner) => Expr::Unary(inner.into_allocated()), Expr::Update(inner) => Expr::Update(inner.into_allocated()), Expr::Yield(inner) => Expr::Yield(inner.into_allocated()), + Expr::OptionalChain(inner) => Expr::OptionalChain(inner.into_allocated()), } } } @@ -406,7 +410,7 @@ where pub struct MemberExpr { pub object: Box>, pub property: Box>, - pub computed: bool, + pub indexer: MemberIndexer, } impl IntoAllocated for MemberExpr @@ -419,7 +423,7 @@ where MemberExpr { object: self.object.into_allocated(), property: self.property.into_allocated(), - computed: self.computed, + indexer: self.indexer, } } } @@ -458,6 +462,7 @@ where #[derive(PartialEq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct CallExpr { + pub optional: bool, pub callee: Box>, pub arguments: Vec>, } @@ -470,6 +475,7 @@ where fn into_allocated(self) -> Self::Allocated { CallExpr { + optional: self.optional, callee: self.callee.into_allocated(), arguments: self .arguments @@ -602,6 +608,7 @@ where } } } + /// A Template literal preceded by a function identifier /// see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) for more details #[derive(PartialEq, Debug, Clone)] diff --git a/src/lib.rs b/src/lib.rs index 0908e4a..a7f8364 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -337,6 +337,17 @@ impl Class { } } +/// The ways to access the member of a value +/// Either a Period `.`, Computed `[ ]`, Optional `?.` or optional computed `?.[ ]` +#[derive(Debug, Clone, PartialEq, Copy)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum MemberIndexer { + Period, + Computed, + Optional, + OptionalComputed, +} + /// The kind of variable being defined (`var`/`let`/`const`) #[derive(Debug, Clone, PartialEq, Copy)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] @@ -367,6 +378,9 @@ pub enum AssignOp { XOrEqual, AndEqual, PowerOfEqual, + DoubleAmpersandEqual, + DoublePipeEqual, + DoubleQuestionmarkEqual, } /// The available logical operators @@ -375,6 +389,7 @@ pub enum AssignOp { pub enum LogicalOp { Or, And, + NullishCoalescing, } /// The available operations for `Binary` Exprs diff --git a/src/spanned/convert.rs b/src/spanned/convert.rs index 2be8578..29ffcf6 100644 --- a/src/spanned/convert.rs +++ b/src/spanned/convert.rs @@ -170,6 +170,7 @@ mod decl { } mod expr { + use crate::spanned::expr::MemberIndexer; use crate::spanned::{ expr::Boolean, tokens::{QuasiQuote, Quote}, @@ -219,6 +220,7 @@ mod expr { Expr::Update(inner) => Self::Update(inner.into()), Expr::Yield(inner) => Self::Yield(inner.into()), Expr::Wrapped(inner) => inner.expr.into(), + Expr::OptionalChain(inner) => Self::OptionalChain(Box::new((*inner.expr).into())), } } } @@ -408,13 +410,23 @@ mod expr { } } + impl From for crate::MemberIndexer { + fn from(other: MemberIndexer) -> Self { + match other { + MemberIndexer::Period(_) => crate::MemberIndexer::Period, + MemberIndexer::Computed { .. } => crate::MemberIndexer::Computed, + MemberIndexer::Optional(_) => crate::MemberIndexer::Optional, + MemberIndexer::OptionalComputed { .. } => crate::MemberIndexer::OptionalComputed, + } + } + } + impl From> for crate::expr::MemberExpr { fn from(other: MemberExpr) -> Self { - let computed = other.computed(); Self { object: Box::new(From::from(*other.object)), property: Box::new(From::from(*other.property)), - computed, + indexer: From::from(other.indexer), } } } @@ -432,6 +444,7 @@ mod expr { impl From> for crate::expr::CallExpr { fn from(other: CallExpr) -> Self { Self { + optional: other.optional.is_some(), callee: Box::new(From::from(*other.callee)), arguments: other.arguments.into_iter().map(|e| e.item.into()).collect(), } @@ -692,6 +705,9 @@ impl From for crate::AssignOp { AssignOp::XOrEqual(_) => Self::XOrEqual, AssignOp::AndEqual(_) => Self::AndEqual, AssignOp::PowerOfEqual(_) => Self::PowerOfEqual, + AssignOp::DoubleAmpersandEqual(_) => Self::DoubleAmpersandEqual, + AssignOp::DoublePipeEqual(_) => Self::DoublePipeEqual, + AssignOp::DoubleQuestionmarkEqual(_) => Self::DoubleQuestionmarkEqual, } } } @@ -701,6 +717,7 @@ impl From for crate::LogicalOp { match other { LogicalOp::Or(_) => Self::Or, LogicalOp::And(_) => Self::And, + LogicalOp::NullishCoalescing(_) => Self::NullishCoalescing, } } } diff --git a/src/spanned/expr.rs b/src/spanned/expr.rs index ba3cb33..3abb7ab 100644 --- a/src/spanned/expr.rs +++ b/src/spanned/expr.rs @@ -3,10 +3,10 @@ use crate::spanned::{Class, Func, FuncArg, FuncBody, Ident}; use crate::IntoAllocated; use super::tokens::{ - AssignOp, Asterisk, Async, Await, BinaryOp, CloseBrace, CloseBracket, CloseParen, Colon, Comma, - Ellipsis, False, FatArrow, ForwardSlash, Get, LogicalOp, New, Null, OpenBrace, OpenBracket, - OpenParen, Period, QuasiQuote, QuestionMark, Quote, Set, Static, Super, This, Token, True, - UnaryOp, UpdateOp, Yield, + self, AssignOp, Asterisk, Async, Await, BinaryOp, CloseBrace, CloseBracket, CloseParen, Colon, + Comma, Ellipsis, False, FatArrow, ForwardSlash, Get, LogicalOp, New, Null, OpenBrace, + OpenBracket, OpenParen, Period, QuasiQuote, QuestionMark, QuestionMarkDot, Quote, Set, Static, + Super, This, Token, True, UnaryOp, UpdateOp, Yield, }; use super::{FuncArgEntry, ListEntry, Node, Slice, SourceLocation}; #[cfg(feature = "serde")] @@ -97,6 +97,7 @@ pub enum Expr { Wrapped(Box>), /// yield a value from inside of a generator function Yield(YieldExpr), + OptionalChain(OptionalChain), } impl IntoAllocated for Expr @@ -139,6 +140,7 @@ where Expr::Update(inner) => Expr::Update(inner.into_allocated()), Expr::Wrapped(inner) => Expr::Wrapped(inner.into_allocated()), Expr::Yield(inner) => Expr::Yield(inner.into_allocated()), + Expr::OptionalChain(inner) => Expr::OptionalChain(inner.into_allocated()), } } } @@ -172,6 +174,7 @@ impl Node for Expr { Expr::Update(inner) => inner.loc(), Expr::Yield(inner) => inner.loc(), Expr::Wrapped(inner) => inner.loc(), + Expr::OptionalChain(inner) => inner.loc(), } } } @@ -967,6 +970,7 @@ where impl MemberExpr { pub fn computed(&self) -> bool { matches!(self.indexer, MemberIndexer::Computed { .. }) + || matches!(self.indexer, MemberIndexer::OptionalComputed { .. }) } } @@ -983,6 +987,15 @@ impl Node for MemberExpr { } } +/// An indexer +/// Either a Period ".", Computed "[..]", Optional "?." or optional computed "?.[..]" +/// ```js +/// var a = {b: 'c'}; +/// a.b +/// a["b"] +/// a?.b +/// a?.["b"] +/// ``` #[derive(PartialEq, Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum MemberIndexer { @@ -991,6 +1004,12 @@ pub enum MemberIndexer { open_bracket: OpenBracket, close_bracket: CloseBracket, }, + Optional(QuestionMarkDot), + OptionalComputed { + optional: QuestionMarkDot, + open_bracket: OpenBracket, + close_bracket: CloseBracket, + }, } impl Node for MemberIndexer { @@ -1004,10 +1023,47 @@ impl Node for MemberIndexer { start: open_bracket.start(), end: close_bracket.end(), }, + MemberIndexer::Optional(inner) => inner.loc(), + MemberIndexer::OptionalComputed { + optional, + open_bracket: _, + close_bracket, + } => SourceLocation { + start: optional.start(), + end: close_bracket.end(), + }, + } + } +} + +#[derive(PartialEq, Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct OptionalChain { + pub expr: Box>, + pub op: tokens::QuestionMarkDot, +} + +impl IntoAllocated for OptionalChain +where + T: ToString, +{ + type Allocated = OptionalChain; + fn into_allocated(self) -> Self::Allocated { + OptionalChain { + expr: Box::new((*self.expr).into_allocated()), + op: self.op, } } } +impl Node for OptionalChain { + fn loc(&self) -> SourceLocation { + let start = self.expr.loc().start; + let end = self.op.end(); + SourceLocation { start, end } + } +} + /// A ternery expression /// ```js /// var a = true ? 'stuff' : 'things'; @@ -1049,11 +1105,13 @@ impl Node for ConditionalExpr { /// Calling a function or method /// ```js /// Math.random() +/// Math.random?.() /// ``` #[derive(PartialEq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct CallExpr { pub callee: Box>, + pub optional: Option, pub open_paren: OpenParen, pub arguments: Vec>>, pub close_paren: CloseParen, @@ -1068,6 +1126,7 @@ where fn into_allocated(self) -> Self::Allocated { CallExpr { callee: self.callee.into_allocated(), + optional: self.optional, open_paren: self.open_paren, arguments: self .arguments diff --git a/src/spanned/tokens.rs b/src/spanned/tokens.rs index 72e6da6..4a05ec5 100644 --- a/src/spanned/tokens.rs +++ b/src/spanned/tokens.rs @@ -162,6 +162,7 @@ define_token!(CloseBracket, "]"); define_token!(Colon, ":"); define_token!(Comma, ","); define_token!(DoubleAmpersand, "&&"); +define_token!(DoubleAmpersandEqual, "&&="); define_token!(DoubleAsterisk, "**"); define_token!(DoubleAsteriskEqual, "**="); define_token!(DoubleEqual, "=="); @@ -171,6 +172,9 @@ define_token!(DoubleGreaterThanEqual, ">>="); define_token!(DoubleLessThan, "<<"); define_token!(DoubleLessThanEqual, "<<="); define_token!(DoublePipe, "||"); +define_token!(DoublePipeEqual, "||="); +define_token!(DoubleQuestionmark, "??"); +define_token!(DoubleQuestionmarkEqual, "??="); define_token!(DoubleQuote, "\""); define_token!(Ellipsis, "..."); define_token!(Equal, "="); @@ -194,6 +198,7 @@ define_token!(PipeEqual, "|="); define_token!(Plus, "+"); define_token!(PlusEqual, "+="); define_token!(QuestionMark, "?"); +define_token!(QuestionMarkDot, "?."); define_token!(Semicolon, ";"); define_token!(SingleQuote, "'"); define_token!(Tilde, "~"); @@ -282,6 +287,9 @@ pub enum AssignOp { XOrEqual(CaretEqual), AndEqual(AmpersandEqual), PowerOfEqual(DoubleAsteriskEqual), + DoubleAmpersandEqual(DoubleAmpersandEqual), + DoublePipeEqual(DoublePipeEqual), + DoubleQuestionmarkEqual(DoubleQuestionmarkEqual), } impl Node for AssignOp { @@ -300,6 +308,9 @@ impl Node for AssignOp { AssignOp::XOrEqual(tok) => tok.loc(), AssignOp::AndEqual(tok) => tok.loc(), AssignOp::PowerOfEqual(tok) => tok.loc(), + AssignOp::DoubleAmpersandEqual(tok) => tok.loc(), + AssignOp::DoublePipeEqual(tok) => tok.loc(), + AssignOp::DoubleQuestionmarkEqual(tok) => tok.loc(), } } } @@ -310,6 +321,7 @@ impl Node for AssignOp { pub enum LogicalOp { Or(DoublePipe), And(DoubleAmpersand), + NullishCoalescing(DoubleQuestionmark), } impl Node for LogicalOp { @@ -317,6 +329,7 @@ impl Node for LogicalOp { match self { LogicalOp::Or(tok) => tok.loc(), LogicalOp::And(tok) => tok.loc(), + LogicalOp::NullishCoalescing(tok) => tok.loc(), } } }