From 5b4ff2d18c8b33f429bce87b2d363cc03c18d30a Mon Sep 17 00:00:00 2001 From: Julian Grimm <51880314+Julfried@users.noreply.github.com> Date: Thu, 29 May 2025 08:51:07 +0200 Subject: [PATCH] Fix Mermaid printer rendering double underscores as bold formatting (#10403) * Escape characters in mermaid viewer * Update test files * Add news fragement * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Pierre Sassoulas --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas (cherry picked from commit 25a0f9e03dac32b9f2d2f0ff1c01ff70dfc0b100) --- doc/whatsnew/fragments/10402.bugfix | 3 +++ pylint/pyreverse/mermaidjs_printer.py | 23 +++++++++++++++---- .../aggregation_filtering/all.mmd | 18 +++++++-------- .../aggregation_filtering/other.mmd | 14 +++++------ .../aggregation_filtering/special.mmd | 4 ++-- 5 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 doc/whatsnew/fragments/10402.bugfix diff --git a/doc/whatsnew/fragments/10402.bugfix b/doc/whatsnew/fragments/10402.bugfix new file mode 100644 index 0000000000..a7cea6782e --- /dev/null +++ b/doc/whatsnew/fragments/10402.bugfix @@ -0,0 +1,3 @@ +Fix double underscores erroneously rendering as bold in pyreverse's Mermaid output. + +Closes #10402 diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index 24fa927761..927b5f59dc 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -32,6 +32,11 @@ def _open_graph(self) -> None: self.emit("classDiagram") self._inc_indent() + def _escape_mermaid_text(self, text: str) -> str: + """Escape characters that conflict with Markdown formatting.""" + text = text.replace("__", r"\_\_") # Double underscore → escaped + return text + def emit_node( self, name: str, @@ -48,14 +53,24 @@ def emit_node( nodetype = self.NODES[type_] body = [] if properties.attrs: - body.extend(properties.attrs) + # Escape attribute names to prevent Markdown formatting issues + escaped_attrs = [ + self._escape_mermaid_text(attr) for attr in properties.attrs + ] + body.extend(escaped_attrs) if properties.methods: for func in properties.methods: args = self._get_method_arguments(func) - line = f"{func.name}({', '.join(args)})" + # Escape method name and arguments + escaped_method_name = self._escape_mermaid_text(func.name) + escaped_args = [self._escape_mermaid_text(arg) for arg in args] + line = f"{escaped_method_name}({', '.join(escaped_args)})" line += "*" if func.is_abstract() else "" if func.returns: - line += f" {get_annotation_label(func.returns)}" + # Escape return type annotation + return_type = get_annotation_label(func.returns) + escaped_return_type = self._escape_mermaid_text(return_type) + line += f" {escaped_return_type}" body.append(line) name = name.split(".")[-1] self.emit(f"{nodetype} {name} {{") @@ -77,7 +92,7 @@ def emit_edge( to_node = to_node.split(".")[-1] edge = f"{from_node} {self.ARROWS[type_]} {to_node}" if label: - edge += f" : {label}" + edge += f" : {self._escape_mermaid_text(label)}" self.emit(edge) def _close_graph(self) -> None: diff --git a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/all.mmd b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/all.mmd index 3b445288a1..378ef5ccaf 100644 --- a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/all.mmd +++ b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/all.mmd @@ -1,25 +1,25 @@ classDiagram class P { name : str - __init__(name: str) + \_\_init\_\_(name: str) } class PrivateAttr { - __x - __init__() + \_\_x + \_\_init\_\_() } class ProtectedAttr { _x - __init__() + \_\_init\_\_() } class PublicAttr { x - __init__() + \_\_init\_\_() } class SpecialAttr { - __x__ - __init__() + \_\_x\_\_ + \_\_init\_\_() } - P --* PrivateAttr : __x + P --* PrivateAttr : \_\_x P --* ProtectedAttr : _x P --* PublicAttr : x - P --* SpecialAttr : __x__ + P --* SpecialAttr : \_\_x\_\_ diff --git a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/other.mmd b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/other.mmd index 6cfcae244f..07439b6c55 100644 --- a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/other.mmd +++ b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/other.mmd @@ -1,21 +1,21 @@ classDiagram class P { name : str - __init__(name: str) + \_\_init\_\_(name: str) } class PrivateAttr { - __init__() + \_\_init\_\_() } class ProtectedAttr { - __init__() + \_\_init\_\_() } class PublicAttr { x - __init__() + \_\_init\_\_() } class SpecialAttr { - __x__ - __init__() + \_\_x\_\_ + \_\_init\_\_() } P --* PublicAttr : x - P --* SpecialAttr : __x__ + P --* SpecialAttr : \_\_x\_\_ diff --git a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/special.mmd b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/special.mmd index 149b0a694a..6b7f39b905 100644 --- a/tests/pyreverse/functional/class_diagrams/aggregation_filtering/special.mmd +++ b/tests/pyreverse/functional/class_diagrams/aggregation_filtering/special.mmd @@ -3,7 +3,7 @@ classDiagram name : str } class PrivateAttr { - __x + \_\_x } class ProtectedAttr { _x @@ -13,6 +13,6 @@ classDiagram } class SpecialAttr { } - P --* PrivateAttr : __x + P --* PrivateAttr : \_\_x P --* ProtectedAttr : _x P --* PublicAttr : x