rstml/parser/
recoverable.rs

1//! Recoverable parser helper module. Contains trait and types that are using
2//! during implementation of parsing with recovery after semantic errors.
3//!
4//! Recoverable parser is a type of parsing technique when parser don't give up
5//! after getting invalid token, and instead continue to parse code to provide
6//! more info about [`TokenStream`] to IDE or user.
7//!
8//! Instead of failing after first unclosed tag, or invalid block, recoverable
9//! parser will try to check if there any other syntax or semantic errors.
10//!
11//! Example:
12//! ```rust
13//!   # use quote::quote;
14//!   # use rstml::{Parser, ParserConfig};
15//!   # Parser::new(ParserConfig::default()).parse_recoverable(quote! {
16//!   <div hello={world.} /> // dot after world is invalid syn::Expr
17//!   <>
18//!       <div>"1"</x> // incorrect closed tag
19//!       <div>"2"</div>
20//!       <div>"3"</div>
21//!       <div {"some-attribute-from-rust-block"}/>
22//!   </>
23//!   <bar> // unclosed tag
24//!   # });
25//! ```
26//! If this example was parsed by regular parser, it will fail with "invalid
27//! expression error" and no output. User will see only one error, and IDE
28//! cannot produce any completion in case of invalid expression.
29//!
30//! But recoverable parser differ (see [`Parser::parse_recoverable`]), it will
31//! return array of errors and array of [`Node`]. Errors should be emitted, and
32//! value should be handled as no errors was found. In result, user will see all
33//! errors, and IDE can provide completion even if some part of token stream was
34//! unexpected.
35//!
36//!
37//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
38//! [`Parser::parse_recoverable`]: struct.Parser.html#method.parse_recoverable
39//! [`Node`]: struct.Node.html
40
41use std::{collections::HashSet, fmt::Debug, rc::Rc};
42
43use proc_macro2_diagnostics::{Diagnostic, Level};
44use syn::parse::{Parse, ParseStream};
45
46use crate::{
47    config::{ElementWildcardFn, TransformBlockFn},
48    node::CustomNode,
49    ParserConfig,
50};
51
52/// Config of parser.
53/// Used to extend parsing functionality by user needs.
54///
55/// Can't be created directly, instead use [`From<ParserConfig>::from`].
56#[derive(Default, Clone)]
57pub struct RecoveryConfig {
58    ///
59    /// Try to parse invalid syn::Block as something.
60    /// Usefull to make expressions more IDE-friendly.
61    pub(crate) recover_block: bool,
62    /// elements that has no child and is always self closed like <img> and <br>
63    pub(crate) always_self_closed_elements: HashSet<&'static str>,
64    /// Elements like `<script>` `<style>`, context of which is not a valid
65    /// html, and should be provided as is.
66    pub(crate) raw_text_elements: HashSet<&'static str>,
67    pub(crate) transform_block: Option<Rc<TransformBlockFn>>,
68    /// Allows wildcard closing tag matching for blocks
69    pub(crate) element_close_wildcard: Option<Rc<ElementWildcardFn>>,
70}
71impl PartialEq for RecoveryConfig {
72    fn eq(&self, other: &Self) -> bool {
73        self.recover_block == other.recover_block
74            && self.always_self_closed_elements == other.always_self_closed_elements
75            && self.raw_text_elements == other.raw_text_elements
76            && self.transform_block.is_some() == other.transform_block.is_some()
77            && self.element_close_wildcard.is_some() == other.element_close_wildcard.is_some()
78    }
79}
80impl Eq for RecoveryConfig {}
81
82impl Debug for RecoveryConfig {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("RecoveryConfig")
85            .field("recover_block", &self.recover_block)
86            .field(
87                "always_self_closed_elements",
88                &self.always_self_closed_elements,
89            )
90            .field("raw_text_elements", &self.raw_text_elements)
91            .field(
92                "element_close_wildcard",
93                &self.element_close_wildcard.is_some(),
94            )
95            .finish()
96    }
97}
98
99/// Context that is provided in [`ParseRecoverable`] interface.
100/// Used to save [`Diagnostic`] messages or [`syn::Result`].
101///
102/// Also can be extended with user needs through [`RecoveryConfig`].
103#[derive(Debug, Default, Clone)]
104pub struct RecoverableContext {
105    pub(super) diagnostics: Vec<Diagnostic>,
106    config: RecoveryConfig,
107}
108
109impl PartialEq for RecoverableContext {
110    fn eq(&self, other: &Self) -> bool {
111        if self.diagnostics.len() != other.diagnostics.len() || self.config != other.config {
112            return false;
113        }
114
115        self.diagnostics
116            .iter()
117            .zip(other.diagnostics.iter())
118            .all(|(a, b)| format!("{:?}", a) == format!("{:?}", b))
119    }
120}
121impl Eq for RecoverableContext {}
122
123impl RecoverableContext {
124    pub fn new(config: RecoveryConfig) -> Self {
125        Self {
126            diagnostics: vec![],
127            config,
128        }
129    }
130    pub fn config(&self) -> &RecoveryConfig {
131        &self.config
132    }
133    pub fn parse_result<T>(self, val: Option<T>) -> ParsingResult<T> {
134        ParsingResult::from_parts(val, self.diagnostics)
135    }
136
137    /// Parse token using [`syn::parse::Parse`]
138    pub fn parse_simple<T: Parse>(&mut self, input: ParseStream) -> Option<T> {
139        match input.parse() {
140            Ok(v) => Some(v),
141            Err(e) => {
142                self.push_diagnostic(e);
143                None
144            }
145        }
146    }
147    /// Parse token using closure
148    pub fn parse_mixed_fn<F, T>(&mut self, input: ParseStream, mut parser: F) -> Option<T>
149    where
150        F: FnMut(&mut Self, ParseStream) -> Result<T, syn::Error>,
151    {
152        match parser(self, input) {
153            Ok(v) => Some(v),
154            Err(e) => {
155                self.push_diagnostic(e);
156                None
157            }
158        }
159    }
160
161    /// Parse token using [`ParseRecoverable`]
162    pub fn parse_recoverable<T: ParseRecoverable>(&mut self, input: ParseStream) -> Option<T> {
163        T::parse_recoverable(self, input)
164    }
165
166    /// Save diagnostic message of [`syn::Result`]
167    /// and convert result to `Option`, that can be used directly
168    /// as output in [`ParseRecoverable::parse_recoverable`]
169    pub fn save_diagnostics<T>(&mut self, val: syn::Result<T>) -> Option<T> {
170        match val {
171            Ok(v) => Some(v),
172            Err(e) => {
173                self.push_diagnostic(e);
174                None
175            }
176        }
177    }
178
179    /// Push custom message of [`syn::Error`] or
180    /// [`proc_macro2_diagnostics::Diagnostic`]
181    pub fn push_diagnostic(&mut self, diagnostic: impl Into<Diagnostic>) {
182        let diag = diagnostic.into();
183        // println!(
184        //     "Push diagnostic: {:?}, backtrace={}",
185        //     diag,
186        //     std::backtrace::Backtrace::capture()
187        // );
188        self.diagnostics.push(diag);
189    }
190}
191
192///
193/// Result of parsing.
194#[derive(Debug)]
195pub enum ParsingResult<T> {
196    /// Fully valid ast that was parsed without errors.
197    Ok(T),
198    /// The ast contain invalid starting tokens, and cannot be parsed.
199    Failed(Vec<Diagnostic>),
200    /// The ast can be partially parsed,
201    /// but some tokens was skipped during parsing, or their meaning was
202    /// changed.
203    Partial(T, Vec<Diagnostic>),
204}
205
206impl<T> ParsingResult<T> {
207    /// Create new ParsingResult from optional value and accumulated errors.
208    pub fn from_parts(value: Option<T>, errors: Vec<Diagnostic>) -> Self {
209        match (value, errors) {
210            (Some(v), err) if err.is_empty() => Self::Ok(v),
211            (Some(v), err) => Self::Partial(v, err),
212            (None, err) => Self::Failed(err),
213        }
214    }
215
216    ///
217    /// Convert into [`syn::Result`], with fail on first diagnostic message,
218    /// Ignores any diagnostic non error message when result is available.
219    /// Returns Error on [`ParsingResult::Failed`], and
220    /// [`ParsingResult::Partial`].
221    pub fn into_result(self) -> syn::Result<T> {
222        match self {
223            ParsingResult::Ok(r) => Ok(r),
224            ParsingResult::Failed(errors) => Err(errors
225                .into_iter()
226                .next()
227                .unwrap_or_else(|| {
228                    Diagnostic::new(
229                        Level::Error,
230                        "Object parsing failed, but no additional info was provided",
231                    )
232                })
233                .into()),
234            ParsingResult::Partial(ok, errors) => {
235                if let Some(err) = errors
236                    .into_iter()
237                    .filter(|p| p.level() == Level::Error)
238                    .next()
239                {
240                    Err(err.into())
241                } else {
242                    Ok(ok)
243                }
244            }
245        }
246    }
247
248    pub fn split(self) -> (Option<T>, Vec<Diagnostic>) {
249        match self {
250            Self::Ok(r) => (Some(r), vec![]),
251            Self::Failed(errors) => (None, errors),
252            Self::Partial(r, errors) => (Some(r), errors),
253        }
254    }
255
256    pub fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
257        *self = match std::mem::replace(self, ParsingResult::Failed(vec![])) {
258            Self::Ok(r) => Self::Partial(r, vec![diagnostic]),
259            Self::Failed(errors) => {
260                Self::Failed(errors.into_iter().chain(Some(diagnostic)).collect())
261            }
262            Self::Partial(r, errors) => {
263                Self::Partial(r, errors.into_iter().chain(Some(diagnostic)).collect())
264            }
265        };
266    }
267
268    pub fn is_ok(&self) -> bool {
269        matches!(self, Self::Ok(_))
270    }
271}
272
273impl<T> ParsingResult<Vec<T>> {
274    pub fn split_vec(self) -> (Vec<T>, Vec<Diagnostic>) {
275        let (r, e) = self.split();
276        (r.unwrap_or_default(), e)
277    }
278    pub fn from_parts_vec(value: Vec<T>, errors: Vec<Diagnostic>) -> Self {
279        match (value, errors) {
280            (v, err) if err.is_empty() => Self::Ok(v),
281            (v, err) if !v.is_empty() => Self::Partial(v, err),
282            (_, err) => Self::Failed(err),
283        }
284    }
285}
286
287impl<T> From<syn::Result<T>> for ParsingResult<T> {
288    ///
289    /// Convert into syn::Result,
290    /// Returns Error on ParsingResult::Failed, and ParsingResult::Partial.
291    fn from(result: syn::Result<T>) -> ParsingResult<T> {
292        match result {
293            Result::Ok(r) => ParsingResult::Ok(r),
294            Result::Err(e) => ParsingResult::Failed(vec![e.into()]),
295        }
296    }
297}
298
299impl<C: CustomNode> From<ParserConfig<C>> for RecoveryConfig {
300    fn from(config: ParserConfig<C>) -> Self {
301        RecoveryConfig {
302            recover_block: config.recover_block,
303            raw_text_elements: config.raw_text_elements.clone(),
304            always_self_closed_elements: config.always_self_closed_elements.clone(),
305            transform_block: config.transform_block.clone(),
306            element_close_wildcard: config.element_close_wildcard.clone(),
307        }
308    }
309}
310
311///
312/// Adaptor to provide a [`syn::parse::Parse`] interface to [`ParseRecoverable`]
313/// types. Returns error if any error was set in [`RecoverableContext`] during
314/// parsing. Use Default implementation of [`RecoveryConfig`].
315///
316/// Panics:
317/// If [`ParseRecoverable`] implementation doesn't save any diagnostic message,
318/// and return [`None`].
319pub struct Recoverable<T>(pub T);
320impl<T> Recoverable<T> {
321    pub fn inner(self) -> T {
322        self.0
323    }
324}
325
326impl<T: ParseRecoverable> Parse for Recoverable<T> {
327    fn parse(input: ParseStream) -> syn::Result<Self> {
328        let mut empty_context = RecoverableContext::default();
329        let parse = T::parse_recoverable(&mut empty_context, input);
330        empty_context
331            .parse_result(parse)
332            .into_result()
333            .map(Recoverable)
334    }
335}
336
337///
338/// Parsing interface for recoverable [`TokenStream`] parsing,
339///     analog to [`syn::parse::Parse`] but with ability to skip unexpected
340/// tokens, and more diagnostic messages.
341///
342/// - If input stream can be parsed to valid, or partially valid object
343/// [`Option::Some`] should be returned.
344///
345/// - If object is parsed partially one can save
346/// diagnostic message in [`RecoverableContext`].
347///
348/// - If object is failed to parse
349/// [`Option::None`] should be returned, and any message should be left in
350/// [`RecoverableContext`].
351///
352/// Instead of using [`RecoverableContext`] the interface can be changed to the
353/// following:
354/// ```rust
355/// # use syn::parse::ParseStream;
356/// # use rstml::ParsingResult;
357/// pub trait ParseRecoverable: Sized {
358///     fn parse_recoverable(input: ParseStream) -> ParsingResult<Self>;
359/// }
360/// ```
361/// It would more type-safe, but because [`std::ops::Try`] is not stable,
362/// writing implementation for this trait would end with a lot of boilerplate.
363///
364/// [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
365pub trait ParseRecoverable: Sized {
366    fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self>;
367}