Repository: executablebooks/MyST-Parser
Pull request #655 (2022-12-13 15:18:41)

✨ NEW: suppress warnings in docutils
with `myst_suppress_warnings` option, which works the same as sphinx `suppress_warnings`.

codecov[bot]: # [Codecov](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) Report
Base: **89.83**% // Head: **89.95**% // Increases project coverage by **`+0.12%`** :tada:
> Coverage data is based on head [(`a6a4347`)](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) compared to base [(`c2f0416`)](https://codecov.io/gh/executablebooks/MyST-Parser/commit/c2f041632825ffe9837ebdded4ed324d4288cfa7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).
> Patch coverage: 95.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff             @@
##           master     #655      +/-   ##
==========================================
+ Coverage   89.83%   89.95%   +0.12%     
==========================================
  Files          21       22       +1     
  Lines        2154     2191      +37     
==========================================
+ Hits         1935     1971      +36     
- Misses        219      220       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| pytests | `89.95% <95.00%> (+0.12%)` | :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/655?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) | Coverage Δ | |
|---|---|---|
| [myst\_parser/config/main.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvY29uZmlnL21haW4ucHk=) | `87.15% <83.33%> (+0.24%)` | :arrow_up: |
| [myst\_parser/warnings\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvd2FybmluZ3NfLnB5) | `95.45% <95.45%> (ø)` | |
| [myst\_parser/mdit\_to\_docutils/base.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9iYXNlLnB5) | `92.07% <100.00%> (-0.06%)` | :arrow_down: |
| [myst\_parser/mdit\_to\_docutils/html\_to\_nodes.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9odG1sX3RvX25vZGVzLnB5) | `90.90% <100.00%> (+0.16%)` | :arrow_up: |
| [myst\_parser/mdit\_to\_docutils/sphinx\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9zcGhpbnhfLnB5) | `94.62% <100.00%> (+0.44%)` | :arrow_up: |
| [myst\_parser/parsers/docutils\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvcGFyc2Vycy9kb2N1dGlsc18ucHk=) | `81.57% <100.00%> (+0.49%)` | :arrow_up: |
| [myst\_parser/parsers/sphinx\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvcGFyc2Vycy9zcGhpbnhfLnB5) | `100.00% <100.00%> (ø)` | |
| [myst\_parser/sphinx\_ext/myst\_refs.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/655/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvc3BoaW54X2V4dC9teXN0X3JlZnMucHk=) | `90.51% <100.00%> (+0.06%)` | :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/655?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/CHANGELOG.md b/CHANGELOG.md
index 0fe72f4..37a50b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -437,7 +437,7 @@ A warning (of type `myst.nested_header`) is now emitted when this occurs.
 - ✨ NEW: Add warning types `myst.subtype`:
   All parsing warnings are assigned a type/subtype, and also the messages are appended with them.
   These warning types can be suppressed with the sphinx `suppress_warnings` config option.
-  See [How-to suppress warnings](howto/warnings) for more information.
+  See [How-to suppress warnings](myst-warnings) for more information.
 
 ## 0.13.3 - 2021-01-20
 
diff --git a/docs/conf.py b/docs/conf.py
index 199a32a..7db210c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -161,9 +161,11 @@ def setup(app: Sphinx):
         DirectiveDoc,
         DocutilsCliHelpDirective,
         MystConfigDirective,
+        MystWarningsDirective,
     )
 
     app.add_css_file("custom.css")
     app.add_directive("myst-config", MystConfigDirective)
     app.add_directive("docutils-cli-help", DocutilsCliHelpDirective)
     app.add_directive("doc-directive", DirectiveDoc)
+    app.add_directive("myst-warnings", MystWarningsDirective)
diff --git a/docs/configuration.md b/docs/configuration.md
index a87ce0b..286a612 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -104,3 +104,26 @@ substitution
 
 tasklist
 : Add check-boxes to the start of list items, [see here](syntax/tasklists) for details
+
+(howto/warnings)=
+(myst-warnings)=
+## Build Warnings
+
+Below lists the MyST specific warnings that may be emitted during the build process. These will be prepended to the end of the warning message, e.g.
+
+```
+WARNING: Non-consecutive header level increase; H1 to H3 [myst.header]
+```
+
+**In general, if your build logs any warnings, you should either fix them or [raise an Issue](https://github.com/executablebooks/MyST-Parser/issues/new/choose) if you think the warning is erroneous.**
+
+However, in some circumstances if you wish to suppress the warning you can use the [`suppress_warnings`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-suppress_warnings) configuration option, e.g.
+
+```python
+suppress_warnings = ["myst.header"]
+```
+
+Or use `--myst-suppress-warnings="myst.header"` for the [docutils CLI](myst-docutils).
+
+```{myst-warnings}
+```
diff --git a/docs/faq/index.md b/docs/faq/index.md
index e4d4581..a62f861 100644
--- a/docs/faq/index.md
+++ b/docs/faq/index.md
@@ -172,28 +172,9 @@ like so:
 {ref}`path/to/file_1:My Subtitle`
 ```
 
-(howto/warnings)=
 ### Suppress warnings
 
-In general, if your build logs any warnings, you should either fix them or [raise an Issue](https://github.com/executablebooks/MyST-Parser/issues/new/choose) if you think the warning is erroneous.
-However, in some circumstances if you wish to suppress the warning you can use the [`suppress_warnings`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-suppress_warnings) configuration option.
-All myst-parser warnings are prepended by their type, e.g. to suppress:
-
-```md
-# Title
-### Subtitle
-```
-
-```
-WARNING: Non-consecutive header level increase; H1 to H3 [myst.header]
-```
-
-Add to your `conf.py`:
-
-```python
-suppress_warnings = ["myst.header"]
-```
-
+Moved to [](myst-warnings)
 
 ### Sphinx-specific page front matter
 
diff --git a/docs/syntax/optional.md b/docs/syntax/optional.md
index c5ee44e..b11ab08 100644
--- a/docs/syntax/optional.md
+++ b/docs/syntax/optional.md
@@ -88,7 +88,7 @@ For example, `~~strikethrough with *emphasis*~~` renders as: ~~strikethrough wit
 :::{warning}
 This extension is currently only supported for HTML output,
 and you will need to suppress the `myst.strikethrough` warning
-(see [](howto/warnings))
+(see [](myst-warnings))
 :::
 
 (syntax/math)=
diff --git a/myst_parser/_docs.py b/myst_parser/_docs.py
index 6644bb3..cdef6c4 100644
--- a/myst_parser/_docs.py
+++ b/myst_parser/_docs.py
@@ -14,8 +14,9 @@ from sphinx.util.docutils import SphinxDirective
 from ._compat import get_args, get_origin
 from .config.main import MdParserConfig
 from .parsers.docutils_ import Parser as DocutilsParser
+from .warnings_ import MystWarnings
 
-logger = logging.getLogger(__name__)
+LOGGER = logging.getLogger(__name__)
 
 
 class _ConfigBase(SphinxDirective):
@@ -72,6 +73,9 @@ class MystConfigDirective(_ConfigBase):
         count = 0
         for name, value, field in config.as_triple():
 
+            if field.metadata.get("deprecated"):
+                continue
+
             # filter by sphinx options
             if "sphinx" in self.options and field.metadata.get("sphinx_exclude"):
                 continue
@@ -146,7 +150,7 @@ class DirectiveDoc(SphinxDirective):
             name, self.state.memo.language, self.state.document
         )
         if klass is None:
-            logger.warning(f"Directive {name} not found.", line=self.lineno)
+            LOGGER.warning(f"Directive {name} not found.", line=self.lineno)
             return []
         content = " ".join(self.content)
         text = f"""\
@@ -196,3 +200,27 @@ def convert_opt(name, func):
     if func is other.int_or_nothing:
         return "integer"
     return ""
+
+
+class MystWarningsDirective(SphinxDirective):
+    """Directive to print all known warnings."""
+
+    has_content = False
+    required_arguments = 0
+    optional_arguments = 0
+    final_argument_whitespace = False
+
+    def run(self):
+        """Run the directive."""
+        from sphinx.pycode import ModuleAnalyzer
+
+        analyzer = ModuleAnalyzer.for_module(MystWarnings.__module__)
+        qname = MystWarnings.__qualname__
+        analyzer.analyze()
+        warning_names = [
+            (e.value, analyzer.attr_docs[(qname, e.name)]) for e in MystWarnings
+        ]
+        text = [f"- `myst.{name}`: {' '.join(doc)}" for name, doc in warning_names]
+        node = nodes.Element()
+        self.state.nested_parse(text, 0, node)
+        return node.children
diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py
index 1223a28..118c3d0 100644
--- a/myst_parser/config/main.py
+++ b/myst_parser/config/main.py
@@ -13,6 +13,7 @@ from typing import (
     cast,
 )
 
+from myst_parser.warnings_ import MystWarnings
 from .dc_validators import (
     deep_iterable,
     deep_mapping,
@@ -126,15 +127,6 @@ class MdParserConfig:
         },
     )
 
-    highlight_code_blocks: bool = dc.field(
-        default=True,
-        metadata={
-            "validator": instance_of(bool),
-            "help": "Syntax highlight code blocks with pygments",
-            "docutils_only": True,
-        },
-    )
-
     number_code_blocks: Sequence[str] = dc.field(
         default_factory=list,
         metadata={
@@ -282,6 +274,27 @@ class MdParserConfig:
         },
     )
 
+    # docutils only (replicating aspects of sphinx config)
+
+    suppress_warnings: Sequence[str] = dc.field(
+        default_factory=list,
+        metadata={
+            "validator": deep_iterable(instance_of(str), instance_of((list, tuple))),
+            "help": "A list of warning types to suppress warning messages",
+            "docutils_only": True,
+            "global_only": True,
+        },
+    )
+
+    highlight_code_blocks: bool = dc.field(
+        default=True,
+        metadata={
+            "validator": instance_of(bool),
+            "help": "Syntax highlight code blocks with pygments",
+            "docutils_only": True,
+        },
+    )
+
     def __post_init__(self):
         validate_fields(self)
 
@@ -311,7 +324,7 @@ class MdParserConfig:
 def merge_file_level(
     config: MdParserConfig,
     topmatter: Dict[str, Any],
-    warning: Callable[[str, str], None],
+    warning: Callable[[MystWarnings, str], None],
 ) -> MdParserConfig:
     """Merge the file-level topmatter with the global config.
 
@@ -324,21 +337,21 @@ def merge_file_level(
     updates: Dict[str, Any] = {}
     myst = topmatter.get("myst", {})
     if not isinstance(myst, dict):
-        warning("topmatter", f"'myst' key not a dict: {type(myst)}")
+        warning(MystWarnings.MD_TOPMATTER, f"'myst' key not a dict: {type(myst)}")
     else:
         updates = myst
 
     # allow html_meta and substitutions at top-level for back-compatibility
     if "html_meta" in topmatter:
         warning(
-            "topmatter",
+            MystWarnings.MD_TOPMATTER,
             "top-level 'html_meta' key is deprecated, "
             "place under 'myst' key instead",
         )
         updates["html_meta"] = topmatter["html_meta"]
     if "substitutions" in topmatter:
         warning(
-            "topmatter",
+            MystWarnings.MD_TOPMATTER,
             "top-level 'substitutions' key is deprecated, "
             "place under 'myst' key instead",
         )
@@ -351,7 +364,7 @@ def merge_file_level(
     for name, value in updates.items():
 
         if name not in fields:
-            warning("topmatter", f"Unknown field: {name}")
+            warning(MystWarnings.MD_TOPMATTER, f"Unknown field: {name}")
             continue
 
         old_value, field = fields[name]
@@ -359,7 +372,7 @@ def merge_file_level(
         try:
             validate_field(new, field, value)
         except Exception as exc:
-            warning("topmatter", str(exc))
+            warning(MystWarnings.MD_TOPMATTER, str(exc))
             continue
 
         if field.metadata.get("merge_topmatter"):
diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py
index cedd6c3..f21e6d1 100644
--- a/myst_parser/mdit_to_docutils/base.py
+++ b/myst_parser/mdit_to_docutils/base.py
@@ -43,6 +43,7 @@ from myst_parser.mocking import (
     MockStateMachine,
 )
 from myst_parser.parsers.directives import DirectiveParsingError, parse_directive_text
+from myst_parser.warnings_ import MystWarnings, create_warning
 from .html_to_nodes import html_to_nodes
 from .utils import is_external_url
 
@@ -68,27 +69,6 @@ def token_line(token: SyntaxTreeNode, default: int | None = None) -> int:
     return token.map[0]  # type: ignore[index]
 
 
-def create_warning(
-    document: nodes.document,
-    message: str,
-    *,
-    line: int | None = None,
-    append_to: nodes.Element | None = None,
-    wtype: str = "myst",
-    subtype: str = "other",
-) -> nodes.system_message | None:
-    """Generate a warning, logging if it is necessary.
-
-    Note this is overridden in the ``SphinxRenderer``,
-    to handle suppressed warning types.
-    """
-    kwargs = {"line": line} if line is not None else {}
-    msg_node = document.reporter.warning(f"{message} [{wtype}.{subtype}]", **kwargs)
-    if append_to is not None:
-        append_to.append(msg_node)
-    return msg_node
-
-
 class DocutilsRenderer(RendererProtocol):
     """A markdown-it-py renderer to populate (in-place) a `docutils.document` AST.
 
@@ -156,24 +136,22 @@ class DocutilsRenderer(RendererProtocol):
     def create_warning(
         self,
         message: str,
+        subtype: MystWarnings,
         *,
         line: int | None = None,
         append_to: nodes.Element | None = None,
-        wtype: str = "myst",
-        subtype: str = "other",
     ) -> nodes.system_message | None:
         """Generate a warning, logging if it is necessary.
 
-        Note this is overridden in the ``SphinxRenderer``,
-        to handle suppressed warning types.
+        If the warning type is listed in the ``suppress_warnings`` configuration,
+        then ``None`` will be returned and no warning logged.
         """
         return create_warning(
             self.document,
             message,
+            subtype,
             line=line,
             append_to=append_to,
-            wtype=wtype,
-            subtype=subtype,
         )
 
     def _render_tokens(self, tokens: list[Token]) -> None:
@@ -211,8 +189,8 @@ class DocutilsRenderer(RendererProtocol):
             else:
                 self.create_warning(
                     f"No render method for: {child.type}",
+                    MystWarnings.RENDER_METHOD,
                     line=token_line(child, default=0),
-                    subtype="render",
                     append_to=self.current_node,
                 )
 
@@ -251,8 +229,8 @@ class DocutilsRenderer(RendererProtocol):
         for dup_ref in self.md_env.get("duplicate_refs", []):
             self.create_warning(
                 f"Duplicate reference definition: {dup_ref['label']}",
+                MystWarnings.MD_DEF_DUPE,
                 line=dup_ref["map"][0] + 1,
-                subtype="ref",
                 append_to=self.document,
             )
 
@@ -272,14 +250,14 @@ class DocutilsRenderer(RendererProtocol):
             if len(foot_ref_tokens) > 1:
                 self.create_warning(
                     f"Multiple footnote definitions found for label: '{footref}'",
-                    subtype="footnote",
+                    MystWarnings.MD_FOOTNOTE_DUPE,
                     append_to=self.current_node,
                 )
 
             if len(foot_ref_tokens) < 1:
                 self.create_warning(
                     f"No footnote definitions found for label: '{footref}'",
-                    subtype="footnote",
+                    MystWarnings.MD_FOOTNOTE_MISSING,
                     append_to=self.current_node,
                 )
             else:
@@ -360,8 +338,8 @@ class DocutilsRenderer(RendererProtocol):
             else:
                 self.create_warning(
                     f"No render method for: {child.type}",
+                    MystWarnings.RENDER_METHOD,
                     line=token_line(child, default=0),
-                    subtype="render",
                     append_to=self.current_node,
                 )
 
@@ -402,8 +380,8 @@ class DocutilsRenderer(RendererProtocol):
                 msg = f"Document headings start at H{level}, not H1"
             self.create_warning(
                 msg,
+                MystWarnings.MD_HEADING_NON_CONSECUTIVE,
                 line=section.line,
-                subtype="header",
                 append_to=self.current_node,
             )
 
@@ -638,8 +616,8 @@ class DocutilsRenderer(RendererProtocol):
             # this would break the document structure
             self.create_warning(
                 "Disallowed nested header found, converting to rubric",
+                MystWarnings.MD_HEADING_NESTED,
                 line=token_line(token, default=0),
-                subtype="nested_header",
                 append_to=self.current_node,
             )
             rubric = nodes.rubric(token.content, "")
@@ -797,8 +775,8 @@ class DocutilsRenderer(RendererProtocol):
             except ValueError:
                 self.create_warning(
                     f"Invalid width value for image: {token.attrs['width']!r}",
+                    MystWarnings.INVALID_ATTRIBUTE,
                     line=token_line(token, default=0),
-                    subtype="image",
                     append_to=self.current_node,
                 )
             else:
@@ -809,8 +787,8 @@ class DocutilsRenderer(RendererProtocol):
             except ValueError:
                 self.create_warning(
                     f"Invalid height value for image: {token.attrs['height']!r}",
+                    MystWarnings.INVALID_ATTRIBUTE,
                     line=token_line(token, default=0),
-                    subtype="image",
                     append_to=self.current_node,
                 )
             else:
@@ -819,8 +797,8 @@ class DocutilsRenderer(RendererProtocol):
             if token.attrs["align"] not in ("left", "center", "right"):
                 self.create_warning(
                     f"Invalid align value for image: {token.attrs['align']!r}",
+                    MystWarnings.INVALID_ATTRIBUTE,
                     line=token_line(token, default=0),
-                    subtype="image",
                     append_to=self.current_node,
                 )
             else:
@@ -844,9 +822,9 @@ class DocutilsRenderer(RendererProtocol):
             except (yaml.parser.ParserError, yaml.scanner.ScannerError):
                 self.create_warning(
                     "Malformed YAML",
+                    MystWarnings.MD_TOPMATTER,
                     line=position,
                     append_to=self.current_node,
-                    subtype="topmatter",
                 )
                 return
         else:
@@ -855,9 +833,9 @@ class DocutilsRenderer(RendererProtocol):
         if not isinstance(data, dict):
             self.create_warning(
                 f"YAML is not a dict: {type(data)}",
+                MystWarnings.MD_TOPMATTER,
                 line=position,
                 append_to=self.current_node,
-                subtype="topmatter",
             )
             return
 
@@ -1004,8 +982,8 @@ class DocutilsRenderer(RendererProtocol):
         # TODO strikethrough not currently directly supported in docutils
         self.create_warning(
             "Strikethrough is currently only supported in HTML output",
+            MystWarnings.STRIKETHROUGH,
             line=token_line(token, 0),
-            subtype="strikethrough",
             append_to=self.current_node,
         )
         self.current_node.append(nodes.raw("", "
", format="html"))
diff --git a/myst_parser/mdit_to_docutils/html_to_nodes.py b/myst_parser/mdit_to_docutils/html_to_nodes.py
index 2cc3066..539405d 100644
--- a/myst_parser/mdit_to_docutils/html_to_nodes.py
+++ b/myst_parser/mdit_to_docutils/html_to_nodes.py
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
 from docutils import nodes
 
 from myst_parser.parsers.parse_html import Data, tokenize_html
+from myst_parser.warnings_ import MystWarnings
 
 if TYPE_CHECKING:
     from .base import DocutilsRenderer
@@ -58,7 +59,7 @@ def html_to_nodes(
         root = tokenize_html(text).strip(inplace=True, recurse=False)
     except Exception:
         msg_node = renderer.create_warning(
-            "HTML could not be parsed", line=line_number, subtype="html"
+            "HTML could not be parsed", MystWarnings.HTML_PARSE, line=line_number
         )
         return ([msg_node] if msg_node else []) + default_html(
             text, renderer.document["source"], line_number
diff --git a/myst_parser/mdit_to_docutils/sphinx_.py b/myst_parser/mdit_to_docutils/sphinx_.py
index 3c1bc23..b6c5572 100644
--- a/myst_parser/mdit_to_docutils/sphinx_.py
+++ b/myst_parser/mdit_to_docutils/sphinx_.py
@@ -17,39 +17,11 @@ from sphinx.util import logging
 from sphinx.util.nodes import clean_astext
 
 from myst_parser.mdit_to_docutils.base import DocutilsRenderer
+from myst_parser.warnings_ import MystWarnings
 
 LOGGER = logging.getLogger(__name__)
 
 
-def create_warning(
-    document: nodes.document,
-    message: str,
-    *,
-    line: int | None = None,
-    append_to: nodes.Element | None = None,
-    wtype: str = "myst",
-    subtype: str = "other",
-) -> nodes.system_message | None:
-    """Generate a warning, logging it if necessary.
-
-    If the warning type is listed in the ``suppress_warnings`` configuration,
-    then ``None`` will be returned and no warning logged.
-    """
-    message = f"{message} [{wtype}.{subtype}]"
-    kwargs = {"line": line} if line is not None else {}
-
-    if logging.is_suppressed_warning(
-        wtype, subtype, document.settings.env.app.config.suppress_warnings
-    ):
-        return None
-
-    msg_node = document.reporter.warning(message, **kwargs)
-    if append_to is not None:
-        append_to.append(msg_node)
-
-    return None
-
-
 class SphinxRenderer(DocutilsRenderer):
     """A markdown-it-py renderer to populate (in-place) a `docutils.document` AST.
 
@@ -61,29 +33,6 @@ class SphinxRenderer(DocutilsRenderer):
     def doc_env(self) -> BuildEnvironment:
         return self.document.settings.env
 
-    def create_warning(
-        self,
-        message: str,
-        *,
-        line: int | None = None,
-        append_to: nodes.Element | None = None,
-        wtype: str = "myst",
-        subtype: str = "other",
-    ) -> nodes.system_message | None:
-        """Generate a warning, logging it if necessary.
-
-        If the warning type is listed in the ``suppress_warnings`` configuration,
-        then ``None`` will be returned and no warning logged.
-        """
-        return create_warning(
-            self.document,
-            message,
-            line=line,
-            append_to=append_to,
-            wtype=wtype,
-            subtype=subtype,
-        )
-
     def render_internal_link(self, token: SyntaxTreeNode) -> None:
         """Render link token `[text](link "title")`,
         where the link has not been identified as an external URL.
@@ -171,8 +120,8 @@ class SphinxRenderer(DocutilsRenderer):
             other_doc = self.doc_env.doc2path(domain.labels[doc_slug][0])
             self.create_warning(
                 f"duplicate label {doc_slug}, other instance in {other_doc}",
+                MystWarnings.ANCHOR_DUPE,
                 line=section.line,
-                subtype="anchor",
             )
         labelid = section["ids"][0]
         domain.anonlabels[doc_slug] = self.doc_env.docname, labelid
diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py
index d0f99bb..84f4ff9 100644
--- a/myst_parser/parsers/docutils_.py
+++ b/myst_parser/parsers/docutils_.py
@@ -13,8 +13,9 @@ from myst_parser.config.main import (
     merge_file_level,
     read_topmatter,
 )
-from myst_parser.mdit_to_docutils.base import DocutilsRenderer, create_warning
+from myst_parser.mdit_to_docutils.base import DocutilsRenderer
 from myst_parser.parsers.mdit import create_md_parser
+from myst_parser.warnings_ import create_warning
 
 
 def _validate_int(
@@ -48,6 +49,10 @@ class Unset:
     def __repr__(self):
         return "UNSET"
 
+    def __bool__(self):
+        # this allows to check if the setting is unset/falsy
+        return False
+
 
 DOCUTILS_UNSET = Unset()
 """Sentinel for arguments not set through docutils.conf."""
@@ -218,7 +223,7 @@ class Parser(RstParser):
         else:
             if topmatter:
                 warning = lambda wtype, msg: create_warning(  # noqa: E731
-                    document, msg, line=1, append_to=document, subtype=wtype
+                    document, msg, wtype, line=1, append_to=document
                 )
                 config = merge_file_level(config, topmatter, warning)
 
diff --git a/myst_parser/parsers/sphinx_.py b/myst_parser/parsers/sphinx_.py
index fff098f..94d5aef 100644
--- a/myst_parser/parsers/sphinx_.py
+++ b/myst_parser/parsers/sphinx_.py
@@ -12,8 +12,9 @@ from myst_parser.config.main import (
     merge_file_level,
     read_topmatter,
 )
-from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer, create_warning
+from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer
 from myst_parser.parsers.mdit import create_md_parser
+from myst_parser.warnings_ import create_warning
 
 SPHINX_LOGGER = logging.getLogger(__name__)
 
@@ -60,7 +61,7 @@ class MystParser(SphinxParser):
         else:
             if topmatter:
                 warning = lambda wtype, msg: create_warning(  # noqa: E731
-                    document, msg, line=1, append_to=document, subtype=wtype
+                    document, msg, wtype, line=1, append_to=document
                 )
                 config = merge_file_level(config, topmatter, warning)
 
diff --git a/myst_parser/sphinx_ext/myst_refs.py b/myst_parser/sphinx_ext/myst_refs.py
index f70a4de..948303a 100644
--- a/myst_parser/sphinx_ext/myst_refs.py
+++ b/myst_parser/sphinx_ext/myst_refs.py
@@ -17,6 +17,7 @@ from sphinx.util import docname_join, logging
 from sphinx.util.nodes import clean_astext, make_refnode
 
 from myst_parser._compat import findall
+from myst_parser.warnings_ import MystWarnings
 
 try:
     from sphinx.errors import NoUri
@@ -156,7 +157,7 @@ class MystReferenceResolver(ReferencesResolver):
                         f"Domain '{domain.__module__}::{domain.name}' has not "
                         "implemented a `resolve_any_xref` method [myst.domains]",
                         type="myst",
-                        subtype="domains",
+                        subtype=MystWarnings.LEGACY_DOMAIN.value,
                         once=True,
                     )
                 for role in domain.roles:
@@ -183,7 +184,7 @@ class MystReferenceResolver(ReferencesResolver):
                 ),
                 location=node,
                 type="myst",
-                subtype="ref",
+                subtype=MystWarnings.XREF_AMBIGUOUS.value,
             )
 
         res_role, newnode = results[0]
diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py
new file mode 100644
index 0000000..4e9bced
--- /dev/null
+++ b/myst_parser/warnings_.py
@@ -0,0 +1,101 @@
+"""Central handling of warnings for the myst extension."""
+from __future__ import annotations
+
+from enum import Enum
+from typing import Sequence
+
+from docutils import nodes
+
+
+class MystWarnings(Enum):
+    """MyST warning types."""
+
+    RENDER_METHOD = "render"
+    """The render method is not implemented."""
+
+    MD_TOPMATTER = "topmatter"
+    """Issue reading top-matter."""
+    MD_DEF_DUPE = "duplicate_def"
+    """Duplicate Markdown reference definition."""
+    MD_FOOTNOTE_DUPE = "footnote"
+    """Duplicate Markdown footnote definition."""
+    MD_FOOTNOTE_MISSING = "footnote"
+    """Missing Markdown footnote definition."""
+    MD_HEADING_NON_CONSECUTIVE = "header"
+    """Non-consecutive heading levels."""
+    MD_HEADING_NESTED = "nested_header"
+    """Header found nested in another element."""
+
+    # cross-reference resolution
+    XREF_AMBIGUOUS = "xref_ambiguous"
+    """Multiple targets were found for a cross-reference."""
+    LEGACY_DOMAIN = "domains"
+    """A legacy domain found, which does not support `resolve_any_xref`."""
+
+    # extensions
+    ANCHOR_DUPE = "anchor_dupe"
+    """Duplicate heading anchors generated in same document."""
+    STRIKETHROUGH = "strikethrough"
+    """Strikethrough warning, since only implemented in HTML."""
+    HTML_PARSE = "html"
+    """HTML could not be parsed."""
+    INVALID_ATTRIBUTE = "attribute"
+    """Invalid attribute value."""
+
+
+def _is_suppressed_warning(
+    type: str, subtype: str, suppress_warnings: Sequence[str]
+) -> bool:
+    """Check whether the warning is suppressed or not.
+
+    Mirrors:
+    https://github.com/sphinx-doc/sphinx/blob/47d9035bca9e83d6db30a0726a02dc9265bd66b1/sphinx/util/logging.py
+    """
+    if type is None:
+        return False
+
+    subtarget: str | None
+
+    for warning_type in suppress_warnings:
+        if "." in warning_type:
+            target, subtarget = warning_type.split(".", 1)
+        else:
+            target, subtarget = warning_type, None
+
+        if target == type and subtarget in (None, subtype, "*"):
+            return True
+
+    return False
+
+
+def create_warning(
+    document: nodes.document,
+    message: str,
+    subtype: MystWarnings,
+    *,
+    line: int | None = None,
+    append_to: nodes.Element | None = None,
+) -> nodes.system_message | None:
+    """Generate a warning, logging if it is necessary.
+
+    If the warning type is listed in the ``suppress_warnings`` configuration,
+    then ``None`` will be returned and no warning logged.
+    """
+    wtype = "myst"
+    # figure out whether to suppress the warning, if sphinx is available,
+    # it will have been set up by the Sphinx environment,
+    # otherwise we will use the configuration set by docutils
+    suppress_warnings: Sequence[str] = []
+    try:
+        suppress_warnings = document.settings.env.app.config.suppress_warnings
+    except AttributeError:
+        suppress_warnings = document.settings.myst_suppress_warnings or []
+    if _is_suppressed_warning(wtype, subtype.value, suppress_warnings):
+        return None
+
+    kwargs = {"line": line} if line is not None else {}
+    message = f"{message} [{wtype}.{subtype.value}]"
+    msg_node = document.reporter.warning(message, **kwargs)
+    if append_to is not None:
+        append_to.append(msg_node)
+    return msg_node


Test patch:
diff --git a/tests/test_renderers/fixtures/myst-config.txt b/tests/test_renderers/fixtures/myst-config.txt
index 668895a..48fd5e5 100644
--- a/tests/test_renderers/fixtures/myst-config.txt
+++ b/tests/test_renderers/fixtures/myst-config.txt
@@ -1,3 +1,15 @@
+[suppress-warnings] --myst-suppress-warnings="myst.header"
+.
+# A
+### B
+.
+<document ids="a" names="a" source="<string>" title="A">
+    <title>
+        A
+    <subtitle ids="b" names="b">
+        B
+.
+
 [title-to-header] --myst-title-to-header="yes"
 .
 ---
@@ -161,16 +173,16 @@ www.commonmark.org/he<lp
     <paragraph>
         <system_message level="2" line="1" source="<string>" type="WARNING">
             <paragraph>
-                Invalid width value for image: '1x' [myst.image]
+                Invalid width value for image: '1x' [myst.attribute]
         <system_message level="2" line="1" source="<string>" type="WARNING">
             <paragraph>
-                Invalid height value for image: '2x' [myst.image]
+                Invalid height value for image: '2x' [myst.attribute]
         <system_message level="2" line="1" source="<string>" type="WARNING">
             <paragraph>
-                Invalid align value for image: 'other' [myst.image]
+                Invalid align value for image: 'other' [myst.attribute]
         <image alt="a" uri="b">
 
-<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.image]
-<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.image]
-<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.image]
+<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.attribute]
+<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.attribute]
+<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.attribute]
 .
diff --git a/tests/test_renderers/fixtures/reporter_warnings.md b/tests/test_renderers/fixtures/reporter_warnings.md
index e9998b9..59a8923 100644
--- a/tests/test_renderers/fixtures/reporter_warnings.md
+++ b/tests/test_renderers/fixtures/reporter_warnings.md
@@ -3,7 +3,7 @@ Duplicate Reference definitions:
 [a]: b
 [a]: c
 .
-<string>:2: (WARNING/2) Duplicate reference definition: A [myst.ref]
+<string>:2: (WARNING/2) Duplicate reference definition: A [myst.duplicate_def]
 .
 
 Missing Reference: