Skip to main content

php_rs_parser/
diagnostics.rs

1use php_ast::Span;
2use php_lexer::TokenKind;
3use std::borrow::Cow;
4use thiserror::Error;
5
6/// Diagnostic severity. Mirrors `php -l`'s split between fatal errors and
7/// warnings (e.g. `final private method` is a PHP warning, not a fatal).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum Severity {
10    #[default]
11    Error,
12    Warning,
13}
14
15/// A parse error or diagnostic emitted during parsing.
16///
17/// The parser recovers from all errors and always produces a complete AST,
18/// so errors are informational rather than fatal. Each variant carries a
19/// [`Span`] identifying the source location.
20#[derive(Debug, Clone, Error)]
21pub enum ParseError {
22    /// A specific token was expected but a different one was found.
23    #[error("expected {expected}, found {found}")]
24    Expected {
25        expected: Cow<'static, str>,
26        found: TokenKind,
27        span: Span,
28    },
29
30    /// An expression was expected but not found (e.g. empty parentheses).
31    #[error("expected expression")]
32    ExpectedExpression { span: Span },
33
34    /// A statement was expected but not found.
35    #[error("expected statement")]
36    ExpectedStatement { span: Span },
37
38    /// PHP source must start with `<?php` or `<?`.
39    #[error("expected opening PHP tag")]
40    ExpectedOpenTag { span: Span },
41
42    /// A string literal was opened but never closed.
43    #[error("unterminated string literal")]
44    UnterminatedString { span: Span },
45
46    /// A required token was missing after another construct.
47    #[error("expected {expected} after {after}")]
48    ExpectedAfter {
49        expected: Cow<'static, str>,
50        after: Cow<'static, str>,
51        span: Span,
52    },
53
54    /// A delimiter (parenthesis, bracket, brace) was opened but never closed.
55    #[error("unclosed {delimiter} opened at {opened_at:?}")]
56    UnclosedDelimiter {
57        delimiter: Cow<'static, str>,
58        opened_at: Span,
59        span: Span,
60    },
61
62    /// A construct that is syntactically valid but semantically forbidden
63    /// (e.g. `(unset)` cast, deprecated syntax). Equivalent to a PHP fatal.
64    #[error("{message}")]
65    Forbidden {
66        message: Cow<'static, str>,
67        span: Span,
68    },
69
70    /// A construct PHP only warns about (e.g. `final private` method). Treated
71    /// as a non-fatal diagnostic; `severity()` returns [`Severity::Warning`].
72    #[error("{message}")]
73    ForbiddenWarning {
74        message: Cow<'static, str>,
75        span: Span,
76    },
77
78    /// Syntax that requires a newer PHP version than the targeted one.
79    /// Emitted by [`crate::parse_versioned`] when the source uses features
80    /// unavailable in the specified [`crate::PhpVersion`].
81    #[error("'{feature}' requires PHP {required} or higher (targeting PHP {used})")]
82    VersionTooLow {
83        feature: Cow<'static, str>,
84        required: Cow<'static, str>,
85        used: Cow<'static, str>,
86        span: Span,
87    },
88}
89
90impl ParseError {
91    pub fn span(&self) -> Span {
92        match self {
93            ParseError::Expected { span, .. }
94            | ParseError::ExpectedExpression { span }
95            | ParseError::ExpectedStatement { span }
96            | ParseError::ExpectedOpenTag { span }
97            | ParseError::UnterminatedString { span }
98            | ParseError::ExpectedAfter { span, .. }
99            | ParseError::UnclosedDelimiter { span, .. }
100            | ParseError::Forbidden { span, .. }
101            | ParseError::ForbiddenWarning { span, .. }
102            | ParseError::VersionTooLow { span, .. } => *span,
103        }
104    }
105
106    /// Returns the diagnostic severity. Currently only [`ParseError::ForbiddenWarning`]
107    /// is at warning level; every other variant is an error.
108    pub fn severity(&self) -> Severity {
109        match self {
110            ParseError::ForbiddenWarning { .. } => Severity::Warning,
111            _ => Severity::Error,
112        }
113    }
114}