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 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 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 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 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 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}