@@ -291,34 +291,51 @@ def end_file(self) -> None:
291291 self .walking = False
292292 self .policy_file = None
293293
294- def add_bundle (self , name : str ) -> None :
294+ def add_bundle (self , name : str , node : Node ) -> None :
295295 """This is called during discovery wherever a bundle is defined.
296296
297297 For example:
298298 bundle agent my_bundle {}
299299
300+ We create a dictionary where the key is the qualified name,
301+ and the value is a list of all the definionts. This is because the
302+ same qualified name can appear multiple times (e.g. inside macro
303+ if/else branches). Each definition records the file, line number,
304+ and parameter list.
300305 """
301306 name = _qualify (name , self .namespace )
302- # TODO: In the future we will record more information than True, like:
303- # - Can be a list / dict of all places a bundle with that
304- # qualified name is defined in cases there are duplicates.
305- # - Can record the location of each definition
306- # - Can record the parameters / signature
307- # - Can record whether the bundle is inside a macro
308- # - Can have a list of classes and vars defined inside
309- self .bundles [name ] = {"is_defined" : True }
310-
311- def add_body (self , name : str ) -> None :
307+ assert self .policy_file
308+ line = node .range .start_point [0 ] + 1
309+ definition = {
310+ "filename" : self .policy_file .filename ,
311+ "line" : line ,
312+ }
313+ if name not in self .bundles :
314+ self .bundles [name ] = []
315+ self .bundles [name ].append (definition )
316+
317+ def add_body (self , name : str , node : Node ) -> None :
312318 """This is called during discovery wherever a body is defined.
313319
314320 For example:
315321 body contain my_body {}
316322
317323 Control bundles are a special case, so would not be called for:
318324 body file control {}
325+
326+ Similar to add_bundles, we record a list of definitions, since
327+ each body can be defined multiple times (inside different macros).
319328 """
320329 name = _qualify (name , self .namespace )
321- self .bodies [name ] = {"is_defined" : True }
330+ assert self .policy_file
331+ line = node .range .start_point [0 ] + 1
332+ definition = {
333+ "filename" : self .policy_file .filename ,
334+ "line" : line ,
335+ }
336+ if name not in self .bodies :
337+ self .bodies [name ] = []
338+ self .bodies [name ].append (definition )
322339
323340 def add_promise_type (self , name : str ) -> None :
324341 """This is called during discovery wherever a custom promise type is
@@ -479,23 +496,23 @@ def _discover_node(node: Node, state: State) -> int:
479496 name = _text (node )
480497 if name == "control" :
481498 return 0 # No need to define control blocks
482- state .add_body (name )
499+ state .add_body (name , node )
483500 qualified_name = _qualify (name , state .namespace )
484501 if (n := node .next_named_sibling ) and n .type == "parameter_list" :
485502 _ , * args , _ = n .children
486503 args = list (filter ("," .__ne__ , iter (_text (x ) for x in args )))
487- state .bodies [qualified_name ]. update ({ "parameters" : args })
504+ state .bodies [qualified_name ][ - 1 ][ "parameters" ] = args
488505 return 0
489506
490507 # Define bundles:
491508 if node .type == "bundle_block_name" :
492509 name = _text (node )
493510 qualified_name = _qualify (name , state .namespace )
494- state .add_bundle (name )
511+ state .add_bundle (name , node )
495512 if (n := node .next_named_sibling ) and n .type == "parameter_list" :
496513 _ , * args , _ = n .children
497514 args = list (filter ("," .__ne__ , iter (_text (x ) for x in args )))
498- state .bundles [qualified_name ]. update ({ "parameters" : args })
515+ state .bundles [qualified_name ][ - 1 ][ "parameters" ] = args
499516 return 0
500517
501518 # Define custom promise types:
@@ -727,19 +744,25 @@ def _lint_node(
727744
728745 qualified_name = _qualify (call , state .namespace )
729746 if qualified_name in state .bundles :
730- max_args = len (state .bundles [qualified_name ].get ("parameters" , []))
731- if max_args != len (args ):
747+ definitions = state .bundles [qualified_name ]
748+ valid_counts = {len (d .get ("parameters" , [])) for d in definitions }
749+ if len (args ) not in valid_counts :
732750 _highlight_range (node , lines )
751+ counts = sorted (valid_counts )
752+ expected = " or " .join (str (c ) for c in counts )
733753 print (
734- f"Error: Expected { max_args } arguments, received { len (args )} for bundle '{ call } ' { location } "
754+ f"Error: Expected { expected } arguments, received { len (args )} for bundle '{ call } ' { location } "
735755 )
736756 return 1
737757 if qualified_name in state .bodies :
738- max_args = len (state .bodies [qualified_name ].get ("parameters" , []))
739- if max_args != len (args ):
758+ definitions = state .bodies [qualified_name ]
759+ valid_counts = {len (d .get ("parameters" , [])) for d in definitions }
760+ if len (args ) not in valid_counts :
740761 _highlight_range (node , lines )
762+ counts = sorted (valid_counts )
763+ expected = " or " .join (str (c ) for c in counts )
741764 print (
742- f"Error: Expected { max_args } arguments, received { len (args )} for body '{ call } ' { location } "
765+ f"Error: Expected { expected } arguments, received { len (args )} for body '{ call } ' { location } "
743766 )
744767 return 1
745768
0 commit comments