Skip to main content

ezno_parser/statements/
try_catch_statement.rs

1use crate::{
2	derive_ASTNode, ASTNode, Block, ParseError, ParseErrors, TSXKeyword, TSXToken, TypeAnnotation,
3	VariableField, WithComment,
4};
5use source_map::Span;
6use tokenizer_lib::Token;
7use visitable_derive::Visitable;
8
9#[cfg_attr(target_family = "wasm", tsify::declare)]
10pub type ExceptionVarField = WithComment<VariableField>;
11
12#[apply(derive_ASTNode)]
13#[derive(Debug, PartialEq, Clone, Visitable, get_field_by_type::GetFieldByType)]
14#[get_field_by_type_target(Span)]
15pub struct TryCatchStatement {
16	pub try_inner: Block,
17	pub catch_inner: Option<Block>,
18	pub exception_var: Option<(ExceptionVarField, Option<TypeAnnotation>)>,
19	pub finally_inner: Option<Block>,
20	pub position: Span,
21}
22
23impl ASTNode for TryCatchStatement {
24	fn get_position(&self) -> Span {
25		self.position
26	}
27
28	fn from_reader(
29		reader: &mut impl tokenizer_lib::TokenReader<TSXToken, crate::TokenStart>,
30		state: &mut crate::ParsingState,
31		options: &crate::ParseOptions,
32	) -> Result<Self, crate::ParseError> {
33		let start = state.expect_keyword(reader, TSXKeyword::Try)?;
34		let try_inner = Block::from_reader(reader, state, options)?;
35
36		let mut catch_inner: Option<Block> = None;
37		let mut exception_var: Option<(ExceptionVarField, Option<TypeAnnotation>)> = None;
38
39		// Optional `catch` clause
40		if let Some(Token(TSXToken::Keyword(TSXKeyword::Catch), _)) = reader.peek() {
41			state.append_keyword_at_pos(reader.next().unwrap().1 .0, TSXKeyword::Catch);
42
43			// Optional exception variable field `catch (e)`
44			if let Some(Token(TSXToken::OpenParentheses, _)) = reader.peek() {
45				reader.expect_next(TSXToken::OpenParentheses)?;
46				let variable_field =
47					WithComment::<VariableField>::from_reader(reader, state, options)?;
48
49				// Optional type reference `catch (e: type)`
50				let mut exception_var_type: Option<TypeAnnotation> = None;
51				if reader
52					.conditional_next(|tok| {
53						options.type_annotations && matches!(tok, TSXToken::Colon)
54					})
55					.is_some()
56				{
57					exception_var_type = Some(TypeAnnotation::from_reader(reader, state, options)?);
58				}
59				exception_var = Some((variable_field, exception_var_type));
60
61				reader.expect_next(TSXToken::CloseParentheses)?;
62			}
63
64			catch_inner = Some(Block::from_reader(reader, state, options)?);
65		}
66
67		// Optional `finally` clause
68		let mut finally_inner: Option<Block> = None;
69		if let Some(Token(TSXToken::Keyword(TSXKeyword::Finally), _)) = reader.peek() {
70			state.append_keyword_at_pos(reader.next().unwrap().1 .0, TSXKeyword::Finally);
71			finally_inner = Some(Block::from_reader(reader, state, options)?);
72		}
73
74		// Determine span based on which clauses are present
75		let position: Span = if let Some(finally_block) = &finally_inner {
76			start.union(finally_block.get_position())
77		} else if let Some(catch_block) = &catch_inner {
78			start.union(catch_block.get_position())
79		} else {
80			// Parse error if neither catch nor finally clause is present
81			return Err(ParseError::new(
82				ParseErrors::ExpectedCatchOrFinally,
83				reader.next().unwrap().get_span(),
84			));
85		};
86
87		Ok(Self { try_inner, catch_inner, exception_var, finally_inner, position })
88	}
89
90	fn to_string_from_buffer<T: source_map::ToString>(
91		&self,
92		buf: &mut T,
93		options: &crate::ToStringOptions,
94		local: crate::LocalToStringInformation,
95	) {
96		// Required `try` block
97		buf.push_str("try");
98		options.push_gap_optionally(buf);
99		self.try_inner.to_string_from_buffer(buf, options, local.next_level());
100
101		// Optional `catch` block
102		if let Some(catch) = &self.catch_inner {
103			options.push_gap_optionally(buf);
104			buf.push_str("catch");
105			options.push_gap_optionally(buf);
106
107			// Optional exception variable: `catch (e)`
108			if let Some((exception_var, exception_var_type)) = &self.exception_var {
109				buf.push('(');
110				exception_var.to_string_from_buffer(buf, options, local);
111
112				// Optional type annotation: `catch (e: any)`
113				if let Some(exception_var_type) = exception_var_type {
114					buf.push_str(": ");
115					exception_var_type.to_string_from_buffer(buf, options, local);
116				}
117				buf.push(')');
118				options.push_gap_optionally(buf);
119			}
120
121			catch.to_string_from_buffer(buf, options, local.next_level());
122		}
123
124		// Optional `finally` block
125		if let Some(finally) = &self.finally_inner {
126			options.push_gap_optionally(buf);
127			buf.push_str("finally");
128			options.push_gap_optionally(buf);
129			finally.to_string_from_buffer(buf, options, local.next_level());
130		}
131	}
132}