From 5d82d7bb6b94a3a6a4d7a4861262519e62916881 Mon Sep 17 00:00:00 2001 From: ViRuSTriNiTy Date: Wed, 26 Apr 2023 21:43:13 +0200 Subject: Added escaping of vertical bar character in annotation labels (#8610) --- doc/whatsnew/fragments/8603.bugfix | 3 +++ pylint/pyreverse/dot_printer.py | 10 +++++++++- tests/data/nullable_pattern.py | 10 ++++++++++ tests/pyreverse/data/classes_No_Name.dot | 1 + tests/pyreverse/data/classes_No_Name.html | 6 +++++- tests/pyreverse/data/classes_No_Name.mmd | 4 ++++ tests/pyreverse/data/classes_No_Name.puml | 4 ++++ tests/pyreverse/data/classes_colorized.dot | 1 + tests/pyreverse/data/classes_colorized.puml | 4 ++++ tests/pyreverse/data/packages_No_Name.dot | 1 + tests/pyreverse/data/packages_No_Name.html | 4 +++- tests/pyreverse/data/packages_No_Name.mmd | 2 ++ tests/pyreverse/data/packages_No_Name.puml | 6 ++---- tests/pyreverse/data/packages_colorized.dot | 1 + tests/pyreverse/data/packages_colorized.puml | 6 ++---- .../class_diagrams/colorized_output/custom_colors.dot | 2 +- tests/pyreverse/test_diadefs.py | 2 ++ tests/pyreverse/test_inspector.py | 1 + 18 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 doc/whatsnew/fragments/8603.bugfix create mode 100644 tests/data/nullable_pattern.py 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|
|}>, shape="record", style="solid"]; "data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label=<{DoSomething|my_int : Optional[int]
my_int_2 : Optional[int]
my_string : str
|do_it(new_int: int): int
}>, shape="record", style="solid"]; "data.suppliermodule_test.Interface" [color="black", fontcolor="black", label=<{Interface|
|get_value()
set_value(value)
}>, shape="record", style="solid"]; +"data.nullable_pattern.NullablePatterns" [color="black", fontcolor="black", label=<{NullablePatterns|
|return_nullable_1(): int \| None
return_nullable_2(): Optional[int]
}>, shape="record", style="solid"]; "data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label=<{PropertyPatterns|prop1
prop2
|}>, shape="record", style="solid"]; "data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str
relation
relation2
top : str
|from_value(value: int)
increment_value(): None
transform_value(value: int): int
}>, 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 - + 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|
|}>, shape="record", style="filled"]; "data.suppliermodule_test.DoSomething" [color="#77AADD", fontcolor="black", label=<{DoSomething|my_int : Optional[int]
my_int_2 : Optional[int]
my_string : str
|do_it(new_int: int): int
}>, shape="record", style="filled"]; "data.suppliermodule_test.Interface" [color="#77AADD", fontcolor="black", label=<{Interface|
|get_value()
set_value(value)
}>, shape="record", style="filled"]; +"data.nullable_pattern.NullablePatterns" [color="#77AADD", fontcolor="black", label=<{NullablePatterns|
|return_nullable_1(): int \| None
return_nullable_2(): Optional[int]
}>, shape="record", style="filled"]; "data.property_pattern.PropertyPatterns" [color="#77AADD", fontcolor="black", label=<{PropertyPatterns|prop1
prop2
|}>, shape="record", style="filled"]; "data.clientmodule_test.Specialization" [color="#77AADD", fontcolor="black", label=<{Specialization|TYPE : str
relation
relation2
top : str
|from_value(value: int)
increment_value(): None
transform_value(value: int): int
}>, 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=, shape="box", style="solid"]; "data.clientmodule_test" [color="black", label=, shape="box", style="solid"]; +"data.nullable_pattern" [color="black", label=, shape="box", style="solid"]; "data.property_pattern" [color="black", label=, shape="box", style="solid"]; "data.suppliermodule_test" [color="black", label=, 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 - + 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=, shape="box", style="filled"]; "data.clientmodule_test" [color="#77AADD", label=, shape="box", style="filled"]; +"data.nullable_pattern" [color="#77AADD", label=, shape="box", style="filled"]; "data.property_pattern" [color="#77AADD", label=, shape="box", style="filled"]; "data.suppliermodule_test" [color="#77AADD", label=, 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
checker2
checker3
|}>, shape="record", style="filled"]; "pylint.extensions.check_elif.ElseifUsedChecker" [color="#44BB88", fontcolor="black", label=<{ElseifUsedChecker|msgs : dict
name : str
|leave_module(_: nodes.Module): None
process_tokens(tokens: list[TokenInfo]): None
visit_if(node: nodes.If): None
}>, shape="record", style="filled"]; "pylint.checkers.exceptions.ExceptionsChecker" [color="yellow", fontcolor="black", label=<{ExceptionsChecker|msgs : dict
name : str
options : tuple
|open(): None
visit_binop(node: nodes.BinOp): None
visit_compare(node: nodes.Compare): None
visit_raise(node: nodes.Raise): None
visit_tryexcept(node: nodes.TryExcept): None
}>, shape="record", style="filled"]; -"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]
name : str
|deprecated_arguments(method: str): tuple[tuple[int | None, str], ...]
deprecated_classes(module: str): Iterable[str]
deprecated_decorators(): Iterable[str]
deprecated_methods(): set[str]
visit_boolop(node: nodes.BoolOp): None
visit_call(node: nodes.Call): None
visit_functiondef(node: nodes.FunctionDef): None
visit_if(node: nodes.If): None
visit_ifexp(node: nodes.IfExp): None
visit_unaryop(node: nodes.UnaryOp): None
}>, shape="record", style="filled"]; +"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]
name : str
|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]
deprecated_classes(module: str): Iterable[str]
deprecated_decorators(): Iterable[str]
deprecated_methods(): set[str]
visit_boolop(node: nodes.BoolOp): None
visit_call(node: nodes.Call): None
visit_functiondef(node: nodes.FunctionDef): None
visit_if(node: nodes.If): None
visit_ifexp(node: nodes.IfExp): None
visit_unaryop(node: nodes.UnaryOp): None
}>, 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", ] -- cgit v1.2.1