@@ -664,6 +664,26 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
664664 }
665665 }
666666 }
667+ ast:: Stmt :: Nonlocal ( nonlocal_stmt) => {
668+ // Handle nonlocal statements - classify identifiers as variables
669+ for identifier in & nonlocal_stmt. names {
670+ self . add_token (
671+ identifier. range ( ) ,
672+ SemanticTokenType :: Variable ,
673+ SemanticTokenModifier :: empty ( ) ,
674+ ) ;
675+ }
676+ }
677+ ast:: Stmt :: Global ( global_stmt) => {
678+ // Handle global statements - classify identifiers as variables
679+ for identifier in & global_stmt. names {
680+ self . add_token (
681+ identifier. range ( ) ,
682+ SemanticTokenType :: Variable ,
683+ SemanticTokenModifier :: empty ( ) ,
684+ ) ;
685+ }
686+ }
667687 _ => {
668688 // For all other statement types, let the default visitor handle them
669689 walk_stmt ( self , stmt) ;
@@ -831,6 +851,71 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
831851 }
832852 }
833853 }
854+
855+ fn visit_except_handler ( & mut self , except_handler : & ast:: ExceptHandler ) {
856+ match except_handler {
857+ ast:: ExceptHandler :: ExceptHandler ( handler) => {
858+ // Visit the exception type expression if present
859+ if let Some ( type_expr) = & handler. type_ {
860+ self . visit_expr ( type_expr) ;
861+ }
862+
863+ // Handle the exception variable name (after "as")
864+ if let Some ( name) = & handler. name {
865+ self . add_token (
866+ name. range ( ) ,
867+ SemanticTokenType :: Variable ,
868+ SemanticTokenModifier :: empty ( ) ,
869+ ) ;
870+ }
871+
872+ // Visit the handler body
873+ self . visit_body ( & handler. body ) ;
874+ }
875+ }
876+ }
877+
878+ fn visit_pattern ( & mut self , pattern : & ast:: Pattern ) {
879+ match pattern {
880+ ast:: Pattern :: MatchAs ( pattern_as) => {
881+ // Visit the nested pattern first to maintain source order
882+ if let Some ( nested_pattern) = & pattern_as. pattern {
883+ self . visit_pattern ( nested_pattern) ;
884+ }
885+
886+ // Now add the "as" variable name token
887+ if let Some ( name) = & pattern_as. name {
888+ self . add_token (
889+ name. range ( ) ,
890+ SemanticTokenType :: Variable ,
891+ SemanticTokenModifier :: empty ( ) ,
892+ ) ;
893+ }
894+ }
895+ ast:: Pattern :: MatchMapping ( pattern_mapping) => {
896+ // Visit keys and patterns in source order by interleaving them
897+ for ( key, nested_pattern) in
898+ pattern_mapping. keys . iter ( ) . zip ( & pattern_mapping. patterns )
899+ {
900+ self . visit_expr ( key) ;
901+ self . visit_pattern ( nested_pattern) ;
902+ }
903+
904+ // Handle the rest parameter (after "**") - this comes last
905+ if let Some ( rest_name) = & pattern_mapping. rest {
906+ self . add_token (
907+ rest_name. range ( ) ,
908+ SemanticTokenType :: Variable ,
909+ SemanticTokenModifier :: empty ( ) ,
910+ ) ;
911+ }
912+ }
913+ _ => {
914+ // For all other pattern types, use the default walker
915+ ruff_python_ast:: visitor:: source_order:: walk_pattern ( self , pattern) ;
916+ }
917+ }
918+ }
834919}
835920
836921#[ cfg( test) ]
@@ -1942,4 +2027,200 @@ complex_fstring = f"User: {name.upper()}, Count: {len(data)}, Hex: {value:x}"<CU
19422027 "x" @ 414..415: String
19432028 "# ) ;
19442029 }
2030+
2031+ #[ test]
2032+ fn test_nonlocal_and_global_statements ( ) {
2033+ let test = cursor_test (
2034+ r#"
2035+ x = "global_value"
2036+ y = "another_global"
2037+
2038+ def outer():
2039+ x = "outer_value"
2040+ z = "outer_local"
2041+
2042+ def inner():
2043+ nonlocal x, z # These should be variable tokens
2044+ global y # This should be a variable token
2045+ x = "modified"
2046+ y = "modified_global"
2047+ z = "modified_local"
2048+
2049+ def deeper():
2050+ nonlocal x # Variable token
2051+ global y, x # Both should be variable tokens
2052+ return x + y
2053+
2054+ return deeper
2055+
2056+ return inner<CURSOR>
2057+ "# ,
2058+ ) ;
2059+
2060+ let tokens = semantic_tokens_full_file ( & test. db , test. cursor . file ) ;
2061+
2062+ assert_snapshot ! ( semantic_tokens_to_snapshot( & test. db, test. cursor. file, & tokens) , @r#"
2063+ "x" @ 1..2: Variable
2064+ "/"global_value/"" @ 5..19: String
2065+ "y" @ 20..21: Variable
2066+ "/"another_global/"" @ 24..40: String
2067+ "outer" @ 46..51: Function [definition]
2068+ "x" @ 59..60: Variable
2069+ "/"outer_value/"" @ 63..76: String
2070+ "z" @ 81..82: Variable
2071+ "/"outer_local/"" @ 85..98: String
2072+ "inner" @ 112..117: Function [definition]
2073+ "x" @ 138..139: Variable
2074+ "z" @ 141..142: Variable
2075+ "y" @ 193..194: Variable
2076+ "x" @ 243..244: Variable
2077+ "/"modified/"" @ 247..257: String
2078+ "y" @ 266..267: Variable
2079+ "/"modified_global/"" @ 270..287: String
2080+ "z" @ 296..297: Variable
2081+ "/"modified_local/"" @ 300..316: String
2082+ "deeper" @ 338..344: Function [definition]
2083+ "x" @ 369..370: Variable
2084+ "y" @ 410..411: Variable
2085+ "x" @ 413..414: Variable
2086+ "x" @ 469..470: Variable
2087+ "y" @ 473..474: Variable
2088+ "deeper" @ 499..505: Function
2089+ "inner" @ 522..527: Function
2090+ "# ) ;
2091+ }
2092+
2093+ #[ test]
2094+ fn test_nonlocal_global_edge_cases ( ) {
2095+ let test = cursor_test (
2096+ r#"
2097+ # Single variable statements
2098+ def test():
2099+ global x
2100+ nonlocal y
2101+
2102+ # Multiple variables in one statement
2103+ global a, b, c
2104+ nonlocal d, e, f
2105+
2106+ return x + y + a + b + c + d + e + f<CURSOR>
2107+ "# ,
2108+ ) ;
2109+
2110+ let tokens = semantic_tokens_full_file ( & test. db , test. cursor . file ) ;
2111+
2112+ assert_snapshot ! ( semantic_tokens_to_snapshot( & test. db, test. cursor. file, & tokens) , @r#"
2113+ "test" @ 34..38: Function [definition]
2114+ "x" @ 53..54: Variable
2115+ "y" @ 68..69: Variable
2116+ "a" @ 128..129: Variable
2117+ "b" @ 131..132: Variable
2118+ "c" @ 134..135: Variable
2119+ "d" @ 149..150: Variable
2120+ "e" @ 152..153: Variable
2121+ "f" @ 155..156: Variable
2122+ "x" @ 173..174: Variable
2123+ "y" @ 177..178: Variable
2124+ "a" @ 181..182: Variable
2125+ "b" @ 185..186: Variable
2126+ "c" @ 189..190: Variable
2127+ "d" @ 193..194: Variable
2128+ "e" @ 197..198: Variable
2129+ "f" @ 201..202: Variable
2130+ "# ) ;
2131+ }
2132+
2133+ #[ test]
2134+ fn test_pattern_matching ( ) {
2135+ let test = cursor_test (
2136+ r#"
2137+ def process_data(data):
2138+ match data:
2139+ case {"name": name, "age": age, **rest} as person:
2140+ print(f"Person {name}, age {age}, extra: {rest}")
2141+ return person
2142+ case [first, *remaining] as sequence:
2143+ print(f"First: {first}, remaining: {remaining}")
2144+ return sequence
2145+ case value as fallback:
2146+ print(f"Fallback: {fallback}")
2147+ return fallback<CURSOR>
2148+ "# ,
2149+ ) ;
2150+
2151+ let tokens = semantic_tokens_full_file ( & test. db , test. cursor . file ) ;
2152+
2153+ assert_snapshot ! ( semantic_tokens_to_snapshot( & test. db, test. cursor. file, & tokens) , @r#"
2154+ "process_data" @ 5..17: Function [definition]
2155+ "data" @ 18..22: Parameter
2156+ "data" @ 35..39: Variable
2157+ "/"name/"" @ 55..61: String
2158+ "name" @ 63..67: Variable
2159+ "/"age/"" @ 69..74: String
2160+ "age" @ 76..79: Variable
2161+ "rest" @ 83..87: Variable
2162+ "person" @ 92..98: Variable
2163+ "print" @ 112..117: Function
2164+ "Person " @ 120..127: String
2165+ "name" @ 128..132: Variable
2166+ ", age " @ 133..139: String
2167+ "age" @ 140..143: Variable
2168+ ", extra: " @ 144..153: String
2169+ "rest" @ 154..158: Variable
2170+ "person" @ 181..187: Variable
2171+ "first" @ 202..207: Variable
2172+ "sequence" @ 224..232: Variable
2173+ "print" @ 246..251: Function
2174+ "First: " @ 254..261: String
2175+ "first" @ 262..267: Variable
2176+ ", remaining: " @ 268..281: String
2177+ "remaining" @ 282..291: Variable
2178+ "sequence" @ 314..322: Variable
2179+ "value" @ 336..341: Variable
2180+ "fallback" @ 345..353: Variable
2181+ "print" @ 367..372: Function
2182+ "Fallback: " @ 375..385: String
2183+ "fallback" @ 386..394: Variable
2184+ "fallback" @ 417..425: Variable
2185+ "# ) ;
2186+ }
2187+
2188+ #[ test]
2189+ fn test_exception_handlers ( ) {
2190+ let test = cursor_test (
2191+ r#"
2192+ try:
2193+ x = 1 / 0
2194+ except ValueError as ve:
2195+ print(ve)
2196+ except (TypeError, RuntimeError) as re:
2197+ print(re)
2198+ except Exception as e:
2199+ print(e)
2200+ finally:
2201+ pass<CURSOR>
2202+ "# ,
2203+ ) ;
2204+
2205+ let tokens = semantic_tokens_full_file ( & test. db , test. cursor . file ) ;
2206+
2207+ assert_snapshot ! ( semantic_tokens_to_snapshot( & test. db, test. cursor. file, & tokens) , @r#"
2208+ "x" @ 10..11: Variable
2209+ "1" @ 14..15: Number
2210+ "0" @ 18..19: Number
2211+ "ValueError" @ 27..37: Class
2212+ "ve" @ 41..43: Variable
2213+ "print" @ 49..54: Function
2214+ "ve" @ 55..57: Variable
2215+ "TypeError" @ 67..76: Class
2216+ "RuntimeError" @ 78..90: Class
2217+ "re" @ 95..97: Variable
2218+ "print" @ 103..108: Function
2219+ "re" @ 109..111: Variable
2220+ "Exception" @ 120..129: Class
2221+ "e" @ 133..134: Variable
2222+ "print" @ 140..145: Function
2223+ "e" @ 146..147: Variable
2224+ "# ) ;
2225+ }
19452226}
0 commit comments