Skip to main content

perl_lsp_diagnostic_catalog/
lib.rs

1//! Diagnostic catalog with stable codes for consistent LSP error reporting.
2//!
3//! This crate provides a focused mapping from [`perl_diagnostics_codes::DiagnosticCode`]
4//! to LSP-facing metadata payloads.
5
6#![deny(unsafe_code)]
7#![warn(rust_2018_idioms)]
8#![warn(missing_docs)]
9#![warn(clippy::all)]
10
11use perl_diagnostics_codes::DiagnosticCode;
12use serde_json::{Value, json};
13
14/// Diagnostic metadata payload used by LSP diagnostics.
15pub struct DiagnosticMeta {
16    /// Stable diagnostic code (for example, `"PL001"`).
17    pub code: Value,
18    /// Optional code description object containing a docs URL.
19    pub desc: Option<Value>,
20}
21
22impl DiagnosticMeta {
23    fn from_code(code: DiagnosticCode) -> Self {
24        Self {
25            code: json!(code.as_str()),
26            desc: code.documentation_url().map(|url| json!({ "href": url })),
27        }
28    }
29}
30
31/// Build LSP diagnostic metadata from a stable diagnostic code.
32#[must_use]
33pub fn diagnostic_meta(code: DiagnosticCode) -> DiagnosticMeta {
34    DiagnosticMeta::from_code(code)
35}
36
37/// General parse error diagnostic (PL001).
38#[must_use]
39pub fn parse_error() -> DiagnosticMeta {
40    diagnostic_meta(DiagnosticCode::ParseError)
41}
42
43/// Syntax error diagnostic (PL002).
44#[must_use]
45pub fn syntax_error() -> DiagnosticMeta {
46    diagnostic_meta(DiagnosticCode::SyntaxError)
47}
48
49/// Unexpected end-of-file diagnostic (PL003).
50#[must_use]
51pub fn unexpected_eof() -> DiagnosticMeta {
52    diagnostic_meta(DiagnosticCode::UnexpectedEof)
53}
54
55/// Missing `use strict` pragma diagnostic (PL100).
56#[must_use]
57pub fn missing_strict() -> DiagnosticMeta {
58    diagnostic_meta(DiagnosticCode::MissingStrict)
59}
60
61/// Missing `use warnings` pragma diagnostic (PL101).
62#[must_use]
63pub fn missing_warnings() -> DiagnosticMeta {
64    diagnostic_meta(DiagnosticCode::MissingWarnings)
65}
66
67/// Unused variable diagnostic (PL102).
68#[must_use]
69pub fn unused_var() -> DiagnosticMeta {
70    diagnostic_meta(DiagnosticCode::UnusedVariable)
71}
72
73/// Undefined variable diagnostic (PL103).
74#[must_use]
75pub fn undefined_var() -> DiagnosticMeta {
76    diagnostic_meta(DiagnosticCode::UndefinedVariable)
77}
78
79/// Missing package declaration diagnostic (PL200).
80#[must_use]
81pub fn missing_package_declaration() -> DiagnosticMeta {
82    diagnostic_meta(DiagnosticCode::MissingPackageDeclaration)
83}
84
85/// Duplicate package declaration diagnostic (PL201).
86#[must_use]
87pub fn duplicate_package() -> DiagnosticMeta {
88    diagnostic_meta(DiagnosticCode::DuplicatePackage)
89}
90
91/// Duplicate subroutine definition diagnostic (PL300).
92#[must_use]
93pub fn duplicate_sub() -> DiagnosticMeta {
94    diagnostic_meta(DiagnosticCode::DuplicateSubroutine)
95}
96
97/// Missing explicit return statement diagnostic (PL301).
98#[must_use]
99pub fn missing_return() -> DiagnosticMeta {
100    diagnostic_meta(DiagnosticCode::MissingReturn)
101}
102
103/// Bareword filehandle usage diagnostic (PL400).
104#[must_use]
105pub fn bareword_filehandle() -> DiagnosticMeta {
106    diagnostic_meta(DiagnosticCode::BarewordFilehandle)
107}
108
109/// Two-argument `open()` usage diagnostic (PL401).
110#[must_use]
111pub fn two_arg_open() -> DiagnosticMeta {
112    diagnostic_meta(DiagnosticCode::TwoArgOpen)
113}
114
115/// Implicit return value diagnostic (PL402).
116#[must_use]
117pub fn implicit_return() -> DiagnosticMeta {
118    diagnostic_meta(DiagnosticCode::ImplicitReturn)
119}
120
121/// Perl::Critic severity-5 violation diagnostic (PC005).
122#[must_use]
123pub fn critic_severity_5() -> DiagnosticMeta {
124    diagnostic_meta(DiagnosticCode::CriticSeverity5)
125}
126
127/// Perl::Critic severity-4 violation diagnostic (PC004).
128#[must_use]
129pub fn critic_severity_4() -> DiagnosticMeta {
130    diagnostic_meta(DiagnosticCode::CriticSeverity4)
131}
132
133/// Perl::Critic severity-3 violation diagnostic (PC003).
134#[must_use]
135pub fn critic_severity_3() -> DiagnosticMeta {
136    diagnostic_meta(DiagnosticCode::CriticSeverity3)
137}
138
139/// Perl::Critic severity-2 violation diagnostic (PC002).
140#[must_use]
141pub fn critic_severity_2() -> DiagnosticMeta {
142    diagnostic_meta(DiagnosticCode::CriticSeverity2)
143}
144
145/// Perl::Critic severity-1 violation diagnostic (PC001).
146#[must_use]
147pub fn critic_severity_1() -> DiagnosticMeta {
148    diagnostic_meta(DiagnosticCode::CriticSeverity1)
149}
150
151/// Guess diagnostic metadata from a free-form message.
152#[must_use]
153pub fn from_message(msg: &str) -> Option<DiagnosticMeta> {
154    DiagnosticCode::from_message(msg).map(diagnostic_meta)
155}
156
157#[cfg(test)]
158mod tests {
159    use super::{from_message, parse_error};
160
161    #[test]
162    fn parse_error_includes_stable_code_and_docs_url() {
163        let meta = parse_error();
164        assert_eq!(meta.code, "PL001");
165        assert_eq!(
166            meta.desc,
167            Some(serde_json::json!({ "href": "https://docs.perl-lsp.org/errors/PL001" }))
168        );
169    }
170
171    #[test]
172    fn critic_codes_have_no_docs_url() {
173        let meta = super::critic_severity_1();
174        assert_eq!(meta.code, "PC001");
175        assert!(meta.desc.is_none());
176    }
177
178    #[test]
179    fn message_inference_is_case_insensitive() {
180        let meta = from_message("Missing USE STRICT pragma");
181        assert!(meta.is_some());
182        assert_eq!(meta.as_ref().map(|m| &m.code), Some(&serde_json::json!("PL100")));
183    }
184}