Repository: pylint-dev/pylint

Issue:
Crash when adding pylint_flask_sqlalchemy to the --load-plugins in pycharm
### Bug description

When parsing the following ``a.py``:

<!--
 If sharing the code is not an option, please state so,
 but providing only the stacktrace would still be helpful.
 -->

```
Proprietary code removed
```

**Pycharm external tools arguments used to create the crash:**
```
--msg-template="/Users/xxx/code/xxxtools/python/dashboard/{path}:{line}:{column}:{C}:({symbol}){msg}"
--load-plugins="pylint_flask,pylint_flask_sqlalchemy"
$FileDir$
```

**There is no crash when I instead use:**
```
--msg-template="/Users/xxx/code/xxxtools/python/dashboard/{path}:{line}:{column}:{C}:({symbol}){msg}"
--load-plugins="pylint_flask"
$FileDir$
```

Becomes: 
```
/Users/xxx/code/xxxtools/python/dashboard/venv/bin/pylint --msg-template=/Users/xxx/code/xxxtools/python/dashboard/{path}:{line}:{column}:{C}:({symbol}){msg} --load-plugins=pylint_flask,pylint_flask_sqlalchemy /Users/xxx/code/xxxtools/python/dashboard/app/admin
```

**Error I was trying to get rid of by including pylint_flask_sqlalchemy:**
```
E:(no-member)Instance of 'SQLAlchemy' has no 'func' member
```
on code line 
```
x = MyObject.query.filter(
        db.func.date(MyObject.timestamp) == given_date
    ).order_by(MyObject.timestamp.desc())
```

**Other issues with AstroidErrors**: https://github.com/pylint-dev/pylint/issues/8842, https://github.com/pylint-dev/pylint/issues/9139
**Reason I am submitting a fresh issue**: The above issues don't mention plugins. This is almost certainly an issue with pylint_flask_sqlalchemy, but perhaps pylint might be able to fail gracefully when a plugin blows up.

### Command used

```shell
pylint a.py
```

### Pylint output

<details open>
    <summary>
        pylint crashed with a ``AstroidError`` and with the following stacktrace:
    </summary>

```python
Traceback (most recent call last):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/lint/pylinter.py", line 788, in _lint_file
    check_astroid_module(module)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/lint/pylinter.py", line 1017, in check_astroid_module
    retval = self._check_astroid_module(
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/lint/pylinter.py", line 1069, in _check_astroid_module
    walker.walk(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/utils/ast_walker.py", line 94, in walk
    self.walk(child)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/utils/ast_walker.py", line 91, in walk
    callback(astroid)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/checkers/variables.py", line 2059, in visit_importfrom
    self._check_module_attrs(node, module, name.split("."))
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/checkers/variables.py", line 3066, in _check_module_attrs
    module = next(module.getattr(name)[0].infer())
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_ng.py", line 169, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 90, in inner
    yield next(generator)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 49, in wrapped
    for res in _func(node, context, **kwargs):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/bases.py", line 179, in _infer_stmts
    for inf in stmt.infer(context=context):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_ng.py", line 169, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 90, in inner
    yield next(generator)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 49, in wrapped
    for res in _func(node, context, **kwargs):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_classes.py", line 1756, in _infer
    for callee in self.func.infer(context):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_ng.py", line 169, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 90, in inner
    yield next(generator)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 49, in wrapped
    for res in _func(node, context, **kwargs):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/bases.py", line 179, in _infer_stmts
    for inf in stmt.infer(context=context):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_ng.py", line 169, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 90, in inner
    yield next(generator)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/decorators.py", line 49, in wrapped
    for res in _func(node, context, **kwargs):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/node_classes.py", line 2863, in _infer
    module = self.do_import_module()
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/_base_nodes.py", line 168, in do_import_module
    return mymodule.import_module(
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/nodes/scoped_nodes/scoped_nodes.py", line 453, in import_module
    return AstroidManager().ast_from_module_name(
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/manager.py", line 259, in ast_from_module_name
    return self.ast_from_file(found_spec.location, modname, fallback=False)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/manager.py", line 151, in ast_from_file
    return AstroidBuilder(self).file_build(filepath, modname)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/builder.py", line 140, in file_build
    return self._post_build(module, builder, encoding)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/builder.py", line 168, in _post_build
    module = self._manager.visit_transforms(module)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/manager.py", line 114, in visit_transforms
    return self._transform.visit(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 151, in visit
    return self._visit(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 81, in _visit
    visited = self._visit_generic(value)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 112, in _visit_generic
    return [self._visit_generic(child) for child in node]
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 112, in <listcomp>
    return [self._visit_generic(child) for child in node]
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 118, in _visit_generic
    return self._visit(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 84, in _visit
    return self._transform(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/astroid/transforms.py", line 66, in _transform
    ret = transform_func(node)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint_flask_sqlalchemy/__init__.py", line 24, in transform
    cls.locals[key] = [ClassDef(key, None)]
TypeError: __init__() missing 2 required positional arguments: 'col_offset' and 'parent'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/lint/pylinter.py", line 752, in _lint_files
    self._lint_file(fileitem, module, check_astroid_module)
  File "/Users/xxx/code/xxxtools/python/dashboard/venv/lib/python3.9/site-packages/pylint/lint/pylinter.py", line 790, in _lint_file
    raise astroid.AstroidError from e
astroid.exceptions.AstroidError

```


</details>

### Expected behavior

No crash.

### Pylint version

```shell
pylint 3.0.3
astroid 3.0.2
Python 3.9.6 (default, Nov 10 2023, 13:38:27) 
[Clang 15.0.0 (clang-1500.1.0.2.5)]
```

### OS / Environment

darwin (Darwin)

### Additional dependencies

<!--
Please remove this part if you're not using any of
your dependencies in the example.
 -->



Discussion:
Thanks for the report. I think it's better that pylint/astroid fail catastrophically so that plugin users/authors are aware of the incompatibility.

The issue with ClassDef instantation should be reported to the plugin.



Patch:
diff --git a/doc/whatsnew/fragments/9398.false_positive b/doc/whatsnew/fragments/9398.false_positive
new file mode 100644
index 0000000000..12069d6478
--- /dev/null
+++ b/doc/whatsnew/fragments/9398.false_positive
@@ -0,0 +1,4 @@
+Classes with only an Ellipsis (``...``) in their body do not trigger 'multiple-statements'
+anymore if they are inlined (in accordance with black's 2024 style).
+
+Closes #9398
diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py
index f4de616598..f8aecbda64 100644
--- a/pylint/checkers/format.py
+++ b/pylint/checkers/format.py
@@ -522,9 +522,8 @@ def visit_default(self, node: nodes.NodeNG) -> None:
 
     def _check_multi_statement_line(self, node: nodes.NodeNG, line: int) -> None:
         """Check for lines containing multiple statements."""
-        # Do not warn about multiple nested context managers
-        # in with statements.
         if isinstance(node, nodes.With):
+            # Do not warn about multiple nested context managers in with statements.
             return
         if (
             isinstance(node.parent, nodes.If)
@@ -539,16 +538,16 @@ def _check_multi_statement_line(self, node: nodes.NodeNG, line: int) -> None:
         ):
             return
 
-        # Functions stubs with ``Ellipsis`` as body are exempted.
+        # Functions stubs and class with ``Ellipsis`` as body are exempted.
         if (
-            isinstance(node.parent, nodes.FunctionDef)
-            and isinstance(node, nodes.Expr)
+            isinstance(node, nodes.Expr)
+            and isinstance(node.parent, (nodes.FunctionDef, nodes.ClassDef))
             and isinstance(node.value, nodes.Const)
             and node.value.value is Ellipsis
         ):
             return
 
-        self.add_message("multiple-statements", node=node)
+        self.add_message("multiple-statements", node=node, confidence=HIGH)
         self._visited_lines[line] = 2
 
     def check_trailing_whitespace_ending(self, line: str, i: int) -> None:


Test patch:
diff --git a/tests/functional/m/multiple_statements.py b/tests/functional/m/multiple_statements.py
index c3252f797c..cc0a53e111 100644
--- a/tests/functional/m/multiple_statements.py
+++ b/tests/functional/m/multiple_statements.py
@@ -4,13 +4,28 @@
 
 from typing import overload
 
+if True: print("Golfing sure is nice")  # [multiple-statements]
 if True: pass  # [multiple-statements]
+if True: ...  # [multiple-statements]
+
+if True: print("Golfing sure is nice")  # [multiple-statements]
+else:
+    pass
 
 if True: pass  # [multiple-statements]
 else:
     pass
 
+if True: ...  # [multiple-statements]
+else:
+    pass
+
+# The following difference in behavior is due to black 2024's style
+# that reformat pass on multiple line but reformat "..." on a single line
+# (only for classes, not for the examples above)
+class MyException(Exception): print("Golfing sure is nice")  # [multiple-statements]
 class MyError(Exception): pass  # [multiple-statements]
+class DebugTrueDetected(Exception): ...
 
 class MyError(Exception): a='a'  # [multiple-statements]
 
diff --git a/tests/functional/m/multiple_statements.txt b/tests/functional/m/multiple_statements.txt
index 661314268d..6b5be1adf6 100644
--- a/tests/functional/m/multiple_statements.txt
+++ b/tests/functional/m/multiple_statements.txt
@@ -1,5 +1,10 @@
-multiple-statements:7:9:7:13::More than one statement on a single line:UNDEFINED
-multiple-statements:9:9:9:13::More than one statement on a single line:UNDEFINED
-multiple-statements:13:26:13:30:MyError:More than one statement on a single line:UNDEFINED
-multiple-statements:15:26:15:31:MyError:More than one statement on a single line:UNDEFINED
-multiple-statements:17:26:17:31:MyError:More than one statement on a single line:UNDEFINED
+multiple-statements:7:9:7:38::More than one statement on a single line:HIGH
+multiple-statements:8:9:8:13::More than one statement on a single line:HIGH
+multiple-statements:9:9:9:12::More than one statement on a single line:HIGH
+multiple-statements:11:9:11:38::More than one statement on a single line:HIGH
+multiple-statements:15:9:15:13::More than one statement on a single line:HIGH
+multiple-statements:19:9:19:12::More than one statement on a single line:HIGH
+multiple-statements:26:30:26:59:MyException:More than one statement on a single line:HIGH
+multiple-statements:27:26:27:30:MyError:More than one statement on a single line:HIGH
+multiple-statements:30:26:30:31:MyError:More than one statement on a single line:HIGH
+multiple-statements:32:26:32:31:MyError:More than one statement on a single line:HIGH
diff --git a/tests/functional/m/multiple_statements_single_line.py b/tests/functional/m/multiple_statements_single_line.py
index 93a470702c..8b39d631bd 100644
--- a/tests/functional/m/multiple_statements_single_line.py
+++ b/tests/functional/m/multiple_statements_single_line.py
@@ -4,19 +4,32 @@
 
 from typing import overload
 
+if True: print("Golfing sure is nice")
 if True: pass
+if True: ...
+
+if True: print("Golfing sure is nice")  # [multiple-statements]
+else:
+    pass
 
 if True: pass  # [multiple-statements]
 else:
     pass
 
+if True: ...  # [multiple-statements]
+else:
+    pass
+
+class MyException(Exception): print("Golfing sure is nice")
 class MyError(Exception): pass
+class DebugTrueDetected(Exception): ...
+
 
 class MyError(Exception): a='a'
 
 class MyError(Exception): a='a'; b='b'  # [multiple-statements]
 
-try:
+try:  #@
     pass
 except:
     pass
diff --git a/tests/functional/m/multiple_statements_single_line.txt b/tests/functional/m/multiple_statements_single_line.txt
index cac2f7eb2e..8d19c72516 100644
--- a/tests/functional/m/multiple_statements_single_line.txt
+++ b/tests/functional/m/multiple_statements_single_line.txt
@@ -1,2 +1,4 @@
-multiple-statements:9:9:9:13::More than one statement on a single line:UNDEFINED
-multiple-statements:17:26:17:31:MyError:More than one statement on a single line:UNDEFINED
+multiple-statements:11:9:11:38::More than one statement on a single line:HIGH
+multiple-statements:15:9:15:13::More than one statement on a single line:HIGH
+multiple-statements:19:9:19:12::More than one statement on a single line:HIGH
+multiple-statements:30:26:30:31:MyError:More than one statement on a single line:HIGH