Skip to main content

sqruff_lsp/
semantic.rs

1//! Builds LSP semantic tokens from sqruff's concrete syntax tree.
2//!
3//! sqruff's parser already disambiguates the grammar while parsing, emitting a
4//! distinct [`SyntaxKind`] for each leaf segment (keyword, literal, operator,
5//! function name, ...). That means highlighting reduces to a flat
6//! `SyntaxKind -> Highlight` lookup ([`classify`]) over the leaf segments, with
7//! no separate query language. Kinds without syntax highlighting are explicitly
8//! listed so new dialect kinds require a deliberate classification.
9
10use lsp_types::{SemanticToken, SemanticTokenType, SemanticTokensLegend};
11use sqruff_lib::core::linter::core::Linter;
12use sqruff_lib_core::dialects::syntax::SyntaxKind;
13use sqruff_lib_core::parser::segments::{ErasedSegment, Tables};
14
15/// The highlight buckets we collapse the ~1000 [`SyntaxKind`] variants into.
16///
17/// The order of this list defines the indices used on the wire, and it must
18/// stay in sync with [`legend`] (which maps each bucket to an LSP
19/// [`SemanticTokenType`]).
20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
21pub(crate) enum Highlight {
22    Keyword,
23    String,
24    Number,
25    Comment,
26    Operator,
27    Function,
28    Type,
29    Variable,
30    Parameter,
31    Property,
32    Macro,
33}
34
35/// All highlight buckets, in wire order.
36const HIGHLIGHTS: [Highlight; 11] = [
37    Highlight::Keyword,
38    Highlight::String,
39    Highlight::Number,
40    Highlight::Comment,
41    Highlight::Operator,
42    Highlight::Function,
43    Highlight::Type,
44    Highlight::Variable,
45    Highlight::Parameter,
46    Highlight::Property,
47    Highlight::Macro,
48];
49
50impl Highlight {
51    /// Index of this bucket in the legend / on the wire.
52    fn token_type(self) -> u32 {
53        HIGHLIGHTS.iter().position(|&h| h == self).unwrap() as u32
54    }
55
56    fn semantic_token_type(self) -> SemanticTokenType {
57        match self {
58            Highlight::Keyword => SemanticTokenType::KEYWORD,
59            Highlight::String => SemanticTokenType::STRING,
60            Highlight::Number => SemanticTokenType::NUMBER,
61            Highlight::Comment => SemanticTokenType::COMMENT,
62            Highlight::Operator => SemanticTokenType::OPERATOR,
63            Highlight::Function => SemanticTokenType::FUNCTION,
64            Highlight::Type => SemanticTokenType::TYPE,
65            Highlight::Variable => SemanticTokenType::VARIABLE,
66            Highlight::Parameter => SemanticTokenType::PARAMETER,
67            Highlight::Property => SemanticTokenType::PROPERTY,
68            Highlight::Macro => SemanticTokenType::MACRO,
69        }
70    }
71}
72
73/// The legend advertised at initialize time. The token type ordering matches
74/// [`HIGHLIGHTS`]; we expose no modifiers yet.
75pub fn legend() -> SemanticTokensLegend {
76    SemanticTokensLegend {
77        token_types: HIGHLIGHTS.iter().map(|h| h.semantic_token_type()).collect(),
78        token_modifiers: Vec::new(),
79    }
80}
81
82/// Map a leaf [`SyntaxKind`] to a highlight bucket, or `None` to leave it
83/// un-highlighted.
84pub(crate) fn classify(kind: SyntaxKind) -> Option<Highlight> {
85    use SyntaxKind::*;
86
87    let highlight = match kind {
88        // Keywords and keyword-like constants.
89        Keyword | BareFunction | NullLiteral | BooleanLiteral | DatePart | DatePartWeek => {
90            Highlight::Keyword
91        }
92
93        // Numeric literals.
94        NumericLiteral | IntegerLiteral | DollarNumericLiteral | BitStringLiteral => {
95            Highlight::Number
96        }
97
98        // String / quoted literals.
99        QuotedLiteral
100        | RawQuotedLiteral
101        | BytesQuotedLiteral
102        | SignedQuotedLiteral
103        | DateConstructorLiteral
104        | FileLiteral
105        | DollarLiteral
106        | AtSignLiteral => Highlight::String,
107
108        // Comments.
109        Comment | InlineComment | BlockComment => Highlight::Comment,
110
111        // Operators.
112        BinaryOperator
113        | ComparisonOperator
114        | RawComparisonOperator
115        | AssignmentOperator
116        | CastingOperator
117        | LikeOperator
118        | WalrusOperator
119        | JsonOperator
120        | ParameterAssigner
121        | FunctionAssigner
122        | SignIndicator
123        | Plus
124        | Minus
125        | Divide
126        | DoubleDivide
127        | Star
128        | Percent
129        | Caret
130        | Tilde
131        | Ampersand
132        | Pipe
133        | VerticalBar
134        | Not
135        | RightArrow
136        | Lambda
137        | Dash => Highlight::Operator,
138
139        // Function / procedure names.
140        FunctionNameIdentifier | ProcedureNameIdentifier | SystemFunctionName => {
141            Highlight::Function
142        }
143
144        // Type names.
145        DataTypeIdentifier | PrimitiveType => Highlight::Type,
146
147        // Parameters.
148        Parameter => Highlight::Parameter,
149
150        // Variables / placeholders.
151        Variable | TsqlVariable | Placeholder => Highlight::Macro,
152
153        // Property-style identifiers.
154        PropertyNameIdentifier | PropertiesNakedIdentifier | WidgetNameIdentifier => {
155            Highlight::Property
156        }
157
158        // Generic identifiers (column / table / object references resolve to
159        // these at the leaf level).
160        NakedIdentifier | QuotedIdentifier | Identifier | NakedIdentifierAll => Highlight::Variable,
161
162        // Explicitly un-highlighted (punctuation, brackets, structural nodes,
163        // meta, and grammar-only nodes). New SyntaxKind variants must be added
164        // here or classified above.
165        Unparsable
166        | File
167        | ColumnReference
168        | ObjectReference
169        | Expression
170        | WildcardIdentifier
171        | Function
172        | FunctionContents
173        | HavingClause
174        | PathSegment
175        | LimitClause
176        | CubeRollupClause
177        | GroupingSetsClause
178        | GroupingExpressionList
179        | SetClause
180        | FetchClause
181        | FunctionDefinition
182        | AlterSequenceOptionsSegment
183        | RoleReference
184        | TablespaceReference
185        | ExtensionReference
186        | TagReference
187        | ColumnDefinition
188        | ColumnConstraintSegment
189        | CommentClause
190        | TableEndClause
191        | MergeMatch
192        | MergeWhenNotMatchedClause
193        | MergeInsertClause
194        | MergeUpdateClause
195        | MergeDeleteClause
196        | MergeTreeOrderByClause
197        | SetClauseList
198        | TableReference
199        | GroupbyClause
200        | FrameClause
201        | WithCompoundStatement
202        | CommonTableExpression
203        | CTEColumnList
204        | ReferencedColumnList
205        | TriggerReference
206        | TableConstraint
207        | JoinOnCondition
208        | DatabaseReference
209        | CollationReference
210        | OverClause
211        | NamedWindow
212        | WindowSpecification
213        | PartitionbyClause
214        | JoinClause
215        | DropTriggerStatement
216        | SampleExpression
217        | TableExpression
218        | CreateTriggerStatement
219        | DropModelStatement
220        | DescribeStatement
221        | UseStatement
222        | ExplainStatement
223        | CreateSequenceStatement
224        | CreateSequenceOptionsSegment
225        | AlterSequenceStatement
226        | DropSequenceStatement
227        | DropCastStatement
228        | CreateFunctionStatement
229        | DropFunctionStatement
230        | CreateModelStatement
231        | CreateViewStatement
232        | DeleteStatement
233        | UpdateStatement
234        | CreateCastStatement
235        | CreateRoleStatement
236        | DropRoleStatement
237        | AlterTableStatement
238        | CreateSchemaStatement
239        | SetSchemaStatement
240        | DropSchemaStatement
241        | DropTypeStatement
242        | CreateDatabaseStatement
243        | DropDatabaseStatement
244        | FunctionParameterList
245        | CreateIndexStatement
246        | DropIndexStatement
247        | CreateTableStatement
248        | AccessStatement
249        | InsertStatement
250        | TransactionStatement
251        | DropTableStatement
252        | DropViewStatement
253        | CreateUserStatement
254        | DropUserStatement
255        | ArrayExpression
256        | LocalAlias
257        | MergeStatement
258        | IndexColumnDefinition
259        | AggregateOrderByClause
260        | FunctionName
261        | CaseExpression
262        | WhenClause
263        | ElseClause
264        | PreWhereClause
265        | WhereClause
266        | SetOperator
267        | ValuesClause
268        | EmptyStructLiteral
269        | ObjectLiteral
270        | ObjectLiteralElement
271        | TimeZoneGrammar
272        | BracketedArguments
273        | DataType
274        | AliasExpression
275        | ArrayAccessor
276        | ArrayLiteral
277        | TypedArrayLiteral
278        | StructType
279        | StructLiteral
280        | TypedStructLiteral
281        | IntervalExpression
282        | ArrayType
283        | SizedArrayType
284        | SelectStatement
285        | OverlapsClause
286        | SelectClause
287        | Statement
288        | WithNoSchemaBindingClause
289        | WithDataClause
290        | SetExpression
291        | FromClause
292        | EmptyStructLiteralBrackets
293        | WildcardExpression
294        | OrderbyClause
295        | TruncateStatement
296        | FromExpression
297        | FromExpressionElement
298        | SelectClauseModifier
299        | NamedWindowExpression
300        | SelectClauseElement
301        | QualifyClause
302        | MultiStatementSegment
303        | AssertStatement
304        | ForInStatements
305        | ForInStatement
306        | RepeatStatements
307        | RepeatStatement
308        | IfStatements
309        | IfStatement
310        | LoopStatements
311        | LoopStatement
312        | WhileStatements
313        | WhileStatement
314        | SelectExceptClause
315        | SelectApplyClause
316        | SelectReplaceClause
317        | StructTypeSchema
318        | Tuple
319        | NamedArgument
320        | DeclareSegment
321        | SetSegment
322        | PartitionBySegment
323        | ClusterBySegment
324        | OptionsSegment
325        | CreateExternalTableStatement
326        | AlterViewStatement
327        | CreateMaterializedViewStatement
328        | CreateDictionaryStatement
329        | AlterMaterializedViewSetOptionsStatement
330        | DropMaterializedViewStatement
331        | ParameterizedExpression
332        | PivotForClause
333        | FromPivotExpression
334        | UnpivotClause
335        | FromUnpivotExpression
336        | NotMatchedByTargetClause
337        | MergeWhenMatchedClause
338        | ProcedureName
339        | ExportStatement
340        | ProcedureParameterList
341        | ProcedureStatements
342        | CallStatement
343        | ReturnStatement
344        | BreakStatement
345        | LeaveStatement
346        | ContinueStatement
347        | RaiseStatement
348        | PsqlVariable
349        | DatetimeTypeIdentifier
350        | DatetimeLiteral
351        | IndexAccessMethod
352        | OperatorClassReference
353        | DefinitionParameter
354        | DefinitionParameters
355        | RelationOption
356        | RelationOptions
357        | AlterFunctionActionSegment
358        | AlterProcedureActionSegment
359        | AlterProcedureStatement
360        | DropProcedureStatement
361        | WktGeometryType
362        | IntoClause
363        | ForClause
364        | AlterRoleStatement
365        | ExplainOption
366        | CreateTableAsStatement
367        | AlterPublicationStatement
368        | CreatePublicationStatement
369        | PublicationObjects
370        | PublicationTable
371        | PublicationReference
372        | DropExtensionStatement
373        | CreateExtensionStatement
374        | VersionIdentifier
375        | AlterTableActionSegment
376        | DropPublicationStatement
377        | AlterMaterializedViewStatement
378        | AlterMaterializedViewActionSegment
379        | RefreshMaterializedViewStatement
380        | WithCheckOption
381        | AlterPolicyStatement
382        | AlterDatabaseStatement
383        | VacuumStatement
384        | LikeOptionSegment
385        | PartitionBoundSpec
386        | IndexParameters
387        | ReferentialActionSegment
388        | IndexElement
389        | ExclusionConstraintElement
390        | AlterDefaultPrivilegesStatement
391        | AlterDefaultPrivilegesObjectPrivilege
392        | AlterDefaultPrivilegesSchemaObject
393        | AlterDefaultPrivilegesToFromRoles
394        | AlterDefaultPrivilegesGrant
395        | DropOwnedStatement
396        | ReassignOwnedStatement
397        | IndexElementOptions
398        | AlterDefaultPrivilegesRevoke
399        | AlterIndexStatement
400        | ReindexStatementSegment
401        | AnalyzeStatement
402        | AlterTrigger
403        | OperationClassReference
404        | ConflictAction
405        | ConflictTarget
406        | SetStatement
407        | CreatePolicyStatement
408        | CreateDomainStatement
409        | AlterDomainStatement
410        | DropDomainStatement
411        | DropPolicyStatement
412        | LoadStatement
413        | ResetStatement
414        | ListenStatement
415        | NotifyStatement
416        | UnlistenStatement
417        | ClusterStatement
418        | LanguageClause
419        | DoStatement
420        | CreateUserMappingStatement
421        | ImportForeignSchemaStatement
422        | CreateServerStatement
423        | CreateCollationStatement
424        | AlterTypeStatement
425        | CreateTypeStatement
426        | LockTableStatement
427        | CopyStatement
428        | DiscardStatement
429        | AlterSchemaStatement
430        | ServerReference
431        | ArrayJoinClause
432        | TableEngineFunction
433        | OnClusterClause
434        | Engine
435        | EngineFunction
436        | DatabaseEngine
437        | ColumnTtlSegment
438        | TableTtlSegment
439        | DropDictionaryStatement
440        | DropQuotaStatement
441        | DropSettingProfileStatement
442        | SystemMergesSegment
443        | SystemTtlMergesSegment
444        | SystemMovesSegment
445        | SystemReplicaSegment
446        | SystemFilesystemSegment
447        | SystemReplicatedSegment
448        | SystemReplicationSegment
449        | SystemFetchesSegment
450        | SystemDistributedSegment
451        | SystemModelSegment
452        | SystemFileSegment
453        | SystemUnfreezeSegment
454        | SystemStatement
455        | ConnectbyClause
456        | CallSegment
457        | WithingroupClause
458        | PatternExpression
459        | MatchRecognizeClause
460        | ChangesClause
461        | MatchConditionClause
462        | FromAtExpression
463        | FromBeforeExpression
464        | SnowflakeKeywordExpression
465        | SemiStructuredExpression
466        | SelectExcludeClause
467        | SelectRenameClause
468        | AlterTableTableColumnAction
469        | AlterTableClusteringAction
470        | AlterTableConstraintAction
471        | AlterWarehouseStatement
472        | AlterShareStatement
473        | AlterStorageIntegrationStatement
474        | AlterExternalTableStatement
475        | CommentEqualsClause
476        | TagBracketedEquals
477        | TagEquals
478        | CreateCloneStatement
479        | CreateDatabaseFromShareStatement
480        | CreateProcedureStatement
481        | ScriptingBlockStatement
482        | ScriptingLetStatement
483        | AlterFunctionStatement
484        | CreateExternalFunctionStatement
485        | WarehouseObjectProperties
486        | ConstraintPropertiesSegment
487        | CopyOptions
488        | SchemaObjectProperties
489        | CreateTaskStatement
490        | SnowflakeTaskExpressionSegment
491        | CreateStatement
492        | CreateFileFormatSegment
493        | AlterFileFormatSegment
494        | CsvFileFormatTypeParameters
495        | JsonFileFormatTypeParameters
496        | AvroFileFormatTypeParameters
497        | OrcFileFormatTypeParameters
498        | ParquetFileFormatTypeParameters
499        | XmlFileFormatTypeParameters
500        | AlterPipeSegment
501        | FileFormatSegment
502        | FormatTypeOptions
503        | CopyIntoLocationStatement
504        | CopyIntoTableStatement
505        | StorageLocation
506        | StageParameters
507        | S3ExternalStageParameters
508        | GcsExternalStageParameters
509        | AzureBlobStorageExternalStageParameters
510        | CreateStageStatement
511        | AlterStageStatement
512        | CreateStreamStatement
513        | AlterStreamStatement
514        | ShowStatement
515        | AlterUserStatement
516        | AlterSessionStatement
517        | AlterSessionSetStatement
518        | AlterSessionUnsetClause
519        | AlterTaskStatement
520        | AlterTaskSpecialSetClause
521        | AlterTaskSetClause
522        | AlterTaskUnsetClause
523        | ExecuteTaskClause
524        | UndropStatement
525        | CommentStatement
526        | DropExternalTableStatement
527        | ListStatement
528        | GetStatement
529        | PutStatement
530        | RemoveStatement
531        | CastExpression
532        | DropObjectStatement
533        | UnsetStatement
534        | SqlConfOption
535        | CreateWidgetStatement
536        | ReplaceTableStatement
537        | RemoveWidgetStatement
538        | UseDatabaseStatement
539        | InsertOverwriteDirectoryStatement
540        | InsertOverwriteDirectoryHiveFmtStatement
541        | LoadDataStatement
542        | ClusterByClause
543        | DistributeByClause
544        | HintFunction
545        | SelectHint
546        | WithCubeRollupClause
547        | SortByClause
548        | LateralViewClause
549        | PivotClause
550        | TransformClause
551        | AddFileStatement
552        | AddJarStatement
553        | AnalyzeTableStatement
554        | CacheTable
555        | ClearCache
556        | ListFileStatement
557        | ListJarStatement
558        | RefreshStatement
559        | UncacheTable
560        | FileReference
561        | GeneratedColumnDefinition
562        | IntervalLiteral
563        | DescribeHistoryStatement
564        | DescribeDetailStatement
565        | GenerateManifestFileStatement
566        | ConvertToDeltaStatement
567        | RestoreTableStatement
568        | ConstraintStatement
569        | ApplyChangesIntoStatement
570        | UsingClause
571        | DataSourceFormat
572        | IcebergTransformation
573        | MsckRepairTableStatement
574        | RowFormatClause
575        | SkewedByClause
576        | Bracketed
577        | EndOfFile
578        | Whitespace
579        | Newline
580        | Unlexable
581        | StartBracket
582        | EndBracket
583        | Raw
584        | Dot
585        | Comma
586        | EmitsSegment
587        | Literal
588        | Meta
589        | Colon
590        | TernaryColon
591        | StatementTerminator
592        | StartSquareBracket
593        | EndSquareBracket
594        | StartCurlyBracket
595        | Word
596        | DoubleQuote
597        | SingleQuote
598        | Semicolon
599        | BackQuote
600        | DollarQuote
601        | Question
602        | EndCurlyBracket
603        | Dedent
604        | Indent
605        | Implicit
606        | QuestionMark
607        | UdfBody
608        | StartAngleBracket
609        | EndAngleBracket
610        | ProcedureOption
611        | ExportOption
612        | Symbol
613        | ExecuteScriptStatement
614        | Batch
615        | PivotColumnReference
616        | IntoTableClause
617        | PasswordAuth
618        | ExecuteAsClause
619        | ExecuteImmediateClause
620        | UnicodeSingleQuote
621        | EscapedSingleQuote
622        | UnicodeDoubleQuote
623        | At
624        | FileKeyword
625        | SemiStructuredElement
626        | BytesDoubleQuote
627        | BytesSingleQuote
628        | FileFormat
629        | FileType
630        | StartHint
631        | EndHint
632        | UnquotedFilePath
633        | Dollar
634        | StageEncryptionOption
635        | BucketPath
636        | QuotedStar
637        | StagePath
638        | ColumnSelector
639        | ExcludeBracketClose
640        | WarehouseSize
641        | ExcludeBracketOpen
642        | SymlinkFormatManifest
643        | StartExcludeBracket
644        | CompressionType
645        | CopyOnErrorOption
646        | ColumnIndexIdentifierSegment
647        | ScalingPolicy
648        | ValidationModeOption
649        | EndExcludeBracket
650        | IdentifierList
651        | TemplateLoop
652        | ColonDelimiter
653        | SqlcmdOperator
654        | Slice
655        | TableEndClauseSegment
656        | PragmaStatement
657        | PragmaReference
658        | Slash
659        | DataFormatSegment
660        | AuthorizationSegment
661        | ColumnAttributeSegment
662        | ShowModelStatement
663        | CreateExternalSchemaStatement
664        | CreateLibraryStatement
665        | UnloadStatement
666        | DeclareStatement
667        | FetchStatement
668        | CloseStatement
669        | CreateDatashareStatement
670        | DescDatashareStatement
671        | DropDatashareStatement
672        | ShowDatasharesStatement
673        | GrantDatashareStatement
674        | CreateRlsPolicyStatement
675        | ManageRlsPolicyStatement
676        | DropRlsPolicyStatement
677        | AnalyzeCompressionStatement
678        | PartitionedBySegment
679        | RowFormatDelimitedSegment
680        | ObjectUnpivoting
681        | ArrayUnnesting
682        | AlterGroup
683        | CreateGroup
684        | ListaggOverflowClauseSegment
685        | UnorderedSelectStatementSegment
686        | MapType
687        | MapTypeSchema
688        | PrepareStatement
689        | ExecuteStatement
690        | RenameTableStatement
691        | ActionParameter
692        | AggregateClause
693        | AggregateOrderBy
694        | AliasOperator
695        | AllowConnections
696        | AlterAccountStatement
697        | AlterAggregateStatement
698        | AlterBiCapacityStatement
699        | AlterCapacityStatement
700        | AlterCatalogStatement
701        | AlterDynamicTableStatement
702        | AlterEventStatement
703        | AlterExtensionStatement
704        | AlterExternalVolumeStatement
705        | AlterForeignTableActionSegment
706        | AlterForeignTableStatement
707        | AlterMaskingPolicy
708        | AlterMasterKeyStatement
709        | AlterNetworkPolicyStatement
710        | AlterOptionSegment
711        | AlterOrganizationStatement
712        | AlterPartitionFunctionStatement
713        | AlterPartitionSchemeStatement
714        | AlterPasswordPolicyStatement
715        | AlterProjectStatement
716        | AlterReservationStatement
717        | AlterResourceMonitorStatement
718        | AlterRowAccessPolicyStatement
719        | AlterSecurityPolicyStatement
720        | AlterStatisticsStatement
721        | AlterStreamlitStatement
722        | AlterSubscription
723        | AlterTableSwitchStatement
724        | AlterTagStatement
725        | AlterTextSearchConfigurationStatement
726        | AlterVolumeStatement
727        | ArnCatalogSchemaSegment
728        | ArrayTypeSchema
729        | AtSign
730        | AtomicBeginEndBlock
731        | Atsign
732        | AutoOption
733        | BackupStorageRedundancy
734        | BeginEndBlock
735        | BeginStatement
736        | BinaryLiteral
737        | BindVariable
738        | BitValueLiteral
739        | BracketedIndexColumnListGrammar
740        | BulkInsertStatement
741        | BulkInsertWithSegment
742        | ByteLengthLiteral
743        | CallOperator
744        | CatalogReference
745        | CharacteristicStatement
746        | CheckConstraintGrammar
747        | CheckTableStatement
748        | ChecksumTableStatement
749        | CloseCursorStatement
750        | ClusterbyClause
751        | Code
752        | CollationClause
753        | ColonLiteral
754        | ColonPrefix
755        | ColumnPathOperator
756        | ColumnPropertiesSegment
757        | ColumnTypeReference
758        | ColumnsExpression
759        | Command
760        | CompatibilityLevel
761        | CompositeValueExpansion
762        | ComputedColumnDefinition
763        | ConflictClause
764        | ConnectionConstraintGrammar
765        | ConnectionLimitSegment
766        | CopyFilesIntoLocationStatement
767        | CreateAggregateStatement
768        | CreateAssignmentStatement
769        | CreateAuthenticationPolicySegment
770        | CreateCapacityStatement
771        | CreateCatalogStatement
772        | CreateColumnstoreIndexStatement
773        | CreateCortexSearchServiceStatement
774        | CreateDatabaseRoleStatement
775        | CreateDatabaseScopedCredentialStatement
776        | CreateDatabaseWithOptions
777        | CreateEventStatement
778        | CreateEventTableStatement
779        | CreateExternalDataSourceStatement
780        | CreateExternalFileFormat
781        | CreateExternalVolumeStatement
782        | CreateForeignDataWrapper
783        | CreateForeignTableStatement
784        | CreateFulltextCatalogStatement
785        | CreateFulltextIndexStatement
786        | CreateLoginStatement
787        | CreateMasterKeyStatement
788        | CreateMaterializedViewAsReplicaOfStatement
789        | CreateOperatorStatement
790        | CreateOptionSegment
791        | CreatePartitionFunctionStatement
792        | CreatePartitionSchemeStatement
793        | CreatePasswordPolicyStatement
794        | CreateReservationStatement
795        | CreateResourceMonitorStatement
796        | CreateRowAccessPolicyStatement
797        | CreateSearchIndexStatement
798        | CreateSecurityPolicyStatement
799        | CreateServerRoleStatement
800        | CreateSnapshotTableStatement
801        | CreateSqlFunctionStatement
802        | CreateStatisticsStatement
803        | CreateStreamlitStatement
804        | CreateSubscription
805        | CreateSynonymStatement
806        | CreateTableAsSelectStatement
807        | CreateTableFunctionStatement
808        | CreateTableGraphStatement
809        | CreateTableUsingStatement
810        | CreateTextSearchConfigurationStatement
811        | CreateTrigger
812        | CreateVectorIndexStatement
813        | CreateVirtualTableStatement
814        | CreateVolumeStatement
815        | CursorDefinition
816        | CursorFetchSegment
817        | CursorOpenCloseSegment
818        | DataGovernancePolicyTagActionSegment
819        | DatabaseRoleReference
820        | DateFormat
821        | DeallocateCursorStatement
822        | DeallocateSegment
823        | DeallocateStatement
824        | DeclareOrReplaceVariableStatement
825        | DefaultCollate
826        | DefinerSegment
827        | DeleteTargetTable
828        | DelimiterStatement
829        | DisableTrigger
830        | DistributebyClause
831        | DoubleAmpersand
832        | DoubleAtSignLiteral
833        | DoubleVerticalBar
834        | DropAggregateStatement
835        | DropAssignmentStatement
836        | DropCapacityStatement
837        | DropCatalogStatement
838        | DropCollationStatement
839        | DropColumnClause
840        | DropDynamicTableSegment
841        | DropEventStatement
842        | DropExternalVolumeStatement
843        | DropForeignTableStatement
844        | DropIcebergTableStatement
845        | DropMasterKeyStatement
846        | DropModelstatement
847        | DropPasswordPolicyStatement
848        | DropReservationStatement
849        | DropResourceMonitorStatement
850        | DropRowAccessPolicyStatement
851        | DropSearchIndexStatement
852        | DropSecurityPolicy
853        | DropStatement
854        | DropStatisticsStatement
855        | DropSubscription
856        | DropSynonymStatement
857        | DropTableFunctionStatement
858        | DropTextSearchConfigurationStatement
859        | DropTrigger
860        | DropVectorIndexStatement
861        | DropVolumeStatement
862        | DynamicTableLagIntervalSegment
863        | DynamicTableOptions
864        | Edition
865        | EncryptedWithGrammar
866        | ExceptionBlockStatement
867        | ExceptionCode
868        | ExecuteArrow
869        | ExecuteImmediate
870        | ExecuteOption
871        | ExecuteSegment
872        | ExtendClause
873        | ExternalAccessIntegrationEquals
874        | ExternalFileDelimitedTextClause
875        | ExternalFileDelimitedTextFormatOptionsClause
876        | ExternalFileDeltaClause
877        | ExternalFileJsonClause
878        | ExternalFileOrcClause
879        | ExternalFileParquetClause
880        | ExternalFileRcClause
881        | ExternalLocation
882        | ExternalVolumeReference
883        | FatRightArrow
884        | FetchCursorStatement
885        | FileCompression
886        | FileEncoding
887        | FileSpec
888        | FileSpecFileGrowth
889        | FileSpecFileName
890        | FileSpecMaxSize
891        | FileSpecNewName
892        | FileSpecSize
893        | FileSpecWithoutBracket
894        | FilegroupClause
895        | FilegroupName
896        | FilestreamOnOptionStatement
897        | FlushStatement
898        | ForSystemTimeAsOfSegment
899        | FormatClause
900        | FromDatashareClause
901        | FromIntegrationClause
902        | FullTextSearchOperator
903        | FunctionOptionSegment
904        | FunctionParameterListWithComments
905        | GetDiagnosticsSegment
906        | GlobOperator
907        | GoStatement
908        | GotoStatement
909        | GrantToSegment
910        | GraphTableConstraint
911        | GroupAndOrderbyClause
912        | HashIdentifier
913        | HashPrefix
914        | HelpStatement
915        | HexadecimalLiteral
916        | IamRoleClause
917        | IcebergTableOptions
918        | IdentifierClauseSegment
919        | IdentityGrammar
920        | IfClause
921        | IfThenStatement
922        | IndexHintClause
923        | IndexOption
924        | IndexReference
925        | IndexType
926        | InitializeType
927        | InlineDollarSign
928        | InlinePathOperator
929        | InsertRowAlias
930        | IntoOutfileClause
931        | IsolationLevelClause
932        | IterateStatement
933        | JsonPath
934        | LabelSegment
935        | LambdaArrow
936        | LambdaFunction
937        | LeadingDot
938        | LimitClauseComponent
939        | ListComprehension
940        | ListaggOverflowClause
941        | LocalAliasSegment
942        | LogLevelEquals
943        | LogicalFileName
944        | LoginUserSegment
945        | MagicCellSegment
946        | MagicLine
947        | MagicSingleLine
948        | MagicStart
949        | MaskStatement
950        | MasterKeyEncryptionOption
951        | MatchCondition
952        | MaxDuration
953        | MaxLiteral
954        | MetaCommand
955        | MetaCommandQueryBuffer
956        | MetaCommandStatement
957        | MlTableExpression
958        | MsckTableStatement
959        | NotOperator
960        | NotebookStart
961        | ObevoAnnotation
962        | OffsetClause
963        | OnPartitionOrFilegroupStatement
964        | OnPartitionsClause
965        | OpenCursorStatement
966        | OpenSymmetricKeyStatement
967        | OpenjsonSegment
968        | OpenjsonWithClause
969        | OpenquerySegment
970        | OpenrowsetSegment
971        | OpenrowsetWithClause
972        | OpenxmlSegment
973        | Operator
974        | OptimizeTableStatement
975        | Option
976        | OptionClause
977        | OptionIndicator
978        | OutputClause
979        | ParameterDirection
980        | PartitionClause
981        | PartitionSchemeClause
982        | PartitionSchemeName
983        | PasswordPolicyOptions
984        | PasswordPolicyReference
985        | PeriodSegment
986        | PgTrgmOperator
987        | PgvectorOperator
988        | PipeOperator
989        | PipeOperatorClause
990        | PipeStatement
991        | PivotOperator
992        | PostTableExpression
993        | PostgisOperator
994        | PrepareSegment
995        | PrewhereClause
996        | PrintStatement
997        | ProcedureStatement
998        | PurgeBinaryLogsStatement
999        | QualifiedOperator
1000        | QueryHintSegment
1001        | QuestionLiteral
1002        | RaiserrorStatement
1003        | RawDoubleQuote
1004        | RawSingleQuote
1005        | ReconfigureStatement
1006        | ReferencesConstraintGrammar
1007        | RelationalIndexOptions
1008        | RenameColumnClause
1009        | RenameStatement
1010        | RepairTableStatement
1011        | ReplaceStatement
1012        | ResetMasterStatement
1013        | ResetSessionAuthorizationStatement
1014        | ResignalSegment
1015        | ResourceConstraint
1016        | ResourceMonitorOptions
1017        | ReturnSegment
1018        | ReturningClause
1019        | SchemaReference
1020        | ScriptingDeclareStatement
1021        | ScriptingIfStatement
1022        | ScriptingRaiseStatement
1023        | SearchOptimizationAction
1024        | SecurityLabelStatement
1025        | SelectVariableAssignment
1026        | SequenceNextValue
1027        | SequenceReference
1028        | SerdeMethod
1029        | ServiceObjective
1030        | SetConstraintStatement
1031        | SetContextInfoStatement
1032        | SetLanguageStatement
1033        | SetLocalVariableSegment
1034        | SetNamesStatement
1035        | SetOperatorClause
1036        | SetSessionAuthorizationStatement
1037        | SetSessionStatement
1038        | SetTimezoneStatement
1039        | SetTransactionStatement
1040        | SetVariableStatement
1041        | SettingsClause
1042        | SimplifiedPivot
1043        | SimplifiedUnpivot
1044        | SingleQuoteWithN
1045        | SizeLiteral
1046        | SortbyClause
1047        | SqlcmdCommandSegment
1048        | SquareQuote
1049        | StatisticsReference
1050        | StoringSegment
1051        | SubscriptionReference
1052        | SynonymReference
1053        | SystemVariable
1054        | TableClausesSegment
1055        | TableClusterByClause
1056        | TableColumnCommentAction
1057        | TableDistributionClause
1058        | TableDistributionIndexClause
1059        | TableIndexClause
1060        | TableIndexSegment
1061        | TableLocationClause
1062        | TableOptionStatement
1063        | TableSpecificationSegment
1064        | TagStatement
1065        | TemporalQuery
1066        | TextimageOnOptionStatement
1067        | ThrowStatement
1068        | TraceLevelEquals
1069        | TruncateTable
1070        | TryCatch
1071        | TupleTypeSchema
1072        | UndropSchemaStatement
1073        | UnpivotMultiColumn
1074        | UnpivotOperator
1075        | UnpivotSingleColumn
1076        | UnquotedRelativeSqlFilePath
1077        | UpdateStatisticsStatement
1078        | UpsertClause
1079        | UpsertClauseList
1080        | UseCatalogStatement
1081        | VolumeReference
1082        | WaitforStatement
1083        | WildcardExclude
1084        | WildcardPatternMatching
1085        | WildcardRename
1086        | WildcardReplace
1087        | WithCheckOptions
1088        | WithFill
1089        | WithRollupClause
1090        | WithinGroupClause
1091        | WithordinalityClause
1092        | OracleAtSign
1093        | OraclePowerOperator
1094        | OracleAssignmentOperator
1095        | HierarchicalQueryClause
1096        | PivotSegment
1097        | UnpivotSegment
1098        | TriggerCorrelationName
1099        | OracleBindVariable
1100        | AlterTableProperties
1101        | AlterTableColumnClauses
1102        | AlterTableConstraintClauses
1103        | IndexTypeReference
1104        | ExecuteFileStatement
1105        | SlashBufferExecutor
1106        | OracleBatch
1107        | OracleCommentStatement
1108        | OracleCreateProcedureStatement
1109        | OracleDropProcedureStatement
1110        | OracleDeclareSegment
1111        | OracleColumnTypeReference
1112        | OracleRowTypeReference
1113        | CollectionType
1114        | RecordType
1115        | RefCursorType
1116        | DeclareCursorVariable
1117        | OracleExecuteImmediateStatement
1118        | OracleBeginEndBlock
1119        | OracleCreateFunctionStatement
1120        | OracleAlterFunctionStatement
1121        | OracleCreateTypeStatement
1122        | OracleTypeReference
1123        | OracleCreateTypeBodyStatement
1124        | OracleCreatePackageStatement
1125        | OraclePackageReference
1126        | OracleAlterPackageStatement
1127        | OracleDropPackageStatement
1128        | OracleCreateTriggerStatement
1129        | DmlEventClause
1130        | OracleReferencingClause
1131        | CompoundTriggerStatement
1132        | TimingPointSection
1133        | OracleAlterTriggerStatement
1134        | AssignmentSegmentStatement
1135        | OracleIfThenStatement
1136        | OracleIfClause
1137        | OracleCaseExpression
1138        | OracleWhenClause
1139        | OracleElseClause
1140        | OracleNullStatement
1141        | ForLoopStatement
1142        | WhileLoopStatement
1143        | OracleLoopStatement
1144        | ForallStatement
1145        | OracleOpenStatement
1146        | OracleOpenForStatement
1147        | OracleFetchStatement
1148        | OracleIntoClause
1149        | BulkCollectIntoClause
1150        | OracleExitStatement
1151        | OracleReturnStatement
1152        | OracleCreateUserStatement
1153        | OracleReturningClause
1154        | DatabaseLinkReference
1155        | OracleCreateDatabaseLinkStatement
1156        | OracleDropDatabaseLinkStatement
1157        | OracleAlterDatabaseLinkStatement
1158        | OracleCreateSynonymStatement
1159        | OracleDropSynonymStatement
1160        | OracleAlterSynonymStatement
1161        | OracleWithinGroupClause
1162        | OracleListaggOverflowClause
1163        | OracleNamedArgument
1164        | OracleCreateTableStatement
1165        | OracleColumnDefinition
1166        | OracleSqlplusVariable
1167        | StartwithClause
1168        | OracleTableReference
1169        | OracleCreateViewStatement
1170        | OracleAlterIndexStatement
1171        | OracleAlterTableStatement
1172        | OracleAlterSessionStatement
1173        | JsonTableColumnDefinition
1174        | JsonTableColumnsClause
1175        | JsonTableFunctionContents
1176        | OracleMergeUpdateClause
1177        | OracleInsertStatement
1178        | OracleUpdateStatement
1179        | OracleDeleteStatement
1180        | OracleTransactionStatement
1181        | OracleValuesClause
1182        | OracleTableConstraint
1183        | OracleFunctionName
1184        | OracleOrderByClause
1185        | EngineType
1186        | PartitionSegment
1187        | DistributionSegment
1188        | IndexDefinition
1189        | CreateRoutineLoadStatement
1190        | RoutineLoadProperties
1191        | RoutineLoadDataSourceProperties
1192        | StopRoutineLoadStatement
1193        | PauseRoutineLoadStatement
1194        | ResumeRoutineLoadStatement
1195        | InsertOverwriteStatement
1196        | AlterConnection
1197        | AlterConsumerGroupStatement
1198        | AlterSystemStatement
1199        | AlterTableAddColumn
1200        | AlterTableAlterColumn
1201        | AlterTableColumnStatement
1202        | AlterTableConstraintStatement
1203        | AlterTableDistributePartitionStatement
1204        | AlterTableDropColumn
1205        | AlterTableModifyColumn
1206        | AlterTableRenameColumn
1207        | AlterVirtualSchemaStatement
1208        | CloseSchemaStatement
1209        | ColumnDatatypeDefinition
1210        | ColumnReferenceList
1211        | ConnectByClause
1212        | ConnectionDefinition
1213        | ConsumerGroupParameter
1214        | CreateAdapterScript
1215        | CreateConnection
1216        | CreateConsumerGroupStatement
1217        | CreateScriptingLuaScript
1218        | CreateUdfScript
1219        | CreateVirtualSchemaStatement
1220        | CsvCols
1221        | DropConnectionStatement
1222        | DropConsumerGroupStatement
1223        | DropScriptStatement
1224        | EscapedIdentifier
1225        | ExplainVirtualStatement
1226        | ExportIntoClause
1227        | FbvCols
1228        | FileOpts
1229        | FlushStatisticsStatement
1230        | FunctionAssignment
1231        | FunctionBody
1232        | FunctionForLoop
1233        | FunctionIfBranch
1234        | FunctionReference
1235        | FunctionScriptTerminator
1236        | FunctionWhileLoop
1237        | GrantRevokeConnection
1238        | GrantRevokeConnectionRestricted
1239        | GrantRevokeImpersonation
1240        | GrantRevokeObjectPrivileges
1241        | GrantRevokeRoles
1242        | GrantRevokeSystemPrivileges
1243        | ImpersonateStatement
1244        | ImportColumns
1245        | ImportErrorDestination
1246        | ImportErrorsClause
1247        | ImportExportDbsrc
1248        | ImportFile
1249        | ImportFromClause
1250        | ImportScript
1251        | ImportStatement
1252        | KerberosAuth
1253        | KillStatement
1254        | LdapAuth
1255        | ObjectPrivilege
1256        | OpenSchemaStatement
1257        | OpenidAuth
1258        | PlusPriorInverse
1259        | PreferenceTerm
1260        | PreferringClause
1261        | PreloadStatement
1262        | RangeOperator
1263        | RecompressReorganizeStatement
1264        | RejectClause
1265        | ScriptContent
1266        | ScriptReference
1267        | SessionParameter
1268        | SystemParameter
1269        | SystemPrivilege
1270        | TableConstraintDefinition
1271        | TableContentDefinition
1272        | TableDistributionPartitionClause
1273        | TableLikeClause
1274        | TruncateAuditLogsStatement
1275        | UdfParamDotSyntax
1276        | ValuesClauseElements
1277        | ValuesInsertClause
1278        | ValuesRangeClause
1279        | ViewReference
1280        | WithInvalidForeignKeyClause
1281        | WithInvalidUniquePkClause
1282        | TupleElementAccess
1283        | MaterializeSize
1284        | AlterConnectionRotateKeys
1285        | AlterRenameStatement
1286        | AlterSecretStatement
1287        | AlterSourceSinkSizeStatement
1288        | CopyToStatement
1289        | CopyFromStatement
1290        | CreateClusterStatement
1291        | CreateClusterReplicaStatement
1292        | CreateConnectionStatement
1293        | CreateSecretStatement
1294        | CreateSinkKafkaStatement
1295        | CreateSourceKafkaStatement
1296        | CreateSourceLoadGeneratorStatement
1297        | CreateSourcePostgresStatement
1298        | ShowCreateStatement
1299        | ShowIndexesStatement
1300        | ShowMaterializedViewsStatement
1301        | BteqKeyWordSegment
1302        | BteqStatement
1303        | CollectStatUsingOptionClause
1304        | CollectStatisticsStatement
1305        | CreateTableOptionsStatement
1306        | DatabaseStatement
1307        | FromInUpdateClause
1308        | SetQueryBandStatement
1309        | TdColumnAttributeConstraint
1310        | TdPartitioningLevel
1311        | TdTableConstraint => return None,
1312    };
1313
1314    Some(highlight)
1315}
1316
1317/// Parse `source` with `linter` and produce LSP semantic tokens for it.
1318///
1319/// Returns an empty list if the source fails to parse.
1320pub fn semantic_tokens(
1321    linter: &Linter,
1322    source: &str,
1323    filename: Option<String>,
1324) -> Vec<SemanticToken> {
1325    let tables = Tables::default();
1326    match linter.parse_string(&tables, source, filename) {
1327        Ok(parsed) => parsed
1328            .tree
1329            .map(|tree| build_semantic_tokens(&tree, source))
1330            .unwrap_or_default(),
1331        Err(e) => {
1332            eprintln!("Failed to parse for semantic tokens: {}", e.value);
1333            Vec::new()
1334        }
1335    }
1336}
1337
1338/// Build delta-encoded LSP semantic tokens from a parsed `tree` over `source`.
1339pub(crate) fn build_semantic_tokens(tree: &ErasedSegment, source: &str) -> Vec<SemanticToken> {
1340    let line_index = LineIndex::new(source);
1341    let mut tokens: Vec<RawToken> = Vec::new();
1342
1343    for segment in tree.get_raw_segments() {
1344        let Some(highlight) = classify(segment.get_type()) else {
1345            continue;
1346        };
1347        let Some(marker) = segment.get_position_marker() else {
1348            continue;
1349        };
1350
1351        let range = marker.source_slice.clone();
1352        if range.is_empty() || range.end > source.len() {
1353            continue;
1354        }
1355        let text = &source[range.clone()];
1356        let token_type = highlight.token_type();
1357
1358        // The LSP wire format cannot represent a token spanning multiple lines,
1359        // so split block comments / multi-line strings into one token per line.
1360        line_index.split_lines(range.start, text, |line, start, length| {
1361            tokens.push(RawToken {
1362                line,
1363                start,
1364                length,
1365                token_type,
1366            });
1367        });
1368    }
1369
1370    // The CST walk yields source order, but the multi-line split can interleave,
1371    // so re-sort before delta encoding.
1372    tokens.sort_by_key(|t| (t.line, t.start));
1373
1374    let mut data = Vec::with_capacity(tokens.len());
1375    let mut prev_line = 0;
1376    let mut prev_start = 0;
1377    for token in tokens {
1378        let delta_line = token.line - prev_line;
1379        let delta_start = if delta_line == 0 {
1380            token.start - prev_start
1381        } else {
1382            token.start
1383        };
1384        data.push(SemanticToken {
1385            delta_line,
1386            delta_start,
1387            length: token.length,
1388            token_type: token.token_type,
1389            token_modifiers_bitset: 0,
1390        });
1391        prev_line = token.line;
1392        prev_start = token.start;
1393    }
1394
1395    data
1396}
1397
1398/// A token before delta encoding: absolute line + UTF-16 start column + length.
1399struct RawToken {
1400    line: u32,
1401    start: u32,
1402    length: u32,
1403    token_type: u32,
1404}
1405
1406/// Maps byte offsets in the source to LSP `(line, character)` positions, where
1407/// `character` is counted in UTF-16 code units (the LSP default encoding).
1408struct LineIndex<'a> {
1409    source: &'a str,
1410    /// Byte offset of the start of each line.
1411    line_starts: Vec<usize>,
1412}
1413
1414impl<'a> LineIndex<'a> {
1415    fn new(source: &'a str) -> Self {
1416        let mut line_starts = vec![0];
1417        for (offset, byte) in source.bytes().enumerate() {
1418            if byte == b'\n' {
1419                line_starts.push(offset + 1);
1420            }
1421        }
1422        Self {
1423            source,
1424            line_starts,
1425        }
1426    }
1427
1428    /// Resolve a byte offset to `(line, utf16_column)`.
1429    fn position(&self, offset: usize) -> (u32, u32) {
1430        let line = match self.line_starts.binary_search(&offset) {
1431            Ok(line) => line,
1432            Err(next) => next - 1,
1433        };
1434        let line_start = self.line_starts[line];
1435        let column = utf16_len(&self.source[line_start..offset]);
1436        (line as u32, column)
1437    }
1438
1439    /// Split `text` (which starts at byte `start` in the source) into one entry
1440    /// per line, invoking `emit(line, start_col, len)` with UTF-16 columns and
1441    /// lengths. Trailing newline / carriage-return characters are excluded.
1442    fn split_lines(&self, start: usize, text: &str, mut emit: impl FnMut(u32, u32, u32)) {
1443        let mut offset = start;
1444        for piece in text.split_inclusive('\n') {
1445            // Strip the line terminator (`\n`, and a preceding `\r` if present).
1446            let content = piece.strip_suffix('\n').unwrap_or(piece);
1447            let content = content.strip_suffix('\r').unwrap_or(content);
1448            if !content.is_empty() {
1449                let (line, column) = self.position(offset);
1450                emit(line, column, utf16_len(content));
1451            }
1452            offset += piece.len();
1453        }
1454    }
1455}
1456
1457/// Count the number of UTF-16 code units in `text`.
1458fn utf16_len(text: &str) -> u32 {
1459    text.chars().map(|c| c.len_utf16() as u32).sum()
1460}
1461
1462#[cfg(test)]
1463mod tests {
1464    use super::*;
1465    use sqruff_lib::core::config::FluffConfig;
1466
1467    fn linter() -> Linter {
1468        let config = FluffConfig::from_source("[sqruff]\ndialect = ansi\n", None);
1469        Linter::new(config, None, None, false).unwrap()
1470    }
1471
1472    /// Decode the delta-encoded wire format back into absolute
1473    /// `(line, start, length, token_type)` tuples for readable assertions.
1474    fn decode(tokens: &[SemanticToken]) -> Vec<(u32, u32, u32, u32)> {
1475        let mut line = 0;
1476        let mut start = 0;
1477        let mut out = Vec::new();
1478        for token in tokens {
1479            if token.delta_line == 0 {
1480                start += token.delta_start;
1481            } else {
1482                line += token.delta_line;
1483                start = token.delta_start;
1484            }
1485            out.push((line, start, token.length, token.token_type));
1486        }
1487        out
1488    }
1489
1490    fn tokens(sql: &str) -> Vec<(u32, u32, u32, u32)> {
1491        decode(&semantic_tokens(&linter(), sql, None))
1492    }
1493
1494    #[test]
1495    fn highlights_keywords_identifiers_and_literals() {
1496        // SELECT a, 1, 'x' FROM t
1497        let decoded = tokens("SELECT a, 1, 'x' FROM t");
1498        let kw = Highlight::Keyword.token_type();
1499        let var = Highlight::Variable.token_type();
1500        let num = Highlight::Number.token_type();
1501        let string = Highlight::String.token_type();
1502
1503        assert_eq!(
1504            decoded,
1505            vec![
1506                (0, 0, 6, kw),      // SELECT
1507                (0, 7, 1, var),     // a
1508                (0, 10, 1, num),    // 1
1509                (0, 13, 3, string), // 'x'
1510                (0, 17, 4, kw),     // FROM
1511                (0, 22, 1, var),    // t
1512            ]
1513        );
1514    }
1515
1516    #[test]
1517    fn skips_whitespace_and_punctuation() {
1518        // Commas / whitespace must not produce tokens.
1519        let decoded = tokens("SELECT a, b");
1520        assert!(decoded.iter().all(|&(_, _, len, _)| len > 0));
1521        // SELECT, a, b => exactly three tokens.
1522        assert_eq!(decoded.len(), 3);
1523    }
1524
1525    #[test]
1526    fn splits_block_comment_across_lines() {
1527        let decoded = tokens("SELECT 1 /* line one\nline two */ FROM t");
1528        let comment = Highlight::Comment.token_type();
1529        let comment_tokens: Vec<_> = decoded
1530            .iter()
1531            .filter(|&&(_, _, _, ty)| ty == comment)
1532            .collect();
1533        // The block comment spans two lines => two comment tokens, one per line.
1534        assert_eq!(comment_tokens.len(), 2);
1535        assert_eq!(comment_tokens[0].0, 0);
1536        assert_eq!(comment_tokens[1].0, 1);
1537    }
1538
1539    #[test]
1540    fn utf16_columns_after_non_ascii() {
1541        // A non-ASCII char in a string shifts later columns; ensure we count
1542        // UTF-16 code units, not bytes.
1543        let sql = "SELECT 'é' AS a";
1544        let decoded = tokens(sql);
1545        let var = Highlight::Variable.token_type();
1546        // `a` is the alias identifier; in UTF-16 it sits at column 14.
1547        // S E L E C T _ ' é ' _ A S _ a
1548        // 0 1 2 3 4 5 6 7 8 9 ...
1549        let alias = decoded
1550            .iter()
1551            .rev()
1552            .find(|&&(_, _, _, ty)| ty == var)
1553            .unwrap();
1554        assert_eq!(alias.1, 14);
1555    }
1556}