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 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 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#[apply(derive_ASTNode)]
153#[derive(Debug, Clone, Copy, PartialEq)]
154pub enum MappedReadonlyKind {
155 Negated,
156 Always,
157 False,
158}
159
160#[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 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 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 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 let token = &reader.peek().ok_or_else(parse_lexing_error)?.0;
231 match token {
232 TSXToken::OpenParentheses => {
234 let parameters =
235 TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
236 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 let position = readonly_position
245 .as_ref()
246 .unwrap_or(¶meters.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 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 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 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 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 let (name, name_span) =
405 token_as_identifier(token, "interface parameter")?;
406
407 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 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 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 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 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 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}