Repository: pylint-dev/astroid

Issue:
Some `__str__` methods of nodes raise errors and warnings
### Steps to reproduce
```python
>>> fd = astroid.nodes.FunctionDef()
>>> str(fd)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jacobwalls/astroid/astroid/nodes/node_ng.py", line 219, in __str__
    value = getattr(self, field)
            ^^^^^^^^^^^^^^^^^^^^
AttributeError: 'FunctionDef' object has no attribute 'args'
```

Less severely, but still not great, launching python with warnings e.g. `python -Wall`:
```python
>>> cd = astroid.nodes.ClassDef()
>>> str(cd)
/Users/.../astroid/astroid/nodes/scoped_nodes/scoped_nodes.py:2041: DeprecationWarning: The 'ClassDef.doc' attribute is deprecated, use 'ClassDef.doc_node' instead.
```

***
We should add a unittest that generates the `str()` and `repr()` of all node types.

### `python -c "from astroid import __pkginfo__; print(__pkginfo__.version)"` output
2.12.13


Discussion / hints:
The first issue is caused by the fact that the `str` relies on `postinit` being called I think.

I ran into this while debugging `astroid` many times..



Fix (patch):
diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py
index 977469df90..af2d270e1b 100644
--- a/astroid/nodes/node_ng.py
+++ b/astroid/nodes/node_ng.py
@@ -210,7 +210,7 @@ def __str__(self) -> str:
             alignment = len(cname) + 1
         result = []
         for field in self._other_fields + self._astroid_fields:
-            value = getattr(self, field)
+            value = getattr(self, field, "Unknown")
             width = 80 - len(field) - alignment
             lines = pprint.pformat(value, indent=2, width=width).splitlines(True)
 
@@ -227,6 +227,11 @@ def __str__(self) -> str:
 
     def __repr__(self) -> str:
         rname = self.repr_name()
+        # The dependencies used to calculate fromlineno (if not cached) may not exist at the time
+        try:
+            lineno = self.fromlineno
+        except AttributeError:
+            lineno = 0
         if rname:
             string = "<%(cname)s.%(rname)s l.%(lineno)s at 0x%(id)x>"
         else:
@@ -234,7 +239,7 @@ def __repr__(self) -> str:
         return string % {
             "cname": type(self).__name__,
             "rname": rname,
-            "lineno": self.fromlineno,
+            "lineno": lineno,
             "id": id(self),
         }
 


Test changes:
diff --git a/tests/test_nodes.py b/tests/test_nodes.py
index 6303bbef28..d5c017dfc4 100644
--- a/tests/test_nodes.py
+++ b/tests/test_nodes.py
@@ -7,7 +7,9 @@
 from __future__ import annotations
 
 import copy
+import inspect
 import os
+import random
 import sys
 import textwrap
 import unittest
@@ -1880,3 +1882,35 @@ def return_from_match(x):
         inferred = node.inferred()
         assert len(inferred) == 2
         assert [inf.value for inf in inferred] == [10, -1]
+
+
+@pytest.mark.parametrize(
+    "node",
+    [
+        node
+        for node in astroid.nodes.ALL_NODE_CLASSES
+        if node.__name__
+        not in ["_BaseContainer", "BaseContainer", "NodeNG", "const_factory"]
+    ],
+)
+@pytest.mark.filterwarnings("error")
+def test_str_repr_no_warnings(node):
+    parameters = inspect.signature(node.__init__).parameters
+
+    args = {}
+    for name, param_type in parameters.items():
+        if name == "self":
+            continue
+
+        if "int" in param_type.annotation:
+            args[name] = random.randint(0, 50)
+        elif "NodeNG" in param_type.annotation:
+            args[name] = nodes.Unknown()
+        elif "str" in param_type.annotation:
+            args[name] = ""
+        else:
+            args[name] = None
+
+    test_node = node(**args)
+    str(test_node)
+    repr(test_node)