@@ -232,14 +232,17 @@ fn parse_import_statement<'tree, 'text: 'tree>(
232232 // We loop so that if creating the `Import` fails, we can try any subsequent imports within
233233 // this node (e.g. in a comma-separated list) to avoid prematurely yielding `None` on the iterator.
234234 while idx < node. named_child_count ( ) {
235- debug_assert_eq ! ( node. field_name_for_named_child( idx as u32 ) , Some ( "name" ) ) ;
235+ let field_name = node. field_name_for_named_child ( idx as u32 ) ;
236236 let name_node = node. named_child ( idx) . expect ( "should be in-bounds" ) ;
237- let parsed = parse_field_child_node ( source_code, name_node) ;
238-
239237 idx += 1 ;
240238
241- if let Ok ( import) = Import :: try_new ( parsed. full_text , parsed. alias , None , false ) {
242- return Some ( import) ;
239+ if field_name != Some ( "name" ) {
240+ continue ;
241+ }
242+ if let Some ( parsed) = parse_field_child_node ( source_code, name_node) {
243+ if let Ok ( import) = Import :: try_new ( parsed. full_text , parsed. alias , None , false ) {
244+ return Some ( import) ;
245+ }
243246 }
244247 }
245248 None
@@ -254,7 +257,8 @@ fn parse_import_from_statement<'text>(
254257 debug_assert_eq ! ( node. kind( ) , "import_from_statement" ) ;
255258 let module_name_node = node. child_by_field_name ( "module_name" ) . expect ( FIELD_EXISTS ) ;
256259 let is_relative = module_name_node. kind ( ) == "relative_import" ;
257- let parsed_module = parse_field_child_node ( source_code, module_name_node) ;
260+ let parsed_module = parse_field_child_node ( source_code, module_name_node)
261+ . ok_or_else ( || format ! ( "invalid node type `{}`" , module_name_node. kind( ) ) ) ?;
258262 // Python syntax invariant: the module of a "from" import can't have an alias.
259263 debug_assert ! ( parsed_module. alias. is_none( ) ) ;
260264 // tree-sitter grammar invariant: a valid wildcard import must end in a `wildcard_import` node.
@@ -269,13 +273,13 @@ fn parse_import_from_statement<'text>(
269273 let field_children = node. children_by_field_name ( "name" , & mut cursor) ;
270274 let entities = field_children
271275 . into_iter ( )
272- . map ( |child| {
273- let parsed = parse_field_child_node ( source_code, child) ;
276+ . filter_map ( |child| {
277+ let parsed = parse_field_child_node ( source_code, child) ? ;
274278 // tree-sitter grammar invariant: these children represent entities, never a module.
275- Entity {
279+ Some ( Entity {
276280 name : parsed. full_text ,
277281 alias : parsed. alias ,
278- }
282+ } )
279283 } )
280284 . collect :: < Vec < _ > > ( ) ;
281285 ImportEntities :: Specific ( entities)
@@ -298,13 +302,13 @@ fn parse_future_import_statement<'text>(
298302 let field_children = node. children_by_field_name ( "name" , & mut cursor) ;
299303 let entities = field_children
300304 . into_iter ( )
301- . map ( |child| {
302- let parsed = parse_field_child_node ( source_code, child) ;
305+ . filter_map ( |child| {
306+ let parsed = parse_field_child_node ( source_code, child) ? ;
303307 // tree-sitter grammar invariant: these children represent entities, never a module.
304- Entity {
308+ Some ( Entity {
305309 name : parsed. full_text ,
306310 alias : parsed. alias ,
307- }
311+ } )
308312 } )
309313 . collect :: < Vec < _ > > ( ) ;
310314 Import :: try_new (
@@ -328,32 +332,32 @@ fn parse_future_import_statement<'text>(
328332fn parse_field_child_node < ' text > (
329333 source_code : & ' text str ,
330334 node : tree_sitter:: Node ,
331- ) -> MaybeAliased < ' text > {
335+ ) -> Option < MaybeAliased < ' text > > {
332336 match node. kind ( ) {
333337 "relative_import" => {
334338 // (relative_import (import_prefix) (dotted_name))
335339 for i in 0 ..node. child_count ( ) {
336340 let child = node. child ( i) . expect ( "i should be in-bounds" ) ;
337341 if child. kind ( ) == "dotted_name" {
338- return MaybeAliased {
342+ return Some ( MaybeAliased {
339343 full_text : ts_node_text ( source_code, child) ,
340344 alias : None ,
341- } ;
345+ } ) ;
342346 }
343347 }
344348 // Otherwise, this `relative_import` only contains an `import_prefix` node,
345349 // so return the entire node's text (which will consist of one or more "."):
346- MaybeAliased {
350+ Some ( MaybeAliased {
347351 full_text : ts_node_text ( source_code, node) ,
348352 alias : None ,
349- }
353+ } )
350354 }
351355 "dotted_name" => {
352356 // (dotted_name (identifier)+)
353- MaybeAliased {
357+ Some ( MaybeAliased {
354358 full_text : ts_node_text ( source_code, node) ,
355359 alias : None ,
356- }
360+ } )
357361 }
358362 "aliased_import" => {
359363 // (aliased_import name: (dotted_name) alias: (identifier))
@@ -363,18 +367,20 @@ fn parse_field_child_node<'text>(
363367 let alias_node = node. child_by_field_name ( "alias" ) . expect ( FIELD_EXISTS ) ;
364368 debug_assert_eq ! ( alias_node. kind( ) , "identifier" ) ;
365369 let alias = ts_node_text ( source_code, alias_node) ;
366- MaybeAliased {
370+ Some ( MaybeAliased {
367371 full_text,
368372 alias : Some ( alias) ,
369- }
373+ } )
370374 }
371- other => panic ! ( "invalid node type `{other}`" ) ,
375+ _ => None ,
372376 }
373377}
374378
375379#[ cfg( test) ]
376380mod tests {
377- use super :: { parse_imports, Entity , Import , ImportEntities } ;
381+ use super :: { parse_imports, parse_imports_with_tree, Entity , Import , ImportEntities } ;
382+ use crate :: analysis:: tree_sitter:: get_tree;
383+ use crate :: model:: common:: Language ;
378384
379385 /// A shorthand to build an [`Entity`] without an alias.
380386 pub fn ent ( name : & str ) -> Entity < ' _ > {
@@ -477,6 +483,30 @@ mod tests {
477483 }
478484 }
479485 }
486+
487+ #[ test]
488+ fn error_node_does_not_panic ( ) {
489+ let code = "import foo, + bar" ;
490+ let tree = get_tree ( code, & Language :: Python ) . unwrap ( ) ;
491+ assert ! ( tree. root_node( ) . has_error( ) ) ;
492+ let imports = parse_imports_with_tree ( code, & tree) ;
493+ assert_eq ! (
494+ imports,
495+ vec![
496+ Import :: try_new( "foo" , None , None , false ) . unwrap( ) ,
497+ Import :: try_new( "bar" , None , None , false ) . unwrap( ) ,
498+ ]
499+ ) ;
500+ }
501+
502+ #[ test]
503+ fn all_error_nodes_does_not_panic ( ) {
504+ let code = "import +, +" ;
505+ let tree = get_tree ( code, & Language :: Python ) . unwrap ( ) ;
506+ assert ! ( tree. root_node( ) . has_error( ) ) ;
507+ let imports = parse_imports_with_tree ( code, & tree) ;
508+ assert ! ( imports. is_empty( ) ) ;
509+ }
480510}
481511
482512/// mod for documenting (intentionally) "incorrect" parsing behavior.
0 commit comments