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/// Placeholder name used in recovered AST nodes when the real name could not be parsed.
7pub(crate) const ERROR_PLACEHOLDER: &str = "<error>";
8
9/// A parse error or diagnostic emitted during parsing.
10///
11/// The parser recovers from all errors and always produces a complete AST,
12/// so errors are informational rather than fatal. Each variant carries a
13/// [`Span`] identifying the source location.
14#[derive(Debug, Clone, Error)]
15pub enum ParseError {
16    /// A specific token was expected but a different one was found.
17    #[error("expected {expected}, found {found}")]
18    Expected {
19        expected: Cow<'static, str>,
20        found: TokenKind,
21        span: Span,
22    },
23
24    /// An expression was expected but not found (e.g. empty parentheses).
25    #[error("expected expression")]
26    ExpectedExpression { span: Span },
27
28    /// A statement was expected but not found.
29    #[error("expected statement")]
30    ExpectedStatement { span: Span },
31
32    /// PHP source must start with `<?php` or `<?`.
33    #[error("expected opening PHP tag")]
34    ExpectedOpenTag { span: Span },
35
36    /// A string literal was opened but never closed.
37    #[error("unterminated string literal")]
38    UnterminatedString { span: Span },
39
40    /// A required token was missing after another construct.
41    #[error("expected {expected} after {after}")]
42    ExpectedAfter {
43        expected: Cow<'static, str>,
44        after: Cow<'static, str>,
45        span: Span,
46    },
47
48    /// A delimiter (parenthesis, bracket, brace) was opened but never closed.
49    #[error("unclosed {delimiter} opened at {opened_at:?}")]
50    UnclosedDelimiter {
51        delimiter: Cow<'static, str>,
52        opened_at: Span,
53        span: Span,
54    },
55
56    /// A construct that is syntactically valid but semantically forbidden
57    /// (e.g. `(unset)` cast, deprecated syntax).
58    #[error("{message}")]
59    Forbidden {
60        message: Cow<'static, str>,
61        span: Span,
62    },
63
64    /// Syntax that requires a newer PHP version than the targeted one.
65    /// Emitted by [`crate::parse_versioned`] when the source uses features
66    /// unavailable in the specified [`crate::PhpVersion`].
67    #[error("'{feature}' requires PHP {required} or higher (targeting PHP {used})")]
68    VersionTooLow {
69        feature: Cow<'static, str>,
70        required: Cow<'static, str>,
71        used: Cow<'static, str>,
72        span: Span,
73    },
74}
75
76impl ParseError {
77    pub fn span(&self) -> Span {
78        match self {
79            ParseError::Expected { span, .. }
80            | ParseError::ExpectedExpression { span }
81            | ParseError::ExpectedStatement { span }
82            | ParseError::ExpectedOpenTag { span }
83            | ParseError::UnterminatedString { span }
84            | ParseError::ExpectedAfter { span, .. }
85            | ParseError::UnclosedDelimiter { span, .. }
86            | ParseError::Forbidden { span, .. }
87            | ParseError::VersionTooLow { span, .. } => *span,
88        }
89    }
90}