use crate::{
errors::parse_lexing_error,
extensions::decorators::Decorated,
parse_bracketed,
property_key::PublicOrPrivate,
throw_unexpected_token_with_token, to_string_bracketed,
tokens::token_as_identifier,
tsx_keywords,
types::{type_annotations::TypeAnnotationFunctionParameters, type_declarations::*},
ASTNode, Expression, GenericTypeConstraint, Keyword, MethodHeader, NumberRepresentation,
ParseOptions, ParseResult, PropertyKey, Span, TSXKeyword, TSXToken, TypeAnnotation,
};
use get_field_by_type::GetFieldByType;
use iterator_endiate::EndiateIteratorExt;
use tokenizer_lib::{sized_tokens::TokenReaderWithTokenEnds, Token, TokenReader};
#[derive(Debug, Clone, PartialEq, Eq, get_field_by_type::GetFieldByType)]
#[get_field_by_type_target(Span)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub struct InterfaceDeclaration {
pub name: String,
#[cfg(feature = "extras")]
pub nominal_keyword: Option<Keyword<tsx_keywords::Nominal>>,
pub type_parameters: Option<Vec<GenericTypeConstraint>>,
pub extends: Option<Vec<TypeAnnotation>>,
pub members: Vec<Decorated<InterfaceMember>>,
pub position: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum Optionality {
Default,
Optional,
Required,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum TypeRule {
In,
InKeyOf,
}
impl ASTNode for InterfaceDeclaration {
fn from_reader(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
) -> ParseResult<Self> {
let start = reader.expect_next(TSXToken::Keyword(TSXKeyword::Interface))?;
#[cfg(feature = "extras")]
let nominal_keyword = Keyword::optionally_from_reader(reader);
let TypeDeclaration { name, type_parameters, .. } =
TypeDeclaration::from_reader(reader, state, options)?;
let extends = if let Some(Token(TSXToken::Keyword(TSXKeyword::Extends), _)) = reader.peek()
{
reader.next();
let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
let mut extends = vec![type_annotation];
if matches!(reader.peek(), Some(Token(TSXToken::Comma, _))) {
reader.next();
loop {
extends.push(TypeAnnotation::from_reader(reader, state, options)?);
match reader.next().ok_or_else(parse_lexing_error)? {
Token(TSXToken::Comma, _) => {
reader.next();
}
Token(TSXToken::OpenBrace, _) => break,
token => {
return throw_unexpected_token_with_token(
token,
&[TSXToken::Comma, TSXToken::OpenBrace],
)
}
}
}
}
Some(extends)
} else {
None
};
reader.expect_next(TSXToken::OpenBrace)?;
let members = parse_interface_members(reader, state, options)?;
let position = start.union(reader.expect_next_get_end(TSXToken::CloseBrace)?);
Ok(InterfaceDeclaration {
name,
members,
type_parameters,
extends,
position,
#[cfg(feature = "extras")]
nominal_keyword,
})
}
fn to_string_from_buffer<T: source_map::ToString>(
&self,
buf: &mut T,
options: &crate::ToStringOptions,
depth: u8,
) {
if options.include_types {
buf.push_str("interface ");
buf.push_str(&self.name);
if let Some(type_parameters) = &self.type_parameters {
to_string_bracketed(type_parameters, ('<', '>'), buf, options, depth);
}
options.add_gap(buf);
if let Some(extends) = &self.extends {
buf.push_str(" extends ");
for (at_end, extends) in extends.iter().endiate() {
extends.to_string_from_buffer(buf, options, depth);
if !at_end {
buf.push(',');
}
}
}
buf.push('{');
if options.pretty && !self.members.is_empty() {
buf.push_new_line();
}
for member in self.members.iter() {
options.add_indent(depth + 1, buf);
member.to_string_from_buffer(buf, options, depth + 1);
if options.pretty {
buf.push_new_line();
}
}
buf.push('}');
}
}
fn get_position(&self) -> &Span {
&self.position
}
}
#[derive(Debug, Clone, PartialEq, Eq, GetFieldByType)]
#[get_field_by_type_target(Span)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum InterfaceMember {
Method {
kind: Option<MethodHeader>,
name: PropertyKey<PublicOrPrivate>,
type_parameters: Option<Vec<GenericTypeConstraint>>,
parameters: TypeAnnotationFunctionParameters,
return_type: Option<TypeAnnotation>,
is_optional: bool,
#[cfg(feature = "extras")]
performs: Option<super::AnnotationPerforms>,
position: Span,
},
Property {
name: PropertyKey<PublicOrPrivate>,
type_annotation: TypeAnnotation,
is_readonly: bool,
is_optional: bool,
position: Span,
},
Indexer {
name: String,
indexer_type: TypeAnnotation,
return_type: TypeAnnotation,
is_readonly: bool,
position: Span,
},
Constructor {
parameters: TypeAnnotationFunctionParameters,
type_parameters: Option<Vec<GenericTypeConstraint>>,
return_type: Option<TypeAnnotation>,
is_readonly: bool,
position: Span,
},
Caller {
parameters: TypeAnnotationFunctionParameters,
type_parameters: Option<Vec<GenericTypeConstraint>>,
return_type: Option<TypeAnnotation>,
is_readonly: bool,
position: Span,
},
Rule {
parameter: String,
rule: TypeRule,
matching_type: Box<TypeAnnotation>,
optionality: Optionality,
is_readonly: bool,
output_type: Box<TypeAnnotation>,
position: Span,
},
Comment(String, Span),
}
impl ASTNode for InterfaceMember {
fn from_reader(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
) -> ParseResult<Self> {
let readonly_keyword = Keyword::<tsx_keywords::Readonly>::optionally_from_reader(reader);
let is_readonly = readonly_keyword.is_some();
let token = &reader.peek().ok_or_else(parse_lexing_error)?.0;
match token {
TSXToken::OpenParentheses => {
let parameters =
TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
let return_type = if let Some(Token(TSXToken::Colon, _)) = reader.peek() {
reader.next();
Some(TypeAnnotation::from_reader(reader, state, options)?)
} else {
None
};
let position = readonly_keyword
.as_ref()
.map_or(¶meters.position, |kw| kw.get_position())
.union(
return_type
.as_ref()
.map(ASTNode::get_position)
.unwrap_or(¶meters.position),
);
Ok(InterfaceMember::Caller {
is_readonly,
position,
parameters,
return_type,
type_parameters: None,
})
}
TSXToken::OpenChevron => {
let (type_parameters, _start_pos) =
parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)?;
let parameters =
TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
let return_type = if let Some(Token(TSXToken::Colon, _)) = reader.peek() {
reader.next();
Some(TypeAnnotation::from_reader(reader, state, options)?)
} else {
None
};
let position = return_type
.as_ref()
.map_or(¶meters.position, ASTNode::get_position)
.clone();
Ok(InterfaceMember::Caller {
is_readonly,
position,
parameters,
type_parameters: Some(type_parameters),
return_type,
})
}
TSXToken::Keyword(TSXKeyword::New) => {
let new_span = reader.next().unwrap().get_span();
let type_parameters = if reader
.conditional_next(|token| *token == TSXToken::OpenChevron)
.is_some()
{
Some(parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)?.0)
} else {
None
};
let parameters =
TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
let return_type =
if reader.conditional_next(|token| *token == TSXToken::Colon).is_some() {
Some(TypeAnnotation::from_reader(reader, state, options)?)
} else {
None
};
Ok(InterfaceMember::Constructor {
is_readonly,
position: readonly_keyword
.as_ref()
.map_or(&new_span, |kw| kw.get_position())
.union(
return_type
.as_ref()
.map(ASTNode::get_position)
.unwrap_or(¶meters.position),
),
parameters,
type_parameters,
return_type,
})
}
TSXToken::MultiLineComment(..) | TSXToken::Comment(..) => {
let token = reader.next().unwrap();
let span = token.get_span();
let comment = if let TSXToken::MultiLineComment(comment)
| TSXToken::Comment(comment) = token.0
{
comment
} else {
unreachable!()
};
Ok(InterfaceMember::Comment(comment, span))
}
_ => {
let kind = MethodHeader::optional_from_reader(reader);
let (name, type_parameters) = if let TSXToken::OpenBracket =
reader.peek().unwrap().0
{
let Token(_, start) = reader.next().unwrap();
let name = match reader.next().ok_or_else(parse_lexing_error)? {
Token(TSXToken::StringLiteral(name, quoted), start) => {
let position = start.with_length(name.len() + 2);
PropertyKey::StringLiteral(name, quoted, position)
}
Token(TSXToken::NumberLiteral(value), start) => {
let position = start.with_length(value.len());
PropertyKey::NumberLiteral(
value.parse::<NumberRepresentation>().unwrap(),
position.clone(),
)
}
token => {
let (name, name_span) =
token_as_identifier(token, "interface parameter")?;
if let Some(Token(TSXToken::Dot, _)) = reader.peek() {
let top = Expression::VariableReference(name, name_span);
let expression = Expression::from_reader_sub_first_expression(
reader, state, options, 0, top,
)?;
let end = reader.expect_next_get_end(TSXToken::CloseBracket)?;
PropertyKey::Computed(Box::new(expression), start.union(end))
} else {
let start_span = readonly_keyword
.as_ref()
.map_or(&name_span, |kw| kw.get_position());
match reader.next().ok_or_else(parse_lexing_error)? {
Token(TSXToken::Colon, _) => {
let indexer_type =
TypeAnnotation::from_reader(reader, state, options)?;
reader.expect_next(TSXToken::CloseBracket)?;
reader.expect_next(TSXToken::Colon)?;
let return_type =
TypeAnnotation::from_reader(reader, state, options)?;
return Ok(InterfaceMember::Indexer {
name,
indexer_type,
position: start_span.union(return_type.get_position()),
return_type,
is_readonly,
});
}
Token(TSXToken::Keyword(TSXKeyword::In), _) => {
let rule = if reader
.conditional_next(|token| {
matches!(
token,
TSXToken::Keyword(TSXKeyword::KeyOf)
)
})
.is_some()
{
TypeRule::InKeyOf
} else {
TypeRule::In
};
let matching_type =
TypeAnnotation::from_reader(reader, state, options)?;
reader.expect_next(TSXToken::CloseBracket)?;
let token = reader.next().ok_or_else(parse_lexing_error)?;
let optionality = match token {
Token(TSXToken::Colon, _) => Optionality::Default,
Token(TSXToken::OptionalMember, _) => {
Optionality::Optional
}
Token(TSXToken::NonOptionalMember, _) => {
Optionality::Required
}
token => {
return throw_unexpected_token_with_token(
token,
&[
TSXToken::Colon,
TSXToken::OptionalMember,
TSXToken::NonOptionalMember,
],
);
}
};
let output_type =
TypeAnnotation::from_reader(reader, state, options)?;
let position = start_span.union(output_type.get_position());
return Ok(InterfaceMember::Rule {
parameter: name,
optionality,
is_readonly,
matching_type: Box::new(matching_type),
rule,
output_type: Box::new(output_type),
position,
});
}
token => {
return throw_unexpected_token_with_token(
token,
&[TSXToken::Colon, TSXToken::Keyword(TSXKeyword::In)],
);
}
}
}
}
};
(name, None)
} else {
let property_key = PropertyKey::from_reader(reader, state, options)?;
let type_parameters = reader
.conditional_next(|token| *token == TSXToken::OpenChevron)
.is_some()
.then(|| {
parse_bracketed(reader, state, options, None, TSXToken::CloseChevron)
})
.transpose()?;
if let Some((type_parameters, _last_chevron)) = type_parameters {
(property_key, Some(type_parameters))
} else {
(property_key, None)
}
};
let start = readonly_keyword.as_ref().map_or_else(
|| name.get_position().get_start(),
|kw| kw.get_position().get_start(),
);
match reader.next().ok_or_else(parse_lexing_error)? {
Token(TSXToken::OpenParentheses, _start_pos) => {
let parameters =
TypeAnnotationFunctionParameters::from_reader_sub_open_parenthesis(
reader, state, options, start,
)?;
let mut position = start.union(¶meters.position);
let return_type = if reader
.conditional_next(|tok| matches!(tok, TSXToken::Colon))
.is_some()
{
let type_annotation =
TypeAnnotation::from_reader(reader, state, options)?;
position = position.union(type_annotation.get_position());
Some(type_annotation)
} else {
None
};
#[cfg(feature = "extras")]
let performs = if let Some(Token(TSXToken::Keyword(TSXKeyword::Performs), _)) =
reader.peek()
{
Some(super::AnnotationPerforms::from_reader(reader, state, options)?)
} else {
None
};
Ok(InterfaceMember::Method {
kind,
name,
parameters,
type_parameters,
return_type,
is_optional: false,
position,
#[cfg(feature = "extras")]
performs,
})
}
Token(TSXToken::QuestionMark, _) => {
let parameters =
TypeAnnotationFunctionParameters::from_reader(reader, state, options)?;
let mut position = start.union(¶meters.position);
let return_type = if reader
.conditional_next(|tok| matches!(tok, TSXToken::Colon))
.is_some()
{
let type_annotation =
TypeAnnotation::from_reader(reader, state, options)?;
position = position.union(type_annotation.get_position());
Some(type_annotation)
} else {
None
};
#[cfg(feature = "extras")]
let performs = if let Some(Token(TSXToken::Keyword(TSXKeyword::Performs), _)) =
reader.peek()
{
Some(super::AnnotationPerforms::from_reader(reader, state, options)?)
} else {
None
};
Ok(InterfaceMember::Method {
kind,
name,
parameters,
type_parameters,
is_optional: true,
position,
return_type,
#[cfg(feature = "extras")]
performs,
})
}
Token(TSXToken::Colon, _) => {
let mut type_annotation =
TypeAnnotation::from_reader(reader, state, options)?;
if is_readonly {
let position = start.union(type_annotation.get_position());
type_annotation =
TypeAnnotation::Readonly(Box::new(type_annotation), position);
}
Ok(InterfaceMember::Property {
name,
position: start.union(type_annotation.get_position()),
type_annotation,
is_optional: false,
is_readonly,
})
}
Token(TSXToken::OptionalMember, _) => {
let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
let position = start.union(type_annotation.get_position());
Ok(InterfaceMember::Property {
name,
type_annotation,
is_optional: true,
is_readonly,
position,
})
}
token => throw_unexpected_token_with_token(
token,
&[
TSXToken::OpenParentheses,
TSXToken::QuestionMark,
TSXToken::Colon,
TSXToken::OptionalMember,
],
),
}
}
}
}
fn to_string_from_buffer<T: source_map::ToString>(
&self,
buf: &mut T,
options: &crate::ToStringOptions,
depth: u8,
) {
match self {
Self::Property { name, type_annotation, is_readonly, .. } => {
if *is_readonly {
buf.push_str("readonly ");
}
name.to_string_from_buffer(buf, options, depth);
buf.push(':');
options.add_gap(buf);
type_annotation.to_string_from_buffer(buf, options, depth);
}
Self::Method {
name, type_parameters, parameters, return_type, is_optional, ..
} => {
name.to_string_from_buffer(buf, options, depth);
if *is_optional {
buf.push('?');
}
if let Some(type_parameters) = &type_parameters {
to_string_bracketed(type_parameters, ('<', '>'), buf, options, depth);
}
parameters.to_string_from_buffer(buf, options, depth);
if let Some(return_type) = return_type {
buf.push(':');
options.add_gap(buf);
return_type.to_string_from_buffer(buf, options, depth);
}
}
Self::Indexer { name, indexer_type, return_type, is_readonly, .. } => {
if *is_readonly {
buf.push_str("readonly ");
}
buf.push('[');
buf.push_str(name.as_str());
buf.push(':');
indexer_type.to_string_from_buffer(buf, options, depth);
buf.push(']');
buf.push(':');
options.add_gap(buf);
return_type.to_string_from_buffer(buf, options, depth);
}
_ => todo!(),
}
}
fn get_position(&self) -> &Span {
GetFieldByType::get(self)
}
}
pub(crate) fn parse_interface_members(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
) -> ParseResult<Vec<Decorated<InterfaceMember>>> {
let mut members = Vec::new();
loop {
if let Some(Token(TSXToken::CloseBrace, _)) = reader.peek() {
break;
}
let decorated_member = Decorated::<InterfaceMember>::from_reader(reader, state, options)?;
members.push(decorated_member);
if let Some(Token(TSXToken::SemiColon | TSXToken::Comma, _)) = reader.peek() {
reader.next();
}
}
Ok(members)
}