ezno_parser/declarations/
mod.rs

1pub mod classes;
2pub mod export;
3pub mod import;
4pub mod variable;
5
6pub use super::types::{
7	declare_variable::*,
8	enum_declaration::{EnumDeclaration, EnumMember},
9	interface::InterfaceDeclaration,
10	type_alias::TypeAlias,
11};
12
13use derive_enum_from_into::{EnumFrom, EnumTryInto};
14use get_field_by_type::GetFieldByType;
15use source_map::Span;
16use tokenizer_lib::{sized_tokens::TokenStart, Token};
17use visitable_derive::Visitable;
18
19use crate::{
20	derive_ASTNode, errors::parse_lexing_error, extensions::decorators,
21	throw_unexpected_token_with_token, Decorated, Marker, ParseError, ParseErrors, ParseOptions,
22	Quoted, StatementPosition, TSXKeyword, TSXToken,
23};
24
25pub use self::{
26	classes::ClassDeclaration,
27	export::ExportDeclaration,
28	import::ImportDeclaration,
29	variable::{VariableDeclaration, VariableDeclarationItem},
30};
31
32pub type StatementFunctionBase = crate::functions::GeneralFunctionBase<StatementPosition>;
33pub type StatementFunction = crate::FunctionBase<StatementFunctionBase>;
34
35#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
36#[allow(dead_code)]
37const TYPES_STATEMENT_FUNCTION: &str = r"
38	export interface StatementFunction extends FunctionBase {
39		header: FunctionHeader,
40		parameters: FunctionParameters<ThisParameter | null, null>,
41		body: Block,
42		name: StatementPosition
43	}
44";
45
46#[apply(derive_ASTNode)]
47#[derive(
48	Debug, Clone, Visitable, EnumFrom, EnumTryInto, PartialEq, get_field_by_type::GetFieldByType,
49)]
50#[get_field_by_type_target(Span)]
51#[try_into_references(&, &mut)]
52pub enum Declaration {
53	Variable(VariableDeclaration),
54	Function(Decorated<StatementFunction>),
55	Class(Decorated<ClassDeclaration<StatementPosition>>),
56	Enum(Decorated<EnumDeclaration>),
57	Interface(Decorated<InterfaceDeclaration>),
58	TypeAlias(TypeAlias),
59	// Special TS only
60	DeclareVariable(DeclareVariableDeclaration),
61	#[cfg(feature = "full-typescript")]
62	Namespace(crate::types::namespace::Namespace),
63	// Top level only
64	Import(ImportDeclaration),
65	Export(Decorated<ExportDeclaration>),
66}
67
68impl Declaration {
69	// TODO strict mode can affect result
70	/// Takes `reader` as sometimes needs to `peek_n`
71	pub(crate) fn is_declaration_start(
72		reader: &mut impl tokenizer_lib::TokenReader<crate::TSXToken, crate::TokenStart>,
73		options: &ParseOptions,
74	) -> bool {
75		let Some(Token(token, _)) = reader.peek() else { return false };
76
77		let result = matches!(
78			token,
79			TSXToken::Keyword(
80				TSXKeyword::Let
81					| TSXKeyword::Const
82					| TSXKeyword::Function
83					| TSXKeyword::Class
84					| TSXKeyword::Export
85			) | TSXToken::At,
86		);
87
88		#[cfg(feature = "extras")]
89		return result
90			|| matches!(token, TSXToken::Keyword(kw) if options.custom_function_headers && kw.is_special_function_header())
91			|| (matches!(token, TSXToken::Keyword(TSXKeyword::Namespace) if cfg!(feature = "full-typescript")))
92			|| {
93				let TSXToken::Keyword(token) = *token else { return false };
94				let Some(Token(after, _)) = reader.peek_n(1) else { return false };
95
96				#[allow(clippy::match_same_arms)]
97				match (token, after) {
98					// For dynamic import
99					(
100						TSXKeyword::Import,
101						TSXToken::OpenBrace
102						| TSXToken::Keyword(..)
103						| TSXToken::Identifier(..)
104						| TSXToken::StringLiteral(..)
105						| TSXToken::Multiply,
106					) => true,
107					(TSXKeyword::Declare | TSXKeyword::Interface, _) => options.type_annotations,
108					(TSXKeyword::Async, TSXToken::Keyword(TSXKeyword::Function)) => true,
109					(TSXKeyword::Async, TSXToken::Keyword(kw)) => {
110						options.custom_function_headers && kw.is_special_function_header()
111					}
112					// Extra
113					(TSXKeyword::From, TSXToken::StringLiteral(..)) => true,
114					(..) => false,
115				}
116			};
117
118		#[cfg(not(feature = "extras"))]
119		return result || {
120			let TSXToken::Keyword(token) = *token else { return false };
121
122			// For dynamic import
123			matches!(token, TSXKeyword::Import)
124				&& matches!(
125					reader.peek_n(1),
126					Some(Token(
127						TSXToken::OpenBrace
128							| TSXToken::Keyword(..)
129							| TSXToken::Identifier(..)
130							| TSXToken::StringLiteral(..)
131							| TSXToken::Multiply,
132						_
133					))
134				)
135		};
136	}
137}
138
139#[apply(derive_ASTNode)]
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub enum ImportLocation {
142	Quoted(String, Quoted),
143	#[cfg_attr(feature = "self-rust-tokenize", self_tokenize_field(0))]
144	Marker(
145		#[cfg_attr(target_family = "wasm", tsify(type = "Marker<ImportLocation>"))] Marker<Self>,
146	),
147}
148
149impl ImportLocation {
150	pub(crate) fn from_reader(
151		reader: &mut impl tokenizer_lib::TokenReader<crate::TSXToken, crate::TokenStart>,
152		state: &mut crate::ParsingState,
153		options: &crate::ParseOptions,
154		start: Option<TokenStart>,
155	) -> crate::ParseResult<(Self, source_map::End)> {
156		if let (true, Some(start), Some(Token(peek, at))) =
157			(options.partial_syntax, start, reader.peek())
158		{
159			let next_is_not_location_like = peek.is_statement_or_declaration_start()
160				&& state
161					.line_starts
162					.byte_indexes_on_different_lines(start.0 as usize, at.0 as usize);
163
164			if next_is_not_location_like {
165				return Ok((
166					ImportLocation::Marker(state.new_partial_point_marker(*at)),
167					source_map::End(start.0),
168				));
169			}
170		}
171
172		let token = reader.next().ok_or_else(parse_lexing_error)?;
173		if let Token(TSXToken::StringLiteral(content, quoted), start) = token {
174			let with_length = start.get_end_after(content.len() + 1);
175			Ok((ImportLocation::Quoted(content, quoted), with_length))
176		} else if options.interpolation_points
177			&& matches!(&token.0, TSXToken::Identifier(i) if i == crate::marker::MARKER)
178		{
179			Ok((Self::Marker(state.new_partial_point_marker(token.1)), source_map::End(token.1 .0)))
180		} else {
181			Err(ParseError::new(
182				ParseErrors::ExpectedStringLiteral { found: token.0 },
183				token.1.with_length(0),
184			))
185		}
186	}
187
188	pub(crate) fn to_string_from_buffer<T: source_map::ToString>(&self, buf: &mut T) {
189		match self {
190			ImportLocation::Quoted(inner, quoted) => {
191				buf.push(quoted.as_char());
192				buf.push_str(inner);
193				buf.push(quoted.as_char());
194			}
195			ImportLocation::Marker(_) => {}
196		}
197	}
198
199	/// Can be None if self is a marker point
200	#[must_use]
201	pub fn get_path(&self) -> Option<&str> {
202		if let Self::Quoted(name, _) = self {
203			Some(name)
204		} else {
205			None
206		}
207	}
208}
209
210impl crate::ASTNode for Declaration {
211	fn from_reader(
212		reader: &mut impl tokenizer_lib::TokenReader<crate::TSXToken, crate::TokenStart>,
213		state: &mut crate::ParsingState,
214		options: &crate::ParseOptions,
215	) -> crate::ParseResult<Self> {
216		// TODO assert decorators are used. If they exist but item is not `Decorated`
217		// then need to throw a parse error
218		let decorators = decorators::decorators_from_reader(reader, state, options)?;
219
220		match reader.peek().ok_or_else(parse_lexing_error)?.0 {
221			// Const can be either variable declaration or const enum
222			TSXToken::Keyword(TSXKeyword::Const) => {
223				let after_const = reader.peek_n(2);
224				if let Some(Token(TSXToken::Keyword(TSXKeyword::Enum), _)) = after_const {
225					EnumDeclaration::from_reader(reader, state, options)
226						.map(|on| Declaration::Enum(Decorated::new(decorators, on)))
227				} else {
228					let declaration = VariableDeclaration::from_reader(reader, state, options)?;
229					Ok(Declaration::Variable(declaration))
230				}
231			}
232			TSXToken::Keyword(TSXKeyword::Let) => {
233				let declaration = VariableDeclaration::from_reader(reader, state, options)?;
234				Ok(Declaration::Variable(declaration))
235			}
236			TSXToken::Keyword(TSXKeyword::Enum) => {
237				EnumDeclaration::from_reader(reader, state, options)
238					.map(|on| Declaration::Enum(Decorated::new(decorators, on)))
239			}
240			#[cfg(feature = "extras")]
241			TSXToken::Keyword(ref kw) if kw.is_special_function_header() => {
242				let function = StatementFunction::from_reader(reader, state, options)?;
243				Ok(Declaration::Function(Decorated::new(decorators, function)))
244			}
245			TSXToken::Keyword(TSXKeyword::Function | TSXKeyword::Async) => {
246				let function = StatementFunction::from_reader(reader, state, options)?;
247				Ok(Declaration::Function(Decorated::new(decorators, function)))
248			}
249			TSXToken::Keyword(TSXKeyword::Class) => {
250				let Token(_, start) = reader.next().unwrap();
251				state.append_keyword_at_pos(start.0, TSXKeyword::Class);
252				ClassDeclaration::from_reader_sub_class_keyword(reader, state, options, start)
253					.map(|on| Declaration::Class(Decorated::new(decorators, on)))
254			}
255			TSXToken::Keyword(TSXKeyword::Export) => {
256				ExportDeclaration::from_reader(reader, state, options)
257					.map(|on| Declaration::Export(Decorated::new(decorators, on)))
258			}
259			TSXToken::Keyword(TSXKeyword::Import) => {
260				ImportDeclaration::from_reader(reader, state, options).map(Into::into)
261			}
262			TSXToken::Keyword(TSXKeyword::Interface) if options.type_annotations => {
263				InterfaceDeclaration::from_reader(reader, state, options)
264					.map(|on| Declaration::Interface(Decorated::new(decorators, on)))
265			}
266			TSXToken::Keyword(TSXKeyword::Type) if options.type_annotations => {
267				TypeAlias::from_reader(reader, state, options).map(Into::into)
268			}
269			TSXToken::Keyword(TSXKeyword::Declare) if options.type_annotations => {
270				let Token(_, start) = reader.next().unwrap();
271				match reader.peek().ok_or_else(parse_lexing_error)?.0 {
272					TSXToken::Keyword(TSXKeyword::Let | TSXKeyword::Const | TSXKeyword::Var) => {
273						DeclareVariableDeclaration::from_reader_sub_declare(
274							reader,
275							state,
276							options,
277							Some(start),
278							decorators,
279						)
280						.map(Into::into)
281					}
282					TSXToken::Keyword(TSXKeyword::Class) => {
283						let mut class = ClassDeclaration::<StatementPosition>::from_reader(
284							reader, state, options,
285						)?;
286						class.name.is_declare = true;
287						class.position.start = start.0;
288						Ok(Declaration::Class(Decorated::new(decorators, class)))
289					}
290					TSXToken::Keyword(TSXKeyword::Function) => {
291						let mut function = StatementFunction::from_reader(reader, state, options)?;
292						function.name.is_declare = true;
293						function.position.start = start.0;
294						Ok(Declaration::Function(Decorated::new(decorators, function)))
295					}
296					TSXToken::Keyword(TSXKeyword::Type) => {
297						let mut alias = TypeAlias::from_reader(reader, state, options)?;
298						alias.name.is_declare = true;
299						alias.position.start = start.0;
300						Ok(Declaration::TypeAlias(alias))
301					}
302					#[cfg(feature = "full-typescript")]
303					TSXToken::Keyword(TSXKeyword::Namespace) => {
304						let mut namespace = crate::types::namespace::Namespace::from_reader(
305							reader, state, options,
306						)?;
307						namespace.is_declare = true;
308						namespace.position.start = start.0;
309						Ok(Declaration::Namespace(namespace))
310					}
311					_ => throw_unexpected_token_with_token(
312						reader.next().ok_or_else(parse_lexing_error)?,
313						&[
314							TSXToken::Keyword(TSXKeyword::Let),
315							TSXToken::Keyword(TSXKeyword::Const),
316							TSXToken::Keyword(TSXKeyword::Var),
317							TSXToken::Keyword(TSXKeyword::Function),
318							TSXToken::Keyword(TSXKeyword::Class),
319							TSXToken::Keyword(TSXKeyword::Type),
320							TSXToken::Keyword(TSXKeyword::Namespace),
321						],
322					),
323				}
324			}
325			#[cfg(feature = "extras")]
326			TSXToken::Keyword(TSXKeyword::From) => {
327				ImportDeclaration::reversed_from_reader(reader, state, options).map(Into::into)
328			}
329			#[cfg(feature = "full-typescript")]
330			TSXToken::Keyword(TSXKeyword::Namespace) => {
331				crate::types::namespace::Namespace::from_reader(reader, state, options)
332					.map(Into::into)
333			}
334			_ => throw_unexpected_token_with_token(
335				reader.next().ok_or_else(parse_lexing_error)?,
336				&[
337					TSXToken::Keyword(TSXKeyword::Let),
338					TSXToken::Keyword(TSXKeyword::Const),
339					TSXToken::Keyword(TSXKeyword::Function),
340					TSXToken::Keyword(TSXKeyword::Class),
341					TSXToken::Keyword(TSXKeyword::Enum),
342					TSXToken::Keyword(TSXKeyword::Type),
343					TSXToken::Keyword(TSXKeyword::Declare),
344					TSXToken::Keyword(TSXKeyword::Import),
345					TSXToken::Keyword(TSXKeyword::Export),
346					TSXToken::Keyword(TSXKeyword::Async),
347					#[cfg(feature = "extras")]
348					TSXToken::Keyword(TSXKeyword::Generator),
349				],
350			),
351		}
352	}
353
354	fn to_string_from_buffer<T: source_map::ToString>(
355		&self,
356		buf: &mut T,
357		options: &crate::ToStringOptions,
358		local: crate::LocalToStringInformation,
359	) {
360		match self {
361			Declaration::Variable(var) => var.to_string_from_buffer(buf, options, local),
362			Declaration::Class(cls) => cls.to_string_from_buffer(buf, options, local),
363			Declaration::Import(is) => is.to_string_from_buffer(buf, options, local),
364			Declaration::Export(es) => es.to_string_from_buffer(buf, options, local),
365			Declaration::Function(f) => f.to_string_from_buffer(buf, options, local),
366			Declaration::Interface(id) => id.to_string_from_buffer(buf, options, local),
367			Declaration::TypeAlias(ta) => ta.to_string_from_buffer(buf, options, local),
368			Declaration::Enum(r#enum) => r#enum.to_string_from_buffer(buf, options, local),
369			Declaration::DeclareVariable(dvd) => dvd.to_string_from_buffer(buf, options, local),
370			#[cfg(feature = "full-typescript")]
371			Declaration::Namespace(ns) => ns.to_string_from_buffer(buf, options, local),
372		}
373	}
374
375	fn get_position(&self) -> Span {
376		*self.get()
377	}
378}
379
380pub trait ImportOrExport: std::fmt::Debug + Clone + PartialEq + Sync + Send + 'static {
381	const PREFIX: bool;
382}
383
384impl ImportOrExport for ImportDeclaration {
385	const PREFIX: bool = true;
386}
387
388impl ImportOrExport for ExportDeclaration {
389	const PREFIX: bool = false;
390}
391
392/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#syntax>
393#[derive(Debug, Clone, PartialEq, Visitable, GetFieldByType)]
394#[get_field_by_type_target(Span)]
395#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
396pub struct ImportExportPart<T: ImportOrExport> {
397	pub just_type: bool,
398	pub name: crate::VariableIdentifier,
399	pub alias: Option<ImportExportName>,
400	pub position: Span,
401	#[visit_skip_field]
402	pub _marker: std::marker::PhantomData<T>,
403}
404
405#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
406#[allow(dead_code)]
407const IMPORT_EXPORT_PART_TYPE: &str = r"
408	type ImportExportPart<_T> = { just_type: boolean, name: VariableIdentifier, alias: ImportExportName | null, position: Span };
409";
410
411impl<T: ImportOrExport> crate::ListItem for ImportExportPart<T> {
412	type LAST = ();
413}
414
415impl<U: ImportOrExport> crate::ASTNode for ImportExportPart<U> {
416	fn get_position(&self) -> Span {
417		*GetFieldByType::get(self)
418	}
419
420	// TODO also single line comments here
421	fn from_reader(
422		reader: &mut impl crate::TokenReader<TSXToken, crate::TokenStart>,
423		state: &mut crate::ParsingState,
424		options: &ParseOptions,
425	) -> crate::ParseResult<Self> {
426		let just_type =
427			reader.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Type))).is_some();
428
429		if U::PREFIX {
430			let (alias, position) = ImportExportName::from_reader(reader, state, options)?;
431			if reader.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::As))).is_some()
432			{
433				let name = crate::VariableIdentifier::from_reader(reader, state, options)?;
434				let position = position.union(name.get_position());
435				Ok(Self {
436					just_type,
437					name,
438					alias: Some(alias),
439					position,
440					_marker: Default::default(),
441				})
442			} else if let ImportExportName::Reference(name) = alias {
443				let name = crate::VariableIdentifier::Standard(name, position);
444				Ok(Self { just_type, name, alias: None, position, _marker: Default::default() })
445			} else {
446				crate::throw_unexpected_token(reader, &[TSXToken::Keyword(TSXKeyword::As)])
447			}
448		} else {
449			let name = crate::VariableIdentifier::from_reader(reader, state, options)?;
450			let mut position = name.get_position();
451			let alias = if reader
452				.conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::As)))
453				.is_some()
454			{
455				let (alias, end) = ImportExportName::from_reader(reader, state, options)?;
456				position = position.union(end);
457				Some(alias)
458			} else {
459				None
460			};
461			Ok(Self { just_type, name, alias, position, _marker: Default::default() })
462		}
463	}
464
465	fn to_string_from_buffer<T: source_map::ToString>(
466		&self,
467		buf: &mut T,
468		options: &crate::ToStringOptions,
469		local: crate::LocalToStringInformation,
470	) {
471		if self.just_type && options.include_type_annotations {
472			buf.push_str("type ");
473		}
474		if let Some(ref alias) = self.alias {
475			if U::PREFIX {
476				alias.to_string_from_buffer(buf, options, local);
477				buf.push_str(" as ");
478				self.name.to_string_from_buffer(buf, options, local);
479			} else {
480				self.name.to_string_from_buffer(buf, options, local);
481				buf.push_str(" as ");
482				alias.to_string_from_buffer(buf, options, local);
483			}
484		} else {
485			self.name.to_string_from_buffer(buf, options, local);
486		}
487	}
488}
489
490// If `options.pretty` sort by name
491fn import_export_parts_to_string_from_buffer<T: source_map::ToString, U: ImportOrExport>(
492	parts: &[ImportExportPart<U>],
493	buf: &mut T,
494	options: &crate::ToStringOptions,
495	local: crate::LocalToStringInformation,
496) {
497	use super::ASTNode;
498	use iterator_endiate::EndiateIteratorExt;
499
500	buf.push('{');
501	options.push_gap_optionally(buf);
502	if options.pretty {
503		let mut parts: Vec<&ImportExportPart<U>> = parts.iter().collect();
504		parts.sort_unstable_by_key(|part| part.name.as_option_str().unwrap_or_default());
505		for (at_end, part) in parts.iter().endiate() {
506			part.to_string_from_buffer(buf, options, local);
507			if !at_end {
508				buf.push(',');
509				options.push_gap_optionally(buf);
510			}
511		}
512	} else {
513		for (at_end, part) in parts.iter().endiate() {
514			part.to_string_from_buffer(buf, options, local);
515			if !at_end {
516				buf.push(',');
517				options.push_gap_optionally(buf);
518			}
519		}
520	}
521	options.push_gap_optionally(buf);
522	buf.push('}');
523}
524
525#[cfg(feature = "self-rust-tokenize")]
526impl<U: ImportOrExport> self_rust_tokenize::SelfRustTokenize for ImportExportPart<U> {
527	fn append_to_token_stream(
528		&self,
529		_token_stream: &mut self_rust_tokenize::proc_macro2::TokenStream,
530	) {
531		todo!("")
532	}
533}
534
535/// TODO `default` should have its own variant?
536#[derive(Debug, Clone, PartialEq)]
537#[apply(derive_ASTNode)]
538pub enum ImportExportName {
539	Reference(String),
540	Quoted(String, Quoted),
541	/// For typing here
542	#[cfg_attr(feature = "self-rust-tokenize", self_tokenize_field(0))]
543	Marker(
544		#[cfg_attr(target_family = "wasm", tsify(type = "Marker<ImportExportName>"))] Marker<Self>,
545	),
546}
547
548impl ImportExportName {
549	pub(crate) fn from_reader(
550		reader: &mut impl crate::TokenReader<TSXToken, crate::TokenStart>,
551		state: &mut crate::ParsingState,
552		options: &ParseOptions,
553	) -> crate::ParseResult<(Self, Span)> {
554		if let Some(Token(TSXToken::Comma, pos)) = reader.peek() {
555			let marker = state.new_partial_point_marker(*pos);
556			return Ok((ImportExportName::Marker(marker), pos.union(source_map::End(pos.0))));
557		}
558		let token = reader.next().unwrap();
559		if let Token(TSXToken::StringLiteral(alias, quoted), start) = token {
560			let with_length = start.with_length(alias.len() + 1);
561			state.constant_imports.push(alias.clone());
562			Ok((ImportExportName::Quoted(alias, quoted), with_length))
563		} else {
564			let (ident, pos) = crate::tokens::token_as_identifier(token, "import alias")?;
565			if options.interpolation_points && ident == crate::marker::MARKER {
566				Ok((ImportExportName::Marker(state.new_partial_point_marker(pos.get_start())), pos))
567			} else {
568				Ok((ImportExportName::Reference(ident), pos))
569			}
570		}
571	}
572
573	pub(crate) fn to_string_from_buffer<T: source_map::ToString>(
574		&self,
575		buf: &mut T,
576		_options: &crate::ToStringOptions,
577		_local: crate::LocalToStringInformation,
578	) {
579		match self {
580			ImportExportName::Reference(alias) => buf.push_str(alias),
581			ImportExportName::Quoted(alias, q) => {
582				buf.push(q.as_char());
583				buf.push_str(alias);
584				buf.push(q.as_char());
585			}
586			ImportExportName::Marker(_) => {}
587		}
588	}
589}