Skip to content

Commit 588aacc

Browse files
authored
Verify nodes' str() and repr() don't raise errors/warnings (#2198)
Calling str() or repr() on certain nodes fails either with errors or warnings. A unittest was added to verify this behaviour and find the offending nodes. Code has been corrected, mainly by accesing node's attributes safely and using placeholders if necessary. Closes #1881
1 parent ee12160 commit 588aacc

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

astroid/nodes/node_ng.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def __str__(self) -> str:
210210
alignment = len(cname) + 1
211211
result = []
212212
for field in self._other_fields + self._astroid_fields:
213-
value = getattr(self, field)
213+
value = getattr(self, field, "Unknown")
214214
width = 80 - len(field) - alignment
215215
lines = pprint.pformat(value, indent=2, width=width).splitlines(True)
216216

@@ -227,14 +227,19 @@ def __str__(self) -> str:
227227

228228
def __repr__(self) -> str:
229229
rname = self.repr_name()
230+
# The dependencies used to calculate fromlineno (if not cached) may not exist at the time
231+
try:
232+
lineno = self.fromlineno
233+
except AttributeError:
234+
lineno = 0
230235
if rname:
231236
string = "<%(cname)s.%(rname)s l.%(lineno)s at 0x%(id)x>"
232237
else:
233238
string = "<%(cname)s l.%(lineno)s at 0x%(id)x>"
234239
return string % {
235240
"cname": type(self).__name__,
236241
"rname": rname,
237-
"lineno": self.fromlineno,
242+
"lineno": lineno,
238243
"id": id(self),
239244
}
240245

tests/test_nodes.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from __future__ import annotations
88

99
import copy
10+
import inspect
1011
import os
12+
import random
1113
import sys
1214
import textwrap
1315
import unittest
@@ -1880,3 +1882,35 @@ def return_from_match(x):
18801882
inferred = node.inferred()
18811883
assert len(inferred) == 2
18821884
assert [inf.value for inf in inferred] == [10, -1]
1885+
1886+
1887+
@pytest.mark.parametrize(
1888+
"node",
1889+
[
1890+
node
1891+
for node in astroid.nodes.ALL_NODE_CLASSES
1892+
if node.__name__
1893+
not in ["_BaseContainer", "BaseContainer", "NodeNG", "const_factory"]
1894+
],
1895+
)
1896+
@pytest.mark.filterwarnings("error")
1897+
def test_str_repr_no_warnings(node):
1898+
parameters = inspect.signature(node.__init__).parameters
1899+
1900+
args = {}
1901+
for name, param_type in parameters.items():
1902+
if name == "self":
1903+
continue
1904+
1905+
if "int" in param_type.annotation:
1906+
args[name] = random.randint(0, 50)
1907+
elif "NodeNG" in param_type.annotation:
1908+
args[name] = nodes.Unknown()
1909+
elif "str" in param_type.annotation:
1910+
args[name] = ""
1911+
else:
1912+
args[name] = None
1913+
1914+
test_node = node(**args)
1915+
str(test_node)
1916+
repr(test_node)

0 commit comments

Comments
 (0)