Skip to content

Commit 78eed6a

Browse files
authored
New checker - Detect use of unnecessary ellipsis (#5470)
Closes #5460
1 parent 35254c2 commit 78eed6a

File tree

8 files changed

+161
-3
lines changed

8 files changed

+161
-3
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Release date: TBA
5353

5454
Closes #5323
5555

56+
* Add checker ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily.
57+
58+
Closes #5460
59+
5660
* Fixed incorrect classification of Numpy-style docstring as Google-style docstring for
5761
docstrings with property setter documentation.
5862
Docstring classification is now based on the highest amount of matched sections instead

doc/whatsnew/2.13.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Summary -- Release highlights
1010

1111
New checkers
1212
============
13+
* ``unnecessary-ellipsis``: Emmitted when the ellipsis constant is used unnecessarily.
14+
15+
Closes #5460
1316

1417
Removed checkers
1518
================

pylint/checkers/ellipsis_checker.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Ellipsis checker for Python code
2+
"""
3+
from astroid import nodes
4+
5+
from pylint.checkers import BaseChecker
6+
from pylint.checkers.utils import check_messages
7+
from pylint.interfaces import IAstroidChecker
8+
from pylint.lint import PyLinter
9+
10+
11+
class EllipsisChecker(BaseChecker):
12+
__implements__ = (IAstroidChecker,)
13+
name = "unnecessary_ellipsis"
14+
msgs = {
15+
"W2301": (
16+
"Unnecessary ellipsis constant",
17+
"unnecessary-ellipsis",
18+
"Used when the ellipsis constant is encountered and can be avoided. "
19+
"A line of code consisting of an ellipsis is unnecessary if "
20+
"there is a docstring on the preceding line or if there is a "
21+
"statement in the same scope.",
22+
)
23+
}
24+
25+
@check_messages("unnecessary-ellipsis")
26+
def visit_const(self, node: nodes.Const) -> None:
27+
"""Check if the ellipsis constant is used unnecessarily.
28+
Emit a warning when:
29+
- A line consisting of an ellipsis is preceded by a docstring.
30+
- A statement exists in the same scope as the ellipsis.
31+
For example: A function consisting of an ellipsis followed by a
32+
return statement on the next line.
33+
"""
34+
if (
35+
node.pytype() == "builtins.Ellipsis"
36+
and not isinstance(node.parent, (nodes.Assign, nodes.AnnAssign, nodes.Call))
37+
and (
38+
len(node.parent.parent.child_sequence(node.parent)) > 1
39+
or (
40+
isinstance(node.parent.parent, (nodes.ClassDef, nodes.FunctionDef))
41+
and (node.parent.parent.doc is not None)
42+
)
43+
)
44+
):
45+
self.add_message("unnecessary-ellipsis", node=node)
46+
47+
48+
def register(linter: PyLinter) -> None:
49+
"""required method to auto register this checker"""
50+
linter.register_checker(EllipsisChecker(linter))

tests/functional/c/class_members.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
class Class:
55
attr: int
6-
...
76

87

98
# `bar` definitely does not exist here, but in a complex scenario,

tests/functional/s/statement_without_effect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Test for statements without effects."""
2-
# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, use-list-literal
2+
# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, unnecessary-ellipsis, use-list-literal
33

44
# +1:[pointless-string-statement]
55
"""inline doc string should use a separated message"""

tests/functional/t/too/too_few_public_methods_excluded.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ class MyJsonEncoder(JSONEncoder):
1111
class InheritedInModule(Control):
1212
"""This class inherits from a class that doesn't have enough mehods,
1313
and its parent is excluded via config, so it doesn't raise."""
14-
...
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Emit a warning when the ellipsis constant is used and can be avoided"""
2+
3+
# pylint: disable=missing-docstring, too-few-public-methods
4+
5+
from typing import List, overload, Union
6+
7+
# Ellipsis and preceding statement
8+
try:
9+
A = 2
10+
except ValueError:
11+
A = 24
12+
... # [unnecessary-ellipsis]
13+
14+
def ellipsis_and_subsequent_statement():
15+
... # [unnecessary-ellipsis]
16+
return 0
17+
18+
# The parent of ellipsis is an assignment
19+
B = ...
20+
C = [..., 1, 2, 3]
21+
22+
# The parent of ellipsis is a call
23+
if "X" is type(...):
24+
...
25+
26+
def docstring_only():
27+
'''In Python, stubbed functions often have a body that contains just a
28+
single `...` constant, indicating that the function doesn't do
29+
anything. However, a stubbed function can also have just a
30+
docstring, and function with a docstring and no body also does
31+
nothing.
32+
'''
33+
34+
35+
# This function has no docstring, so it needs a `...` constant.
36+
def ellipsis_only():
37+
...
38+
39+
40+
def docstring_and_ellipsis():
41+
'''This function doesn't do anything, but it has a docstring, so its
42+
`...` constant is useless clutter.
43+
44+
NEW CHECK: unnecessary-ellipsis
45+
46+
This would check for stubs with both docstrings and `...`
47+
constants, suggesting the removal of the useless `...`
48+
constants
49+
'''
50+
... # [unnecessary-ellipsis]
51+
52+
53+
class DocstringOnly:
54+
'''The same goes for class stubs: docstring, or `...`, but not both.
55+
'''
56+
57+
58+
# No problem
59+
class EllipsisOnly:
60+
...
61+
62+
63+
class DocstringAndEllipsis:
64+
'''Whoops! Mark this one as bad too.
65+
'''
66+
... # [unnecessary-ellipsis]
67+
68+
69+
# Function overloading
70+
@overload
71+
def summarize(data: int) -> float: ...
72+
73+
74+
@overload
75+
def summarize(data: str) -> str: ...
76+
77+
78+
def summarize(data):
79+
if isinstance(data, str):
80+
...
81+
return float(data)
82+
83+
84+
85+
# Method overloading
86+
class MyIntegerList(List[int]):
87+
@overload
88+
def __getitem__(self, index: int) -> int: ...
89+
90+
@overload
91+
def __getitem__(self, index: slice) -> List[int]: ...
92+
93+
def __getitem__(self, index: Union[int, slice]) -> Union[int, List[int]]:
94+
if isinstance(index, int):
95+
...
96+
elif isinstance(index, slice):
97+
...
98+
else:
99+
raise TypeError(...)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unnecessary-ellipsis:12:4:12:7::Unnecessary ellipsis constant:UNDEFINED
2+
unnecessary-ellipsis:15:4:15:7:ellipsis_and_subsequent_statement:Unnecessary ellipsis constant:UNDEFINED
3+
unnecessary-ellipsis:50:4:50:7:docstring_and_ellipsis:Unnecessary ellipsis constant:UNDEFINED
4+
unnecessary-ellipsis:66:4:66:7:DocstringAndEllipsis:Unnecessary ellipsis constant:UNDEFINED

0 commit comments

Comments
 (0)