ezno_parser/
comments.rs

1//! Contains wrappers for AST with comments
2
3use super::{ASTNode, Span, TSXToken, TokenReader};
4use crate::{ParseOptions, ParseResult};
5
6use tokenizer_lib::Token;
7use visitable_derive::Visitable;
8
9#[cfg_attr(target_family = "wasm", derive(tsify::Tsify))]
10#[derive(Debug, Clone, Visitable)]
11pub enum WithComment<T> {
12	None(T),
13	PrefixComment(String, T, Span),
14	PostfixComment(T, String, Span),
15}
16
17// Ignore comments for now
18#[cfg(feature = "self-rust-tokenize")]
19impl<T> self_rust_tokenize::SelfRustTokenize for WithComment<T>
20where
21	T: self_rust_tokenize::SelfRustTokenize,
22{
23	fn append_to_token_stream(
24		&self,
25		token_stream: &mut self_rust_tokenize::proc_macro2::TokenStream,
26	) {
27		let inner = self_rust_tokenize::SelfRustTokenize::to_tokens(self.get_ast_ref());
28		token_stream.extend(self_rust_tokenize::quote!(WithComment::None(#inner)));
29	}
30}
31
32// TODO comments
33#[cfg(feature = "serde-serialize")]
34impl<T: serde::Serialize> serde::Serialize for WithComment<T> {
35	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36	where
37		S: serde::Serializer,
38	{
39		self.get_ast_ref().serialize(serializer)
40	}
41}
42
43impl<T: PartialEq> PartialEq for WithComment<T> {
44	fn eq(&self, other: &Self) -> bool {
45		self.get_ast_ref() == other.get_ast_ref()
46	}
47}
48
49impl<T> From<T> for WithComment<T> {
50	fn from(item: T) -> Self {
51		Self::None(item)
52	}
53}
54
55impl<T> WithComment<T> {
56	pub fn get_ast(self) -> T {
57		match self {
58			Self::None(ast) | Self::PrefixComment(_, ast, _) | Self::PostfixComment(ast, _, _) => {
59				ast
60			}
61		}
62	}
63
64	pub fn get_ast_ref(&self) -> &T {
65		match self {
66			Self::None(ast) | Self::PrefixComment(_, ast, _) | Self::PostfixComment(ast, _, _) => {
67				ast
68			}
69		}
70	}
71
72	pub fn get_ast_mut(&mut self) -> &mut T {
73		match self {
74			Self::None(ast) | Self::PrefixComment(_, ast, _) | Self::PostfixComment(ast, _, _) => {
75				ast
76			}
77		}
78	}
79
80	pub fn map<U>(self, cb: impl FnOnce(T) -> U) -> WithComment<U> {
81		match self {
82			Self::None(item) => WithComment::None(cb(item)),
83			Self::PrefixComment(comment, item, position) => {
84				WithComment::PrefixComment(comment, cb(item), position)
85			}
86			Self::PostfixComment(item, comment, position) => {
87				WithComment::PostfixComment(cb(item), comment, position)
88			}
89		}
90	}
91}
92
93impl<T: ASTNode> ASTNode for WithComment<T> {
94	fn from_reader(
95		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
96		state: &mut crate::ParsingState,
97		options: &ParseOptions,
98	) -> ParseResult<Self> {
99		if let Some(token) =
100			reader.conditional_next(|t| matches!(t, TSXToken::MultiLineComment(..)))
101		{
102			let Token(TSXToken::MultiLineComment(comment), position) = token else {
103				unreachable!();
104			};
105			let item = T::from_reader(reader, state, options)?;
106			let position = position.union(item.get_position());
107			Ok(Self::PrefixComment(comment, item, position))
108		} else {
109			let item = T::from_reader(reader, state, options)?;
110			if let Some(token) =
111				reader.conditional_next(|t| matches!(t, TSXToken::MultiLineComment(..)))
112			{
113				let end = token.get_span();
114				let Token(TSXToken::MultiLineComment(comment), _) = token else {
115					unreachable!();
116				};
117				let position = item.get_position().union(end);
118				Ok(Self::PostfixComment(item, comment, position))
119			} else {
120				Ok(Self::None(item))
121			}
122		}
123	}
124
125	fn get_position(&self) -> Span {
126		match self {
127			Self::None(ast) => ast.get_position(),
128			Self::PostfixComment(_, _, position) | Self::PrefixComment(_, _, position) => *position,
129		}
130	}
131
132	fn to_string_from_buffer<U: source_map::ToString>(
133		&self,
134		buf: &mut U,
135		options: &crate::ToStringOptions,
136		local: crate::LocalToStringInformation,
137	) {
138		match self {
139			Self::None(ast) => ast.to_string_from_buffer(buf, options, local),
140			Self::PrefixComment(content, ast, _) => {
141				if options.should_add_comment(content) {
142					buf.push_str("/*");
143					if options.pretty {
144						// Perform indent correction
145						// Have to use '\n' as `.lines` with it's handling of '\r'
146						for (idx, line) in content.split('\n').enumerate() {
147							if idx > 0 {
148								buf.push_new_line();
149							}
150							options.add_indent(local.depth, buf);
151							buf.push_str(line.trim());
152						}
153					// buf.push_new_line();
154					} else {
155						buf.push_str_contains_new_line(content.as_str());
156					}
157					buf.push_str("*/");
158				}
159				ast.to_string_from_buffer(buf, options, local);
160			}
161			Self::PostfixComment(ast, comment, _) => {
162				ast.to_string_from_buffer(buf, options, local);
163				if options.should_add_comment(comment) {
164					buf.push_str("/*");
165					if options.pretty {
166						// Perform indent correction
167						for (idx, line) in comment.split('\n').enumerate() {
168							if idx > 0 {
169								buf.push_new_line();
170							}
171							options.add_indent(local.depth, buf);
172							buf.push_str(line.trim());
173						}
174					} else {
175						buf.push_str_contains_new_line(comment.as_str());
176					}
177					buf.push_str("*/");
178				}
179			}
180		}
181	}
182}