ezno_parser/declarations/
variable.rs

1use derive_partial_eq_extras::PartialEqExtras;
2use get_field_by_type::GetFieldByType;
3use iterator_endiate::EndiateIteratorExt;
4
5use crate::{
6	derive_ASTNode, errors::parse_lexing_error, expressions::operators::COMMA_PRECEDENCE,
7	throw_unexpected_token_with_token, ASTNode, Expression, ParseError, ParseErrors, ParseOptions,
8	ParseResult, Span, TSXKeyword, TSXToken, Token, TokenReader, TypeAnnotation, VariableField,
9	WithComment,
10};
11use visitable_derive::Visitable;
12
13/// This is for `const` declarations vs `let` and `var` declarations
14pub trait DeclarationExpression:
15	PartialEq + Clone + std::fmt::Debug + Send + std::marker::Sync + crate::Visitable
16{
17	fn expression_from_reader(
18		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
19		state: &mut crate::ParsingState,
20		options: &ParseOptions,
21	) -> ParseResult<Self>;
22
23	fn expression_to_string_from_buffer<T: source_map::ToString>(
24		&self,
25		buf: &mut T,
26		options: &crate::ToStringOptions,
27		local: crate::LocalToStringInformation,
28	);
29
30	fn get_declaration_position(&self) -> Option<Span>;
31
32	fn as_option_expression_ref(&self) -> Option<&Expression>;
33
34	fn as_option_expr_mut(&mut self) -> Option<&mut Expression>;
35}
36
37impl DeclarationExpression for Option<Expression> {
38	fn expression_from_reader(
39		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
40		state: &mut crate::ParsingState,
41		options: &ParseOptions,
42		// expect_value: bool,
43	) -> ParseResult<Self> {
44		if let Some(Token(_, start)) = reader.conditional_next(|t| matches!(t, TSXToken::Assign)) {
45			Expression::from_reader_with_precedence(
46				reader,
47				state,
48				options,
49				COMMA_PRECEDENCE,
50				Some(start),
51			)
52			.map(Some)
53		} else {
54			Ok(None)
55		}
56	}
57
58	fn expression_to_string_from_buffer<T: source_map::ToString>(
59		&self,
60		buf: &mut T,
61		options: &crate::ToStringOptions,
62		local: crate::LocalToStringInformation,
63	) {
64		if let Some(expr) = self {
65			buf.push_str(if options.pretty { " = " } else { "=" });
66			expr.to_string_from_buffer(buf, options, local);
67		}
68	}
69
70	fn get_declaration_position(&self) -> Option<Span> {
71		self.as_ref().map(ASTNode::get_position)
72	}
73
74	fn as_option_expression_ref(&self) -> Option<&Expression> {
75		self.as_ref()
76	}
77
78	fn as_option_expr_mut(&mut self) -> Option<&mut Expression> {
79		self.as_mut()
80	}
81}
82
83impl DeclarationExpression for crate::Expression {
84	fn expression_from_reader(
85		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
86		state: &mut crate::ParsingState,
87		options: &ParseOptions,
88	) -> ParseResult<Self> {
89		let start = reader.expect_next(TSXToken::Assign)?;
90		Expression::from_reader_with_precedence(
91			reader,
92			state,
93			options,
94			COMMA_PRECEDENCE,
95			Some(start),
96		)
97	}
98
99	fn expression_to_string_from_buffer<T: source_map::ToString>(
100		&self,
101		buf: &mut T,
102		options: &crate::ToStringOptions,
103		local: crate::LocalToStringInformation,
104	) {
105		buf.push_str(if options.pretty { " = " } else { "=" });
106		ASTNode::to_string_from_buffer(self, buf, options, local);
107	}
108
109	fn get_declaration_position(&self) -> Option<Span> {
110		Some(ASTNode::get_position(self))
111	}
112
113	fn as_option_expression_ref(&self) -> Option<&Expression> {
114		Some(self)
115	}
116
117	fn as_option_expr_mut(&mut self) -> Option<&mut Expression> {
118		Some(self)
119	}
120}
121
122/// Represents a name =
123#[apply(derive_ASTNode)]
124#[derive(Debug, Clone, PartialEqExtras, Visitable, get_field_by_type::GetFieldByType)]
125#[get_field_by_type_target(Span)]
126#[partial_eq_ignore_types(Span)]
127pub struct VariableDeclarationItem<TExpr: DeclarationExpression> {
128	pub name: WithComment<VariableField>,
129	pub type_annotation: Option<TypeAnnotation>,
130	pub expression: TExpr,
131	pub position: Span,
132}
133
134impl<TExpr: DeclarationExpression + 'static> ASTNode for VariableDeclarationItem<TExpr> {
135	fn from_reader(
136		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
137		state: &mut crate::ParsingState,
138		options: &ParseOptions,
139	) -> ParseResult<Self> {
140		let name = WithComment::<VariableField>::from_reader(reader, state, options)?;
141		let type_annotation = if reader
142			.conditional_next(|tok| options.type_annotations && matches!(tok, TSXToken::Colon))
143			.is_some()
144		{
145			let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
146			Some(type_annotation)
147		} else {
148			None
149		};
150		let expression = TExpr::expression_from_reader(reader, state, options)?;
151		let position = name.get_position().union(
152			expression
153				.get_declaration_position()
154				.or(type_annotation.as_ref().map(ASTNode::get_position))
155				.unwrap_or(name.get_position()),
156		);
157
158		Ok(Self { name, type_annotation, expression, position })
159	}
160
161	fn to_string_from_buffer<T: source_map::ToString>(
162		&self,
163		buf: &mut T,
164		options: &crate::ToStringOptions,
165		local: crate::LocalToStringInformation,
166	) {
167		self.name.to_string_from_buffer(buf, options, local);
168		if let (true, Some(type_annotation)) =
169			(options.include_type_annotations, &self.type_annotation)
170		{
171			buf.push_str(": ");
172			type_annotation.to_string_from_buffer(buf, options, local);
173		}
174
175		self.expression.expression_to_string_from_buffer(buf, options, local);
176	}
177
178	fn get_position(&self) -> Span {
179		*self.get()
180	}
181}
182
183#[apply(derive_ASTNode)]
184#[derive(Debug, Clone, PartialEqExtras, Visitable, get_field_by_type::GetFieldByType)]
185#[partial_eq_ignore_types(Span)]
186#[get_field_by_type_target(Span)]
187pub enum VariableDeclaration {
188	ConstDeclaration {
189		declarations: Vec<VariableDeclarationItem<Expression>>,
190		position: Span,
191	},
192	LetDeclaration {
193		declarations: Vec<VariableDeclarationItem<Option<Expression>>>,
194		position: Span,
195	},
196}
197
198#[derive(Debug, PartialEq, Eq, Clone, Copy, Visitable)]
199#[apply(derive_ASTNode)]
200pub enum VariableDeclarationKeyword {
201	Const,
202	Let,
203}
204
205impl VariableDeclarationKeyword {
206	#[must_use]
207	pub fn is_token_variable_keyword(token: &TSXToken) -> bool {
208		matches!(token, TSXToken::Keyword(TSXKeyword::Const | TSXKeyword::Let))
209	}
210
211	pub(crate) fn from_token(token: Token<TSXToken, crate::TokenStart>) -> ParseResult<Self> {
212		match token {
213			Token(TSXToken::Keyword(TSXKeyword::Const), _) => Ok(Self::Const),
214			Token(TSXToken::Keyword(TSXKeyword::Let), _) => Ok(Self::Let),
215			token => throw_unexpected_token_with_token(
216				token,
217				&[TSXToken::Keyword(TSXKeyword::Const), TSXToken::Keyword(TSXKeyword::Let)],
218			),
219		}
220	}
221
222	#[must_use]
223	pub fn as_str(&self) -> &str {
224		match self {
225			VariableDeclarationKeyword::Const => "const ",
226			VariableDeclarationKeyword::Let => "let ",
227		}
228	}
229}
230
231impl ASTNode for VariableDeclaration {
232	fn from_reader(
233		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
234		state: &mut crate::ParsingState,
235		options: &ParseOptions,
236	) -> ParseResult<Self> {
237		let token = reader.next().ok_or_else(parse_lexing_error)?;
238		let start = token.1;
239		let kind = VariableDeclarationKeyword::from_token(token)?;
240		Ok(match kind {
241			VariableDeclarationKeyword::Let => {
242				state.append_keyword_at_pos(start.0, TSXKeyword::Let);
243				let mut declarations = Vec::new();
244				loop {
245					// Some people like to have trailing comments in declarations ?
246					if reader.peek().is_some_and(|t| t.0.is_comment()) {
247						let (..) = TSXToken::try_into_comment(reader.next().unwrap()).unwrap();
248						if reader.peek_n(1).is_some_and(|t| !t.0.is_identifier_or_ident()) {
249							break;
250						}
251						continue;
252					}
253
254					let value = VariableDeclarationItem::<Option<Expression>>::from_reader(
255						reader, state, options,
256					)?;
257
258					if value.expression.is_none()
259						&& !matches!(value.name.get_ast_ref(), VariableField::Name(_))
260					{
261						return Err(crate::ParseError::new(
262							crate::ParseErrors::DestructuringRequiresValue,
263							value.name.get_ast_ref().get_position(),
264						));
265					}
266
267					declarations.push(value);
268					if let Some(Token(TSXToken::Comma, _)) = reader.peek() {
269						reader.next();
270					} else {
271						break;
272					}
273				}
274
275				let position = if let Some(last) = declarations.last() {
276					start.union(last.get_position())
277				} else {
278					let position = start.with_length(3);
279					if options.partial_syntax {
280						position
281					} else {
282						return Err(ParseError::new(ParseErrors::ExpectedDeclaration, position));
283					}
284				};
285
286				VariableDeclaration::LetDeclaration { position, declarations }
287			}
288			VariableDeclarationKeyword::Const => {
289				state.append_keyword_at_pos(start.0, TSXKeyword::Const);
290				let mut declarations = Vec::new();
291				loop {
292					// Some people like to have trailing comments in declarations ?
293					if reader.peek().is_some_and(|t| t.0.is_comment()) {
294						let (..) = TSXToken::try_into_comment(reader.next().unwrap()).unwrap();
295						if reader.peek_n(1).is_some_and(|t| !t.0.is_identifier_or_ident()) {
296							break;
297						}
298						continue;
299					}
300
301					let value =
302						VariableDeclarationItem::<Expression>::from_reader(reader, state, options)?;
303					declarations.push(value);
304					if let Some(Token(TSXToken::Comma, _)) = reader.peek() {
305						reader.next();
306					} else {
307						break;
308					}
309				}
310
311				let position = if let Some(last) = declarations.last() {
312					start.union(last.get_position())
313				} else {
314					let position = start.with_length(3);
315					if options.partial_syntax {
316						position
317					} else {
318						return Err(ParseError::new(ParseErrors::ExpectedDeclaration, position));
319					}
320				};
321
322				VariableDeclaration::ConstDeclaration { position, declarations }
323			}
324		})
325	}
326
327	fn to_string_from_buffer<T: source_map::ToString>(
328		&self,
329		buf: &mut T,
330		options: &crate::ToStringOptions,
331		local: crate::LocalToStringInformation,
332	) {
333		match self {
334			VariableDeclaration::LetDeclaration { declarations, .. } => {
335				if declarations.is_empty() {
336					return;
337				}
338				buf.push_str("let ");
339				let available_space = u32::from(options.max_line_length)
340					.saturating_sub(buf.characters_on_current_line());
341
342				let split_lines = crate::are_nodes_over_length(
343					declarations.iter(),
344					options,
345					local,
346					Some(available_space),
347					true,
348				);
349				declarations_to_string(declarations, buf, options, local, split_lines);
350			}
351			VariableDeclaration::ConstDeclaration { declarations, .. } => {
352				if declarations.is_empty() {
353					return;
354				}
355				buf.push_str("const ");
356				let available_space = u32::from(options.max_line_length)
357					.saturating_sub(buf.characters_on_current_line());
358
359				let split_lines = crate::are_nodes_over_length(
360					declarations.iter(),
361					options,
362					local,
363					Some(available_space),
364					true,
365				);
366				declarations_to_string(declarations, buf, options, local, split_lines);
367			}
368		}
369	}
370
371	fn get_position(&self) -> Span {
372		*self.get()
373	}
374}
375
376impl VariableDeclaration {
377	#[must_use]
378	pub fn is_constant(&self) -> bool {
379		matches!(self, VariableDeclaration::ConstDeclaration { .. })
380	}
381}
382
383pub(crate) fn declarations_to_string<
384	T: source_map::ToString,
385	U: DeclarationExpression + 'static,
386>(
387	declarations: &[VariableDeclarationItem<U>],
388	buf: &mut T,
389	options: &crate::ToStringOptions,
390	local: crate::LocalToStringInformation,
391	separate_lines: bool,
392) {
393	for (at_end, declaration) in declarations.iter().endiate() {
394		declaration.to_string_from_buffer(buf, options, local);
395		if !at_end {
396			buf.push(',');
397			if separate_lines {
398				buf.push_new_line();
399				options.add_indent(local.depth + 1, buf);
400			} else {
401				options.push_gap_optionally(buf);
402			}
403		}
404	}
405}