diff --git a/checker/specification/Cargo.toml b/checker/specification/Cargo.toml index e8f0492b..f4afd7fc 100644 --- a/checker/specification/Cargo.toml +++ b/checker/specification/Cargo.toml @@ -7,8 +7,8 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # Prevent this from interfering with workspaces -# [workspace] -# members = ["."] +[workspace] +members = ["."] [[test]] name = "specification_test" diff --git a/parser/src/declarations/classes/class_member.rs b/parser/src/declarations/classes/class_member.rs index 36abbb99..ced80933 100644 --- a/parser/src/declarations/classes/class_member.rs +++ b/parser/src/declarations/classes/class_member.rs @@ -57,12 +57,11 @@ impl ASTNode for ClassMember { state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - if let Some(Token(TSXToken::MultiLineComment(_), _)) = reader.peek() { - let Some(Token(TSXToken::MultiLineComment(c), start)) = reader.next() else { + if reader.peek().map_or(false, |t| t.0.is_comment()) { + let Ok((comment, span)) = TSXToken::try_into_comment(reader.next().unwrap()) else { unreachable!() }; - let with_length = start.with_length(c.len() + 2); - return Ok(Self::Comment(c, with_length)); + return Ok(Self::Comment(comment, span)); } if let Some(Token(TSXToken::Keyword(TSXKeyword::Constructor), _)) = reader.peek() { @@ -177,16 +176,6 @@ impl ASTNode for ClassMember { } } -impl ClassMember { - // pub fn get_property_id(&self) -> Option { - // match self { - // ClassMember::Method(_, ClassMethod { key, .. }) - // | ClassMember::Property(_, ClassProperty { key, .. }) => Some(key.get_ast().get_property_id()), - // ClassMember::Constructor { .. } => None, - // } - // } -} - impl ClassFunction { fn from_reader_with_config( reader: &mut impl TokenReader, @@ -210,10 +199,6 @@ impl FunctionBased for ClassFunctionBase { type Header = Option; type Name = WithComment>; - // fn get_chain_variable(this: &FunctionBase) -> ChainVariable { - // ChainVariable::UnderClassMethod(this.body.1) - // } - fn header_and_name_from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, diff --git a/parser/src/expressions/mod.rs b/parser/src/expressions/mod.rs index 83b6ad6c..c6f268f8 100644 --- a/parser/src/expressions/mod.rs +++ b/parser/src/expressions/mod.rs @@ -465,7 +465,7 @@ impl Expression { }; let (arguments, end) = if reader - .conditional_next(|token| *token == TSXToken::OpenChevron) + .conditional_next(|token| *token == TSXToken::OpenParentheses) .is_some() { parse_bracketed(reader, state, options, None, TSXToken::CloseParentheses) @@ -826,13 +826,14 @@ impl Expression { falsy_result: Box::new(rhs), }; } - TSXToken::OpenParentheses => { + TSXToken::OpenParentheses | TSXToken::OptionalCall => { if AssociativityDirection::LeftToRight .should_return(parent_precedence, FUNCTION_CALL_PRECEDENCE) { return Ok(top); } - reader.next(); + let next = reader.next().unwrap(); + let is_optional = matches!(next.0, TSXToken::OptionalCall); let (arguments, end) = parse_bracketed(reader, state, options, None, TSXToken::CloseParentheses)?; let position = top.get_position().union(end); @@ -841,16 +842,18 @@ impl Expression { type_arguments: None, arguments, position, - is_optional: false, + is_optional, }; } - TSXToken::OpenBracket => { + TSXToken::OpenBracket | TSXToken::OptionalIndex => { if AssociativityDirection::LeftToRight .should_return(parent_precedence, INDEX_PRECEDENCE) { return Ok(top); } - reader.next(); + let next = reader.next().unwrap(); + let is_optional = matches!(next.0, TSXToken::OptionalIndex); + let indexer = MultipleExpression::from_reader(reader, state, options)?; let end = reader.expect_next_get_end(TSXToken::CloseBracket)?; let position = top.get_position().union(end); @@ -858,7 +861,7 @@ impl Expression { position, indexee: Box::new(top), indexer: Box::new(indexer), - is_optional: false, + is_optional, }; } TSXToken::TemplateLiteralStart => { @@ -877,75 +880,15 @@ impl Expression { ); top = template_lit.map(Expression::TemplateLiteral)?; } - TSXToken::OptionalChain => { - if AssociativityDirection::LeftToRight - .should_return(parent_precedence, MEMBER_ACCESS_PRECEDENCE) - { - return Ok(top); - } - let _ = reader.next().unwrap(); - let token = reader.next().ok_or_else(parse_lexing_error)?; - top = match token { - Token(TSXToken::OpenBracket, _start) => { - let indexer = MultipleExpression::from_reader(reader, state, options)?; - let end = reader.expect_next_get_end(TSXToken::CloseBracket)?; - let position = top.get_position().union(end); - Expression::Index { - position, - indexee: Box::new(top), - indexer: Box::new(indexer), - is_optional: false, - } - } - Token(TSXToken::OpenParentheses, _start) => { - let (arguments, end) = parse_bracketed( - reader, - state, - options, - Some(TSXToken::CloseParentheses), - TSXToken::CloseParentheses, - )?; - let position = top.get_position().union(end); - Expression::FunctionCall { - function: Box::new(top), - type_arguments: None, - arguments, - position, - is_optional: true, - } - } - token => { - let (property, position) = - if let Token(TSXToken::Cursor(cursor_id), start) = token { - ( - PropertyReference::Cursor(cursor_id.into_cursor()), - start.with_length(1), - ) - } else { - let is_private = reader - .conditional_next(|t| matches!(t, TSXToken::HashTag)) - .is_some(); - let (property, position) = - token_as_identifier(token, "variable reference")?; - (PropertyReference::Standard { property, is_private }, position) - }; - let position = top.get_position().union(&position); - Expression::PropertyAccess { - parent: Box::new(top), - property, - position, - is_optional: true, - } - } - } - } - TSXToken::Dot => { + TSXToken::Dot | TSXToken::OptionalChain => { if AssociativityDirection::LeftToRight .should_return(parent_precedence, MEMBER_ACCESS_PRECEDENCE) { return Ok(top); } - let _ = reader.next().unwrap(); + let next = reader.next().unwrap(); + let is_optional = matches!(next.0, TSXToken::OptionalChain); + let token = reader.next().ok_or_else(parse_lexing_error)?; let (property, position) = if let Token(TSXToken::Cursor(cursor_id), start) = token @@ -963,7 +906,7 @@ impl Expression { parent: Box::new(top), property, position, - is_optional: false, + is_optional, }; } TSXToken::Assign => { @@ -1376,13 +1319,16 @@ impl Expression { buf.push(')'); } } - Self::Index { indexee: expression, indexer, .. } => { + Self::Index { indexee: expression, indexer, is_optional, .. } => { expression.to_string_from_buffer(buf, options, depth); + if *is_optional { + buf.push_str("?."); + } buf.push('['); indexer.to_string_from_buffer(buf, options, depth); buf.push(']'); } - Self::FunctionCall { function, type_arguments, arguments, .. } => { + Self::FunctionCall { function, type_arguments, arguments, is_optional, .. } => { // TODO is this okay? if let Some(ExpressionOrBlock::Expression(expression)) = self.is_iife() { expression.to_string_from_buffer(buf, options, depth); @@ -1392,6 +1338,7 @@ impl Expression { &**function, Expression::ArrowFunction(..) | Expression::ExpressionFunction(..) ); + // Fixes precedence from badly created ASTs if is_raw_function { buf.push('('); } @@ -1402,6 +1349,9 @@ impl Expression { if let (true, Some(type_arguments)) = (options.include_types, type_arguments) { to_string_bracketed(type_arguments, ('<', '>'), buf, options, depth); } + if *is_optional { + buf.push_str("?."); + } arguments_to_string(arguments, buf, options, depth); } Self::ConstructorCall { constructor, type_arguments, arguments, .. } => { @@ -1597,7 +1547,6 @@ fn is_generic_arguments(reader: &mut impl TokenReader for f64 { NumberRepresentation::Number { internal, .. } => internal, NumberRepresentation::Hex(sign, nat) | NumberRepresentation::Bin(sign, nat) - | NumberRepresentation::Octal(sign, nat) => sign.apply(nat as f64), + | NumberRepresentation::Octal(sign, nat, _) => sign.apply(nat as f64), NumberRepresentation::BigInt(..) => todo!(), } } @@ -592,7 +593,7 @@ impl FromStr for NumberRepresentation { return Err(s.to_owned()); } } - Ok(Self::Octal(sign, number)) + Ok(Self::Octal(sign, number, !uses_character)) } None => Ok(Self::Number { internal: 0f64, @@ -645,7 +646,7 @@ impl PartialEq for NumberRepresentation { // TODO needs to do conversion (Self::Hex(l0, l1), Self::Hex(r0, r1)) => l0 == r0 && l1 == r1, (Self::Bin(l0, l1), Self::Bin(r0, r1)) => l0 == r0 && l1 == r1, - (Self::Octal(l0, l1), Self::Octal(r0, r1)) => l0 == r0 && l1 == r1, + (Self::Octal(l0, l1, _), Self::Octal(r0, r1, _)) => l0 == r0 && l1 == r1, (Self::Number { internal: l0, .. }, Self::Number { internal: r0, .. }) => l0 == r0, _ => core::mem::discriminant(self) == core::mem::discriminant(other), } @@ -668,7 +669,10 @@ impl NumberRepresentation { NumberRepresentation::Bin(sign, value) => { format!("{sign}0b{value:#b}") } - NumberRepresentation::Octal(sign, value) => { + NumberRepresentation::Octal(sign, value, true) => { + format!("{sign}0{value:o}") + } + NumberRepresentation::Octal(sign, value, false) => { format!("{sign}0o{value:o}") } NumberRepresentation::Number { internal, elided_zero_before_point, trailing_point } => { diff --git a/parser/src/tokens.rs b/parser/src/tokens.rs index 7dc2e62e..f1b8aa08 100644 --- a/parser/src/tokens.rs +++ b/parser/src/tokens.rs @@ -62,6 +62,8 @@ use crate::ParseError; "?" => TSXToken::QuestionMark, "?:" => TSXToken::OptionalMember, "?." => TSXToken::OptionalChain, + "?.(" => TSXToken::OptionalCall, + "?.[" => TSXToken::OptionalIndex, "-?:" => TSXToken::NonOptionalMember, "??" => TSXToken::NullishCoalescing, "??=" => TSXToken::NullishCoalescingAssign, @@ -131,7 +133,7 @@ pub enum TSXToken { LogicalOrAssign, LogicalAndAssign, Equal, NotEqual, StrictEqual, StrictNotEqual, GreaterThanEqual, LessThanEqual, - OptionalChain, NullishCoalescing, NullishCoalescingAssign, + OptionalChain, OptionalCall, OptionalIndex, NullishCoalescing, NullishCoalescingAssign, /// `?:` OptionalMember, /// '!:` @@ -255,13 +257,15 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { | TSXToken::LogicalAnd | TSXToken::LogicalNot | TSXToken::Arrow + | TSXToken::BitwiseShiftLeft + | TSXToken::BitwiseShiftRight | TSXToken::TemplateLiteralExpressionStart | TSXToken::JSXFragmentStart => 2, - TSXToken::BitwiseShiftLeft - | TSXToken::BitwiseShiftRight - | TSXToken::Spread + TSXToken::Spread | TSXToken::StrictEqual + | TSXToken::OptionalCall + | TSXToken::OptionalIndex | TSXToken::NullishCoalescingAssign | TSXToken::LogicalOrAssign | TSXToken::LogicalAndAssign @@ -369,6 +373,20 @@ impl TSXToken { matches!(self, TSXToken::Comment(_) | TSXToken::MultiLineComment(_)) } + pub fn try_into_comment( + token: Token, + ) -> Result<(String, Span), Token> { + if let Token(TSXToken::MultiLineComment(c), d) = token { + let len = c.len(); + Ok((c, d.with_length(len + 4))) + } else if let Token(TSXToken::Comment(c), d) = token { + let len = c.len(); + Ok((c, d.with_length(len + 2))) + } else { + Err(token) + } + } + pub fn is_string_literal(&self) -> bool { matches!( self, diff --git a/parser/tests/expressions.rs b/parser/tests/expressions.rs index f1658063..e14e02ba 100644 --- a/parser/tests/expressions.rs +++ b/parser/tests/expressions.rs @@ -25,3 +25,25 @@ param => { // let output = module.to_string(&ToStringOptions::typescript()); // assert_eq!(output, input); } + +#[test] +fn function_calls() { + let input = r#" +x(4, 5); +y.t(2, 3); +y.t<4, 2>(3); +y.t<4, Array<5>>(3); +a(y<2>(4)); +a.a?.(y<2>(4)); +a.a(...expr, y) + "# + .trim(); + + let module = + Module::from_string(input.to_owned(), Default::default(), SourceId::NULL, None).unwrap(); + + eprintln!("Module: {:#?}", module); + + let output = module.to_string(&ezno_parser::ToStringOptions::typescript()); + assert_eq!(output, input); +}