Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/pest-2.7.15/src/error.rs
Line
Count
Source (jump to first uncovered line)
1
// pest. The Elegant Parser
2
// Copyright (c) 2018 Dragoș Tiselice
3
//
4
// Licensed under the Apache License, Version 2.0
5
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7
// option. All files in the project carrying such notice may not be copied,
8
// modified, or distributed except according to those terms.
9
10
//! Types for different kinds of parsing failures.
11
12
use crate::parser_state::{ParseAttempts, ParsingToken, RulesCallStack};
13
use alloc::borrow::Cow;
14
use alloc::borrow::ToOwned;
15
use alloc::boxed::Box;
16
use alloc::collections::{BTreeMap, BTreeSet};
17
use alloc::format;
18
use alloc::string::String;
19
use alloc::string::ToString;
20
use alloc::vec;
21
use alloc::vec::Vec;
22
use core::cmp;
23
use core::fmt;
24
use core::mem;
25
26
use crate::position::Position;
27
use crate::span::Span;
28
use crate::RuleType;
29
30
/// Parse-related error type.
31
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
32
#[cfg_attr(feature = "std", derive(thiserror::Error))]
33
pub struct Error<R> {
34
    /// Variant of the error
35
    pub variant: ErrorVariant<R>,
36
    /// Location within the input string
37
    pub location: InputLocation,
38
    /// Line/column within the input string
39
    pub line_col: LineColLocation,
40
    path: Option<String>,
41
    line: String,
42
    continued_line: Option<String>,
43
    parse_attempts: Option<ParseAttempts<R>>,
44
}
45
46
/// Different kinds of parsing errors.
47
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
48
#[cfg_attr(feature = "std", derive(thiserror::Error))]
49
pub enum ErrorVariant<R> {
50
    /// Generated parsing error with expected and unexpected `Rule`s
51
    ParsingError {
52
        /// Positive attempts
53
        positives: Vec<R>,
54
        /// Negative attempts
55
        negatives: Vec<R>,
56
    },
57
    /// Custom error with a message
58
    CustomError {
59
        /// Short explanation
60
        message: String,
61
    },
62
}
63
64
/// Where an `Error` has occurred.
65
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
66
pub enum InputLocation {
67
    /// `Error` was created by `Error::new_from_pos`
68
    Pos(usize),
69
    /// `Error` was created by `Error::new_from_span`
70
    Span((usize, usize)),
71
}
72
73
/// Line/column where an `Error` has occurred.
74
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
75
pub enum LineColLocation {
76
    /// Line/column pair if `Error` was created by `Error::new_from_pos`
77
    Pos((usize, usize)),
78
    /// Line/column pairs if `Error` was created by `Error::new_from_span`
79
    Span((usize, usize), (usize, usize)),
80
}
81
82
impl From<Position<'_>> for LineColLocation {
83
0
    fn from(value: Position<'_>) -> Self {
84
0
        Self::Pos(value.line_col())
85
0
    }
86
}
87
88
impl From<Span<'_>> for LineColLocation {
89
0
    fn from(value: Span<'_>) -> Self {
90
0
        let (start, end) = value.split();
91
0
        Self::Span(start.line_col(), end.line_col())
92
0
    }
93
}
94
95
/// Function mapping rule to its helper message defined by user.
96
pub type RuleToMessageFn<R> = Box<dyn Fn(&R) -> Option<String>>;
97
/// Function mapping string element to bool denoting whether it's a whitespace defined by user.
98
pub type IsWhitespaceFn = Box<dyn Fn(String) -> bool>;
99
100
impl ParsingToken {
101
0
    pub fn is_whitespace(&self, is_whitespace: &IsWhitespaceFn) -> bool {
102
0
        match self {
103
0
            ParsingToken::Sensitive { token } => is_whitespace(token.clone()),
104
0
            ParsingToken::Insensitive { token } => is_whitespace(token.clone()),
105
0
            ParsingToken::Range { .. } => false,
106
0
            ParsingToken::BuiltInRule => false,
107
        }
108
0
    }
109
}
110
111
impl<R: RuleType> ParseAttempts<R> {
112
    /// Helper formatting function to get message informing about tokens we've
113
    /// (un)expected to see.
114
    /// Used as a part of `parse_attempts_error`.
115
0
    fn tokens_helper_messages(
116
0
        &self,
117
0
        is_whitespace_fn: &IsWhitespaceFn,
118
0
        spacing: &str,
119
0
    ) -> Vec<String> {
120
0
        let mut helper_messages = Vec::new();
121
0
        let tokens_header_pairs = vec![
122
0
            (self.expected_tokens(), "expected"),
123
0
            (self.unexpected_tokens(), "unexpected"),
124
0
        ];
125
126
0
        for (tokens, header) in &tokens_header_pairs {
127
0
            if tokens.is_empty() {
128
0
                continue;
129
0
            }
130
0
131
0
            let mut helper_tokens_message = format!("{spacing}note: {header} ");
132
0
            helper_tokens_message.push_str(if tokens.len() == 1 {
133
0
                "token: "
134
            } else {
135
0
                "one of tokens: "
136
            });
137
138
0
            let expected_tokens_set: BTreeSet<String> = tokens
139
0
                .iter()
140
0
                .map(|token| {
141
0
                    if token.is_whitespace(is_whitespace_fn) {
142
0
                        String::from("WHITESPACE")
143
                    } else {
144
0
                        format!("`{}`", token)
145
                    }
146
0
                })
147
0
                .collect();
148
0
149
0
            helper_tokens_message.push_str(
150
0
                &expected_tokens_set
151
0
                    .iter()
152
0
                    .cloned()
153
0
                    .collect::<Vec<String>>()
154
0
                    .join(", "),
155
0
            );
156
0
            helper_messages.push(helper_tokens_message);
157
0
        }
158
159
0
        helper_messages
160
0
    }
161
}
162
163
impl<R: RuleType> Error<R> {
164
    /// Creates `Error` from `ErrorVariant` and `Position`.
165
    ///
166
    /// # Examples
167
    ///
168
    /// ```
169
    /// # use pest::error::{Error, ErrorVariant};
170
    /// # use pest::Position;
171
    /// # #[allow(non_camel_case_types)]
172
    /// # #[allow(dead_code)]
173
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
174
    /// # enum Rule {
175
    /// #     open_paren,
176
    /// #     closed_paren
177
    /// # }
178
    /// # let input = "";
179
    /// # let pos = Position::from_start(input);
180
    /// let error = Error::new_from_pos(
181
    ///     ErrorVariant::ParsingError {
182
    ///         positives: vec![Rule::open_paren],
183
    ///         negatives: vec![Rule::closed_paren],
184
    ///     },
185
    ///     pos
186
    /// );
187
    ///
188
    /// println!("{}", error);
189
    /// ```
190
0
    pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
191
0
        let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
192
0
        let line_of = pos.line_of();
193
0
        let line = if visualize_ws {
194
0
            visualize_whitespace(line_of)
195
        } else {
196
0
            line_of.replace(&['\r', '\n'][..], "")
197
        };
198
0
        Error {
199
0
            variant,
200
0
            location: InputLocation::Pos(pos.pos()),
201
0
            path: None,
202
0
            line,
203
0
            continued_line: None,
204
0
            line_col: LineColLocation::Pos(pos.line_col()),
205
0
            parse_attempts: None,
206
0
        }
207
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::new_from_pos
Unexecuted instantiation: <pest::error::Error<_>>::new_from_pos
208
209
    /// Wrapper function to track `parse_attempts` as a result
210
    /// of `state` function call in `parser_state.rs`.
211
0
    pub(crate) fn new_from_pos_with_parsing_attempts(
212
0
        variant: ErrorVariant<R>,
213
0
        pos: Position<'_>,
214
0
        parse_attempts: ParseAttempts<R>,
215
0
    ) -> Error<R> {
216
0
        let mut error = Self::new_from_pos(variant, pos);
217
0
        error.parse_attempts = Some(parse_attempts);
218
0
        error
219
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::new_from_pos_with_parsing_attempts
Unexecuted instantiation: <pest::error::Error<_>>::new_from_pos_with_parsing_attempts
220
221
    /// Creates `Error` from `ErrorVariant` and `Span`.
222
    ///
223
    /// # Examples
224
    ///
225
    /// ```
226
    /// # use pest::error::{Error, ErrorVariant};
227
    /// # use pest::{Position, Span};
228
    /// # #[allow(non_camel_case_types)]
229
    /// # #[allow(dead_code)]
230
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
231
    /// # enum Rule {
232
    /// #     open_paren,
233
    /// #     closed_paren
234
    /// # }
235
    /// # let input = "";
236
    /// # let start = Position::from_start(input);
237
    /// # let end = start.clone();
238
    /// # let span = start.span(&end);
239
    /// let error = Error::new_from_span(
240
    ///     ErrorVariant::ParsingError {
241
    ///         positives: vec![Rule::open_paren],
242
    ///         negatives: vec![Rule::closed_paren],
243
    ///     },
244
    ///     span
245
    /// );
246
    ///
247
    /// println!("{}", error);
248
    /// ```
249
0
    pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
250
0
        let end = span.end_pos();
251
0
        let mut end_line_col = end.line_col();
252
0
        // end position is after a \n, so we want to point to the visual lf symbol
253
0
        if end_line_col.1 == 1 {
254
0
            let mut visual_end = end;
255
0
            visual_end.skip_back(1);
256
0
            let lc = visual_end.line_col();
257
0
            end_line_col = (lc.0, lc.1 + 1);
258
0
        };
259
260
0
        let mut line_iter = span.lines();
261
0
        let sl = line_iter.next().unwrap_or("");
262
0
        let mut chars = span.as_str().chars();
263
0
        let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
264
0
            || matches!(chars.last(), Some('\n') | Some('\r'));
265
0
        let start_line = if visualize_ws {
266
0
            visualize_whitespace(sl)
267
        } else {
268
0
            sl.to_owned().replace(&['\r', '\n'][..], "")
269
        };
270
0
        let ll = line_iter.last();
271
0
        let continued_line = if visualize_ws {
272
0
            ll.map(str::to_owned)
273
        } else {
274
0
            ll.map(visualize_whitespace)
275
        };
276
277
0
        Error {
278
0
            variant,
279
0
            location: InputLocation::Span((span.start(), end.pos())),
280
0
            path: None,
281
0
            line: start_line,
282
0
            continued_line,
283
0
            line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
284
0
            parse_attempts: None,
285
0
        }
286
0
    }
287
288
    /// Returns `Error` variant with `path` which is shown when formatted with `Display`.
289
    ///
290
    /// # Examples
291
    ///
292
    /// ```
293
    /// # use pest::error::{Error, ErrorVariant};
294
    /// # use pest::Position;
295
    /// # #[allow(non_camel_case_types)]
296
    /// # #[allow(dead_code)]
297
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
298
    /// # enum Rule {
299
    /// #     open_paren,
300
    /// #     closed_paren
301
    /// # }
302
    /// # let input = "";
303
    /// # let pos = Position::from_start(input);
304
    /// Error::new_from_pos(
305
    ///     ErrorVariant::ParsingError {
306
    ///         positives: vec![Rule::open_paren],
307
    ///         negatives: vec![Rule::closed_paren],
308
    ///     },
309
    ///     pos
310
    /// ).with_path("file.rs");
311
    /// ```
312
0
    pub fn with_path(mut self, path: &str) -> Error<R> {
313
0
        self.path = Some(path.to_owned());
314
0
315
0
        self
316
0
    }
317
318
    /// Returns the path set using [`Error::with_path()`].
319
    ///
320
    /// # Examples
321
    ///
322
    /// ```
323
    /// # use pest::error::{Error, ErrorVariant};
324
    /// # use pest::Position;
325
    /// # #[allow(non_camel_case_types)]
326
    /// # #[allow(dead_code)]
327
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
328
    /// # enum Rule {
329
    /// #     open_paren,
330
    /// #     closed_paren
331
    /// # }
332
    /// # let input = "";
333
    /// # let pos = Position::from_start(input);
334
    /// # let error = Error::new_from_pos(
335
    /// #     ErrorVariant::ParsingError {
336
    /// #         positives: vec![Rule::open_paren],
337
    /// #         negatives: vec![Rule::closed_paren],
338
    /// #     },
339
    /// #     pos);
340
    /// let error = error.with_path("file.rs");
341
    /// assert_eq!(Some("file.rs"), error.path());
342
    /// ```
343
0
    pub fn path(&self) -> Option<&str> {
344
0
        self.path.as_deref()
345
0
    }
346
347
    /// Returns the line that the error is on.
348
0
    pub fn line(&self) -> &str {
349
0
        self.line.as_str()
350
0
    }
351
352
    /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
353
    /// [`CustomError`].
354
    ///
355
    /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
356
    ///
357
    /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
358
    /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
359
    ///
360
    /// # Examples
361
    ///
362
    /// ```
363
    /// # use pest::error::{Error, ErrorVariant};
364
    /// # use pest::Position;
365
    /// # #[allow(non_camel_case_types)]
366
    /// # #[allow(dead_code)]
367
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
368
    /// # enum Rule {
369
    /// #     open_paren,
370
    /// #     closed_paren
371
    /// # }
372
    /// # let input = "";
373
    /// # let pos = Position::from_start(input);
374
    /// Error::new_from_pos(
375
    ///     ErrorVariant::ParsingError {
376
    ///         positives: vec![Rule::open_paren],
377
    ///         negatives: vec![Rule::closed_paren],
378
    ///     },
379
    ///     pos
380
    /// ).renamed_rules(|rule| {
381
    ///     match *rule {
382
    ///         Rule::open_paren => "(".to_owned(),
383
    ///         Rule::closed_paren => "closed paren".to_owned()
384
    ///     }
385
    /// });
386
    /// ```
387
0
    pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
388
0
    where
389
0
        F: FnMut(&R) -> String,
390
0
    {
391
0
        let variant = match self.variant {
392
            ErrorVariant::ParsingError {
393
0
                positives,
394
0
                negatives,
395
0
            } => {
396
0
                let message = Error::parsing_error_message(&positives, &negatives, f);
397
0
                ErrorVariant::CustomError { message }
398
            }
399
0
            variant => variant,
400
        };
401
402
0
        self.variant = variant;
403
0
404
0
        self
405
0
    }
406
407
    /// Get detailed information about errored rules sequence.
408
    /// Returns `Some(results)` only for `ParsingError`.
409
0
    pub fn parse_attempts(&self) -> Option<ParseAttempts<R>> {
410
0
        self.parse_attempts.clone()
411
0
    }
412
413
    /// Get error message based on parsing attempts.
414
    /// Returns `None` in case self `parse_attempts` is `None`.
415
0
    pub fn parse_attempts_error(
416
0
        &self,
417
0
        input: &str,
418
0
        rule_to_message: &RuleToMessageFn<R>,
419
0
        is_whitespace: &IsWhitespaceFn,
420
0
    ) -> Option<Error<R>> {
421
0
        let attempts = if let Some(ref parse_attempts) = self.parse_attempts {
422
0
            parse_attempts.clone()
423
        } else {
424
0
            return None;
425
        };
426
427
0
        let spacing = self.spacing() + "   ";
428
0
        let error_position = attempts.max_position;
429
0
        let message = {
430
0
            let mut help_lines: Vec<String> = Vec::new();
431
0
            help_lines.push(String::from("error: parsing error occurred."));
432
433
            // Note: at least one of `(un)expected_tokens` must not be empty.
434
0
            for tokens_helper_message in attempts.tokens_helper_messages(is_whitespace, &spacing) {
435
0
                help_lines.push(tokens_helper_message);
436
0
            }
437
438
0
            let call_stacks = attempts.call_stacks();
439
0
            // Group call stacks by their parents so that we can print common header and
440
0
            // several sub helper messages.
441
0
            let mut call_stacks_parents_groups: BTreeMap<Option<R>, Vec<RulesCallStack<R>>> =
442
0
                BTreeMap::new();
443
0
            for call_stack in call_stacks {
444
0
                call_stacks_parents_groups
445
0
                    .entry(call_stack.parent)
446
0
                    .or_default()
447
0
                    .push(call_stack);
448
0
            }
449
450
0
            for (group_parent, group) in call_stacks_parents_groups {
451
0
                if let Some(parent_rule) = group_parent {
452
0
                    let mut contains_meaningful_info = false;
453
0
                    help_lines.push(format!(
454
0
                        "{spacing}help: {}",
455
0
                        if let Some(message) = rule_to_message(&parent_rule) {
456
0
                            contains_meaningful_info = true;
457
0
                            message
458
                        } else {
459
0
                            String::from("[Unknown parent rule]")
460
                        }
461
                    ));
462
0
                    for call_stack in group {
463
0
                        if let Some(r) = call_stack.deepest.get_rule() {
464
0
                            if let Some(message) = rule_to_message(r) {
465
0
                                contains_meaningful_info = true;
466
0
                                help_lines.push(format!("{spacing}      - {message}"));
467
0
                            }
468
0
                        }
469
                    }
470
0
                    if !contains_meaningful_info {
471
0
                        // Have to remove useless line for unknown parent rule.
472
0
                        help_lines.pop();
473
0
                    }
474
                } else {
475
0
                    for call_stack in group {
476
                        // Note that `deepest` rule may be `None`. E.g. in case it corresponds
477
                        // to WHITESPACE expected token which has no parent rule (on the top level
478
                        // parsing).
479
0
                        if let Some(r) = call_stack.deepest.get_rule() {
480
0
                            let helper_message = rule_to_message(r);
481
0
                            if let Some(helper_message) = helper_message {
482
0
                                help_lines.push(format!("{spacing}help: {helper_message}"));
483
0
                            }
484
0
                        }
485
                    }
486
                }
487
            }
488
489
0
            help_lines.join("\n")
490
0
        };
491
0
        let error = Error::new_from_pos(
492
0
            ErrorVariant::CustomError { message },
493
0
            Position::new_internal(input, error_position),
494
0
        );
495
0
        Some(error)
496
0
    }
497
498
0
    fn start(&self) -> (usize, usize) {
499
0
        match self.line_col {
500
0
            LineColLocation::Pos(line_col) => line_col,
501
0
            LineColLocation::Span(start_line_col, _) => start_line_col,
502
        }
503
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::start
Unexecuted instantiation: <pest::error::Error<_>>::start
504
505
0
    fn spacing(&self) -> String {
506
0
        let line = match self.line_col {
507
0
            LineColLocation::Pos((line, _)) => line,
508
0
            LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
509
        };
510
511
0
        let line_str_len = format!("{}", line).len();
512
0
513
0
        let mut spacing = String::new();
514
0
        for _ in 0..line_str_len {
515
0
            spacing.push(' ');
516
0
        }
517
518
0
        spacing
519
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::spacing
Unexecuted instantiation: <pest::error::Error<_>>::spacing
520
521
0
    fn underline(&self) -> String {
522
0
        let mut underline = String::new();
523
0
524
0
        let mut start = self.start().1;
525
0
        let end = match self.line_col {
526
0
            LineColLocation::Span(_, (_, mut end)) => {
527
0
                let inverted_cols = start > end;
528
0
                if inverted_cols {
529
0
                    mem::swap(&mut start, &mut end);
530
0
                    start -= 1;
531
0
                    end += 1;
532
0
                }
533
534
0
                Some(end)
535
            }
536
0
            _ => None,
537
        };
538
0
        let offset = start - 1;
539
0
        let line_chars = self.line.chars();
540
541
0
        for c in line_chars.take(offset) {
542
0
            match c {
543
0
                '\t' => underline.push('\t'),
544
0
                _ => underline.push(' '),
545
            }
546
        }
547
548
0
        if let Some(end) = end {
549
0
            underline.push('^');
550
0
            if end - start > 1 {
551
0
                for _ in 2..(end - start) {
552
0
                    underline.push('-');
553
0
                }
554
0
                underline.push('^');
555
0
            }
556
        } else {
557
0
            underline.push_str("^---")
558
        }
559
560
0
        underline
561
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::underline
Unexecuted instantiation: <pest::error::Error<_>>::underline
562
563
0
    fn message(&self) -> String {
564
0
        self.variant.message().to_string()
565
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::message
Unexecuted instantiation: <pest::error::Error<_>>::message
566
567
0
    fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
568
0
    where
569
0
        F: FnMut(&R) -> String,
570
0
    {
571
0
        match (negatives.is_empty(), positives.is_empty()) {
572
0
            (false, false) => format!(
573
0
                "unexpected {}; expected {}",
574
0
                Error::enumerate(negatives, &mut f),
575
0
                Error::enumerate(positives, &mut f)
576
0
            ),
577
0
            (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
578
0
            (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
579
0
            (true, true) => "unknown parsing error".to_owned(),
580
        }
581
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::parsing_error_message::<<pest::error::ErrorVariant<async_graphql_parser::parse::generated::Rule>>::message::{closure#0}>
Unexecuted instantiation: <pest::error::Error<_>>::parsing_error_message::<_>
582
583
0
    fn enumerate<F>(rules: &[R], f: &mut F) -> String
584
0
    where
585
0
        F: FnMut(&R) -> String,
586
0
    {
587
0
        match rules.len() {
588
0
            1 => f(&rules[0]),
589
0
            2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
590
0
            l => {
591
0
                let non_separated = f(&rules[l - 1]);
592
0
                let separated = rules
593
0
                    .iter()
594
0
                    .take(l - 1)
595
0
                    .map(f)
596
0
                    .collect::<Vec<_>>()
597
0
                    .join(", ");
598
0
                format!("{}, or {}", separated, non_separated)
599
            }
600
        }
601
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::enumerate::<<pest::error::ErrorVariant<async_graphql_parser::parse::generated::Rule>>::message::{closure#0}>
Unexecuted instantiation: <pest::error::Error<_>>::enumerate::<_>
602
603
0
    pub(crate) fn format(&self) -> String {
604
0
        let spacing = self.spacing();
605
0
        let path = self
606
0
            .path
607
0
            .as_ref()
608
0
            .map(|path| format!("{}:", path))
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::format::{closure#0}
Unexecuted instantiation: <pest::error::Error<_>>::format::{closure#0}
609
0
            .unwrap_or_default();
610
0
611
0
        let pair = (self.line_col.clone(), &self.continued_line);
612
0
        if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
613
0
            let has_line_gap = end.0 - self.start().0 > 1;
614
0
            if has_line_gap {
615
0
                format!(
616
0
                    "{s    }--> {p}{ls}:{c}\n\
617
0
                     {s    } |\n\
618
0
                     {ls:w$} | {line}\n\
619
0
                     {s    } | ...\n\
620
0
                     {le:w$} | {continued_line}\n\
621
0
                     {s    } | {underline}\n\
622
0
                     {s    } |\n\
623
0
                     {s    } = {message}",
624
0
                    s = spacing,
625
0
                    w = spacing.len(),
626
0
                    p = path,
627
0
                    ls = self.start().0,
628
0
                    le = end.0,
629
0
                    c = self.start().1,
630
0
                    line = self.line,
631
0
                    continued_line = continued_line,
632
0
                    underline = self.underline(),
633
0
                    message = self.message()
634
0
                )
635
            } else {
636
0
                format!(
637
0
                    "{s    }--> {p}{ls}:{c}\n\
638
0
                     {s    } |\n\
639
0
                     {ls:w$} | {line}\n\
640
0
                     {le:w$} | {continued_line}\n\
641
0
                     {s    } | {underline}\n\
642
0
                     {s    } |\n\
643
0
                     {s    } = {message}",
644
0
                    s = spacing,
645
0
                    w = spacing.len(),
646
0
                    p = path,
647
0
                    ls = self.start().0,
648
0
                    le = end.0,
649
0
                    c = self.start().1,
650
0
                    line = self.line,
651
0
                    continued_line = continued_line,
652
0
                    underline = self.underline(),
653
0
                    message = self.message()
654
0
                )
655
            }
656
        } else {
657
0
            format!(
658
0
                "{s}--> {p}{l}:{c}\n\
659
0
                 {s} |\n\
660
0
                 {l} | {line}\n\
661
0
                 {s} | {underline}\n\
662
0
                 {s} |\n\
663
0
                 {s} = {message}",
664
0
                s = spacing,
665
0
                p = path,
666
0
                l = self.start().0,
667
0
                c = self.start().1,
668
0
                line = self.line,
669
0
                underline = self.underline(),
670
0
                message = self.message()
671
0
            )
672
        }
673
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule>>::format
Unexecuted instantiation: <pest::error::Error<_>>::format
674
675
    #[cfg(feature = "miette-error")]
676
    /// Turns an error into a [miette](crates.io/miette) Diagnostic.
677
    pub fn into_miette(self) -> impl ::miette::Diagnostic {
678
        miette_adapter::MietteAdapter(self)
679
    }
680
}
681
682
impl<R: RuleType> ErrorVariant<R> {
683
    ///
684
    /// Returns the error message for [`ErrorVariant`]
685
    ///
686
    /// If [`ErrorVariant`] is [`CustomError`], it returns a
687
    /// [`Cow::Borrowed`] reference to [`message`]. If [`ErrorVariant`] is [`ParsingError`], a
688
    /// [`Cow::Owned`] containing "expected [ErrorVariant::ParsingError::positives] [ErrorVariant::ParsingError::negatives]" is returned.
689
    ///
690
    /// [`ErrorVariant`]: enum.ErrorVariant.html
691
    /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
692
    /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
693
    /// [`Cow::Owned`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Owned
694
    /// [`Cow::Borrowed`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Borrowed
695
    /// [`message`]: enum.ErrorVariant.html#variant.CustomError.field.message
696
    /// # Examples
697
    ///
698
    /// ```
699
    /// # use pest::error::ErrorVariant;
700
    /// let variant = ErrorVariant::<()>::CustomError {
701
    ///     message: String::from("unexpected error")
702
    /// };
703
    ///
704
    /// println!("{}", variant.message());
705
0
    pub fn message(&self) -> Cow<'_, str> {
706
0
        match self {
707
            ErrorVariant::ParsingError {
708
0
                ref positives,
709
0
                ref negatives,
710
0
            } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
711
0
                format!("{:?}", r)
712
0
            })),
Unexecuted instantiation: <pest::error::ErrorVariant<async_graphql_parser::parse::generated::Rule>>::message::{closure#0}
Unexecuted instantiation: <pest::error::ErrorVariant<_>>::message::{closure#0}
713
0
            ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
714
        }
715
0
    }
Unexecuted instantiation: <pest::error::ErrorVariant<async_graphql_parser::parse::generated::Rule>>::message
Unexecuted instantiation: <pest::error::ErrorVariant<_>>::message
716
}
717
718
impl<R: RuleType> fmt::Display for Error<R> {
719
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720
0
        write!(f, "{}", self.format())
721
0
    }
Unexecuted instantiation: <pest::error::Error<async_graphql_parser::parse::generated::Rule> as core::fmt::Display>::fmt
Unexecuted instantiation: <pest::error::Error<_> as core::fmt::Display>::fmt
722
}
723
724
impl<R: RuleType> fmt::Display for ErrorVariant<R> {
725
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726
0
        match self {
727
0
            ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
728
0
            ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
729
        }
730
0
    }
731
}
732
733
0
fn visualize_whitespace(input: &str) -> String {
734
0
    input.to_owned().replace('\r', "␍").replace('\n', "␊")
735
0
}
736
737
#[cfg(feature = "miette-error")]
738
mod miette_adapter {
739
    use alloc::string::ToString;
740
    use std::boxed::Box;
741
742
    use crate::error::LineColLocation;
743
744
    use super::{Error, RuleType};
745
746
    use miette::{Diagnostic, LabeledSpan, SourceCode};
747
748
    #[derive(thiserror::Error, Debug)]
749
    #[error("Failure to parse at {:?}", self.0.line_col)]
750
    pub(crate) struct MietteAdapter<R: RuleType>(pub(crate) Error<R>);
751
752
    impl<R: RuleType> Diagnostic for MietteAdapter<R> {
753
        fn source_code(&self) -> Option<&dyn SourceCode> {
754
            Some(&self.0.line)
755
        }
756
757
        fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan>>> {
758
            let message = self.0.variant.message().to_string();
759
760
            let (offset, length) = match self.0.line_col {
761
                LineColLocation::Pos((_, c)) => (c - 1, 1),
762
                LineColLocation::Span((_, start_c), (_, end_c)) => {
763
                    (start_c - 1, end_c - start_c + 1)
764
                }
765
            };
766
767
            let span = LabeledSpan::new(Some(message), offset, length);
768
769
            Some(Box::new(std::iter::once(span)))
770
        }
771
772
        fn help<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
773
            Some(Box::new(self.0.message()))
774
        }
775
    }
776
}
777
778
#[cfg(test)]
779
mod tests {
780
    use super::*;
781
    use alloc::vec;
782
783
    #[test]
784
    fn display_parsing_error_mixed() {
785
        let input = "ab\ncd\nef";
786
        let pos = Position::new(input, 4).unwrap();
787
        let error: Error<u32> = Error::new_from_pos(
788
            ErrorVariant::ParsingError {
789
                positives: vec![1, 2, 3],
790
                negatives: vec![4, 5, 6],
791
            },
792
            pos,
793
        );
794
795
        assert_eq!(
796
            format!("{}", error),
797
            [
798
                " --> 2:2",
799
                "  |",
800
                "2 | cd",
801
                "  |  ^---",
802
                "  |",
803
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
804
            ]
805
            .join("\n")
806
        );
807
    }
808
809
    #[test]
810
    fn display_parsing_error_positives() {
811
        let input = "ab\ncd\nef";
812
        let pos = Position::new(input, 4).unwrap();
813
        let error: Error<u32> = Error::new_from_pos(
814
            ErrorVariant::ParsingError {
815
                positives: vec![1, 2],
816
                negatives: vec![],
817
            },
818
            pos,
819
        );
820
821
        assert_eq!(
822
            format!("{}", error),
823
            [
824
                " --> 2:2",
825
                "  |",
826
                "2 | cd",
827
                "  |  ^---",
828
                "  |",
829
                "  = expected 1 or 2"
830
            ]
831
            .join("\n")
832
        );
833
    }
834
835
    #[test]
836
    fn display_parsing_error_negatives() {
837
        let input = "ab\ncd\nef";
838
        let pos = Position::new(input, 4).unwrap();
839
        let error: Error<u32> = Error::new_from_pos(
840
            ErrorVariant::ParsingError {
841
                positives: vec![],
842
                negatives: vec![4, 5, 6],
843
            },
844
            pos,
845
        );
846
847
        assert_eq!(
848
            format!("{}", error),
849
            [
850
                " --> 2:2",
851
                "  |",
852
                "2 | cd",
853
                "  |  ^---",
854
                "  |",
855
                "  = unexpected 4, 5, or 6"
856
            ]
857
            .join("\n")
858
        );
859
    }
860
861
    #[test]
862
    fn display_parsing_error_unknown() {
863
        let input = "ab\ncd\nef";
864
        let pos = Position::new(input, 4).unwrap();
865
        let error: Error<u32> = Error::new_from_pos(
866
            ErrorVariant::ParsingError {
867
                positives: vec![],
868
                negatives: vec![],
869
            },
870
            pos,
871
        );
872
873
        assert_eq!(
874
            format!("{}", error),
875
            [
876
                " --> 2:2",
877
                "  |",
878
                "2 | cd",
879
                "  |  ^---",
880
                "  |",
881
                "  = unknown parsing error"
882
            ]
883
            .join("\n")
884
        );
885
    }
886
887
    #[test]
888
    fn display_custom_pos() {
889
        let input = "ab\ncd\nef";
890
        let pos = Position::new(input, 4).unwrap();
891
        let error: Error<u32> = Error::new_from_pos(
892
            ErrorVariant::CustomError {
893
                message: "error: big one".to_owned(),
894
            },
895
            pos,
896
        );
897
898
        assert_eq!(
899
            format!("{}", error),
900
            [
901
                " --> 2:2",
902
                "  |",
903
                "2 | cd",
904
                "  |  ^---",
905
                "  |",
906
                "  = error: big one"
907
            ]
908
            .join("\n")
909
        );
910
    }
911
912
    #[test]
913
    fn display_custom_span_two_lines() {
914
        let input = "ab\ncd\nefgh";
915
        let start = Position::new(input, 4).unwrap();
916
        let end = Position::new(input, 9).unwrap();
917
        let error: Error<u32> = Error::new_from_span(
918
            ErrorVariant::CustomError {
919
                message: "error: big one".to_owned(),
920
            },
921
            start.span(&end),
922
        );
923
924
        assert_eq!(
925
            format!("{}", error),
926
            [
927
                " --> 2:2",
928
                "  |",
929
                "2 | cd",
930
                "3 | efgh",
931
                "  |  ^^",
932
                "  |",
933
                "  = error: big one"
934
            ]
935
            .join("\n")
936
        );
937
    }
938
939
    #[test]
940
    fn display_custom_span_three_lines() {
941
        let input = "ab\ncd\nefgh";
942
        let start = Position::new(input, 1).unwrap();
943
        let end = Position::new(input, 9).unwrap();
944
        let error: Error<u32> = Error::new_from_span(
945
            ErrorVariant::CustomError {
946
                message: "error: big one".to_owned(),
947
            },
948
            start.span(&end),
949
        );
950
951
        assert_eq!(
952
            format!("{}", error),
953
            [
954
                " --> 1:2",
955
                "  |",
956
                "1 | ab",
957
                "  | ...",
958
                "3 | efgh",
959
                "  |  ^^",
960
                "  |",
961
                "  = error: big one"
962
            ]
963
            .join("\n")
964
        );
965
    }
966
967
    #[test]
968
    fn display_custom_span_two_lines_inverted_cols() {
969
        let input = "abcdef\ngh";
970
        let start = Position::new(input, 5).unwrap();
971
        let end = Position::new(input, 8).unwrap();
972
        let error: Error<u32> = Error::new_from_span(
973
            ErrorVariant::CustomError {
974
                message: "error: big one".to_owned(),
975
            },
976
            start.span(&end),
977
        );
978
979
        assert_eq!(
980
            format!("{}", error),
981
            [
982
                " --> 1:6",
983
                "  |",
984
                "1 | abcdef",
985
                "2 | gh",
986
                "  | ^----^",
987
                "  |",
988
                "  = error: big one"
989
            ]
990
            .join("\n")
991
        );
992
    }
993
994
    #[test]
995
    fn display_custom_span_end_after_newline() {
996
        let input = "abcdef\n";
997
        let start = Position::new(input, 0).unwrap();
998
        let end = Position::new(input, 7).unwrap();
999
        assert!(start.at_start());
1000
        assert!(end.at_end());
1001
1002
        let error: Error<u32> = Error::new_from_span(
1003
            ErrorVariant::CustomError {
1004
                message: "error: big one".to_owned(),
1005
            },
1006
            start.span(&end),
1007
        );
1008
1009
        assert_eq!(
1010
            format!("{}", error),
1011
            [
1012
                " --> 1:1",
1013
                "  |",
1014
                "1 | abcdef␊",
1015
                "  | ^-----^",
1016
                "  |",
1017
                "  = error: big one"
1018
            ]
1019
            .join("\n")
1020
        );
1021
    }
1022
1023
    #[test]
1024
    fn display_custom_span_empty() {
1025
        let input = "";
1026
        let start = Position::new(input, 0).unwrap();
1027
        let end = Position::new(input, 0).unwrap();
1028
        assert!(start.at_start());
1029
        assert!(end.at_end());
1030
1031
        let error: Error<u32> = Error::new_from_span(
1032
            ErrorVariant::CustomError {
1033
                message: "error: empty".to_owned(),
1034
            },
1035
            start.span(&end),
1036
        );
1037
1038
        assert_eq!(
1039
            format!("{}", error),
1040
            [
1041
                " --> 1:1",
1042
                "  |",
1043
                "1 | ",
1044
                "  | ^",
1045
                "  |",
1046
                "  = error: empty"
1047
            ]
1048
            .join("\n")
1049
        );
1050
    }
1051
1052
    #[test]
1053
    fn mapped_parsing_error() {
1054
        let input = "ab\ncd\nef";
1055
        let pos = Position::new(input, 4).unwrap();
1056
        let error: Error<u32> = Error::new_from_pos(
1057
            ErrorVariant::ParsingError {
1058
                positives: vec![1, 2, 3],
1059
                negatives: vec![4, 5, 6],
1060
            },
1061
            pos,
1062
        )
1063
        .renamed_rules(|n| format!("{}", n + 1));
1064
1065
        assert_eq!(
1066
            format!("{}", error),
1067
            [
1068
                " --> 2:2",
1069
                "  |",
1070
                "2 | cd",
1071
                "  |  ^---",
1072
                "  |",
1073
                "  = unexpected 5, 6, or 7; expected 2, 3, or 4"
1074
            ]
1075
            .join("\n")
1076
        );
1077
    }
1078
1079
    #[test]
1080
    fn error_with_path() {
1081
        let input = "ab\ncd\nef";
1082
        let pos = Position::new(input, 4).unwrap();
1083
        let error: Error<u32> = Error::new_from_pos(
1084
            ErrorVariant::ParsingError {
1085
                positives: vec![1, 2, 3],
1086
                negatives: vec![4, 5, 6],
1087
            },
1088
            pos,
1089
        )
1090
        .with_path("file.rs");
1091
1092
        assert_eq!(
1093
            format!("{}", error),
1094
            [
1095
                " --> file.rs:2:2",
1096
                "  |",
1097
                "2 | cd",
1098
                "  |  ^---",
1099
                "  |",
1100
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1101
            ]
1102
            .join("\n")
1103
        );
1104
    }
1105
1106
    #[test]
1107
    fn underline_with_tabs() {
1108
        let input = "a\txbc";
1109
        let pos = Position::new(input, 2).unwrap();
1110
        let error: Error<u32> = Error::new_from_pos(
1111
            ErrorVariant::ParsingError {
1112
                positives: vec![1, 2, 3],
1113
                negatives: vec![4, 5, 6],
1114
            },
1115
            pos,
1116
        )
1117
        .with_path("file.rs");
1118
1119
        assert_eq!(
1120
            format!("{}", error),
1121
            [
1122
                " --> file.rs:1:3",
1123
                "  |",
1124
                "1 | a  xbc",
1125
                "  |    ^---",
1126
                "  |",
1127
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1128
            ]
1129
            .join("\n")
1130
        );
1131
    }
1132
1133
    #[test]
1134
    fn pos_to_lcl_conversion() {
1135
        let input = "input";
1136
1137
        let pos = Position::new(input, 2).unwrap();
1138
1139
        assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
1140
    }
1141
1142
    #[test]
1143
    fn span_to_lcl_conversion() {
1144
        let input = "input";
1145
1146
        let span = Span::new(input, 2, 4).unwrap();
1147
        let (start, end) = span.split();
1148
1149
        assert_eq!(
1150
            LineColLocation::Span(start.line_col(), end.line_col()),
1151
            span.into()
1152
        );
1153
    }
1154
1155
    #[cfg(feature = "miette-error")]
1156
    #[test]
1157
    fn miette_error() {
1158
        let input = "abc\ndef";
1159
        let pos = Position::new(input, 4).unwrap();
1160
        let error: Error<u32> = Error::new_from_pos(
1161
            ErrorVariant::ParsingError {
1162
                positives: vec![1, 2, 3],
1163
                negatives: vec![4, 5, 6],
1164
            },
1165
            pos,
1166
        );
1167
1168
        let miette_error = miette::Error::new(error.into_miette());
1169
1170
        assert_eq!(
1171
            format!("{:?}", miette_error),
1172
            [
1173
                "",
1174
                "  \u{1b}[31m×\u{1b}[0m Failure to parse at Pos((2, 1))",
1175
                "   ╭────",
1176
                " \u{1b}[2m1\u{1b}[0m │ def",
1177
                "   · \u{1b}[35;1m┬\u{1b}[0m",
1178
                "   · \u{1b}[35;1m╰── \u{1b}[35;1munexpected 4, 5, or 6; expected 1, 2, or 3\u{1b}[0m\u{1b}[0m",
1179
                "   ╰────",
1180
                "\u{1b}[36m  help: \u{1b}[0munexpected 4, 5, or 6; expected 1, 2, or 3\n"
1181
            ]
1182
            .join("\n")
1183
        );
1184
    }
1185
}