summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViRuSTriNiTy <cradle-of-mail@gmx.de>2023-04-26 21:43:13 +0200
committerGitHub <noreply@github.com>2023-04-26 21:43:13 +0200
commit5d82d7bb6b94a3a6a4d7a4861262519e62916881 (patch)
tree5768cea98251c8c909ec33b01949ae2505044a75
parente2d6c4db610be73ccaa89c9c40509da8b11854ab (diff)
downloadpylint-git-5d82d7bb6b94a3a6a4d7a4861262519e62916881.tar.gz
Added escaping of vertical bar character in annotation labels (#8610)
-rw-r--r--doc/whatsnew/fragments/8603.bugfix3
-rw-r--r--pylint/pyreverse/dot_printer.py10
-rw-r--r--tests/data/nullable_pattern.py10
-rw-r--r--tests/pyreverse/data/classes_No_Name.dot1
-rw-r--r--tests/pyreverse/data/classes_No_Name.html6
-rw-r--r--tests/pyreverse/data/classes_No_Name.mmd4
-rw-r--r--tests/pyreverse/data/classes_No_Name.puml4
-rw-r--r--tests/pyreverse/data/classes_colorized.dot1
-rw-r--r--tests/pyreverse/data/classes_colorized.puml4
-rw-r--r--tests/pyreverse/data/packages_No_Name.dot1
-rw-r--r--tests/pyreverse/data/packages_No_Name.html4
-rw-r--r--tests/pyreverse/data/packages_No_Name.mmd2
-rw-r--r--tests/pyreverse/data/packages_No_Name.puml6
-rw-r--r--tests/pyreverse/data/packages_colorized.dot1
-rw-r--r--tests/pyreverse/data/packages_colorized.puml6
-rw-r--r--tests/pyreverse/functional/class_diagrams/colorized_output/custom_colors.dot2
-rw-r--r--tests/pyreverse/test_diadefs.py2
-rw-r--r--tests/pyreverse/test_inspector.py1
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",
]