@@ -232,14 +232,13 @@ 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" ) ) ;
236235 let name_node = node. named_child ( idx) . expect ( "should be in-bounds" ) ;
237- let parsed = parse_field_child_node ( source_code, name_node) ;
238-
239236 idx += 1 ;
240237
241- if let Ok ( import) = Import :: try_new ( parsed. full_text , parsed. alias , None , false ) {
242- return Some ( import) ;
238+ if let Some ( parsed) = parse_field_child_node ( source_code, name_node) {
239+ if let Ok ( import) = Import :: try_new ( parsed. full_text , parsed. alias , None , false ) {
240+ return Some ( import) ;
241+ }
243242 }
244243 }
245244 None
@@ -254,7 +253,8 @@ fn parse_import_from_statement<'text>(
254253 debug_assert_eq ! ( node. kind( ) , "import_from_statement" ) ;
255254 let module_name_node = node. child_by_field_name ( "module_name" ) . expect ( FIELD_EXISTS ) ;
256255 let is_relative = module_name_node. kind ( ) == "relative_import" ;
257- let parsed_module = parse_field_child_node ( source_code, module_name_node) ;
256+ let parsed_module = parse_field_child_node ( source_code, module_name_node)
257+ . ok_or_else ( || format ! ( "invalid node type `{}`" , module_name_node. kind( ) ) ) ?;
258258 // Python syntax invariant: the module of a "from" import can't have an alias.
259259 debug_assert ! ( parsed_module. alias. is_none( ) ) ;
260260 // tree-sitter grammar invariant: a valid wildcard import must end in a `wildcard_import` node.
@@ -269,13 +269,13 @@ fn parse_import_from_statement<'text>(
269269 let field_children = node. children_by_field_name ( "name" , & mut cursor) ;
270270 let entities = field_children
271271 . into_iter ( )
272- . map ( |child| {
273- let parsed = parse_field_child_node ( source_code, child) ;
272+ . filter_map ( |child| {
273+ let parsed = parse_field_child_node ( source_code, child) ? ;
274274 // tree-sitter grammar invariant: these children represent entities, never a module.
275- Entity {
275+ Some ( Entity {
276276 name : parsed. full_text ,
277277 alias : parsed. alias ,
278- }
278+ } )
279279 } )
280280 . collect :: < Vec < _ > > ( ) ;
281281 ImportEntities :: Specific ( entities)
@@ -298,13 +298,13 @@ fn parse_future_import_statement<'text>(
298298 let field_children = node. children_by_field_name ( "name" , & mut cursor) ;
299299 let entities = field_children
300300 . into_iter ( )
301- . map ( |child| {
302- let parsed = parse_field_child_node ( source_code, child) ;
301+ . filter_map ( |child| {
302+ let parsed = parse_field_child_node ( source_code, child) ? ;
303303 // tree-sitter grammar invariant: these children represent entities, never a module.
304- Entity {
304+ Some ( Entity {
305305 name : parsed. full_text ,
306306 alias : parsed. alias ,
307- }
307+ } )
308308 } )
309309 . collect :: < Vec < _ > > ( ) ;
310310 Import :: try_new (
@@ -318,42 +318,37 @@ fn parse_future_import_statement<'text>(
318318}
319319
320320/// Constructs a [`MaybeAliased`] from the provided `node`. This is intended to be used on
321- /// the `name` and `module_name` field children.
322- ///
323- /// # Panics
324- /// Panics if the provided node isn't a:
325- /// * `dotted_name`
326- /// * `aliased_import`
327- /// * `relative_import`
321+ /// the `name` and `module_name` field children. Returns `None` if the node isn't a
322+ /// `relative_import`, `dotted_name`, or an `aliased_import`.
328323fn parse_field_child_node < ' text > (
329324 source_code : & ' text str ,
330325 node : tree_sitter:: Node ,
331- ) -> MaybeAliased < ' text > {
326+ ) -> Option < MaybeAliased < ' text > > {
332327 match node. kind ( ) {
333328 "relative_import" => {
334329 // (relative_import (import_prefix) (dotted_name))
335330 for i in 0 ..node. child_count ( ) {
336331 let child = node. child ( i) . expect ( "i should be in-bounds" ) ;
337332 if child. kind ( ) == "dotted_name" {
338- return MaybeAliased {
333+ return Some ( MaybeAliased {
339334 full_text : ts_node_text ( source_code, child) ,
340335 alias : None ,
341- } ;
336+ } ) ;
342337 }
343338 }
344339 // Otherwise, this `relative_import` only contains an `import_prefix` node,
345340 // so return the entire node's text (which will consist of one or more "."):
346- MaybeAliased {
341+ Some ( MaybeAliased {
347342 full_text : ts_node_text ( source_code, node) ,
348343 alias : None ,
349- }
344+ } )
350345 }
351346 "dotted_name" => {
352347 // (dotted_name (identifier)+)
353- MaybeAliased {
348+ Some ( MaybeAliased {
354349 full_text : ts_node_text ( source_code, node) ,
355350 alias : None ,
356- }
351+ } )
357352 }
358353 "aliased_import" => {
359354 // (aliased_import name: (dotted_name) alias: (identifier))
@@ -363,18 +358,20 @@ fn parse_field_child_node<'text>(
363358 let alias_node = node. child_by_field_name ( "alias" ) . expect ( FIELD_EXISTS ) ;
364359 debug_assert_eq ! ( alias_node. kind( ) , "identifier" ) ;
365360 let alias = ts_node_text ( source_code, alias_node) ;
366- MaybeAliased {
361+ Some ( MaybeAliased {
367362 full_text,
368363 alias : Some ( alias) ,
369- }
364+ } )
370365 }
371- other => panic ! ( "invalid node type `{other}`" ) ,
366+ _ => None ,
372367 }
373368}
374369
375370#[ cfg( test) ]
376371mod tests {
377- use super :: { parse_imports, Entity , Import , ImportEntities } ;
372+ use super :: { parse_imports, parse_imports_with_tree, Entity , Import , ImportEntities } ;
373+ use crate :: analysis:: tree_sitter:: get_tree;
374+ use crate :: model:: common:: Language ;
378375
379376 /// A shorthand to build an [`Entity`] without an alias.
380377 pub fn ent ( name : & str ) -> Entity < ' _ > {
@@ -477,6 +474,30 @@ mod tests {
477474 }
478475 }
479476 }
477+
478+ #[ test]
479+ fn parse_imports_error_node ( ) {
480+ let code = "import foo, + bar" ;
481+ let tree = get_tree ( code, & Language :: Python ) . unwrap ( ) ;
482+ assert ! ( tree. root_node( ) . has_error( ) ) ;
483+ let imports = parse_imports_with_tree ( code, & tree) ;
484+ assert_eq ! (
485+ imports,
486+ vec![
487+ Import :: try_new( "foo" , None , None , false ) . unwrap( ) ,
488+ Import :: try_new( "bar" , None , None , false ) . unwrap( ) ,
489+ ]
490+ ) ;
491+ }
492+
493+ #[ test]
494+ fn parse_imports_all_error_nodes ( ) {
495+ let code = "import +, +" ;
496+ let tree = get_tree ( code, & Language :: Python ) . unwrap ( ) ;
497+ assert ! ( tree. root_node( ) . has_error( ) ) ;
498+ let imports = parse_imports_with_tree ( code, & tree) ;
499+ assert ! ( imports. is_empty( ) ) ;
500+ }
480501}
481502
482503/// mod for documenting (intentionally) "incorrect" parsing behavior.
0 commit comments