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 VariableShadowing,
110 VariableRedeclaration,
112 DuplicateParameter,
114 ParameterShadowsGlobal,
116 UnusedParameter,
118 UnquotedBareword,
120 UninitializedVariable,
122 MisspelledPragma,
124
125 MissingPackageDeclaration,
128 DuplicatePackage,
130
131 DuplicateSubroutine,
134 MissingReturn,
136
137 BarewordFilehandle,
140 TwoArgOpen,
142 ImplicitReturn,
144 AssignmentInCondition,
146 NumericComparisonWithUndef,
148 PrintfFormatMismatch,
150 UnreachableCode,
152
153 DeprecatedDefined,
156 DeprecatedArrayBase,
158
159 SecurityStringEval,
162 SecurityBacktickExec,
164
165 UnusedImport,
168 ModuleNotFound,
170
171 HeredocInFormat,
174 HeredocInBegin,
176 HeredocDynamicDelimiter,
178 HeredocInSourceFilter,
180 HeredocInRegexCode,
182 HeredocInEval,
184 HeredocTiedHandle,
186
187 VersionIncompatFeature,
190
191 CriticSeverity1,
194 CriticSeverity2,
196 CriticSeverity3,
198 CriticSeverity4,
200 CriticSeverity5,
202}
203
204impl DiagnosticCode {
205 pub fn as_str(&self) -> &'static str {
207 match self {
208 DiagnosticCode::ParseError => "PL001",
209 DiagnosticCode::SyntaxError => "PL002",
210 DiagnosticCode::UnexpectedEof => "PL003",
211 DiagnosticCode::MissingStrict => "PL100",
212 DiagnosticCode::MissingWarnings => "PL101",
213 DiagnosticCode::UnusedVariable => "PL102",
214 DiagnosticCode::UndefinedVariable => "PL103",
215 DiagnosticCode::VariableShadowing => "PL104",
216 DiagnosticCode::VariableRedeclaration => "PL105",
217 DiagnosticCode::DuplicateParameter => "PL106",
218 DiagnosticCode::ParameterShadowsGlobal => "PL107",
219 DiagnosticCode::UnusedParameter => "PL108",
220 DiagnosticCode::UnquotedBareword => "PL109",
221 DiagnosticCode::UninitializedVariable => "PL110",
222 DiagnosticCode::MisspelledPragma => "PL111",
223 DiagnosticCode::MissingPackageDeclaration => "PL200",
224 DiagnosticCode::DuplicatePackage => "PL201",
225 DiagnosticCode::DuplicateSubroutine => "PL300",
226 DiagnosticCode::MissingReturn => "PL301",
227 DiagnosticCode::BarewordFilehandle => "PL400",
228 DiagnosticCode::TwoArgOpen => "PL401",
229 DiagnosticCode::ImplicitReturn => "PL402",
230 DiagnosticCode::AssignmentInCondition => "PL403",
231 DiagnosticCode::NumericComparisonWithUndef => "PL404",
232 DiagnosticCode::PrintfFormatMismatch => "PL405",
233 DiagnosticCode::UnreachableCode => "PL406",
234 DiagnosticCode::DeprecatedDefined => "PL500",
235 DiagnosticCode::DeprecatedArrayBase => "PL501",
236 DiagnosticCode::SecurityStringEval => "PL600",
237 DiagnosticCode::SecurityBacktickExec => "PL601",
238 DiagnosticCode::UnusedImport => "PL700",
239 DiagnosticCode::ModuleNotFound => "PL701",
240 DiagnosticCode::HeredocInFormat => "PL800",
241 DiagnosticCode::HeredocInBegin => "PL801",
242 DiagnosticCode::HeredocDynamicDelimiter => "PL802",
243 DiagnosticCode::HeredocInSourceFilter => "PL803",
244 DiagnosticCode::HeredocInRegexCode => "PL804",
245 DiagnosticCode::HeredocInEval => "PL805",
246 DiagnosticCode::HeredocTiedHandle => "PL806",
247 DiagnosticCode::VersionIncompatFeature => "PL900",
248 DiagnosticCode::CriticSeverity1 => "PC001",
249 DiagnosticCode::CriticSeverity2 => "PC002",
250 DiagnosticCode::CriticSeverity3 => "PC003",
251 DiagnosticCode::CriticSeverity4 => "PC004",
252 DiagnosticCode::CriticSeverity5 => "PC005",
253 }
254 }
255
256 pub fn documentation_url(&self) -> Option<&'static str> {
258 let code = self.as_str();
259 if code.starts_with("PC") {
261 return None;
262 }
263 Some(match code {
265 "PL001" => "https://docs.perl-lsp.org/errors/PL001",
266 "PL002" => "https://docs.perl-lsp.org/errors/PL002",
267 "PL003" => "https://docs.perl-lsp.org/errors/PL003",
268 "PL100" => "https://docs.perl-lsp.org/errors/PL100",
269 "PL101" => "https://docs.perl-lsp.org/errors/PL101",
270 "PL102" => "https://docs.perl-lsp.org/errors/PL102",
271 "PL103" => "https://docs.perl-lsp.org/errors/PL103",
272 "PL104" => "https://docs.perl-lsp.org/errors/PL104",
273 "PL105" => "https://docs.perl-lsp.org/errors/PL105",
274 "PL106" => "https://docs.perl-lsp.org/errors/PL106",
275 "PL107" => "https://docs.perl-lsp.org/errors/PL107",
276 "PL108" => "https://docs.perl-lsp.org/errors/PL108",
277 "PL109" => "https://docs.perl-lsp.org/errors/PL109",
278 "PL110" => "https://docs.perl-lsp.org/errors/PL110",
279 "PL111" => "https://docs.perl-lsp.org/errors/PL111",
280 "PL200" => "https://docs.perl-lsp.org/errors/PL200",
281 "PL201" => "https://docs.perl-lsp.org/errors/PL201",
282 "PL300" => "https://docs.perl-lsp.org/errors/PL300",
283 "PL301" => "https://docs.perl-lsp.org/errors/PL301",
284 "PL400" => "https://docs.perl-lsp.org/errors/PL400",
285 "PL401" => "https://docs.perl-lsp.org/errors/PL401",
286 "PL402" => "https://docs.perl-lsp.org/errors/PL402",
287 "PL403" => "https://docs.perl-lsp.org/errors/PL403",
288 "PL404" => "https://docs.perl-lsp.org/errors/PL404",
289 "PL405" => "https://docs.perl-lsp.org/errors/PL405",
290 "PL406" => "https://docs.perl-lsp.org/errors/PL406",
291 "PL500" => "https://docs.perl-lsp.org/errors/PL500",
292 "PL501" => "https://docs.perl-lsp.org/errors/PL501",
293 "PL600" => "https://docs.perl-lsp.org/errors/PL600",
294 "PL601" => "https://docs.perl-lsp.org/errors/PL601",
295 "PL700" => "https://docs.perl-lsp.org/errors/PL700",
296 "PL701" => "https://docs.perl-lsp.org/errors/PL701",
297 "PL800" => "https://docs.perl-lsp.org/errors/PL800",
298 "PL801" => "https://docs.perl-lsp.org/errors/PL801",
299 "PL802" => "https://docs.perl-lsp.org/errors/PL802",
300 "PL803" => "https://docs.perl-lsp.org/errors/PL803",
301 "PL804" => "https://docs.perl-lsp.org/errors/PL804",
302 "PL805" => "https://docs.perl-lsp.org/errors/PL805",
303 "PL806" => "https://docs.perl-lsp.org/errors/PL806",
304 "PL900" => "https://docs.perl-lsp.org/errors/PL900",
305 _ => return None,
306 })
307 }
308
309 pub fn severity(&self) -> DiagnosticSeverity {
311 match self {
312 DiagnosticCode::ParseError
314 | DiagnosticCode::SyntaxError
315 | DiagnosticCode::UnexpectedEof
316 | DiagnosticCode::UndefinedVariable
317 | DiagnosticCode::VariableRedeclaration
318 | DiagnosticCode::DuplicateParameter
319 | DiagnosticCode::UnquotedBareword => DiagnosticSeverity::Error,
320
321 DiagnosticCode::MissingStrict
323 | DiagnosticCode::MissingWarnings
324 | DiagnosticCode::UnusedVariable
325 | DiagnosticCode::VariableShadowing
326 | DiagnosticCode::ParameterShadowsGlobal
327 | DiagnosticCode::UnusedParameter
328 | DiagnosticCode::UninitializedVariable
329 | DiagnosticCode::MisspelledPragma
330 | DiagnosticCode::MissingPackageDeclaration
331 | DiagnosticCode::DuplicatePackage
332 | DiagnosticCode::DuplicateSubroutine
333 | DiagnosticCode::MissingReturn
334 | DiagnosticCode::BarewordFilehandle
335 | DiagnosticCode::TwoArgOpen
336 | DiagnosticCode::ImplicitReturn
337 | DiagnosticCode::AssignmentInCondition
338 | DiagnosticCode::NumericComparisonWithUndef
339 | DiagnosticCode::PrintfFormatMismatch
340 | DiagnosticCode::DeprecatedDefined
341 | DiagnosticCode::DeprecatedArrayBase
342 | DiagnosticCode::SecurityStringEval
343 | DiagnosticCode::SecurityBacktickExec
344 | DiagnosticCode::ModuleNotFound
345 | DiagnosticCode::VersionIncompatFeature
346 | DiagnosticCode::CriticSeverity1
347 | DiagnosticCode::CriticSeverity2 => DiagnosticSeverity::Warning,
348
349 DiagnosticCode::HeredocInFormat
351 | DiagnosticCode::HeredocInBegin
352 | DiagnosticCode::HeredocDynamicDelimiter
353 | DiagnosticCode::HeredocInSourceFilter
354 | DiagnosticCode::HeredocInRegexCode
355 | DiagnosticCode::HeredocInEval
356 | DiagnosticCode::HeredocTiedHandle => DiagnosticSeverity::Information,
357
358 DiagnosticCode::UnusedImport
360 | DiagnosticCode::UnreachableCode
361 | DiagnosticCode::CriticSeverity3
362 | DiagnosticCode::CriticSeverity4
363 | DiagnosticCode::CriticSeverity5 => DiagnosticSeverity::Hint,
364 }
365 }
366
367 pub fn tags(&self) -> &'static [DiagnosticTag] {
369 match self {
370 DiagnosticCode::UnusedVariable
371 | DiagnosticCode::UnusedParameter
372 | DiagnosticCode::UnusedImport
373 | DiagnosticCode::UnreachableCode => &[DiagnosticTag::Unnecessary],
374 DiagnosticCode::DeprecatedDefined | DiagnosticCode::DeprecatedArrayBase => {
375 &[DiagnosticTag::Deprecated]
376 }
377 _ => &[],
378 }
379 }
380
381 pub fn context_hint(&self) -> Option<&'static str> {
388 match self {
389 DiagnosticCode::ParseError => Some(
390 "The parser could not understand this code. \
391 Check for missing semicolons, unmatched brackets, or incorrect syntax.",
392 ),
393 DiagnosticCode::SyntaxError => Some(
394 "Perl syntax error. Check for typos, missing operators, \
395 or unbalanced parentheses near this location.",
396 ),
397 DiagnosticCode::UnexpectedEof => Some(
398 "The file ended unexpectedly. Check for unclosed blocks `{}`, \
399 heredocs, or multi-line strings.",
400 ),
401 DiagnosticCode::MissingStrict => Some(
402 "Add `use strict;` at the top of your file. \
403 Strict mode catches common variable mistakes at compile time.",
404 ),
405 DiagnosticCode::MissingWarnings => Some(
406 "Add `use warnings;` at the top of your file. \
407 Warnings highlight many common programming mistakes.",
408 ),
409 DiagnosticCode::UnusedVariable => Some(
410 "This variable is declared but never used. \
411 Remove it, or prefix with `_` (e.g., `$_unused`) to suppress.",
412 ),
413 DiagnosticCode::UndefinedVariable => Some(
414 "This variable was not declared with `my`, `our`, or `local`. \
415 Add `use strict;` and declare all variables before use.",
416 ),
417 DiagnosticCode::MissingPackageDeclaration => Some(
418 "This file has no `package` declaration. \
419 Add `package MyModule;` at the top for module files.",
420 ),
421 DiagnosticCode::DuplicatePackage => Some(
422 "This package name is declared more than once in the same file. \
423 Each package should appear once, or split into separate files.",
424 ),
425 DiagnosticCode::DuplicateSubroutine => Some(
426 "A subroutine with this name is defined more than once. \
427 The later definition silently replaces the earlier one.",
428 ),
429 DiagnosticCode::MissingReturn => Some(
430 "This subroutine has no explicit `return` statement. \
431 Add `return $value;` to make the return value clear.",
432 ),
433 DiagnosticCode::BarewordFilehandle => Some(
434 "Bareword filehandles (e.g., `open FH, ...`) are global and unsafe. \
435 Use a lexical filehandle instead: `open my $fh, '<', $file or die $!;`",
436 ),
437 DiagnosticCode::TwoArgOpen => Some(
438 "Two-argument `open()` is vulnerable to injection. \
439 Use three-argument form: `open my $fh, '<', $filename or die $!;`",
440 ),
441 DiagnosticCode::ImplicitReturn => Some(
442 "The return value of this expression is used implicitly. \
443 Make it explicit with `return` or assign it to a variable.",
444 ),
445 DiagnosticCode::AssignmentInCondition => Some(
446 "This looks like an assignment `=` inside a condition where \
447 a comparison `==` or `eq` was likely intended.",
448 ),
449 DiagnosticCode::NumericComparisonWithUndef => Some(
450 "Comparing a potentially undefined value with a numeric operator \
451 produces a warning at runtime. Check for definedness first with `defined()`.",
452 ),
453 DiagnosticCode::UnreachableCode => Some(
454 "This statement cannot be executed because a preceding statement \
455 unconditionally exits (return, die, exit, croak). Remove or relocate it.",
456 ),
457 DiagnosticCode::PrintfFormatMismatch => Some(
458 "The number of format specifiers does not match the number of arguments. \
459 Each %s/%d/%f/etc. consumes one argument (except %% which consumes none).",
460 ),
461 DiagnosticCode::VariableShadowing => Some(
462 "This variable shadows an outer variable with the same name. \
463 Rename it to avoid confusion, or use the outer variable directly.",
464 ),
465 DiagnosticCode::VariableRedeclaration => Some(
466 "This variable is declared again in the same scope. \
467 Remove the duplicate `my` declaration and reuse the existing variable.",
468 ),
469 DiagnosticCode::DuplicateParameter => Some(
470 "This subroutine signature has a duplicate parameter name. \
471 Each parameter must have a unique name.",
472 ),
473 DiagnosticCode::ParameterShadowsGlobal => Some(
474 "This subroutine parameter shadows a global (`our`) variable. \
475 Rename the parameter to avoid confusion with the global.",
476 ),
477 DiagnosticCode::UnusedParameter => Some(
478 "This subroutine parameter is declared but never used. \
479 Remove it or prefix with `_` (e.g., `$_unused`) to suppress.",
480 ),
481 DiagnosticCode::UnquotedBareword => Some(
482 "This bareword is used where a quoted string is expected. \
483 Under `use strict`, barewords are not allowed. Quote it: `'word'`.",
484 ),
485 DiagnosticCode::UninitializedVariable => Some(
486 "This variable is used before being assigned a value. \
487 Initialize it before use to avoid `Use of uninitialized value` warnings.",
488 ),
489 DiagnosticCode::MisspelledPragma => Some(
490 "This pragma name appears to be misspelled. \
491 Check the spelling and ensure the module is installed.",
492 ),
493 DiagnosticCode::DeprecatedDefined => Some(
494 "`defined(@array)` and `defined(%hash)` are deprecated since Perl 5.6. \
495 Use `@array` or `%hash` directly in boolean context instead.",
496 ),
497 DiagnosticCode::DeprecatedArrayBase => Some(
498 "The `$[` variable is deprecated. Array indices always start at 0 \
499 in modern Perl. Remove any assignment to `$[`.",
500 ),
501 DiagnosticCode::SecurityStringEval => Some(
502 "String `eval` executes arbitrary code and is a security risk. \
503 Use block eval `eval { ... }` or safer alternatives.",
504 ),
505 DiagnosticCode::SecurityBacktickExec => Some(
506 "Backticks/`qx()` execute shell commands and can be exploited. \
507 Use `system()` with a list form or IPC::Run for safer execution.",
508 ),
509 DiagnosticCode::UnusedImport => Some(
510 "This module is imported but none of its exports appear to be used. \
511 Remove the `use` statement to reduce unnecessary dependencies.",
512 ),
513 DiagnosticCode::ModuleNotFound => Some(
514 "This module was not found in the workspace or configured include paths. \
515 Install it with cpanm or add it to cpanfile.",
516 ),
517 DiagnosticCode::HeredocInFormat => Some(
518 "Heredocs inside `format` blocks can cause subtle parsing issues. \
519 Extract the heredoc content into a variable before the format.",
520 ),
521 DiagnosticCode::HeredocInBegin => Some(
522 "Heredocs inside `BEGIN` blocks may behave unexpectedly due to \
523 compile-time execution. Move the heredoc outside the BEGIN block.",
524 ),
525 DiagnosticCode::HeredocDynamicDelimiter => Some(
526 "The heredoc delimiter contains a variable, making it dynamic. \
527 Use a static delimiter string to avoid surprising behavior.",
528 ),
529 DiagnosticCode::HeredocInSourceFilter => Some(
530 "Heredocs inside source-filtered code may be mangled by the filter. \
531 Avoid combining heredocs with source filters.",
532 ),
533 DiagnosticCode::HeredocInRegexCode => Some(
534 "Heredocs inside regex code blocks `(?{ ... })` can cause parsing failures. \
535 Move the heredoc content outside the regex.",
536 ),
537 DiagnosticCode::HeredocInEval => Some(
538 "Heredocs inside string `eval` are fragile and error-prone. \
539 Use a variable or block eval instead.",
540 ),
541 DiagnosticCode::HeredocTiedHandle => Some(
542 "Heredocs written to tied filehandles may not behave as expected. \
543 The tie interface may not handle multi-line heredoc output correctly.",
544 ),
545 DiagnosticCode::VersionIncompatFeature => Some(
546 "This Perl feature requires a newer Perl version than declared. \
547 Update 'use vN.NN' or 'use feature' to enable it.",
548 ),
549 DiagnosticCode::CriticSeverity1
551 | DiagnosticCode::CriticSeverity2
552 | DiagnosticCode::CriticSeverity3
553 | DiagnosticCode::CriticSeverity4
554 | DiagnosticCode::CriticSeverity5 => None,
555 }
556 }
557
558 pub fn from_message(msg: &str) -> Option<DiagnosticCode> {
560 let msg_lower = msg.to_lowercase();
561 if msg_lower.contains("use strict") {
562 Some(DiagnosticCode::MissingStrict)
563 } else if msg_lower.contains("use warnings") {
564 Some(DiagnosticCode::MissingWarnings)
565 } else if msg_lower.contains("unused variable") || msg_lower.contains("never used") {
566 Some(DiagnosticCode::UnusedVariable)
567 } else if msg_lower.contains("undefined") || msg_lower.contains("not declared") {
568 Some(DiagnosticCode::UndefinedVariable)
569 } else if msg_lower.contains("bareword filehandle") {
570 Some(DiagnosticCode::BarewordFilehandle)
571 } else if msg_lower.contains("two-argument") || msg_lower.contains("2-arg") {
572 Some(DiagnosticCode::TwoArgOpen)
573 } else if msg_lower.contains("parse error") || msg_lower.contains("syntax error") {
574 Some(DiagnosticCode::ParseError)
575 } else {
576 None
577 }
578 }
579
580 pub fn parse_code(code: &str) -> Option<DiagnosticCode> {
582 match code {
583 "PL001" => Some(DiagnosticCode::ParseError),
584 "PL002" => Some(DiagnosticCode::SyntaxError),
585 "PL003" => Some(DiagnosticCode::UnexpectedEof),
586 "PL100" => Some(DiagnosticCode::MissingStrict),
587 "PL101" => Some(DiagnosticCode::MissingWarnings),
588 "PL102" => Some(DiagnosticCode::UnusedVariable),
589 "PL103" => Some(DiagnosticCode::UndefinedVariable),
590 "PL104" => Some(DiagnosticCode::VariableShadowing),
591 "PL105" => Some(DiagnosticCode::VariableRedeclaration),
592 "PL106" => Some(DiagnosticCode::DuplicateParameter),
593 "PL107" => Some(DiagnosticCode::ParameterShadowsGlobal),
594 "PL108" => Some(DiagnosticCode::UnusedParameter),
595 "PL109" => Some(DiagnosticCode::UnquotedBareword),
596 "PL110" => Some(DiagnosticCode::UninitializedVariable),
597 "PL111" => Some(DiagnosticCode::MisspelledPragma),
598 "PL200" => Some(DiagnosticCode::MissingPackageDeclaration),
599 "PL201" => Some(DiagnosticCode::DuplicatePackage),
600 "PL300" => Some(DiagnosticCode::DuplicateSubroutine),
601 "PL301" => Some(DiagnosticCode::MissingReturn),
602 "PL400" => Some(DiagnosticCode::BarewordFilehandle),
603 "PL401" => Some(DiagnosticCode::TwoArgOpen),
604 "PL402" => Some(DiagnosticCode::ImplicitReturn),
605 "PL403" => Some(DiagnosticCode::AssignmentInCondition),
606 "PL404" => Some(DiagnosticCode::NumericComparisonWithUndef),
607 "PL405" => Some(DiagnosticCode::PrintfFormatMismatch),
608 "PL406" => Some(DiagnosticCode::UnreachableCode),
609 "PL500" => Some(DiagnosticCode::DeprecatedDefined),
610 "PL501" => Some(DiagnosticCode::DeprecatedArrayBase),
611 "PL600" => Some(DiagnosticCode::SecurityStringEval),
612 "PL601" => Some(DiagnosticCode::SecurityBacktickExec),
613 "PL700" => Some(DiagnosticCode::UnusedImport),
614 "PL701" => Some(DiagnosticCode::ModuleNotFound),
615 "PL800" => Some(DiagnosticCode::HeredocInFormat),
616 "PL801" => Some(DiagnosticCode::HeredocInBegin),
617 "PL802" => Some(DiagnosticCode::HeredocDynamicDelimiter),
618 "PL803" => Some(DiagnosticCode::HeredocInSourceFilter),
619 "PL804" => Some(DiagnosticCode::HeredocInRegexCode),
620 "PL805" => Some(DiagnosticCode::HeredocInEval),
621 "PL806" => Some(DiagnosticCode::HeredocTiedHandle),
622 "PL900" => Some(DiagnosticCode::VersionIncompatFeature),
623 "PC001" => Some(DiagnosticCode::CriticSeverity1),
624 "PC002" => Some(DiagnosticCode::CriticSeverity2),
625 "PC003" => Some(DiagnosticCode::CriticSeverity3),
626 "PC004" => Some(DiagnosticCode::CriticSeverity4),
627 "PC005" => Some(DiagnosticCode::CriticSeverity5),
628 _ => None,
629 }
630 }
631}
632
633impl fmt::Display for DiagnosticCode {
634 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635 write!(f, "{}", self.as_str())
636 }
637}
638
639#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
641#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
642pub enum DiagnosticCategory {
643 Parser,
645 StrictWarnings,
647 PackageModule,
649 Subroutine,
651 BestPractices,
653 Deprecated,
655 Security,
657 Import,
659 Heredoc,
661 PerlCritic,
663}
664
665impl DiagnosticCode {
666 pub fn category(&self) -> DiagnosticCategory {
668 match self {
669 DiagnosticCode::ParseError
670 | DiagnosticCode::SyntaxError
671 | DiagnosticCode::UnexpectedEof => DiagnosticCategory::Parser,
672
673 DiagnosticCode::MissingStrict
674 | DiagnosticCode::MissingWarnings
675 | DiagnosticCode::UnusedVariable
676 | DiagnosticCode::UndefinedVariable
677 | DiagnosticCode::VariableShadowing
678 | DiagnosticCode::VariableRedeclaration
679 | DiagnosticCode::DuplicateParameter
680 | DiagnosticCode::ParameterShadowsGlobal
681 | DiagnosticCode::UnusedParameter
682 | DiagnosticCode::UnquotedBareword
683 | DiagnosticCode::UninitializedVariable
684 | DiagnosticCode::MisspelledPragma => DiagnosticCategory::StrictWarnings,
685
686 DiagnosticCode::MissingPackageDeclaration | DiagnosticCode::DuplicatePackage => {
687 DiagnosticCategory::PackageModule
688 }
689
690 DiagnosticCode::DuplicateSubroutine | DiagnosticCode::MissingReturn => {
691 DiagnosticCategory::Subroutine
692 }
693
694 DiagnosticCode::BarewordFilehandle
695 | DiagnosticCode::TwoArgOpen
696 | DiagnosticCode::ImplicitReturn
697 | DiagnosticCode::AssignmentInCondition
698 | DiagnosticCode::NumericComparisonWithUndef
699 | DiagnosticCode::PrintfFormatMismatch
700 | DiagnosticCode::UnreachableCode => DiagnosticCategory::BestPractices,
701
702 DiagnosticCode::DeprecatedDefined | DiagnosticCode::DeprecatedArrayBase => {
703 DiagnosticCategory::Deprecated
704 }
705
706 DiagnosticCode::SecurityStringEval | DiagnosticCode::SecurityBacktickExec => {
707 DiagnosticCategory::Security
708 }
709
710 DiagnosticCode::UnusedImport | DiagnosticCode::ModuleNotFound => {
711 DiagnosticCategory::Import
712 }
713
714 DiagnosticCode::HeredocInFormat
715 | DiagnosticCode::HeredocInBegin
716 | DiagnosticCode::HeredocDynamicDelimiter
717 | DiagnosticCode::HeredocInSourceFilter
718 | DiagnosticCode::HeredocInRegexCode
719 | DiagnosticCode::HeredocInEval
720 | DiagnosticCode::HeredocTiedHandle => DiagnosticCategory::Heredoc,
721
722 DiagnosticCode::VersionIncompatFeature => DiagnosticCategory::BestPractices,
723
724 DiagnosticCode::CriticSeverity1
725 | DiagnosticCode::CriticSeverity2
726 | DiagnosticCode::CriticSeverity3
727 | DiagnosticCode::CriticSeverity4
728 | DiagnosticCode::CriticSeverity5 => DiagnosticCategory::PerlCritic,
729 }
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736
737 #[test]
738 fn test_code_strings() {
739 assert_eq!(DiagnosticCode::ParseError.as_str(), "PL001");
740 assert_eq!(DiagnosticCode::MissingStrict.as_str(), "PL100");
741 assert_eq!(DiagnosticCode::CriticSeverity1.as_str(), "PC001");
742 }
743
744 #[test]
745 fn test_severity() {
746 assert_eq!(DiagnosticCode::ParseError.severity(), DiagnosticSeverity::Error);
747 assert_eq!(DiagnosticCode::UnusedVariable.severity(), DiagnosticSeverity::Warning);
748 assert_eq!(DiagnosticCode::CriticSeverity5.severity(), DiagnosticSeverity::Hint);
749 }
750
751 #[test]
752 fn test_from_message() {
753 assert_eq!(
754 DiagnosticCode::from_message("Missing 'use strict' pragma"),
755 Some(DiagnosticCode::MissingStrict)
756 );
757 assert_eq!(
758 DiagnosticCode::from_message("Unused variable $foo"),
759 Some(DiagnosticCode::UnusedVariable)
760 );
761 }
762
763 #[test]
764 fn test_from_str() {
765 assert_eq!(DiagnosticCode::parse_code("PL001"), Some(DiagnosticCode::ParseError));
766 assert_eq!(DiagnosticCode::parse_code("INVALID"), None);
767 }
768
769 #[test]
770 fn test_category() {
771 assert_eq!(DiagnosticCode::ParseError.category(), DiagnosticCategory::Parser);
772 assert_eq!(DiagnosticCode::MissingStrict.category(), DiagnosticCategory::StrictWarnings);
773 assert_eq!(DiagnosticCode::CriticSeverity1.category(), DiagnosticCategory::PerlCritic);
774 }
775
776 #[test]
777 fn test_tags() {
778 assert!(DiagnosticCode::UnusedVariable.tags().contains(&DiagnosticTag::Unnecessary));
779 assert!(DiagnosticCode::ParseError.tags().is_empty());
780 }
781}