1use super::{DiagnosticCode, DiagnosticSeverity, DiagnosticTag};
2
3fn is_undefined_variable_message(msg_lower: &str) -> bool {
4 contains_diagnostic_phrase(msg_lower, "not declared")
5 || contains_diagnostic_phrase(msg_lower, "undefined variable")
6 || (contains_diagnostic_phrase(msg_lower, "undefined")
7 && (contains_diagnostic_phrase(msg_lower, "variable")
8 || contains_diagnostic_phrase(msg_lower, "global symbol")))
9 || contains_diagnostic_phrase(msg_lower, "requires explicit package name")
11}
12
13fn contains_diagnostic_phrase(haystack: &str, needle: &str) -> bool {
14 haystack.match_indices(needle).any(|(start, matched)| {
15 let end = start + matched.len();
16 has_phrase_boundary(haystack, start, end)
17 })
18}
19
20fn has_phrase_boundary(haystack: &str, start: usize, end: usize) -> bool {
21 let bytes = haystack.as_bytes();
22 let before_is_word = start
23 .checked_sub(1)
24 .and_then(|index| bytes.get(index))
25 .is_some_and(|byte| is_phrase_word_byte(*byte));
26 let after_is_word = bytes.get(end).is_some_and(|byte| is_phrase_word_byte(*byte));
27
28 !before_is_word && !after_is_word
29}
30
31fn is_phrase_word_byte(byte: u8) -> bool {
32 byte.is_ascii_alphanumeric() || byte == b'_'
33}
34
35impl DiagnosticCode {
36 pub fn as_str(&self) -> &'static str {
38 match self {
39 Self::ParseError => "PL001",
40 Self::SyntaxError => "PL002",
41 Self::UnexpectedEof => "PL003",
42 Self::MissingStrict => "PL100",
43 Self::MissingWarnings => "PL101",
44 Self::UnusedVariable => "PL102",
45 Self::UndefinedVariable => "PL103",
46 Self::VariableShadowing => "PL104",
47 Self::VariableRedeclaration => "PL105",
48 Self::DuplicateParameter => "PL106",
49 Self::ParameterShadowsGlobal => "PL107",
50 Self::UnusedParameter => "PL108",
51 Self::UnquotedBareword => "PL109",
52 Self::UninitializedVariable => "PL110",
53 Self::MisspelledPragma => "PL111",
54 Self::CaptureVarWithoutRegexMatch => "PL112",
55 Self::MissingPackageDeclaration => "PL200",
56 Self::DuplicatePackage => "PL201",
57 Self::DuplicateSubroutine => "PL300",
58 Self::MissingReturn => "PL301",
59 Self::InvalidPrototype => "PL302",
60 Self::RoleConflict => "PL303",
61 Self::MissingPodCoverage => "PL304",
62 Self::BarewordFilehandle => "PL400",
63 Self::TwoArgOpen => "PL401",
64 Self::ImplicitReturn => "PL402",
65 Self::AssignmentInCondition => "PL403",
66 Self::NumericComparisonWithUndef => "PL404",
67 Self::PrintfFormatMismatch => "PL405",
68 Self::UnreachableCode => "PL406",
69 Self::EvalErrorFlow => "PL407",
70 Self::DuplicateHashKey => "PL408",
71 Self::GotoUndefinedLabel => "PL409",
72 Self::LoopControlUndefinedLabel => "PL410",
73 Self::DeprecatedDefined => "PL500",
74 Self::DeprecatedArrayBase => "PL501",
75 Self::PhaseScopedStrictPragma => "PL502",
76 Self::PhaseScopedWarningsPragma => "PL503",
77 Self::SecurityStringEval => "PL600",
78 Self::SecurityBacktickExec => "PL601",
79 Self::SecuritySignalHandler => "PL602",
80 Self::SecuritySystemCall => "PL603",
81 Self::SecurityExecCall => "PL604",
82 Self::SecurityPipeOpen => "PL605",
83 Self::SecurityReadpipe => "PL606",
84 Self::UnusedImport => "PL700",
85 Self::ModuleNotFound => "PL701",
86 Self::HeredocInFormat => "PL800",
87 Self::HeredocInBegin => "PL801",
88 Self::HeredocDynamicDelimiter => "PL802",
89 Self::HeredocInSourceFilter => "PL803",
90 Self::HeredocInRegexCode => "PL804",
91 Self::HeredocInEval => "PL805",
92 Self::HeredocTiedHandle => "PL806",
93 Self::VersionIncompatFeature => "PL900",
94 Self::CriticSeverity1 => "PC001",
95 Self::CriticSeverity2 => "PC002",
96 Self::CriticSeverity3 => "PC003",
97 Self::CriticSeverity4 => "PC004",
98 Self::CriticSeverity5 => "PC005",
99 }
100 }
101
102 pub fn documentation_url(&self) -> Option<&'static str> {
104 let code = self.as_str();
105 if code.starts_with("PC") {
107 return None;
108 }
109 Some(match code {
111 "PL001" => "https://docs.perl-lsp.org/errors/PL001",
112 "PL002" => "https://docs.perl-lsp.org/errors/PL002",
113 "PL003" => "https://docs.perl-lsp.org/errors/PL003",
114 "PL100" => "https://docs.perl-lsp.org/errors/PL100",
115 "PL101" => "https://docs.perl-lsp.org/errors/PL101",
116 "PL102" => "https://docs.perl-lsp.org/errors/PL102",
117 "PL103" => "https://docs.perl-lsp.org/errors/PL103",
118 "PL104" => "https://docs.perl-lsp.org/errors/PL104",
119 "PL105" => "https://docs.perl-lsp.org/errors/PL105",
120 "PL106" => "https://docs.perl-lsp.org/errors/PL106",
121 "PL107" => "https://docs.perl-lsp.org/errors/PL107",
122 "PL108" => "https://docs.perl-lsp.org/errors/PL108",
123 "PL109" => "https://docs.perl-lsp.org/errors/PL109",
124 "PL110" => "https://docs.perl-lsp.org/errors/PL110",
125 "PL111" => "https://docs.perl-lsp.org/errors/PL111",
126 "PL112" => "https://docs.perl-lsp.org/errors/PL112",
127 "PL200" => "https://docs.perl-lsp.org/errors/PL200",
128 "PL201" => "https://docs.perl-lsp.org/errors/PL201",
129 "PL300" => "https://docs.perl-lsp.org/errors/PL300",
130 "PL301" => "https://docs.perl-lsp.org/errors/PL301",
131 "PL302" => "https://docs.perl-lsp.org/errors/PL302",
132 "PL303" => "https://docs.perl-lsp.org/errors/PL303",
133 "PL304" => "https://docs.perl-lsp.org/errors/PL304",
134 "PL400" => "https://docs.perl-lsp.org/errors/PL400",
135 "PL401" => "https://docs.perl-lsp.org/errors/PL401",
136 "PL402" => "https://docs.perl-lsp.org/errors/PL402",
137 "PL403" => "https://docs.perl-lsp.org/errors/PL403",
138 "PL404" => "https://docs.perl-lsp.org/errors/PL404",
139 "PL405" => "https://docs.perl-lsp.org/errors/PL405",
140 "PL406" => "https://docs.perl-lsp.org/errors/PL406",
141 "PL407" => "https://docs.perl-lsp.org/errors/PL407",
142 "PL408" => "https://docs.perl-lsp.org/errors/PL408",
143 "PL409" => "https://docs.perl-lsp.org/errors/PL409",
144 "PL410" => "https://docs.perl-lsp.org/errors/PL410",
145 "PL500" => "https://docs.perl-lsp.org/errors/PL500",
146 "PL501" => "https://docs.perl-lsp.org/errors/PL501",
147 "PL502" => "https://docs.perl-lsp.org/errors/PL502",
148 "PL503" => "https://docs.perl-lsp.org/errors/PL503",
149 "PL600" => "https://docs.perl-lsp.org/errors/PL600",
150 "PL601" => "https://docs.perl-lsp.org/errors/PL601",
151 "PL602" => "https://docs.perl-lsp.org/errors/PL602",
152 "PL603" => "https://docs.perl-lsp.org/errors/PL603",
153 "PL604" => "https://docs.perl-lsp.org/errors/PL604",
154 "PL605" => "https://docs.perl-lsp.org/errors/PL605",
155 "PL606" => "https://docs.perl-lsp.org/errors/PL606",
156 "PL700" => "https://docs.perl-lsp.org/errors/PL700",
157 "PL701" => "https://docs.perl-lsp.org/errors/PL701",
158 "PL800" => "https://docs.perl-lsp.org/errors/PL800",
159 "PL801" => "https://docs.perl-lsp.org/errors/PL801",
160 "PL802" => "https://docs.perl-lsp.org/errors/PL802",
161 "PL803" => "https://docs.perl-lsp.org/errors/PL803",
162 "PL804" => "https://docs.perl-lsp.org/errors/PL804",
163 "PL805" => "https://docs.perl-lsp.org/errors/PL805",
164 "PL806" => "https://docs.perl-lsp.org/errors/PL806",
165 "PL900" => "https://docs.perl-lsp.org/errors/PL900",
166 _ => return None,
167 })
168 }
169
170 pub fn severity(&self) -> DiagnosticSeverity {
172 match self {
173 Self::ParseError
175 | Self::SyntaxError
176 | Self::UnexpectedEof
177 | Self::UndefinedVariable
178 | Self::VariableRedeclaration
179 | Self::DuplicateParameter
180 | Self::UnquotedBareword => DiagnosticSeverity::Error,
181
182 Self::MissingStrict
184 | Self::MissingWarnings
185 | Self::UnusedVariable
186 | Self::VariableShadowing
187 | Self::ParameterShadowsGlobal
188 | Self::UnusedParameter
189 | Self::UninitializedVariable
190 | Self::MisspelledPragma
191 | Self::MissingPackageDeclaration
192 | Self::DuplicatePackage
193 | Self::DuplicateSubroutine
194 | Self::MissingReturn
195 | Self::InvalidPrototype
196 | Self::RoleConflict
197 | Self::BarewordFilehandle
198 | Self::TwoArgOpen
199 | Self::ImplicitReturn
200 | Self::AssignmentInCondition
201 | Self::NumericComparisonWithUndef
202 | Self::PrintfFormatMismatch
203 | Self::DuplicateHashKey
204 | Self::GotoUndefinedLabel
205 | Self::LoopControlUndefinedLabel
206 | Self::DeprecatedDefined
207 | Self::DeprecatedArrayBase
208 | Self::PhaseScopedStrictPragma
209 | Self::PhaseScopedWarningsPragma
210 | Self::SecurityStringEval
211 | Self::SecurityBacktickExec
212 | Self::SecuritySignalHandler
213 | Self::SecuritySystemCall
214 | Self::SecurityExecCall
215 | Self::SecurityPipeOpen
216 | Self::SecurityReadpipe
217 | Self::ModuleNotFound
218 | Self::VersionIncompatFeature
219 | Self::EvalErrorFlow
220 | Self::CriticSeverity1
221 | Self::CriticSeverity2 => DiagnosticSeverity::Warning,
222
223 Self::CaptureVarWithoutRegexMatch
225 | Self::HeredocInFormat
226 | Self::HeredocInBegin
227 | Self::HeredocDynamicDelimiter
228 | Self::HeredocInSourceFilter
229 | Self::HeredocInRegexCode
230 | Self::HeredocInEval
231 | Self::HeredocTiedHandle => DiagnosticSeverity::Information,
232
233 Self::MissingPodCoverage
235 | Self::UnusedImport
236 | Self::UnreachableCode
237 | Self::CriticSeverity3
238 | Self::CriticSeverity4
239 | Self::CriticSeverity5 => DiagnosticSeverity::Hint,
240 }
241 }
242
243 pub fn tags(&self) -> &'static [DiagnosticTag] {
245 match self {
246 Self::UnusedVariable
247 | Self::UnusedParameter
248 | Self::UnusedImport
249 | Self::UnreachableCode => &[DiagnosticTag::Unnecessary],
250 Self::DeprecatedDefined | Self::DeprecatedArrayBase => &[DiagnosticTag::Deprecated],
251 _ => &[],
252 }
253 }
254
255 pub fn context_hint(&self) -> Option<&'static str> {
262 match self {
263 Self::ParseError => Some(
264 "The parser could not understand this code. \
265 Check for missing semicolons, unmatched brackets, or incorrect syntax.",
266 ),
267 Self::SyntaxError => Some(
268 "Perl syntax error. Check for typos, missing operators, \
269 or unbalanced parentheses near this location.",
270 ),
271 Self::UnexpectedEof => Some(
272 "The file ended unexpectedly. Check for unclosed blocks `{}`, \
273 heredocs, or multi-line strings.",
274 ),
275 Self::MissingStrict => Some(
276 "Add `use strict;` at the top of your file. \
277 Strict mode catches common variable mistakes at compile time.",
278 ),
279 Self::MissingWarnings => Some(
280 "Add `use warnings;` at the top of your file. \
281 Warnings highlight many common programming mistakes.",
282 ),
283 Self::UnusedVariable => Some(
284 "This variable is declared but never used. \
285 Remove it, or prefix with `_` (e.g., `$_unused`) to suppress.",
286 ),
287 Self::UndefinedVariable => Some(
288 "This variable was not declared with `my`, `our`, or `local`. \
289 Add `use strict;` and declare all variables before use.",
290 ),
291 Self::MissingPackageDeclaration => Some(
292 "This file has no `package` declaration. \
293 Add `package MyModule;` at the top for module files.",
294 ),
295 Self::DuplicatePackage => Some(
296 "This package name is declared more than once in the same file. \
297 Each package should appear once, or split into separate files.",
298 ),
299 Self::DuplicateSubroutine => Some(
300 "A subroutine with this name is defined more than once. \
301 The later definition silently replaces the earlier one.",
302 ),
303 Self::MissingReturn => Some(
304 "This subroutine has no explicit `return` statement. \
305 Add `return $value;` to make the return value clear.",
306 ),
307 Self::RoleConflict => Some(
308 "Two or more consumed Moo/Moose roles provide the same method. \
309 Define the method in the class or remove one of the conflicting roles.",
310 ),
311 Self::MissingPodCoverage => Some(
312 "This exported subroutine has no corresponding `=head2` or `=item` POD section. \
313 Add documentation so users of your module can discover its API.",
314 ),
315 Self::InvalidPrototype => Some(
316 "The prototype contains a character that Perl does not recognise. \
317 Valid prototype characters are: $, @, %, &, *, \\, ;, +, _ and spaces. \
318 See perlsub for the full prototype syntax.",
319 ),
320 Self::BarewordFilehandle => Some(
321 "Bareword filehandles (e.g., `open FH, ...`) are global and unsafe. \
322 Use a lexical filehandle instead: `open my $fh, '<', $file or die $!;`",
323 ),
324 Self::TwoArgOpen => Some(
325 "Two-argument `open()` is vulnerable to injection. \
326 Use three-argument form: `open my $fh, '<', $filename or die $!;`",
327 ),
328 Self::ImplicitReturn => Some(
329 "The return value of this expression is used implicitly. \
330 Make it explicit with `return` or assign it to a variable.",
331 ),
332 Self::AssignmentInCondition => Some(
333 "This looks like an assignment `=` inside a condition where \
334 a comparison `==` or `eq` was likely intended.",
335 ),
336 Self::NumericComparisonWithUndef => Some(
337 "Comparing a potentially undefined value with a numeric operator \
338 produces a warning at runtime. Check for definedness first with `defined()`.",
339 ),
340 Self::EvalErrorFlow => Some(
341 "Read `$@` or `$EVAL_ERROR` immediately after an `eval` or `try` \
342 block; intervening statements can clobber the exception state.",
343 ),
344 Self::UnreachableCode => Some(
345 "This statement cannot be executed because a preceding statement \
346 unconditionally exits (return, die, exit, croak). Remove or relocate it.",
347 ),
348 Self::DuplicateHashKey => Some(
349 "This hash key appears more than once in the same literal. \
350 Only the last value will be used; the earlier assignment is silently discarded.",
351 ),
352 Self::GotoUndefinedLabel => Some(
353 "This goto target label is not defined in the current file. \
354 Define the label or use a dynamic goto form only when the target is known at runtime.",
355 ),
356 Self::LoopControlUndefinedLabel => Some(
357 "This `next`, `last`, or `redo` references a label that is not defined in the current file. \
358 Add a matching `LABEL:` on an enclosing loop, or remove the label to target the innermost loop.",
359 ),
360 Self::PrintfFormatMismatch => Some(
361 "The number of format specifiers does not match the number of arguments. \
362 Each %s/%d/%f/etc. consumes one argument (except %% which consumes none).",
363 ),
364 Self::VariableShadowing => Some(
365 "This variable shadows an outer variable with the same name. \
366 Rename it to avoid confusion, or use the outer variable directly.",
367 ),
368 Self::VariableRedeclaration => Some(
369 "This variable is declared again in the same scope. \
370 Remove the duplicate `my` declaration and reuse the existing variable.",
371 ),
372 Self::DuplicateParameter => Some(
373 "This subroutine signature has a duplicate parameter name. \
374 Each parameter must have a unique name.",
375 ),
376 Self::ParameterShadowsGlobal => Some(
377 "This subroutine parameter shadows a global (`our`) variable. \
378 Rename the parameter to avoid confusion with the global.",
379 ),
380 Self::UnusedParameter => Some(
381 "This subroutine parameter is declared but never used. \
382 Remove it or prefix with `_` (e.g., `$_unused`) to suppress.",
383 ),
384 Self::UnquotedBareword => Some(
385 "This bareword is used where a quoted string is expected. \
386 Under `use strict`, barewords are not allowed. Quote it: `'word'`.",
387 ),
388 Self::UninitializedVariable => Some(
389 "This variable is used before being assigned a value. \
390 Initialize it before use to avoid `Use of uninitialized value` warnings.",
391 ),
392 Self::MisspelledPragma => Some(
393 "This pragma name appears to be misspelled. \
394 Check the spelling and ensure the module is installed.",
395 ),
396 Self::CaptureVarWithoutRegexMatch => Some(
397 "Capture variables ($1, $2, etc.) are only meaningful after a successful regex match. \
398 Perform a regex match with =~ /.../ before using $1 or $2.",
399 ),
400 Self::DeprecatedDefined => Some(
401 "`defined(@array)` and `defined(%hash)` are deprecated since Perl 5.6. \
402 Use `@array` or `%hash` directly in boolean context instead.",
403 ),
404 Self::DeprecatedArrayBase => Some(
405 "The `$[` variable is deprecated. Array indices always start at 0 \
406 in modern Perl. Remove any assignment to `$[`.",
407 ),
408 Self::PhaseScopedStrictPragma => Some(
409 "`use strict` inside a phase block only applies inside that block. \
410 Move `use strict;` to file scope for file-wide strict enforcement.",
411 ),
412 Self::PhaseScopedWarningsPragma => Some(
413 "`use warnings` inside a phase block only applies inside that block. \
414 Move `use warnings;` to file scope for file-wide warnings coverage.",
415 ),
416 Self::SecurityStringEval => Some(
417 "String `eval` executes arbitrary code and is a security risk. \
418 Use block eval `eval { ... }` or safer alternatives.",
419 ),
420 Self::SecurityBacktickExec => Some(
421 "Backticks/`qx()` execute shell commands and can be exploited. \
422 Use `system()` with a list form or IPC::Run for safer execution.",
423 ),
424 Self::SecuritySignalHandler => Some(
425 "Assigning to $SIG{__DIE__} or $SIG{__WARN__} globally changes exception \
426 and warning handling for the whole process. Use `local` to scope the handler.",
427 ),
428 Self::SecuritySystemCall => Some(
429 "`system()` executes a shell command. If the arguments include user input, \
430 use the list form `system($cmd, @args)` to avoid shell injection.",
431 ),
432 Self::SecurityExecCall => Some(
433 "`exec()` replaces the current process with a shell command. If arguments \
434 include user input, use the list form `exec($cmd, @args)` to avoid shell injection.",
435 ),
436 Self::SecurityPipeOpen => Some(
437 "Pipe-open executes a shell command. Pass a list to `open` for safe argument \
438 handling: `open(my $fh, '-|', $cmd, @args)` instead of `open(my $fh, \"|$cmd\")`.",
439 ),
440 Self::SecurityReadpipe => Some(
441 "`readpipe()` executes a shell command (equivalent to backticks/qx//). \
442 Use `open(my $fh, '-|', $cmd, @args)` or IPC::Run for safer command execution.",
443 ),
444 Self::UnusedImport => Some(
445 "This module is imported but none of its exports appear to be used. \
446 Remove the `use` statement to reduce unnecessary dependencies.",
447 ),
448 Self::ModuleNotFound => Some(
449 "This module was not found in the workspace or configured include paths. \
450 Install it with cpanm or add it to cpanfile.",
451 ),
452 Self::HeredocInFormat => Some(
453 "Heredocs inside `format` blocks can cause subtle parsing issues. \
454 Extract the heredoc content into a variable before the format.",
455 ),
456 Self::HeredocInBegin => Some(
457 "Heredocs inside `BEGIN` blocks may behave unexpectedly due to \
458 compile-time execution. Move the heredoc outside the BEGIN block.",
459 ),
460 Self::HeredocDynamicDelimiter => Some(
461 "The heredoc delimiter contains a variable, making it dynamic. \
462 Use a static delimiter string to avoid surprising behavior.",
463 ),
464 Self::HeredocInSourceFilter => Some(
465 "Heredocs inside source-filtered code may be mangled by the filter. \
466 Avoid combining heredocs with source filters.",
467 ),
468 Self::HeredocInRegexCode => Some(
469 "Heredocs inside regex code blocks `(?{ ... })` can cause parsing failures. \
470 Move the heredoc content outside the regex.",
471 ),
472 Self::HeredocInEval => Some(
473 "Heredocs inside string `eval` are fragile and error-prone. \
474 Use a variable or block eval instead.",
475 ),
476 Self::HeredocTiedHandle => Some(
477 "Heredocs written to tied filehandles may not behave as expected. \
478 The tie interface may not handle multi-line heredoc output correctly.",
479 ),
480 Self::VersionIncompatFeature => Some(
481 "This Perl feature requires a newer Perl version than declared. \
482 Update 'use vN.NN' or 'use feature' to enable it.",
483 ),
484 Self::CriticSeverity1
486 | Self::CriticSeverity2
487 | Self::CriticSeverity3
488 | Self::CriticSeverity4
489 | Self::CriticSeverity5 => None,
490 }
491 }
492
493 pub fn from_message(msg: &str) -> Option<Self> {
495 let msg_lower = msg.to_lowercase();
496 if contains_diagnostic_phrase(&msg_lower, "inside a begin block does not enable strict")
497 || contains_diagnostic_phrase(&msg_lower, "inside a phase block does not enable strict")
498 {
499 Some(Self::PhaseScopedStrictPragma)
500 } else if contains_diagnostic_phrase(
501 &msg_lower,
502 "inside a begin block does not enable warnings",
503 ) || contains_diagnostic_phrase(
504 &msg_lower,
505 "inside a phase block does not enable warnings",
506 ) {
507 Some(Self::PhaseScopedWarningsPragma)
508 } else if contains_diagnostic_phrase(&msg_lower, "use strict") {
509 Some(Self::MissingStrict)
510 } else if contains_diagnostic_phrase(&msg_lower, "use warnings") {
511 Some(Self::MissingWarnings)
512 } else if contains_diagnostic_phrase(&msg_lower, "unused variable")
513 || contains_diagnostic_phrase(&msg_lower, "never used")
514 {
515 Some(Self::UnusedVariable)
516 } else if is_undefined_variable_message(&msg_lower) {
517 Some(Self::UndefinedVariable)
518 } else if contains_diagnostic_phrase(&msg_lower, "bareword filehandle") {
519 Some(Self::BarewordFilehandle)
520 } else if contains_diagnostic_phrase(&msg_lower, "two-argument")
521 || contains_diagnostic_phrase(&msg_lower, "2-argument")
522 || contains_diagnostic_phrase(&msg_lower, "2-arg")
523 {
524 Some(Self::TwoArgOpen)
525 } else if contains_diagnostic_phrase(&msg_lower, "subroutine")
527 && contains_diagnostic_phrase(&msg_lower, "redefined")
528 {
529 Some(Self::DuplicateSubroutine)
530 } else if contains_diagnostic_phrase(&msg_lower, "invalid prototype character")
531 || contains_diagnostic_phrase(&msg_lower, "illegal character in prototype")
532 || contains_diagnostic_phrase(&msg_lower, "prototype mismatch")
534 {
535 Some(Self::InvalidPrototype)
536 } else if contains_diagnostic_phrase(&msg_lower, "parse error")
537 || contains_diagnostic_phrase(&msg_lower, "syntax error")
538 {
539 Some(Self::ParseError)
540 } else if contains_diagnostic_phrase(&msg_lower, "uninitialized value") {
543 Some(Self::UninitializedVariable)
544 } else if contains_diagnostic_phrase(&msg_lower, "can't locate")
546 && msg_lower.contains(".pm")
547 {
548 Some(Self::ModuleNotFound)
549 } else if contains_diagnostic_phrase(&msg_lower, "strict subs")
554 || contains_diagnostic_phrase(&msg_lower, "unquoted string")
555 || contains_diagnostic_phrase(&msg_lower, "bareword not allowed")
556 {
557 Some(Self::UnquotedBareword)
558 } else if msg_lower.contains("defined(")
561 && contains_diagnostic_phrase(&msg_lower, "deprecated")
562 {
563 Some(Self::DeprecatedDefined)
564 } else {
565 None
566 }
567 }
568
569 pub fn parse_code(code: &str) -> Option<Self> {
571 match code {
572 "PL001" => Some(Self::ParseError),
573 "PL002" => Some(Self::SyntaxError),
574 "PL003" => Some(Self::UnexpectedEof),
575 "PL100" => Some(Self::MissingStrict),
576 "PL101" => Some(Self::MissingWarnings),
577 "PL102" => Some(Self::UnusedVariable),
578 "PL103" => Some(Self::UndefinedVariable),
579 "PL104" => Some(Self::VariableShadowing),
580 "PL105" => Some(Self::VariableRedeclaration),
581 "PL106" => Some(Self::DuplicateParameter),
582 "PL107" => Some(Self::ParameterShadowsGlobal),
583 "PL108" => Some(Self::UnusedParameter),
584 "PL109" => Some(Self::UnquotedBareword),
585 "PL110" => Some(Self::UninitializedVariable),
586 "PL111" => Some(Self::MisspelledPragma),
587 "PL112" => Some(Self::CaptureVarWithoutRegexMatch),
588 "PL200" => Some(Self::MissingPackageDeclaration),
589 "PL201" => Some(Self::DuplicatePackage),
590 "PL300" => Some(Self::DuplicateSubroutine),
591 "PL301" => Some(Self::MissingReturn),
592 "PL302" => Some(Self::InvalidPrototype),
593 "PL303" => Some(Self::RoleConflict),
594 "PL304" => Some(Self::MissingPodCoverage),
595 "PL400" => Some(Self::BarewordFilehandle),
596 "PL401" => Some(Self::TwoArgOpen),
597 "PL402" => Some(Self::ImplicitReturn),
598 "PL403" => Some(Self::AssignmentInCondition),
599 "PL404" => Some(Self::NumericComparisonWithUndef),
600 "PL405" => Some(Self::PrintfFormatMismatch),
601 "PL406" => Some(Self::UnreachableCode),
602 "PL407" => Some(Self::EvalErrorFlow),
603 "PL408" => Some(Self::DuplicateHashKey),
604 "PL409" => Some(Self::GotoUndefinedLabel),
605 "PL410" => Some(Self::LoopControlUndefinedLabel),
606 "PL500" => Some(Self::DeprecatedDefined),
607 "PL501" => Some(Self::DeprecatedArrayBase),
608 "PL502" => Some(Self::PhaseScopedStrictPragma),
609 "PL503" => Some(Self::PhaseScopedWarningsPragma),
610 "PL600" => Some(Self::SecurityStringEval),
611 "PL601" => Some(Self::SecurityBacktickExec),
612 "PL602" => Some(Self::SecuritySignalHandler),
613 "PL603" => Some(Self::SecuritySystemCall),
614 "PL604" => Some(Self::SecurityExecCall),
615 "PL605" => Some(Self::SecurityPipeOpen),
616 "PL606" => Some(Self::SecurityReadpipe),
617 "PL700" => Some(Self::UnusedImport),
618 "PL701" => Some(Self::ModuleNotFound),
619 "PL800" => Some(Self::HeredocInFormat),
620 "PL801" => Some(Self::HeredocInBegin),
621 "PL802" => Some(Self::HeredocDynamicDelimiter),
622 "PL803" => Some(Self::HeredocInSourceFilter),
623 "PL804" => Some(Self::HeredocInRegexCode),
624 "PL805" => Some(Self::HeredocInEval),
625 "PL806" => Some(Self::HeredocTiedHandle),
626 "PL900" => Some(Self::VersionIncompatFeature),
627 "PC001" => Some(Self::CriticSeverity1),
628 "PC002" => Some(Self::CriticSeverity2),
629 "PC003" => Some(Self::CriticSeverity3),
630 "PC004" => Some(Self::CriticSeverity4),
631 "PC005" => Some(Self::CriticSeverity5),
632 _ => None,
633 }
634 }
635}