Skip to main content

scrape_core/parser/
warnings.rs

1//! Parse warning collection for developer experience.
2
3use crate::error::SourceSpan;
4
5/// Severity level for parse warnings.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub enum WarningSeverity {
8    /// Informational message (HTML5 quirks mode, etc.).
9    Info,
10    /// Warning (recoverable issue).
11    Warning,
12    /// Error that was recovered from.
13    RecoveredError,
14}
15
16/// A warning or error encountered during parsing.
17#[derive(Debug, Clone)]
18pub struct ParseWarning {
19    /// Severity of the warning.
20    pub severity: WarningSeverity,
21    /// Description of the issue.
22    pub message: String,
23    /// Location in source, if available.
24    pub span: Option<SourceSpan>,
25    /// HTML5 spec reference, if applicable.
26    pub spec_reference: Option<String>,
27}
28
29impl ParseWarning {
30    /// Creates a new parse warning.
31    #[must_use]
32    pub fn new(severity: WarningSeverity, message: impl Into<String>) -> Self {
33        Self { severity, message: message.into(), span: None, spec_reference: None }
34    }
35
36    /// Sets the span for this warning.
37    #[must_use]
38    pub fn with_span(mut self, span: SourceSpan) -> Self {
39        self.span = Some(span);
40        self
41    }
42
43    /// Sets the spec reference for this warning.
44    #[must_use]
45    pub fn with_spec_reference(mut self, reference: impl Into<String>) -> Self {
46        self.spec_reference = Some(reference.into());
47        self
48    }
49}
50
51/// Result of parsing with warnings collected.
52#[derive(Debug)]
53pub struct ParseResultWithWarnings<T> {
54    /// The parsed result.
55    pub result: T,
56    /// Warnings encountered during parsing.
57    pub warnings: Vec<ParseWarning>,
58}
59
60impl<T> ParseResultWithWarnings<T> {
61    /// Creates a new result with warnings.
62    #[must_use]
63    pub fn new(result: T, warnings: Vec<ParseWarning>) -> Self {
64        Self { result, warnings }
65    }
66
67    /// Returns true if there are any warnings.
68    #[must_use]
69    pub fn has_warnings(&self) -> bool {
70        !self.warnings.is_empty()
71    }
72
73    /// Returns warnings filtered by minimum severity.
74    #[must_use]
75    pub fn warnings_at_least(&self, min_severity: WarningSeverity) -> Vec<&ParseWarning> {
76        self.warnings.iter().filter(|w| w.severity >= min_severity).collect()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::error::SourcePosition;
84
85    #[test]
86    fn test_warning_creation() {
87        let warning = ParseWarning::new(WarningSeverity::Warning, "unclosed tag");
88        assert_eq!(warning.severity, WarningSeverity::Warning);
89        assert_eq!(warning.message, "unclosed tag");
90        assert!(warning.span.is_none());
91        assert!(warning.spec_reference.is_none());
92    }
93
94    #[test]
95    fn test_warning_with_span() {
96        let span = SourceSpan::new(SourcePosition::new(1, 1, 0), SourcePosition::new(1, 5, 4));
97        let warning = ParseWarning::new(WarningSeverity::Warning, "test").with_span(span);
98
99        assert!(warning.span.is_some());
100        assert_eq!(warning.span.unwrap(), span);
101    }
102
103    #[test]
104    fn test_warning_with_spec_reference() {
105        let warning = ParseWarning::new(WarningSeverity::Info, "test")
106            .with_spec_reference("https://html.spec.whatwg.org/#parse-state");
107
108        assert!(warning.spec_reference.is_some());
109        assert!(warning.spec_reference.unwrap().contains("html.spec.whatwg.org"));
110    }
111
112    #[test]
113    fn test_severity_ordering() {
114        assert!(WarningSeverity::Info < WarningSeverity::Warning);
115        assert!(WarningSeverity::Warning < WarningSeverity::RecoveredError);
116    }
117
118    #[test]
119    fn test_parse_result_with_warnings() {
120        let warnings = vec![
121            ParseWarning::new(WarningSeverity::Info, "info"),
122            ParseWarning::new(WarningSeverity::Warning, "warning"),
123            ParseWarning::new(WarningSeverity::RecoveredError, "error"),
124        ];
125        let result = ParseResultWithWarnings::new(42, warnings);
126
127        assert!(result.has_warnings());
128        assert_eq!(result.warnings.len(), 3);
129    }
130
131    #[test]
132    fn test_warnings_filtering() {
133        let warnings = vec![
134            ParseWarning::new(WarningSeverity::Info, "info"),
135            ParseWarning::new(WarningSeverity::Warning, "warning"),
136            ParseWarning::new(WarningSeverity::RecoveredError, "error"),
137        ];
138        let result = ParseResultWithWarnings::new((), warnings);
139
140        let filtered = result.warnings_at_least(WarningSeverity::Warning);
141        assert_eq!(filtered.len(), 2);
142        assert_eq!(filtered[0].message, "warning");
143        assert_eq!(filtered[1].message, "error");
144    }
145
146    #[test]
147    fn test_no_warnings() {
148        let result = ParseResultWithWarnings::new(42, Vec::new());
149        assert!(!result.has_warnings());
150        assert_eq!(result.warnings_at_least(WarningSeverity::Info).len(), 0);
151    }
152}