Skip to main content

sql_dialect_fmt_syntax/
kind.rs

1//! The [`SyntaxKind`] enumeration and its `u16` conversions / predicates.
2
3/// Every lexical token kind and syntax node kind understood by sql-dialect-fmt.
4///
5/// Ordering is significant: variants are contiguous from `0`, which makes
6/// [`SyntaxKind::from_u16`] a checked `transmute`. The `__KW_START` / `__KW_END`
7/// sentinels bracket the keyword block so [`SyntaxKind::is_keyword`] is a range check.
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
9#[repr(u16)]
10pub enum SyntaxKind {
11    // ---- Trivia (preserved verbatim in the lossless tree) ----
12    WHITESPACE = 0,
13    NEWLINE,
14    COMMENT,       // -- line  or  // line
15    BLOCK_COMMENT, // /* ... */
16
17    // ---- Literals & names ----
18    IDENT,         // unquoted identifier (also covers un-resolved keywords; see keyword_kind)
19    QUOTED_IDENT,  // "quoted identifier"
20    STRING,        // 'string literal'
21    DOLLAR_STRING, // delimited body token; current Snowflake delimiter is $$ ... $$
22    INT_NUMBER,
23    FLOAT_NUMBER,
24    VARIABLE, // $1, $42 (positional)  or  $name (session/binding)
25
26    // ---- Punctuation & operators ----
27    L_PAREN,   // (
28    R_PAREN,   // )
29    L_BRACKET, // [
30    R_BRACKET, // ]
31    L_BRACE,   // {
32    R_BRACE,   // }
33    COMMA,     // ,
34    DOT,       // .
35    SEMICOLON, // ;
36    COLON,     // :   (semi-structured path access, named args in some dialects)
37    COLON2,    // ::  (cast)
38    ASSIGN,    // :=  (Snowflake Scripting assignment)
39    EQ,        // =
40    NEQ,       // <> or !=
41    LT,        // <
42    LTE,       // <=
43    GT,        // >
44    GTE,       // >=
45    PLUS,      // +
46    MINUS,     // -
47    STAR,      // *
48    SLASH,     // /
49    PERCENT,   // %
50    CONCAT,    // ||
51    PIPE,      // |
52    PIPE_GT,   // |>  (GoogleSQL-style pipe; kept for compatibility and corpus coverage)
53    FLOW_PIPE, // ->> (Snowflake flow / pipe operator)
54    ARROW,     // ->  (lambda)
55    FAT_ARROW, // =>  (named argument)
56    AMP,       // &
57    CARET,     // ^
58    TILDE,     // ~
59    AT,        // @   (stage reference)
60    DOLLAR,    // $   (lone dollar, not a variable or $$ )
61    QUESTION,  // ?   (bind marker)
62    BANG,      // !   (only valid as part of !=; standalone is an error)
63
64    // ---- Keywords (case-insensitive; recognized via keyword_kind) ----
65    // NOTE: this is an intentionally partial but representative set covering the SELECT
66    // pipeline, common DDL/DML, Snowflake Scripting, and embedded-language declarations.
67    // It will grow phase-by-phase as parser coverage expands.
68    #[doc(hidden)]
69    __KW_START,
70    SELECT_KW,
71    FROM_KW,
72    WHERE_KW,
73    GROUP_KW,
74    BY_KW,
75    HAVING_KW,
76    ORDER_KW,
77    LIMIT_KW,
78    OFFSET_KW,
79    FETCH_KW,
80    TOP_KW,
81    AS_KW,
82    AND_KW,
83    OR_KW,
84    NOT_KW,
85    NULL_KW,
86    IS_KW,
87    IN_KW,
88    LIKE_KW,
89    ILIKE_KW,
90    RLIKE_KW,
91    REGEXP_KW,
92    BETWEEN_KW,
93    CASE_KW,
94    WHEN_KW,
95    THEN_KW,
96    ELSE_KW,
97    END_KW,
98    JOIN_KW,
99    INNER_KW,
100    LEFT_KW,
101    RIGHT_KW,
102    FULL_KW,
103    OUTER_KW,
104    CROSS_KW,
105    LATERAL_KW,
106    NATURAL_KW,
107    ON_KW,
108    USING_KW,
109    WITH_KW,
110    RECURSIVE_KW,
111    UNION_KW,
112    ALL_KW,
113    ANY_KW,
114    EXCEPT_KW,
115    INTERSECT_KW,
116    MINUS_KW,
117    DISTINCT_KW,
118    QUALIFY_KW,
119    OVER_KW,
120    PARTITION_KW,
121    WINDOW_KW,
122    ROWS_KW,
123    RANGE_KW,
124    UNBOUNDED_KW,
125    PRECEDING_KW,
126    FOLLOWING_KW,
127    CURRENT_KW,
128    ROW_KW,
129    ASC_KW,
130    DESC_KW,
131    NULLS_KW,
132    FIRST_KW,
133    LAST_KW,
134    TRUE_KW,
135    FALSE_KW,
136    CAST_KW,
137    TRY_CAST_KW,
138    EXISTS_KW,
139    VALUES_KW,
140    PIVOT_KW,
141    UNPIVOT_KW,
142    SAMPLE_KW,
143    TABLESAMPLE_KW,
144    CREATE_KW,
145    REPLACE_KW,
146    IF_KW,
147    TABLE_KW,
148    VIEW_KW,
149    TEMPORARY_KW,
150    TEMP_KW,
151    TRANSIENT_KW,
152    VOLATILE_KW,
153    SECURE_KW,
154    INSERT_KW,
155    INTO_KW,
156    UPDATE_KW,
157    DELETE_KW,
158    MERGE_KW,
159    SET_KW,
160    FLATTEN_KW,
161    CONNECT_KW,
162    START_KW,
163    PRIOR_KW,
164    LANGUAGE_KW,
165    JAVASCRIPT_KW,
166    PYTHON_KW,
167    JAVA_KW,
168    SCALA_KW,
169    SQL_KW,
170    BEGIN_KW,
171    DECLARE_KW,
172    LET_KW,
173    RETURN_KW,
174    CALL_KW,
175    PROCEDURE_KW,
176    FUNCTION_KW,
177    RETURNS_KW,
178    TASK_KW,
179    WAREHOUSE_KW,
180    SCHEDULE_KW,
181    AFTER_KW,
182    COPY_KW,
183    GRANTS_KW,
184    HANDLER_KW,
185    PACKAGES_KW,
186    IMPORTS_KW,
187    RUNTIME_VERSION_KW,
188    EXECUTE_KW,
189    OWNER_KW,
190    CALLER_KW,
191    STRICT_KW,
192    CALLED_KW,
193    INPUT_KW,
194    OUTPUT_KW,
195    OUT_KW,
196    MATCHED_KW,
197    DROP_KW,
198    ALTER_KW,
199    WITHIN_KW,
200    FOR_KW,
201    IMMEDIATE_KW,
202    OVERWRITE_KW,
203    GRANT_KW,
204    REVOKE_KW,
205    USE_KW,
206    SHOW_KW,
207    DESCRIBE_KW,
208    TRUNCATE_KW,
209    COMMIT_KW,
210    ROLLBACK_KW,
211    UNDROP_KW,
212    ELSEIF_KW,
213    WHILE_KW,
214    LOOP_KW,
215    REPEAT_KW,
216    UNTIL_KW,
217    DO_KW,
218    EXCEPTION_KW,
219    CURSOR_KW,
220    RESULTSET_KW,
221    #[doc(hidden)]
222    __KW_END,
223
224    // ---- Node kinds ----
225    SOURCE_FILE,
226    ERROR,
227    EOF,
228    /// A *soft* (contextual) keyword token: a word that the grammar recognized as a keyword in a
229    /// specific position (e.g. `ASOF`, `MATCH_RECOGNIZE`, `AT`/`BEFORE`, `GROUPING SETS`) but that
230    /// is **not** reserved — elsewhere it is an ordinary identifier. Tagged via `bump_as`, it sits
231    /// outside the keyword range (so it never reserves the word) yet lets the formatter upper-case
232    /// it and the highlighter colour it like a keyword. See `parser::ContextualKeyword`.
233    CONTEXTUAL_KEYWORD,
234    // statements
235    SELECT_STMT,
236    EXPR_STMT,
237    // clauses & fragments
238    SELECT_LIST,
239    SELECT_ITEM,
240    FROM_CLAUSE,
241    WHERE_CLAUSE,
242    TABLE_REF,
243    LATERAL_VIEW,
244    AS_OF_TRAVEL,
245    ARG_LIST,
246    TYPE_NAME,
247    NAME,
248    NAME_REF,
249    // expressions
250    LITERAL,
251    STAR_EXPR,
252    PAREN_EXPR,
253    PREFIX_EXPR,
254    BIN_EXPR,
255    CALL_EXPR,
256    INDEX_EXPR,
257    CAST_EXPR,
258    // queries & set operations (Phase 2)
259    WITH_QUERY,
260    WITH_CLAUSE,
261    CTE,
262    COLUMN_LIST,
263    SET_OP,
264    SUBQUERY,
265    // clauses (Phase 2)
266    GROUP_BY_CLAUSE,
267    HAVING_CLAUSE,
268    QUALIFY_CLAUSE,
269    ORDER_BY_CLAUSE,
270    ORDER_BY_ITEM,
271    LIMIT_CLAUSE,
272    OFFSET_CLAUSE,
273    JOIN,
274    // predicates (Phase 2)
275    IS_EXPR,
276    IN_EXPR,
277    BETWEEN_EXPR,
278    EXISTS_EXPR,
279    EXPR_LIST,
280    // window functions (Phase 2)
281    WINDOW_EXPR,
282    WINDOW_SPEC,
283    PARTITION_BY_CLAUSE,
284    WINDOW_FRAME,
285    // Phase 2b: CASE / CAST(...) / semi-structured path / VALUES
286    CASE_EXPR,
287    CASE_WHEN,
288    JSON_ACCESS,
289    LAMBDA_EXPR,
290    LAMBDA_PARAMS,
291    VALUES_CLAUSE,
292    VALUES_ROW,
293    // Phase 6: DML statements
294    INSERT_STMT,
295    UPDATE_STMT,
296    DELETE_STMT,
297    MERGE_STMT,
298    SET_CLAUSE,
299    ASSIGNMENT,
300    MERGE_WHEN,
301    // Phase 7: DDL statements
302    CREATE_STMT,
303    DROP_STMT,
304    ALTER_STMT,
305    GRANT_STMT,
306    REVOKE_STMT,
307    CALL_STMT,
308    USE_STMT,
309    SHOW_STMT,
310    DESCRIBE_STMT,
311    TRUNCATE_STMT,
312    COMMENT_STMT,
313    TRANSACTION_STMT,
314    UNDROP_STMT,
315    // Phase 8: Snowflake Scripting blocks
316    BLOCK_STMT,
317    DECLARE_SECTION,
318    DECLARE_ITEM,
319    STMT_LIST,
320    EXCEPTION_SECTION,
321    EXCEPTION_WHEN,
322    LET_STMT,
323    ASSIGN_STMT,
324    RETURN_STMT,
325    IF_STMT,
326    LOOP_STMT,
327    /// A procedural `CASE … END [CASE]` statement inside a block, distinct from `CASE_EXPR`.
328    CASE_STMT,
329    /// One `WHEN <test> THEN <stmts>` arm of a procedural `CASE_STMT`.
330    CASE_STMT_WHEN,
331    SCRIPT_STMT,
332    COLUMN_DEF_LIST,
333    COLUMN_DEF,
334    // Phase 4: Snowflake query extensions
335    WITHIN_GROUP,
336    PIVOT_CLAUSE,
337    NAMED_ARG,
338    MATCH_RECOGNIZE,
339    // MATCH_RECOGNIZE body sub-clauses
340    MEASURES_CLAUSE,
341    ROW_MATCH_CLAUSE,
342    AFTER_MATCH_CLAUSE,
343    PATTERN_CLAUSE,
344    PATTERN_BODY,
345    SUBSET_CLAUSE,
346    DEFINE_CLAUSE,
347    DEFINE_ITEM,
348    // Hierarchical queries
349    START_WITH_CLAUSE,
350    CONNECT_BY_CLAUSE,
351    // Flow / pipe operator: a chain of statements joined by `->>`
352    FLOW_STMT,
353    // Phase 8 / scripting-adjacent statements
354    SET_STMT,
355    EXECUTE_STMT,
356    GROUPING_SETS,
357    // Phase 6: COPY INTO
358    COPY_STMT,
359    COPY_LOCATION,
360    COPY_OPTION,
361    // A `@stage[/path]` reference used as a table/source (e.g. `FROM @s/p`, `COPY ... FROM @s`).
362    STAGE_REF,
363    // Phase 6: multi-table INSERT
364    INTO_CLAUSE,
365    INSERT_WHEN,
366    // Phase 7: structural object DDL (CREATE SCHEMA/DATABASE/WAREHOUSE/STAGE/FILE FORMAT/SEQUENCE/
367    // STREAM/TASK/DYNAMIC TABLE) and access control (GRANT/REVOKE).
368    OBJECT_PROPERTY, // one `KEY = value`, `KEY = ( ... )`, or bare flag word property
369    STREAM_SOURCE,   // a stream's `ON { TABLE | VIEW | STAGE } <name> [AT|BEFORE ( ... )]` source
370    TASK_AFTER,      // a task's `AFTER <pred> [, <pred>]*` predecessor list
371    SEMANTIC_VIEW_CLAUSE, // a top-level `CREATE SEMANTIC VIEW` clause (`TABLES (...)`, `METRICS (...)`, ...)
372    SEMANTIC_VIEW_ITEM,   // one top-level item inside a semantic-view parenthesized clause
373    PRIV_LIST, // the privilege list of a GRANT/REVOKE (`SELECT, INSERT` / `ALL PRIVILEGES`)
374    GRANT_TARGET, // the `ON <object_type> <object_name>` securable of a GRANT/REVOKE
375    GRANTEE,   // the `[ROLE|USER] <name>` recipient of a GRANT/REVOKE
376
377    // Databricks / Delta maintenance + cache statements (recognized only under the Databricks
378    // dialect; the leading words stay ordinary identifiers under Snowflake).
379    VACUUM_STMT,           // `VACUUM <table|path> [RETAIN n HOURS] [DRY RUN]`
380    OPTIMIZE_STMT,         // `OPTIMIZE <table> [WHERE p] [ZORDER BY (cols)]`
381    ZORDER_CLAUSE,         // the `ZORDER BY ( col [, ...] )` tail of an OPTIMIZE
382    CACHE_STMT,            // `CACHE [LAZY] TABLE <t> [OPTIONS (...)] [[AS] <query>]`
383    UNCACHE_STMT,          // `UNCACHE TABLE [IF EXISTS] <t>`
384    REFRESH_STMT,          // `REFRESH [TABLE] <t>` / `REFRESH <path>`
385    DESCRIBE_HISTORY_STMT, // `DESCRIBE HISTORY <table>` (Delta change history)
386
387    #[doc(hidden)]
388    __LAST,
389}
390
391impl SyntaxKind {
392    /// Raw `u16` discriminant (as stored by the rowan green tree).
393    #[inline]
394    pub const fn to_u16(self) -> u16 {
395        self as u16
396    }
397
398    /// Reconstruct a [`SyntaxKind`] from its raw discriminant.
399    ///
400    /// # Panics
401    /// Panics if `raw` is out of range. Because the enum is contiguous and `#[repr(u16)]`,
402    /// any in-range value corresponds to a real variant, so the `transmute` is sound.
403    #[inline]
404    pub fn from_u16(raw: u16) -> SyntaxKind {
405        assert!(
406            raw <= SyntaxKind::__LAST as u16,
407            "SyntaxKind out of range: {raw}"
408        );
409        // SAFETY: variants are contiguous `0..=__LAST` with `#[repr(u16)]`, and we just
410        // bounds-checked `raw`, so it names a valid discriminant.
411        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw) }
412    }
413
414    /// Whitespace, newlines, and comments — preserved but ignored by the grammar.
415    #[inline]
416    pub const fn is_trivia(self) -> bool {
417        matches!(
418            self,
419            SyntaxKind::WHITESPACE
420                | SyntaxKind::NEWLINE
421                | SyntaxKind::COMMENT
422                | SyntaxKind::BLOCK_COMMENT
423        )
424    }
425
426    /// `--` / `//` line comments and `/* */` block comments.
427    #[inline]
428    pub const fn is_comment(self) -> bool {
429        matches!(self, SyntaxKind::COMMENT | SyntaxKind::BLOCK_COMMENT)
430    }
431
432    /// True for any reserved/keyword kind (the block between the sentinels).
433    #[inline]
434    pub fn is_keyword(self) -> bool {
435        let v = self as u16;
436        v > SyntaxKind::__KW_START as u16 && v < SyntaxKind::__KW_END as u16
437    }
438
439    /// A human-readable name for this kind, for diagnostics.
440    ///
441    /// Where the `Debug` representation reads `SyntaxKind::INTO_KW` (useless to a SQL author),
442    /// this reads `INTO`; punctuation is quoted (`'('`), and the literal/name kinds get a phrase
443    /// (`an identifier`, `a string literal`). Keyword kinds derive their text from the variant
444    /// name by stripping the trailing `_KW`, so every keyword is covered without a per-variant arm.
445    pub fn describe(self) -> &'static str {
446        use SyntaxKind::*;
447        match self {
448            // Literals & names.
449            IDENT => "an identifier",
450            QUOTED_IDENT => "a quoted identifier",
451            STRING => "a string literal",
452            DOLLAR_STRING => "a dollar-quoted string",
453            INT_NUMBER => "an integer literal",
454            FLOAT_NUMBER => "a number literal",
455            VARIABLE => "a variable",
456            // Punctuation & operators (quoted so the symbol is unambiguous in a sentence).
457            L_PAREN => "'('",
458            R_PAREN => "')'",
459            L_BRACKET => "'['",
460            R_BRACKET => "']'",
461            L_BRACE => "'{'",
462            R_BRACE => "'}'",
463            COMMA => "','",
464            DOT => "'.'",
465            SEMICOLON => "';'",
466            COLON => "':'",
467            COLON2 => "'::'",
468            ASSIGN => "':='",
469            EQ => "'='",
470            NEQ => "'<>'",
471            LT => "'<'",
472            LTE => "'<='",
473            GT => "'>'",
474            GTE => "'>='",
475            PLUS => "'+'",
476            MINUS => "'-'",
477            STAR => "'*'",
478            SLASH => "'/'",
479            PERCENT => "'%'",
480            CONCAT => "'||'",
481            PIPE => "'|'",
482            PIPE_GT => "'|>'",
483            FLOW_PIPE => "'->>'",
484            ARROW => "'->'",
485            FAT_ARROW => "'=>'",
486            AMP => "'&'",
487            CARET => "'^'",
488            TILDE => "'~'",
489            AT => "'@'",
490            DOLLAR => "'$'",
491            QUESTION => "'?'",
492            BANG => "'!'",
493            // Trivia / structural.
494            WHITESPACE => "whitespace",
495            NEWLINE => "a newline",
496            COMMENT => "a comment",
497            BLOCK_COMMENT => "a block comment",
498            EOF => "end of input",
499            CONTEXTUAL_KEYWORD => "a keyword",
500            // Keywords: derive the bare spelling (`INTO_KW` -> `INTO`) from the variant name.
501            kind if kind.is_keyword() => kind.keyword_word(),
502            // Any node kind reached here (should not happen on an error path).
503            _ => "input",
504        }
505    }
506
507    /// The bare keyword spelling for a keyword kind (`INTO_KW` -> `INTO`), derived from the
508    /// variant's `Debug` name with the trailing `_KW` removed. Returns `""` for non-keywords.
509    fn keyword_word(self) -> &'static str {
510        macro_rules! kw {
511            ($($variant:ident => $word:literal,)*) => {
512                match self {
513                    $(SyntaxKind::$variant => $word,)*
514                    _ => "",
515                }
516            };
517        }
518        kw! {
519            SELECT_KW => "SELECT", FROM_KW => "FROM", WHERE_KW => "WHERE", GROUP_KW => "GROUP",
520            BY_KW => "BY", HAVING_KW => "HAVING", ORDER_KW => "ORDER", LIMIT_KW => "LIMIT",
521            OFFSET_KW => "OFFSET", FETCH_KW => "FETCH", TOP_KW => "TOP", AS_KW => "AS",
522            AND_KW => "AND", OR_KW => "OR", NOT_KW => "NOT", NULL_KW => "NULL", IS_KW => "IS",
523            IN_KW => "IN", LIKE_KW => "LIKE", ILIKE_KW => "ILIKE", RLIKE_KW => "RLIKE",
524            REGEXP_KW => "REGEXP", BETWEEN_KW => "BETWEEN", CASE_KW => "CASE", WHEN_KW => "WHEN",
525            THEN_KW => "THEN", ELSE_KW => "ELSE", END_KW => "END", JOIN_KW => "JOIN",
526            INNER_KW => "INNER", LEFT_KW => "LEFT", RIGHT_KW => "RIGHT", FULL_KW => "FULL",
527            OUTER_KW => "OUTER", CROSS_KW => "CROSS", LATERAL_KW => "LATERAL",
528            NATURAL_KW => "NATURAL", ON_KW => "ON", USING_KW => "USING", WITH_KW => "WITH",
529            RECURSIVE_KW => "RECURSIVE", UNION_KW => "UNION", ALL_KW => "ALL", ANY_KW => "ANY",
530            EXCEPT_KW => "EXCEPT", INTERSECT_KW => "INTERSECT", MINUS_KW => "MINUS",
531            DISTINCT_KW => "DISTINCT", QUALIFY_KW => "QUALIFY", OVER_KW => "OVER",
532            PARTITION_KW => "PARTITION", WINDOW_KW => "WINDOW", ROWS_KW => "ROWS",
533            RANGE_KW => "RANGE", UNBOUNDED_KW => "UNBOUNDED", PRECEDING_KW => "PRECEDING",
534            FOLLOWING_KW => "FOLLOWING", CURRENT_KW => "CURRENT", ROW_KW => "ROW", ASC_KW => "ASC",
535            DESC_KW => "DESC", NULLS_KW => "NULLS", FIRST_KW => "FIRST", LAST_KW => "LAST",
536            TRUE_KW => "TRUE", FALSE_KW => "FALSE", CAST_KW => "CAST", TRY_CAST_KW => "TRY_CAST",
537            EXISTS_KW => "EXISTS", VALUES_KW => "VALUES", PIVOT_KW => "PIVOT",
538            UNPIVOT_KW => "UNPIVOT", SAMPLE_KW => "SAMPLE", TABLESAMPLE_KW => "TABLESAMPLE",
539            CREATE_KW => "CREATE", REPLACE_KW => "REPLACE", IF_KW => "IF", TABLE_KW => "TABLE",
540            VIEW_KW => "VIEW", TEMPORARY_KW => "TEMPORARY", TEMP_KW => "TEMP",
541            TRANSIENT_KW => "TRANSIENT", VOLATILE_KW => "VOLATILE", SECURE_KW => "SECURE",
542            INSERT_KW => "INSERT", INTO_KW => "INTO", UPDATE_KW => "UPDATE", DELETE_KW => "DELETE",
543            MERGE_KW => "MERGE", SET_KW => "SET", FLATTEN_KW => "FLATTEN", CONNECT_KW => "CONNECT",
544            START_KW => "START", PRIOR_KW => "PRIOR", LANGUAGE_KW => "LANGUAGE",
545            JAVASCRIPT_KW => "JAVASCRIPT", PYTHON_KW => "PYTHON", JAVA_KW => "JAVA",
546            SCALA_KW => "SCALA", SQL_KW => "SQL", BEGIN_KW => "BEGIN", DECLARE_KW => "DECLARE",
547            LET_KW => "LET", RETURN_KW => "RETURN", CALL_KW => "CALL", PROCEDURE_KW => "PROCEDURE",
548            FUNCTION_KW => "FUNCTION", RETURNS_KW => "RETURNS", TASK_KW => "TASK",
549            WAREHOUSE_KW => "WAREHOUSE", SCHEDULE_KW => "SCHEDULE", AFTER_KW => "AFTER",
550            COPY_KW => "COPY", GRANTS_KW => "GRANTS", HANDLER_KW => "HANDLER",
551            PACKAGES_KW => "PACKAGES", IMPORTS_KW => "IMPORTS",
552            RUNTIME_VERSION_KW => "RUNTIME_VERSION", EXECUTE_KW => "EXECUTE", OWNER_KW => "OWNER",
553            CALLER_KW => "CALLER", STRICT_KW => "STRICT", CALLED_KW => "CALLED",
554            INPUT_KW => "INPUT", OUTPUT_KW => "OUTPUT", OUT_KW => "OUT", MATCHED_KW => "MATCHED",
555            DROP_KW => "DROP", ALTER_KW => "ALTER", WITHIN_KW => "WITHIN", FOR_KW => "FOR",
556            IMMEDIATE_KW => "IMMEDIATE", OVERWRITE_KW => "OVERWRITE", GRANT_KW => "GRANT",
557            REVOKE_KW => "REVOKE", USE_KW => "USE", SHOW_KW => "SHOW", DESCRIBE_KW => "DESCRIBE",
558            TRUNCATE_KW => "TRUNCATE", COMMIT_KW => "COMMIT", ROLLBACK_KW => "ROLLBACK",
559            UNDROP_KW => "UNDROP", ELSEIF_KW => "ELSEIF", WHILE_KW => "WHILE", LOOP_KW => "LOOP",
560            REPEAT_KW => "REPEAT", UNTIL_KW => "UNTIL", DO_KW => "DO", EXCEPTION_KW => "EXCEPTION",
561            CURSOR_KW => "CURSOR", RESULTSET_KW => "RESULTSET",
562        }
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::SyntaxKind;
569
570    #[test]
571    fn keyword_and_trivia_predicates() {
572        assert!(SyntaxKind::SELECT_KW.is_keyword());
573        assert!(SyntaxKind::QUALIFY_KW.is_keyword());
574        assert!(!SyntaxKind::IDENT.is_keyword());
575        assert!(!SyntaxKind::PIPE_GT.is_keyword());
576        assert!(SyntaxKind::WHITESPACE.is_trivia());
577        assert!(SyntaxKind::BLOCK_COMMENT.is_trivia());
578        assert!(SyntaxKind::COMMENT.is_comment());
579        assert!(!SyntaxKind::IDENT.is_trivia());
580    }
581
582    #[test]
583    fn u16_roundtrip_is_total() {
584        // Confirms the enum is contiguous and from_u16/to_u16 are inverses for every kind.
585        for raw in 0..=SyntaxKind::__LAST.to_u16() {
586            let kind = SyntaxKind::from_u16(raw);
587            assert_eq!(kind.to_u16(), raw);
588        }
589    }
590
591    #[test]
592    #[should_panic]
593    fn from_u16_out_of_range_panics() {
594        let _ = SyntaxKind::from_u16(u16::MAX);
595    }
596
597    #[test]
598    fn describe_is_human_readable() {
599        assert_eq!(SyntaxKind::INTO_KW.describe(), "INTO");
600        assert_eq!(SyntaxKind::SELECT_KW.describe(), "SELECT");
601        assert_eq!(SyntaxKind::L_PAREN.describe(), "'('");
602        assert_eq!(SyntaxKind::FAT_ARROW.describe(), "'=>'");
603        assert_eq!(SyntaxKind::IDENT.describe(), "an identifier");
604        assert_eq!(SyntaxKind::STRING.describe(), "a string literal");
605        assert_eq!(SyntaxKind::EOF.describe(), "end of input");
606    }
607
608    #[test]
609    fn describe_covers_every_keyword() {
610        // Every keyword kind must yield a non-empty spelling; a new keyword added to the enum
611        // without a `describe` arm would fall through to "" and fail here.
612        for raw in (SyntaxKind::__KW_START as u16 + 1)..(SyntaxKind::__KW_END as u16) {
613            let kind = SyntaxKind::from_u16(raw);
614            assert!(kind.is_keyword());
615            assert!(
616                !kind.describe().is_empty(),
617                "{kind:?} has no describe() text"
618            );
619        }
620    }
621}