@@ -4,12 +4,13 @@ pub use crate::goto_type_definition::goto_type_definition;
44
55use crate :: find_node:: covering_node;
66use crate :: stub_mapping:: StubMapper ;
7- use ruff_db:: parsed:: ParsedModuleRef ;
7+ use ruff_db:: parsed:: { ParsedModuleRef , parsed_module } ;
88use ruff_python_ast:: { self as ast, AnyNodeRef } ;
99use ruff_python_parser:: TokenKind ;
1010use ruff_text_size:: { Ranged , TextRange , TextSize } ;
1111use ty_python_semantic:: types:: Type ;
12- use ty_python_semantic:: { HasType , SemanticModel } ;
12+ use ty_python_semantic:: types:: definitions_for_keyword_argument;
13+ use ty_python_semantic:: { HasType , SemanticModel , definitions_for_name} ;
1314
1415#[ derive( Clone , Copy , Debug ) ]
1516pub ( crate ) enum GotoTarget < ' a > {
@@ -150,15 +151,19 @@ impl GotoTarget<'_> {
150151 use ruff_python_ast as ast;
151152
152153 match self {
153- // For names, find the definitions of the symbol
154- GotoTarget :: Expression ( expression) => {
155- if let ast:: ExprRef :: Name ( name) = expression {
156- Self :: get_name_definition_targets ( name, file, db, stub_mapper)
157- } else {
158- // For other expressions, we can't find definitions
159- None
160- }
161- }
154+ GotoTarget :: Expression ( expression) => match expression {
155+ ast:: ExprRef :: Name ( name) => definitions_to_navigation_targets (
156+ db,
157+ stub_mapper,
158+ definitions_for_name ( db, file, name) ,
159+ ) ,
160+ ast:: ExprRef :: Attribute ( attribute) => definitions_to_navigation_targets (
161+ db,
162+ stub_mapper,
163+ ty_python_semantic:: definitions_for_attribute ( db, file, attribute) ,
164+ ) ,
165+ _ => None ,
166+ } ,
162167
163168 // For already-defined symbols, they are their own definitions
164169 GotoTarget :: FunctionDef ( function) => {
@@ -195,40 +200,30 @@ impl GotoTarget<'_> {
195200 None
196201 }
197202
198- // TODO: Handle attribute and method accesses (y in `x.y` expressions)
199- // TODO: Handle keyword arguments in call expression
200- // TODO: Handle multi-part module names in import statements
201- // TODO: Handle imported symbol in y in `from x import y as z` statement
202- // TODO: Handle string literals that map to TypedDict fields
203- _ => None ,
204- }
205- }
203+ // Handle keyword arguments in call expressions
204+ GotoTarget :: KeywordArgument ( keyword) => {
205+ // Find the call expression that contains this keyword
206+ let module = parsed_module ( db, file) . load ( db) ;
206207
207- /// Get navigation targets for definitions associated with a name expression
208- fn get_name_definition_targets (
209- name : & ruff_python_ast:: ExprName ,
210- file : ruff_db:: files:: File ,
211- db : & dyn crate :: Db ,
212- stub_mapper : Option < & StubMapper > ,
213- ) -> Option < crate :: NavigationTargets > {
214- use ty_python_semantic:: definitions_for_name;
208+ // Use the keyword's range to find the containing call expression
209+ let covering_node = covering_node ( module. syntax ( ) . into ( ) , keyword. range ( ) )
210+ . find_first ( |node| matches ! ( node, AnyNodeRef :: ExprCall ( _) ) )
211+ . ok ( ) ?;
215212
216- // Get all definitions for this name
217- let mut definitions = definitions_for_name ( db, file, name) ;
213+ if let AnyNodeRef :: ExprCall ( call_expr) = covering_node. node ( ) {
214+ let definitions =
215+ definitions_for_keyword_argument ( db, file, keyword, call_expr) ;
216+ return definitions_to_navigation_targets ( db, stub_mapper, definitions) ;
217+ }
218218
219- // Apply stub mapping if a mapper is provided
220- if let Some ( mapper) = stub_mapper {
221- definitions = mapper. map_definitions ( definitions) ;
222- }
219+ None
220+ }
223221
224- if definitions. is_empty ( ) {
225- return None ;
222+ // TODO: Handle multi-part module names in import statements
223+ // TODO: Handle imported symbol in y in `from x import y as z` statement
224+ // TODO: Handle string literals that map to TypedDict fields
225+ _ => None ,
226226 }
227-
228- // Convert definitions to navigation targets
229- let targets = convert_resolved_definitions_to_targets ( db, definitions) ;
230-
231- Some ( crate :: NavigationTargets :: unique ( targets) )
232227 }
233228}
234229
@@ -279,18 +274,35 @@ fn convert_resolved_definitions_to_targets(
279274 full_range : full_range. range ( ) ,
280275 }
281276 }
282- ty_python_semantic:: ResolvedDefinition :: ModuleFile ( module_file ) => {
283- // For module files , navigate to the beginning of the file
277+ ty_python_semantic:: ResolvedDefinition :: FileWithRange ( file_range ) => {
278+ // For file ranges , navigate to the specific range within the file
284279 crate :: NavigationTarget {
285- file : module_file ,
286- focus_range : ruff_text_size :: TextRange :: default ( ) , // Start of file
287- full_range : ruff_text_size :: TextRange :: default ( ) , // Start of file
280+ file : file_range . file ( ) ,
281+ focus_range : file_range . range ( ) ,
282+ full_range : file_range . range ( ) ,
288283 }
289284 }
290285 } )
291286 . collect ( )
292287}
293288
289+ /// Shared helper to map and convert resolved definitions into navigation targets.
290+ fn definitions_to_navigation_targets < ' db > (
291+ db : & dyn crate :: Db ,
292+ stub_mapper : Option < & StubMapper < ' db > > ,
293+ mut definitions : Vec < ty_python_semantic:: ResolvedDefinition < ' db > > ,
294+ ) -> Option < crate :: NavigationTargets > {
295+ if let Some ( mapper) = stub_mapper {
296+ definitions = mapper. map_definitions ( definitions) ;
297+ }
298+ if definitions. is_empty ( ) {
299+ None
300+ } else {
301+ let targets = convert_resolved_definitions_to_targets ( db, definitions) ;
302+ Some ( crate :: NavigationTargets :: unique ( targets) )
303+ }
304+ }
305+
294306pub ( crate ) fn find_goto_target (
295307 parsed : & ParsedModuleRef ,
296308 offset : TextSize ,
0 commit comments