@@ -759,7 +759,28 @@ module.exports = grammar({
759759 choice (
760760 $ . _ddl_statement ,
761761 $ . _dml_write ,
762- optional_parenthesis ( $ . _dml_read )
762+ optional_parenthesis ( $ . _dml_read ) ,
763+ oneOfKeywords ( [
764+ "alter" ,
765+ "comment" ,
766+ "copy" ,
767+ "create" ,
768+ "delete" ,
769+ "drop" ,
770+ "grant" ,
771+ "insert" ,
772+ "merge" ,
773+ "reset" ,
774+ "revoke" ,
775+ "select" ,
776+ "set" ,
777+ "show" ,
778+ "truncate" ,
779+ "unload" ,
780+ "update" ,
781+ "vacuum" ,
782+ "with" ,
783+ ] )
763784 )
764785 ) ,
765786
@@ -1003,7 +1024,25 @@ module.exports = grammar({
10031024 $ . create_extension ,
10041025 $ . create_trigger ,
10051026 $ . create_policy ,
1006- prec . left ( seq ( $ . create_schema , repeat ( $ . _create_statement ) ) )
1027+ prec . left ( seq ( $ . create_schema , repeat ( $ . _create_statement ) ) ) ,
1028+ seq (
1029+ completableKeyword ( $ , "create" , { minLength : 2 } ) ,
1030+ oneOfKeywords ( [
1031+ "database" ,
1032+ "extension" ,
1033+ "function" ,
1034+ "index" ,
1035+ "materialized" ,
1036+ "policy" ,
1037+ "role" ,
1038+ "schema" ,
1039+ "sequence" ,
1040+ "table" ,
1041+ "trigger" ,
1042+ "type" ,
1043+ "view" ,
1044+ ] )
1045+ )
10071046 )
10081047 ) ,
10091048
@@ -1708,7 +1747,21 @@ module.exports = grammar({
17081747 $ . alter_database ,
17091748 $ . alter_role ,
17101749 $ . alter_sequence ,
1711- $ . alter_policy
1750+ $ . alter_policy ,
1751+ seq (
1752+ completableKeyword ( $ , "alter" , { minLength : 2 } ) ,
1753+ oneOfKeywords ( [
1754+ "database" ,
1755+ "index" ,
1756+ "policy" ,
1757+ "role" ,
1758+ "schema" ,
1759+ "sequence" ,
1760+ "table" ,
1761+ "type" ,
1762+ "view" ,
1763+ ] )
1764+ )
17121765 )
17131766 ) ,
17141767
@@ -2082,7 +2135,23 @@ module.exports = grammar({
20822135 $ . drop_sequence ,
20832136 $ . drop_extension ,
20842137 $ . drop_function ,
2085- $ . drop_policy
2138+ $ . drop_policy ,
2139+ seq (
2140+ completableKeyword ( $ , "drop" , { minLength : 2 } ) ,
2141+ oneOfKeywords ( [
2142+ "database" ,
2143+ "extension" ,
2144+ "function" ,
2145+ "index" ,
2146+ "policy" ,
2147+ "role" ,
2148+ "schema" ,
2149+ "sequence" ,
2150+ "table" ,
2151+ "type" ,
2152+ "view" ,
2153+ ] )
2154+ )
20862155 )
20872156 ) ,
20882157
@@ -2265,7 +2334,8 @@ module.exports = grammar({
22652334 choice (
22662335 seq ( $ . keyword_default , $ . keyword_values ) ,
22672336 $ . insert_values ,
2268- $ . _select_statement
2337+ $ . _select_statement ,
2338+ oneOfKeywords ( [ "default" , "on" , "select" , "values" ] )
22692339 ) ,
22702340 optional ( $ . _on_conflict )
22712341 ) ,
@@ -2402,7 +2472,7 @@ module.exports = grammar({
24022472 $ . relation ,
24032473 $ . _set_values ,
24042474 // optional($.from),
2405- optional ( $ . where )
2475+ optional ( choice ( $ . where , oneOfKeywords ( [ "from" , "returning" , "where" ] ) ) )
24062476 ) ,
24072477
24082478 storage_location : ( $ ) =>
@@ -2866,7 +2936,29 @@ module.exports = grammar({
28662936 comma_list ( $ . relation , true ) ,
28672937 optional ( $ . index_hint ) ,
28682938 repeat (
2869- choice ( $ . join , $ . cross_join , $ . lateral_join , $ . lateral_cross_join )
2939+ choice (
2940+ $ . join ,
2941+ $ . cross_join ,
2942+ $ . lateral_join ,
2943+ $ . lateral_cross_join ,
2944+ oneOfKeywords ( [
2945+ "cross" ,
2946+ "full" ,
2947+ "group" ,
2948+ "having" ,
2949+ "inner" ,
2950+ "join" ,
2951+ "lateral" ,
2952+ "left" ,
2953+ "limit" ,
2954+ "natural" ,
2955+ "offset" ,
2956+ "order" ,
2957+ "right" ,
2958+ "where" ,
2959+ "window" ,
2960+ ] )
2961+ )
28702962 ) ,
28712963 optional ( $ . where ) ,
28722964 optional ( $ . group_by ) ,
@@ -3718,22 +3810,54 @@ function completableKeyword($, keyword, opts = {}) {
37183810}
37193811
37203812/**
3721- * Provides an alternative node before we match any other keyword.
3722- * The LSP will be able to infer the possible keywords from the node kind.
3813+ * Provides alternatives for ambiguous keyword prefixes.
3814+ * For each prefix that matches multiple keywords, creates an alias like:
3815+ * any_keyword:select:set (for "se" which matches both)
37233816 *
37243817 * @param {string[] } keywords
37253818 */
37263819function oneOfKeywords ( keywords ) {
3727- const kwAlias = `any_keyword:${ keywords . join ( ":" ) } ` ;
3820+ const allKwAlias = `any_keyword:${ keywords . join ( ":" ) } ` ;
3821+
3822+ // find all ambiguous prefixes and group keywords by prefix
3823+ const prefixToKeywords = new Map ( ) ;
3824+
3825+ for ( const kw of keywords ) {
3826+ for ( let len = 1 ; len < kw . length ; len ++ ) {
3827+ const prefix = kw . substring ( 0 , len ) ;
3828+ if ( ! prefixToKeywords . has ( prefix ) ) {
3829+ prefixToKeywords . set ( prefix , [ ] ) ;
3830+ }
3831+ prefixToKeywords . get ( prefix ) . push ( kw ) ;
3832+ }
3833+ }
37283834
3729- const anything = new RegExp ( "[a-z]" , "i" ) ;
3835+ // build choices for ambiguous prefixes (those matching 2+ keywords)
3836+ const choices = [ ] ;
3837+
3838+ // group ambiguous prefixes by their matching keyword set
3839+ const keywordSetToPrefix = new Map ( ) ;
3840+ for ( const [ prefix , matchingKws ] of prefixToKeywords ) {
3841+ if ( matchingKws . length >= 2 ) {
3842+ const key = matchingKws . sort ( ) . join ( ":" ) ;
3843+ if ( ! keywordSetToPrefix . has ( key ) ) {
3844+ keywordSetToPrefix . set ( key , [ ] ) ;
3845+ }
3846+ keywordSetToPrefix . get ( key ) . push ( prefix ) ;
3847+ }
3848+ }
37303849
3731- return prec (
3732- - 10 , // low precedence, since this should serve as a fallback
3733- choice (
3734- alias ( anything , kwAlias ) ,
3735- alias ( new RegExp ( "REPLACED_TOKEN" , "i" ) , kwAlias ) ,
3736- alias ( new RegExp ( "REPLACED_TOKEN_WITH_QUOTE" , "i" ) , kwAlias )
3737- )
3738- ) ;
3850+ // create a regex for each unique keyword set
3851+ for ( const [ keySet , prefixes ] of keywordSetToPrefix ) {
3852+ const sortedPrefixes = prefixes . sort ( ( a , b ) => b . length - a . length ) ;
3853+ const pattern = sortedPrefixes . join ( "|" ) ;
3854+ const alias_name = `any_keyword:${ keySet } ` ;
3855+ choices . push ( alias ( new RegExp ( pattern , "i" ) , alias_name ) ) ;
3856+ }
3857+
3858+ // always include REPLACED_TOKEN with all keywords
3859+ choices . push ( alias ( new RegExp ( "REPLACED_TOKEN" , "i" ) , allKwAlias ) ) ;
3860+ choices . push ( alias ( new RegExp ( "REPLACED_TOKEN_WITH_QUOTE" , "i" ) , allKwAlias ) ) ;
3861+
3862+ return prec ( - 10 , choice ( ...choices ) ) ;
37393863}
0 commit comments