ezno_parser/types/
interface.rs

1use crate::{
2	derive_ASTNode, errors::parse_lexing_error, extensions::decorators::Decorated,
3	functions::MethodHeader, parse_bracketed, property_key::PublicOrPrivate,
4	throw_unexpected_token_with_token, to_string_bracketed, tokens::token_as_identifier,
5	types::type_annotations::TypeAnnotationFunctionParameters, ASTNode,
6	ExpressionOrStatementPosition, ParseErrors, ParseOptions, ParseResult, PropertyKey, Span,
7	StatementPosition, TSXKeyword, TSXToken, TypeAnnotation, TypeParameter, WithComment,
8};
9
10use get_field_by_type::GetFieldByType;
11use iterator_endiate::EndiateIteratorExt;
12use tokenizer_lib::{sized_tokens::TokenReaderWithTokenEnds, Token, TokenReader};
13
14#[apply(derive_ASTNode)]
15#[derive(Debug, Clone, PartialEq, get_field_by_type::GetFieldByType)]
16#[get_field_by_type_target(Span)]
17pub struct InterfaceDeclaration {
18	pub is_is_declare: bool,
19	pub name: StatementPosition,
20	#[cfg(feature = "extras")]
21	pub is_nominal: bool,
22	pub type_parameters: Option<Vec<TypeParameter>>,
23	/// The document interface extends a multiple of other interfaces
24	pub extends: Option<Vec<TypeAnnotation>>,
25	pub members: Vec<WithComment<Decorated<InterfaceMember>>>,
26	pub position: Span,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[apply(derive_ASTNode)]
31pub enum Optionality {
32	Default,
33	Optional,
34	// Will make existing optional fields required, whereas default does not change status
35	Required,
36}
37
38impl ASTNode for InterfaceDeclaration {
39	fn from_reader(
40		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
41		state: &mut crate::ParsingState,
42		options: &ParseOptions,
43	) -> ParseResult<Self> {
44		let start = state.expect_keyword(reader, TSXKeyword::Interface)?;
45
46		#[cfg(feature = "extras")]
47		let is_nominal = reader
48			.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Nominal)))
49			.is_some();
50
51		let name = StatementPosition::from_reader(reader, state, options)?;
52		let type_parameters = reader
53			.conditional_next(|token| *token == TSXToken::OpenChevron)
54			.is_some()
55			.then(|| {
56				crate::parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)
57					.map(|(params, _, _)| params)
58			})
59			.transpose()?;
60
61		let extends = if reader
62			.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Extends)))
63			.is_some()
64		{
65			let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
66			let mut extends = vec![type_annotation];
67			if reader.conditional_next(|t| matches!(t, TSXToken::Comma)).is_some() {
68				loop {
69					extends.push(TypeAnnotation::from_reader(reader, state, options)?);
70					match reader.peek() {
71						Some(Token(TSXToken::Comma, _)) => {
72							reader.next();
73						}
74						Some(Token(TSXToken::OpenBrace, _)) | None => break,
75						_ => {
76							return throw_unexpected_token_with_token(
77								reader.next().unwrap(),
78								&[TSXToken::Comma, TSXToken::OpenBrace],
79							)
80						}
81					}
82				}
83			}
84			Some(extends)
85		} else {
86			None
87		};
88
89		reader.expect_next(TSXToken::OpenBrace)?;
90		let members = parse_interface_members(reader, state, options)?;
91		let position = start.union(reader.expect_next_get_end(TSXToken::CloseBrace)?);
92		Ok(InterfaceDeclaration {
93			name,
94			is_is_declare: false,
95			#[cfg(feature = "extras")]
96			is_nominal,
97			type_parameters,
98			extends,
99			members,
100			position,
101		})
102	}
103
104	fn to_string_from_buffer<T: source_map::ToString>(
105		&self,
106		buf: &mut T,
107		options: &crate::ToStringOptions,
108		local: crate::LocalToStringInformation,
109	) {
110		if options.include_type_annotations {
111			if self.name.is_declare() {
112				buf.push_str("declare ");
113			}
114			buf.push_str("interface ");
115			self.name.identifier.to_string_from_buffer(buf, options, local);
116			if let Some(type_parameters) = &self.type_parameters {
117				to_string_bracketed(type_parameters, ('<', '>'), buf, options, local);
118				options.push_gap_optionally(buf);
119			}
120			if let Some(extends) = &self.extends {
121				buf.push_str(" extends ");
122				for (at_end, extends) in extends.iter().endiate() {
123					extends.to_string_from_buffer(buf, options, local);
124					if !at_end {
125						buf.push(',');
126						options.push_gap_optionally(buf);
127					}
128				}
129			}
130			options.push_gap_optionally(buf);
131			buf.push('{');
132			if options.pretty && !self.members.is_empty() {
133				buf.push_new_line();
134			}
135			for member in &self.members {
136				options.add_indent(local.depth + 1, buf);
137				member.to_string_from_buffer(buf, options, local.next_level());
138				if options.pretty {
139					buf.push_new_line();
140				}
141			}
142			buf.push('}');
143		}
144	}
145
146	fn get_position(&self) -> Span {
147		self.position
148	}
149}
150
151/// For some reason mapped types can have a negated a readonly keyword
152#[apply(derive_ASTNode)]
153#[derive(Debug, Clone, Copy, PartialEq)]
154pub enum MappedReadonlyKind {
155	Negated,
156	Always,
157	False,
158}
159
160/// This is also used for [`TypeAnnotation::ObjectLiteral`]
161#[apply(derive_ASTNode)]
162#[derive(Debug, Clone, PartialEq, GetFieldByType)]
163#[get_field_by_type_target(Span)]
164pub enum InterfaceMember {
165	Method {
166		header: MethodHeader,
167		name: PropertyKey<PublicOrPrivate>,
168		type_parameters: Option<Vec<TypeParameter>>,
169		parameters: TypeAnnotationFunctionParameters,
170		return_type: Option<TypeAnnotation>,
171		is_optional: bool,
172		position: Span,
173	},
174	Property {
175		name: PropertyKey<PublicOrPrivate>,
176		type_annotation: TypeAnnotation,
177		is_readonly: bool,
178		/// Marked with `?:`
179		is_optional: bool,
180		position: Span,
181	},
182	Indexer {
183		name: String,
184		indexer_type: TypeAnnotation,
185		return_type: TypeAnnotation,
186		is_readonly: bool,
187		position: Span,
188	},
189	/// Example
190	/// ```ts
191	/// new (...params: any[]): HTMLElement
192	/// ```
193	Constructor {
194		type_parameters: Option<Vec<TypeParameter>>,
195		parameters: TypeAnnotationFunctionParameters,
196		return_type: Option<TypeAnnotation>,
197		is_readonly: bool,
198		position: Span,
199	},
200	Caller {
201		type_parameters: Option<Vec<TypeParameter>>,
202		parameters: TypeAnnotationFunctionParameters,
203		return_type: Option<TypeAnnotation>,
204		is_readonly: bool,
205		position: Span,
206	},
207	/// [For mapped types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
208	Rule {
209		parameter: String,
210		matching_type: Box<TypeAnnotation>,
211		as_type: Option<Box<TypeAnnotation>>,
212		optionality: Optionality,
213		is_readonly: MappedReadonlyKind,
214		output_type: Box<TypeAnnotation>,
215		position: Span,
216	},
217	Comment(String, bool, Span),
218}
219
220#[allow(clippy::similar_names)]
221impl ASTNode for InterfaceMember {
222	fn from_reader(
223		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
224		state: &mut crate::ParsingState,
225		options: &ParseOptions,
226	) -> ParseResult<Self> {
227		let readonly_position = state.optionally_expect_keyword(reader, TSXKeyword::Readonly);
228
229		// This match will early return if not a method
230		let token = &reader.peek().ok_or_else(parse_lexing_error)?.0;
231		match token {
232			// Calling self
233			TSXToken::OpenParentheses => {
234				let parameters =
235					TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
236				// let parameters = function_parameters_from_reader(reader, state, options)?;
237				let return_type =
238					if reader.conditional_next(|tok| matches!(tok, TSXToken::Colon)).is_some() {
239						Some(TypeAnnotation::from_reader(reader, state, options)?)
240					} else {
241						None
242					};
243				// TODO parameter.pos can be union'ed with itself
244				let position = readonly_position
245					.as_ref()
246					.unwrap_or(&parameters.position)
247					.union(return_type.as_ref().map_or(parameters.position, ASTNode::get_position));
248				Ok(InterfaceMember::Caller {
249					is_readonly: readonly_position.is_some(),
250					position,
251					parameters,
252					return_type,
253					type_parameters: None,
254				})
255			}
256			// Caller self with generic parameters
257			TSXToken::OpenChevron => {
258				let _ = reader.next();
259				let (type_parameters, _, _start_pos) =
260					parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)?;
261				let parameters =
262					TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
263				let return_type =
264					if reader.conditional_next(|tok| matches!(tok, TSXToken::Colon)).is_some() {
265						Some(TypeAnnotation::from_reader(reader, state, options)?)
266					} else {
267						None
268					};
269				let position =
270					return_type.as_ref().map_or(parameters.position, ASTNode::get_position);
271
272				Ok(InterfaceMember::Caller {
273					is_readonly: readonly_position.is_some(),
274					position,
275					parameters,
276					type_parameters: Some(type_parameters),
277					return_type,
278				})
279			}
280			// Constructor
281			TSXToken::Keyword(TSXKeyword::New) => {
282				let new_span = reader.next().unwrap().get_span();
283				let type_parameters = reader
284					.conditional_next(|token| *token == TSXToken::OpenChevron)
285					.is_some()
286					.then(|| parse_bracketed(reader, state, options, None, TSXToken::CloseChevron))
287					.transpose()?
288					.map(|(tp, _, _)| tp);
289
290				let parameters =
291					TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
292
293				let return_type = if reader
294					.conditional_next(|tok| {
295						options.type_annotations && matches!(tok, TSXToken::Colon)
296					})
297					.is_some()
298				{
299					Some(TypeAnnotation::from_reader(reader, state, options)?)
300				} else {
301					None
302				};
303
304				let end = return_type.as_ref().map_or(parameters.position, ASTNode::get_position);
305
306				let position = readonly_position.as_ref().unwrap_or(&new_span).union(end);
307
308				Ok(InterfaceMember::Constructor {
309					is_readonly: readonly_position.is_some(),
310					position,
311					parameters,
312					type_parameters,
313					return_type,
314				})
315			}
316			TSXToken::Subtract => {
317				// Little bit weird, but prevents a lot of duplication
318				let subtract_pos = reader.next().unwrap().get_span();
319				let inner = Self::from_reader(reader, state, options)?;
320				if let Self::Rule {
321					parameter,
322					matching_type,
323					as_type,
324					optionality,
325					is_readonly: MappedReadonlyKind::Always,
326					output_type,
327					position,
328				} = inner
329				{
330					Ok(Self::Rule {
331						parameter,
332						matching_type,
333						as_type,
334						optionality,
335						is_readonly: MappedReadonlyKind::Negated,
336						output_type,
337						position,
338					})
339				} else {
340					Err(crate::ParseError::new(ParseErrors::ExpectRule, subtract_pos))
341				}
342			}
343			token if token.is_comment() => {
344				let token = reader.next().unwrap();
345				if let Ok((comment, is_multiline, span)) = TSXToken::try_into_comment(token) {
346					Ok(InterfaceMember::Comment(comment, is_multiline, span))
347				} else {
348					unreachable!()
349				}
350			}
351			_ => {
352				let first = reader
353					.conditional_next(|t| matches!(t, TSXToken::OpenBracket))
354					.map(|res| (None, res))
355					.or_else(|| {
356						let is_get_set_async_index_type =
357							matches!(
358								reader.peek(),
359								Some(Token(
360									TSXToken::Keyword(
361										TSXKeyword::Get | TSXKeyword::Set | TSXKeyword::Async
362									),
363									_
364								))
365							) && matches!(reader.peek_n(1), Some(Token(TSXToken::OpenBracket, _)));
366
367						if is_get_set_async_index_type {
368							let token = reader.next().unwrap();
369							let header = match token.0 {
370								TSXToken::Keyword(TSXKeyword::Get) => MethodHeader::Get,
371								TSXToken::Keyword(TSXKeyword::Set) => MethodHeader::Set,
372								TSXToken::Keyword(TSXKeyword::Async) => {
373									MethodHeader::Regular { is_async: true, generator: None }
374								}
375								_ => unreachable!(),
376							};
377							let open_bracket_token = reader.next().unwrap();
378							Some((Some(header), open_bracket_token))
379						} else {
380							None
381						}
382					});
383
384				// Non literal property names and index type
385				let (header, name, type_parameters) = if let Some((header, Token(_, start))) = first
386				{
387					let name = match reader.next().ok_or_else(parse_lexing_error)? {
388						Token(TSXToken::StringLiteral(name, quoted), start) => {
389							let position = start.with_length(name.len() + 2);
390							let _end = reader.expect_next_get_end(TSXToken::CloseBracket)?;
391							PropertyKey::StringLiteral(name, quoted, position)
392						}
393						Token(TSXToken::NumberLiteral(value), start) => {
394							let position = start.with_length(value.len());
395							let _end = reader.expect_next_get_end(TSXToken::CloseBracket)?;
396							PropertyKey::NumberLiteral(
397								value.parse::<crate::number::NumberRepresentation>().unwrap(),
398								position,
399							)
400						}
401						token => {
402							use crate::Expression;
403							// "name" is the name of the parameter name for indexing
404							let (name, name_span) =
405								token_as_identifier(token, "interface parameter")?;
406
407							// Catch for computed symbol: e.g. `[Symbol.instanceOf()]`, rather than indexer
408							if let Some(Token(TSXToken::Dot, _)) = reader.peek() {
409								let top = Expression::VariableReference(name, name_span);
410								let expression = Expression::from_reader_sub_first_expression(
411									reader, state, options, 0, top,
412								)?;
413								let end = reader.expect_next_get_end(TSXToken::CloseBracket)?;
414								PropertyKey::Computed(Box::new(expression), start.union(end))
415							} else {
416								let start_span = readonly_position.as_ref().unwrap_or(&name_span);
417								match reader.next().ok_or_else(parse_lexing_error)? {
418									// Indexed type
419									Token(TSXToken::Colon, _start) => {
420										let indexer_type =
421											TypeAnnotation::from_reader(reader, state, options)?;
422										reader.expect_next(TSXToken::CloseBracket)?;
423										reader.expect_next(TSXToken::Colon)?;
424										let return_type =
425											TypeAnnotation::from_reader(reader, state, options)?;
426										return Ok(InterfaceMember::Indexer {
427											name,
428											is_readonly: readonly_position.is_some(),
429											indexer_type,
430											position: start_span.union(return_type.get_position()),
431											return_type,
432										});
433									}
434									// For mapped types
435									Token(TSXToken::Keyword(TSXKeyword::In), _) => {
436										let matching_type =
437											TypeAnnotation::from_reader(reader, state, options)?;
438
439										let next_is_as = reader.conditional_next(|t| {
440											matches!(t, TSXToken::Keyword(TSXKeyword::As))
441										});
442
443										let as_type = if next_is_as.is_some() {
444											Some(Box::new(TypeAnnotation::from_reader(
445												reader, state, options,
446											)?))
447										} else {
448											None
449										};
450
451										reader.expect_next(TSXToken::CloseBracket)?;
452										let token = reader.next().ok_or_else(parse_lexing_error)?;
453										let optionality = match token {
454											Token(TSXToken::Colon, _) => Optionality::Default,
455											Token(TSXToken::OptionalMember, _) => {
456												Optionality::Optional
457											}
458											Token(TSXToken::NonOptionalMember, _) => {
459												Optionality::Required
460											}
461											token => {
462												return throw_unexpected_token_with_token(
463													token,
464													&[
465														TSXToken::Colon,
466														TSXToken::OptionalMember,
467														TSXToken::NonOptionalMember,
468													],
469												);
470											}
471										};
472
473										let output_type =
474											TypeAnnotation::from_reader(reader, state, options)?;
475
476										let position = start_span.union(output_type.get_position());
477
478										return Ok(InterfaceMember::Rule {
479											parameter: name,
480											optionality,
481											is_readonly: if readonly_position.is_some() {
482												MappedReadonlyKind::Always
483											} else {
484												MappedReadonlyKind::False
485											},
486											matching_type: Box::new(matching_type),
487											output_type: Box::new(output_type),
488											position,
489											as_type,
490										});
491									}
492									token => {
493										return throw_unexpected_token_with_token(
494											token,
495											&[TSXToken::Colon, TSXToken::Keyword(TSXKeyword::In)],
496										);
497									}
498								}
499							}
500						}
501					};
502					(header, name, None)
503				} else {
504					let (header, name) = crate::functions::get_method_name(reader, state, options)?;
505					let type_parameters = reader
506						.conditional_next(|token| *token == TSXToken::OpenChevron)
507						.is_some()
508						.then(|| {
509							parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)
510						})
511						.transpose()?
512						.map(|(tp, _, _)| tp);
513
514					let name = name.get_ast();
515
516					let header = if header.is_no_modifiers() { None } else { Some(header) };
517
518					(header, name, type_parameters)
519				};
520
521				let start = readonly_position.unwrap_or_else(|| name.get_position());
522
523				// TODO a little weird as only functions can have type parameters:
524				match reader.next().ok_or_else(parse_lexing_error)? {
525					Token(TSXToken::OpenParentheses, _start_pos) => {
526						let parameters =
527							TypeAnnotationFunctionParameters::from_reader_sub_open_parenthesis(
528								reader,
529								state,
530								options,
531								start.get_start(),
532							)?;
533						let mut position = start.union(parameters.position);
534						let return_type = if reader
535							.conditional_next(|tok| matches!(tok, TSXToken::Colon))
536							.is_some()
537						{
538							let type_annotation =
539								TypeAnnotation::from_reader(reader, state, options)?;
540							position = position.union(type_annotation.get_position());
541							Some(type_annotation)
542						} else {
543							None
544						};
545
546						Ok(InterfaceMember::Method {
547							header: header.unwrap_or_default(),
548							name,
549							parameters,
550							type_parameters,
551							return_type,
552							is_optional: false,
553							position,
554						})
555					}
556					Token(TSXToken::QuestionMark, _) => {
557						// This is a function. If it was a property it would be the `?:` token
558						let parameters =
559							TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
560
561						let mut position = start.union(parameters.position);
562
563						let return_type = if reader
564							.conditional_next(|tok| matches!(tok, TSXToken::Colon))
565							.is_some()
566						{
567							let type_annotation =
568								TypeAnnotation::from_reader(reader, state, options)?;
569							position = position.union(type_annotation.get_position());
570							Some(type_annotation)
571						} else {
572							None
573						};
574
575						Ok(InterfaceMember::Method {
576							header: header.unwrap_or_default(),
577							name,
578							parameters,
579							type_parameters,
580							is_optional: true,
581							position,
582							return_type,
583						})
584					}
585					t @ Token(TSXToken::Colon | TSXToken::OptionalMember, _) => {
586						if header.is_none() {
587							let type_annotation =
588								TypeAnnotation::from_reader(reader, state, options)?;
589							let position = start.union(type_annotation.get_position());
590							let is_optional = matches!(t, Token(TSXToken::OptionalMember, _));
591							Ok(InterfaceMember::Property {
592								position,
593								name,
594								type_annotation,
595								is_optional,
596								is_readonly: readonly_position.is_some(),
597							})
598						} else {
599							throw_unexpected_token_with_token(t, &[TSXToken::OpenParentheses])
600						}
601					}
602					token => throw_unexpected_token_with_token(
603						token,
604						&[
605							TSXToken::OpenParentheses,
606							TSXToken::QuestionMark,
607							TSXToken::Colon,
608							TSXToken::OptionalMember,
609						],
610					),
611				}
612			}
613		}
614	}
615
616	fn to_string_from_buffer<T: source_map::ToString>(
617		&self,
618		buf: &mut T,
619		options: &crate::ToStringOptions,
620		local: crate::LocalToStringInformation,
621	) {
622		match self {
623			InterfaceMember::Property { name, type_annotation, is_readonly, .. } => {
624				if *is_readonly {
625					buf.push_str("readonly ");
626				}
627				name.to_string_from_buffer(buf, options, local);
628				buf.push(':');
629				options.push_gap_optionally(buf);
630				type_annotation.to_string_from_buffer(buf, options, local);
631			}
632			InterfaceMember::Method {
633				name,
634				type_parameters,
635				parameters,
636				return_type,
637				is_optional,
638				..
639			} => {
640				name.to_string_from_buffer(buf, options, local);
641				if *is_optional {
642					buf.push('?');
643				}
644				if let Some(type_parameters) = &type_parameters {
645					to_string_bracketed(type_parameters, ('<', '>'), buf, options, local);
646				}
647				parameters.to_string_from_buffer(buf, options, local);
648				if let Some(return_type) = return_type {
649					buf.push_str(": ");
650					return_type.to_string_from_buffer(buf, options, local);
651				}
652			}
653			InterfaceMember::Indexer { name, indexer_type, return_type, is_readonly, .. } => {
654				if *is_readonly {
655					buf.push_str("readonly ");
656				}
657				buf.push('[');
658				buf.push_str(name.as_str());
659				buf.push(':');
660				indexer_type.to_string_from_buffer(buf, options, local);
661				buf.push_str("]: ");
662				return_type.to_string_from_buffer(buf, options, local);
663			}
664			InterfaceMember::Constructor {
665				parameters,
666				type_parameters,
667				return_type,
668				is_readonly,
669				position: _,
670			} => {
671				if *is_readonly {
672					buf.push_str("readonly ");
673				}
674				buf.push_str("new ");
675				if let Some(ref type_parameters) = type_parameters {
676					to_string_bracketed(type_parameters, ('<', '>'), buf, options, local);
677				}
678				parameters.to_string_from_buffer(buf, options, local);
679				if let Some(ref return_type) = return_type {
680					buf.push_str(": ");
681					return_type.to_string_from_buffer(buf, options, local);
682				}
683			}
684			InterfaceMember::Caller {
685				parameters,
686				type_parameters,
687				return_type,
688				is_readonly,
689				position: _,
690			} => {
691				if *is_readonly {
692					buf.push_str("readonly ");
693				}
694				if let Some(ref type_parameters) = type_parameters {
695					to_string_bracketed(type_parameters, ('<', '>'), buf, options, local);
696				}
697				parameters.to_string_from_buffer(buf, options, local);
698				if let Some(ref return_type) = return_type {
699					buf.push_str(": ");
700					return_type.to_string_from_buffer(buf, options, local);
701				}
702			}
703			InterfaceMember::Rule {
704				is_readonly,
705				matching_type,
706				optionality,
707				output_type,
708				as_type,
709				parameter,
710				position: _,
711			} => {
712				buf.push_str(match is_readonly {
713					MappedReadonlyKind::Negated => "-readonly ",
714					MappedReadonlyKind::Always => "readonly ",
715					MappedReadonlyKind::False => "",
716				});
717				buf.push('[');
718				buf.push_str(parameter.as_str());
719				buf.push_str(" in ");
720				matching_type.to_string_from_buffer(buf, options, local);
721				if let Some(as_type) = as_type {
722					buf.push_str(" as ");
723					as_type.to_string_from_buffer(buf, options, local);
724				}
725				buf.push(']');
726				buf.push_str(match optionality {
727					Optionality::Default => ": ",
728					Optionality::Optional => "?:",
729					Optionality::Required => "-?:",
730				});
731				output_type.to_string_from_buffer(buf, options, local);
732			}
733			InterfaceMember::Comment(content, is_multiline, _) => {
734				if *is_multiline {
735					buf.push_str("/*");
736					buf.push_str(content);
737					buf.push_str("*/");
738				} else {
739					buf.push_str("//");
740					buf.push_str(content);
741					buf.push_new_line();
742				}
743			}
744		}
745	}
746
747	fn get_position(&self) -> Span {
748		*GetFieldByType::get(self)
749	}
750}
751
752pub(crate) fn parse_interface_members(
753	reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
754	state: &mut crate::ParsingState,
755	options: &ParseOptions,
756) -> ParseResult<Vec<WithComment<Decorated<InterfaceMember>>>> {
757	let mut members = Vec::new();
758	loop {
759		if let Some(Token(TSXToken::CloseBrace, _)) = reader.peek() {
760			break;
761		}
762		let decorated_member = WithComment::from_reader(reader, state, options)?;
763		// Semi colons and commas are optional here. Should expect_semi_colon
764		if let Some(Token(TSXToken::Comma, _)) = reader.peek() {
765			reader.next();
766		} else {
767			let _ = crate::expect_semi_colon(
768				reader,
769				&state.line_starts,
770				decorated_member.get_position().end,
771				options,
772			)?;
773		}
774		members.push(decorated_member);
775	}
776	Ok(members)
777}