1use std::path::PathBuf;
2
3use crate::model::SourceRange;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6#[non_exhaustive]
7pub enum Severity {
8 Warning,
9 Error,
10}
11
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct Diagnostic {
14 pub path: PathBuf,
15 pub severity: Severity,
16 pub message: String,
17 pub source_range: Option<SourceRange>,
18}
19
20impl Diagnostic {
21 pub fn sort_key(&self) -> (&std::path::Path, &str, usize) {
26 (
27 self.path.as_path(),
28 self.message.as_str(),
29 self.source_range
30 .as_ref()
31 .map_or(usize::MAX, |range| range.byte_start),
32 )
33 }
34}
35
36#[derive(Debug, thiserror::Error)]
37pub enum Error {
38 #[error("IO: {0}")]
39 Io(#[from] std::io::Error),
40
41 #[error("parse error in {path}: {message}")]
42 Parse { path: PathBuf, message: String },
43
44 #[error("query error ({language}): {message}")]
45 Query {
46 language: crate::language::LangId,
47 message: String,
48 },
49
50 #[error("config: {0}")]
51 Config(String),
52
53 #[error("identifier space exhausted")]
54 IdExhausted,
55
56 #[error("analysis reported {errors} error(s) and {warnings} warning(s)")]
57 Diagnostics { errors: usize, warnings: usize },
58
59 #[error("invalid source URI '{uri}': {message}")]
60 InvalidSourceUri { uri: String, message: String },
61
62 #[error("graph error: {0}")]
63 Graph(String),
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::language::LangId;
70 use std::path::PathBuf;
71
72 #[test]
73 fn io_error_from_std_io() {
74 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
75 let err: Error = io_err.into();
76 assert!(matches!(err, Error::Io(_)));
77 assert!(err.to_string().contains("file not found"));
78 }
79
80 #[test]
81 fn parse_error_display_format() {
82 let err = Error::Parse {
83 path: PathBuf::from("foo.bar"),
84 message: "bad syntax".into(),
85 };
86 let displayed = err.to_string();
87 assert!(displayed.contains("foo.bar"), "{displayed}");
88 assert!(displayed.contains("bad syntax"), "{displayed}");
89 assert!(displayed.starts_with("parse error in"), "{displayed}");
90 }
91
92 #[test]
93 fn query_error_display_format() {
94 let err = Error::Query {
95 language: LangId::Rust,
96 message: "no match".into(),
97 };
98 let displayed = err.to_string();
99 assert!(displayed.contains("rust"), "{displayed}");
100 assert!(displayed.contains("no match"), "{displayed}");
101 assert!(displayed.starts_with("query error"), "{displayed}");
102 }
103
104 #[test]
105 fn config_error_display() {
106 let err = Error::Config("bad setting".into());
107 let displayed = err.to_string();
108 assert!(displayed.starts_with("config:"), "{displayed}");
109 assert!(displayed.contains("bad setting"), "{displayed}");
110 }
111
112 #[test]
113 fn diagnostic_construction() {
114 let sr = SourceRange {
115 byte_start: 0,
116 byte_end: 10,
117 start: crate::model::LineColumn { line: 1, column: 0 },
118 end: crate::model::LineColumn {
119 line: 1,
120 column: 10,
121 },
122 };
123 let d = Diagnostic {
124 path: PathBuf::from("test.rs"),
125 severity: Severity::Error,
126 message: "undefined variable".into(),
127 source_range: Some(sr.clone()),
128 };
129 assert_eq!(d.path, PathBuf::from("test.rs"));
130 assert_eq!(d.severity, Severity::Error);
131 assert_eq!(d.message, "undefined variable");
132 assert!(d.source_range.is_some());
133 let range = d.source_range.unwrap();
134 assert_eq!(range.byte_start, 0);
135 assert_eq!(range.byte_end, 10);
136 }
137
138 #[test]
139 fn severity_variants() {
140 assert_ne!(Severity::Warning, Severity::Error);
141 let v = [Severity::Warning, Severity::Error];
142 assert_eq!(v.len(), 2);
143 }
144
145 #[test]
146 fn graph_error_display_format() {
147 let err = Error::Graph("cycle detected".into());
148 let displayed = err.to_string();
149 assert!(displayed.starts_with("graph error:"), "{displayed}");
150 assert!(displayed.contains("cycle detected"), "{displayed}");
151 }
152
153 #[test]
154 fn graph_error_matches() {
155 let err = Error::Graph("test".into());
156 assert!(matches!(err, Error::Graph(_)));
157 }
158}