1use std::fmt::Debug;
2
3use crate::{
4 derive_ASTNode,
5 errors::parse_lexing_error,
6 functions::{
7 FunctionBased, FunctionBody, HeadingAndPosition, MethodHeader, SuperParameter,
8 ThisParameter,
9 },
10 property_key::PublicOrPrivate,
11 tokens::token_as_identifier,
12 visiting::Visitable,
13 ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, PropertyKey, TSXKeyword,
14 TSXToken, TypeAnnotation, WithComment,
15};
16use source_map::Span;
17use tokenizer_lib::{sized_tokens::TokenStart, Token, TokenReader};
18use visitable_derive::Visitable;
19
20#[cfg_attr(target_family = "wasm", tsify::declare)]
21pub type IsStatic = bool;
22
23#[apply(derive_ASTNode)]
24#[derive(Debug, Clone, PartialEq, Visitable)]
25pub enum ClassMember {
26 Constructor(ClassConstructor),
27 Method(IsStatic, ClassFunction),
28 Property(IsStatic, ClassProperty),
29 StaticBlock(Block),
30 Indexer {
32 name: String,
33 indexer_type: TypeAnnotation,
34 return_type: TypeAnnotation,
35 is_readonly: bool,
36 position: Span,
37 },
38 Comment(String, bool, Span),
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct ClassConstructorBase;
43pub type ClassConstructor = FunctionBase<ClassConstructorBase>;
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct ClassFunctionBase;
47pub type ClassFunction = FunctionBase<ClassFunctionBase>;
48
49#[derive(Debug, Clone, PartialEq, Visitable)]
50#[apply(derive_ASTNode)]
51pub struct ClassProperty {
52 pub is_readonly: bool,
53 pub is_optional: bool,
54 pub key: WithComment<PropertyKey<PublicOrPrivate>>,
55 pub type_annotation: Option<TypeAnnotation>,
56 pub value: Option<Box<Expression>>,
57 pub position: Span,
58}
59
60impl ASTNode for ClassMember {
61 fn get_position(&self) -> Span {
62 match self {
63 Self::Constructor(cst) => cst.get_position(),
64 Self::Method(_, mtd) => mtd.get_position(),
65 Self::Property(_, prop) => prop.position,
66 Self::StaticBlock(blk) => blk.get_position(),
67 Self::Indexer { position: pos, .. } | Self::Comment(.., pos) => *pos,
68 }
69 }
70
71 #[allow(clippy::similar_names)]
72 fn from_reader(
73 reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
74 state: &mut crate::ParsingState,
75 options: &ParseOptions,
76 ) -> ParseResult<Self> {
77 if reader.peek().map_or(false, |t| t.0.is_comment()) {
78 let (comment, is_multiline, span) =
79 TSXToken::try_into_comment(reader.next().unwrap()).unwrap();
80 return Ok(Self::Comment(comment, is_multiline, span));
81 }
82
83 if let Some(Token(TSXToken::Keyword(TSXKeyword::Constructor), _)) = reader.peek() {
84 let constructor = ClassConstructor::from_reader(reader, state, options)?;
85 return Ok(ClassMember::Constructor(constructor));
86 }
87
88 let is_static = reader
89 .conditional_next(|tok| matches!(tok, TSXToken::Keyword(TSXKeyword::Static)))
90 .is_some();
91
92 if let Some(Token(TSXToken::OpenBrace, _)) = reader.peek() {
93 return Ok(ClassMember::StaticBlock(Block::from_reader(reader, state, options)?));
94 }
95
96 let readonly_position = state.optionally_expect_keyword(reader, TSXKeyword::Readonly);
97
98 if let Some(Token(TSXToken::OpenBracket, _)) = reader.peek() {
99 if let Some(Token(TSXToken::Colon, _)) = reader.peek_n(2) {
100 let Token(_, start) = reader.next().unwrap();
101 let (name, _) = token_as_identifier(
102 reader.next().ok_or_else(parse_lexing_error)?,
103 "class indexer",
104 )?;
105 reader.expect_next(TSXToken::Colon)?;
106 let indexer_type = TypeAnnotation::from_reader(reader, state, options)?;
107 reader.expect_next(TSXToken::CloseBracket)?;
108 reader.expect_next(TSXToken::Colon)?;
109 let return_type = TypeAnnotation::from_reader(reader, state, options)?;
110 return Ok(ClassMember::Indexer {
111 name,
112 is_readonly: readonly_position.is_some(),
113 indexer_type,
114 position: start.union(return_type.get_position()),
115 return_type,
116 });
117 }
118 }
119
120 let start = reader.peek().ok_or_else(parse_lexing_error)?.1;
122
123 let (header, key) = crate::functions::get_method_name(reader, state, options)?;
124
125 match reader.peek() {
126 Some(Token(TSXToken::OpenParentheses | TSXToken::OpenChevron, _))
127 if readonly_position.is_none() =>
128 {
129 let function = ClassFunction::from_reader_with_config(
130 reader,
131 state,
132 options,
133 (Some(start), header),
134 key,
135 )?;
136 Ok(ClassMember::Method(is_static, function))
137 }
138 Some(Token(token, _)) => {
139 if !header.is_no_modifiers() {
140 return crate::throw_unexpected_token(reader, &[TSXToken::OpenParentheses]);
141 }
142 let (member_type, is_optional) =
143 if let TSXToken::Colon | TSXToken::OptionalMember = token {
144 let is_optional = matches!(token, TSXToken::OptionalMember);
145 reader.next();
146 let type_annotation = TypeAnnotation::from_reader(reader, state, options)?;
147 (Some(type_annotation), is_optional)
148 } else {
149 (None, false)
150 };
151 let member_expression: Option<Expression> =
152 if let Some(Token(TSXToken::Assign, _)) = reader.peek() {
153 reader.next();
154 let expression = Expression::from_reader(reader, state, options)?;
155 Some(expression)
156 } else {
157 None
158 };
159 Ok(Self::Property(
160 is_static,
161 ClassProperty {
162 is_readonly: readonly_position.is_some(),
163 is_optional,
164 position: key.get_position(),
165 key,
166 type_annotation: member_type,
167 value: member_expression.map(Box::new),
168 },
169 ))
170 }
171 None => Err(parse_lexing_error()),
172 }
173 }
174
175 fn to_string_from_buffer<T: source_map::ToString>(
176 &self,
177 buf: &mut T,
178 options: &crate::ToStringOptions,
179 local: crate::LocalToStringInformation,
180 ) {
181 match self {
182 Self::Property(
183 is_static,
184 ClassProperty {
185 is_readonly,
186 is_optional: _,
187 key,
188 type_annotation,
189 value,
190 position: _,
191 },
192 ) => {
193 if *is_static {
194 buf.push_str("static ");
195 }
196 if *is_readonly {
197 buf.push_str("readonly ");
198 }
199 key.to_string_from_buffer(buf, options, local);
200 if let (true, Some(type_annotation)) =
201 (options.include_type_annotations, type_annotation)
202 {
203 buf.push_str(": ");
204 type_annotation.to_string_from_buffer(buf, options, local);
205 }
206 if let Some(value) = value {
207 buf.push_str(if options.pretty { " = " } else { "=" });
208 value.to_string_from_buffer(buf, options, local);
209 }
210 }
211 Self::Method(is_static, function) => {
212 if *is_static {
213 buf.push_str("static ");
214 }
215 function.to_string_from_buffer(buf, options, local.next_level());
216 }
217 Self::Constructor(constructor) => {
218 constructor.to_string_from_buffer(buf, options, local.next_level());
219 }
220 Self::StaticBlock(block) => {
221 buf.push_str("static ");
222 block.to_string_from_buffer(buf, options, local.next_level());
223 }
224 Self::Comment(content, is_multiline, _) => {
225 if options.should_add_comment(content) {
226 if *is_multiline {
227 buf.push_str("/*");
228 buf.push_str(content);
229 buf.push_str("*/");
230 } else {
231 buf.push_str("//");
232 buf.push_str(content);
233 buf.push_new_line();
234 }
235 }
236 }
237 Self::Indexer { name, indexer_type, return_type, is_readonly: _, position: _ } => {
238 if options.include_type_annotations {
239 buf.push('[');
240 buf.push_str(name);
241 buf.push_str(": ");
242 indexer_type.to_string_from_buffer(buf, options, local);
243 buf.push_str("]: ");
244 return_type.to_string_from_buffer(buf, options, local);
245 }
246 }
247 }
248 }
249}
250
251impl ClassFunction {
252 fn from_reader_with_config(
253 reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
254 state: &mut crate::ParsingState,
255 options: &ParseOptions,
256 get_set_generator: (Option<TokenStart>, MethodHeader),
257 key: WithComment<PropertyKey<PublicOrPrivate>>,
258 ) -> ParseResult<Self> {
259 FunctionBase::from_reader_with_header_and_name(
260 reader,
261 state,
262 options,
263 get_set_generator,
264 key,
265 )
266 }
267}
268
269impl FunctionBased for ClassFunctionBase {
270 type Header = MethodHeader;
271 type Name = WithComment<PropertyKey<PublicOrPrivate>>;
272 type LeadingParameter = (Option<ThisParameter>, Option<SuperParameter>);
273 type ParameterVisibility = ();
274 type Body = FunctionBody;
275
276 fn has_body(body: &Self::Body) -> bool {
277 body.0.is_some()
278 }
279
280 #[allow(clippy::similar_names)]
281 fn header_and_name_from_reader(
282 reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
283 state: &mut crate::ParsingState,
284 options: &ParseOptions,
285 ) -> ParseResult<(HeadingAndPosition<Self>, Self::Name)> {
286 let start = reader.peek().ok_or_else(parse_lexing_error)?.1;
288 let header = MethodHeader::from_reader(reader);
289 let name = WithComment::<PropertyKey<_>>::from_reader(reader, state, options)?;
290 Ok((((!header.is_no_modifiers()).then_some(start), header), name))
291 }
292
293 fn header_and_name_to_string_from_buffer<T: source_map::ToString>(
294 buf: &mut T,
295 header: &Self::Header,
296 name: &Self::Name,
297 options: &crate::ToStringOptions,
298 local: crate::LocalToStringInformation,
299 ) {
300 header.to_string_from_buffer(buf);
301 name.to_string_from_buffer(buf, options, local);
302 }
303
304 fn visit_name<TData>(
305 name: &Self::Name,
306 visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
307 data: &mut TData,
308 options: &crate::visiting::VisitOptions,
309 chain: &mut temporary_annex::Annex<crate::Chain>,
310 ) {
311 name.visit(visitors, data, options, chain);
312 }
313
314 fn visit_name_mut<TData>(
315 name: &mut Self::Name,
316 visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
317 data: &mut TData,
318 options: &crate::visiting::VisitOptions,
319 chain: &mut temporary_annex::Annex<crate::Chain>,
320 ) {
321 name.visit_mut(visitors, data, options, chain);
322 }
323
324 fn get_name(name: &Self::Name) -> Option<&str> {
325 if let PropertyKey::Identifier(name, ..) = name.get_ast_ref() {
326 Some(name.as_str())
327 } else {
328 None
329 }
330 }
331}
332
333impl FunctionBased for ClassConstructorBase {
334 type Header = ();
335 type Name = ();
336 type Body = FunctionBody;
337 type LeadingParameter = (Option<ThisParameter>, Option<SuperParameter>);
338 type ParameterVisibility = Option<crate::types::Visibility>;
339
340 fn has_body(body: &Self::Body) -> bool {
345 body.0.is_some()
346 }
347
348 fn header_and_name_from_reader(
349 reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
350 state: &mut crate::ParsingState,
351 _options: &ParseOptions,
352 ) -> ParseResult<(HeadingAndPosition<Self>, Self::Name)> {
353 let start = state.expect_keyword(reader, TSXKeyword::Constructor)?;
354 Ok(((Some(start), ()), ()))
355 }
356
357 fn header_and_name_to_string_from_buffer<T: source_map::ToString>(
358 buf: &mut T,
359 _header: &Self::Header,
360 _name: &Self::Name,
361 _options: &crate::ToStringOptions,
362 _local: crate::LocalToStringInformation,
363 ) {
364 buf.push_str("constructor");
365 }
366
367 fn visit_name<TData>(
368 (): &Self::Name,
369 _: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
370 _: &mut TData,
371 _: &crate::visiting::VisitOptions,
372 _: &mut temporary_annex::Annex<crate::Chain>,
373 ) {
374 }
375
376 fn visit_name_mut<TData>(
377 (): &mut Self::Name,
378 _: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
379 _: &mut TData,
380 _: &crate::visiting::VisitOptions,
381 _: &mut temporary_annex::Annex<crate::Chain>,
382 ) {
383 }
384
385 fn get_name((): &Self::Name) -> Option<&str> {
386 None
387 }
388}
389
390#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
391#[allow(dead_code)]
392const CLASS_CONSTRUCTOR_AND_FUNCTION_TYPES: &str = r"
393 export interface ClassConstructor extends FunctionBase {
394 body: FunctionBody,
395 parameters: FunctionParameters<[ThisParameter | null, SuperParameter | null], Visibility>,
396 }
397
398 export interface ClassFunction extends FunctionBase {
399 header: MethodHeader,
400 name: WithComment<PropertyKey<PublicOrPrivate>>
401 parameters: FunctionParameters<ThisParameter | null, null>,
402 body: FunctionBody,
403 }
404";