Skip to content

Commit 1a0fed3

Browse files
committed
simplify checker
1 parent c36c1eb commit 1a0fed3

File tree

8 files changed

+87
-145
lines changed

8 files changed

+87
-145
lines changed

doc/whatsnew/2.13.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ New checkers
2525

2626
Removed checkers
2727
================
28-
* ``non-ascii-name`` has been renamed to ``non-ascii-identifier``
2928

3029
Extensions
3130
==========

pylint/checkers/base.py

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@
7373
import itertools
7474
import re
7575
import sys
76-
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, Optional, Pattern, cast
76+
from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Pattern, cast
7777

7878
import astroid
7979
from astroid import nodes
8080

81-
from pylint import checkers, constants, interfaces
81+
from pylint import constants, interfaces
8282
from pylint import utils as lint_utils
83-
from pylint.checkers import utils
83+
from pylint.checkers import base_checker, utils
8484
from pylint.checkers.utils import (
8585
infer_all,
8686
is_overload_stub,
@@ -456,12 +456,7 @@ def x(self, value): self._x = value
456456
return False
457457

458458

459-
class _BasicChecker(checkers.BaseChecker):
460-
__implements__ = interfaces.IAstroidChecker
461-
name = "basic"
462-
463-
464-
class BasicErrorChecker(_BasicChecker):
459+
class BasicErrorChecker(base_checker._BasicChecker):
465460
msgs = {
466461
"E0100": (
467462
"__init__ method is a generator",
@@ -937,7 +932,7 @@ def _check_redefinition(self, redeftype, node):
937932
)
938933

939934

940-
class BasicChecker(_BasicChecker):
935+
class BasicChecker(base_checker._BasicChecker):
941936
"""checks for :
942937
* doc strings
943938
* number of arguments, local variables, branches, returns and statements in
@@ -1719,31 +1714,7 @@ def _create_naming_options():
17191714
return tuple(name_options)
17201715

17211716

1722-
class NameCheckerHelper:
1723-
"""Class containing functions required by NameChecker and NonAsciiNamesChecker"""
1724-
1725-
def _check_name(
1726-
self, node_type: str, name: str, node: nodes.NodeNG, optional_kwarg: Any = None
1727-
):
1728-
"""Only Dummy function will be overwritten by implementing classes
1729-
1730-
Note: kwarg arguments will be different in implementing classes
1731-
"""
1732-
raise NotImplementedError
1733-
1734-
def _recursive_check_names(self, args: Iterable[nodes.AssignName]):
1735-
"""Check names in a possibly recursive list <arg>"""
1736-
for arg in args:
1737-
if isinstance(arg, nodes.AssignName):
1738-
self._check_name("argument", arg.name, arg)
1739-
else:
1740-
# pylint: disable-next=fixme
1741-
# TODO: Check if we can remove this if branch because of
1742-
# the up to date astroid version used
1743-
self._recursive_check_names(arg.elts)
1744-
1745-
1746-
class NameChecker(_BasicChecker, NameCheckerHelper):
1717+
class NameChecker(base_checker._NameCheckerBase):
17471718
msgs = {
17481719
"C0103": (
17491720
'%s name "%s" doesn\'t conform to %s',
@@ -2043,7 +2014,6 @@ def _name_disallowed_by_regex(self, name: str) -> bool:
20432014
pattern.match(name) for pattern in self._bad_names_rgxs_compiled
20442015
)
20452016

2046-
# pylint: disable-next=arguments-renamed
20472017
def _check_name(self, node_type, name, node, confidence=interfaces.HIGH):
20482018
"""check for a name using the type's regexp"""
20492019

@@ -2092,7 +2062,7 @@ def _name_became_keyword_in_version(name, rules):
20922062
return None
20932063

20942064

2095-
class DocStringChecker(_BasicChecker):
2065+
class DocStringChecker(base_checker._BasicChecker):
20962066
msgs = {
20972067
"C0112": (
20982068
"Empty %s docstring",
@@ -2258,7 +2228,7 @@ def _check_docstring(
22582228
)
22592229

22602230

2261-
class PassChecker(_BasicChecker):
2231+
class PassChecker(base_checker._BasicChecker):
22622232
"""check if the pass statement is really necessary"""
22632233

22642234
msgs = {
@@ -2300,7 +2270,7 @@ def _infer_dunder_doc_attribute(node):
23002270
return docstring.value
23012271

23022272

2303-
class ComparisonChecker(_BasicChecker):
2273+
class ComparisonChecker(base_checker._BasicChecker):
23042274
"""Checks for comparisons
23052275
23062276
- singleton comparison: 'expr == True', 'expr == False' and 'expr == None'

pylint/checkers/base_checker.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
2020
import functools
2121
from inspect import cleandoc
22-
from typing import Any, Optional
22+
from typing import Any, Iterable, Optional
2323

2424
from astroid import nodes
2525

26+
from pylint import interfaces
2627
from pylint.config import OptionsProviderMixIn
2728
from pylint.constants import _MSG_ORDER, WarningScope
2829
from pylint.exceptions import InvalidMessageError
@@ -201,3 +202,32 @@ class BaseTokenChecker(BaseChecker):
201202
def process_tokens(self, tokens):
202203
"""Should be overridden by subclasses."""
203204
raise NotImplementedError()
205+
206+
207+
class _BasicChecker(BaseChecker):
208+
__implements__ = interfaces.IAstroidChecker
209+
name = "basic"
210+
211+
212+
class _NameCheckerBase(_BasicChecker):
213+
"""Class containing functions required by NameChecker and NonAsciiNameChecker"""
214+
215+
def _check_name(
216+
self, node_type: str, name: str, node: nodes.NodeNG, confidence=interfaces.HIGH
217+
):
218+
"""Only Dummy function will be overwritten by implementing classes
219+
220+
Note: kwarg arguments will be different in implementing classes
221+
"""
222+
raise NotImplementedError
223+
224+
def _recursive_check_names(self, args: Iterable[nodes.AssignName]):
225+
"""Check names in a possibly recursive list <arg>"""
226+
for arg in args:
227+
if isinstance(arg, nodes.AssignName):
228+
self._check_name("argument", arg.name, arg)
229+
else:
230+
# pylint: disable-next=fixme
231+
# TODO: Check if we can remove this if branch because of
232+
# the up to date astroid version used
233+
self._recursive_check_names(arg.elts)

pylint/checkers/non_ascii_names.py

Lines changed: 43 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,13 @@
1010
The following checkers are intended to make users are aware of these issues.
1111
"""
1212

13-
import re
1413
import sys
1514
from typing import Optional, Union
1615

1716
from astroid import nodes
1817

19-
import pylint.checkers.base
20-
import pylint.checkers.utils
21-
from pylint import interfaces
22-
from pylint.constants import HUMAN_READABLE_TYPES
23-
from pylint.lint import PyLinter
24-
25-
if sys.version_info >= (3, 8):
26-
from typing import Protocol
27-
else:
28-
from typing_extensions import Protocol
29-
18+
from pylint import constants, interfaces, lint
19+
from pylint.checkers import base_checker, utils
3020

3121
if sys.version_info[:2] >= (3, 7):
3222
# pylint: disable-next=fixme
@@ -40,13 +30,7 @@ def isascii(self: str) -> bool:
4030
return all("\u0000" <= x <= "\u007F" for x in self)
4131

4232

43-
class _AsciiOnlyCheckedNode(Protocol):
44-
_is_ascii_only: bool
45-
46-
47-
class NonAsciiNamesChecker(
48-
pylint.checkers.BaseChecker, pylint.checkers.base.NameCheckerHelper
49-
):
33+
class NonAsciiNameChecker(base_checker._NameCheckerBase):
5034
"""A strict name checker only allowing ASCII
5135
5236
If your programming guideline defines that you are programming in English,
@@ -101,36 +85,12 @@ class NonAsciiNamesChecker(
10185

10286
name = "NonASCII-Checker"
10387

104-
def __init__(self, linter: PyLinter) -> None:
105-
super().__init__(linter)
106-
self._non_ascii_rgx_compiled = re.compile("[^A-Za-z0-9_]")
107-
108-
def _raise_name_warning(
109-
self,
110-
node: nodes.NodeNG,
111-
node_type: str,
112-
name: str,
113-
) -> None:
114-
type_label = HUMAN_READABLE_TYPES.get(node_type, node_type)
115-
args = (type_label.capitalize(), name)
116-
117-
msg = "non-ascii-name"
118-
119-
# Some node types have customized messages
120-
if node_type == "file":
121-
msg = "non-ascii-file-name"
122-
elif node_type == "module":
123-
msg = "non-ascii-module-import"
124-
125-
self.add_message(msg, node=node, args=args, confidence=interfaces.HIGH)
126-
127-
# pylint: disable-next=arguments-renamed
12888
def _check_name(
12989
self,
13090
node_type: str,
131-
name: str,
132-
node: Union[nodes.NodeNG, _AsciiOnlyCheckedNode],
133-
check_string: Optional[str] = None,
91+
name: Optional[str],
92+
node: nodes.NodeNG,
93+
confidence=interfaces.HIGH,
13494
) -> None:
13595
"""Check whether a name is using non-ASCII characters.
13696
@@ -139,31 +99,29 @@ def _check_name(
13999
too many edge cases.
140100
"""
141101

142-
current_state = getattr(node, "_is_ascii_only", True)
143-
144102
if name is None:
145103
# For some nodes i.e. *kwargs from a dict, the name will be empty
146104
return
147105

148-
if check_string is None:
149-
check_string = name
106+
if not (Py37Str(name).isascii()):
107+
type_label = constants.HUMAN_READABLE_TYPES.get(node_type, node_type)
108+
args = (type_label.capitalize(), name)
109+
110+
msg = "non-ascii-name"
150111

151-
if not (
152-
Py37Str(check_string).isascii()
153-
and self._non_ascii_rgx_compiled.match(check_string) is None
154-
):
155-
# Note that we require the `.isascii` method as it is fast and
156-
# handles the complexities of unicode, so we can use simple regex.
157-
self._raise_name_warning(node, node_type, name)
158-
current_state = False
112+
# Some node types have customized messages
113+
if node_type == "file":
114+
msg = "non-ascii-file-name"
115+
elif node_type == "module":
116+
msg = "non-ascii-module-import"
159117

160-
node._is_ascii_only = current_state # pylint: disable=protected-access
118+
self.add_message(msg, node=node, args=args, confidence=confidence)
161119

162-
@pylint.checkers.utils.check_messages("non-ascii-name")
120+
@utils.check_messages("non-ascii-name")
163121
def visit_module(self, node: nodes.Module) -> None:
164122
self._check_name("file", node.name.split(".")[-1], node)
165123

166-
@pylint.checkers.utils.check_messages("non-ascii-name")
124+
@utils.check_messages("non-ascii-name")
167125
def visit_functiondef(
168126
self, node: Union[nodes.FunctionDef, nodes.AsyncFunctionDef]
169127
) -> None:
@@ -189,76 +147,62 @@ def visit_functiondef(
189147

190148
visit_asyncfunctiondef = visit_functiondef
191149

192-
@pylint.checkers.utils.check_messages("non-ascii-name")
150+
@utils.check_messages("non-ascii-name")
193151
def visit_global(self, node: nodes.Global) -> None:
194152
for name in node.names:
195153
self._check_name("const", name, node)
196154

197-
@pylint.checkers.utils.check_messages("non-ascii-name")
155+
@utils.check_messages("non-ascii-name")
198156
def visit_assignname(self, node: nodes.AssignName) -> None:
199157
"""check module level assigned names"""
200158
# The NameChecker from which this Checker originates knows a lot of different
201159
# versions of variables, i.e. constants, inline variables etc.
202160
# To simplify we use only `variable` here, as we don't need to apply different
203161
# rules to different types of variables.
204162
frame = node.frame()
205-
assign_type = node.assign_type()
206-
if isinstance(assign_type, nodes.Comprehension):
207-
# called inlinevar in NamesChecker
208-
self._check_name("variable", node.name, node)
209-
elif isinstance(frame, nodes.Module):
210-
self._check_name("variable", node.name, node)
211-
elif isinstance(frame, nodes.FunctionDef):
212-
if not hasattr(node, "_is_ascii_only"):
213-
# only check if not already done
163+
164+
if isinstance(frame, nodes.FunctionDef):
165+
if node.parent in frame.body:
166+
# Only perform the check if the assigment was done in within the body
167+
# of the function (and not the function parameter definition
168+
# (will be handled in visit_functiondef)
169+
# or within a decorator (handled in visit_call)
214170
self._check_name("variable", node.name, node)
215171
elif isinstance(frame, nodes.ClassDef):
216172
self._check_name("attr", node.name, node)
217173
else:
218-
# Just to make sure we check EVERYTHING (!)
174+
# Possibilities here:
175+
# - isinstance(node.assign_type(), nodes.Comprehension) == inlinevar
176+
# - isinstance(frame, nodes.Module) == variable (constant?)
177+
# - some other kind of assigment missed but still most likely a variable
219178
self._check_name("variable", node.name, node)
220179

221-
@pylint.checkers.utils.check_messages("non-ascii-name")
180+
@utils.check_messages("non-ascii-name")
222181
def visit_classdef(self, node: nodes.ClassDef) -> None:
223182
self._check_name("class", node.name, node)
224183
for attr, anodes in node.instance_attrs.items():
225184
if not any(node.instance_attr_ancestors(attr)):
226185
self._check_name("attr", attr, anodes[0])
227186

228-
def _check_module_import(
229-
self, node: Union[nodes.ImportFrom, nodes.Import], is_import_from: bool = False
230-
):
187+
def _check_module_import(self, node: Union[nodes.ImportFrom, nodes.Import]):
231188
for module_name, alias in node.names:
232-
if alias:
233-
name = alias
234-
else:
235-
if is_import_from and module_name == "*":
236-
# Ignore ``from xyz import *``
237-
continue
238-
name = module_name
239-
240-
if is_import_from or alias:
241-
self._check_name("module", name, node)
242-
else:
243-
# Normal module import can contain "." for which we don't want to check
244-
self._check_name(
245-
"module", name, node, check_string=name.replace(".", "")
246-
)
247-
248-
@pylint.checkers.utils.check_messages("non-ascii-name")
189+
name = alias or module_name
190+
self._check_name("module", name, node)
191+
192+
@utils.check_messages("non-ascii-name")
249193
def visit_import(self, node: nodes.Import) -> None:
250194
self._check_module_import(node)
251195

252-
@pylint.checkers.utils.check_messages("non-ascii-name")
196+
@utils.check_messages("non-ascii-name")
253197
def visit_importfrom(self, node: nodes.ImportFrom) -> None:
254-
self._check_module_import(node, is_import_from=True)
198+
self._check_module_import(node)
255199

256-
@pylint.checkers.utils.check_messages("non-ascii-name")
200+
@utils.check_messages("non-ascii-name")
257201
def visit_call(self, node: nodes.Call) -> None:
258202
# lets check if the used keyword args are correct
259203
for keyword in node.keywords:
260204
self._check_name("argument", keyword.arg, keyword)
261205

262206

263-
def register(linter: PyLinter) -> None:
264-
linter.register_checker(NonAsciiNamesChecker(linter))
207+
def register(linter: lint.PyLinter) -> None:
208+
linter.register_checker(NonAsciiNameChecker(linter))

0 commit comments

Comments
 (0)