Skip to content

Commit

Permalink
simplify checker
Browse files Browse the repository at this point in the history
  • Loading branch information
CarliJoy committed Jan 7, 2022
1 parent c36c1eb commit 1a0fed3
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 145 deletions.
1 change: 0 additions & 1 deletion doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ New checkers

Removed checkers
================
* ``non-ascii-name`` has been renamed to ``non-ascii-identifier``

Extensions
==========
Expand Down
48 changes: 9 additions & 39 deletions pylint/checkers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@
import itertools
import re
import sys
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, Optional, Pattern, cast
from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Pattern, cast

import astroid
from astroid import nodes

from pylint import checkers, constants, interfaces
from pylint import constants, interfaces
from pylint import utils as lint_utils
from pylint.checkers import utils
from pylint.checkers import base_checker, utils
from pylint.checkers.utils import (
infer_all,
is_overload_stub,
Expand Down Expand Up @@ -456,12 +456,7 @@ def x(self, value): self._x = value
return False


class _BasicChecker(checkers.BaseChecker):
__implements__ = interfaces.IAstroidChecker
name = "basic"


class BasicErrorChecker(_BasicChecker):
class BasicErrorChecker(base_checker._BasicChecker):
msgs = {
"E0100": (
"__init__ method is a generator",
Expand Down Expand Up @@ -937,7 +932,7 @@ def _check_redefinition(self, redeftype, node):
)


class BasicChecker(_BasicChecker):
class BasicChecker(base_checker._BasicChecker):
"""checks for :
* doc strings
* number of arguments, local variables, branches, returns and statements in
Expand Down Expand Up @@ -1719,31 +1714,7 @@ def _create_naming_options():
return tuple(name_options)


class NameCheckerHelper:
"""Class containing functions required by NameChecker and NonAsciiNamesChecker"""

def _check_name(
self, node_type: str, name: str, node: nodes.NodeNG, optional_kwarg: Any = None
):
"""Only Dummy function will be overwritten by implementing classes
Note: kwarg arguments will be different in implementing classes
"""
raise NotImplementedError

def _recursive_check_names(self, args: Iterable[nodes.AssignName]):
"""Check names in a possibly recursive list <arg>"""
for arg in args:
if isinstance(arg, nodes.AssignName):
self._check_name("argument", arg.name, arg)
else:
# pylint: disable-next=fixme
# TODO: Check if we can remove this if branch because of
# the up to date astroid version used
self._recursive_check_names(arg.elts)


class NameChecker(_BasicChecker, NameCheckerHelper):
class NameChecker(base_checker._NameCheckerBase):
msgs = {
"C0103": (
'%s name "%s" doesn\'t conform to %s',
Expand Down Expand Up @@ -2043,7 +2014,6 @@ def _name_disallowed_by_regex(self, name: str) -> bool:
pattern.match(name) for pattern in self._bad_names_rgxs_compiled
)

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

Expand Down Expand Up @@ -2092,7 +2062,7 @@ def _name_became_keyword_in_version(name, rules):
return None


class DocStringChecker(_BasicChecker):
class DocStringChecker(base_checker._BasicChecker):
msgs = {
"C0112": (
"Empty %s docstring",
Expand Down Expand Up @@ -2258,7 +2228,7 @@ def _check_docstring(
)


class PassChecker(_BasicChecker):
class PassChecker(base_checker._BasicChecker):
"""check if the pass statement is really necessary"""

msgs = {
Expand Down Expand Up @@ -2300,7 +2270,7 @@ def _infer_dunder_doc_attribute(node):
return docstring.value


class ComparisonChecker(_BasicChecker):
class ComparisonChecker(base_checker._BasicChecker):
"""Checks for comparisons
- singleton comparison: 'expr == True', 'expr == False' and 'expr == None'
Expand Down
32 changes: 31 additions & 1 deletion pylint/checkers/base_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
import functools
from inspect import cleandoc
from typing import Any, Optional
from typing import Any, Iterable, Optional

from astroid import nodes

from pylint import interfaces
from pylint.config import OptionsProviderMixIn
from pylint.constants import _MSG_ORDER, WarningScope
from pylint.exceptions import InvalidMessageError
Expand Down Expand Up @@ -201,3 +202,32 @@ class BaseTokenChecker(BaseChecker):
def process_tokens(self, tokens):
"""Should be overridden by subclasses."""
raise NotImplementedError()


class _BasicChecker(BaseChecker):
__implements__ = interfaces.IAstroidChecker
name = "basic"


class _NameCheckerBase(_BasicChecker):
"""Class containing functions required by NameChecker and NonAsciiNameChecker"""

def _check_name(
self, node_type: str, name: str, node: nodes.NodeNG, confidence=interfaces.HIGH
):
"""Only Dummy function will be overwritten by implementing classes
Note: kwarg arguments will be different in implementing classes
"""
raise NotImplementedError

def _recursive_check_names(self, args: Iterable[nodes.AssignName]):
"""Check names in a possibly recursive list <arg>"""
for arg in args:
if isinstance(arg, nodes.AssignName):
self._check_name("argument", arg.name, arg)
else:
# pylint: disable-next=fixme
# TODO: Check if we can remove this if branch because of
# the up to date astroid version used
self._recursive_check_names(arg.elts)
142 changes: 43 additions & 99 deletions pylint/checkers/non_ascii_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,13 @@
The following checkers are intended to make users are aware of these issues.
"""

import re
import sys
from typing import Optional, Union

from astroid import nodes

import pylint.checkers.base
import pylint.checkers.utils
from pylint import interfaces
from pylint.constants import HUMAN_READABLE_TYPES
from pylint.lint import PyLinter

if sys.version_info >= (3, 8):
from typing import Protocol
else:
from typing_extensions import Protocol

from pylint import constants, interfaces, lint
from pylint.checkers import base_checker, utils

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


class _AsciiOnlyCheckedNode(Protocol):
_is_ascii_only: bool


class NonAsciiNamesChecker(
pylint.checkers.BaseChecker, pylint.checkers.base.NameCheckerHelper
):
class NonAsciiNameChecker(base_checker._NameCheckerBase):
"""A strict name checker only allowing ASCII
If your programming guideline defines that you are programming in English,
Expand Down Expand Up @@ -101,36 +85,12 @@ class NonAsciiNamesChecker(

name = "NonASCII-Checker"

def __init__(self, linter: PyLinter) -> None:
super().__init__(linter)
self._non_ascii_rgx_compiled = re.compile("[^A-Za-z0-9_]")

def _raise_name_warning(
self,
node: nodes.NodeNG,
node_type: str,
name: str,
) -> None:
type_label = HUMAN_READABLE_TYPES.get(node_type, node_type)
args = (type_label.capitalize(), name)

msg = "non-ascii-name"

# Some node types have customized messages
if node_type == "file":
msg = "non-ascii-file-name"
elif node_type == "module":
msg = "non-ascii-module-import"

self.add_message(msg, node=node, args=args, confidence=interfaces.HIGH)

# pylint: disable-next=arguments-renamed
def _check_name(
self,
node_type: str,
name: str,
node: Union[nodes.NodeNG, _AsciiOnlyCheckedNode],
check_string: Optional[str] = None,
name: Optional[str],
node: nodes.NodeNG,
confidence=interfaces.HIGH,
) -> None:
"""Check whether a name is using non-ASCII characters.
Expand All @@ -139,31 +99,29 @@ def _check_name(
too many edge cases.
"""

current_state = getattr(node, "_is_ascii_only", True)

if name is None:
# For some nodes i.e. *kwargs from a dict, the name will be empty
return

if check_string is None:
check_string = name
if not (Py37Str(name).isascii()):
type_label = constants.HUMAN_READABLE_TYPES.get(node_type, node_type)
args = (type_label.capitalize(), name)

msg = "non-ascii-name"

if not (
Py37Str(check_string).isascii()
and self._non_ascii_rgx_compiled.match(check_string) is None
):
# Note that we require the `.isascii` method as it is fast and
# handles the complexities of unicode, so we can use simple regex.
self._raise_name_warning(node, node_type, name)
current_state = False
# Some node types have customized messages
if node_type == "file":
msg = "non-ascii-file-name"
elif node_type == "module":
msg = "non-ascii-module-import"

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

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

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

visit_asyncfunctiondef = visit_functiondef

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

@pylint.checkers.utils.check_messages("non-ascii-name")
@utils.check_messages("non-ascii-name")
def visit_assignname(self, node: nodes.AssignName) -> None:
"""check module level assigned names"""
# The NameChecker from which this Checker originates knows a lot of different
# versions of variables, i.e. constants, inline variables etc.
# To simplify we use only `variable` here, as we don't need to apply different
# rules to different types of variables.
frame = node.frame()
assign_type = node.assign_type()
if isinstance(assign_type, nodes.Comprehension):
# called inlinevar in NamesChecker
self._check_name("variable", node.name, node)
elif isinstance(frame, nodes.Module):
self._check_name("variable", node.name, node)
elif isinstance(frame, nodes.FunctionDef):
if not hasattr(node, "_is_ascii_only"):
# only check if not already done

if isinstance(frame, nodes.FunctionDef):
if node.parent in frame.body:
# Only perform the check if the assigment was done in within the body
# of the function (and not the function parameter definition
# (will be handled in visit_functiondef)
# or within a decorator (handled in visit_call)
self._check_name("variable", node.name, node)
elif isinstance(frame, nodes.ClassDef):
self._check_name("attr", node.name, node)
else:
# Just to make sure we check EVERYTHING (!)
# Possibilities here:
# - isinstance(node.assign_type(), nodes.Comprehension) == inlinevar
# - isinstance(frame, nodes.Module) == variable (constant?)
# - some other kind of assigment missed but still most likely a variable
self._check_name("variable", node.name, node)

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

def _check_module_import(
self, node: Union[nodes.ImportFrom, nodes.Import], is_import_from: bool = False
):
def _check_module_import(self, node: Union[nodes.ImportFrom, nodes.Import]):
for module_name, alias in node.names:
if alias:
name = alias
else:
if is_import_from and module_name == "*":
# Ignore ``from xyz import *``
continue
name = module_name

if is_import_from or alias:
self._check_name("module", name, node)
else:
# Normal module import can contain "." for which we don't want to check
self._check_name(
"module", name, node, check_string=name.replace(".", "")
)

@pylint.checkers.utils.check_messages("non-ascii-name")
name = alias or module_name
self._check_name("module", name, node)

@utils.check_messages("non-ascii-name")
def visit_import(self, node: nodes.Import) -> None:
self._check_module_import(node)

@pylint.checkers.utils.check_messages("non-ascii-name")
@utils.check_messages("non-ascii-name")
def visit_importfrom(self, node: nodes.ImportFrom) -> None:
self._check_module_import(node, is_import_from=True)
self._check_module_import(node)

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


def register(linter: PyLinter) -> None:
linter.register_checker(NonAsciiNamesChecker(linter))
def register(linter: lint.PyLinter) -> None:
linter.register_checker(NonAsciiNameChecker(linter))
Loading

0 comments on commit 1a0fed3

Please sign in to comment.