diff --git a/src/lib.rs b/src/lib.rs index 5357273..25b68de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ //! Stmt::Expr( //! Expr::Call( //! CallExpr { +//! optional: false, //! callee: Box::new( //! Expr::ident_from("alert") //! ), @@ -320,6 +321,7 @@ where /// let js = "function helloWorld() { alert('Hello world'); }"; /// let mut p = Parser::new(&js).unwrap(); /// let call = CallExpr { + /// optional: false, /// callee: Box::new(Expr::ident_from("alert")), /// arguments: vec![Expr::Lit(Lit::single_string_from("Hello world"))], /// }; diff --git a/src/spanned/mod.rs b/src/spanned/mod.rs index 219f052..34fd29b 100644 --- a/src/spanned/mod.rs +++ b/src/spanned/mod.rs @@ -28,6 +28,7 @@ //! Stmt::Expr( //! Expr::Call( //! CallExpr { +//! optional: false, //! callee: Box::new( //! Expr::ident_from("alert") //! ), @@ -98,6 +99,7 @@ use crate::lexical_names::DeclKind; use crate::lhs; use crate::LabelKind; use crate::{Config, Context}; +use resast::spanned::tokens::QuestionMarkDot; use std::borrow::Cow; use std::{ collections::{HashMap, HashSet}, @@ -340,6 +342,7 @@ where /// let js = "function helloWorld() { alert('Hello world'); }"; /// let mut p = Parser::new(&js).unwrap(); /// let call = CallExpr { + /// optional: false, /// callee: Box::new(Expr::ident_from("alert")), /// arguments: vec![Expr::Lit(Lit::single_string_from("Hello world"))], /// }; @@ -6069,6 +6072,117 @@ where Ok(Expr::Super(keyword)) } + /// parses calls and optional calls + /// a() + /// a?.() + fn parse_call( + &mut self, + is_async: bool, + start_pos: Position, + expr: Expr>, + opt: Option, + ) -> Res>> { + let current_pos = self.look_ahead_position; + let async_arrow = is_async && start_pos.line == current_pos.line; + self.context.set_is_binding_element(false); + self.context.set_is_assignment_target(false); + + let (open_paren, args, close_paren) = if async_arrow { + self.parse_async_args()? + } else { + self.parse_args()? + }; + //TODO: check for bad import call + if async_arrow && self.at_punct(Punct::EqualGreaterThan) { + let args = args + .into_iter() + .map(|e| { + let ListEntry { item, comma } = e; + ListEntry { + item: FuncArg::Expr(item), + comma, + } + }) + .collect(); + let inner = ArrowParamPlaceHolder { + keyword: None, + open_paren: Some(open_paren), + args, + close_paren: Some(close_paren), + }; + Ok(Expr::ArrowParamPlaceHolder(inner)) + } else { + let inner = CallExpr { + callee: Box::new(expr), + optional: opt, + open_paren, + arguments: args, + close_paren, + }; + Ok(Expr::Call(inner)) + } + } + + /// parses index + /// a.b + /// a?.b + fn parse_index( + &mut self, + object: Expr>, + opt: Option, + ) -> Res>> { + self.context.set_is_binding_element(false); + self.context.set_is_assignment_target(true); + let indexer = if let Some(qmd) = opt { + MemberIndexer::Optional(qmd) + } else { + let period = self.expect_punct(Punct::Period)?; + MemberIndexer::Period(period) + }; + let prop = Expr::Ident(self.parse_ident_name()?); + let expr = Expr::Member(MemberExpr { + object: Box::new(object), + property: Box::new(prop), + indexer, + }); + log::debug!(target: "look_ahead", "1 {:?}", expr); + Ok(expr) + } + + /// parses computed index + /// a['b'] + /// a?.['b'] + fn parse_computed_index( + &mut self, + expr: Expr>, + opt: Option, + ) -> Res>> { + self.context.set_is_assignment_target(true); + self.context.set_is_binding_element(false); + let open_bracket = self.expect_punct(Punct::OpenBracket)?; + let prop = isolate_cover_grammar!(self, parse_expression)?; + let close_bracket = self.expect_punct(Punct::CloseBracket)?; + let indexer = if let Some(qmd) = opt { + MemberIndexer::OptionalComputed { + optional: qmd, + open_bracket, + close_bracket, + } + } else { + MemberIndexer::Computed { + open_bracket, + close_bracket, + } + }; + let member = MemberExpr { + object: Box::new(expr), + indexer, + property: Box::new(prop), + }; + log::debug!(target: "look_ahead", "{:?}", member); + Ok(Expr::Member(member)) + } + #[tracing::instrument(level = "trace", skip(self))] fn parse_left_hand_side_expr_allow_call(&mut self) -> Res>> { log::debug!( @@ -6091,73 +6205,27 @@ where ret }; loop { - if self.at_punct(Punct::Period) { - self.context.set_is_binding_element(false); - self.context.set_is_assignment_target(true); - let period = self.expect_punct(Punct::Period)?; - let indexer = MemberIndexer::Period(period); - let prop = Expr::Ident(self.parse_ident_name()?); - expr = Expr::Member(MemberExpr { - object: Box::new(expr), - property: Box::new(prop), - indexer, - }); - log::debug!(target: "look_ahead", "1 {:?}", expr); - } else if self.at_punct(Punct::OpenParen) { - let current_pos = self.look_ahead_position; - let async_arrow = is_async && start_pos.line == current_pos.line; - self.context.set_is_binding_element(false); - self.context.set_is_assignment_target(false); - let (open_paren, args, close_paren) = if async_arrow { - self.parse_async_args()? - } else { - self.parse_args()? - }; - //TODO: check for bad import call - if async_arrow && self.at_punct(Punct::EqualGreaterThan) { - let args = args - .into_iter() - .map(|e| { - let ListEntry { item, comma } = e; - ListEntry { - item: FuncArg::Expr(item), - comma, - } - }) - .collect(); - let inner = ArrowParamPlaceHolder { - keyword: None, - open_paren: Some(open_paren), - args, - close_paren: Some(close_paren), - }; - expr = Expr::ArrowParamPlaceHolder(inner); + if self.at_punct(Punct::QuestionMarkDot) { + let opt = Some(self.expect_punct(Punct::QuestionMarkDot)?); + if self.at_punct(Punct::OpenParen) { + // a?.() + expr = self.parse_call(is_async, start_pos, expr, opt)?; + } else if self.at_punct(Punct::OpenBracket) { + // a?.['b'] + expr = self.parse_computed_index(expr, opt)? } else { - let inner = CallExpr { - callee: Box::new(expr), - arguments: args, - open_paren, - close_paren, - }; - expr = Expr::Call(inner); + // a?.b + expr = self.parse_index(expr, opt)?; } + } else if self.at_punct(Punct::Period) { + // a.b + expr = self.parse_index(expr, None)?; + } else if self.at_punct(Punct::OpenParen) { + // a() + expr = self.parse_call(is_async, start_pos, expr, None)?; } else if self.at_punct(Punct::OpenBracket) { - self.context.set_is_assignment_target(true); - self.context.set_is_binding_element(false); - let open_bracket = self.expect_punct(Punct::OpenBracket)?; - let prop = isolate_cover_grammar!(self, parse_expression)?; - let close_bracket = self.expect_punct(Punct::CloseBracket)?; - let indexer = MemberIndexer::Computed { - open_bracket, - close_bracket, - }; - let member = MemberExpr { - object: Box::new(expr), - indexer, - property: Box::new(prop), - }; - log::debug!(target: "look_ahead", "{:?}", member); - expr = Expr::Member(member); + // a[b] + expr = self.parse_computed_index(expr, None)?; } else if self.look_ahead.token.is_template_head() { let quasi = self.parse_template_lit(true)?; let temp = TaggedTemplateExpr { diff --git a/tests/snapshots/everything_js__es2015_module.snap b/tests/snapshots/everything_js__es2015_module.snap index 66b27a8..a6b3085 100644 --- a/tests/snapshots/everything_js__es2015_module.snap +++ b/tests/snapshots/everything_js__es2015_module.snap @@ -15057,6 +15057,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 136, @@ -15105,6 +15106,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 136, @@ -15120,6 +15122,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 136, @@ -15166,6 +15169,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 136, @@ -15233,6 +15237,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 136, @@ -15360,6 +15365,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15402,6 +15408,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15487,6 +15494,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15535,6 +15543,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15587,6 +15596,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15662,6 +15672,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 137, @@ -15708,6 +15719,7 @@ Mod( }, }, ), + optional: None, open_paren: OpenParen( Position { line: 138, @@ -27543,6 +27555,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 236, @@ -31427,6 +31440,7 @@ Mod( }, ), ), + optional: None, open_paren: OpenParen( Position { line: 266, @@ -31510,6 +31524,7 @@ Mod( }, ), ), + optional: None, open_paren: OpenParen( Position { line: 267, @@ -31613,6 +31628,7 @@ Mod( }, ), ), + optional: None, open_paren: OpenParen( Position { line: 268, @@ -32349,6 +32365,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 271, @@ -32532,6 +32549,7 @@ Mod( ), }, ), + optional: None, open_paren: OpenParen( Position { line: 273, @@ -35938,6 +35956,7 @@ Mod( }, ), ), + optional: None, open_paren: OpenParen( Position { line: 294, diff --git a/tests/snapshots/everything_js__es2015_script.snap b/tests/snapshots/everything_js__es2015_script.snap index 5f28921..331710c 100644 --- a/tests/snapshots/everything_js__es2015_script.snap +++ b/tests/snapshots/everything_js__es2015_script.snap @@ -3473,7 +3473,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -3492,7 +3492,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -3511,7 +3511,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -3530,7 +3530,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), ), @@ -3596,7 +3596,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), ), @@ -3627,7 +3627,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -3689,7 +3689,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -3713,7 +3713,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -3739,7 +3739,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), property: Ident( @@ -3747,7 +3747,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -3773,7 +3773,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), property: Lit( @@ -3781,7 +3781,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -3862,7 +3862,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -3891,7 +3891,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -3903,6 +3903,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -3917,8 +3918,10 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -3936,6 +3939,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -3956,6 +3960,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -3981,10 +3986,12 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Ident( @@ -3997,7 +4004,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -4008,7 +4015,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -4020,10 +4027,12 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Ident( @@ -4036,7 +4045,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -4047,7 +4056,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -4059,12 +4068,14 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -4078,7 +4089,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), property: Lit( @@ -4086,7 +4097,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -4098,6 +4109,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -8822,6 +8834,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Super, arguments: [ MetaProp( @@ -8845,6 +8858,7 @@ Script( TaggedTemplateExpr { tag: Call( CallExpr { + optional: false, callee: Super, arguments: [], }, @@ -8872,6 +8886,7 @@ Script( body: Expr( Call( CallExpr { + optional: false, callee: Super, arguments: [ This, @@ -9106,6 +9121,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Super, @@ -9114,7 +9130,7 @@ Script( name: "m", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -9134,7 +9150,7 @@ Script( name: "m", }, ), - computed: false, + indexer: Period, }, ), quasi: TemplateLit { @@ -9160,6 +9176,7 @@ Script( body: Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Super, @@ -9168,7 +9185,7 @@ Script( name: "m", }, ), - computed: false, + indexer: Period, }, ), arguments: [ @@ -10465,6 +10482,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Super, arguments: [], }, diff --git a/tests/snapshots/everything_js__es5.snap b/tests/snapshots/everything_js__es5.snap index 761e8c8..e88745c 100644 --- a/tests/snapshots/everything_js__es5.snap +++ b/tests/snapshots/everything_js__es5.snap @@ -1891,7 +1891,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -1910,7 +1910,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), ), @@ -1976,7 +1976,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), ), @@ -2007,7 +2007,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), ), @@ -2069,7 +2069,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -2093,7 +2093,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -2119,7 +2119,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), property: Ident( @@ -2127,7 +2127,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -2153,7 +2153,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), property: Lit( @@ -2161,7 +2161,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -2242,7 +2242,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -2271,7 +2271,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -2283,6 +2283,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -2297,8 +2298,10 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -2316,6 +2319,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -2336,6 +2340,7 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -2361,10 +2366,12 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Ident( @@ -2377,7 +2384,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -2388,7 +2395,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), arguments: [], @@ -2400,10 +2407,12 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Ident( @@ -2416,7 +2425,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -2427,7 +2436,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], @@ -2439,12 +2448,14 @@ Script( Expr( Call( CallExpr { + optional: false, callee: Member( MemberExpr { object: Member( MemberExpr { object: Call( CallExpr { + optional: false, callee: Ident( Ident { name: "x", @@ -2458,7 +2469,7 @@ Script( name: "a", }, ), - computed: false, + indexer: Period, }, ), property: Lit( @@ -2466,7 +2477,7 @@ Script( "0", ), ), - computed: true, + indexer: Computed, }, ), arguments: [], diff --git a/tests/snippets.rs b/tests/snippets.rs index 7912eb2..0eb4025 100644 --- a/tests/snippets.rs +++ b/tests/snippets.rs @@ -1,4 +1,4 @@ -use resast::{expr::QuasiQuote, prelude::*, spanned::Position}; +use resast::{expr::QuasiQuote, prelude::*, spanned::Position, MemberIndexer}; use ressa::*; use std::borrow::Cow; #[test] @@ -9,6 +9,7 @@ fn doc1() { id: Some(Ident::from(Cow::Borrowed("helloWorld"))), params: vec![], body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { + optional: false, callee: Box::new(Expr::ident_from("alert".into())), arguments: vec![Expr::Lit(Lit::single_string_from("Hello world".into()))], })))]), @@ -28,6 +29,7 @@ fn readme_iter_example() { id: Some(Ident::from(Cow::Borrowed("helloWorld"))), params: vec![], body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { + optional: false, callee: Box::new(Expr::ident_from(Cow::Borrowed("alert"))), arguments: vec![Expr::Lit(Lit::String(StringLit::Single( Cow::Borrowed("Hello world").into(), @@ -770,6 +772,7 @@ fn class_extended_by_call() { let callee = Expr::Ident(callee); let callee = Box::new(callee); let super_class = CallExpr { + optional: false, callee, arguments: vec![], }; @@ -797,6 +800,7 @@ fn class_anon_extended_by_call() { let callee = Expr::Ident(callee); let callee = Box::new(callee); let super_class = CallExpr { + optional: false, callee, arguments: vec![], }; @@ -962,6 +966,7 @@ fn super_tagged_template_in_ctor() { body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::TaggedTemplate( TaggedTemplateExpr { tag: Box::new(Expr::Call(CallExpr { + optional: false, callee: Box::new(Expr::Super), arguments: vec![], })), @@ -991,6 +996,7 @@ fn super_in_new_class_expr() { .unwrap(); let tokens = p.parse().unwrap(); let call_super = CallExpr { + optional: false, callee: Box::new(Expr::Super), arguments: vec![], }; @@ -1006,6 +1012,7 @@ fn super_in_new_class_expr() { }; let arrow_call_super = Expr::ArrowFunc(arrow_call_super); let call_arrow = CallExpr { + optional: false, callee: Box::new(arrow_call_super), arguments: vec![], }; @@ -1403,6 +1410,93 @@ fn nullish_coalesce() { run_spanned_test("b ?? false", false).unwrap(); } +#[test] +fn optional_chaining1() { + run_spanned_test("a?.b", false).unwrap(); +} + +#[test] +fn optional_chaining2() { + run_spanned_test("a?.['b']", false).unwrap(); +} + +#[test] +fn optional_chaining3() { + run_spanned_test("a?.()", false).unwrap(); +} + +#[test] +fn optional_chaining4() { + run_test("a?.()", false).unwrap(); +} + +#[test] +fn optional_chaining5() { + run_test("a?.b", false).unwrap(); +} + +#[test] +fn optional_chaining6() { + run_test("a?.['b']", false).unwrap(); +} + +#[test] +fn optional_chaining7() { + run_spanned_test("a?.()", false).unwrap(); +} + +#[test] +fn optional_chaining8() { + let js = "a?.()"; + let p = Parser::new(&js).unwrap(); + let f: ProgramPart> = ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { + optional: true, + callee: Box::new(Expr::Ident(Ident { + name: Cow::from("a"), + })), + arguments: vec![], + }))); + for part in p { + assert_eq!(part.unwrap(), f); + } +} + +#[test] +fn optional_chaining9() { + let js = "a?.b"; + let p = Parser::new(&js).unwrap(); + let f: ProgramPart> = + ProgramPart::Stmt(Stmt::Expr(Expr::Member(MemberExpr { + object: Box::new(Expr::Ident(Ident { + name: Cow::from("a"), + })), + property: Box::new(Expr::Ident(Ident { + name: Cow::from("b"), + })), + indexer: MemberIndexer::Optional, + }))); + for part in p { + assert_eq!(part.unwrap(), f); + } +} + +#[test] +fn optional_chaining10() { + let js = "a?.['b']"; + let p = Parser::new(&js).unwrap(); + let f: ProgramPart> = + ProgramPart::Stmt(Stmt::Expr(Expr::Member(MemberExpr { + object: Box::new(Expr::Ident(Ident { + name: Cow::from("a"), + })), + property: Box::new(Expr::Lit(Lit::String(StringLit::Single(Cow::from("b"))))), + indexer: MemberIndexer::OptionalComputed, + }))); + for part in p { + assert_eq!(part.unwrap(), f); + } +} + fn run_test(js: &str, as_mod: bool) -> Result<(), ressa::Error> { let p = generate_program(js, as_mod); log::debug!("{:#?}", p);