use std::{fmt::Debug, mem};
use crate::{
errors::parse_lexing_error, property_key::PublicOrPrivate, tsx_keywords, visiting::Visitable,
};
use source_map::Span;
use tokenizer_lib::{Token, TokenReader};
use visitable_derive::Visitable;
use crate::{
functions::{FunctionBased, MethodHeader},
ASTNode, Block, Expression, FunctionBase, Keyword, ParseOptions, ParseResult, PropertyKey,
TSXKeyword, TSXToken, TypeAnnotation, WithComment,
};
#[derive(Debug, Clone, PartialEq, Eq, Visitable)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum ClassMember {
Constructor(ClassConstructor),
Method(Option<Keyword<tsx_keywords::Static>>, ClassFunction),
Property(Option<Keyword<tsx_keywords::Static>>, ClassProperty),
StaticBlock(Block),
Comment(String, Span),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClassConstructorBase;
pub type ClassConstructor = FunctionBase<ClassConstructorBase>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClassFunctionBase;
pub type ClassFunction = FunctionBase<ClassFunctionBase>;
#[derive(Debug, Clone, PartialEq, Eq, Visitable)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub struct ClassProperty {
pub readonly_keyword: Option<Keyword<tsx_keywords::Readonly>>,
pub key: WithComment<PropertyKey<PublicOrPrivate>>,
pub type_annotation: Option<TypeAnnotation>,
pub value: Option<Box<Expression>>,
pub position: Span,
}
impl ASTNode for ClassMember {
fn get_position(&self) -> &Span {
match self {
Self::Constructor(cst) => cst.get_position(),
Self::Method(_, mtd) => mtd.get_position(),
Self::Property(_, prop) => &prop.position,
Self::StaticBlock(blk) => blk.get_position(),
Self::Comment(_, pos) => pos,
}
}
#[allow(clippy::similar_names)]
fn from_reader(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
) -> ParseResult<Self> {
if reader.peek().map_or(false, |t| t.0.is_comment()) {
let Ok((comment, span)) = TSXToken::try_into_comment(reader.next().unwrap()) else {
unreachable!()
};
return Ok(Self::Comment(comment, span));
}
if let Some(Token(TSXToken::Keyword(TSXKeyword::Constructor), _)) = reader.peek() {
let constructor = ClassConstructor::from_reader(reader, state, options)?;
return Ok(ClassMember::Constructor(constructor));
}
let is_static = reader
.conditional_next(|tok| *tok == TSXToken::Keyword(TSXKeyword::Static))
.map(|token| Keyword::new(token.get_span()));
if let Some(Token(TSXToken::OpenBrace, _)) = reader.peek() {
return Ok(ClassMember::StaticBlock(Block::from_reader(reader, state, options)?));
}
let readonly_keyword = reader
.conditional_next(|tok| *tok == TSXToken::Keyword(TSXKeyword::Readonly))
.map(|token| Keyword::new(token.get_span()));
let mut header = MethodHeader::from_reader(reader);
let is_named_get_set_or_async = matches!(
(reader.peek(), &header),
(
Some(Token(
TSXToken::OpenParentheses
| TSXToken::OpenChevron | TSXToken::Colon
| TSXToken::Comma | TSXToken::CloseBrace,
_
)),
MethodHeader::Get(..)
| MethodHeader::Set(..)
| MethodHeader::Regular { r#async: Some(_), .. }
)
);
let key = if is_named_get_set_or_async {
let (name, position) = match mem::take(&mut header) {
MethodHeader::Get(kw) => ("get", kw.1),
MethodHeader::Set(kw) => ("set", kw.1),
MethodHeader::Regular { r#async: Some(kw), .. } => ("async", kw.1),
MethodHeader::Regular { .. } => unreachable!(),
};
WithComment::None(PropertyKey::Ident(name.to_owned(), position, false))
} else {
WithComment::<PropertyKey<_>>::from_reader(reader, state, options)?
};
match reader.peek() {
Some(Token(TSXToken::OpenParentheses, _)) if readonly_keyword.is_none() => {
let function =
ClassFunction::from_reader_with_config(reader, state, options, header, key)?;
Ok(ClassMember::Method(is_static, function))
}
Some(Token(token, _)) => {
if header.is_some() {
return crate::throw_unexpected_token(reader, &[TSXToken::OpenParentheses]);
}
let member_type: Option<TypeAnnotation> = match token {
TSXToken::Colon => {
reader.next();
let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
Some(type_annotation)
}
_ => None,
};
let member_expression: Option<Expression> = match reader.peek() {
Some(Token(TSXToken::Assign, _)) => {
reader.next();
let expression = Expression::from_reader(reader, state, options)?;
Some(expression)
}
_ => None,
};
Ok(Self::Property(
is_static,
ClassProperty {
readonly_keyword,
position: *key.get_position(),
key,
type_annotation: member_type,
value: member_expression.map(Box::new),
},
))
}
None => Err(parse_lexing_error()),
}
}
fn to_string_from_buffer<T: source_map::ToString>(
&self,
buf: &mut T,
options: &crate::ToStringOptions,
depth: u8,
) {
match self {
Self::Property(
is_static,
ClassProperty { readonly_keyword, key, type_annotation, value, position: _ },
) => {
if is_static.is_some() {
buf.push_str("static ");
}
if readonly_keyword.is_some() {
buf.push_str("readonly ");
}
key.to_string_from_buffer(buf, options, depth);
if let (true, Some(type_annotation)) = (options.include_types, type_annotation) {
buf.push_str(": ");
type_annotation.to_string_from_buffer(buf, options, depth);
}
if let Some(value) = value {
buf.push_str(if options.pretty { " = " } else { "=" });
value.to_string_from_buffer(buf, options, depth);
}
}
Self::Method(is_static, function) => {
if is_static.is_some() {
buf.push_str("static ");
}
function.to_string_from_buffer(buf, options, depth + 1);
}
Self::Constructor(constructor) => {
constructor.to_string_from_buffer(buf, options, depth + 1);
}
Self::StaticBlock(block) => {
buf.push_str("static ");
block.to_string_from_buffer(buf, options, depth + 1);
}
Self::Comment(c, _) => {
if options.should_add_comment(c.starts_with('.')) {
buf.push_str("/*");
buf.push_str(c);
buf.push_str("*/");
}
}
}
}
}
impl ClassFunction {
fn from_reader_with_config(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
get_set_generator: MethodHeader,
key: WithComment<PropertyKey<PublicOrPrivate>>,
) -> ParseResult<Self> {
FunctionBase::from_reader_with_header_and_name(
reader,
state,
options,
get_set_generator,
key,
)
}
}
impl FunctionBased for ClassFunctionBase {
type Body = Block;
type Header = MethodHeader;
type Name = WithComment<PropertyKey<PublicOrPrivate>>;
#[allow(clippy::similar_names)]
fn header_and_name_from_reader(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
state: &mut crate::ParsingState,
options: &ParseOptions,
) -> ParseResult<(Self::Header, Self::Name)> {
let header = MethodHeader::from_reader(reader);
let name = WithComment::<PropertyKey<_>>::from_reader(reader, state, options)?;
Ok((header, name))
}
fn header_and_name_to_string_from_buffer<T: source_map::ToString>(
buf: &mut T,
header: &Self::Header,
name: &Self::Name,
options: &crate::ToStringOptions,
depth: u8,
) {
header.to_string_from_buffer(buf);
name.to_string_from_buffer(buf, options, depth);
}
fn header_left(header: &Self::Header) -> Option<source_map::Start> {
header.get_start()
}
fn visit_name<TData>(
name: &Self::Name,
visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
data: &mut TData,
options: &crate::visiting::VisitOptions,
chain: &mut temporary_annex::Annex<crate::Chain>,
) {
name.visit(visitors, data, options, chain);
}
fn visit_name_mut<TData>(
name: &mut Self::Name,
visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
data: &mut TData,
options: &crate::visiting::VisitOptions,
chain: &mut temporary_annex::Annex<crate::Chain>,
) {
name.visit_mut(visitors, data, options, chain);
}
}
impl FunctionBased for ClassConstructorBase {
type Header = Keyword<tsx_keywords::Constructor>;
type Name = ();
type Body = Block;
fn header_and_name_from_reader(
reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
_state: &mut crate::ParsingState,
_options: &ParseOptions,
) -> ParseResult<(Self::Header, Self::Name)> {
Ok((Keyword::from_reader(reader)?, ()))
}
fn header_and_name_to_string_from_buffer<T: source_map::ToString>(
buf: &mut T,
_header: &Self::Header,
_name: &Self::Name,
_options: &crate::ToStringOptions,
_depth: u8,
) {
buf.push_str("constructor");
}
fn header_left(header: &Self::Header) -> Option<source_map::Start> {
Some(header.get_position().get_start())
}
fn visit_name<TData>(
_: &Self::Name,
_: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
_: &mut TData,
_: &crate::visiting::VisitOptions,
_: &mut temporary_annex::Annex<crate::Chain>,
) {
}
fn visit_name_mut<TData>(
_: &mut Self::Name,
_: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
_: &mut TData,
_: &crate::visiting::VisitOptions,
_: &mut temporary_annex::Annex<crate::Chain>,
) {
}
}