Repository: executablebooks/MyST-Parser
Pull request #718 (2023-02-23 08:58:57)

👌 Improve: Apply implicit targets to nested headings
This commit builds on ac111ea6f58508b0987091595edfb33db1ee033f,
to allow for implicit heading slugs to be applied also to nested headings,
i.e. so they can be referenced in Markdown links

codecov[bot]: # [Codecov](https://codecov.io/gh/executablebooks/MyST-Parser/pull/718?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) Report
Base: **89.80**% // Head: **89.81**% // Increases project coverage by **`+0.01%`** :tada:
> Coverage data is based on head [(`4d5ebf1`)](https://codecov.io/gh/executablebooks/MyST-Parser/pull/718?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) compared to base [(`7ecf1bb`)](https://codecov.io/gh/executablebooks/MyST-Parser/commit/7ecf1bb7b3f377ace86c86bb61d0d322a3937dce?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).
> Patch coverage: 89.47% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #718      +/-   ##
==========================================
+ Coverage   89.80%   89.81%   +0.01%     
==========================================
  Files          23       23              
  Lines        2883     2887       +4     
==========================================
+ Hits         2589     2593       +4     
  Misses        294      294              
```

| Flag | Coverage Δ | |
|---|---|---|
| pytests | `89.81% <89.47%> (+0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/executablebooks/MyST-Parser/pull/718?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) | Coverage Δ | |
|---|---|---|
| [myst\_parser/mdit\_to\_docutils/base.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/718?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9iYXNlLnB5) | `92.90% <89.47%> (+0.03%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/executablebooks/MyST-Parser/pull/718?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).


Patch:
diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py
index f95105f..b1de9d5 100644
--- a/myst_parser/mdit_to_docutils/base.py
+++ b/myst_parser/mdit_to_docutils/base.py
@@ -146,8 +146,8 @@ class DocutilsRenderer(RendererProtocol):
         self._level_to_section: dict[int, nodes.document | nodes.section] = {
             0: self.document
         }
-        # mapping of section slug to section node
-        self._slug_to_section: dict[str, nodes.section] = {}
+        # mapping of section slug to (line, id, implicit_text)
+        self._heading_slugs: dict[str, tuple[int | None, str, str]] = {}
 
     @property
     def sphinx_env(self) -> BuildEnvironment | None:
@@ -249,13 +249,11 @@ class DocutilsRenderer(RendererProtocol):
         """Finalise the render of the document."""
 
         # save for later reference resolution
-        slugs = {
-            slug: (snode.line, snode["ids"][0], clean_astext(snode[0]))
-            for slug, snode in self._slug_to_section.items()
-        }
-        self.document.myst_slugs = slugs
-        if slugs and self.sphinx_env:
-            self.sphinx_env.metadata[self.sphinx_env.docname]["myst_slugs"] = slugs
+        self.document.myst_slugs = self._heading_slugs
+        if self._heading_slugs and self.sphinx_env:
+            self.sphinx_env.metadata[self.sphinx_env.docname][
+                "myst_slugs"
+            ] = self._heading_slugs
 
         # log warnings for duplicate reference definitions
         # "duplicate_refs": [{"href": "ijk", "label": "B", "map": [4, 5], "title": ""}],
@@ -779,6 +777,52 @@ class DocutilsRenderer(RendererProtocol):
             and self.md_config.update_mathjax
         )
 
+    def generate_heading_target(
+        self,
+        token: SyntaxTreeNode,
+        level: int,
+        node: nodes.Element,
+        title_node: nodes.Element,
+    ) -> None:
+        """Generate a heading target, and add it to the document."""
+
+        implicit_text = clean_astext(title_node)
+
+        # create a target reference for the section, based on the heading text.
+        # Note, this is an implicit target, meaning that it is not prioritised,
+        # during ref resolution, and is not stored in the document.
+        # TODO this is purely to mimic docutils, but maybe we don't need it?
+        # (since we have the slugify logic below)
+        name = nodes.fully_normalize_name(implicit_text)
+        node["names"].append(name)
+        self.document.note_implicit_target(node, node)
+
+        if level > self.md_config.heading_anchors:
+            return
+
+        # Create an implicit reference slug.
+        # The problem with this reference slug,
+        # is that it might not be in the "normalised" format required by docutils,
+        # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#normalized-reference-names
+        # so we store it separately, and have separate logic than docutils
+        # TODO maybe revisit this assumption, or improve the logic
+        try:
+            slug = compute_unique_slug(
+                token,
+                self._heading_slugs,
+                self.md_config.heading_slug_func,
+            )
+        except Exception as error:
+            self.create_warning(
+                str(error),
+                MystWarnings.HEADING_SLUG,
+                line=token_line(token, default=0),
+                append_to=self.current_node,
+            )
+        else:
+            node["slug"] = slug
+            self._heading_slugs[slug] = (node.line, node["ids"][0], implicit_text)
+
     def render_heading(self, token: SyntaxTreeNode) -> None:
         """Render a heading, e.g. `# Heading`."""
 
@@ -802,6 +846,7 @@ class DocutilsRenderer(RendererProtocol):
             self.copy_attributes(token, rubric, ("class", "id"))
             with self.current_node_context(rubric, append=True):
                 self.render_children(token)
+            self.generate_heading_target(token, level, rubric, rubric)
             return
 
         # create the section node
@@ -825,39 +870,7 @@ class DocutilsRenderer(RendererProtocol):
         with self.current_node_context(title_node):
             self.render_children(token)
 
-        # create a target reference for the section, based on the heading text.
-        # Note, this is an implicit target, meaning that it is not prioritised,
-        # during ref resolution, and is not stored in the document.
-        # TODO this is purely to mimic docutils, but maybe we don't need it?
-        # (since we have the slugify logic below)
-        name = nodes.fully_normalize_name(title_node.astext())
-        new_section["names"].append(name)
-        self.document.note_implicit_target(new_section, new_section)
-
-        if level <= self.md_config.heading_anchors:
-
-            # Create an implicit reference slug.
-            # The problem with this reference slug,
-            # is that it might not be in the "normalised" format required by docutils,
-            # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#normalized-reference-names
-            # so we store it separately, and have separate logic than docutils
-            # TODO maybe revisit this assumption, or improve the logic
-            try:
-                slug = compute_unique_slug(
-                    token,
-                    self._slug_to_section,
-                    self.md_config.heading_slug_func,
-                )
-            except Exception as error:
-                self.create_warning(
-                    str(error),
-                    MystWarnings.HEADING_SLUG,
-                    line=token_line(token, default=0),
-                    append_to=self.current_node,
-                )
-            else:
-                new_section["slug"] = slug
-                self._slug_to_section[slug] = new_section
+        self.generate_heading_target(token, level, new_section, title_node)
 
         # set the section as the current node for subsequent rendering
         self.current_node = new_section


Test patch:
diff --git a/tests/test_renderers/fixtures/docutil_syntax_elements.md b/tests/test_renderers/fixtures/docutil_syntax_elements.md
index 5b99631..6badaa4 100644
--- a/tests/test_renderers/fixtures/docutil_syntax_elements.md
+++ b/tests/test_renderers/fixtures/docutil_syntax_elements.md
@@ -117,7 +117,7 @@ Nested heading
 .
 <document source="notset">
     <block_quote>
-        <rubric level="1">
+        <rubric ids="heading" level="1" names="heading">
             heading
 .
 
diff --git a/tests/test_renderers/fixtures/sphinx_syntax_elements.md b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
index 542f242..cdc72b7 100644
--- a/tests/test_renderers/fixtures/sphinx_syntax_elements.md
+++ b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
@@ -117,7 +117,7 @@ Nested heading
 .
 <document source="<src>/index.md">
     <block_quote>
-        <rubric level="1">
+        <rubric ids="heading" level="1" names="heading">
             heading
 .