Skip to content

Commit 4350c6f

Browse files
authored
Added escaping of vertical bar character in annotation labels (#8610) (#8631)
1 parent e507a4d commit 4350c6f

19 files changed

+80
-25
lines changed

doc/whatsnew/fragments/8603.bugfix

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``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.
2+
3+
Closes #8603

pylint/pyreverse/dot_printer.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,19 @@ def _build_label_for_node(self, properties: NodeProperties) -> str:
121121
)
122122
label += rf"{method_name}({', '.join(args)})"
123123
if func.returns:
124-
label += ": " + get_annotation_label(func.returns)
124+
annotation_label = get_annotation_label(func.returns)
125+
label += ": " + self._escape_annotation_label(annotation_label)
125126
label += rf"{HTMLLabels.LINEBREAK_LEFT.value}"
126127
label += "}"
127128
return label
128129

130+
def _escape_annotation_label(self, annotation_label: str) -> str:
131+
# Escape vertical bar characters to make them appear as a literal characters
132+
# otherwise it gets treated as field separator of record-based nodes
133+
annotation_label = annotation_label.replace("|", r"\|")
134+
135+
return annotation_label
136+
129137
def emit_edge(
130138
self,
131139
from_node: str,

tests/data/nullable_pattern.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
""" docstring for file nullable_pattern.py """
2+
from typing import Optional
3+
4+
class NullablePatterns:
5+
def return_nullable_1(self) -> int | None:
6+
""" Nullable return type using the | operator as mentioned in PEP 604, see https://peps.python.org/pep-0604 """
7+
pass
8+
9+
def return_nullable_2(self) -> Optional[int]:
10+
pass

tests/pyreverse/data/classes_No_Name.dot

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ charset="utf-8"
77
"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
88
"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"];
99
"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"];
10+
"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"];
1011
"data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
1112
"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"];
1213
"data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"];

tests/pyreverse/data/classes_No_Name.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
get_value()*
2727
set_value(value)*
2828
}
29+
class NullablePatterns {
30+
return_nullable_1()* int | None
31+
return_nullable_2()* Optional[int]
32+
}
2933
class PropertyPatterns {
3034
prop1
3135
prop2
@@ -44,7 +48,7 @@
4448
DoNothing --* Ancestor : cls_member
4549
DoNothing --* Specialization : relation
4650
DoNothing2 --o Specialization : relation2
47-
51+
4852
</div>
4953
</body>
5054
</html>

tests/pyreverse/data/classes_No_Name.mmd

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ classDiagram
2121
get_value()*
2222
set_value(value)*
2323
}
24+
class NullablePatterns {
25+
return_nullable_1()* int | None
26+
return_nullable_2()* Optional[int]
27+
}
2428
class PropertyPatterns {
2529
prop1
2630
prop2

tests/pyreverse/data/classes_No_Name.puml

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class "Interface" as data.suppliermodule_test.Interface {
2222
{abstract}get_value()
2323
{abstract}set_value(value)
2424
}
25+
class "NullablePatterns" as data.nullable_pattern.NullablePatterns {
26+
{abstract}return_nullable_1() -> int | None
27+
{abstract}return_nullable_2() -> Optional[int]
28+
}
2529
class "PropertyPatterns" as data.property_pattern.PropertyPatterns {
2630
prop1
2731
prop2

tests/pyreverse/data/classes_No_Name.vcg

+16-13
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,53 @@ graph:{
66
manhattan_edges:yes
77
node: {title:"data.clientmodule_test.Ancestor" label:"\fbAncestor\fn\n\f____________\n\f08attr : str\n\f08cls_member\n\f____________\n\f10get_value()\n\f10set_value()"
88
shape:box
9-
}
9+
}
1010
node: {title:"data.suppliermodule_test.CustomException" label:"\fb 09CustomException\fn\n\f_________________"
1111
shape:box
12-
}
12+
}
1313
node: {title:"data.suppliermodule_test.DoNothing" label:"\fbDoNothing\fn\n\f___________"
1414
shape:box
15-
}
15+
}
1616
node: {title:"data.suppliermodule_test.DoNothing2" label:"\fbDoNothing2\fn\n\f____________"
1717
shape:box
18-
}
18+
}
1919
node: {title:"data.suppliermodule_test.DoSomething" label:"\fbDoSomething\fn\n\f__________________________\n\f08my_int : Optional[int]\n\f08my_int_2 : Optional[int]\n\f08my_string : str\n\f__________________________\n\f10do_it()"
2020
shape:box
21-
}
21+
}
2222
node: {title:"data.suppliermodule_test.Interface" label:"\fbInterface\fn\n\f___________\n\f10get_value()\n\f10set_value()"
2323
shape:box
24-
}
24+
}
25+
node: {title:"data.nullable_pattern.NullablePatterns" label:"\fbNullablePatterns\fn\n\f___________________\n\f10return_nullable_1()\n\f10return_nullable_2()"
26+
shape:box
27+
}
2528
node: {title:"data.property_pattern.PropertyPatterns" label:"\fbPropertyPatterns\fn\n\f__________________\n\f08prop1\n\f08prop2\n\f__________________"
2629
shape:box
27-
}
30+
}
2831
node: {title:"data.clientmodule_test.Specialization" label:"\fbSpecialization\fn\n\f_________________\n\f08TYPE : str\n\f08relation\n\f08relation2\n\f08top : str\n\f_________________\n\f10from_value()\n\f10increment_value()\n\f10transform_value()"
2932
shape:box
30-
}
33+
}
3134
edge: {sourcename:"data.clientmodule_test.Specialization" targetname:"data.clientmodule_test.Ancestor" arrowstyle:solid
3235
backarrowstyle:none
3336
backarrowsize:10
34-
}
37+
}
3538
edge: {sourcename:"data.clientmodule_test.Ancestor" targetname:"data.suppliermodule_test.Interface" arrowstyle:solid
3639
backarrowstyle:none
3740
linestyle:dotted
3841
backarrowsize:10
39-
}
42+
}
4043
edge: {sourcename:"data.suppliermodule_test.DoNothing" targetname:"data.clientmodule_test.Ancestor" arrowstyle:solid
4144
backarrowstyle:none
4245
textcolor:green
4346
label:"cls_member"
44-
}
47+
}
4548
edge: {sourcename:"data.suppliermodule_test.DoNothing" targetname:"data.clientmodule_test.Specialization" arrowstyle:solid
4649
backarrowstyle:none
4750
textcolor:green
4851
label:"relation"
49-
}
52+
}
5053
edge: {sourcename:"data.suppliermodule_test.DoNothing2" targetname:"data.clientmodule_test.Specialization" arrowstyle:solid
5154
backarrowstyle:none
5255
textcolor:green
5356
label:"relation2"
54-
}
57+
}
5558
}

tests/pyreverse/data/classes_colorized.dot

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ charset="utf-8"
77
"data.suppliermodule_test.DoNothing2" [color="aliceblue", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
88
"data.suppliermodule_test.DoSomething" [color="aliceblue", 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"];
99
"data.suppliermodule_test.Interface" [color="aliceblue", 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"];
10+
"data.nullable_pattern.NullablePatterns" [color="aliceblue", 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"];
1011
"data.property_pattern.PropertyPatterns" [color="aliceblue", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
1112
"data.clientmodule_test.Specialization" [color="aliceblue", 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"];
1213
"data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"];

tests/pyreverse/data/classes_colorized.puml

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class "Interface" as data.suppliermodule_test.Interface #aliceblue {
2222
{abstract}get_value()
2323
{abstract}set_value(value)
2424
}
25+
class "NullablePatterns" as data.nullable_pattern.NullablePatterns #aliceblue {
26+
{abstract}return_nullable_1() -> int | None
27+
{abstract}return_nullable_2() -> Optional[int]
28+
}
2529
class "PropertyPatterns" as data.property_pattern.PropertyPatterns #aliceblue {
2630
prop1
2731
prop2

tests/pyreverse/data/packages_No_Name.dot

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ rankdir=BT
33
charset="utf-8"
44
"data" [color="black", label=<data>, shape="box", style="solid"];
55
"data.clientmodule_test" [color="black", label=<data.clientmodule_test>, shape="box", style="solid"];
6+
"data.nullable_pattern" [color="black", label=<data.nullable_pattern>, shape="box", style="solid"];
67
"data.property_pattern" [color="black", label=<data.property_pattern>, shape="box", style="solid"];
78
"data.suppliermodule_test" [color="black", label=<data.suppliermodule_test>, shape="box", style="solid"];
89
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];

tests/pyreverse/data/packages_No_Name.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
}
99
class clientmodule_test {
1010
}
11+
class nullable_pattern {
12+
}
1113
class property_pattern {
1214
}
1315
class suppliermodule_test {
1416
}
1517
clientmodule_test --> suppliermodule_test
16-
18+
1719
</div>
1820
</body>
1921
</html>

tests/pyreverse/data/packages_No_Name.mmd

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ classDiagram
33
}
44
class clientmodule_test {
55
}
6+
class nullable_pattern {
7+
}
68
class property_pattern {
79
}
810
class suppliermodule_test {
+2-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
@startuml packages_No_Name
22
set namespaceSeparator none
33
package "data" as data {
4-
54
}
65
package "data.clientmodule_test" as data.clientmodule_test {
7-
6+
}
7+
package "data.nullable_pattern" as data.nullable_pattern {
88
}
99
package "data.property_pattern" as data.property_pattern {
10-
1110
}
1211
package "data.suppliermodule_test" as data.suppliermodule_test {
13-
1412
}
1513
data.clientmodule_test --> data.suppliermodule_test
1614
@enduml

tests/pyreverse/data/packages_No_Name.vcg

+8-5
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@ graph:{
66
manhattan_edges:yes
77
node: {title:"data" label:"\fbdata\fn"
88
shape:box
9-
}
9+
}
1010
node: {title:"data.clientmodule_test" label:"\fbdata.clientmodule_test\fn"
1111
shape:box
12-
}
12+
}
13+
node: {title:"data.nullable_pattern" label:"\fbdata.nullable_pattern\fn"
14+
shape:box
15+
}
1316
node: {title:"data.property_pattern" label:"\fbdata.property_pattern\fn"
1417
shape:box
15-
}
18+
}
1619
node: {title:"data.suppliermodule_test" label:"\fbdata.suppliermodule_test\fn"
1720
shape:box
18-
}
21+
}
1922
edge: {sourcename:"data.clientmodule_test" targetname:"data.suppliermodule_test" arrowstyle:solid
2023
backarrowstyle:none
2124
backarrowsize:0
22-
}
25+
}
2326
}

tests/pyreverse/data/packages_colorized.dot

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ rankdir=BT
33
charset="utf-8"
44
"data" [color="aliceblue", label=<data>, shape="box", style="filled"];
55
"data.clientmodule_test" [color="aliceblue", label=<data.clientmodule_test>, shape="box", style="filled"];
6+
"data.nullable_pattern" [color="aliceblue", label=<data.nullable_pattern>, shape="box", style="filled"];
67
"data.property_pattern" [color="aliceblue", label=<data.property_pattern>, shape="box", style="filled"];
78
"data.suppliermodule_test" [color="aliceblue", label=<data.suppliermodule_test>, shape="box", style="filled"];
89
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];

tests/pyreverse/data/packages_colorized.puml

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ package "data" as data #aliceblue {
55
}
66
package "data.clientmodule_test" as data.clientmodule_test #aliceblue {
77

8+
}
9+
package "data.nullable_pattern" as data.nullable_pattern #aliceblue {
10+
811
}
912
package "data.property_pattern" as data.property_pattern #aliceblue {
1013

tests/pyreverse/test_diadefs.py

+2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def test_known_values1(HANDLER: DiadefsHandler, PROJECT: Project) -> None:
141141
assert modules == [
142142
(True, "data"),
143143
(True, "data.clientmodule_test"),
144+
(True, "data.nullable_pattern"),
144145
(True, "data.property_pattern"),
145146
(True, "data.suppliermodule_test"),
146147
]
@@ -154,6 +155,7 @@ def test_known_values1(HANDLER: DiadefsHandler, PROJECT: Project) -> None:
154155
(True, "DoNothing2"),
155156
(True, "DoSomething"),
156157
(True, "Interface"),
158+
(True, "NullablePatterns"),
157159
(True, "PropertyPatterns"),
158160
(True, "Specialization"),
159161
]

tests/pyreverse/test_inspector.py

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def test_project_node(project: Project) -> None:
136136
expected = [
137137
"data",
138138
"data.clientmodule_test",
139+
"data.nullable_pattern",
139140
"data.property_pattern",
140141
"data.suppliermodule_test",
141142
]

0 commit comments

Comments
 (0)