Skip to content

Commit 31e4ce5

Browse files
authored
Improve error message for function bodies with docstrings and ... (introduce Y048) (#256)
1 parent c100d15 commit 31e4ce5

5 files changed

Lines changed: 43 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
New error codes:
66
* Y046: Detect unused `Protocol`s.
7+
* Y048: Function bodies should contain exactly one statement.
8+
9+
Bugfixes:
10+
* Improve error message for the case where a function body contains a docstring
11+
and a `...` or `pass` statement.
712

813
Other changes:
914
* Pin required flake8 version to <5.0.0 (flake8-pyi is not currently compatible with flake8 5.0.0).

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ currently emitted:
7878
| Y044 | `from __future__ import annotations` has no effect in stub files, as forward references in stubs are enabled by default.
7979
| Y045 | `__iter__` methods should never return `Iterable[T]`, as they should always return some kind of iterator.
8080
| Y046 | A private `Protocol` should be used at least once in the file in which it is defined.
81+
| Y048 | Function bodies should contain exactly one statement. (Note that if a function body includes a docstring, the docstring counts as a "statement".)
8182

8283
Many error codes enforce modern conventions, and some cannot yet be used in
8384
all cases:

pyi.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,19 +1560,21 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
15601560
with self.in_function.enabled():
15611561
self.generic_visit(node)
15621562

1563-
for i, statement in enumerate(node.body):
1564-
if i == 0:
1565-
# normally, should just be "..."
1566-
if isinstance(statement, ast.Pass):
1567-
self.error(statement, Y009)
1568-
continue
1569-
# Ellipsis is fine. Str (docstrings) is not but we produce
1570-
# tailored error message for it elsewhere.
1571-
elif isinstance(statement, ast.Expr) and isinstance(
1572-
statement.value, (ast.Ellipsis, ast.Str)
1573-
):
1574-
continue
1575-
self.error(statement, Y010)
1563+
body = node.body
1564+
if len(body) > 1:
1565+
self.error(body[1], Y048)
1566+
elif body:
1567+
statement = body[0]
1568+
# normally, should just be "..."
1569+
if isinstance(statement, ast.Pass):
1570+
self.error(statement, Y009)
1571+
# Ellipsis is fine. Str (docstrings) is not but we produce
1572+
# tailored error message for it elsewhere.
1573+
elif not (
1574+
isinstance(statement, ast.Expr)
1575+
and isinstance(statement.value, (ast.Ellipsis, ast.Str))
1576+
):
1577+
self.error(statement, Y010)
15761578

15771579
if self.in_class.active:
15781580
self.check_self_typevars(node)
@@ -1745,3 +1747,4 @@ def parse_options(
17451747
Y044 = 'Y044 "from __future__ import annotations" has no effect in stub files.'
17461748
Y045 = 'Y045 "{iter_method}" methods should return an {good_cls}, not an {bad_cls}'
17471749
Y046 = 'Y046 Protocol "{protocol_name}" is not used'
1750+
Y048 = "Y048 Function body should contain exactly one statement"

tests/emptyfunctions.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def returning(x: int) -> float:
2222

2323
def multiple_ellipses(x: int) -> float:
2424
...
25-
... # Y010 Function body must contain only "..."
25+
... # Y048 Function body should contain exactly one statement
2626

2727
async def empty_async(x: int) -> float: ...
2828

@@ -37,4 +37,4 @@ async def returning_async(x: int) -> float:
3737

3838
async def multiple_ellipses_async(x: int) -> float:
3939
...
40-
... # Y010 Function body must contain only "..."
40+
... # Y048 Function body should contain exactly one statement

tests/quotes.pyi

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,22 @@ elif sys.platform == "win32":
3434
f: "str" # Y020 Quoted annotations should never be used in stubs
3535
else:
3636
f: "bytes" # Y020 Quoted annotations should never be used in stubs
37+
38+
class Empty:
39+
"""Empty""" # Y021 Docstrings should not be included in stubs
40+
41+
def docstring_and_ellipsis() -> None:
42+
"""Docstring""" # Y021 Docstrings should not be included in stubs
43+
... # Y048 Function body should contain exactly one statement
44+
45+
def docstring_and_pass() -> None:
46+
"""Docstring""" # Y021 Docstrings should not be included in stubs
47+
pass # Y048 Function body should contain exactly one statement
48+
49+
class DocstringAndEllipsis:
50+
"""Docstring""" # Y021 Docstrings should not be included in stubs
51+
... # Y013 Non-empty class body must not contain "..."
52+
53+
class DocstringAndPass:
54+
"""Docstring""" # Y021 Docstrings should not be included in stubs
55+
pass # Y012 Class body must not contain "pass"

0 commit comments

Comments
 (0)