diff options
-rw-r--r-- | doc/whatsnew/fragments/8603.bugfix | 3 | ||||
-rw-r--r-- | pylint/pyreverse/dot_printer.py | 10 | ||||
-rw-r--r-- | tests/data/nullable_pattern.py | 10 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_No_Name.dot | 1 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_No_Name.html | 6 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_No_Name.mmd | 4 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_No_Name.puml | 4 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_colorized.dot | 1 | ||||
-rw-r--r-- | tests/pyreverse/data/classes_colorized.puml | 4 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_No_Name.dot | 1 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_No_Name.html | 4 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_No_Name.mmd | 2 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_No_Name.puml | 6 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_colorized.dot | 1 | ||||
-rw-r--r-- | tests/pyreverse/data/packages_colorized.puml | 6 | ||||
-rw-r--r-- | tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot | 2 | ||||
-rw-r--r-- | tests/pyreverse/test_diadefs.py | 2 | ||||
-rw-r--r-- | tests/pyreverse/test_inspector.py | 1 |
18 files changed, 56 insertions, 12 deletions
diff --git a/doc/whatsnew/fragments/8603.bugfix b/doc/whatsnew/fragments/8603.bugfix new file mode 100644 index 000000000..1a1025c4b --- /dev/null +++ b/doc/whatsnew/fragments/8603.bugfix @@ -0,0 +1,3 @@ +``pyreverse``: added escaping of vertical bar character in annotation labels produced by DOT printer to ensure it is not treated as field separator of record-based nodes. + +Closes #8603 diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 054814d05..edaea2384 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -119,11 +119,19 @@ class DotPrinter(Printer): ) label += rf"{method_name}({', '.join(args)})" if func.returns: - label += ": " + get_annotation_label(func.returns) + annotation_label = get_annotation_label(func.returns) + label += ": " + self._escape_annotation_label(annotation_label) label += rf"{HTMLLabels.LINEBREAK_LEFT.value}" label += "}" return label + def _escape_annotation_label(self, annotation_label: str) -> str: + # Escape vertical bar characters to make them appear as a literal characters + # otherwise it gets treated as field separator of record-based nodes + annotation_label = annotation_label.replace("|", r"\|") + + return annotation_label + def emit_edge( self, from_node: str, diff --git a/tests/data/nullable_pattern.py b/tests/data/nullable_pattern.py new file mode 100644 index 000000000..bd730bbd6 --- /dev/null +++ b/tests/data/nullable_pattern.py @@ -0,0 +1,10 @@ +""" docstring for file nullable_pattern.py """ +from typing import Optional + +class NullablePatterns: + def return_nullable_1(self) -> int | None: + """ Nullable return type using the | operator as mentioned in PEP 604, see https://peps.python.org/pep-0604 """ + pass + + def return_nullable_2(self) -> Optional[int]: + pass diff --git a/tests/pyreverse/data/classes_No_Name.dot b/tests/pyreverse/data/classes_No_Name.dot index 684f4fe68..a33f65da3 100644 --- a/tests/pyreverse/data/classes_No_Name.dot +++ b/tests/pyreverse/data/classes_No_Name.dot @@ -7,6 +7,7 @@ charset="utf-8" "data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"]; "data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label=<{DoSomething|my_int : Optional[int]<br ALIGN="LEFT"/>my_int_2 : Optional[int]<br ALIGN="LEFT"/>my_string : str<br ALIGN="LEFT"/>|do_it(new_int: int): int<br ALIGN="LEFT"/>}>, shape="record", style="solid"]; "data.suppliermodule_test.Interface" [color="black", fontcolor="black", label=<{Interface|<br ALIGN="LEFT"/>|<I>get_value</I>()<br ALIGN="LEFT"/><I>set_value</I>(value)<br ALIGN="LEFT"/>}>, shape="record", style="solid"]; +"data.nullable_pattern.NullablePatterns" [color="black", fontcolor="black", label=<{NullablePatterns|<br ALIGN="LEFT"/>|<I>return_nullable_1</I>(): int \| None<br ALIGN="LEFT"/><I>return_nullable_2</I>(): Optional[int]<br ALIGN="LEFT"/>}>, shape="record", style="solid"]; "data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="solid"]; "data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, shape="record", style="solid"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html index 08bac0034..bf4c17d0d 100644 --- a/tests/pyreverse/data/classes_No_Name.html +++ b/tests/pyreverse/data/classes_No_Name.html @@ -26,6 +26,10 @@ get_value()* set_value(value)* } + class NullablePatterns { + return_nullable_1()* int | None + return_nullable_2()* Optional[int] + } class PropertyPatterns { prop1 prop2 @@ -43,7 +47,7 @@ DoNothing --* Ancestor : cls_member DoNothing --* Specialization : relation DoNothing2 --o Specialization : relation2 - + </div> </body> </html> diff --git a/tests/pyreverse/data/classes_No_Name.mmd b/tests/pyreverse/data/classes_No_Name.mmd index 7ad6f1423..9f38089de 100644 --- a/tests/pyreverse/data/classes_No_Name.mmd +++ b/tests/pyreverse/data/classes_No_Name.mmd @@ -21,6 +21,10 @@ classDiagram get_value()* set_value(value)* } + class NullablePatterns { + return_nullable_1()* int | None + return_nullable_2()* Optional[int] + } class PropertyPatterns { prop1 prop2 diff --git a/tests/pyreverse/data/classes_No_Name.puml b/tests/pyreverse/data/classes_No_Name.puml index ee5fb124a..0b01169e4 100644 --- a/tests/pyreverse/data/classes_No_Name.puml +++ b/tests/pyreverse/data/classes_No_Name.puml @@ -22,6 +22,10 @@ class "Interface" as data.suppliermodule_test.Interface { {abstract}get_value() {abstract}set_value(value) } +class "NullablePatterns" as data.nullable_pattern.NullablePatterns { + {abstract}return_nullable_1() -> int | None + {abstract}return_nullable_2() -> Optional[int] +} class "PropertyPatterns" as data.property_pattern.PropertyPatterns { prop1 prop2 diff --git a/tests/pyreverse/data/classes_colorized.dot b/tests/pyreverse/data/classes_colorized.dot index 7c43f0888..729b05828 100644 --- a/tests/pyreverse/data/classes_colorized.dot +++ b/tests/pyreverse/data/classes_colorized.dot @@ -7,6 +7,7 @@ charset="utf-8" "data.suppliermodule_test.DoNothing2" [color="#77AADD", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="filled"]; "data.suppliermodule_test.DoSomething" [color="#77AADD", fontcolor="black", label=<{DoSomething|my_int : Optional[int]<br ALIGN="LEFT"/>my_int_2 : Optional[int]<br ALIGN="LEFT"/>my_string : str<br ALIGN="LEFT"/>|do_it(new_int: int): int<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; "data.suppliermodule_test.Interface" [color="#77AADD", fontcolor="black", label=<{Interface|<br ALIGN="LEFT"/>|<I>get_value</I>()<br ALIGN="LEFT"/><I>set_value</I>(value)<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; +"data.nullable_pattern.NullablePatterns" [color="#77AADD", fontcolor="black", label=<{NullablePatterns|<br ALIGN="LEFT"/>|<I>return_nullable_1</I>(): int \| None<br ALIGN="LEFT"/><I>return_nullable_2</I>(): Optional[int]<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; "data.property_pattern.PropertyPatterns" [color="#77AADD", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="filled"]; "data.clientmodule_test.Specialization" [color="#77AADD", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; diff --git a/tests/pyreverse/data/classes_colorized.puml b/tests/pyreverse/data/classes_colorized.puml index d1106153d..ef97398bc 100644 --- a/tests/pyreverse/data/classes_colorized.puml +++ b/tests/pyreverse/data/classes_colorized.puml @@ -22,6 +22,10 @@ class "Interface" as data.suppliermodule_test.Interface #77AADD { {abstract}get_value() {abstract}set_value(value) } +class "NullablePatterns" as data.nullable_pattern.NullablePatterns #77AADD { + {abstract}return_nullable_1() -> int | None + {abstract}return_nullable_2() -> Optional[int] +} class "PropertyPatterns" as data.property_pattern.PropertyPatterns #77AADD { prop1 prop2 diff --git a/tests/pyreverse/data/packages_No_Name.dot b/tests/pyreverse/data/packages_No_Name.dot index 5421c328c..61d3eef5c 100644 --- a/tests/pyreverse/data/packages_No_Name.dot +++ b/tests/pyreverse/data/packages_No_Name.dot @@ -3,6 +3,7 @@ rankdir=BT charset="utf-8" "data" [color="black", label=<data>, shape="box", style="solid"]; "data.clientmodule_test" [color="black", label=<data.clientmodule_test>, shape="box", style="solid"]; +"data.nullable_pattern" [color="black", label=<data.nullable_pattern>, shape="box", style="solid"]; "data.property_pattern" [color="black", label=<data.property_pattern>, shape="box", style="solid"]; "data.suppliermodule_test" [color="black", label=<data.suppliermodule_test>, shape="box", style="solid"]; "data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"]; diff --git a/tests/pyreverse/data/packages_No_Name.html b/tests/pyreverse/data/packages_No_Name.html index 128f8d1a4..3f2b7f3dd 100644 --- a/tests/pyreverse/data/packages_No_Name.html +++ b/tests/pyreverse/data/packages_No_Name.html @@ -8,12 +8,14 @@ } class clientmodule_test { } + class nullable_pattern { + } class property_pattern { } class suppliermodule_test { } clientmodule_test --> suppliermodule_test - + </div> </body> </html> diff --git a/tests/pyreverse/data/packages_No_Name.mmd b/tests/pyreverse/data/packages_No_Name.mmd index e8b02d070..e25fe795e 100644 --- a/tests/pyreverse/data/packages_No_Name.mmd +++ b/tests/pyreverse/data/packages_No_Name.mmd @@ -3,6 +3,8 @@ classDiagram } class clientmodule_test { } + class nullable_pattern { + } class property_pattern { } class suppliermodule_test { diff --git a/tests/pyreverse/data/packages_No_Name.puml b/tests/pyreverse/data/packages_No_Name.puml index 4037b91bd..773b69ff5 100644 --- a/tests/pyreverse/data/packages_No_Name.puml +++ b/tests/pyreverse/data/packages_No_Name.puml @@ -1,16 +1,14 @@ @startuml packages_No_Name set namespaceSeparator none package "data" as data { - } package "data.clientmodule_test" as data.clientmodule_test { - +} +package "data.nullable_pattern" as data.nullable_pattern { } package "data.property_pattern" as data.property_pattern { - } package "data.suppliermodule_test" as data.suppliermodule_test { - } data.clientmodule_test --> data.suppliermodule_test @enduml diff --git a/tests/pyreverse/data/packages_colorized.dot b/tests/pyreverse/data/packages_colorized.dot index 69346a355..202c9a4e5 100644 --- a/tests/pyreverse/data/packages_colorized.dot +++ b/tests/pyreverse/data/packages_colorized.dot @@ -3,6 +3,7 @@ rankdir=BT charset="utf-8" "data" [color="#77AADD", label=<data>, shape="box", style="filled"]; "data.clientmodule_test" [color="#77AADD", label=<data.clientmodule_test>, shape="box", style="filled"]; +"data.nullable_pattern" [color="#77AADD", label=<data.nullable_pattern>, shape="box", style="filled"]; "data.property_pattern" [color="#77AADD", label=<data.property_pattern>, shape="box", style="filled"]; "data.suppliermodule_test" [color="#77AADD", label=<data.suppliermodule_test>, shape="box", style="filled"]; "data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"]; diff --git a/tests/pyreverse/data/packages_colorized.puml b/tests/pyreverse/data/packages_colorized.puml index afc12b512..639c0654f 100644 --- a/tests/pyreverse/data/packages_colorized.puml +++ b/tests/pyreverse/data/packages_colorized.puml @@ -1,16 +1,14 @@ @startuml packages_colorized set namespaceSeparator none package "data" as data #77AADD { - } package "data.clientmodule_test" as data.clientmodule_test #77AADD { - +} +package "data.nullable_pattern" as data.nullable_pattern #77AADD { } package "data.property_pattern" as data.property_pattern #77AADD { - } package "data.suppliermodule_test" as data.suppliermodule_test #77AADD { - } data.clientmodule_test --> data.suppliermodule_test @enduml diff --git a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot index e1af85e58..7263cf8f5 100644 --- a/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot +++ b/tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot @@ -4,7 +4,7 @@ charset="utf-8" "custom_colors.CheckerCollector" [color="red", fontcolor="black", label=<{CheckerCollector|checker1<br ALIGN="LEFT"/>checker2<br ALIGN="LEFT"/>checker3<br ALIGN="LEFT"/>|}>, shape="record", style="filled"]; "pylint.extensions.check_elif.ElseifUsedChecker" [color="#44BB88", fontcolor="black", label=<{ElseifUsedChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|leave_module(_: nodes.Module): None<br ALIGN="LEFT"/>process_tokens(tokens: list[TokenInfo]): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; "pylint.checkers.exceptions.ExceptionsChecker" [color="yellow", fontcolor="black", label=<{ExceptionsChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>options : tuple<br ALIGN="LEFT"/>|open(): None<br ALIGN="LEFT"/>visit_binop(node: nodes.BinOp): None<br ALIGN="LEFT"/>visit_compare(node: nodes.Compare): None<br ALIGN="LEFT"/>visit_raise(node: nodes.Raise): None<br ALIGN="LEFT"/>visit_tryexcept(node: nodes.TryExcept): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; -"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int | None, str], ...]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; +"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"]; "pylint.checkers.exceptions.ExceptionsChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker1", style="solid"]; "pylint.checkers.stdlib.StdlibChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker3", style="solid"]; "pylint.extensions.check_elif.ElseifUsedChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker2", style="solid"]; diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index 5ab327617..cdcdea7c5 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -183,6 +183,7 @@ def test_known_values1(HANDLER: DiadefsHandler, PROJECT: Project) -> None: assert modules == [ (True, "data"), (True, "data.clientmodule_test"), + (True, "data.nullable_pattern"), (True, "data.property_pattern"), (True, "data.suppliermodule_test"), ] @@ -196,6 +197,7 @@ def test_known_values1(HANDLER: DiadefsHandler, PROJECT: Project) -> None: (True, "DoNothing2"), (True, "DoSomething"), (True, "Interface"), + (True, "NullablePatterns"), (True, "PropertyPatterns"), (True, "Specialization"), ] diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index ce7a65b62..d28d99584 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -73,6 +73,7 @@ def test_project_node(project: Project) -> None: expected = [ "data", "data.clientmodule_test", + "data.nullable_pattern", "data.property_pattern", "data.suppliermodule_test", ] |