nginx_lint_parser/
syntax_kind.rs

1//! Syntax kind definitions for the rowan-based nginx config parser.
2//!
3//! Each variant of [`SyntaxKind`] represents either a leaf token or an interior
4//! node in the lossless concrete syntax tree.
5
6/// All token and node kinds used by the nginx configuration parser.
7///
8/// Token kinds (leaf nodes) represent individual lexical elements such as
9/// identifiers, strings, punctuation, and whitespace.  Node kinds (interior
10/// nodes) group tokens into higher-level constructs like directives and blocks.
11///
12/// **Maintenance note:** When adding a new variant, you must also update
13/// `to_raw()`, `from_raw()`, and the `ALL_KINDS` array in the tests.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[allow(non_camel_case_types)]
16pub enum SyntaxKind {
17    // ── Tokens (leaf nodes) ─────────────────────────────────────────
18    /// Horizontal whitespace (spaces / tabs).
19    WHITESPACE = 0,
20    /// A newline character (`\n`).
21    NEWLINE,
22    /// A comment (`# …` through end of line).
23    COMMENT,
24    /// A directive name (alphabetic / `_` start, may contain `-`).
25    IDENT,
26    /// An unquoted argument (numbers, paths, regex fragments, …).
27    ARGUMENT,
28    /// A double-quoted string (`"…"`), including the quotes.
29    DOUBLE_QUOTED_STRING,
30    /// A single-quoted string (`'…'`), including the quotes.
31    SINGLE_QUOTED_STRING,
32    /// A variable reference (`$var` or `${var}`).
33    VARIABLE,
34    /// Semicolon (`;`).
35    SEMICOLON,
36    /// Opening brace (`{`).
37    L_BRACE,
38    /// Closing brace (`}`).
39    R_BRACE,
40    /// Raw content inside a lua-block or similar directive.
41    RAW_CONTENT,
42    /// A token that the lexer could not classify (error recovery).
43    ERROR,
44
45    // ── Composite nodes (interior nodes) ────────────────────────────
46    /// The root node, corresponding to `Config`.
47    ROOT,
48    /// A directive node (name + arguments + optional block).
49    DIRECTIVE,
50    /// A brace-delimited block (`{ … }`).
51    BLOCK,
52    /// A blank line (whitespace-only line).
53    BLANK_LINE,
54}
55
56impl SyntaxKind {
57    /// Returns `true` for trivia tokens (whitespace, newlines, comments).
58    pub fn is_trivia(self) -> bool {
59        matches!(self, Self::WHITESPACE | Self::NEWLINE | Self::COMMENT)
60    }
61
62    /// Convert a `SyntaxKind` to its raw `u16` discriminant.
63    fn to_raw(self) -> u16 {
64        match self {
65            Self::WHITESPACE => 0,
66            Self::NEWLINE => 1,
67            Self::COMMENT => 2,
68            Self::IDENT => 3,
69            Self::ARGUMENT => 4,
70            Self::DOUBLE_QUOTED_STRING => 5,
71            Self::SINGLE_QUOTED_STRING => 6,
72            Self::VARIABLE => 7,
73            Self::SEMICOLON => 8,
74            Self::L_BRACE => 9,
75            Self::R_BRACE => 10,
76            Self::RAW_CONTENT => 11,
77            Self::ERROR => 12,
78            Self::ROOT => 13,
79            Self::DIRECTIVE => 14,
80            Self::BLOCK => 15,
81            Self::BLANK_LINE => 16,
82        }
83    }
84}
85
86/// Converts `SyntaxKind` to a raw `u16` for rowan.
87impl From<SyntaxKind> for rowan::SyntaxKind {
88    fn from(kind: SyntaxKind) -> Self {
89        Self(kind.to_raw())
90    }
91}
92
93/// The language tag used by rowan to parameterise the syntax tree.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub enum NginxLanguage {}
96
97impl SyntaxKind {
98    /// Convert a raw `u16` discriminant back to a `SyntaxKind`.
99    ///
100    /// Panics if the value is out of range.
101    fn from_raw(raw: u16) -> Self {
102        match raw {
103            0 => Self::WHITESPACE,
104            1 => Self::NEWLINE,
105            2 => Self::COMMENT,
106            3 => Self::IDENT,
107            4 => Self::ARGUMENT,
108            5 => Self::DOUBLE_QUOTED_STRING,
109            6 => Self::SINGLE_QUOTED_STRING,
110            7 => Self::VARIABLE,
111            8 => Self::SEMICOLON,
112            9 => Self::L_BRACE,
113            10 => Self::R_BRACE,
114            11 => Self::RAW_CONTENT,
115            12 => Self::ERROR,
116            13 => Self::ROOT,
117            14 => Self::DIRECTIVE,
118            15 => Self::BLOCK,
119            16 => Self::BLANK_LINE,
120            _ => panic!("invalid SyntaxKind raw value: {raw}"),
121        }
122    }
123}
124
125impl rowan::Language for NginxLanguage {
126    type Kind = SyntaxKind;
127
128    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
129        SyntaxKind::from_raw(raw.0)
130    }
131
132    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
133        kind.into()
134    }
135}
136
137/// A node in the nginx configuration syntax tree.
138pub type SyntaxNode = rowan::SyntaxNode<NginxLanguage>;
139/// A token (leaf) in the nginx configuration syntax tree.
140pub type SyntaxToken = rowan::SyntaxToken<NginxLanguage>;
141/// Either a node or a token.
142pub type SyntaxElement = rowan::SyntaxElement<NginxLanguage>;
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use rowan::Language;
148
149    /// All valid `SyntaxKind` variants (excluding `__LAST`).
150    const ALL_KINDS: &[SyntaxKind] = &[
151        SyntaxKind::WHITESPACE,
152        SyntaxKind::NEWLINE,
153        SyntaxKind::COMMENT,
154        SyntaxKind::IDENT,
155        SyntaxKind::ARGUMENT,
156        SyntaxKind::DOUBLE_QUOTED_STRING,
157        SyntaxKind::SINGLE_QUOTED_STRING,
158        SyntaxKind::VARIABLE,
159        SyntaxKind::SEMICOLON,
160        SyntaxKind::L_BRACE,
161        SyntaxKind::R_BRACE,
162        SyntaxKind::RAW_CONTENT,
163        SyntaxKind::ERROR,
164        SyntaxKind::ROOT,
165        SyntaxKind::DIRECTIVE,
166        SyntaxKind::BLOCK,
167        SyntaxKind::BLANK_LINE,
168    ];
169
170    #[test]
171    fn kind_round_trip() {
172        for (raw, &expected) in ALL_KINDS.iter().enumerate() {
173            let kind = SyntaxKind::from_raw(raw as u16);
174            assert_eq!(kind, expected);
175            let rowan_kind: rowan::SyntaxKind = kind.into();
176            assert_eq!(rowan_kind.0, raw as u16);
177            let back = NginxLanguage::kind_from_raw(rowan_kind);
178            assert_eq!(back, kind);
179        }
180    }
181
182    #[test]
183    fn trivia_classification() {
184        assert!(SyntaxKind::WHITESPACE.is_trivia());
185        assert!(SyntaxKind::NEWLINE.is_trivia());
186        assert!(SyntaxKind::COMMENT.is_trivia());
187        assert!(!SyntaxKind::IDENT.is_trivia());
188        assert!(!SyntaxKind::SEMICOLON.is_trivia());
189    }
190}