lib_ruby_parser/error/
diagnostic.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
10#[repr(C)]
11pub struct Diagnostic {
12 pub level: ErrorLevel,
14
15 pub message: DiagnosticMessage,
17
18 pub loc: Loc,
20}
21
22impl Diagnostic {
23 pub fn render_message(&self) -> String {
25 self.message.render()
26 }
27
28 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 pub fn is_warning(&self) -> bool {
70 matches!(self.level, ErrorLevel::Warning)
71 }
72
73 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}