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