@@ -96,6 +96,15 @@ def __init__(
9696 self ._module = module
9797 self ._out_stream = out_stream
9898 self ._public_load_path = public_load_path
99+ self ._typedef_stack = []
100+
101+ def _get_colons (self ):
102+ # There's a weird behavior where increasing colon indents doesn't
103+ # parse as nested objects correctly, so we have to reduce the
104+ # number of colons based on the indent level
105+ indent = 10 - len (self ._typedef_stack )
106+ assert indent >= 0
107+ return ":::" + ":" * indent
99108
100109 def render (self ):
101110 self ._render_module (self ._module )
@@ -115,11 +124,10 @@ def _render_module(self, module: stardoc_output_pb2.ModuleInfo):
115124 "\n \n " ,
116125 )
117126
118- # Sort the objects by name
119127 objects = itertools .chain (
120128 ((r .rule_name , r , self ._render_rule ) for r in module .rule_info ),
121129 ((p .provider_name , p , self ._render_provider ) for p in module .provider_info ),
122- ((f .function_name , f , self ._render_func ) for f in module .func_info ),
130+ ((f .function_name , f , self ._process_func_info ) for f in module .func_info ),
123131 ((a .aspect_name , a , self ._render_aspect ) for a in module .aspect_info ),
124132 (
125133 (m .extension_name , m , self ._render_module_extension )
@@ -130,13 +138,31 @@ def _render_module(self, module: stardoc_output_pb2.ModuleInfo):
130138 for r in module .repository_rule_info
131139 ),
132140 )
141+ # Sort by name, ignoring case. The `.TYPEDEF` string is removed so
142+ # that the .TYPEDEF entries come before what is in the typedef.
143+ objects = sorted (objects , key = lambda v : v [0 ].removesuffix (".TYPEDEF" ).lower ())
133144
134- objects = sorted (objects , key = lambda v : v [0 ].lower ())
135-
136- for _ , obj , func in objects :
137- func (obj )
145+ for name , obj , func in objects :
146+ self ._process_object (name , obj , func )
138147 self ._write ("\n " )
139148
149+ # Close any typedefs
150+ while self ._typedef_stack :
151+ self ._typedef_stack .pop ()
152+ self ._render_typedef_end ()
153+
154+ def _process_object (self , name , obj , renderer ):
155+ # The trailing doc is added to prevent matching a common prefix
156+ typedef_group = name .removesuffix (".TYPEDEF" ) + "."
157+ while self ._typedef_stack and not typedef_group .startswith (
158+ self ._typedef_stack [- 1 ]
159+ ):
160+ self ._typedef_stack .pop ()
161+ self ._render_typedef_end ()
162+ renderer (obj )
163+ if name .endswith (".TYPEDEF" ):
164+ self ._typedef_stack .append (typedef_group )
165+
140166 def _render_aspect (self , aspect : stardoc_output_pb2 .AspectInfo ):
141167 _sort_attributes_inplace (aspect .attribute )
142168 self ._write ("::::::{bzl:aspect} " , aspect .aspect_name , "\n \n " )
@@ -242,12 +268,32 @@ def _rule_attr_type_string(self, attr: stardoc_output_pb2.AttributeInfo) -> str:
242268 # Rather than error, give some somewhat understandable value.
243269 return _AttributeType .Name (attr .type )
244270
271+ def _process_func_info (self , func ):
272+ if func .function_name .endswith (".TYPEDEF" ):
273+ self ._render_typedef_start (func )
274+ else :
275+ self ._render_func (func )
276+
277+ def _render_typedef_start (self , func ):
278+ self ._write (
279+ self ._get_colons (),
280+ "{bzl:typedef} " ,
281+ func .function_name .removesuffix (".TYPEDEF" ),
282+ "\n " ,
283+ )
284+ if func .doc_string :
285+ self ._write (func .doc_string .strip (), "\n " )
286+
287+ def _render_typedef_end (self ):
288+ self ._write (self ._get_colons (), "\n \n " )
289+
245290 def _render_func (self , func : stardoc_output_pb2 .StarlarkFunctionInfo ):
246- self ._write (":::::: {bzl:function} " )
291+ self ._write (self . _get_colons (), " {bzl:function} " )
247292
248293 parameters = self ._render_func_signature (func )
249294
250- self ._write (func .doc_string .strip (), "\n \n " )
295+ if doc_string := func .doc_string .strip ():
296+ self ._write (doc_string , "\n \n " )
251297
252298 if parameters :
253299 for param in parameters :
@@ -268,10 +314,13 @@ def _render_func(self, func: stardoc_output_pb2.StarlarkFunctionInfo):
268314 self ._write (":::::{deprecated}: unknown\n " )
269315 self ._write (" " , _indent_block_text (func .deprecated .doc_string ), "\n " )
270316 self ._write (":::::\n " )
271- self ._write (":::::: \n " )
317+ self ._write (self . _get_colons (), " \n " )
272318
273319 def _render_func_signature (self , func ):
274- self ._write (f"{ func .function_name } (" )
320+ func_name = func .function_name
321+ if self ._typedef_stack :
322+ func_name = func .function_name .removeprefix (self ._typedef_stack [- 1 ])
323+ self ._write (f"{ func_name } (" )
275324 # TODO: Have an "is method" directive in the docstring to decide if
276325 # the self parameter should be removed.
277326 parameters = [param for param in func .parameter if param .name != "self" ]
0 commit comments