1use std::fmt;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[repr(u8)]
31pub enum DiagnosticSeverity {
32 #[default]
34 Error = 1,
35 Warning = 2,
37 Information = 3,
39 Hint = 4,
41}
42
43impl DiagnosticSeverity {
44 pub fn to_lsp_value(self) -> u8 {
46 self as u8
47 }
48}
49
50impl fmt::Display for DiagnosticSeverity {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::Error => write!(f, "error"),
54 Self::Warning => write!(f, "warning"),
55 Self::Information => write!(f, "info"),
56 Self::Hint => write!(f, "hint"),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub enum DiagnosticTag {
65 Unnecessary = 1,
67 Deprecated = 2,
69}
70
71impl DiagnosticTag {
72 pub fn to_lsp_value(self) -> u8 {
74 self as u8
75 }
76}
77
78impl fmt::Display for DiagnosticTag {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::Unnecessary => write!(f, "unnecessary"),
82 Self::Deprecated => write!(f, "deprecated"),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub enum DiagnosticCode {
93 #[default]
96 ParseError,
97 SyntaxError,
99 UnexpectedEof,
101
102 MissingStrict,
105 MissingWarnings,
107 UnusedVariable,
109 UndefinedVariable,
111 VariableShadowing,
113 VariableRedeclaration,
115 DuplicateParameter,
117 ParameterShadowsGlobal,
119 UnusedParameter,
121 UnquotedBareword,
123 UninitializedVariable,
125 MisspelledPragma,
127 CaptureVarWithoutRegexMatch,
129
130 MissingPackageDeclaration,
133 DuplicatePackage,
135
136 DuplicateSubroutine,
139 MissingReturn,
141 InvalidPrototype,
147 RoleConflict,
149 MissingPodCoverage,
151
152 BarewordFilehandle,
155 TwoArgOpen,
157 ImplicitReturn,
159 AssignmentInCondition,
161 NumericComparisonWithUndef,
163 PrintfFormatMismatch,
165 UnreachableCode,
167 EvalErrorFlow,
169 DuplicateHashKey,
171 GotoUndefinedLabel,
173 LoopControlUndefinedLabel,
175
176 DeprecatedDefined,
179 DeprecatedArrayBase,
181 PhaseScopedStrictPragma,
183 PhaseScopedWarningsPragma,
185
186 SecurityStringEval,
189 SecurityBacktickExec,
191 SecuritySignalHandler,
193 SecuritySystemCall,
195 SecurityExecCall,
197 SecurityPipeOpen,
199 SecurityReadpipe,
201
202 UnusedImport,
205 ModuleNotFound,
207
208 HeredocInFormat,
211 HeredocInBegin,
213 HeredocDynamicDelimiter,
215 HeredocInSourceFilter,
217 HeredocInRegexCode,
219 HeredocInEval,
221 HeredocTiedHandle,
223
224 VersionIncompatFeature,
227
228 CriticSeverity1,
231 CriticSeverity2,
233 CriticSeverity3,
235 CriticSeverity4,
237 CriticSeverity5,
239}
240
241impl DiagnosticCode {
242 pub fn as_str(&self) -> &'static str {
244 match self {
245 Self::ParseError => "PL001",
246 Self::SyntaxError => "PL002",
247 Self::UnexpectedEof => "PL003",
248 Self::MissingStrict => "PL100",
249 Self::MissingWarnings => "PL101",
250 Self::UnusedVariable => "PL102",
251 Self::UndefinedVariable => "PL103",
252 Self::VariableShadowing => "PL104",
253 Self::VariableRedeclaration => "PL105",
254 Self::DuplicateParameter => "PL106",
255 Self::ParameterShadowsGlobal => "PL107",
256 Self::UnusedParameter => "PL108",
257 Self::UnquotedBareword => "PL109",
258 Self::UninitializedVariable => "PL110",
259 Self::MisspelledPragma => "PL111",
260 Self::CaptureVarWithoutRegexMatch => "PL112",
261 Self::MissingPackageDeclaration => "PL200",
262 Self::DuplicatePackage => "PL201",
263 Self::DuplicateSubroutine => "PL300",
264 Self::MissingReturn => "PL301",
265 Self::InvalidPrototype => "PL302",
266 Self::RoleConflict => "PL303",
267 Self::MissingPodCoverage => "PL304",
268 Self::BarewordFilehandle => "PL400",
269 Self::TwoArgOpen => "PL401",
270 Self::ImplicitReturn => "PL402",
271 Self::AssignmentInCondition => "PL403",
272 Self::NumericComparisonWithUndef => "PL404",
273 Self::PrintfFormatMismatch => "PL405",
274 Self::UnreachableCode => "PL406",
275 Self::EvalErrorFlow => "PL407",
276 Self::DuplicateHashKey => "PL408",
277 Self::GotoUndefinedLabel => "PL409",
278 Self::LoopControlUndefinedLabel => "PL410",
279 Self::DeprecatedDefined => "PL500",
280 Self::DeprecatedArrayBase => "PL501",
281 Self::PhaseScopedStrictPragma => "PL502",
282 Self::PhaseScopedWarningsPragma => "PL503",
283 Self::SecurityStringEval => "PL600",
284 Self::SecurityBacktickExec => "PL601",
285 Self::SecuritySignalHandler => "PL602",
286 Self::SecuritySystemCall => "PL603",
287 Self::SecurityExecCall => "PL604",
288 Self::SecurityPipeOpen => "PL605",
289 Self::SecurityReadpipe => "PL606",
290 Self::UnusedImport => "PL700",
291 Self::ModuleNotFound => "PL701",
292 Self::HeredocInFormat => "PL800",
293 Self::HeredocInBegin => "PL801",
294 Self::HeredocDynamicDelimiter => "PL802",
295 Self::HeredocInSourceFilter => "PL803",
296 Self::HeredocInRegexCode => "PL804",
297 Self::HeredocInEval => "PL805",
298 Self::HeredocTiedHandle => "PL806",
299 Self::VersionIncompatFeature => "PL900",
300 Self::CriticSeverity1 => "PC001",
301 Self::CriticSeverity2 => "PC002",
302 Self::CriticSeverity3 => "PC003",
303 Self::CriticSeverity4 => "PC004",
304 Self::CriticSeverity5 => "PC005",
305 }
306 }
307
308 pub fn documentation_url(&self) -> Option<&'static str> {
310 let code = self.as_str();
311 if code.starts_with("PC") {
313 return None;
314 }
315 Some(match code {
317 "PL001" => "https://docs.perl-lsp.org/errors/PL001",
318 "PL002" => "https://docs.perl-lsp.org/errors/PL002",
319 "PL003" => "https://docs.perl-lsp.org/errors/PL003",
320 "PL100" => "https://docs.perl-lsp.org/errors/PL100",
321 "PL101" => "https://docs.perl-lsp.org/errors/PL101",
322 "PL102" => "https://docs.perl-lsp.org/errors/PL102",
323 "PL103" => "https://docs.perl-lsp.org/errors/PL103",
324 "PL104" => "https://docs.perl-lsp.org/errors/PL104",
325 "PL105" => "https://docs.perl-lsp.org/errors/PL105",
326 "PL106" => "https://docs.perl-lsp.org/errors/PL106",
327 "PL107" => "https://docs.perl-lsp.org/errors/PL107",
328 "PL108" => "https://docs.perl-lsp.org/errors/PL108",
329 "PL109" => "https://docs.perl-lsp.org/errors/PL109",
330 "PL110" => "https://docs.perl-lsp.org/errors/PL110",
331 "PL111" => "https://docs.perl-lsp.org/errors/PL111",
332 "PL112" => "https://docs.perl-lsp.org/errors/PL112",
333 "PL200" => "https://docs.perl-lsp.org/errors/PL200",
334 "PL201" => "https://docs.perl-lsp.org/errors/PL201",
335 "PL300" => "https://docs.perl-lsp.org/errors/PL300",
336 "PL301" => "https://docs.perl-lsp.org/errors/PL301",
337 "PL302" => "https://docs.perl-lsp.org/errors/PL302",
338 "PL303" => "https://docs.perl-lsp.org/errors/PL303",
339 "PL304" => "https://docs.perl-lsp.org/errors/PL304",
340 "PL400" => "https://docs.perl-lsp.org/errors/PL400",
341 "PL401" => "https://docs.perl-lsp.org/errors/PL401",
342 "PL402" => "https://docs.perl-lsp.org/errors/PL402",
343 "PL403" => "https://docs.perl-lsp.org/errors/PL403",
344 "PL404" => "https://docs.perl-lsp.org/errors/PL404",
345 "PL405" => "https://docs.perl-lsp.org/errors/PL405",
346 "PL406" => "https://docs.perl-lsp.org/errors/PL406",
347 "PL407" => "https://docs.perl-lsp.org/errors/PL407",
348 "PL408" => "https://docs.perl-lsp.org/errors/PL408",
349 "PL409" => "https://docs.perl-lsp.org/errors/PL409",
350 "PL410" => "https://docs.perl-lsp.org/errors/PL410",
351 "PL500" => "https://docs.perl-lsp.org/errors/PL500",
352 "PL501" => "https://docs.perl-lsp.org/errors/PL501",
353 "PL502" => "https://docs.perl-lsp.org/errors/PL502",
354 "PL503" => "https://docs.perl-lsp.org/errors/PL503",
355 "PL600" => "https://docs.perl-lsp.org/errors/PL600",
356 "PL601" => "https://docs.perl-lsp.org/errors/PL601",
357 "PL602" => "https://docs.perl-lsp.org/errors/PL602",
358 "PL603" => "https://docs.perl-lsp.org/errors/PL603",
359 "PL604" => "https://docs.perl-lsp.org/errors/PL604",
360 "PL605" => "https://docs.perl-lsp.org/errors/PL605",
361 "PL606" => "https://docs.perl-lsp.org/errors/PL606",
362 "PL700" => "https://docs.perl-lsp.org/errors/PL700",
363 "PL701" => "https://docs.perl-lsp.org/errors/PL701",
364 "PL800" => "https://docs.perl-lsp.org/errors/PL800",
365 "PL801" => "https://docs.perl-lsp.org/errors/PL801",
366 "PL802" => "https://docs.perl-lsp.org/errors/PL802",
367 "PL803" => "https://docs.perl-lsp.org/errors/PL803",
368 "PL804" => "https://docs.perl-lsp.org/errors/PL804",
369 "PL805" => "https://docs.perl-lsp.org/errors/PL805",
370 "PL806" => "https://docs.perl-lsp.org/errors/PL806",
371 "PL900" => "https://docs.perl-lsp.org/errors/PL900",
372 _ => return None,
373 })
374 }
375
376 pub fn severity(&self) -> DiagnosticSeverity {
378 match self {
379 Self::ParseError
381 | Self::SyntaxError
382 | Self::UnexpectedEof
383 | Self::UndefinedVariable
384 | Self::VariableRedeclaration
385 | Self::DuplicateParameter
386 | Self::UnquotedBareword => DiagnosticSeverity::Error,
387
388 Self::MissingStrict
390 | Self::MissingWarnings
391 | Self::UnusedVariable
392 | Self::VariableShadowing
393 | Self::ParameterShadowsGlobal
394 | Self::UnusedParameter
395 | Self::UninitializedVariable
396 | Self::MisspelledPragma
397 | Self::MissingPackageDeclaration
398 | Self::DuplicatePackage
399 | Self::DuplicateSubroutine
400 | Self::MissingReturn
401 | Self::InvalidPrototype
402 | Self::RoleConflict
403 | Self::BarewordFilehandle
404 | Self::TwoArgOpen
405 | Self::ImplicitReturn
406 | Self::AssignmentInCondition
407 | Self::NumericComparisonWithUndef
408 | Self::PrintfFormatMismatch
409 | Self::DuplicateHashKey
410 | Self::GotoUndefinedLabel
411 | Self::LoopControlUndefinedLabel
412 | Self::DeprecatedDefined
413 | Self::DeprecatedArrayBase
414 | Self::PhaseScopedStrictPragma
415 | Self::PhaseScopedWarningsPragma
416 | Self::SecurityStringEval
417 | Self::SecurityBacktickExec
418 | Self::SecuritySignalHandler
419 | Self::SecuritySystemCall
420 | Self::SecurityExecCall
421 | Self::SecurityPipeOpen
422 | Self::SecurityReadpipe
423 | Self::ModuleNotFound
424 | Self::VersionIncompatFeature
425 | Self::EvalErrorFlow
426 | Self::CriticSeverity1
427 | Self::CriticSeverity2 => DiagnosticSeverity::Warning,
428
429 Self::CaptureVarWithoutRegexMatch
431 | Self::HeredocInFormat
432 | Self::HeredocInBegin
433 | Self::HeredocDynamicDelimiter
434 | Self::HeredocInSourceFilter
435 | Self::HeredocInRegexCode
436 | Self::HeredocInEval
437 | Self::HeredocTiedHandle => DiagnosticSeverity::Information,
438
439 Self::MissingPodCoverage
441 | Self::UnusedImport
442 | Self::UnreachableCode
443 | Self::CriticSeverity3
444 | Self::CriticSeverity4
445 | Self::CriticSeverity5 => DiagnosticSeverity::Hint,
446 }
447 }
448
449 pub fn tags(&self) -> &'static [DiagnosticTag] {
451 match self {
452 Self::UnusedVariable
453 | Self::UnusedParameter
454 | Self::UnusedImport
455 | Self::UnreachableCode => &[DiagnosticTag::Unnecessary],
456 Self::DeprecatedDefined | Self::DeprecatedArrayBase => &[DiagnosticTag::Deprecated],
457 _ => &[],
458 }
459 }
460
461 pub fn context_hint(&self) -> Option<&'static str> {
468 match self {
469 Self::ParseError => Some(
470 "The parser could not understand this code. \
471 Check for missing semicolons, unmatched brackets, or incorrect syntax.",
472 ),
473 Self::SyntaxError => Some(
474 "Perl syntax error. Check for typos, missing operators, \
475 or unbalanced parentheses near this location.",
476 ),
477 Self::UnexpectedEof => Some(
478 "The file ended unexpectedly. Check for unclosed blocks `{}`, \
479 heredocs, or multi-line strings.",
480 ),
481 Self::MissingStrict => Some(
482 "Add `use strict;` at the top of your file. \
483 Strict mode catches common variable mistakes at compile time.",
484 ),
485 Self::MissingWarnings => Some(
486 "Add `use warnings;` at the top of your file. \
487 Warnings highlight many common programming mistakes.",
488 ),
489 Self::UnusedVariable => Some(
490 "This variable is declared but never used. \
491 Remove it, or prefix with `_` (e.g., `$_unused`) to suppress.",
492 ),
493 Self::UndefinedVariable => Some(
494 "This variable was not declared with `my`, `our`, or `local`. \
495 Add `use strict;` and declare all variables before use.",
496 ),
497 Self::MissingPackageDeclaration => Some(
498 "This file has no `package` declaration. \
499 Add `package MyModule;` at the top for module files.",
500 ),
501 Self::DuplicatePackage => Some(
502 "This package name is declared more than once in the same file. \
503 Each package should appear once, or split into separate files.",
504 ),
505 Self::DuplicateSubroutine => Some(
506 "A subroutine with this name is defined more than once. \
507 The later definition silently replaces the earlier one.",
508 ),
509 Self::MissingReturn => Some(
510 "This subroutine has no explicit `return` statement. \
511 Add `return $value;` to make the return value clear.",
512 ),
513 Self::RoleConflict => Some(
514 "Two or more consumed Moo/Moose roles provide the same method. \
515 Define the method in the class or remove one of the conflicting roles.",
516 ),
517 Self::MissingPodCoverage => Some(
518 "This exported subroutine has no corresponding `=head2` or `=item` POD section. \
519 Add documentation so users of your module can discover its API.",
520 ),
521 Self::InvalidPrototype => Some(
522 "The prototype contains a character that Perl does not recognise. \
523 Valid prototype characters are: $, @, %, &, *, \\, ;, +, _ and spaces. \
524 See perlsub for the full prototype syntax.",
525 ),
526 Self::BarewordFilehandle => Some(
527 "Bareword filehandles (e.g., `open FH, ...`) are global and unsafe. \
528 Use a lexical filehandle instead: `open my $fh, '<', $file or die $!;`",
529 ),
530 Self::TwoArgOpen => Some(
531 "Two-argument `open()` is vulnerable to injection. \
532 Use three-argument form: `open my $fh, '<', $filename or die $!;`",
533 ),
534 Self::ImplicitReturn => Some(
535 "The return value of this expression is used implicitly. \
536 Make it explicit with `return` or assign it to a variable.",
537 ),
538 Self::AssignmentInCondition => Some(
539 "This looks like an assignment `=` inside a condition where \
540 a comparison `==` or `eq` was likely intended.",
541 ),
542 Self::NumericComparisonWithUndef => Some(
543 "Comparing a potentially undefined value with a numeric operator \
544 produces a warning at runtime. Check for definedness first with `defined()`.",
545 ),
546 Self::EvalErrorFlow => Some(
547 "Read `$@` or `$EVAL_ERROR` immediately after an `eval` or `try` \
548 block; intervening statements can clobber the exception state.",
549 ),
550 Self::UnreachableCode => Some(
551 "This statement cannot be executed because a preceding statement \
552 unconditionally exits (return, die, exit, croak). Remove or relocate it.",
553 ),
554 Self::DuplicateHashKey => Some(
555 "This hash key appears more than once in the same literal. \
556 Only the last value will be used; the earlier assignment is silently discarded.",
557 ),
558 Self::GotoUndefinedLabel => Some(
559 "This goto target label is not defined in the current file. \
560 Define the label or use a dynamic goto form only when the target is known at runtime.",
561 ),
562 Self::LoopControlUndefinedLabel => Some(
563 "This `next`, `last`, or `redo` references a label that is not defined in the current file. \
564 Add a matching `LABEL:` on an enclosing loop, or remove the label to target the innermost loop.",
565 ),
566 Self::PrintfFormatMismatch => Some(
567 "The number of format specifiers does not match the number of arguments. \
568 Each %s/%d/%f/etc. consumes one argument (except %% which consumes none).",
569 ),
570 Self::VariableShadowing => Some(
571 "This variable shadows an outer variable with the same name. \
572 Rename it to avoid confusion, or use the outer variable directly.",
573 ),
574 Self::VariableRedeclaration => Some(
575 "This variable is declared again in the same scope. \
576 Remove the duplicate `my` declaration and reuse the existing variable.",
577 ),
578 Self::DuplicateParameter => Some(
579 "This subroutine signature has a duplicate parameter name. \
580 Each parameter must have a unique name.",
581 ),
582 Self::ParameterShadowsGlobal => Some(
583 "This subroutine parameter shadows a global (`our`) variable. \
584 Rename the parameter to avoid confusion with the global.",
585 ),
586 Self::UnusedParameter => Some(
587 "This subroutine parameter is declared but never used. \
588 Remove it or prefix with `_` (e.g., `$_unused`) to suppress.",
589 ),
590 Self::UnquotedBareword => Some(
591 "This bareword is used where a quoted string is expected. \
592 Under `use strict`, barewords are not allowed. Quote it: `'word'`.",
593 ),
594 Self::UninitializedVariable => Some(
595 "This variable is used before being assigned a value. \
596 Initialize it before use to avoid `Use of uninitialized value` warnings.",
597 ),
598 Self::MisspelledPragma => Some(
599 "This pragma name appears to be misspelled. \
600 Check the spelling and ensure the module is installed.",
601 ),
602 Self::CaptureVarWithoutRegexMatch => Some(
603 "Capture variables ($1, $2, etc.) are only meaningful after a successful regex match. \
604 Perform a regex match with =~ /.../ before using $1 or $2.",
605 ),
606 Self::DeprecatedDefined => Some(
607 "`defined(@array)` and `defined(%hash)` are deprecated since Perl 5.6. \
608 Use `@array` or `%hash` directly in boolean context instead.",
609 ),
610 Self::DeprecatedArrayBase => Some(
611 "The `$[` variable is deprecated. Array indices always start at 0 \
612 in modern Perl. Remove any assignment to `$[`.",
613 ),
614 Self::PhaseScopedStrictPragma => Some(
615 "`use strict` inside a phase block only applies inside that block. \
616 Move `use strict;` to file scope for file-wide strict enforcement.",
617 ),
618 Self::PhaseScopedWarningsPragma => Some(
619 "`use warnings` inside a phase block only applies inside that block. \
620 Move `use warnings;` to file scope for file-wide warnings coverage.",
621 ),
622 Self::SecurityStringEval => Some(
623 "String `eval` executes arbitrary code and is a security risk. \
624 Use block eval `eval { ... }` or safer alternatives.",
625 ),
626 Self::SecurityBacktickExec => Some(
627 "Backticks/`qx()` execute shell commands and can be exploited. \
628 Use `system()` with a list form or IPC::Run for safer execution.",
629 ),
630 Self::SecuritySignalHandler => Some(
631 "Assigning to $SIG{__DIE__} or $SIG{__WARN__} globally changes exception \
632 and warning handling for the whole process. Use `local` to scope the handler.",
633 ),
634 Self::SecuritySystemCall => Some(
635 "`system()` executes a shell command. If the arguments include user input, \
636 use the list form `system($cmd, @args)` to avoid shell injection.",
637 ),
638 Self::SecurityExecCall => Some(
639 "`exec()` replaces the current process with a shell command. If arguments \
640 include user input, use the list form `exec($cmd, @args)` to avoid shell injection.",
641 ),
642 Self::SecurityPipeOpen => Some(
643 "Pipe-open executes a shell command. Pass a list to `open` for safe argument \
644 handling: `open(my $fh, '-|', $cmd, @args)` instead of `open(my $fh, \"|$cmd\")`.",
645 ),
646 Self::SecurityReadpipe => Some(
647 "`readpipe()` executes a shell command (equivalent to backticks/qx//). \
648 Use `open(my $fh, '-|', $cmd, @args)` or IPC::Run for safer command execution.",
649 ),
650 Self::UnusedImport => Some(
651 "This module is imported but none of its exports appear to be used. \
652 Remove the `use` statement to reduce unnecessary dependencies.",
653 ),
654 Self::ModuleNotFound => Some(
655 "This module was not found in the workspace or configured include paths. \
656 Install it with cpanm or add it to cpanfile.",
657 ),
658 Self::HeredocInFormat => Some(
659 "Heredocs inside `format` blocks can cause subtle parsing issues. \
660 Extract the heredoc content into a variable before the format.",
661 ),
662 Self::HeredocInBegin => Some(
663 "Heredocs inside `BEGIN` blocks may behave unexpectedly due to \
664 compile-time execution. Move the heredoc outside the BEGIN block.",
665 ),
666 Self::HeredocDynamicDelimiter => Some(
667 "The heredoc delimiter contains a variable, making it dynamic. \
668 Use a static delimiter string to avoid surprising behavior.",
669 ),
670 Self::HeredocInSourceFilter => Some(
671 "Heredocs inside source-filtered code may be mangled by the filter. \
672 Avoid combining heredocs with source filters.",
673 ),
674 Self::HeredocInRegexCode => Some(
675 "Heredocs inside regex code blocks `(?{ ... })` can cause parsing failures. \
676 Move the heredoc content outside the regex.",
677 ),
678 Self::HeredocInEval => Some(
679 "Heredocs inside string `eval` are fragile and error-prone. \
680 Use a variable or block eval instead.",
681 ),
682 Self::HeredocTiedHandle => Some(
683 "Heredocs written to tied filehandles may not behave as expected. \
684 The tie interface may not handle multi-line heredoc output correctly.",
685 ),
686 Self::VersionIncompatFeature => Some(
687 "This Perl feature requires a newer Perl version than declared. \
688 Update 'use vN.NN' or 'use feature' to enable it.",
689 ),
690 Self::CriticSeverity1
692 | Self::CriticSeverity2
693 | Self::CriticSeverity3
694 | Self::CriticSeverity4
695 | Self::CriticSeverity5 => None,
696 }
697 }
698
699 pub fn from_message(msg: &str) -> Option<Self> {
701 let msg_lower = msg.to_lowercase();
702 if msg_lower.contains("inside a begin block does not enable strict")
703 || msg_lower.contains("inside a phase block does not enable strict")
704 {
705 Some(Self::PhaseScopedStrictPragma)
706 } else if msg_lower.contains("inside a begin block does not enable warnings")
707 || msg_lower.contains("inside a phase block does not enable warnings")
708 {
709 Some(Self::PhaseScopedWarningsPragma)
710 } else if msg_lower.contains("use strict") {
711 Some(Self::MissingStrict)
712 } else if msg_lower.contains("use warnings") {
713 Some(Self::MissingWarnings)
714 } else if msg_lower.contains("unused variable") || msg_lower.contains("never used") {
715 Some(Self::UnusedVariable)
716 } else if msg_lower.contains("undefined") || msg_lower.contains("not declared") {
717 Some(Self::UndefinedVariable)
718 } else if msg_lower.contains("bareword filehandle") {
719 Some(Self::BarewordFilehandle)
720 } else if msg_lower.contains("two-argument") || msg_lower.contains("2-arg") {
721 Some(Self::TwoArgOpen)
722 } else if msg_lower.contains("invalid prototype character")
723 || msg_lower.contains("illegal character in prototype")
724 {
725 Some(Self::InvalidPrototype)
726 } else if msg_lower.contains("parse error") || msg_lower.contains("syntax error") {
727 Some(Self::ParseError)
728 } else {
729 None
730 }
731 }
732
733 pub fn parse_code(code: &str) -> Option<Self> {
735 match code {
736 "PL001" => Some(Self::ParseError),
737 "PL002" => Some(Self::SyntaxError),
738 "PL003" => Some(Self::UnexpectedEof),
739 "PL100" => Some(Self::MissingStrict),
740 "PL101" => Some(Self::MissingWarnings),
741 "PL102" => Some(Self::UnusedVariable),
742 "PL103" => Some(Self::UndefinedVariable),
743 "PL104" => Some(Self::VariableShadowing),
744 "PL105" => Some(Self::VariableRedeclaration),
745 "PL106" => Some(Self::DuplicateParameter),
746 "PL107" => Some(Self::ParameterShadowsGlobal),
747 "PL108" => Some(Self::UnusedParameter),
748 "PL109" => Some(Self::UnquotedBareword),
749 "PL110" => Some(Self::UninitializedVariable),
750 "PL111" => Some(Self::MisspelledPragma),
751 "PL112" => Some(Self::CaptureVarWithoutRegexMatch),
752 "PL200" => Some(Self::MissingPackageDeclaration),
753 "PL201" => Some(Self::DuplicatePackage),
754 "PL300" => Some(Self::DuplicateSubroutine),
755 "PL301" => Some(Self::MissingReturn),
756 "PL302" => Some(Self::InvalidPrototype),
757 "PL303" => Some(Self::RoleConflict),
758 "PL304" => Some(Self::MissingPodCoverage),
759 "PL400" => Some(Self::BarewordFilehandle),
760 "PL401" => Some(Self::TwoArgOpen),
761 "PL402" => Some(Self::ImplicitReturn),
762 "PL403" => Some(Self::AssignmentInCondition),
763 "PL404" => Some(Self::NumericComparisonWithUndef),
764 "PL405" => Some(Self::PrintfFormatMismatch),
765 "PL406" => Some(Self::UnreachableCode),
766 "PL407" => Some(Self::EvalErrorFlow),
767 "PL408" => Some(Self::DuplicateHashKey),
768 "PL409" => Some(Self::GotoUndefinedLabel),
769 "PL410" => Some(Self::LoopControlUndefinedLabel),
770 "PL500" => Some(Self::DeprecatedDefined),
771 "PL501" => Some(Self::DeprecatedArrayBase),
772 "PL502" => Some(Self::PhaseScopedStrictPragma),
773 "PL503" => Some(Self::PhaseScopedWarningsPragma),
774 "PL600" => Some(Self::SecurityStringEval),
775 "PL601" => Some(Self::SecurityBacktickExec),
776 "PL602" => Some(Self::SecuritySignalHandler),
777 "PL603" => Some(Self::SecuritySystemCall),
778 "PL604" => Some(Self::SecurityExecCall),
779 "PL605" => Some(Self::SecurityPipeOpen),
780 "PL606" => Some(Self::SecurityReadpipe),
781 "PL700" => Some(Self::UnusedImport),
782 "PL701" => Some(Self::ModuleNotFound),
783 "PL800" => Some(Self::HeredocInFormat),
784 "PL801" => Some(Self::HeredocInBegin),
785 "PL802" => Some(Self::HeredocDynamicDelimiter),
786 "PL803" => Some(Self::HeredocInSourceFilter),
787 "PL804" => Some(Self::HeredocInRegexCode),
788 "PL805" => Some(Self::HeredocInEval),
789 "PL806" => Some(Self::HeredocTiedHandle),
790 "PL900" => Some(Self::VersionIncompatFeature),
791 "PC001" => Some(Self::CriticSeverity1),
792 "PC002" => Some(Self::CriticSeverity2),
793 "PC003" => Some(Self::CriticSeverity3),
794 "PC004" => Some(Self::CriticSeverity4),
795 "PC005" => Some(Self::CriticSeverity5),
796 _ => None,
797 }
798 }
799
800 pub fn category(&self) -> DiagnosticCategory {
802 match self {
803 Self::ParseError | Self::SyntaxError | Self::UnexpectedEof => {
804 DiagnosticCategory::Parser
805 }
806
807 Self::MissingStrict
808 | Self::MissingWarnings
809 | Self::UnusedVariable
810 | Self::UndefinedVariable
811 | Self::VariableShadowing
812 | Self::VariableRedeclaration
813 | Self::DuplicateParameter
814 | Self::ParameterShadowsGlobal
815 | Self::UnusedParameter
816 | Self::UnquotedBareword
817 | Self::UninitializedVariable
818 | Self::MisspelledPragma
819 | Self::CaptureVarWithoutRegexMatch
820 | Self::PhaseScopedStrictPragma
821 | Self::PhaseScopedWarningsPragma => DiagnosticCategory::StrictWarnings,
822
823 Self::MissingPackageDeclaration | Self::DuplicatePackage => {
824 DiagnosticCategory::PackageModule
825 }
826
827 Self::DuplicateSubroutine
828 | Self::MissingReturn
829 | Self::InvalidPrototype
830 | Self::RoleConflict
831 | Self::MissingPodCoverage => DiagnosticCategory::Subroutine,
832
833 Self::BarewordFilehandle
834 | Self::TwoArgOpen
835 | Self::ImplicitReturn
836 | Self::AssignmentInCondition
837 | Self::NumericComparisonWithUndef
838 | Self::PrintfFormatMismatch
839 | Self::UnreachableCode
840 | Self::EvalErrorFlow
841 | Self::DuplicateHashKey
842 | Self::GotoUndefinedLabel
843 | Self::LoopControlUndefinedLabel
844 | Self::VersionIncompatFeature => DiagnosticCategory::BestPractices,
845
846 Self::DeprecatedDefined | Self::DeprecatedArrayBase => DiagnosticCategory::Deprecated,
847
848 Self::SecurityStringEval
849 | Self::SecurityBacktickExec
850 | Self::SecuritySignalHandler
851 | Self::SecuritySystemCall
852 | Self::SecurityExecCall
853 | Self::SecurityPipeOpen
854 | Self::SecurityReadpipe => DiagnosticCategory::Security,
855
856 Self::UnusedImport | Self::ModuleNotFound => DiagnosticCategory::Import,
857
858 Self::HeredocInFormat
859 | Self::HeredocInBegin
860 | Self::HeredocDynamicDelimiter
861 | Self::HeredocInSourceFilter
862 | Self::HeredocInRegexCode
863 | Self::HeredocInEval
864 | Self::HeredocTiedHandle => DiagnosticCategory::Heredoc,
865
866 Self::CriticSeverity1
867 | Self::CriticSeverity2
868 | Self::CriticSeverity3
869 | Self::CriticSeverity4
870 | Self::CriticSeverity5 => DiagnosticCategory::PerlCritic,
871 }
872 }
873}
874
875impl fmt::Display for DiagnosticCode {
876 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877 write!(f, "{}", self.as_str())
878 }
879}
880
881#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
883#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
884pub enum DiagnosticCategory {
885 Parser,
887 StrictWarnings,
889 PackageModule,
891 Subroutine,
893 BestPractices,
895 Deprecated,
897 Security,
899 Import,
901 Heredoc,
903 PerlCritic,
905}
906
907impl fmt::Display for DiagnosticCategory {
908 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909 match self {
910 Self::Parser => write!(f, "Parser"),
911 Self::StrictWarnings => write!(f, "Strict/Warnings"),
912 Self::PackageModule => write!(f, "Package/Module"),
913 Self::Subroutine => write!(f, "Subroutine"),
914 Self::BestPractices => write!(f, "Best Practices"),
915 Self::Deprecated => write!(f, "Deprecated"),
916 Self::Security => write!(f, "Security"),
917 Self::Import => write!(f, "Import"),
918 Self::Heredoc => write!(f, "Heredoc"),
919 Self::PerlCritic => write!(f, "Perl::Critic"),
920 }
921 }
922}