1use std::fmt;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[repr(u8)]
36pub enum DiagnosticSeverity {
37 Error = 1,
39 Warning = 2,
41 Information = 3,
43 Hint = 4,
45}
46
47impl DiagnosticSeverity {
48 pub fn to_lsp_value(self) -> u8 {
50 self as u8
51 }
52}
53
54impl fmt::Display for DiagnosticSeverity {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 DiagnosticSeverity::Error => write!(f, "error"),
58 DiagnosticSeverity::Warning => write!(f, "warning"),
59 DiagnosticSeverity::Information => write!(f, "info"),
60 DiagnosticSeverity::Hint => write!(f, "hint"),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub enum DiagnosticTag {
69 Unnecessary,
71 Deprecated,
73}
74
75impl DiagnosticTag {
76 pub fn to_lsp_value(self) -> u8 {
78 match self {
79 DiagnosticTag::Unnecessary => 1,
80 DiagnosticTag::Deprecated => 2,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90pub enum DiagnosticCode {
91 ParseError,
94 SyntaxError,
96 UnexpectedEof,
98
99 MissingStrict,
102 MissingWarnings,
104 UnusedVariable,
106 UndefinedVariable,
108
109 MissingPackageDeclaration,
112 DuplicatePackage,
114
115 DuplicateSubroutine,
118 MissingReturn,
120
121 BarewordFilehandle,
124 TwoArgOpen,
126 ImplicitReturn,
128
129 CriticSeverity1,
132 CriticSeverity2,
134 CriticSeverity3,
136 CriticSeverity4,
138 CriticSeverity5,
140}
141
142impl DiagnosticCode {
143 pub fn as_str(&self) -> &'static str {
145 match self {
146 DiagnosticCode::ParseError => "PL001",
147 DiagnosticCode::SyntaxError => "PL002",
148 DiagnosticCode::UnexpectedEof => "PL003",
149 DiagnosticCode::MissingStrict => "PL100",
150 DiagnosticCode::MissingWarnings => "PL101",
151 DiagnosticCode::UnusedVariable => "PL102",
152 DiagnosticCode::UndefinedVariable => "PL103",
153 DiagnosticCode::MissingPackageDeclaration => "PL200",
154 DiagnosticCode::DuplicatePackage => "PL201",
155 DiagnosticCode::DuplicateSubroutine => "PL300",
156 DiagnosticCode::MissingReturn => "PL301",
157 DiagnosticCode::BarewordFilehandle => "PL400",
158 DiagnosticCode::TwoArgOpen => "PL401",
159 DiagnosticCode::ImplicitReturn => "PL402",
160 DiagnosticCode::CriticSeverity1 => "PC001",
161 DiagnosticCode::CriticSeverity2 => "PC002",
162 DiagnosticCode::CriticSeverity3 => "PC003",
163 DiagnosticCode::CriticSeverity4 => "PC004",
164 DiagnosticCode::CriticSeverity5 => "PC005",
165 }
166 }
167
168 pub fn documentation_url(&self) -> Option<&'static str> {
170 match self {
171 DiagnosticCode::ParseError => Some("https://docs.perl-lsp.org/errors/PL001"),
172 DiagnosticCode::SyntaxError => Some("https://docs.perl-lsp.org/errors/PL002"),
173 DiagnosticCode::UnexpectedEof => Some("https://docs.perl-lsp.org/errors/PL003"),
174 DiagnosticCode::MissingStrict => Some("https://docs.perl-lsp.org/errors/PL100"),
175 DiagnosticCode::MissingWarnings => Some("https://docs.perl-lsp.org/errors/PL101"),
176 DiagnosticCode::UnusedVariable => Some("https://docs.perl-lsp.org/errors/PL102"),
177 DiagnosticCode::UndefinedVariable => Some("https://docs.perl-lsp.org/errors/PL103"),
178 DiagnosticCode::MissingPackageDeclaration => {
179 Some("https://docs.perl-lsp.org/errors/PL200")
180 }
181 DiagnosticCode::DuplicatePackage => Some("https://docs.perl-lsp.org/errors/PL201"),
182 DiagnosticCode::DuplicateSubroutine => Some("https://docs.perl-lsp.org/errors/PL300"),
183 DiagnosticCode::MissingReturn => Some("https://docs.perl-lsp.org/errors/PL301"),
184 DiagnosticCode::BarewordFilehandle => Some("https://docs.perl-lsp.org/errors/PL400"),
185 DiagnosticCode::TwoArgOpen => Some("https://docs.perl-lsp.org/errors/PL401"),
186 DiagnosticCode::ImplicitReturn => Some("https://docs.perl-lsp.org/errors/PL402"),
187 DiagnosticCode::CriticSeverity1
189 | DiagnosticCode::CriticSeverity2
190 | DiagnosticCode::CriticSeverity3
191 | DiagnosticCode::CriticSeverity4
192 | DiagnosticCode::CriticSeverity5 => None,
193 }
194 }
195
196 pub fn severity(&self) -> DiagnosticSeverity {
198 match self {
199 DiagnosticCode::ParseError
201 | DiagnosticCode::SyntaxError
202 | DiagnosticCode::UnexpectedEof
203 | DiagnosticCode::UndefinedVariable => DiagnosticSeverity::Error,
204
205 DiagnosticCode::MissingStrict
207 | DiagnosticCode::MissingWarnings
208 | DiagnosticCode::UnusedVariable
209 | DiagnosticCode::MissingPackageDeclaration
210 | DiagnosticCode::DuplicatePackage
211 | DiagnosticCode::DuplicateSubroutine
212 | DiagnosticCode::MissingReturn
213 | DiagnosticCode::BarewordFilehandle
214 | DiagnosticCode::TwoArgOpen
215 | DiagnosticCode::ImplicitReturn
216 | DiagnosticCode::CriticSeverity1
217 | DiagnosticCode::CriticSeverity2 => DiagnosticSeverity::Warning,
218
219 DiagnosticCode::CriticSeverity3
221 | DiagnosticCode::CriticSeverity4
222 | DiagnosticCode::CriticSeverity5 => DiagnosticSeverity::Hint,
223 }
224 }
225
226 pub fn tags(&self) -> &'static [DiagnosticTag] {
228 match self {
229 DiagnosticCode::UnusedVariable => &[DiagnosticTag::Unnecessary],
230 _ => &[],
231 }
232 }
233
234 pub fn from_message(msg: &str) -> Option<DiagnosticCode> {
236 let msg_lower = msg.to_lowercase();
237 if msg_lower.contains("use strict") {
238 Some(DiagnosticCode::MissingStrict)
239 } else if msg_lower.contains("use warnings") {
240 Some(DiagnosticCode::MissingWarnings)
241 } else if msg_lower.contains("unused variable") || msg_lower.contains("never used") {
242 Some(DiagnosticCode::UnusedVariable)
243 } else if msg_lower.contains("undefined") || msg_lower.contains("not declared") {
244 Some(DiagnosticCode::UndefinedVariable)
245 } else if msg_lower.contains("bareword filehandle") {
246 Some(DiagnosticCode::BarewordFilehandle)
247 } else if msg_lower.contains("two-argument") || msg_lower.contains("2-arg") {
248 Some(DiagnosticCode::TwoArgOpen)
249 } else if msg_lower.contains("parse error") || msg_lower.contains("syntax error") {
250 Some(DiagnosticCode::ParseError)
251 } else {
252 None
253 }
254 }
255
256 pub fn parse_code(code: &str) -> Option<DiagnosticCode> {
258 match code {
259 "PL001" => Some(DiagnosticCode::ParseError),
260 "PL002" => Some(DiagnosticCode::SyntaxError),
261 "PL003" => Some(DiagnosticCode::UnexpectedEof),
262 "PL100" => Some(DiagnosticCode::MissingStrict),
263 "PL101" => Some(DiagnosticCode::MissingWarnings),
264 "PL102" => Some(DiagnosticCode::UnusedVariable),
265 "PL103" => Some(DiagnosticCode::UndefinedVariable),
266 "PL200" => Some(DiagnosticCode::MissingPackageDeclaration),
267 "PL201" => Some(DiagnosticCode::DuplicatePackage),
268 "PL300" => Some(DiagnosticCode::DuplicateSubroutine),
269 "PL301" => Some(DiagnosticCode::MissingReturn),
270 "PL400" => Some(DiagnosticCode::BarewordFilehandle),
271 "PL401" => Some(DiagnosticCode::TwoArgOpen),
272 "PL402" => Some(DiagnosticCode::ImplicitReturn),
273 "PC001" => Some(DiagnosticCode::CriticSeverity1),
274 "PC002" => Some(DiagnosticCode::CriticSeverity2),
275 "PC003" => Some(DiagnosticCode::CriticSeverity3),
276 "PC004" => Some(DiagnosticCode::CriticSeverity4),
277 "PC005" => Some(DiagnosticCode::CriticSeverity5),
278 _ => None,
279 }
280 }
281}
282
283impl fmt::Display for DiagnosticCode {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 write!(f, "{}", self.as_str())
286 }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
291#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
292pub enum DiagnosticCategory {
293 Parser,
295 StrictWarnings,
297 PackageModule,
299 Subroutine,
301 BestPractices,
303 PerlCritic,
305}
306
307impl DiagnosticCode {
308 pub fn category(&self) -> DiagnosticCategory {
310 match self {
311 DiagnosticCode::ParseError
312 | DiagnosticCode::SyntaxError
313 | DiagnosticCode::UnexpectedEof => DiagnosticCategory::Parser,
314
315 DiagnosticCode::MissingStrict
316 | DiagnosticCode::MissingWarnings
317 | DiagnosticCode::UnusedVariable
318 | DiagnosticCode::UndefinedVariable => DiagnosticCategory::StrictWarnings,
319
320 DiagnosticCode::MissingPackageDeclaration | DiagnosticCode::DuplicatePackage => {
321 DiagnosticCategory::PackageModule
322 }
323
324 DiagnosticCode::DuplicateSubroutine | DiagnosticCode::MissingReturn => {
325 DiagnosticCategory::Subroutine
326 }
327
328 DiagnosticCode::BarewordFilehandle
329 | DiagnosticCode::TwoArgOpen
330 | DiagnosticCode::ImplicitReturn => DiagnosticCategory::BestPractices,
331
332 DiagnosticCode::CriticSeverity1
333 | DiagnosticCode::CriticSeverity2
334 | DiagnosticCode::CriticSeverity3
335 | DiagnosticCode::CriticSeverity4
336 | DiagnosticCode::CriticSeverity5 => DiagnosticCategory::PerlCritic,
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn test_code_strings() {
347 assert_eq!(DiagnosticCode::ParseError.as_str(), "PL001");
348 assert_eq!(DiagnosticCode::MissingStrict.as_str(), "PL100");
349 assert_eq!(DiagnosticCode::CriticSeverity1.as_str(), "PC001");
350 }
351
352 #[test]
353 fn test_severity() {
354 assert_eq!(DiagnosticCode::ParseError.severity(), DiagnosticSeverity::Error);
355 assert_eq!(DiagnosticCode::UnusedVariable.severity(), DiagnosticSeverity::Warning);
356 assert_eq!(DiagnosticCode::CriticSeverity5.severity(), DiagnosticSeverity::Hint);
357 }
358
359 #[test]
360 fn test_from_message() {
361 assert_eq!(
362 DiagnosticCode::from_message("Missing 'use strict' pragma"),
363 Some(DiagnosticCode::MissingStrict)
364 );
365 assert_eq!(
366 DiagnosticCode::from_message("Unused variable $foo"),
367 Some(DiagnosticCode::UnusedVariable)
368 );
369 }
370
371 #[test]
372 fn test_from_str() {
373 assert_eq!(DiagnosticCode::parse_code("PL001"), Some(DiagnosticCode::ParseError));
374 assert_eq!(DiagnosticCode::parse_code("INVALID"), None);
375 }
376
377 #[test]
378 fn test_category() {
379 assert_eq!(DiagnosticCode::ParseError.category(), DiagnosticCategory::Parser);
380 assert_eq!(DiagnosticCode::MissingStrict.category(), DiagnosticCategory::StrictWarnings);
381 assert_eq!(DiagnosticCode::CriticSeverity1.category(), DiagnosticCategory::PerlCritic);
382 }
383
384 #[test]
385 fn test_tags() {
386 assert!(DiagnosticCode::UnusedVariable.tags().contains(&DiagnosticTag::Unnecessary));
387 assert!(DiagnosticCode::ParseError.tags().is_empty());
388 }
389}