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