Skip to content

Commit

Permalink
New checker - Detect use of unnecessary ellipsis (#5470)
Browse files Browse the repository at this point in the history
Closes #5460
  • Loading branch information
mbyrnepr2 committed Dec 13, 2021
1 parent 35254c2 commit 78eed6a
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 3 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Release date: TBA

Closes #5323

* Add checker ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily.

Closes #5460

* Fixed incorrect classification of Numpy-style docstring as Google-style docstring for
docstrings with property setter documentation.
Docstring classification is now based on the highest amount of matched sections instead
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Summary -- Release highlights

New checkers
============
* ``unnecessary-ellipsis``: Emmitted when the ellipsis constant is used unnecessarily.

Closes #5460

Removed checkers
================
Expand Down
50 changes: 50 additions & 0 deletions pylint/checkers/ellipsis_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Ellipsis checker for Python code
"""
from astroid import nodes

from pylint.checkers import BaseChecker
from pylint.checkers.utils import check_messages
from pylint.interfaces import IAstroidChecker
from pylint.lint import PyLinter


class EllipsisChecker(BaseChecker):
__implements__ = (IAstroidChecker,)
name = "unnecessary_ellipsis"
msgs = {
"W2301": (
"Unnecessary ellipsis constant",
"unnecessary-ellipsis",
"Used when the ellipsis constant is encountered and can be avoided. "
"A line of code consisting of an ellipsis is unnecessary if "
"there is a docstring on the preceding line or if there is a "
"statement in the same scope.",
)
}

@check_messages("unnecessary-ellipsis")
def visit_const(self, node: nodes.Const) -> None:
"""Check if the ellipsis constant is used unnecessarily.
Emit a warning when:
- A line consisting of an ellipsis is preceded by a docstring.
- A statement exists in the same scope as the ellipsis.
For example: A function consisting of an ellipsis followed by a
return statement on the next line.
"""
if (
node.pytype() == "builtins.Ellipsis"
and not isinstance(node.parent, (nodes.Assign, nodes.AnnAssign, nodes.Call))
and (
len(node.parent.parent.child_sequence(node.parent)) > 1
or (
isinstance(node.parent.parent, (nodes.ClassDef, nodes.FunctionDef))
and (node.parent.parent.doc is not None)
)
)
):
self.add_message("unnecessary-ellipsis", node=node)


def register(linter: PyLinter) -> None:
"""required method to auto register this checker"""
linter.register_checker(EllipsisChecker(linter))
1 change: 0 additions & 1 deletion tests/functional/c/class_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

class Class:
attr: int
...


# `bar` definitely does not exist here, but in a complex scenario,
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/s/statement_without_effect.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Test for statements without effects."""
# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, use-list-literal
# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, unnecessary-ellipsis, use-list-literal

# +1:[pointless-string-statement]
"""inline doc string should use a separated message"""
Expand Down
1 change: 0 additions & 1 deletion tests/functional/t/too/too_few_public_methods_excluded.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ class MyJsonEncoder(JSONEncoder):
class InheritedInModule(Control):
"""This class inherits from a class that doesn't have enough mehods,
and its parent is excluded via config, so it doesn't raise."""
...
99 changes: 99 additions & 0 deletions tests/functional/u/unnecessary/unnecessary_ellipsis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Emit a warning when the ellipsis constant is used and can be avoided"""

# pylint: disable=missing-docstring, too-few-public-methods

from typing import List, overload, Union

# Ellipsis and preceding statement
try:
A = 2
except ValueError:
A = 24
... # [unnecessary-ellipsis]

def ellipsis_and_subsequent_statement():
... # [unnecessary-ellipsis]
return 0

# The parent of ellipsis is an assignment
B = ...
C = [..., 1, 2, 3]

# The parent of ellipsis is a call
if "X" is type(...):
...

def docstring_only():
'''In Python, stubbed functions often have a body that contains just a
single `...` constant, indicating that the function doesn't do
anything. However, a stubbed function can also have just a
docstring, and function with a docstring and no body also does
nothing.
'''


# This function has no docstring, so it needs a `...` constant.
def ellipsis_only():
...


def docstring_and_ellipsis():
'''This function doesn't do anything, but it has a docstring, so its
`...` constant is useless clutter.
NEW CHECK: unnecessary-ellipsis
This would check for stubs with both docstrings and `...`
constants, suggesting the removal of the useless `...`
constants
'''
... # [unnecessary-ellipsis]


class DocstringOnly:
'''The same goes for class stubs: docstring, or `...`, but not both.
'''


# No problem
class EllipsisOnly:
...


class DocstringAndEllipsis:
'''Whoops! Mark this one as bad too.
'''
... # [unnecessary-ellipsis]


# Function overloading
@overload
def summarize(data: int) -> float: ...


@overload
def summarize(data: str) -> str: ...


def summarize(data):
if isinstance(data, str):
...
return float(data)



# Method overloading
class MyIntegerList(List[int]):
@overload
def __getitem__(self, index: int) -> int: ...

@overload
def __getitem__(self, index: slice) -> List[int]: ...

def __getitem__(self, index: Union[int, slice]) -> Union[int, List[int]]:
if isinstance(index, int):
...
elif isinstance(index, slice):
...
else:
raise TypeError(...)
4 changes: 4 additions & 0 deletions tests/functional/u/unnecessary/unnecessary_ellipsis.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
unnecessary-ellipsis:12:4:12:7::Unnecessary ellipsis constant:UNDEFINED
unnecessary-ellipsis:15:4:15:7:ellipsis_and_subsequent_statement:Unnecessary ellipsis constant:UNDEFINED
unnecessary-ellipsis:50:4:50:7:docstring_and_ellipsis:Unnecessary ellipsis constant:UNDEFINED
unnecessary-ellipsis:66:4:66:7:DocstringAndEllipsis:Unnecessary ellipsis constant:UNDEFINED

0 comments on commit 78eed6a

Please sign in to comment.