lib_ruby_parser/error/
diagnostic.rs

1use crate::loc_ext::LocExt;
2use crate::source::DecodedInput;
3use crate::Loc;
4use crate::{DiagnosticMessage, ErrorLevel};
5use std::cell::RefCell;
6use std::rc::Rc;
7
8/// Diagnostic message that comes from the parser when there's an error or warning
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[repr(C)]
11pub struct Diagnostic {
12    /// Level of the diagnostic (error or warnings)
13    pub level: ErrorLevel,
14
15    /// Message of the diagnostic
16    pub message: DiagnosticMessage,
17
18    /// Location of the diagnostic
19    pub loc: Loc,
20}
21
22impl Diagnostic {
23    /// Returns rendered message
24    pub fn render_message(&self) -> String {
25        self.message.render()
26    }
27
28    /// Renders all data into a single String, produces an output like:
29    ///
30    /// ```text
31    /// (test.rb):1:5: error: unexpected END_OF_INPUT
32    /// (test.rb):1: foo++
33    /// (test.rb):1:      ^
34    /// ```
35    pub fn render(&self, input: &DecodedInput) -> Option<String> {
36        let (line_no, line_loc) = self.loc.expand_to_line(input)?;
37        let line = line_loc.source(input)?;
38
39        let filename = &input.name;
40        let (_, start_col) = self.loc.begin_line_col(input)?;
41
42        let prefix = format!("{}:{}", filename, line_no + 1);
43        let highlight = format!(
44            "{indent}^{tildes}",
45            indent = " ".repeat(start_col),
46            tildes = if self.loc.size() > 0 {
47                "~".repeat(self.loc.size() - 1)
48            } else {
49                "".to_string()
50            }
51        );
52
53        Some(
54            format!(
55                "{prefix}:{start_col}: {level}: {message}\n{prefix}: {line}\n{prefix}: {highlight}",
56                prefix = prefix,
57                start_col = start_col,
58                level = self.level.to_string(),
59                message = self.message.render(),
60                line = line,
61                highlight = highlight
62            )
63            .trim()
64            .to_string(),
65        )
66    }
67
68    /// Returns `true` if level of the diagnostic is `Warning`
69    pub fn is_warning(&self) -> bool {
70        matches!(self.level, ErrorLevel::Warning)
71    }
72
73    /// Returns `true` if level of the diagnostic is `Error`
74    pub fn is_error(&self) -> bool {
75        matches!(self.level, ErrorLevel::Error)
76    }
77}
78
79#[derive(Debug, Default, Clone)]
80pub(crate) struct Diagnostics {
81    list: Rc<RefCell<Vec<Diagnostic>>>,
82}
83
84impl Diagnostics {
85    pub(crate) fn new() -> Self {
86        Self {
87            list: Rc::new(RefCell::new(vec![])),
88        }
89    }
90
91    pub(crate) fn emit(&self, diagnostic: Diagnostic) {
92        self.list.borrow_mut().push(diagnostic)
93    }
94
95    pub(crate) fn take_inner(self) -> Vec<Diagnostic> {
96        self.list.replace(vec![])
97    }
98}
99
100#[test]
101fn test_renders() {
102    let source = "line 1\nvery long line 2\n";
103    let mut input = crate::source::DecodedInput::named("(test_render)");
104    input.update_bytes(Vec::from(source));
105
106    let error = Diagnostic {
107        level: ErrorLevel::Warning,
108        message: DiagnosticMessage::FractionAfterNumeric {},
109        loc: Loc { begin: 8, end: 12 },
110    };
111
112    assert_eq!(
113        error.render(&input).expect("failed to render diagnostic"),
114        vec![
115            "(test_render):2:1: warning: unexpected fraction part after numeric literal",
116            "(test_render):2: very long line 2",
117            "(test_render):2:  ^~~~"
118        ]
119        .join("\n")
120    );
121}
122
123#[test]
124fn test_predicates() {
125    let error = Diagnostic {
126        level: ErrorLevel::Error,
127        message: DiagnosticMessage::AliasNthRef {},
128        loc: Loc { begin: 1, end: 2 },
129    };
130
131    let warning = Diagnostic {
132        level: ErrorLevel::Warning,
133        message: DiagnosticMessage::AliasNthRef {},
134        loc: Loc { begin: 1, end: 2 },
135    };
136
137    assert!(error.is_error());
138    assert!(!error.is_warning());
139
140    assert!(!warning.is_error());
141    assert!(warning.is_warning());
142}