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
13pub 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 ) -> 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#[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 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 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}