ezno_parser/expressions/
object_literal.rs

1use crate::{
2	derive_ASTNode,
3	errors::parse_lexing_error,
4	functions::{FunctionBased, HeadingAndPosition, MethodHeader, ThisParameter},
5	property_key::AlwaysPublic,
6	throw_unexpected_token_with_token,
7	visiting::Visitable,
8	ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, PropertyKey, Span,
9	TSXToken, Token, TokenReader, WithComment,
10};
11
12use derive_partial_eq_extras::PartialEqExtras;
13use std::fmt::Debug;
14use tokenizer_lib::sized_tokens::{TokenReaderWithTokenEnds, TokenStart};
15use visitable_derive::Visitable;
16
17#[apply(derive_ASTNode)]
18#[derive(Debug, Clone, PartialEq, Visitable, get_field_by_type::GetFieldByType)]
19#[get_field_by_type_target(Span)]
20pub struct ObjectLiteral {
21	pub members: Vec<ObjectLiteralMember>,
22	pub position: Span,
23}
24
25#[apply(derive_ASTNode)]
26#[derive(Debug, Clone, PartialEqExtras, get_field_by_type::GetFieldByType)]
27#[partial_eq_ignore_types(Span, VariableId)]
28#[get_field_by_type_target(Span)]
29pub enum ObjectLiteralMember {
30	Spread(Expression, Span),
31	Shorthand(String, Span),
32	Property {
33		key: WithComment<PropertyKey<AlwaysPublic>>,
34		/// Makes object destructuring syntax a subset of object literal syntax
35		assignment: bool,
36		value: Expression,
37		position: Span,
38	},
39	Method(ObjectLiteralMethod),
40	Comment(String, bool, Span),
41}
42
43impl crate::Visitable for ObjectLiteralMember {
44	fn visit<TData>(
45		&self,
46		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
47		data: &mut TData,
48		options: &crate::VisitOptions,
49		chain: &mut temporary_annex::Annex<crate::Chain>,
50	) {
51		match self {
52			ObjectLiteralMember::Shorthand(..)
53			| ObjectLiteralMember::Property { .. }
54			| ObjectLiteralMember::Spread(..)
55			| ObjectLiteralMember::Comment(..) => {}
56			ObjectLiteralMember::Method(method) => method.visit(visitors, data, options, chain),
57		}
58	}
59
60	fn visit_mut<TData>(
61		&mut self,
62		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
63		data: &mut TData,
64		options: &crate::VisitOptions,
65		chain: &mut temporary_annex::Annex<crate::Chain>,
66	) {
67		match self {
68			ObjectLiteralMember::Shorthand(..)
69			| ObjectLiteralMember::Property { .. }
70			| ObjectLiteralMember::Spread(..)
71			| ObjectLiteralMember::Comment(..) => {}
72			ObjectLiteralMember::Method(method) => method.visit_mut(visitors, data, options, chain),
73		}
74	}
75}
76
77#[derive(Debug, PartialEq, Eq, Clone, Hash)]
78pub struct ObjectLiteralMethodBase;
79pub type ObjectLiteralMethod = FunctionBase<ObjectLiteralMethodBase>;
80
81#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
82#[allow(dead_code)]
83const OBJECT_LITERAL_METHOD_TYPE: &str = r"
84	export interface ObjectLiteralMethod extends FunctionBase {
85		header: MethodHeader,
86		body: Block,
87		name: WithComment<PropertyKey<AlwaysPublic>>,
88		parameters: FunctionParameters<ThisParameter | null, null>
89	}
90";
91
92impl FunctionBased for ObjectLiteralMethodBase {
93	type Name = WithComment<PropertyKey<AlwaysPublic>>;
94	type Header = MethodHeader;
95	type Body = Block;
96	type LeadingParameter = Option<ThisParameter>;
97	type ParameterVisibility = ();
98
99	fn header_and_name_from_reader(
100		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
101		state: &mut crate::ParsingState,
102		options: &ParseOptions,
103	) -> ParseResult<(HeadingAndPosition<Self>, Self::Name)> {
104		// TODO not great
105		let start = reader.peek().ok_or_else(parse_lexing_error)?.1;
106		Ok((
107			(Some(start), MethodHeader::from_reader(reader)),
108			WithComment::from_reader(reader, state, options)?,
109		))
110	}
111
112	fn header_and_name_to_string_from_buffer<T: source_map::ToString>(
113		buf: &mut T,
114		header: &Self::Header,
115		name: &Self::Name,
116		options: &crate::ToStringOptions,
117		local: crate::LocalToStringInformation,
118	) {
119		header.to_string_from_buffer(buf);
120		name.to_string_from_buffer(buf, options, local);
121	}
122
123	fn visit_name<TData>(
124		name: &Self::Name,
125		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
126		data: &mut TData,
127		options: &crate::visiting::VisitOptions,
128		chain: &mut temporary_annex::Annex<crate::Chain>,
129	) {
130		name.visit(visitors, data, options, chain);
131	}
132
133	fn visit_name_mut<TData>(
134		name: &mut Self::Name,
135		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
136		data: &mut TData,
137		options: &crate::visiting::VisitOptions,
138		chain: &mut temporary_annex::Annex<crate::Chain>,
139	) {
140		name.visit_mut(visitors, data, options, chain);
141	}
142
143	fn get_name(name: &Self::Name) -> Option<&str> {
144		if let PropertyKey::Identifier(name, ..) = name.get_ast_ref() {
145			Some(name.as_str())
146		} else {
147			None
148		}
149	}
150}
151
152impl Eq for ObjectLiteralMember {}
153
154impl ASTNode for ObjectLiteral {
155	fn get_position(&self) -> Span {
156		self.position
157	}
158
159	fn from_reader(
160		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
161		state: &mut crate::ParsingState,
162		options: &ParseOptions,
163	) -> ParseResult<Self> {
164		let start = reader.expect_next(TSXToken::OpenBrace)?;
165		Self::from_reader_sub_open_curly(reader, state, options, start)
166	}
167
168	fn to_string_from_buffer<T: source_map::ToString>(
169		&self,
170		buf: &mut T,
171		options: &crate::ToStringOptions,
172		local: crate::LocalToStringInformation,
173	) {
174		crate::to_string_bracketed(&self.members, ('{', '}'), buf, options, local);
175	}
176}
177
178impl ObjectLiteral {
179	pub(crate) fn from_reader_sub_open_curly(
180		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
181		state: &mut crate::ParsingState,
182		options: &ParseOptions,
183		start: TokenStart,
184	) -> ParseResult<Self> {
185		let mut members: Vec<ObjectLiteralMember> = Vec::new();
186		loop {
187			if matches!(reader.peek(), Some(Token(TSXToken::CloseBrace, _))) {
188				break;
189			}
190			let member = ObjectLiteralMember::from_reader(reader, state, options)?;
191			let is_comment = matches!(member, ObjectLiteralMember::Comment(..));
192			members.push(member);
193			if let Some(Token(TSXToken::Comma, _)) = reader.peek() {
194				reader.next();
195			} else if !is_comment {
196				break;
197			}
198		}
199		let end = reader.expect_next_get_end(TSXToken::CloseBrace)?;
200		Ok(ObjectLiteral { members, position: start.union(end) })
201	}
202}
203
204impl ASTNode for ObjectLiteralMember {
205	#[allow(clippy::similar_names)]
206	fn from_reader(
207		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
208		state: &mut crate::ParsingState,
209		options: &ParseOptions,
210	) -> ParseResult<Self> {
211		if reader.peek().map_or(false, |t| t.0.is_comment()) {
212			let (comment, is_multiline, span) =
213				TSXToken::try_into_comment(reader.next().unwrap()).unwrap();
214			return Ok(Self::Comment(comment, is_multiline, span));
215		}
216
217		if let Some(Token(_, spread_start)) =
218			reader.conditional_next(|tok| matches!(tok, TSXToken::Spread))
219		{
220			// TODO precedence okay?
221			let expression = Expression::from_reader(reader, state, options)?;
222			let position = spread_start.union(expression.get_position());
223			return Ok(Self::Spread(expression, position));
224		};
225
226		// TODO not great
227		let start = reader.peek().ok_or_else(parse_lexing_error)?.1;
228
229		// Catch for named get or set :(
230		let (header, key) = crate::functions::get_method_name(reader, state, options)?;
231
232		let Token(token, _) = &reader.peek().ok_or_else(parse_lexing_error)?;
233		match token {
234			// Functions, (OpenChevron is for generic parameters)
235			TSXToken::OpenParentheses | TSXToken::OpenChevron => {
236				let method: ObjectLiteralMethod = FunctionBase::from_reader_with_header_and_name(
237					reader,
238					state,
239					options,
240					(Some(start), header),
241					key,
242				)?;
243
244				Ok(Self::Method(method))
245			}
246			_ => {
247				if !header.is_no_modifiers() {
248					return crate::throw_unexpected_token(reader, &[TSXToken::OpenParentheses]);
249				}
250				if let Some(Token(TSXToken::Comma | TSXToken::CloseBrace, _)) = reader.peek() {
251					if let PropertyKey::Identifier(name, position, _) = key.get_ast() {
252						Ok(Self::Shorthand(name, position))
253					} else {
254						let token = reader.next().ok_or_else(parse_lexing_error)?;
255						throw_unexpected_token_with_token(token, &[TSXToken::Colon])
256					}
257				} else {
258					let token = reader.next().ok_or_else(parse_lexing_error)?;
259					let assignment = match token.0 {
260						TSXToken::Colon => false,
261						TSXToken::Assign => true,
262						_ => return throw_unexpected_token_with_token(token, &[TSXToken::Colon]),
263					};
264					// let assignment = if let
265					let value = Expression::from_reader(reader, state, options)?;
266					let position = key.get_position().union(value.get_position());
267					Ok(Self::Property { assignment, key, value, position })
268				}
269			}
270		}
271	}
272
273	fn to_string_from_buffer<T: source_map::ToString>(
274		&self,
275		buf: &mut T,
276		options: &crate::ToStringOptions,
277		local: crate::LocalToStringInformation,
278	) {
279		match self {
280			Self::Property { assignment: _, key, value, position: _ } => {
281				key.to_string_from_buffer(buf, options, local);
282				buf.push(':');
283				options.push_gap_optionally(buf);
284				value.to_string_from_buffer(buf, options, local);
285			}
286			Self::Shorthand(name, ..) => {
287				buf.push_str(name.as_str());
288			}
289			Self::Method(func) => {
290				func.to_string_from_buffer(buf, options, local);
291			}
292			Self::Spread(spread_expr, _) => {
293				buf.push_str("...");
294				spread_expr.to_string_from_buffer(buf, options, local);
295			}
296			Self::Comment(content, is_multiline, _) => {
297				if options.should_add_comment(content) {
298					if *is_multiline {
299						buf.push_str("/*");
300						buf.push_str(content);
301						buf.push_str("*/");
302					} else {
303						buf.push_str("//");
304						buf.push_str(content);
305						buf.push_new_line();
306					}
307				}
308			}
309		};
310	}
311
312	fn get_position(&self) -> Span {
313		*get_field_by_type::GetFieldByType::get(self)
314	}
315}