Skip to content

Commit

Permalink
Reinstate checks from the python3 checker that are still useful for py3
Browse files Browse the repository at this point in the history
Closes #5025
  • Loading branch information
Pierre-Sassoulas committed Feb 26, 2022
1 parent 5bdd503 commit db038ab
Show file tree
Hide file tree
Showing 16 changed files with 66 additions and 9 deletions.
39 changes: 39 additions & 0 deletions pylint/checkers/python3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

"""This is the remnant of the python3 checker. It was removed because
the transition from python 2 to python3 is behind us, but some checks
are still useful in python3 after all.
See https://github.com/PyCQA/pylint/issues/5025
"""


from pylint import checkers, interfaces
from pylint.checkers import utils


class Python3Checker(checkers.BaseChecker):

__implements__ = interfaces.IAstroidChecker
enabled = True
name = "python3"

msgs = {
"W1641": (
"Implementing __eq__ without also implementing __hash__",
"eq-without-hash",
"Used when a class implements __eq__ but not __hash__. In Python 2, objects "
"get object.__hash__ as the default implementation, in Python 3 objects get "
"None as their default __hash__ implementation if they also implement __eq__.",
),
}

@utils.check_messages("eq-without-hash")
def visit_classdef(self, node):
locals_and_methods = set(node.locals).union(x.name for x in node.mymethods())
if "__eq__" in locals_and_methods and "__hash__" not in locals_and_methods:
self.add_message("eq-without-hash", node=node)


def register(linter):
linter.register_checker(Python3Checker(linter))
6 changes: 3 additions & 3 deletions pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ class DeletedMessage(NamedTuple):
old_names: List[Tuple[str, str]] = []


DELETED_MSGID_PREFIXES = [
16, # the PY3K+ checker, see https://github.com/PyCQA/pylint/pull/4942
DELETED_MSGID_PREFIXES: List[int] = [
# The PY3K+ checker was deleted see https://github.com/PyCQA/pylint/pull/4942
# And then reinstated see https://github.com/PyCQA/pylint/issues/5025
]

DELETED_MESSAGES = [
Expand Down Expand Up @@ -139,7 +140,6 @@ class DeletedMessage(NamedTuple):
DeletedMessage("W1638", "range-builtin-not-iterating"),
DeletedMessage("W1639", "filter-builtin-not-iterating"),
DeletedMessage("W1640", "using-cmp-argument"),
DeletedMessage("W1641", "eq-without-hash"),
DeletedMessage("W1642", "div-method"),
DeletedMessage("W1643", "idiv-method"),
DeletedMessage("W1644", "rdiv-method"),
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/a/access/access_to_protected_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def incorrect_access(self):
return None


class Issue1802(object):
class Issue1802(object): # [eq-without-hash]
"""Test for GitHub issue 1802"""
def __init__(self, value):
self._foo = value
Expand Down
1 change: 1 addition & 0 deletions tests/functional/a/access/access_to_protected_members.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ protected-access:42:6:42:21::Access to a protected member _protected of a client
protected-access:43:0:43:19::Access to a protected member _cls_protected of a client class:UNDEFINED
protected-access:44:6:44:25::Access to a protected member _cls_protected of a client class:UNDEFINED
protected-access:58:19:58:40:Issue1031.incorrect_access:Access to a protected member _protected of a client class:UNDEFINED
eq-without-hash:62:0:101:20:Issue1802:Implementing __eq__ without also implementing __hash__:UNDEFINED
protected-access:72:48:72:63:Issue1802.__eq__:Access to a protected member __private of a client class:UNDEFINED
protected-access:80:32:80:42:Issue1802.not_in_special:Access to a protected member _foo of a client class:UNDEFINED
protected-access:100:32:100:42:Issue1802.__fake_special__:Access to a protected member _foo of a client class:UNDEFINED
Expand Down
11 changes: 11 additions & 0 deletions tests/functional/e/eq_without_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Regression test for #5025"""

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


class AClass: # [eq-without-hash]
def __init__(self) -> None:
self.x = 5

def __eq__(self, other: object) -> bool:
return isinstance(other, AClass) and other.x == self.x
1 change: 1 addition & 0 deletions tests/functional/e/eq_without_hash.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eq-without-hash:6:0:11:62:AClass:Implementing __eq__ without also implementing __hash__:UNDEFINED
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ class MyClass:
pass


class Child(MyClass):
class Child(MyClass): # [eq-without-hash]
def __eq__(self, other): # [missing-function-docstring]
return True
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
eq-without-hash:9:0:11:19:Child:Implementing __eq__ without also implementing __hash__:UNDEFINED
missing-function-docstring:10:4:11:19:Child.__eq__:Missing function or method docstring:INFERENCE
2 changes: 1 addition & 1 deletion tests/functional/n/name/name_preset_snake_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def say_hello(some_argument):
return [some_argument * some_value for some_value in range(10)]


class MyClass: # [invalid-name]
class MyClass: # [invalid-name, eq-without-hash]]
def __init__(self, arg_x):
self._my_secret_x = arg_x

Expand Down
1 change: 1 addition & 0 deletions tests/functional/n/name/name_preset_snake_case.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
invalid-name:6:0:6:13::"Constant name ""SOME_CONSTANT"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]*|__.*__)$' pattern)":HIGH
eq-without-hash:13:0:22:83:MyClass:Implementing __eq__ without also implementing __hash__:UNDEFINED
invalid-name:13:0:22:83:MyClass:"Class name ""MyClass"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH
invalid-name:25:0:26:8:sayHello:"Function name ""sayHello"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]{2,}|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)":HIGH
invalid-name:29:0:31:22:FooEnum:"Class name ""FooEnum"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/n/namePresetCamelCase.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def sayHello(someArgument):
return [someArgument * someValue for someValue in range(10)]


class MyClass: # [invalid-name]
class MyClass: # [invalid-name, eq-without-hash]
def __init__(self, argX):
self._mySecretX = argX

Expand Down
1 change: 1 addition & 0 deletions tests/functional/n/namePresetCamelCase.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
invalid-name:3:0:3:13::"Constant name ""SOME_CONSTANT"" doesn't conform to camelCase naming style ('([^\\W\\dA-Z][^\\W_]*|__.*__)$' pattern)":HIGH
eq-without-hash:10:0:19:79:MyClass:Implementing __eq__ without also implementing __hash__:UNDEFINED
invalid-name:10:0:19:79:MyClass:"Class name ""MyClass"" doesn't conform to camelCase naming style ('[^\\W\\dA-Z][^\\W_]+$' pattern)":HIGH
invalid-name:22:0:23:8:say_hello:"Function name ""say_hello"" doesn't conform to camelCase naming style ('([^\\W\\dA-Z][^\\W_]{2,}|__[^\\W\\dA-Z_]\\w+__)$' pattern)":HIGH
2 changes: 1 addition & 1 deletion tests/functional/p/protected_access_special_methods_off.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# pylint: disable=too-few-public-methods


class Protected:
class Protected: # [eq-without-hash]
"""A class"""

def __init__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
eq-without-hash:9:0:23:40:Protected:Implementing __eq__ without also implementing __hash__:UNDEFINED
unused-private-member:15:8:15:22:Protected.__init__:Unused private member `Protected.__private`:UNDEFINED
protected-access:22:22:22:38:Protected._fake_special_:Access to a protected member _protected of a client class:UNDEFINED
protected-access:23:25:23:40:Protected._fake_special_:Access to a protected member __private of a client class:UNDEFINED
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/p/protected_access_special_methods_on.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# pylint: disable=too-few-public-methods


class Protected:
class Protected: # [eq-without-hash]
"""A class"""

def __init__(self):
Expand Down
1 change: 1 addition & 0 deletions tests/functional/p/protected_access_special_methods_on.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
eq-without-hash:9:0:23:40:Protected:Implementing __eq__ without also implementing __hash__:UNDEFINED
unused-private-member:15:8:15:22:Protected.__init__:Unused private member `Protected.__private`:UNDEFINED
protected-access:18:26:18:42:Protected.__eq__:Access to a protected member _protected of a client class:UNDEFINED
protected-access:22:22:22:38:Protected._fake_special_:Access to a protected member _protected of a client class:UNDEFINED
Expand Down

0 comments on commit db038ab

Please sign in to comment.