ezno_parser/
variable_fields.rs

1/// Contains:
2/// - [`VariableId`] given to variable declaring items
3/// - [`VariableField`] for destructuring things and its nested derivatives + visiting behavior + tests for self
4use std::fmt::Debug;
5
6use crate::{
7	derive_ASTNode,
8	errors::parse_lexing_error,
9	parse_bracketed,
10	property_key::PropertyKey,
11	throw_unexpected_token_with_token,
12	tokens::token_as_identifier,
13	visiting::{ImmutableVariableOrProperty, MutableVariableOrProperty},
14	ASTNode, Expression, ListItem, Marker, ParseError, ParseErrors, ParseOptions, ParseResult,
15	Span, TSXToken, Token, VisitOptions, Visitable, WithComment,
16};
17
18use derive_partial_eq_extras::PartialEqExtras;
19use get_field_by_type::GetFieldByType;
20use iterator_endiate::EndiateIteratorExt;
21use tokenizer_lib::TokenReader;
22
23#[apply(derive_ASTNode)]
24#[derive(Debug, PartialEqExtras, Clone, GetFieldByType)]
25#[partial_eq_ignore_types(Span)]
26#[get_field_by_type_target(Span)]
27pub enum VariableIdentifier {
28	Standard(String, Span),
29	// TODO does this need Span
30	#[cfg_attr(feature = "self-rust-tokenize", self_tokenize_field(0))]
31	Marker(
32		#[cfg_attr(target_family = "wasm", tsify(type = "VariableIdentifier"))] Marker<Self>,
33		Span,
34	),
35}
36
37impl ASTNode for VariableIdentifier {
38	fn from_reader(
39		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
40		state: &mut crate::ParsingState,
41		options: &ParseOptions,
42	) -> ParseResult<Self> {
43		let (ident, span) = token_as_identifier(reader.next().unwrap(), "variable identifier")?;
44		if ident == "let" {
45			return Err(ParseError::new(ParseErrors::ReservedIdentifier, span));
46		}
47		Ok(if options.interpolation_points && ident == crate::marker::MARKER {
48			Self::Marker(state.new_partial_point_marker(span.get_start()), span)
49		} else {
50			Self::Standard(ident, span)
51		})
52	}
53
54	fn to_string_from_buffer<T: source_map::ToString>(
55		&self,
56		buf: &mut T,
57		options: &crate::ToStringOptions,
58		_local: crate::LocalToStringInformation,
59	) {
60		match self {
61			VariableIdentifier::Standard(name, _) => buf.push_str(name),
62			VariableIdentifier::Marker(_, _) => {
63				assert!(!options.expect_markers, "variable marker attempted to convert to string");
64			}
65		}
66	}
67
68	fn get_position(&self) -> Span {
69		*self.get()
70	}
71}
72
73impl VariableIdentifier {
74	#[must_use]
75	pub fn as_option_str(&self) -> Option<&str> {
76		match self {
77			VariableIdentifier::Standard(s, _) => Some(s.as_str()),
78			VariableIdentifier::Marker(_, _) => None,
79		}
80	}
81}
82
83/// A variable declaration name, used in variable declarations and function parameters.
84/// See [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
85#[derive(Debug, Clone, PartialEq)]
86#[apply(derive_ASTNode)]
87pub enum VariableField {
88	/// `x`
89	Name(VariableIdentifier),
90	/// `[x, y, z]`
91	Array {
92		members: Vec<WithComment<ArrayDestructuringField<VariableField>>>,
93		spread: Option<SpreadDestructuringField<VariableField>>,
94		position: Span,
95	},
96	/// `{ x, y: z }`.
97	Object {
98		members: Vec<WithComment<ObjectDestructuringField<VariableField>>>,
99		spread: Option<SpreadDestructuringField<VariableField>>,
100		position: Span,
101	},
102}
103
104impl ASTNode for VariableField {
105	fn from_reader(
106		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
107		state: &mut crate::ParsingState,
108		options: &ParseOptions,
109	) -> ParseResult<Self> {
110		match reader.peek().ok_or_else(parse_lexing_error)?.0 {
111			TSXToken::OpenBrace => {
112				let Token(_, start_pos) = reader.next().unwrap();
113				let (members, spread, last_pos) =
114					parse_bracketed(reader, state, options, None, TSXToken::CloseBrace)?;
115				Ok(Self::Object { members, spread, position: start_pos.union(last_pos) })
116			}
117			TSXToken::OpenBracket => {
118				let Token(_, start_pos) = reader.next().unwrap();
119				let (members, spread, end) =
120					parse_bracketed(reader, state, options, None, TSXToken::CloseBracket)?;
121				Ok(Self::Array { members, spread, position: start_pos.union(end) })
122			}
123			_ => Ok(Self::Name(VariableIdentifier::from_reader(reader, state, options)?)),
124		}
125	}
126
127	fn to_string_from_buffer<T: source_map::ToString>(
128		&self,
129		buf: &mut T,
130		options: &crate::ToStringOptions,
131		local: crate::LocalToStringInformation,
132	) {
133		match self {
134			Self::Name(identifier) => {
135				buf.add_mapping(&identifier.get_position().with_source(local.under));
136				identifier.to_string_from_buffer(buf, options, local);
137			}
138			Self::Array { members, spread, position: _ } => {
139				buf.push('[');
140				for (at_end, member) in members.iter().endiate() {
141					member.to_string_from_buffer(buf, options, local);
142					if !at_end {
143						buf.push(',');
144						options.push_gap_optionally(buf);
145					}
146				}
147				if let Some(ref spread) = spread {
148					if !members.is_empty() {
149						buf.push(',');
150						options.push_gap_optionally(buf);
151					}
152					buf.push_str("...");
153					spread.0.to_string_from_buffer(buf, options, local);
154				}
155				buf.push(']');
156			}
157			Self::Object { members, spread, position: _ } => {
158				buf.push('{');
159				options.push_gap_optionally(buf);
160				for (at_end, member) in members.iter().endiate() {
161					member.to_string_from_buffer(buf, options, local);
162					if !at_end {
163						buf.push(',');
164						options.push_gap_optionally(buf);
165					}
166				}
167				if let Some(ref spread) = spread {
168					if !members.is_empty() {
169						buf.push(',');
170						options.push_gap_optionally(buf);
171					}
172					buf.push_str("...");
173					spread.0.to_string_from_buffer(buf, options, local);
174				}
175				options.push_gap_optionally(buf);
176				buf.push('}');
177			}
178		}
179	}
180
181	fn get_position(&self) -> Span {
182		match self {
183			VariableField::Array { position, .. } | VariableField::Object { position, .. } => {
184				*position
185			}
186			VariableField::Name(id) => id.get_position(),
187		}
188	}
189}
190
191pub trait DestructuringFieldInto: ASTNode {
192	// This in an extra
193	type TypeAnnotation: Clone + PartialEq + Debug + Sync + Send + 'static;
194
195	fn type_annotation_from_reader(
196		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
197		state: &mut crate::ParsingState,
198		options: &ParseOptions,
199	) -> ParseResult<Self::TypeAnnotation>;
200}
201
202impl DestructuringFieldInto for VariableField {
203	type TypeAnnotation = Option<crate::TypeAnnotation>;
204
205	fn type_annotation_from_reader(
206		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
207		state: &mut crate::ParsingState,
208		options: &ParseOptions,
209	) -> ParseResult<Self::TypeAnnotation> {
210		if let (true, Some(Token(TSXToken::Colon, _))) =
211			(options.destructuring_type_annotation, reader.peek())
212		{
213			reader.next();
214			crate::TypeAnnotation::from_reader(reader, state, options).map(Some)
215		} else {
216			Ok(None)
217		}
218	}
219}
220
221impl DestructuringFieldInto for crate::ast::LHSOfAssignment {
222	type TypeAnnotation = ();
223
224	fn type_annotation_from_reader(
225		_reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
226		_state: &mut crate::ParsingState,
227		_options: &ParseOptions,
228	) -> ParseResult<Self::TypeAnnotation> {
229		Ok(())
230	}
231}
232
233/// For
234/// - declarations: `T = VariableField`
235/// - expressions: `T = LHSOfAssignment`
236#[derive(Debug, Clone, PartialEq, Eq)]
237#[apply(derive_ASTNode)]
238pub enum ArrayDestructuringField<T: DestructuringFieldInto> {
239	Name(T, T::TypeAnnotation, Option<Box<Expression>>),
240	Comment { content: String, is_multiline: bool, position: Span },
241	None,
242}
243
244/// Covers [`ArrayDestructuring`] AND [`ObjectDestructuringField`]
245#[derive(Debug, Clone, PartialEq, Eq, visitable_derive::Visitable)]
246#[apply(derive_ASTNode)]
247pub struct SpreadDestructuringField<T: DestructuringFieldInto>(pub Box<T>, pub Span);
248
249impl<T: DestructuringFieldInto> ListItem for WithComment<ArrayDestructuringField<T>> {
250	const EMPTY: Option<Self> = Some(WithComment::None(ArrayDestructuringField::None));
251
252	const LAST_PREFIX: Option<TSXToken> = Some(TSXToken::Spread);
253
254	type LAST = SpreadDestructuringField<T>;
255
256	fn parse_last_item(
257		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
258		state: &mut crate::ParsingState,
259		options: &ParseOptions,
260	) -> ParseResult<Self::LAST> {
261		let start = reader.expect_next(TSXToken::Spread)?;
262		let node = T::from_reader(reader, state, options)?;
263		let position = start.union(node.get_position());
264		Ok(SpreadDestructuringField(Box::new(node), position))
265	}
266}
267
268impl<T: DestructuringFieldInto> ASTNode for ArrayDestructuringField<T> {
269	fn from_reader(
270		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
271		state: &mut crate::ParsingState,
272		options: &ParseOptions,
273	) -> ParseResult<Self> {
274		let Token(token, _start) = reader.peek().ok_or_else(parse_lexing_error)?;
275		if matches!(token, TSXToken::Comma | TSXToken::CloseBracket) {
276			Ok(Self::None)
277		} else {
278			let name = T::from_reader(reader, state, options)?;
279			let annotation = T::type_annotation_from_reader(reader, state, options)?;
280			let default_value = reader
281				.conditional_next(|t| matches!(t, TSXToken::Assign))
282				.is_some()
283				.then(|| ASTNode::from_reader(reader, state, options).map(Box::new))
284				.transpose()?;
285
286			// let position =
287			// 	if let Some(ref pos) = default_value {
288			// 		key.get_position().union(pos)
289			// 	} else {
290			// 		*key.get_position()
291			// 	};
292			Ok(Self::Name(name, annotation, default_value))
293		}
294	}
295
296	fn to_string_from_buffer<U: source_map::ToString>(
297		&self,
298		buf: &mut U,
299		options: &crate::ToStringOptions,
300		local: crate::LocalToStringInformation,
301	) {
302		match self {
303			Self::Name(name, _annotation, default_value) => {
304				name.to_string_from_buffer(buf, options, local);
305				if let Some(default_value) = default_value {
306					options.push_gap_optionally(buf);
307					buf.push('=');
308					options.push_gap_optionally(buf);
309					default_value.to_string_from_buffer(buf, options, local);
310				}
311			}
312			Self::Comment { content, is_multiline: _is_multiline, position: _ } => {
313				if options.should_add_comment(content) {
314					buf.push_str("/*");
315					buf.push_str(content);
316					buf.push_str("*/");
317				}
318			}
319			Self::None => {}
320		}
321	}
322
323	fn get_position(&self) -> Span {
324		match self {
325			ArrayDestructuringField::Comment { position, .. } => *position,
326			// TODO misses out optional expression
327			ArrayDestructuringField::Name(vf, ..) => vf.get_position(),
328			ArrayDestructuringField::None => source_map::Nullable::NULL,
329		}
330	}
331}
332
333/// For
334/// - declarations: `T = VariableField`
335/// - expressions: `T = LHSOfAssignment`
336#[apply(derive_ASTNode)]
337#[derive(Debug, Clone, PartialEqExtras, get_field_by_type::GetFieldByType)]
338#[get_field_by_type_target(Span)]
339#[partial_eq_ignore_types(Span)]
340pub enum ObjectDestructuringField<T: DestructuringFieldInto> {
341	/// `{ x }` and (annoyingly) `{ x = 2 }`
342	Name(VariableIdentifier, T::TypeAnnotation, Option<Box<Expression>>, Span),
343	/// `{ x: y }`
344	Map {
345		from: PropertyKey<crate::property_key::AlwaysPublic>,
346		annotation: T::TypeAnnotation,
347		name: WithComment<T>,
348		default_value: Option<Box<Expression>>,
349		position: Span,
350	},
351}
352
353impl<T: DestructuringFieldInto> ListItem for WithComment<ObjectDestructuringField<T>> {
354	const LAST_PREFIX: Option<TSXToken> = Some(TSXToken::Spread);
355
356	type LAST = SpreadDestructuringField<T>;
357
358	fn parse_last_item(
359		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
360		state: &mut crate::ParsingState,
361		options: &ParseOptions,
362	) -> ParseResult<Self::LAST> {
363		let start = reader.expect_next(TSXToken::Spread)?;
364		let node = T::from_reader(reader, state, options)?;
365		let position = start.union(node.get_position());
366		Ok(SpreadDestructuringField(Box::new(node), position))
367	}
368}
369
370impl<T: DestructuringFieldInto> ASTNode for ObjectDestructuringField<T> {
371	fn from_reader(
372		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
373		state: &mut crate::ParsingState,
374		options: &ParseOptions,
375	) -> ParseResult<Self> {
376		let key = PropertyKey::from_reader(reader, state, options)?;
377		if reader.peek().is_some_and(|Token(t, _)| is_destructuring_into_marker(t, options)) {
378			reader.next();
379			let name = WithComment::<T>::from_reader(reader, state, options)?;
380			let annotation = T::type_annotation_from_reader(reader, state, options)?;
381
382			let default_value = reader
383				.conditional_next(|t| matches!(t, TSXToken::Assign))
384				.is_some()
385				.then(|| Expression::from_reader(reader, state, options).map(Box::new))
386				.transpose()?;
387
388			let position = if let Some(ref dv) = default_value {
389				key.get_position().union(dv.get_position())
390			} else {
391				key.get_position()
392			};
393
394			Ok(Self::Map { from: key, annotation, name, default_value, position })
395		} else if let PropertyKey::Identifier(name, key_pos, _) = key {
396			let default_value = reader
397				.conditional_next(|t| matches!(t, TSXToken::Assign))
398				.is_some()
399				.then(|| Expression::from_reader(reader, state, options).map(Box::new))
400				.transpose()?;
401
402			let standard = VariableIdentifier::Standard(name, key_pos);
403			let annotation = T::type_annotation_from_reader(reader, state, options)?;
404			let position = if let Some(ref dv) = default_value {
405				key_pos.union(dv.get_position())
406			} else {
407				key_pos
408			};
409
410			Ok(Self::Name(standard, annotation, default_value, position))
411		} else {
412			let token = reader.next().ok_or_else(parse_lexing_error)?;
413			throw_unexpected_token_with_token(token, &[TSXToken::Colon])
414		}
415	}
416
417	fn to_string_from_buffer<U: source_map::ToString>(
418		&self,
419		buf: &mut U,
420		options: &crate::ToStringOptions,
421		local: crate::LocalToStringInformation,
422	) {
423		match self {
424			Self::Name(name, _annotation, default_value, ..) => {
425				name.to_string_from_buffer(buf, options, local);
426				if let Some(default_value) = default_value {
427					options.push_gap_optionally(buf);
428					buf.push('=');
429					options.push_gap_optionally(buf);
430					default_value.to_string_from_buffer(buf, options, local);
431				}
432			}
433			Self::Map { from, annotation: _, name: variable_name, default_value, .. } => {
434				from.to_string_from_buffer(buf, options, local);
435				buf.push(':');
436				options.push_gap_optionally(buf);
437				variable_name.to_string_from_buffer(buf, options, local);
438				if let Some(default_value) = default_value {
439					options.push_gap_optionally(buf);
440					buf.push('=');
441					options.push_gap_optionally(buf);
442					default_value.to_string_from_buffer(buf, options, local);
443				}
444			}
445		}
446	}
447
448	fn get_position(&self) -> Span {
449		*self.get()
450	}
451}
452
453/// For object literals and things with computable or literal keys
454impl Visitable for VariableField {
455	fn visit<TData>(
456		&self,
457		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
458		data: &mut TData,
459		options: &VisitOptions,
460		chain: &mut temporary_annex::Annex<crate::visiting::Chain>,
461	) {
462		match self {
463			VariableField::Name(id) => {
464				if let VariableIdentifier::Standard(name, pos) = id {
465					let item = ImmutableVariableOrProperty::VariableFieldName(name, pos);
466					visitors.visit_variable(&item, data, chain);
467				}
468			}
469			VariableField::Array { members, spread: _, .. } => {
470				members.iter().for_each(|f| f.visit(visitors, data, options, chain));
471			}
472			VariableField::Object { members, spread: _, .. } => {
473				members.iter().for_each(|f| f.visit(visitors, data, options, chain));
474			}
475		}
476	}
477
478	fn visit_mut<TData>(
479		&mut self,
480		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
481		data: &mut TData,
482		options: &VisitOptions,
483		chain: &mut temporary_annex::Annex<crate::visiting::Chain>,
484	) {
485		match self {
486			VariableField::Name(identifier) => {
487				if let VariableIdentifier::Standard(name, _span) = identifier {
488					visitors.visit_variable_mut(
489						&mut MutableVariableOrProperty::VariableFieldName(name),
490						data,
491						chain,
492					);
493				}
494			}
495			VariableField::Array { members, spread: _, .. } => {
496				members.iter_mut().for_each(|f| f.visit_mut(visitors, data, options, chain));
497			}
498			VariableField::Object { members, spread: _, .. } => {
499				members.iter_mut().for_each(|f| f.visit_mut(visitors, data, options, chain));
500			}
501		}
502	}
503}
504
505impl Visitable for WithComment<ArrayDestructuringField<VariableField>> {
506	fn visit<TData>(
507		&self,
508		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
509		data: &mut TData,
510		options: &VisitOptions,
511		chain: &mut temporary_annex::Annex<crate::Chain>,
512	) {
513		let field = self.get_ast_ref();
514		let array_destructuring_member =
515			ImmutableVariableOrProperty::ArrayDestructuringMember(field);
516		visitors.visit_variable(&array_destructuring_member, data, chain);
517		match field {
518			// TODO should be okay, no nesting here
519			ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {}
520			ArrayDestructuringField::Name(variable_field, _, expression) => {
521				variable_field.visit(visitors, data, options, chain);
522				expression.visit(visitors, data, options, chain);
523			}
524		}
525	}
526
527	fn visit_mut<TData>(
528		&mut self,
529		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
530		data: &mut TData,
531		options: &VisitOptions,
532		chain: &mut temporary_annex::Annex<crate::Chain>,
533	) {
534		let mut array_destructuring_member =
535			MutableVariableOrProperty::ArrayDestructuringMember(self.get_ast_mut());
536		visitors.visit_variable_mut(&mut array_destructuring_member, data, chain);
537		match self.get_ast_mut() {
538			ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {}
539			ArrayDestructuringField::Name(variable_field, _, default_value) => {
540				variable_field.visit_mut(visitors, data, options, chain);
541				default_value.visit_mut(visitors, data, options, chain);
542			}
543		}
544	}
545}
546
547impl Visitable for WithComment<ArrayDestructuringField<crate::ast::LHSOfAssignment>> {
548	fn visit<TData>(
549		&self,
550		_visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
551		_data: &mut TData,
552		_options: &VisitOptions,
553		_chain: &mut temporary_annex::Annex<crate::Chain>,
554	) {
555		todo!()
556	}
557
558	fn visit_mut<TData>(
559		&mut self,
560		_visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
561		_data: &mut TData,
562		_options: &VisitOptions,
563		_chain: &mut temporary_annex::Annex<crate::Chain>,
564	) {
565		todo!()
566	}
567}
568
569impl Visitable for WithComment<ObjectDestructuringField<VariableField>> {
570	fn visit<TData>(
571		&self,
572		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
573		data: &mut TData,
574		options: &VisitOptions,
575		chain: &mut temporary_annex::Annex<crate::Chain>,
576	) {
577		visitors.visit_variable(
578			&ImmutableVariableOrProperty::ObjectDestructuringMember(self),
579			data,
580			chain,
581		);
582		match self.get_ast_ref() {
583			ObjectDestructuringField::Name(_name, _, default_value, _) => {
584				default_value.visit(visitors, data, options, chain);
585			}
586			ObjectDestructuringField::Map {
587				name: variable_name,
588				annotation: _,
589				default_value,
590				..
591			} => {
592				variable_name.visit(visitors, data, options, chain);
593				default_value.visit(visitors, data, options, chain);
594			}
595		}
596	}
597
598	fn visit_mut<TData>(
599		&mut self,
600		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
601		data: &mut TData,
602		options: &VisitOptions,
603		chain: &mut temporary_annex::Annex<crate::Chain>,
604	) {
605		visitors.visit_variable_mut(
606			&mut MutableVariableOrProperty::ObjectDestructuringMember(self),
607			data,
608			chain,
609		);
610		match self.get_ast_mut() {
611			ObjectDestructuringField::Name(_id, _, default_value, _) => {
612				default_value.visit_mut(visitors, data, options, chain);
613			}
614			ObjectDestructuringField::Map {
615				name: variable_name,
616				annotation: _,
617				default_value,
618				..
619			} => {
620				variable_name.visit_mut(visitors, data, options, chain);
621				default_value.visit_mut(visitors, data, options, chain);
622			}
623		}
624	}
625}
626impl Visitable for WithComment<ObjectDestructuringField<crate::ast::LHSOfAssignment>> {
627	fn visit<TData>(
628		&self,
629		_visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
630		_data: &mut TData,
631		_options: &VisitOptions,
632		_chain: &mut temporary_annex::Annex<crate::Chain>,
633	) {
634		todo!()
635	}
636
637	fn visit_mut<TData>(
638		&mut self,
639		_visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
640		_data: &mut TData,
641		_options: &VisitOptions,
642		_chain: &mut temporary_annex::Annex<crate::Chain>,
643	) {
644		todo!()
645	}
646}
647
648#[cfg(not(feature = "extras"))]
649fn is_destructuring_into_marker(t: &TSXToken, _options: &ParseOptions) -> bool {
650	matches!(t, TSXToken::Colon)
651}
652
653#[cfg(feature = "extras")]
654fn is_destructuring_into_marker(t: &TSXToken, options: &ParseOptions) -> bool {
655	if options.destructuring_type_annotation {
656		matches!(t, TSXToken::Keyword(crate::TSXKeyword::As))
657	} else {
658		matches!(t, TSXToken::Colon)
659	}
660}
661
662#[cfg(test)]
663mod tests {
664	use super::*;
665	use crate::{assert_matches_ast, span};
666
667	#[test]
668	fn name() {
669		assert_matches_ast!(
670			"x",
671			VariableField::Name(VariableIdentifier::Standard(
672				Deref @ "x",
673				Span { start: 0, end: 1, .. },
674			))
675		);
676	}
677
678	#[test]
679	fn array() {
680		assert_matches_ast!(
681			"[x, y, z]",
682			VariableField::Array {
683				members: Deref @ [WithComment::None(ArrayDestructuringField::Name(
684					VariableField::Name(VariableIdentifier::Standard(Deref @ "x", span!(1, 2))),
685					None,
686					None,
687				)), WithComment::None(ArrayDestructuringField::Name(
688					VariableField::Name(VariableIdentifier::Standard(Deref @ "y", span!(4, 5))),
689					None,
690					None,
691				)), WithComment::None(ArrayDestructuringField::Name(
692					VariableField::Name(VariableIdentifier::Standard(Deref @ "z", span!(7, 8))),
693					None,
694					None,
695				))],
696				spread: _,
697				position: _
698			}
699		);
700
701		assert_matches_ast!(
702			"[x,,z]",
703			VariableField::Array {
704				members:
705				Deref @ [WithComment::None(ArrayDestructuringField::Name(
706					VariableField::Name(VariableIdentifier::Standard(Deref @ "x", span!(1, 2))),
707					None,
708					None,
709				)), WithComment::None(ArrayDestructuringField::None), WithComment::None(ArrayDestructuringField::Name(
710					VariableField::Name(VariableIdentifier::Standard(Deref @ "z", span!(4, 5))),
711					None,
712					None,
713				))],
714				spread: None,
715				position: span!(0, 6),
716			}
717		);
718	}
719
720	#[test]
721	fn object() {
722		assert_matches_ast!(
723			"{x, y, z}",
724			VariableField::Object {
725				members: Deref @ [WithComment::None(ObjectDestructuringField::Name(
726					VariableIdentifier::Standard(Deref @ "x", span!(1, 2)),
727					None,
728					None,
729					span!(1, 2),
730				)), WithComment::None(ObjectDestructuringField::Name(
731					VariableIdentifier::Standard(Deref @ "y", span!(4, 5)),
732					None,
733					None,
734					span!(4, 5),
735				)), WithComment::None(ObjectDestructuringField::Name(
736					VariableIdentifier::Standard(Deref @ "z", span!(7, 8)),
737					None,
738					None,
739					span!(7, 8),
740				))],
741				spread: None,
742				position: span!(0, 9),
743			}
744		);
745	}
746
747	#[test]
748	fn name_with_default() {
749		assert_matches_ast!(
750			"{ x = 2 }",
751			VariableField::Object {
752				members:
753				Deref @ [WithComment::None(ObjectDestructuringField::Name(
754					VariableIdentifier::Standard(Deref @ "x", span!(2, 3)),
755					None,
756					Some(
757						Deref @ Expression::NumberLiteral(
758							crate::number::NumberRepresentation::Number { .. },
759							span!(6, 7),
760						),
761					),
762					span!(2, 7),
763				))],
764				spread: None,
765				position: span!(0, 9),
766			}
767		);
768	}
769
770	#[test]
771	fn array_spread() {
772		assert_matches_ast!(
773			"[x, ...y]",
774			VariableField::Array {
775				members:Deref @ [WithComment::None(ArrayDestructuringField::Name(
776					VariableField::Name(VariableIdentifier::Standard(Deref @ "x", span!(1, 2))),
777					None,
778					None,
779				))],
780				spread: Some(SpreadDestructuringField( Deref @ VariableField::Name(VariableIdentifier::Standard(Deref @ "y", span!(7, 8))), span!(4, 8))),
781				position: span!(0, 9)
782			}
783		);
784	}
785}