Skip to main content

ezno_parser/statements/
for_statement.rs

1use crate::{
2	ast::MultipleExpression, block::BlockOrSingleStatement,
3	declarations::variable::VariableDeclaration, derive_ASTNode, ParseError, ParseErrors,
4	ParseOptions, TSXKeyword, VariableField, VariableKeyword, WithComment,
5};
6use tokenizer_lib::sized_tokens::TokenReaderWithTokenEnds;
7use visitable_derive::Visitable;
8
9use super::{
10	ASTNode, Expression, ParseResult, Span, TSXToken, Token, TokenReader, VarVariableStatement,
11};
12
13#[apply(derive_ASTNode)]
14#[derive(Debug, Clone, PartialEq, Visitable, get_field_by_type::GetFieldByType)]
15#[get_field_by_type_target(Span)]
16pub struct ForLoopStatement {
17	pub condition: ForLoopCondition,
18	pub inner: BlockOrSingleStatement,
19	pub position: Span,
20}
21
22impl ASTNode for ForLoopStatement {
23	fn get_position(&self) -> Span {
24		self.position
25	}
26
27	fn from_reader(
28		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
29		state: &mut crate::ParsingState,
30		options: &ParseOptions,
31	) -> ParseResult<Self> {
32		let start = state.expect_keyword(reader, TSXKeyword::For)?;
33		let is_await = reader
34			.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Await)))
35			.is_some();
36		let mut condition = ForLoopCondition::from_reader(reader, state, options)?;
37		if is_await {
38			if let ForLoopCondition::ForOf { is_await: ref mut a, .. } = condition {
39				*a = is_await;
40			} else {
41				return Err(ParseError::new(
42					ParseErrors::AwaitRequiresForOf,
43					condition.get_position(),
44				));
45			}
46		}
47		let inner = BlockOrSingleStatement::from_reader(reader, state, options)?;
48		let position = start.union(inner.get_position());
49		Ok(ForLoopStatement { condition, inner, position })
50	}
51
52	fn to_string_from_buffer<T: source_map::ToString>(
53		&self,
54		buf: &mut T,
55		options: &crate::ToStringOptions,
56		local: crate::LocalToStringInformation,
57	) {
58		buf.push_str("for");
59		if let ForLoopCondition::ForOf { is_await: true, .. } = self.condition {
60			buf.push_str(" await");
61		}
62		options.push_gap_optionally(buf);
63		self.condition.to_string_from_buffer(buf, options, local);
64		options.push_gap_optionally(buf);
65		self.inner.to_string_from_buffer(buf, options, local.next_level());
66	}
67}
68
69#[derive(Debug, Clone, PartialEq, Visitable)]
70#[apply(derive_ASTNode)]
71pub enum ForLoopStatementInitialiser {
72	VariableDeclaration(VariableDeclaration),
73	VarStatement(VarVariableStatement),
74	Expression(MultipleExpression),
75}
76
77#[derive(Debug, Clone, PartialEq, Visitable)]
78#[apply(derive_ASTNode)]
79pub enum ForLoopCondition {
80	ForOf {
81		keyword: Option<VariableKeyword>,
82		variable: WithComment<VariableField>,
83		of: Expression,
84		is_await: bool,
85		position: Span,
86	},
87	ForIn {
88		keyword: Option<VariableKeyword>,
89		variable: WithComment<VariableField>,
90		/// Yes `of` is single expression, `in` is multiple
91		r#in: MultipleExpression,
92		position: Span,
93	},
94	Statements {
95		initialiser: Option<ForLoopStatementInitialiser>,
96		condition: Option<MultipleExpression>,
97		afterthought: Option<MultipleExpression>,
98		position: Span,
99	},
100}
101
102impl ASTNode for ForLoopCondition {
103	fn from_reader(
104		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
105		state: &mut crate::ParsingState,
106		options: &ParseOptions,
107	) -> ParseResult<Self> {
108		reader.expect_next(TSXToken::OpenParentheses)?;
109		// Figure out if after variable declaration there exists a "=", "in" or a "of"
110		let mut destructuring_depth = 0;
111		let mut ate_variable_specifier = false;
112		let next = reader.scan(|token, _| {
113			if ate_variable_specifier {
114				match token {
115					TSXToken::OpenBrace | TSXToken::OpenBracket => destructuring_depth += 1,
116					TSXToken::CloseBrace | TSXToken::CloseBracket => destructuring_depth -= 1,
117					_ => {}
118				}
119				destructuring_depth == 0
120			} else {
121				ate_variable_specifier = true;
122				!VariableKeyword::is_token_variable_keyword(token)
123			}
124		});
125
126		let condition = match next {
127			Some(Token(TSXToken::Keyword(TSXKeyword::Of), _)) => {
128				let (start, keyword) = reader
129					.conditional_next(VariableKeyword::is_token_variable_keyword)
130					.map(|token| (token.1, VariableKeyword::from_reader(token).unwrap()))
131					.unzip();
132
133				let variable = WithComment::<VariableField>::from_reader(reader, state, options)?;
134
135				let _ = state.expect_keyword(reader, TSXKeyword::Of)?;
136
137				let of = Expression::from_reader(reader, state, options)?;
138				let position = start
139					.unwrap_or_else(|| variable.get_position().get_start())
140					.union(of.get_position());
141
142				// Not great, set from above
143				Self::ForOf { variable, keyword, of, position, is_await: false }
144			}
145			Some(Token(TSXToken::Keyword(TSXKeyword::In), _)) => {
146				let (start, keyword) = reader
147					.conditional_next(VariableKeyword::is_token_variable_keyword)
148					.map(|token| (token.1, VariableKeyword::from_reader(token).unwrap()))
149					.unzip();
150
151				let variable = WithComment::<VariableField>::from_reader(reader, state, options)?;
152
153				let _ = state.expect_keyword(reader, TSXKeyword::In)?;
154
155				let r#in = MultipleExpression::from_reader(reader, state, options)?;
156				let position = start
157					.unwrap_or_else(|| variable.get_position().get_start())
158					.union(r#in.get_position());
159				Self::ForIn { variable, keyword, r#in, position }
160			}
161			_ => {
162				let peek = reader.peek();
163				let initialiser =
164					if let Some(Token(TSXToken::Keyword(TSXKeyword::Const | TSXKeyword::Let), _)) =
165						peek
166					{
167						let declaration = VariableDeclaration::from_reader(reader, state, options)?;
168						Some(ForLoopStatementInitialiser::VariableDeclaration(declaration))
169					} else if let Some(Token(TSXToken::Keyword(TSXKeyword::Var), _)) = peek {
170						let stmt = VarVariableStatement::from_reader(reader, state, options)?;
171						Some(ForLoopStatementInitialiser::VarStatement(stmt))
172					} else if let Some(Token(TSXToken::SemiColon, _)) = peek {
173						None
174					} else {
175						let expr = MultipleExpression::from_reader(reader, state, options)?;
176						Some(ForLoopStatementInitialiser::Expression(expr))
177					};
178
179				let semi_colon_one = reader.expect_next(TSXToken::SemiColon)?;
180				let start = initialiser.as_ref().map_or(semi_colon_one, |init| match init {
181					ForLoopStatementInitialiser::VariableDeclaration(item) => {
182						item.get_position().get_start()
183					}
184					ForLoopStatementInitialiser::VarStatement(item) => {
185						item.get_position().get_start()
186					}
187					ForLoopStatementInitialiser::Expression(item) => {
188						item.get_position().get_start()
189					}
190				});
191
192				let condition = if matches!(reader.peek(), Some(Token(TSXToken::SemiColon, _))) {
193					None
194				} else {
195					Some(MultipleExpression::from_reader(reader, state, options)?)
196				};
197				let semi_colon_two = reader.expect_next_get_end(TSXToken::SemiColon)?;
198				let afterthought =
199					if matches!(reader.peek(), Some(Token(TSXToken::CloseParentheses, _))) {
200						None
201					} else {
202						Some(MultipleExpression::from_reader(reader, state, options)?)
203					};
204				let end = afterthought
205					.as_ref()
206					.map_or(semi_colon_two, |expr| expr.get_position().get_end());
207				let position = start.union(end);
208				Self::Statements { initialiser, condition, afterthought, position }
209			}
210		};
211		reader.expect_next(TSXToken::CloseParentheses)?;
212		Ok(condition)
213	}
214
215	fn to_string_from_buffer<T: source_map::ToString>(
216		&self,
217		buf: &mut T,
218		options: &crate::ToStringOptions,
219		local: crate::LocalToStringInformation,
220	) {
221		buf.push('(');
222		match self {
223			Self::ForOf { keyword, variable, of, position: _, is_await: _ } => {
224				if let Some(keyword) = keyword {
225					buf.push_str(keyword.as_str());
226				}
227				variable.to_string_from_buffer(buf, options, local);
228				// TODO whitespace here if variable is array of object destructuring
229				buf.push_str(" of ");
230				of.to_string_from_buffer(buf, options, local);
231			}
232			Self::ForIn { keyword, variable, r#in, position: _ } => {
233				if let Some(keyword) = keyword {
234					buf.push_str(keyword.as_str());
235				}
236				variable.to_string_from_buffer(buf, options, local);
237				// TODO whitespace here if variable is array of object destructuring
238				buf.push_str(" in ");
239				r#in.to_string_from_buffer(buf, options, local);
240			}
241			Self::Statements { initialiser, condition, afterthought, position: _ } => {
242				let mut large = false;
243				if options.enforce_limit_length_limit() && local.should_try_pretty_print {
244					let room = options.max_line_length as usize;
245					let mut buf = source_map::StringWithOptionalSourceMap {
246						source: String::new(),
247						source_map: None,
248						quit_after: Some(room),
249						since_new_line: 0,
250					};
251
252					if let Some(initialiser) = initialiser {
253						initialiser_to_string(initialiser, &mut buf, options, local);
254					};
255					large = buf.source.len() > room;
256					if !large {
257						if let Some(condition) = condition {
258							condition.to_string_from_buffer(&mut buf, options, local);
259						};
260						large = buf.source.len() > room;
261						if !large {
262							if let Some(afterthought) = afterthought {
263								afterthought.to_string_from_buffer(&mut buf, options, local);
264							};
265							large = buf.source.len() > room;
266						}
267					}
268				}
269				let inner_local = if large { local.next_level() } else { local };
270
271				if let Some(initialiser) = initialiser {
272					if large {
273						buf.push_new_line();
274						options.add_indent(inner_local.depth, buf);
275					}
276					initialiser_to_string(initialiser, buf, options, inner_local);
277				}
278				buf.push(';');
279				if let Some(condition) = condition {
280					if large {
281						buf.push_new_line();
282						options.add_indent(inner_local.depth, buf);
283					} else {
284						options.push_gap_optionally(buf);
285					}
286					condition.to_string_from_buffer(buf, options, inner_local);
287				}
288				buf.push(';');
289				if let Some(afterthought) = afterthought {
290					if large {
291						buf.push_new_line();
292						options.add_indent(inner_local.depth, buf);
293					} else {
294						options.push_gap_optionally(buf);
295					}
296					afterthought.to_string_from_buffer(buf, options, inner_local);
297				}
298				if large {
299					buf.push_new_line();
300					options.add_indent(local.depth, buf);
301				}
302			}
303		}
304		buf.push(')');
305	}
306
307	fn get_position(&self) -> Span {
308		match self {
309			ForLoopCondition::ForOf { position, .. }
310			| ForLoopCondition::ForIn { position, .. }
311			| ForLoopCondition::Statements { position, .. } => *position,
312		}
313	}
314}
315
316fn initialiser_to_string<T: source_map::ToString>(
317	initialiser: &ForLoopStatementInitialiser,
318	buf: &mut T,
319	options: &crate::ToStringOptions,
320	local: crate::LocalToStringInformation,
321) {
322	match initialiser {
323		ForLoopStatementInitialiser::VariableDeclaration(stmt) => {
324			stmt.to_string_from_buffer(buf, options, local);
325		}
326		ForLoopStatementInitialiser::Expression(expr) => {
327			expr.to_string_from_buffer(buf, options, local);
328		}
329		ForLoopStatementInitialiser::VarStatement(stmt) => {
330			stmt.to_string_from_buffer(buf, options, local);
331		}
332	}
333}
334
335#[cfg(test)]
336mod tests {
337	use super::ForLoopCondition;
338	use crate::{assert_matches_ast, statements::ForLoopStatement, ASTNode};
339
340	#[test]
341	fn condition_without_variable_keyword() {
342		assert_matches_ast!("(k in x)", ForLoopCondition::ForIn { .. });
343	}
344
345	#[test]
346	fn for_await() {
347		assert_matches_ast!(
348			"for await (let k of x) {}",
349			ForLoopStatement { condition: ForLoopCondition::ForOf { is_await: true, .. }, .. }
350		);
351		assert_matches_ast!(
352			"for (let k of x) {}",
353			ForLoopStatement { condition: ForLoopCondition::ForOf { is_await: false, .. }, .. }
354		);
355
356		assert!(ForLoopStatement::from_string(
357			"for await (let x = 0; x < 5; x++) {}".into(),
358			Default::default()
359		)
360		.is_err());
361	}
362}