Skip to main content

perl_diagnostics/catalog/
mod.rs

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