From d57aab059b7782387a9410ce91e6d711ab377880 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Fri, 5 Dec 2025 14:17:57 -0500 Subject: [PATCH 1/4] fix: wrap None in code when used as annotation --- quartodoc/renderers/md_renderer.py | 36 +++++++++++++++---- .../tests/__snapshots__/test_renderers.ambr | 10 +++--- quartodoc/tests/test_renderers.py | 21 +++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 72d12430..8a690471 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -114,6 +114,7 @@ def to_tuple(self, style: Literal["parameters", "attributes", "returns"]): @dataclass class SummaryRow: """Represents a row in a summary table.""" + link: str description: str @@ -300,12 +301,26 @@ def render_annotation(self, el: str) -> str: Parameters ---------- el: - An object representing a type annotation. + An object representing a type annotation. Can be a structural string + (like '[', ']', ' | '), literal contents, or "None" when None is used + as in "x: int | None". """ + # Special case for None - it's used as shorthand for NoneType in type annotations + # e.g., "int | None" is common for Optional types + if el == "None": + if self.render_interlinks: + # Render as markdown link like other types + return f"[None](`None`)" + else: + # Render with backticks for consistency with other types + return "None" + + # For structural strings (brackets, operators, etc.), use existing logic return sanitize(el, escape_quotes=True) @dispatch def render_annotation(self, el: None) -> str: + # this is used to indicate no annotation, not the literal None return "" @dispatch @@ -490,13 +505,16 @@ def render(self, el: Union[layout.DocClass, layout.DocModule]): # TODO: for now, we skip making an attribute table on classes, unless # they contain an attributes section in the docstring if ( - raw_attrs and not _has_attr_section(el.obj.docstring) + raw_attrs + and not _has_attr_section(el.obj.docstring) # TODO: what should backwards compat be? # and not isinstance(el, layout.DocClass) ): # Collect SummaryRow objects and render as TOC attr_rows = [self.summarize(attr) for attr in raw_attrs] - _attrs_table = self._render_summary_table(attr_rows, self.table_style_tocs, include_headers=True) + _attrs_table = self._render_summary_table( + attr_rows, self.table_style_tocs, include_headers=True + ) attrs = f"{sub_header} Attributes\n\n{_attrs_table}" attr_docs.append(attrs) @@ -504,7 +522,9 @@ def render(self, el: Union[layout.DocClass, layout.DocModule]): if raw_classes: # Collect SummaryRow objects and render as TOC class_rows = [self.summarize(cls) for cls in raw_classes] - _summary_table = self._render_summary_table(class_rows, self.table_style_tocs, include_headers=True) + _summary_table = self._render_summary_table( + class_rows, self.table_style_tocs, include_headers=True + ) section_name = "Classes" objs = f"{sub_header} {section_name}\n\n{_summary_table}" class_docs.append(objs) @@ -523,7 +543,9 @@ def render(self, el: Union[layout.DocClass, layout.DocModule]): if raw_meths: # Collect SummaryRow objects and render as TOC meth_rows = [self.summarize(meth) for meth in raw_meths] - _summary_table = self._render_summary_table(meth_rows, self.table_style_tocs, include_headers=True) + _summary_table = self._render_summary_table( + meth_rows, self.table_style_tocs, include_headers=True + ) section_name = ( "Methods" if isinstance(el, layout.DocClass) else "Functions" ) @@ -821,7 +843,9 @@ def render(self, el): # layout.Section, or a row in the table for layout.Page or layout.DocFunction. def _summary_row(self, link, description): - return SummaryRow(link=link, description=sanitize(description, allow_markdown=True)) + return SummaryRow( + link=link, description=sanitize(description, allow_markdown=True) + ) # Summarization methods --------------------------------------------------- diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index 9a848944..75443c09 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -13,11 +13,11 @@ ## Parameters {.doc-section .doc-section-parameters} - | Name | Type | Description | Default | - |--------|--------------------------------------------------------------------------------------|-------------------------------|------------| - | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ | - | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | - | z | | The z parameter (unannotated) | _required_ | + | Name | Type | Description | Default | + |--------|------------------------------------------------------------------------------------------------|-------------------------------|------------| + | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| [None](`None`)\] | The x parameter | _required_ | + | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | + | z | | The z parameter (unannotated) | _required_ | ''' # --- # name: test_render_annotations_complex_no_interlinks diff --git a/quartodoc/tests/test_renderers.py b/quartodoc/tests/test_renderers.py index 89f09e8c..a7636bd7 100644 --- a/quartodoc/tests/test_renderers.py +++ b/quartodoc/tests/test_renderers.py @@ -213,6 +213,27 @@ def test_render_annotations_complex_no_interlinks(snapshot): assert res == snapshot +def test_render_annotation_none(): + """Test rendering of None in type annotations. + + None is special in type annotations - it's used as shorthand for NoneType + in annotations like 'int | None'. It should be rendered consistently with + other types like int and str. + """ + renderer_with_links = MdRenderer(render_interlinks=True) + renderer_no_links = MdRenderer(render_interlinks=False) + + # Test None with interlinks: should be markdown link + with_links = renderer_with_links.render_annotation("None") + assert ( + with_links == "[None](`None`)" + ), "None with interlinks should be markdown link" + + # Test None without interlinks: should have backticks + without_links = renderer_no_links.render_annotation("None") + assert without_links == "None", "None without interlinks should have no backticks" + + @pytest.mark.parametrize("children", ["embedded", "flat"]) def test_render_doc_class(snapshot, renderer, children): bp = blueprint(Auto(name="quartodoc.tests.example_class.C", children=children)) From d337b83e56607e5830a4d75c731fc7e5df856e7b Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Mon, 8 Dec 2025 16:42:19 -0500 Subject: [PATCH 2/4] Update quartodoc/renderers/md_renderer.py --- quartodoc/renderers/md_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 8a690471..d6184028 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -312,7 +312,7 @@ def render_annotation(self, el: str) -> str: # Render as markdown link like other types return f"[None](`None`)" else: - # Render with backticks for consistency with other types + # Render without backticks like any instance (e.g. 1, "a") return "None" # For structural strings (brackets, operators, etc.), use existing logic From 1f678781c16b163c1e42d3830f3883bab62ac7e7 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Mon, 8 Dec 2025 16:47:10 -0500 Subject: [PATCH 3/4] tests: update snapshot --- .../tests/__snapshots__/test_renderers.ambr | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index 2befccf0..26350008 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -13,15 +13,15 @@ ## Parameters {.doc-section .doc-section-parameters} - +--------+--------------------------------------------------------------------------------------+-------------------------------+------------+ - | Name | Type | Description | Default | - +========+======================================================================================+===============================+============+ - | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ | - +--------+--------------------------------------------------------------------------------------+-------------------------------+------------+ - | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | - +--------+--------------------------------------------------------------------------------------+-------------------------------+------------+ - | z | | The z parameter (unannotated) | _required_ | - +--------+--------------------------------------------------------------------------------------+-------------------------------+------------+ + +--------+------------------------------------------------------------------------------------------------+-------------------------------+------------+ + | Name | Type | Description | Default | + +========+================================================================================================+===============================+============+ + | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| [None](`None`)\] | The x parameter | _required_ | + +--------+------------------------------------------------------------------------------------------------+-------------------------------+------------+ + | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | + +--------+------------------------------------------------------------------------------------------------+-------------------------------+------------+ + | z | | The z parameter (unannotated) | _required_ | + +--------+------------------------------------------------------------------------------------------------+-------------------------------+------------+ ''' # --- # name: test_render_annotations_complex_no_interlinks From 4cf65bee0b9590cbb5bedbfaa29e239479ce8149 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Mon, 8 Dec 2025 17:10:44 -0500 Subject: [PATCH 4/4] docs: skip method documentation for now --- quartodoc/renderers/md_renderer.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index f8476444..af912527 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -311,14 +311,7 @@ def _render_summary_table(rows, style_param, include_headers=False): @dispatch def render_annotation(self, el: str) -> str: - """Special hook for rendering a type annotation. - Parameters - ---------- - el: - An object representing a type annotation. Can be a structural string - (like '[', ']', ' | '), literal contents, or "None" when None is used - as in "x: int | None". - """ + """Special hook for rendering a type annotation.""" # Special case for None - it's used as shorthand for NoneType in type annotations # e.g., "int | None" is common for Optional types if el == "None":