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