ezno_parser/declarations/classes/
class_member.rs

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	/// Really for interfaces but here
31	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		// TODO not great
121		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		// TODO not great
287		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 get_chain_variable(this: &FunctionBase<Self>) -> ChainVariable {
341	// 	ChainVariable::UnderClassConstructor(this.body.1)
342	// }
343
344	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";