Skip to content

Commit efb36a6

Browse files
authored
feat: add checks for built-in exceptions (#23)
Closes #13. This can only be done for built-in exceptions, since there's no way to tell if a variable is a class or an object. Signed-off-by: Henry Schreiner <[email protected]>
1 parent 0d7997e commit efb36a6

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ A checker for Flake8 that helps format nice error messages. The checks are:
1212
- **EM102**: Check for raw usage of an f-string literal in Exception raising.
1313
- **EM103**: Check for raw usage of `.format` on a string literal in Exception
1414
raising.
15+
- **EM104**: Check for missing parentheses for built-in exceptions.
16+
- **EM105**: Check for missing message for built-in exceptions.
1517

1618
The issue is that Python includes the line with the raise in the default
1719
traceback (and most other formatters, like Rich and IPython to too). That means
@@ -78,7 +80,8 @@ script entry-point (`pipx run flake8-errmsg <files>`) or module entry-point
7880
Q: Why Python 3.10+ only? <br/> A: This is a static checker and for developers.
7981
Developers and static checks should be on 3.10 already. And I was lazy and match
8082
statements are fantastic for this sort of thing. And the AST module changed in
81-
3.8 anyway.
83+
3.8 anyway. Use [Ruff][] (which contains the checks from this plugin) if you
84+
need to run on older versions.
8285

8386
Q: What other sorts of checks are acceptable? <br/> A: Things that help with
8487
nice errors. For example, maybe requiring `raise SystemExit(n)` over `sys.exit`,
@@ -91,4 +94,5 @@ nice errors. For example, maybe requiring `raise SystemExit(n)` over `sys.exit`,
9194
[pypi-link]: https://pypi.org/project/flake8-errmsg/
9295
[pypi-platforms]: https://img.shields.io/pypi/pyversions/flake8-errmsg
9396
[pypi-version]: https://img.shields.io/pypi/v/flake8-errmsg
97+
[ruff]: https://github.com/astral-sh/ruff
9498
<!-- prettier-ignore-end -->

src/flake8_errmsg/__init__.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@
88

99
import argparse
1010
import ast
11+
import builtins
1112
import dataclasses
13+
import inspect
1214
import traceback
1315
from collections.abc import Iterator
1416
from pathlib import Path
1517
from typing import Any, ClassVar, NamedTuple
1618

1719
__all__ = ("__version__", "run_on_file", "main", "ErrMsgASTPlugin")
1820

19-
__version__ = "0.4.0"
21+
__version__ = "0.5.0"
22+
23+
BUILTIN_EXCEPTION_LIST = {
24+
name
25+
for name in dir(builtins)
26+
if inspect.isclass(_cls := getattr(builtins, name))
27+
and issubclass(_cls, BaseException)
28+
}
2029

2130

2231
class Flake8ASTErrorInfo(NamedTuple):
@@ -46,6 +55,12 @@ def visit_Raise(self, node: ast.Raise) -> None:
4655
]
4756
):
4857
self.errors.append(EM103(node))
58+
case ast.Name(id=name) if name in BUILTIN_EXCEPTION_LIST:
59+
self.errors.append(EM104(node))
60+
case ast.Call(
61+
func=ast.Name(id=name), args=[]
62+
) if name in BUILTIN_EXCEPTION_LIST:
63+
self.errors.append(EM105(node))
4964
case _:
5065
pass
5166

@@ -65,6 +80,16 @@ def EM103(node: ast.AST) -> Flake8ASTErrorInfo:
6580
return Flake8ASTErrorInfo(node.lineno, node.col_offset, msg, Visitor)
6681

6782

83+
def EM104(node: ast.AST) -> Flake8ASTErrorInfo:
84+
msg = "EM104 Built-in Exceptions must not be thrown without being called"
85+
return Flake8ASTErrorInfo(node.lineno, node.col_offset, msg, Visitor)
86+
87+
88+
def EM105(node: ast.AST) -> Flake8ASTErrorInfo:
89+
msg = "EM105 Built-in Exceptions must have a useful message"
90+
return Flake8ASTErrorInfo(node.lineno, node.col_offset, msg, Visitor)
91+
92+
6893
MAX_STRING_LENGTH = 0
6994

7095

tests/test_package.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,32 @@ def test_err2():
6161
results[0].msg
6262
== "EM103 Exception must not use a .format() string directly, assign to variable first"
6363
)
64+
65+
66+
ERR3 = """\
67+
raise RuntimeError
68+
"""
69+
70+
71+
def test_err3():
72+
node = ast.parse(ERR3)
73+
results = list(m.ErrMsgASTPlugin(node).run())
74+
assert len(results) == 1
75+
assert results[0].line_number == 1
76+
assert (
77+
results[0].msg
78+
== "EM104 Built-in Exceptions must not be thrown without being called"
79+
)
80+
81+
82+
ERR4 = """\
83+
raise RuntimeError()
84+
"""
85+
86+
87+
def test_err4():
88+
node = ast.parse(ERR4)
89+
results = list(m.ErrMsgASTPlugin(node).run())
90+
assert len(results) == 1
91+
assert results[0].line_number == 1
92+
assert results[0].msg == "EM105 Built-in Exceptions must have a useful message"

0 commit comments

Comments
 (0)